Skip to content

Commit 11d5afa

Browse files
author
Akos Kitta
committed
feat: use a monaco editor for the monitor output
Closes arduino#105 Signed-off-by: Akos Kitta <[email protected]>
1 parent 669955f commit 11d5afa

11 files changed

+552
-118
lines changed

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ import {
159159
MonitorManagerProxyFactory,
160160
MonitorManagerProxyPath,
161161
} from '../common/protocol';
162-
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
162+
import {
163+
MonacoEditorModelFactory,
164+
MonacoTextModelService as TheiaMonacoTextModelService,
165+
} from '@theia/monaco/lib/browser/monaco-text-model-service';
163166
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
164167
import { ResponseServiceImpl } from './response-service-impl';
165168
import {
@@ -253,7 +256,7 @@ import {
253256
UserFieldsDialog,
254257
UserFieldsDialogProps,
255258
} from './dialogs/user-fields/user-fields-dialog';
256-
import { nls } from '@theia/core/lib/common';
259+
import { nls, ResourceResolver } from '@theia/core/lib/common';
257260
import { IDEUpdaterCommands } from './ide-updater/ide-updater-commands';
258261
import {
259262
IDEUpdater,
@@ -317,6 +320,7 @@ import {
317320
} from './widgets/component-list/filter-renderer';
318321
import { CheckForUpdates } from './contributions/check-for-updates';
319322
import { OutputEditorFactory } from './theia/output/output-editor-factory';
323+
import { OutputEditorFactory as TheiaOutputEditorFactory } from '@theia/output/lib/browser/output-editor-factory';
320324
import { StartupTaskProvider } from '../electron-common/startup-task';
321325
import { DeleteSketch } from './contributions/delete-sketch';
322326
import { UserFields } from './contributions/user-fields';
@@ -354,6 +358,10 @@ import { FileResourceResolver as TheiaFileResourceResolver } from '@theia/filesy
354358
import { StylingParticipant } from '@theia/core/lib/browser/styling-service';
355359
import { MonacoEditorMenuContribution } from './theia/monaco/monaco-menu';
356360
import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } from '@theia/monaco/lib/browser/monaco-menu';
361+
import { MonitorResourceProvider } from './serial/monitor/monitor-resource-provider';
362+
import { MonitorEditorFactory } from './serial/monitor/monitor-editor-factory';
363+
import { MonitorEditorModelFactory } from './serial/monitor/monitor-editor-model-factory';
364+
import { MonitorContextMenuService } from './serial/monitor/monitor-context-menu-service';
357365

358366
// Hack to fix copy/cut/paste issue after electron version update in Theia.
359367
// https://github.com/eclipse-theia/theia/issues/12487
@@ -501,9 +509,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
501509
bind(MonitorModel).toSelf().inSingletonScope();
502510
bindViewContribution(bind, MonitorViewContribution);
503511
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
504-
bind(WidgetFactory).toDynamicValue((context) => ({
512+
bind(WidgetFactory).toDynamicValue(({ container }) => ({
505513
id: MonitorWidget.ID,
506-
createWidget: () => context.container.get(MonitorWidget),
514+
createWidget: () => container.get(MonitorWidget),
507515
}));
508516

509517
bind(MonitorManagerProxyFactory).toFactory(
@@ -527,6 +535,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
527535
.to(MonitorManagerProxyClientImpl)
528536
.inSingletonScope();
529537

538+
bind(MonitorResourceProvider).toSelf().inSingletonScope();
539+
bind(ResourceResolver).toService(MonitorResourceProvider);
540+
bind(MonitorEditorFactory).toSelf().inSingletonScope();
541+
bind(MonacoEditorFactory).toService(MonitorEditorFactory);
542+
bind(MonacoEditorModelFactory)
543+
.to(MonitorEditorModelFactory)
544+
.inSingletonScope();
545+
bind(MonitorContextMenuService).toSelf().inSingletonScope();
546+
530547
bind(WorkspaceService).toSelf().inSingletonScope();
531548
rebind(TheiaWorkspaceService).toService(WorkspaceService);
532549
bind(WorkspaceVariableContribution).toSelf().inSingletonScope();
@@ -639,8 +656,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
639656

640657
// To disable the highlighting of non-unicode characters in the _Output_ view
641658
bind(OutputEditorFactory).toSelf().inSingletonScope();
642-
// Rebind to `TheiaOutputEditorFactory` when https://github.com/eclipse-theia/theia/pull/11615 is available.
643-
rebind(MonacoEditorFactory).toService(OutputEditorFactory);
659+
rebind(TheiaOutputEditorFactory).toService(OutputEditorFactory);
644660

645661
bind(ArduinoDaemon)
646662
.toDynamicValue((context) =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { MenuPath } from '@theia/core/lib/common/menu';
2+
import { injectable } from '@theia/core/shared/inversify';
3+
import { MonacoContextMenuService } from '@theia/monaco/lib/browser/monaco-context-menu';
4+
5+
export namespace MonitorContextMenu {
6+
export const MENU_PATH: MenuPath = ['monitor_context_menu'];
7+
export const TEXT_EDIT_GROUP = [...MENU_PATH, '0_text_edit_group'];
8+
export const WIDGET_GROUP = [...MENU_PATH, '1_widget_group'];
9+
}
10+
11+
@injectable()
12+
export class MonitorContextMenuService extends MonacoContextMenuService {
13+
protected override menuPath(): MenuPath {
14+
return MonitorContextMenu.MENU_PATH;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { inject, injectable } from '@theia/core/shared/inversify';
2+
import { IContextMenuService } from '@theia/monaco-editor-core/esm/vs/platform/contextview/browser/contextView';
3+
import { MonacoContextMenuService } from '@theia/monaco/lib/browser/monaco-context-menu';
4+
import {
5+
EditorServiceOverrides,
6+
MonacoEditor,
7+
} from '@theia/monaco/lib/browser/monaco-editor';
8+
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
9+
import { OutputEditorFactory } from '../../theia/output/output-editor-factory';
10+
import { MonitorContextMenuService } from './monitor-context-menu-service';
11+
import { MonitorUri } from './monitor-uri';
12+
13+
@injectable()
14+
export class MonitorEditorFactory extends OutputEditorFactory {
15+
@inject(MonitorContextMenuService)
16+
private readonly monitorContextMenuService: MonacoContextMenuService;
17+
18+
override readonly scheme: string = MonitorUri.scheme;
19+
20+
protected override createOptions(
21+
model: MonacoEditorModel,
22+
defaultOptions: MonacoEditor.IOptions
23+
): MonacoEditor.IOptions {
24+
return {
25+
...super.createOptions(model, defaultOptions),
26+
// To hide the margin in the editor https://github.com/microsoft/monaco-editor/issues/1960
27+
lineNumbers: 'off',
28+
glyphMargin: false,
29+
folding: false,
30+
lineDecorationsWidth: 0,
31+
lineNumbersMinChars: 0,
32+
};
33+
}
34+
35+
protected override *createOverrides(
36+
model: MonacoEditorModel,
37+
defaultOverrides: EditorServiceOverrides
38+
): EditorServiceOverrides {
39+
yield [IContextMenuService, this.monitorContextMenuService];
40+
for (const [identifier, provider] of defaultOverrides) {
41+
if (identifier !== IContextMenuService) {
42+
yield [identifier, provider];
43+
}
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { injectable } from '@theia/core/shared/inversify';
2+
import { OutputEditorModelFactory } from '@theia/output/lib/browser/output-editor-model-factory';
3+
import { MonitorUri } from './monitor-uri';
4+
5+
@injectable()
6+
export class MonitorEditorModelFactory extends OutputEditorModelFactory {
7+
override readonly scheme: string = MonitorUri.scheme;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Deferred } from '@theia/core/lib/common/promise-util';
2+
import { Resource, ResourceResolver } from '@theia/core/lib/common/resource';
3+
import URI from '@theia/core/lib/common/uri';
4+
import { inject, injectable } from '@theia/core/shared/inversify';
5+
import { IReference } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
6+
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
7+
import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
8+
import { MonitorResource } from './monitor-resource';
9+
import { MonitorUri } from './monitor-uri';
10+
11+
@injectable()
12+
export class MonitorResourceProvider implements ResourceResolver {
13+
readonly resource: MonitorResource;
14+
15+
constructor(
16+
@inject(MonacoTextModelService) textModelService: MonacoTextModelService
17+
) {
18+
const editorModelRef = new Deferred<IReference<MonacoEditorModel>>();
19+
this.resource = new MonitorResource(MonitorUri, editorModelRef);
20+
textModelService
21+
.createModelReference(MonitorUri)
22+
.then((ref) => editorModelRef.resolve(ref));
23+
}
24+
25+
async resolve(uri: URI): Promise<Resource> {
26+
if (this.resource.uri.toString() === uri.toString()) {
27+
return this.resource;
28+
}
29+
// Note: this is totally normal. This is the way Theia loads a resource.
30+
throw new Error(`Cannot handle URI: ${uri.toString()}`);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ResourceReadOptions } from '@theia/core/lib/common/resource';
2+
import { OutputResource } from '@theia/output/lib/browser/output-resource';
3+
4+
export class MonitorResource extends OutputResource {
5+
override async readContents(options?: ResourceReadOptions): Promise<string> {
6+
if (!this._textModel) {
7+
return '';
8+
}
9+
return super.readContents(options);
10+
}
11+
12+
async reset(): Promise<void> {
13+
this.textModel?.setValue('');
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import URI from '@theia/core/lib/common/uri';
2+
3+
export const MonitorUri = new URI('monitor:/arduino');

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

+59-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import * as React from '@theia/core/shared/react';
22
import { injectable, inject } from '@theia/core/shared/inversify';
3-
import { AbstractViewContribution, codicon } from '@theia/core/lib/browser';
3+
import {
4+
AbstractViewContribution,
5+
codicon,
6+
CommonCommands,
7+
Widget,
8+
} from '@theia/core/lib/browser';
49
import { MonitorWidget } from './monitor-widget';
510
import { MenuModelRegistry, Command, CommandRegistry } from '@theia/core';
611
import {
@@ -13,6 +18,8 @@ import { nls } from '@theia/core/lib/common';
1318
import { Event } from '@theia/core/lib/common/event';
1419
import { MonitorModel } from '../../monitor-model';
1520
import { MonitorManagerProxyClient } from '../../../common/protocol';
21+
import { MonitorContextMenu } from './monitor-context-menu-service';
22+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
1623

1724
export namespace SerialMonitor {
1825
export namespace Commands {
@@ -37,7 +44,10 @@ export namespace SerialMonitor {
3744
iconClass: codicon('clear-all'),
3845
},
3946
'vscode/output.contribution/clearOutput.label'
40-
);
47+
) as Command & { label: string };
48+
export const COPY_ALL: Command = {
49+
id: 'serial-monitor-copy-all',
50+
};
4151
}
4252
}
4353

@@ -51,6 +61,9 @@ export class MonitorViewContribution
5161
MonitorWidget.ID + ':toggle-toolbar';
5262
static readonly RESET_SERIAL_MONITOR = MonitorWidget.ID + ':reset';
5363

64+
@inject(ClipboardService)
65+
private readonly clipboardService: ClipboardService;
66+
5467
constructor(
5568
@inject(MonitorModel)
5669
protected readonly model: MonitorModel,
@@ -78,6 +91,17 @@ export class MonitorViewContribution
7891
order: '5',
7992
});
8093
}
94+
menus.registerMenuAction(MonitorContextMenu.TEXT_EDIT_GROUP, {
95+
commandId: CommonCommands.COPY.id,
96+
});
97+
menus.registerMenuAction(MonitorContextMenu.TEXT_EDIT_GROUP, {
98+
commandId: SerialMonitor.Commands.COPY_ALL.id,
99+
label: nls.localizeByDefault('Copy All'),
100+
});
101+
menus.registerMenuAction(MonitorContextMenu.WIDGET_GROUP, {
102+
commandId: SerialMonitor.Commands.CLEAR_OUTPUT.id,
103+
label: nls.localizeByDefault('Clear Output'),
104+
});
81105
}
82106

83107
registerToolbarItems(registry: TabBarToolbarRegistry): void {
@@ -105,12 +129,25 @@ export class MonitorViewContribution
105129

106130
override registerCommands(commands: CommandRegistry): void {
107131
commands.registerCommand(SerialMonitor.Commands.CLEAR_OUTPUT, {
108-
isEnabled: (widget) => widget instanceof MonitorWidget,
109-
isVisible: (widget) => widget instanceof MonitorWidget,
110-
execute: (widget) => {
111-
if (widget instanceof MonitorWidget) {
112-
widget.clearConsole();
132+
isEnabled: (arg) => {
133+
if (arg instanceof Widget) {
134+
return arg instanceof MonitorWidget;
113135
}
136+
return this.shell.currentWidget instanceof MonitorWidget;
137+
},
138+
isVisible: (arg) => {
139+
if (arg instanceof Widget) {
140+
return arg instanceof MonitorWidget;
141+
}
142+
return this.shell.currentWidget instanceof MonitorWidget;
143+
},
144+
execute: () => {
145+
this.widget.then((widget) => {
146+
this.withWidget(widget, (output) => {
147+
output.clearConsole();
148+
return true;
149+
});
150+
});
114151
},
115152
});
116153
if (this.toggleCommand) {
@@ -130,6 +167,14 @@ export class MonitorViewContribution
130167
{ id: MonitorViewContribution.RESET_SERIAL_MONITOR },
131168
{ execute: () => this.reset() }
132169
);
170+
commands.registerCommand(SerialMonitor.Commands.COPY_ALL, {
171+
execute: () => {
172+
const text = this.tryGetWidget()?.text;
173+
if (text) {
174+
this.clipboardService.writeText(text);
175+
}
176+
},
177+
});
133178
}
134179

135180
protected async toggle(): Promise<void> {
@@ -191,4 +236,11 @@ export class MonitorViewContribution
191236
protected async doToggleTimestamp(): Promise<void> {
192237
this.model.toggleTimestamp();
193238
}
239+
240+
private withWidget(
241+
widget: Widget | undefined = this.tryGetWidget(),
242+
predicate: (monitorWidget: MonitorWidget) => boolean = () => true
243+
): boolean | false {
244+
return widget instanceof MonitorWidget ? predicate(widget) : false;
245+
}
194246
}

0 commit comments

Comments
 (0)