feat: can dock the monitor widget to the "right"

Added a new preference (`arduino.monitor.dockPanel`) to specify the
location of the application shell where the _Serial Monitor_ widget
resides. The possible values are `"bottom"` and `"right"`. The default\
value is the `"bottom"`.

The dock panel is per application and not per workspace or window.
However, advanced users can create the `./.vscode/settings.json` and
configure per sketch preference.

Signed-off-by: dankeboy36 <dankeboy36@gmail.com>
This commit is contained in:
dankeboy36 2023-06-17 13:03:59 +02:00 committed by Akos Kitta
parent 94d2962985
commit f6a43254f5
6 changed files with 316 additions and 240 deletions

View File

@ -1,12 +1,15 @@
import { interfaces } from '@theia/core/shared/inversify';
import {
createPreferenceProxy,
PreferenceProxy,
PreferenceService,
PreferenceContribution,
PreferenceProxy,
PreferenceSchema,
PreferenceService,
createPreferenceProxy,
} from '@theia/core/lib/browser/preferences';
import { nls } from '@theia/core/lib/common';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { nls } from '@theia/core/lib/common/nls';
import { PreferenceSchemaProperty } from '@theia/core/lib/common/preferences/preference-schema';
import { interfaces } from '@theia/core/shared/inversify';
import { serialMonitorWidgetLabel } from '../common/nls';
import { CompilerWarningLiterals, CompilerWarnings } from '../common/protocol';
export enum UpdateChannel {
@ -31,7 +34,7 @@ export const ErrorRevealStrategyLiterals = [
*/
'centerIfOutsideViewport',
] as const;
export type ErrorRevealStrategy = typeof ErrorRevealStrategyLiterals[number];
export type ErrorRevealStrategy = (typeof ErrorRevealStrategyLiterals)[number];
export namespace ErrorRevealStrategy {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
export function is(arg: any): arg is ErrorRevealStrategy {
@ -40,225 +43,254 @@ export namespace ErrorRevealStrategy {
export const Default: ErrorRevealStrategy = 'centerIfOutsideViewport';
}
export type MonitorWidgetDockPanel = Extract<
ApplicationShell.Area,
'bottom' | 'right'
>;
export const defaultMonitorWidgetDockPanel: MonitorWidgetDockPanel = 'bottom';
export function isMonitorWidgetDockPanel(
arg: unknown
): arg is MonitorWidgetDockPanel {
return arg === 'bottom' || arg === 'right';
}
type StrictPreferenceSchemaProperties<T extends object> = {
[p in keyof T]: PreferenceSchemaProperty;
};
type ArduinoPreferenceSchemaProperties =
StrictPreferenceSchemaProperties<ArduinoConfiguration> & { 'arduino.window.zoomLevel': PreferenceSchemaProperty };
const properties: ArduinoPreferenceSchemaProperties = {
'arduino.language.log': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/language.log',
"True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default."
),
default: false,
},
'arduino.language.realTimeDiagnostics': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/language.realTimeDiagnostics',
"If true, the language server provides real-time diagnostics when typing in the editor. It's false by default."
),
default: false,
},
'arduino.compile.verbose': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/compile.verbose',
'True for verbose compile output. False by default'
),
default: false,
},
'arduino.compile.experimental': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/compile.experimental',
'True if the IDE should handle multiple compiler errors. False by default'
),
default: false,
},
'arduino.compile.revealRange': {
enum: [...ErrorRevealStrategyLiterals],
description: nls.localize(
'arduino/preferences/compile.revealRange',
"Adjusts how compiler errors are revealed in the editor after a failed verify/upload. Possible values: 'auto': Scroll vertically as necessary and reveal a line. 'center': Scroll vertically as necessary and reveal a line centered vertically. 'top': Scroll vertically as necessary and reveal a line close to the top of the viewport, optimized for viewing a code definition. 'centerIfOutsideViewport': Scroll vertically as necessary and reveal a line centered vertically only if it lies outside the viewport. The default value is '{0}'.",
ErrorRevealStrategy.Default
),
default: ErrorRevealStrategy.Default,
},
'arduino.compile.warnings': {
enum: [...CompilerWarningLiterals],
description: nls.localize(
'arduino/preferences/compile.warnings',
"Tells gcc which warning level to use. It's 'None' by default"
),
default: 'None',
},
'arduino.upload.verbose': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/upload.verbose',
'True for verbose upload output. False by default.'
),
default: false,
},
'arduino.upload.verify': {
type: 'boolean',
default: false,
},
'arduino.window.autoScale': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/window.autoScale',
'True if the user interface automatically scales with the font size.'
),
default: true,
},
'arduino.window.zoomLevel': {
type: 'number',
description: '',
default: 0,
deprecationMessage: nls.localize(
'arduino/preferences/window.zoomLevel/deprecationMessage',
"Deprecated. Use 'window.zoomLevel' instead."
),
},
'arduino.ide.updateChannel': {
type: 'string',
enum: Object.values(UpdateChannel) as UpdateChannel[],
default: UpdateChannel.Stable,
description: nls.localize(
'arduino/preferences/ide.updateChannel',
"Release channel to get updated from. 'stable' is the stable release, 'nightly' is the latest development build."
),
},
'arduino.ide.updateBaseUrl': {
type: 'string',
default: 'https://downloads.arduino.cc/arduino-ide',
description: nls.localize(
'arduino/preferences/ide.updateBaseUrl',
"The base URL where to download updates from. Defaults to 'https://downloads.arduino.cc/arduino-ide'"
),
},
'arduino.board.certificates': {
type: 'string',
description: nls.localize(
'arduino/preferences/board.certificates',
'List of certificates that can be uploaded to boards'
),
default: '',
},
'arduino.sketchbook.showAllFiles': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/sketchbook.showAllFiles',
'True to show all sketch files inside the sketch. It is false by default.'
),
default: false,
},
'arduino.cloud.enabled': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.enabled',
'True if the sketch sync functions are enabled. Defaults to true.'
),
default: true,
},
'arduino.cloud.pull.warn': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.pull.warn',
'True if users should be warned before pulling a cloud sketch. Defaults to true.'
),
default: true,
},
'arduino.cloud.push.warn': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.push.warn',
'True if users should be warned before pushing a cloud sketch. Defaults to true.'
),
default: true,
},
'arduino.cloud.pushpublic.warn': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.pushpublic.warn',
'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.'
),
default: true,
},
'arduino.cloud.sketchSyncEndpoint': {
type: 'string',
description: nls.localize(
'arduino/preferences/cloud.sketchSyncEndpoint',
'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.'
),
default: 'https://api2.arduino.cc/create',
},
'arduino.auth.clientID': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.clientID',
'The OAuth2 client ID.'
),
default: 'C34Ya6ex77jTNxyKWj01lCe1vAHIaPIo',
},
'arduino.auth.domain': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.domain',
'The OAuth2 domain.'
),
default: 'login.arduino.cc',
},
'arduino.auth.audience': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.audience',
'The OAuth2 audience.'
),
default: 'https://api.arduino.cc',
},
'arduino.auth.registerUri': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.registerUri',
'The URI used to register a new user.'
),
default: 'https://auth.arduino.cc/login#/register',
},
'arduino.survey.notification': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/survey.notification',
'True if users should be notified if a survey is available. True by default.'
),
default: true,
},
'arduino.cli.daemon.debug': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cli.daemonDebug',
"Enable debug logging of the gRPC calls to the Arduino CLI. A restart of the IDE is needed for this setting to take effect. It's false by default."
),
default: false,
},
'arduino.checkForUpdates': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/checkForUpdate',
"Receive notifications of available updates for the IDE, boards, and libraries. Requires an IDE restart after change. It's true by default."
),
default: true,
},
'arduino.sketch.inoBlueprint': {
type: 'string',
markdownDescription: nls.localize(
'arduino/preferences/sketch/inoBlueprint',
'Absolute filesystem path to the default `.ino` blueprint file. If specified, the content of the blueprint file will be used for every new sketch created by the IDE. The sketches will be generated with the default Arduino content if not specified. Unaccessible blueprint files are ignored. **A restart of the IDE is needed** for this setting to take effect.'
),
default: undefined,
},
'arduino.monitor.dockPanel': {
type: 'string',
enum: ['bottom', 'right'],
markdownDescription: nls.localize(
'arduino/preferences/monitor/dockPanel',
'The area of the application shell where the _{0}_ widget will reside. It is either "bottom" or "right". It defaults to "{1}".',
serialMonitorWidgetLabel,
defaultMonitorWidgetDockPanel
),
default: defaultMonitorWidgetDockPanel,
},
};
export const ArduinoConfigSchema: PreferenceSchema = {
type: 'object',
properties: {
'arduino.language.log': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/language.log',
"True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default."
),
default: false,
},
'arduino.language.realTimeDiagnostics': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/language.realTimeDiagnostics',
"If true, the language server provides real-time diagnostics when typing in the editor. It's false by default."
),
default: false,
},
'arduino.compile.verbose': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/compile.verbose',
'True for verbose compile output. False by default'
),
default: false,
},
'arduino.compile.experimental': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/compile.experimental',
'True if the IDE should handle multiple compiler errors. False by default'
),
default: false,
},
'arduino.compile.revealRange': {
enum: [...ErrorRevealStrategyLiterals],
description: nls.localize(
'arduino/preferences/compile.revealRange',
"Adjusts how compiler errors are revealed in the editor after a failed verify/upload. Possible values: 'auto': Scroll vertically as necessary and reveal a line. 'center': Scroll vertically as necessary and reveal a line centered vertically. 'top': Scroll vertically as necessary and reveal a line close to the top of the viewport, optimized for viewing a code definition. 'centerIfOutsideViewport': Scroll vertically as necessary and reveal a line centered vertically only if it lies outside the viewport. The default value is '{0}'.",
ErrorRevealStrategy.Default
),
default: ErrorRevealStrategy.Default,
},
'arduino.compile.warnings': {
enum: [...CompilerWarningLiterals],
description: nls.localize(
'arduino/preferences/compile.warnings',
"Tells gcc which warning level to use. It's 'None' by default"
),
default: 'None',
},
'arduino.upload.verbose': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/upload.verbose',
'True for verbose upload output. False by default.'
),
default: false,
},
'arduino.upload.verify': {
type: 'boolean',
default: false,
},
'arduino.window.autoScale': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/window.autoScale',
'True if the user interface automatically scales with the font size.'
),
default: true,
},
'arduino.window.zoomLevel': {
type: 'number',
description: '',
default: 0,
deprecationMessage: nls.localize(
'arduino/preferences/window.zoomLevel/deprecationMessage',
"Deprecated. Use 'window.zoomLevel' instead."
),
},
'arduino.ide.updateChannel': {
type: 'string',
enum: Object.values(UpdateChannel) as UpdateChannel[],
default: UpdateChannel.Stable,
description: nls.localize(
'arduino/preferences/ide.updateChannel',
"Release channel to get updated from. 'stable' is the stable release, 'nightly' is the latest development build."
),
},
'arduino.ide.updateBaseUrl': {
type: 'string',
default: 'https://downloads.arduino.cc/arduino-ide',
description: nls.localize(
'arduino/preferences/ide.updateBaseUrl',
"The base URL where to download updates from. Defaults to 'https://downloads.arduino.cc/arduino-ide'"
),
},
'arduino.board.certificates': {
type: 'string',
description: nls.localize(
'arduino/preferences/board.certificates',
'List of certificates that can be uploaded to boards'
),
default: '',
},
'arduino.sketchbook.showAllFiles': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/sketchbook.showAllFiles',
'True to show all sketch files inside the sketch. It is false by default.'
),
default: false,
},
'arduino.cloud.enabled': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.enabled',
'True if the sketch sync functions are enabled. Defaults to true.'
),
default: true,
},
'arduino.cloud.pull.warn': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.pull.warn',
'True if users should be warned before pulling a cloud sketch. Defaults to true.'
),
default: true,
},
'arduino.cloud.push.warn': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.push.warn',
'True if users should be warned before pushing a cloud sketch. Defaults to true.'
),
default: true,
},
'arduino.cloud.pushpublic.warn': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cloud.pushpublic.warn',
'True if users should be warned before pushing a public sketch to the cloud. Defaults to true.'
),
default: true,
},
'arduino.cloud.sketchSyncEndpoint': {
type: 'string',
description: nls.localize(
'arduino/preferences/cloud.sketchSyncEndpoint',
'The endpoint used to push and pull sketches from a backend. By default it points to Arduino Cloud API.'
),
default: 'https://api2.arduino.cc/create',
},
'arduino.auth.clientID': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.clientID',
'The OAuth2 client ID.'
),
default: 'C34Ya6ex77jTNxyKWj01lCe1vAHIaPIo',
},
'arduino.auth.domain': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.domain',
'The OAuth2 domain.'
),
default: 'login.arduino.cc',
},
'arduino.auth.audience': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.audience',
'The OAuth2 audience.'
),
default: 'https://api.arduino.cc',
},
'arduino.auth.registerUri': {
type: 'string',
description: nls.localize(
'arduino/preferences/auth.registerUri',
'The URI used to register a new user.'
),
default: 'https://auth.arduino.cc/login#/register',
},
'arduino.survey.notification': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/survey.notification',
'True if users should be notified if a survey is available. True by default.'
),
default: true,
},
'arduino.cli.daemon.debug': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/cli.daemonDebug',
"Enable debug logging of the gRPC calls to the Arduino CLI. A restart of the IDE is needed for this setting to take effect. It's false by default."
),
default: false,
},
'arduino.checkForUpdates': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/checkForUpdate',
"Receive notifications of available updates for the IDE, boards, and libraries. Requires an IDE restart after change. It's true by default."
),
default: true,
},
'arduino.sketch.inoBlueprint': {
type: 'string',
markdownDescription: nls.localize(
'arduino/preferences/sketch/inoBlueprint',
'Absolute filesystem path to the default `.ino` blueprint file. If specified, the content of the blueprint file will be used for every new sketch created by the IDE. The sketches will be generated with the default Arduino content if not specified. Unaccessible blueprint files are ignored. **A restart of the IDE is needed** for this setting to take effect.'
),
default: undefined,
},
},
properties,
};
export interface ArduinoConfiguration {
@ -288,6 +320,7 @@ export interface ArduinoConfiguration {
'arduino.cli.daemon.debug': boolean;
'arduino.sketch.inoBlueprint': string;
'arduino.checkForUpdates': boolean;
'arduino.monitor.dockPanel': MonitorWidgetDockPanel;
}
export const ArduinoPreferences = Symbol('ArduinoPreferences');

View File

@ -1,6 +1,10 @@
import * as React from '@theia/core/shared/react';
import { injectable, inject } from '@theia/core/shared/inversify';
import { AbstractViewContribution, codicon } from '@theia/core/lib/browser';
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import {
AbstractViewContribution,
ApplicationShell,
codicon
} from '@theia/core/lib/browser';
import { MonitorWidget } from './monitor-widget';
import { MenuModelRegistry, Command, CommandRegistry } from '@theia/core';
import {
@ -13,6 +17,12 @@ import { nls } from '@theia/core/lib/common';
import { Event } from '@theia/core/lib/common/event';
import { MonitorModel } from '../../monitor-model';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import {
ArduinoPreferences,
defaultMonitorWidgetDockPanel,
isMonitorWidgetDockPanel
} from '../../arduino-preferences';
import { serialMonitorWidgetLabel } from '../../../common/nls';
export namespace SerialMonitor {
export namespace Commands {
@ -50,31 +60,59 @@ export class MonitorViewContribution
static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR =
MonitorWidget.ID + ':toggle-toolbar';
static readonly RESET_SERIAL_MONITOR = MonitorWidget.ID + ':reset';
@inject(MonitorModel)
private readonly model: MonitorModel;
@inject(MonitorManagerProxyClient)
private readonly monitorManagerProxy: MonitorManagerProxyClient;
@inject(ArduinoPreferences)
private readonly arduinoPreferences: ArduinoPreferences;
constructor(
@inject(MonitorModel)
protected readonly model: MonitorModel,
private _panel: ApplicationShell.Area;
@inject(MonitorManagerProxyClient)
protected readonly monitorManagerProxy: MonitorManagerProxyClient
) {
constructor() {
super({
widgetId: MonitorWidget.ID,
widgetName: MonitorWidget.LABEL,
widgetName: serialMonitorWidgetLabel,
defaultWidgetOptions: {
area: 'bottom',
area: defaultMonitorWidgetDockPanel,
},
toggleCommandId: MonitorViewContribution.TOGGLE_SERIAL_MONITOR,
toggleKeybinding: 'CtrlCmd+Shift+M',
});
this._panel = defaultMonitorWidgetDockPanel;
}
@postConstruct()
protected init(): void {
this._panel = this.arduinoPreferences['arduino.monitor.dockPanel'] ?? defaultMonitorWidgetDockPanel;
this.monitorManagerProxy.onMonitorShouldReset(() => this.reset());
this.arduinoPreferences.onPreferenceChanged((event) => {
if (event.preferenceName === 'arduino.monitor.dockPanel' && isMonitorWidgetDockPanel(event.newValue) && event.newValue !== this._panel) {
this._panel = event.newValue;
const widget = this.tryGetWidget();
// reopen at the new position if opened
if (widget) {
widget.close();
this.openView({ activate: true, reveal: true });
}
}
})
}
override get defaultViewOptions(): ApplicationShell.WidgetOptions {
const viewOptions = super.defaultViewOptions;
return {
...viewOptions,
area: this._panel
};
}
override registerMenus(menus: MenuModelRegistry): void {
if (this.toggleCommand) {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
commandId: this.toggleCommand.id,
label: MonitorWidget.LABEL,
label: serialMonitorWidgetLabel,
order: '5',
});
}

View File

@ -27,13 +27,10 @@ import {
} from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { serialMonitorWidgetLabel } from '../../../common/nls';
@injectable()
export class MonitorWidget extends ReactWidget {
static readonly LABEL = nls.localize(
'arduino/common/serialMonitor',
'Serial Monitor'
);
static readonly ID = 'serial-monitor';
protected settings: MonitorSettings = {};
@ -65,7 +62,7 @@ export class MonitorWidget extends ReactWidget {
constructor() {
super();
this.id = MonitorWidget.ID;
this.title.label = MonitorWidget.LABEL;
this.title.label = serialMonitorWidgetLabel;
this.title.iconClass = 'monitor-tab-icon';
this.title.closable = true;
this.scrollOptions = undefined;

View File

@ -20,7 +20,7 @@
.serial-monitor .head {
display: flex;
padding: 0px 5px 5px 0px;
padding: 0px 5px 5px 5px;
height: 27px;
background-color: var(--theia-activityBar-background);
}

View File

@ -19,3 +19,8 @@ export const InstallManually = nls.localize(
'arduino/common/installManually',
'Install Manually'
);
export const serialMonitorWidgetLabel = nls.localize(
'arduino/common/serialMonitor',
'Serial Monitor'
);

View File

@ -381,6 +381,9 @@
"language.log": "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.",
"language.realTimeDiagnostics": "If true, the language server provides real-time diagnostics when typing in the editor. It's false by default.",
"manualProxy": "Manual proxy configuration",
"monitor": {
"dockPanel": "The area of the application shell where the _{0}_ widget will reside. It is either \"bottom\" or \"right\". It defaults to \"{1}\"."
},
"network": "Network",
"newSketchbookLocation": "Select new sketchbook location",
"noCliConfig": "Could not load the CLI configuration",