Skip to content

Commit 93d27ea

Browse files
authored
feat: add a copy button to serial monitor (#2718)
* 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. * Test merging lines to str in serial monitor utils Adds a test for merging one or more lines to a single string. It is supposed to just concatenate the content of the lines, without doing anything else. This method is used when copying the serial monitor content to the clipboard. * Add copy output translation key This serves as an addition to the previous commits. It is the result of running `yarn i18n:generate` on the state after adding the copy output button to the serial monitor (see 2df3f46). I hope that this will resolve the current Github action failure. * Improve readability for serial monitor utils Replace return statement in inline method by direct statement, some minor formatting changes. Does not affect the functionality. * Rename linesToMergedStr in monitor-utils Renames the method linesToMergedStr to joinLines in the serial monitor utils. This brings the name more in line with truncateLines. No functionality changes. * Move label and icon registration for copy serial Moves the registration of the label and icon for the copy output button of the serial monitor to the toolbar item registration. Before, it happened at the command registration, but is not necessary at this level, as the icon and label are meant for the toolbar button only. * Do not update widget when copying output No longer updates the serial monitor output after its content is copied. Copying the content does not change anything for the view, so there is no need to update.
1 parent 155f0ae commit 93d27ea

File tree

6 files changed

+50
-1
lines changed

6 files changed

+50
-1
lines changed

arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,7 @@ export function truncateLines(
6767
}
6868
return [lines, charCount];
6969
}
70+
71+
export function joinLines(lines: Line[]): string {
72+
return lines.map((line: Line) => line.message).join('');
73+
}

arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export namespace SerialMonitor {
5252
},
5353
'vscode/output.contribution/clearOutput.label'
5454
);
55+
export const COPY_OUTPUT = {
56+
id: 'serial-monitor-copy-output',
57+
};
5558
}
5659
}
5760

@@ -149,6 +152,12 @@ export class MonitorViewContribution
149152
'Clear Output'
150153
),
151154
});
155+
registry.registerItem({
156+
id: SerialMonitor.Commands.COPY_OUTPUT.id,
157+
command: SerialMonitor.Commands.COPY_OUTPUT.id,
158+
icon: codicon('copy'),
159+
tooltip: nls.localize('arduino/serial/copyOutput', 'Copy Output'),
160+
});
152161
}
153162

154163
override registerCommands(commands: CommandRegistry): void {
@@ -161,6 +170,15 @@ export class MonitorViewContribution
161170
}
162171
},
163172
});
173+
commands.registerCommand(SerialMonitor.Commands.COPY_OUTPUT, {
174+
isEnabled: (widget) => widget instanceof MonitorWidget,
175+
isVisible: (widget) => widget instanceof MonitorWidget,
176+
execute: (widget) => {
177+
if (widget instanceof MonitorWidget) {
178+
widget.copyOutput();
179+
}
180+
},
181+
});
164182
if (this.toggleCommand) {
165183
commands.registerCommand(this.toggleCommand, {
166184
execute: () => this.toggle(),

arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
import { MonitorModel } from '../../monitor-model';
2929
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
3030
import { serialMonitorWidgetLabel } from '../../../common/nls';
31+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
3132

3233
@injectable()
3334
export class MonitorWidget extends ReactWidget {
@@ -47,6 +48,7 @@ export class MonitorWidget extends ReactWidget {
4748
*/
4849
protected closing = false;
4950
protected readonly clearOutputEmitter = new Emitter<void>();
51+
protected readonly copyOutputEmitter = new Emitter<void>();
5052

5153
@inject(MonitorModel)
5254
private readonly monitorModel: MonitorModel;
@@ -56,6 +58,8 @@ export class MonitorWidget extends ReactWidget {
5658
private readonly boardsServiceProvider: BoardsServiceProvider;
5759
@inject(FrontendApplicationStateService)
5860
private readonly appStateService: FrontendApplicationStateService;
61+
@inject(ClipboardService)
62+
private readonly clipboardService: ClipboardService;
5963

6064
private readonly toDisposeOnReset: DisposableCollection;
6165

@@ -102,6 +106,10 @@ export class MonitorWidget extends ReactWidget {
102106
this.clearOutputEmitter.fire(undefined);
103107
this.update();
104108
}
109+
110+
copyOutput(): void {
111+
this.copyOutputEmitter.fire();
112+
}
105113

106114
override dispose(): void {
107115
this.toDisposeOnReset.dispose();
@@ -247,6 +255,8 @@ export class MonitorWidget extends ReactWidget {
247255
monitorModel={this.monitorModel}
248256
monitorManagerProxy={this.monitorManagerProxy}
249257
clearConsoleEvent={this.clearOutputEmitter.event}
258+
copyOutputEvent={this.copyOutputEmitter.event}
259+
clipboardService={this.clipboardService}
250260
height={Math.floor(this.widgetHeight - 50)}
251261
/>
252262
</div>

arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { Event } from '@theia/core/lib/common/event';
33
import { DisposableCollection } from '@theia/core/lib/common/disposable';
44
import { areEqual, FixedSizeList as List } from 'react-window';
55
import dateFormat from 'dateformat';
6-
import { messagesToLines, truncateLines } from './monitor-utils';
6+
import { messagesToLines, truncateLines, joinLines } from './monitor-utils';
77
import { MonitorManagerProxyClient } from '../../../common/protocol';
88
import { MonitorModel } from '../../monitor-model';
9+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
910

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

@@ -74,6 +75,9 @@ export class SerialMonitorOutput extends React.Component<
7475
this.props.clearConsoleEvent(() =>
7576
this.setState({ lines: [], charCount: 0 })
7677
),
78+
this.props.copyOutputEvent(() =>
79+
this.props.clipboardService.writeText(joinLines(this.state.lines))
80+
),
7781
this.props.monitorModel.onChange(({ property }) => {
7882
if (property === 'timestamp') {
7983
const { timestamp } = this.props.monitorModel;
@@ -130,6 +134,8 @@ export namespace SerialMonitorOutput {
130134
readonly monitorModel: MonitorModel;
131135
readonly monitorManagerProxy: MonitorManagerProxyClient;
132136
readonly clearConsoleEvent: Event<void>;
137+
readonly copyOutputEvent: Event<void>;
138+
readonly clipboardService: ClipboardService;
133139
readonly height: number;
134140
}
135141

arduino-ide-extension/src/test/browser/monitor-utils.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'chai';
22
import {
33
messagesToLines,
44
truncateLines,
5+
joinLines,
56
} from '../../browser/serial/monitor/monitor-utils';
67
import { Line } from '../../browser/serial/monitor/serial-monitor-send-output';
78
import { set, reset } from 'mockdate';
@@ -15,13 +16,15 @@ type TestLine = {
1516
charCount: number;
1617
maxCharacters?: number;
1718
};
19+
expectedJoined?: string;
1820
};
1921

2022
const date = new Date();
2123
const testLines: TestLine[] = [
2224
{
2325
messages: ['Hello'],
2426
expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 },
27+
expectedJoined: 'Hello',
2528
},
2629
{
2730
messages: ['Hello', 'Dog!'],
@@ -36,6 +39,7 @@ const testLines: TestLine[] = [
3639
],
3740
charCount: 10,
3841
},
42+
expectedJoined: 'Hello\nDog!'
3943
},
4044
{
4145
messages: ['Dog!'],
@@ -67,6 +71,7 @@ const testLines: TestLine[] = [
6771
{ message: "You're a good boy!", lineLen: 8 },
6872
],
6973
},
74+
expectedJoined: "Hello Dog!\n Who's a good boy?\nYou're a good boy!",
7075
},
7176
{
7277
messages: ['boy?\n', "You're a good boy!"],
@@ -116,6 +121,7 @@ const testLines: TestLine[] = [
116121
{ message: 'Yo', lineLen: 2 },
117122
],
118123
},
124+
expectedJoined: "Hello Dog!\nWho's a good boy?\nYo",
119125
},
120126
];
121127

@@ -165,6 +171,10 @@ describe('Monitor Utils', () => {
165171
});
166172
expect(totalCharCount).to.equal(charCount);
167173
}
174+
if (testLine.expectedJoined) {
175+
const joined_str = joinLines(testLine.expected.lines);
176+
expect(joined_str).to.equal(testLine.expectedJoined);
177+
}
168178
});
169179
});
170180
});

i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@
435435
"autoscroll": "Autoscroll",
436436
"carriageReturn": "Carriage Return",
437437
"connecting": "Connecting to '{0}' on '{1}'...",
438+
"copyOutput": "Copy Output",
438439
"message": "Message (Enter to send message to '{0}' on '{1}')",
439440
"newLine": "New Line",
440441
"newLineCarriageReturn": "Both NL & CR",

0 commit comments

Comments
 (0)