Skip to content

Commit 666ad26

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 ea91904 commit 666ad26

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2242
-1499
lines changed

arduino-ide-extension/package.json

-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@
9797
"react-perfect-scrollbar": "^1.5.8",
9898
"react-select": "^5.6.0",
9999
"react-tabs": "^3.1.2",
100-
"react-window": "^1.8.6",
101100
"semver": "^7.3.2",
102101
"string-natural-compare": "^2.0.3",
103102
"temp": "^0.9.1",
@@ -110,7 +109,6 @@
110109
"@octokit/rest": "^18.12.0",
111110
"@types/chai": "^4.2.7",
112111
"@types/mocha": "^5.2.7",
113-
"@types/react-window": "^1.8.5",
114112
"@xhmikosr/downloader": "^13.0.1",
115113
"chai": "^4.2.0",
116114
"cross-env": "^7.0.3",

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

+22-6
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,10 @@ import {
161161
MonitorManagerProxyFactory,
162162
MonitorManagerProxyPath,
163163
} from '../common/protocol';
164-
import { MonacoTextModelService as TheiaMonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
164+
import {
165+
MonacoEditorModelFactory,
166+
MonacoTextModelService as TheiaMonacoTextModelService,
167+
} from '@theia/monaco/lib/browser/monaco-text-model-service';
165168
import { MonacoTextModelService } from './theia/monaco/monaco-text-model-service';
166169
import { ResponseServiceImpl } from './response-service-impl';
167170
import {
@@ -254,7 +257,7 @@ import {
254257
UserFieldsDialog,
255258
UserFieldsDialogProps,
256259
} from './dialogs/user-fields/user-fields-dialog';
257-
import { nls } from '@theia/core/lib/common';
260+
import { nls, ResourceResolver } from '@theia/core/lib/common';
258261
import { IDEUpdaterCommands } from './ide-updater/ide-updater-commands';
259262
import {
260263
IDEUpdater,
@@ -318,6 +321,7 @@ import {
318321
} from './widgets/component-list/filter-renderer';
319322
import { CheckForUpdates } from './contributions/check-for-updates';
320323
import { OutputEditorFactory } from './theia/output/output-editor-factory';
324+
import { OutputEditorFactory as TheiaOutputEditorFactory } from '@theia/output/lib/browser/output-editor-factory';
321325
import { StartupTaskProvider } from '../electron-common/startup-task';
322326
import { DeleteSketch } from './contributions/delete-sketch';
323327
import { UserFields } from './contributions/user-fields';
@@ -358,6 +362,10 @@ import { MonacoEditorMenuContribution as TheiaMonacoEditorMenuContribution } fro
358362
import { UpdateArduinoState } from './contributions/update-arduino-state';
359363
import { TerminalFrontendContribution } from './theia/terminal/terminal-frontend-contribution';
360364
import { TerminalFrontendContribution as TheiaTerminalFrontendContribution } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
365+
import { MonitorResourceProvider } from './serial/monitor/monitor-resource-provider';
366+
import { MonitorEditorFactory } from './serial/monitor/monitor-editor-factory';
367+
import { MonitorEditorModelFactory } from './serial/monitor/monitor-editor-model-factory';
368+
import { MonitorContextMenuService } from './serial/monitor/monitor-context-menu-service';
361369

362370
// Hack to fix copy/cut/paste issue after electron version update in Theia.
363371
// https://github.com/eclipse-theia/theia/issues/12487
@@ -502,9 +510,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
502510
bind(MonitorModel).toSelf().inSingletonScope();
503511
bindViewContribution(bind, MonitorViewContribution);
504512
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
505-
bind(WidgetFactory).toDynamicValue((context) => ({
513+
bind(WidgetFactory).toDynamicValue(({ container }) => ({
506514
id: MonitorWidget.ID,
507-
createWidget: () => context.container.get(MonitorWidget),
515+
createWidget: () => container.get(MonitorWidget),
508516
}));
509517

510518
bind(MonitorManagerProxyFactory).toFactory(
@@ -528,6 +536,15 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
528536
.to(MonitorManagerProxyClientImpl)
529537
.inSingletonScope();
530538

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

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

646662
bind(ArduinoDaemon)
647663
.toDynamicValue((context) =>

arduino-ide-extension/src/browser/arduino-preferences.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ type StrictPreferenceSchemaProperties<T extends object> = {
5858
[p in keyof T]: PreferenceSchemaProperty;
5959
};
6060
type ArduinoPreferenceSchemaProperties =
61-
StrictPreferenceSchemaProperties<ArduinoConfiguration> & { 'arduino.window.zoomLevel': PreferenceSchemaProperty };
61+
StrictPreferenceSchemaProperties<ArduinoConfiguration> & {
62+
'arduino.window.zoomLevel': PreferenceSchemaProperty;
63+
};
6264

6365
const properties: ArduinoPreferenceSchemaProperties = {
6466
'arduino.language.log': {
@@ -287,6 +289,22 @@ const properties: ArduinoPreferenceSchemaProperties = {
287289
),
288290
default: defaultMonitorWidgetDockPanel,
289291
},
292+
'arduino.monitor.stopRenderingLineAfter': {
293+
type: 'number',
294+
markdownDescription: nls.localize(
295+
'arduino/preferences/monitor/stopRenderingLineAfter',
296+
'Performance guard: Stop rendering a line after x characters in the _Serial Monitor_ view. Defaults to 1000. Use -1 to never stop rendering.'
297+
),
298+
default: 1_000,
299+
},
300+
'arduino.monitor.maxLineNumber': {
301+
type: 'number',
302+
markdownDescription: nls.localize(
303+
'arduino/preferences/monitor/maxLineNumber',
304+
'Performance guard: Truncates previous lines after x lines in the _Serial Monitor_ view. Defaults to 1000. Use -1 to never truncate lines.'
305+
),
306+
default: 1_000,
307+
},
290308
};
291309
export const ArduinoConfigSchema: PreferenceSchema = {
292310
type: 'object',
@@ -321,6 +339,8 @@ export interface ArduinoConfiguration {
321339
'arduino.sketch.inoBlueprint': string;
322340
'arduino.checkForUpdates': boolean;
323341
'arduino.monitor.dockPanel': MonitorWidgetDockPanel;
342+
'arduino.monitor.stopRenderingLineAfter': number;
343+
'arduino.monitor.maxLineNumber': number;
324344
}
325345

326346
export const ArduinoPreferences = Symbol('ArduinoPreferences');

arduino-ide-extension/src/browser/contributions/open-sketch-external.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { injectable } from '@theia/core/shared/inversify';
22
import URI from '@theia/core/lib/common/uri';
3+
import { CommandService } from '@theia/core/lib/common/command';
34
import { ArduinoMenus } from '../menu/arduino-menus';
45
import {
56
SketchContribution,
@@ -14,7 +15,7 @@ import { nls } from '@theia/core/lib/common/nls';
1415
export class OpenSketchExternal extends SketchContribution {
1516
override registerCommands(registry: CommandRegistry): void {
1617
registry.registerCommand(OpenSketchExternal.Commands.OPEN_EXTERNAL, {
17-
execute: () => this.openExternal(),
18+
execute: () => this.openExternal(registry),
1819
});
1920
}
2021

@@ -33,7 +34,7 @@ export class OpenSketchExternal extends SketchContribution {
3334
});
3435
}
3536

36-
protected async openExternal(): Promise<void> {
37+
protected async openExternal(commandService: CommandService): Promise<void> {
3738
const uri = await this.sketchServiceClient.currentSketchFile();
3839
if (uri) {
3940
const exists = await this.fileService.exists(new URI(uri));

arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export class MonitorManagerProxyClientImpl
4040
// this event is triggered.
4141
// Ideally a frontend component is connected to this event
4242
// to update the UI.
43-
private readonly onMessagesReceivedEmitter = new Emitter<{
44-
messages: string[];
43+
protected readonly onMessagesReceivedEmitter = new Emitter<{
44+
message: string;
4545
}>();
4646
readonly onMessagesReceived = this.onMessagesReceivedEmitter.event;
4747

@@ -92,8 +92,8 @@ export class MonitorManagerProxyClientImpl
9292
this.webSocket.onerror = () => opened.reject();
9393
this.webSocket.onmessage = (message) => {
9494
const parsedMessage = JSON.parse(message.data);
95-
if (Array.isArray(parsedMessage))
96-
this.onMessagesReceivedEmitter.fire({ messages: parsedMessage });
95+
if (typeof parsedMessage === 'string')
96+
this.onMessagesReceivedEmitter.fire({ message: parsedMessage });
9797
else if (
9898
parsedMessage.command ===
9999
Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE

arduino-ide-extension/src/browser/notification-center.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class NotificationCenter
4646
new Emitter<ProgressMessage>();
4747
private readonly indexUpdateDidFailEmitter =
4848
new Emitter<IndexUpdateDidFailParams>();
49-
private readonly daemonDidStartEmitter = new Emitter<string>();
49+
private readonly daemonDidStartEmitter = new Emitter<number>();
5050
private readonly daemonDidStopEmitter = new Emitter<void>();
5151
private readonly configDidChangeEmitter = new Emitter<ConfigState>();
5252
private readonly platformDidInstallEmitter = new Emitter<{
@@ -136,7 +136,7 @@ export class NotificationCenter
136136
this.indexUpdateDidFailEmitter.fire(params);
137137
}
138138

139-
notifyDaemonDidStart(port: string): void {
139+
notifyDaemonDidStart(port: number): void {
140140
this.daemonDidStartEmitter.fire(port);
141141
}
142142

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,87 @@
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 { ArduinoPreferences } from '../../arduino-preferences';
10+
import { OutputEditorFactory } from '../../theia/output/output-editor-factory';
11+
import { MonitorContextMenuService } from './monitor-context-menu-service';
12+
import { MonitorUri } from './monitor-uri';
13+
14+
// To hide the margin in the editor https://github.com/microsoft/monaco-editor/issues/1960
15+
const noMargin = {
16+
lineNumbers: 'off',
17+
glyphMargin: false,
18+
folding: false,
19+
lineDecorationsWidth: 0,
20+
lineNumbersMinChars: 0,
21+
} as const;
22+
@injectable()
23+
export class MonitorEditorFactory extends OutputEditorFactory {
24+
@inject(MonitorContextMenuService)
25+
private readonly monitorContextMenuService: MonacoContextMenuService;
26+
@inject(ArduinoPreferences)
27+
private readonly preference: ArduinoPreferences;
28+
29+
override readonly scheme: string = MonitorUri.scheme;
30+
31+
protected override createOptions(
32+
model: MonacoEditorModel,
33+
defaultOptions: MonacoEditor.IOptions
34+
): MonacoEditor.IOptions {
35+
return {
36+
...super.createOptions(model, defaultOptions),
37+
...noMargin,
38+
stopRenderingLineAfter:
39+
this.preference['arduino.monitor.stopRenderingLineAfter'],
40+
hideCursorInOverviewRuler: true,
41+
trimAutoWhitespace: false,
42+
maxTokenizationLineLength: 0,
43+
cursorBlinking: 'solid',
44+
cursorStyle: undefined,
45+
domReadOnly: true,
46+
renderLineHighlight: 'none',
47+
renderValidationDecorations: 'off',
48+
fixedOverflowWidgets: false,
49+
acceptSuggestionOnCommitCharacter: false,
50+
acceptSuggestionOnEnter: 'off',
51+
autoClosingBrackets: 'never',
52+
autoClosingDelete: 'never',
53+
autoClosingOvertype: 'never',
54+
autoClosingQuotes: 'never',
55+
autoIndent: 'none',
56+
unusualLineTerminators: 'off',
57+
glyphMargin: false,
58+
lineDecorationsWidth: 0,
59+
disableLayerHinting: true,
60+
disableMonospaceOptimizations: true,
61+
inlineSuggest: { enabled: false },
62+
quickSuggestions: false,
63+
parameterHints: { enabled: false },
64+
suggestOnTriggerCharacters: false,
65+
snippetSuggestions: 'none',
66+
tabCompletion: 'off',
67+
codeLens: false,
68+
lightbulb: { enabled: false },
69+
folding: false,
70+
matchBrackets: 'never',
71+
renderLineHighlightOnlyWhenFocus: true,
72+
inlayHints: { enabled: 'off' },
73+
};
74+
}
75+
76+
protected override *createOverrides(
77+
model: MonacoEditorModel,
78+
defaultOverrides: EditorServiceOverrides
79+
): EditorServiceOverrides {
80+
yield [IContextMenuService, this.monitorContextMenuService];
81+
for (const [identifier, provider] of defaultOverrides) {
82+
if (identifier !== IContextMenuService) {
83+
yield [identifier, provider];
84+
}
85+
}
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { CancellationToken, Resource } from '@theia/core';
2+
import type { SaveOptions } from '@theia/core/lib/browser';
3+
import { injectable } from '@theia/core/shared/inversify';
4+
import type { TextDocumentContentChangeEvent } from '@theia/core/shared/vscode-languageserver-protocol';
5+
import {
6+
MonacoEditorModel,
7+
TextDocumentSaveReason,
8+
} from '@theia/monaco/lib/browser/monaco-editor-model';
9+
import {
10+
OutputEditorModel,
11+
OutputEditorModelFactory,
12+
} from '@theia/output/lib/browser/output-editor-model-factory';
13+
import { MonitorUri } from './monitor-uri';
14+
15+
@injectable()
16+
export class MonitorEditorModelFactory extends OutputEditorModelFactory {
17+
override readonly scheme: string = MonitorUri.scheme;
18+
19+
override createModel(resource: Resource): MonacoEditorModel {
20+
const model = new MonitorEditorModel(resource, this.m2p, this.p2m);
21+
model.autoSave = 'off';
22+
model['ignoreContentChanges'] = true;
23+
return model;
24+
}
25+
}
26+
27+
class MonitorEditorModel extends OutputEditorModel {
28+
override get readOnly(): boolean {
29+
return true;
30+
}
31+
32+
override isReadonly(): boolean {
33+
return true;
34+
}
35+
36+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
37+
protected override setDirty(dirty: boolean): void {
38+
// NOOP
39+
}
40+
41+
protected override markAsDirty(): void {
42+
// NOOP
43+
}
44+
45+
protected override async doSave(
46+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
47+
reason: TextDocumentSaveReason,
48+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
49+
token: CancellationToken,
50+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
51+
overwriteEncoding?: boolean | undefined,
52+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
53+
options?: SaveOptions | undefined
54+
): Promise<void> {
55+
// NOOP
56+
}
57+
58+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
59+
protected override async doSync(token: CancellationToken): Promise<void> {
60+
// NOOP
61+
}
62+
63+
protected override pushContentChanges(
64+
// eslint-disable-next-line unused-imports/no-unused-vars, @typescript-eslint/no-unused-vars
65+
contentChanges: TextDocumentContentChangeEvent[]
66+
): void {
67+
// NOOP
68+
}
69+
}

0 commit comments

Comments
 (0)