From 2df3f46515b1d4494ba18cbfb5850fa289f899a8 Mon Sep 17 00:00:00 2001 From: 502E532E <502E532E@posteo.org> Date: Tue, 22 Apr 2025 22:53:45 +0200 Subject: [PATCH 1/7] 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. --- .../browser/serial/monitor/monitor-utils.ts | 4 +++ .../monitor/monitor-view-contribution.tsx | 25 +++++++++++++++++++ .../browser/serial/monitor/monitor-widget.tsx | 11 ++++++++ .../monitor/serial-monitor-send-output.tsx | 8 +++++- 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts index 41cb4f450..d8e08c010 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts @@ -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(""); +} \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index 98bf53625..b9d8268ae 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -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' + ); } } @@ -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 { @@ -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(), diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index f5c394603..5d8c77b11 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -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 { @@ -47,6 +48,7 @@ export class MonitorWidget extends ReactWidget { */ protected closing = false; protected readonly clearOutputEmitter = new Emitter(); + protected readonly copyOutputEmitter = new Emitter(); @inject(MonitorModel) private readonly monitorModel: MonitorModel; @@ -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; @@ -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(); @@ -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)} /> diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx index ec2327ad5..4d075fd7b 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx @@ -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 }; @@ -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; @@ -130,6 +134,8 @@ export namespace SerialMonitorOutput { readonly monitorModel: MonitorModel; readonly monitorManagerProxy: MonitorManagerProxyClient; readonly clearConsoleEvent: Event; + readonly copyOutputEvent: Event; + readonly clipboardService: ClipboardService; readonly height: number; } From 1274fc311b8879f8c3a4a363723722be917cfb44 Mon Sep 17 00:00:00 2001 From: 502E532E <502E532E@posteo.org> Date: Tue, 22 Apr 2025 23:35:13 +0200 Subject: [PATCH 2/7] 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. --- .../src/test/browser/monitor-utils.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts index cf1025740..4d8a9f486 100644 --- a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts +++ b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { messagesToLines, truncateLines, + linesToMergedStr, } from '../../browser/serial/monitor/monitor-utils'; import { Line } from '../../browser/serial/monitor/serial-monitor-send-output'; import { set, reset } from 'mockdate'; @@ -15,6 +16,7 @@ type TestLine = { charCount: number; maxCharacters?: number; }; + expectedMerged?: string; }; const date = new Date(); @@ -22,6 +24,7 @@ const testLines: TestLine[] = [ { messages: ['Hello'], expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 }, + expectedMerged: 'Hello', }, { messages: ['Hello', 'Dog!'], @@ -36,6 +39,7 @@ const testLines: TestLine[] = [ ], charCount: 10, }, + expectedMerged: 'Hello\nDog!' }, { messages: ['Dog!'], @@ -67,6 +71,7 @@ const testLines: TestLine[] = [ { message: "You're a good boy!", lineLen: 8 }, ], }, + expectedMerged: "Hello Dog!\n Who's a good boy?\nYou're a good boy!", }, { messages: ['boy?\n', "You're a good boy!"], @@ -116,6 +121,7 @@ const testLines: TestLine[] = [ { message: 'Yo', lineLen: 2 }, ], }, + expectedMerged: "Hello Dog!\nWho's a good boy?\nYo", }, ]; @@ -165,6 +171,10 @@ describe('Monitor Utils', () => { }); expect(totalCharCount).to.equal(charCount); } + if (testLine.expectedMerged) { + const merged_str = linesToMergedStr(testLine.expected.lines); + expect(merged_str).to.equal(testLine.expectedMerged); + } }); }); }); From b8e1985e926c96a892c1e61ad306f6dce9912f6e Mon Sep 17 00:00:00 2001 From: 502E532E <502E532E@posteo.org> Date: Sun, 27 Apr 2025 15:30:47 +0200 Subject: [PATCH 3/7] 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 2df3f465). I hope that this will resolve the current Github action failure. --- i18n/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/en.json b/i18n/en.json index e6dbab0c0..5e3d19be2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -435,6 +435,7 @@ "autoscroll": "Autoscroll", "carriageReturn": "Carriage Return", "connecting": "Connecting to '{0}' on '{1}'...", + "copyOutput": "Copy Output", "message": "Message (Enter to send message to '{0}' on '{1}')", "newLine": "New Line", "newLineCarriageReturn": "Both NL & CR", From da8803e7b1f1ff6bbcdb17e25f1c88832bed0089 Mon Sep 17 00:00:00 2001 From: 502E532E <502E532E@posteo.org> Date: Fri, 2 May 2025 23:12:56 +0200 Subject: [PATCH 4/7] Improve readability for serial monitor utils Replace return statement in inline method by direct statement, some minor formatting changes. Does not affect the functionality. --- .../src/browser/serial/monitor/monitor-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts index d8e08c010..9f60f5b90 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts @@ -68,6 +68,6 @@ export function truncateLines( return [lines, charCount]; } -export function linesToMergedStr(lines: Line[]) : string { - return lines.map((line: Line) => {return line.message}).join(""); +export function linesToMergedStr(lines: Line[]): string { + return lines.map((line: Line) => line.message).join(''); } \ No newline at end of file From 503376947c3dff3837f25f4d9142fcbc0c1523bc Mon Sep 17 00:00:00 2001 From: 502E532E <502E532E@posteo.org> Date: Fri, 2 May 2025 23:19:24 +0200 Subject: [PATCH 5/7] 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. --- .../browser/serial/monitor/monitor-utils.ts | 2 +- .../monitor/serial-monitor-send-output.tsx | 4 ++-- .../src/test/browser/monitor-utils.test.ts | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts index 9f60f5b90..eca1756f2 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts @@ -68,6 +68,6 @@ export function truncateLines( return [lines, charCount]; } -export function linesToMergedStr(lines: Line[]): string { +export function joinLines(lines: Line[]): string { return lines.map((line: Line) => line.message).join(''); } \ No newline at end of file diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx index 4d075fd7b..f93b24e53 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-output.tsx @@ -3,7 +3,7 @@ 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, linesToMergedStr } from './monitor-utils'; +import { messagesToLines, truncateLines, joinLines } from './monitor-utils'; import { MonitorManagerProxyClient } from '../../../common/protocol'; import { MonitorModel } from '../../monitor-model'; import { ClipboardService } from '@theia/core/lib/browser/clipboard-service'; @@ -76,7 +76,7 @@ export class SerialMonitorOutput extends React.Component< this.setState({ lines: [], charCount: 0 }) ), this.props.copyOutputEvent(() => - this.props.clipboardService.writeText(linesToMergedStr(this.state.lines)) + this.props.clipboardService.writeText(joinLines(this.state.lines)) ), this.props.monitorModel.onChange(({ property }) => { if (property === 'timestamp') { diff --git a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts index 4d8a9f486..b219ec2ea 100644 --- a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts +++ b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { messagesToLines, truncateLines, - linesToMergedStr, + joinLines, } from '../../browser/serial/monitor/monitor-utils'; import { Line } from '../../browser/serial/monitor/serial-monitor-send-output'; import { set, reset } from 'mockdate'; @@ -16,7 +16,7 @@ type TestLine = { charCount: number; maxCharacters?: number; }; - expectedMerged?: string; + expectedJoined?: string; }; const date = new Date(); @@ -24,7 +24,7 @@ const testLines: TestLine[] = [ { messages: ['Hello'], expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 }, - expectedMerged: 'Hello', + expectedJoined: 'Hello', }, { messages: ['Hello', 'Dog!'], @@ -39,7 +39,7 @@ const testLines: TestLine[] = [ ], charCount: 10, }, - expectedMerged: 'Hello\nDog!' + expectedJoined: 'Hello\nDog!' }, { messages: ['Dog!'], @@ -71,7 +71,7 @@ const testLines: TestLine[] = [ { message: "You're a good boy!", lineLen: 8 }, ], }, - expectedMerged: "Hello Dog!\n Who's a good boy?\nYou're a good boy!", + expectedJoined: "Hello Dog!\n Who's a good boy?\nYou're a good boy!", }, { messages: ['boy?\n', "You're a good boy!"], @@ -121,7 +121,7 @@ const testLines: TestLine[] = [ { message: 'Yo', lineLen: 2 }, ], }, - expectedMerged: "Hello Dog!\nWho's a good boy?\nYo", + expectedJoined: "Hello Dog!\nWho's a good boy?\nYo", }, ]; @@ -171,9 +171,9 @@ describe('Monitor Utils', () => { }); expect(totalCharCount).to.equal(charCount); } - if (testLine.expectedMerged) { - const merged_str = linesToMergedStr(testLine.expected.lines); - expect(merged_str).to.equal(testLine.expectedMerged); + if (testLine.expectedJoined) { + const joined_str = joinLines(testLine.expected.lines); + expect(joined_str).to.equal(testLine.expectedJoined); } }); }); From 5d470766178afdcf9103fdf3c35639086fc63fa8 Mon Sep 17 00:00:00 2001 From: 502E532E <502E532E@posteo.org> Date: Sat, 3 May 2025 11:24:03 +0200 Subject: [PATCH 6/7] 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. --- .../monitor/monitor-view-contribution.tsx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index b9d8268ae..3697363ef 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -52,14 +52,9 @@ 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' - ); + export const COPY_OUTPUT = { + id: 'serial-monitor-copy-output', + }; } } @@ -160,10 +155,8 @@ export class MonitorViewContribution registry.registerItem({ id: SerialMonitor.Commands.COPY_OUTPUT.id, command: SerialMonitor.Commands.COPY_OUTPUT.id, - tooltip: nls.localize( - 'arduino/serial/copyOutput', - 'Copy Output' - ), + icon: codicon('copy'), + tooltip: nls.localize('arduino/serial/copyOutput', 'Copy Output'), }); } From 1a95c3358a9be8f7fdfdbacfd34dcbe838641c30 Mon Sep 17 00:00:00 2001 From: 502E532E <502E532E@posteo.org> Date: Sat, 3 May 2025 11:31:31 +0200 Subject: [PATCH 7/7] 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. --- .../src/browser/serial/monitor/monitor-widget.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 5d8c77b11..ee39ec228 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -109,7 +109,6 @@ export class MonitorWidget extends ReactWidget { copyOutput(): void { this.copyOutputEmitter.fire(); - this.update(); } override dispose(): void {