Skip to content

feat: Add a copy button to serial monitor #2718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Next Next commit
Add a copy output button to serial monitor
If the arduino collects some data that you want to store on your
computer, a rather simple way is to write it to the serial monitor and
copy it to the clipboard. This commit introduces a button that copies
the whole content of the serial monitor to the clipboard to make this
rather simple. It is a new component added to the menu, and does not
change the behaviour of other compontents.
  • Loading branch information
502E532E committed Apr 22, 2025
commit 2df3f46515b1d4494ba18cbfb5850fa289f899a8
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,7 @@ export function truncateLines(
}
return [lines, charCount];
}

export function linesToMergedStr(lines: Line[]) : string {
return lines.map((line: Line) => {return line.message}).join("");
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ export namespace SerialMonitor {
},
'vscode/output.contribution/clearOutput.label'
);
export const COPY_OUTPUT = Command.toLocalizedCommand(
{
id: 'serial-monitor-copy-output',
label: 'Copy Output',
iconClass: codicon('copy'),
},
'arduino/serial/copyOutput'
);
}
}

Expand Down Expand Up @@ -149,6 +157,14 @@ export class MonitorViewContribution
'Clear Output'
),
});
registry.registerItem({
id: SerialMonitor.Commands.COPY_OUTPUT.id,
command: SerialMonitor.Commands.COPY_OUTPUT.id,
tooltip: nls.localize(
'arduino/serial/copyOutput',
'Copy Output'
),
});
}

override registerCommands(commands: CommandRegistry): void {
Expand All @@ -161,6 +177,15 @@ export class MonitorViewContribution
}
},
});
commands.registerCommand(SerialMonitor.Commands.COPY_OUTPUT, {
isEnabled: (widget) => widget instanceof MonitorWidget,
isVisible: (widget) => widget instanceof MonitorWidget,
execute: (widget) => {
if (widget instanceof MonitorWidget) {
widget.copyOutput();
}
},
});
if (this.toggleCommand) {
commands.registerCommand(this.toggleCommand, {
execute: () => this.toggle(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import { MonitorModel } from '../../monitor-model';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { serialMonitorWidgetLabel } from '../../../common/nls';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';

@injectable()
export class MonitorWidget extends ReactWidget {
Expand All @@ -47,6 +48,7 @@ export class MonitorWidget extends ReactWidget {
*/
protected closing = false;
protected readonly clearOutputEmitter = new Emitter<void>();
protected readonly copyOutputEmitter = new Emitter<void>();

@inject(MonitorModel)
private readonly monitorModel: MonitorModel;
Expand All @@ -56,6 +58,8 @@ export class MonitorWidget extends ReactWidget {
private readonly boardsServiceProvider: BoardsServiceProvider;
@inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService;
@inject(ClipboardService)
private readonly clipboardService: ClipboardService;

private readonly toDisposeOnReset: DisposableCollection;

Expand Down Expand Up @@ -102,6 +106,11 @@ export class MonitorWidget extends ReactWidget {
this.clearOutputEmitter.fire(undefined);
this.update();
}

copyOutput(): void {
this.copyOutputEmitter.fire();
this.update();
}

override dispose(): void {
this.toDisposeOnReset.dispose();
Expand Down Expand Up @@ -247,6 +256,8 @@ export class MonitorWidget extends ReactWidget {
monitorModel={this.monitorModel}
monitorManagerProxy={this.monitorManagerProxy}
clearConsoleEvent={this.clearOutputEmitter.event}
copyOutputEvent={this.copyOutputEmitter.event}
clipboardService={this.clipboardService}
height={Math.floor(this.widgetHeight - 50)}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { Event } from '@theia/core/lib/common/event';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { areEqual, FixedSizeList as List } from 'react-window';
import dateFormat from 'dateformat';
import { messagesToLines, truncateLines } from './monitor-utils';
import { messagesToLines, truncateLines, linesToMergedStr } from './monitor-utils';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model';
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';

export type Line = { message: string; timestamp?: Date; lineLen: number };

Expand Down Expand Up @@ -74,6 +75,9 @@ export class SerialMonitorOutput extends React.Component<
this.props.clearConsoleEvent(() =>
this.setState({ lines: [], charCount: 0 })
),
this.props.copyOutputEvent(() =>
this.props.clipboardService.writeText(linesToMergedStr(this.state.lines))
),
this.props.monitorModel.onChange(({ property }) => {
if (property === 'timestamp') {
const { timestamp } = this.props.monitorModel;
Expand Down Expand Up @@ -130,6 +134,8 @@ export namespace SerialMonitorOutput {
readonly monitorModel: MonitorModel;
readonly monitorManagerProxy: MonitorManagerProxyClient;
readonly clearConsoleEvent: Event<void>;
readonly copyOutputEvent: Event<void>;
readonly clipboardService: ClipboardService;
readonly height: number;
}

Expand Down