#714: UX improvements of the Arduino LS in IDE2

- Debounced the connectivity status update.
 - Silent the output channel for the Arduino LS.
 - Delay the problem markers update with 500ms.
 - Do not update the status bar on every `keypress` event.
 - Debounced the tab-bar toolbar updates when typing in editor.
 - Fixed electron menu contribution binding.
 - Aligned the editor widget factory's API to Theia.
 - Set the zoom level when the app is ready (Closes #1244)
 - Fixed event listener leak (Closes #1062)

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2022-07-22 12:02:31 +02:00 committed by Akos Kitta
parent 90d2950bdd
commit b62f3dec84
12 changed files with 129 additions and 35 deletions

View File

@ -104,7 +104,7 @@ export class ArduinoFrontendContribution
} }
} }
}); });
this.appStateService.reachedState('initialized_layout').then(() => this.appStateService.reachedState('ready').then(() =>
this.arduinoPreferences.ready.then(() => { this.arduinoPreferences.ready.then(() => {
const webContents = remote.getCurrentWebContents(); const webContents = remote.getCurrentWebContents();
const zoomLevel = this.arduinoPreferences.get( const zoomLevel = this.arduinoPreferences.get(

View File

@ -50,13 +50,17 @@ import {
ApplicationShell as TheiaApplicationShell, ApplicationShell as TheiaApplicationShell,
ShellLayoutRestorer as TheiaShellLayoutRestorer, ShellLayoutRestorer as TheiaShellLayoutRestorer,
CommonFrontendContribution as TheiaCommonFrontendContribution, CommonFrontendContribution as TheiaCommonFrontendContribution,
DockPanelRenderer as TheiaDockPanelRenderer,
TabBarRendererFactory, TabBarRendererFactory,
ContextMenuRenderer, ContextMenuRenderer,
createTreeContainer, createTreeContainer,
TreeWidget, TreeWidget,
} from '@theia/core/lib/browser'; } from '@theia/core/lib/browser';
import { MenuContribution } from '@theia/core/lib/common/menu'; import { MenuContribution } from '@theia/core/lib/common/menu';
import { ApplicationShell } from './theia/core/application-shell'; import {
ApplicationShell,
DockPanelRenderer,
} from './theia/core/application-shell';
import { FrontendApplication } from './theia/core/frontend-application'; import { FrontendApplication } from './theia/core/frontend-application';
import { import {
BoardsConfigDialog, BoardsConfigDialog,
@ -315,6 +319,8 @@ import { OpenBoardsConfig } from './contributions/open-boards-config';
import { SketchFilesTracker } from './contributions/sketch-files-tracker'; import { SketchFilesTracker } from './contributions/sketch-files-tracker';
import { MonacoThemeServiceIsReady } from './utils/window'; import { MonacoThemeServiceIsReady } from './utils/window';
import { Deferred } from '@theia/core/lib/common/promise-util'; import { Deferred } from '@theia/core/lib/common/promise-util';
import { StatusBarImpl } from './theia/core/status-bar';
import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser';
const registerArduinoThemes = () => { const registerArduinoThemes = () => {
const themes: MonacoThemeJson[] = [ const themes: MonacoThemeJson[] = [
@ -583,7 +589,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Disabled reference counter in the editor manager to avoid opening the same editor (with different opener options) multiple times. // Disabled reference counter in the editor manager to avoid opening the same editor (with different opener options) multiple times.
bind(EditorManager).toSelf().inSingletonScope(); bind(EditorManager).toSelf().inSingletonScope();
rebind(TheiaEditorManager).to(EditorManager); rebind(TheiaEditorManager).toService(EditorManager);
// replace search icon // replace search icon
rebind(TheiaSearchInWorkspaceFactory) rebind(TheiaSearchInWorkspaceFactory)
@ -822,6 +828,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(WidgetManager).toSelf().inSingletonScope(); bind(WidgetManager).toSelf().inSingletonScope();
rebind(TheiaWidgetManager).toService(WidgetManager); rebind(TheiaWidgetManager).toService(WidgetManager);
// To avoid running a status bar update on every single `keypress` event from the editor.
bind(StatusBarImpl).toSelf().inSingletonScope();
rebind(TheiaStatusBarImpl).toService(StatusBarImpl);
// Debounced update for the tab-bar toolbar when typing in the editor.
bind(DockPanelRenderer).toSelf();
rebind(TheiaDockPanelRenderer).toService(DockPanelRenderer);
// Preferences // Preferences
bindArduinoPreferences(bind); bindArduinoPreferences(bind);

View File

@ -145,6 +145,7 @@ export class InoLanguage extends SketchContribution {
name: name ? `"${name}"` : undefined, name: name ? `"${name}"` : undefined,
}, },
realTimeDiagnostics, realTimeDiagnostics,
silentOutput: true,
} }
), ),
]); ]);

View File

@ -10,28 +10,35 @@ import {
import { import {
ApplicationShell as TheiaApplicationShell, ApplicationShell as TheiaApplicationShell,
DockPanel, DockPanel,
DockPanelRenderer as TheiaDockPanelRenderer,
Panel, Panel,
TabBar,
Widget, Widget,
SHELL_TABBAR_CONTEXT_MENU,
} from '@theia/core/lib/browser'; } from '@theia/core/lib/browser';
import { Sketch } from '../../../common/protocol'; import { Sketch } from '../../../common/protocol';
import { SaveAsSketch } from '../../contributions/save-as-sketch'; import { SaveAsSketch } from '../../contributions/save-as-sketch';
import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl'; import {
CurrentSketch,
SketchesServiceClientImpl,
} from '../../../common/protocol/sketches-service-client-impl';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { ToolbarAwareTabBar } from './tab-bars';
@injectable() @injectable()
export class ApplicationShell extends TheiaApplicationShell { export class ApplicationShell extends TheiaApplicationShell {
@inject(CommandService) @inject(CommandService)
protected readonly commandService: CommandService; private readonly commandService: CommandService;
@inject(MessageService) @inject(MessageService)
protected readonly messageService: MessageService; private readonly messageService: MessageService;
@inject(SketchesServiceClientImpl) @inject(SketchesServiceClientImpl)
protected readonly sketchesServiceClient: SketchesServiceClientImpl; private readonly sketchesServiceClient: SketchesServiceClientImpl;
@inject(ConnectionStatusService) @inject(ConnectionStatusService)
protected readonly connectionStatusService: ConnectionStatusService; private readonly connectionStatusService: ConnectionStatusService;
protected override track(widget: Widget): void { protected override track(widget: Widget): void {
super.track(widget); super.track(widget);
@ -43,7 +50,7 @@ export class ApplicationShell extends TheiaApplicationShell {
this.sketchesServiceClient.currentSketch().then((sketch) => { this.sketchesServiceClient.currentSketch().then((sketch) => {
if (CurrentSketch.isValid(sketch)) { if (CurrentSketch.isValid(sketch)) {
if (!this.isSketchFile(widget.editor.uri, sketch.uri)) { if (!this.isSketchFile(widget.editor.uri, sketch.uri)) {
return; return;
} }
if (Sketch.isInSketch(widget.editor.uri, sketch)) { if (Sketch.isInSketch(widget.editor.uri, sketch)) {
widget.title.closable = false; widget.title.closable = false;
@ -54,11 +61,11 @@ export class ApplicationShell extends TheiaApplicationShell {
} }
private isSketchFile(uri: URI, sketchUriString: string): boolean { private isSketchFile(uri: URI, sketchUriString: string): boolean {
const sketchUri = new URI(sketchUriString); const sketchUri = new URI(sketchUriString);
if (uri.parent.isEqual(sketchUri)) { if (uri.parent.isEqual(sketchUri)) {
return true; return true;
} }
return false; return false;
} }
override async addWidget( override async addWidget(
@ -120,15 +127,41 @@ export class ApplicationShell extends TheiaApplicationShell {
} }
} }
export class DockPanelRenderer extends TheiaDockPanelRenderer {
override createTabBar(): TabBar<Widget> {
const renderer = this.tabBarRendererFactory();
// `ToolbarAwareTabBar` is from IDE2 and not from Theia. Check the imports.
const tabBar = new ToolbarAwareTabBar(
this.tabBarToolbarRegistry,
this.tabBarToolbarFactory,
this.breadcrumbsRendererFactory,
{
renderer,
// Scroll bar options
handlers: ['drag-thumb', 'keyboard', 'wheel', 'touch'],
useBothWheelAxes: true,
scrollXMarginOffset: 4,
suppressScrollY: true,
}
);
this.tabBarClasses.forEach((c) => tabBar.addClass(c));
renderer.tabBar = tabBar;
tabBar.disposed.connect(() => renderer.dispose());
renderer.contextMenuPath = SHELL_TABBAR_CONTEXT_MENU;
tabBar.currentChanged.connect(this.onCurrentTabChanged, this);
return tabBar;
}
}
const originalHandleEvent = DockPanel.prototype.handleEvent; const originalHandleEvent = DockPanel.prototype.handleEvent;
DockPanel.prototype.handleEvent = function (event) { DockPanel.prototype.handleEvent = function (event) {
switch (event.type) { switch (event.type) {
case 'p-dragenter': case 'p-dragenter':
case 'p-dragleave': case 'p-dragleave':
case 'p-dragover': case 'p-dragover':
case 'p-drop': case 'p-drop':
return; return;
} }
originalHandleEvent.bind(this)(event); originalHandleEvent.bind(this)(event);
}; };

View File

@ -13,6 +13,7 @@ import {
import { ArduinoDaemon } from '../../../common/protocol'; import { ArduinoDaemon } from '../../../common/protocol';
import { NotificationCenter } from '../../notification-center'; import { NotificationCenter } from '../../notification-center';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import debounce = require('lodash.debounce');
@injectable() @injectable()
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService { export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
@ -36,10 +37,11 @@ export class FrontendConnectionStatusService extends TheiaFrontendConnectionStat
this.notificationCenter.onDaemonDidStop( this.notificationCenter.onDaemonDidStop(
() => (this.connectedPort = undefined) () => (this.connectedPort = undefined)
); );
this.wsConnectionProvider.onIncomingMessageActivity(() => { const refresh = debounce(() => {
this.updateStatus(!!this.connectedPort); this.updateStatus(!!this.connectedPort);
this.schedulePing(); this.schedulePing();
}); }, this.options.offlineTimeout - 10);
this.wsConnectionProvider.onIncomingMessageActivity(() => refresh());
} }
} }

View File

@ -0,0 +1,13 @@
import { injectable } from '@theia/core/shared/inversify';
import { StatusBarImpl as TheiaStatusBarImpl } from '@theia/core/lib/browser';
@injectable()
export class StatusBarImpl extends TheiaStatusBarImpl {
override async removeElement(id: string): Promise<void> {
await this.ready;
if (this.entries.delete(id)) {
// Unlike Theia, IDE2 updates the status bar only if the element to remove was among the entries. Otherwise, it's a NOOP.
this.update();
}
}
}

View File

@ -1,8 +1,13 @@
import { TabBar } from '@theia/core/shared/@phosphor/widgets'; import type { TabBar } from '@theia/core/shared/@phosphor/widgets';
import { Saveable } from '@theia/core/lib/browser/saveable'; import { Saveable } from '@theia/core/lib/browser/saveable';
import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars'; import {
TabBarRenderer as TheiaTabBarRenderer,
ToolbarAwareTabBar as TheiaToolbarAwareTabBar,
} from '@theia/core/lib/browser/shell/tab-bars';
import debounce = require('lodash.debounce');
export class TabBarRenderer extends TheiaTabBarRenderer { export class TabBarRenderer extends TheiaTabBarRenderer {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
override createTabClass(data: TabBar.IRenderData<any>): string { override createTabClass(data: TabBar.IRenderData<any>): string {
let className = super.createTabClass(data); let className = super.createTabClass(data);
if (!data.title.closable && Saveable.isDirty(data.title.owner)) { if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
@ -16,3 +21,16 @@ export class TabBarRenderer extends TheiaTabBarRenderer {
// Context menus are empty, so they have been removed // Context menus are empty, so they have been removed
}; };
} }
export class ToolbarAwareTabBar extends TheiaToolbarAwareTabBar {
protected override async updateBreadcrumbs(): Promise<void> {
// NOOP
// IDE2 does not use breadcrumbs.
}
private readonly doUpdateToolbar = debounce(() => super.updateToolbar(), 500);
protected override updateToolbar(): void {
// Unlike Theia, IDE2 debounces the toolbar updates with 500ms
this.doUpdateToolbar();
}
}

View File

@ -1,7 +1,7 @@
import { inject, injectable } from '@theia/core/shared/inversify'; import { inject, injectable } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { EditorWidget } from '@theia/editor/lib/browser'; import { EditorWidget } from '@theia/editor/lib/browser';
import { LabelProvider } from '@theia/core/lib/browser'; import type { NavigatableWidgetOptions } from '@theia/core/lib/browser';
import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory'; import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/lib/browser/editor-widget-factory';
import { import {
CurrentSketch, CurrentSketch,
@ -13,16 +13,16 @@ import { nls } from '@theia/core/lib/common';
@injectable() @injectable()
export class EditorWidgetFactory extends TheiaEditorWidgetFactory { export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
@inject(SketchesService) @inject(SketchesService)
protected readonly sketchesService: SketchesService; private readonly sketchesService: SketchesService;
@inject(SketchesServiceClientImpl) @inject(SketchesServiceClientImpl)
protected readonly sketchesServiceClient: SketchesServiceClientImpl; private readonly sketchesServiceClient: SketchesServiceClientImpl;
@inject(LabelProvider) protected override async createEditor(
protected override readonly labelProvider: LabelProvider; uri: URI,
options: NavigatableWidgetOptions
protected override async createEditor(uri: URI): Promise<EditorWidget> { ): Promise<EditorWidget> {
const widget = await super.createEditor(uri); const widget = await super.createEditor(uri, options);
return this.maybeUpdateCaption(widget); return this.maybeUpdateCaption(widget);
} }

View File

@ -1,10 +1,15 @@
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { Diagnostic } from 'vscode-languageserver-types'; import { Diagnostic } from 'vscode-languageserver-types';
import URI from '@theia/core/lib/common/uri'; import URI from '@theia/core/lib/common/uri';
import { ILogger } from '@theia/core'; import { ILogger } from '@theia/core';
import { Marker } from '@theia/markers/lib/common/marker'; import { Marker } from '@theia/markers/lib/common/marker';
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser/problem/problem-manager'; import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
import { ConfigService } from '../../../common/protocol/config-service'; import { ConfigService } from '../../../common/protocol/config-service';
import debounce = require('lodash.debounce');
@injectable() @injectable()
export class ProblemManager extends TheiaProblemManager { export class ProblemManager extends TheiaProblemManager {
@ -37,4 +42,12 @@ export class ProblemManager extends TheiaProblemManager {
} }
return super.setMarkers(uri, owner, data); return super.setMarkers(uri, owner, data);
} }
private readonly debouncedFireOnDidChangeMakers = debounce(
(uri: URI) => this.onDidChangeMarkersEmitter.fire(uri),
500
);
protected override fireOnDidChangeMarkers(uri: URI): void {
this.debouncedFireOnDidChangeMakers(uri);
}
} }

View File

@ -38,7 +38,7 @@ Object.assign(dialogs, {
export default new ContainerModule((bind, unbind, isBound, rebind) => { export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ElectronMenuContribution).toSelf().inSingletonScope(); bind(ElectronMenuContribution).toSelf().inSingletonScope();
bind(MainMenuManager).toService(ElectronMenuContribution); bind(MainMenuManager).toService(ElectronMenuContribution);
rebind(TheiaElectronMenuContribution).to(ElectronMenuContribution); rebind(TheiaElectronMenuContribution).toService(ElectronMenuContribution);
bind(ElectronMainMenuFactory).toSelf().inSingletonScope(); bind(ElectronMainMenuFactory).toSelf().inSingletonScope();
rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory); rebind(TheiaElectronMainMenuFactory).toService(ElectronMainMenuFactory);
bind(ElectronWindowService).toSelf().inSingletonScope(); bind(ElectronWindowService).toSelf().inSingletonScope();

View File

@ -141,7 +141,7 @@
"theiaPluginsDir": "plugins", "theiaPluginsDir": "plugins",
"theiaPlugins": { "theiaPlugins": {
"vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix", "vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix",
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.4.vsix", "vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.5.vsix",
"vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix", "vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix",
"vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix", "vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
"cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix", "cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix",

View File

@ -75,7 +75,7 @@
"theiaPluginsDir": "plugins", "theiaPluginsDir": "plugins",
"theiaPlugins": { "theiaPlugins": {
"vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix", "vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix",
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.4.vsix", "vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.5.vsix",
"vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix", "vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix",
"vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix", "vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
"cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix", "cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix",