Skip to content

[WIP] 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 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
10 changes: 10 additions & 0 deletions arduino-ide-extension/src/test/browser/monitor-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -15,13 +16,15 @@ type TestLine = {
charCount: number;
maxCharacters?: number;
};
expectedMerged?: string;
};

const date = new Date();
const testLines: TestLine[] = [
{
messages: ['Hello'],
expected: { lines: [{ message: 'Hello', lineLen: 5 }], charCount: 5 },
expectedMerged: 'Hello',
},
{
messages: ['Hello', 'Dog!'],
Expand All @@ -36,6 +39,7 @@ const testLines: TestLine[] = [
],
charCount: 10,
},
expectedMerged: 'Hello\nDog!'
},
{
messages: ['Dog!'],
Expand Down Expand Up @@ -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!"],
Expand Down Expand Up @@ -116,6 +121,7 @@ const testLines: TestLine[] = [
{ message: 'Yo', lineLen: 2 },
],
},
expectedMerged: "Hello Dog!\nWho's a good boy?\nYo",
},
];

Expand Down Expand Up @@ -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);
}
});
});
});
Expand Down
Loading