mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-15 21:29:27 +00:00
Make tab width 2 spaces (#445)
This commit is contained in:
@@ -4,12 +4,12 @@ import { CommandService } from '@theia/core/lib/common/command';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { OutputWidget } from '@theia/output/lib/browser/output-widget';
|
||||
import {
|
||||
ConnectionStatusService,
|
||||
ConnectionStatus,
|
||||
ConnectionStatusService,
|
||||
ConnectionStatus,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import {
|
||||
ApplicationShell as TheiaApplicationShell,
|
||||
Widget,
|
||||
ApplicationShell as TheiaApplicationShell,
|
||||
Widget,
|
||||
} from '@theia/core/lib/browser';
|
||||
import { Sketch } from '../../../common/protocol';
|
||||
import { SaveAsSketch } from '../../contributions/save-as-sketch';
|
||||
@@ -17,76 +17,75 @@ import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-ser
|
||||
|
||||
@injectable()
|
||||
export class ApplicationShell extends TheiaApplicationShell {
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(ConnectionStatusService)
|
||||
protected readonly connectionStatusService: ConnectionStatusService;
|
||||
@inject(ConnectionStatusService)
|
||||
protected readonly connectionStatusService: ConnectionStatusService;
|
||||
|
||||
protected track(widget: Widget): void {
|
||||
super.track(widget);
|
||||
if (widget instanceof OutputWidget) {
|
||||
widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
||||
}
|
||||
if (widget instanceof EditorWidget) {
|
||||
// Make the editor un-closeable asynchronously.
|
||||
this.sketchesServiceClient.currentSketch().then((sketch) => {
|
||||
if (sketch) {
|
||||
if (Sketch.isInSketch(widget.editor.uri, sketch)) {
|
||||
widget.title.closable = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
protected track(widget: Widget): void {
|
||||
super.track(widget);
|
||||
if (widget instanceof OutputWidget) {
|
||||
widget.title.closable = false; // TODO: https://arduino.slack.com/archives/C01698YT7S4/p1598011990133700
|
||||
}
|
||||
|
||||
async addWidget(
|
||||
widget: Widget,
|
||||
options: Readonly<TheiaApplicationShell.WidgetOptions> = {}
|
||||
): Promise<void> {
|
||||
// By default, Theia open a widget **next** to the currently active in the target area.
|
||||
// Instead of this logic, we want to open the new widget after the last of the target area.
|
||||
if (!widget.id) {
|
||||
console.error(
|
||||
'Widgets added to the application shell must have a unique id property.'
|
||||
);
|
||||
return;
|
||||
if (widget instanceof EditorWidget) {
|
||||
// Make the editor un-closeable asynchronously.
|
||||
this.sketchesServiceClient.currentSketch().then((sketch) => {
|
||||
if (sketch) {
|
||||
if (Sketch.isInSketch(widget.editor.uri, sketch)) {
|
||||
widget.title.closable = false;
|
||||
}
|
||||
}
|
||||
let ref: Widget | undefined = options.ref;
|
||||
const area: TheiaApplicationShell.Area = options.area || 'main';
|
||||
if (!ref && (area === 'main' || area === 'bottom')) {
|
||||
const tabBar = this.getTabBarFor(area);
|
||||
if (tabBar) {
|
||||
const last = tabBar.titles[tabBar.titles.length - 1];
|
||||
if (last) {
|
||||
ref = last.owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.addWidget(widget, { ...options, ref });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async saveAll(): Promise<void> {
|
||||
if (
|
||||
this.connectionStatusService.currentStatus ===
|
||||
ConnectionStatus.OFFLINE
|
||||
) {
|
||||
this.messageService.error(
|
||||
'Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.'
|
||||
);
|
||||
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
|
||||
}
|
||||
await super.saveAll();
|
||||
const options = { execOnlyIfTemp: true, openAfterMove: true };
|
||||
await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
options
|
||||
);
|
||||
async addWidget(
|
||||
widget: Widget,
|
||||
options: Readonly<TheiaApplicationShell.WidgetOptions> = {}
|
||||
): Promise<void> {
|
||||
// By default, Theia open a widget **next** to the currently active in the target area.
|
||||
// Instead of this logic, we want to open the new widget after the last of the target area.
|
||||
if (!widget.id) {
|
||||
console.error(
|
||||
'Widgets added to the application shell must have a unique id property.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
let ref: Widget | undefined = options.ref;
|
||||
const area: TheiaApplicationShell.Area = options.area || 'main';
|
||||
if (!ref && (area === 'main' || area === 'bottom')) {
|
||||
const tabBar = this.getTabBarFor(area);
|
||||
if (tabBar) {
|
||||
const last = tabBar.titles[tabBar.titles.length - 1];
|
||||
if (last) {
|
||||
ref = last.owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.addWidget(widget, { ...options, ref });
|
||||
}
|
||||
|
||||
async saveAll(): Promise<void> {
|
||||
if (
|
||||
this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE
|
||||
) {
|
||||
this.messageService.error(
|
||||
'Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.'
|
||||
);
|
||||
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
|
||||
}
|
||||
await super.saveAll();
|
||||
const options = { execOnlyIfTemp: true, openAfterMove: true };
|
||||
await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { injectable } from 'inversify';
|
||||
import {
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
MenuBarWidget,
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
MenuBarWidget,
|
||||
} from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
|
||||
@injectable()
|
||||
export class BrowserMainMenuFactory
|
||||
extends TheiaBrowserMainMenuFactory
|
||||
implements MainMenuManager
|
||||
extends TheiaBrowserMainMenuFactory
|
||||
implements MainMenuManager
|
||||
{
|
||||
protected menuBar: MenuBarWidget | undefined;
|
||||
protected menuBar: MenuBarWidget | undefined;
|
||||
|
||||
createMenuBar(): MenuBarWidget {
|
||||
this.menuBar = super.createMenuBar();
|
||||
return this.menuBar;
|
||||
}
|
||||
createMenuBar(): MenuBarWidget {
|
||||
this.menuBar = super.createMenuBar();
|
||||
return this.menuBar;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.menuBar) {
|
||||
this.menuBar.clearMenus();
|
||||
this.fillMenuBar(this.menuBar);
|
||||
}
|
||||
update() {
|
||||
if (this.menuBar) {
|
||||
this.menuBar.clearMenus();
|
||||
this.fillMenuBar(this.menuBar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import '../../../../src/browser/style/browser-menu.css';
|
||||
import { ContainerModule } from 'inversify';
|
||||
import {
|
||||
BrowserMenuBarContribution,
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
BrowserMenuBarContribution,
|
||||
BrowserMainMenuFactory as TheiaBrowserMainMenuFactory,
|
||||
} from '@theia/core/lib/browser/menu/browser-menu-plugin';
|
||||
import { MainMenuManager } from '../../../common/main-menu-manager';
|
||||
import { ArduinoMenuContribution } from './browser-menu-plugin';
|
||||
import { BrowserMainMenuFactory } from './browser-main-menu-factory';
|
||||
|
||||
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||
bind(BrowserMainMenuFactory).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(BrowserMainMenuFactory);
|
||||
rebind(TheiaBrowserMainMenuFactory).toService(BrowserMainMenuFactory);
|
||||
rebind(BrowserMenuBarContribution)
|
||||
.to(ArduinoMenuContribution)
|
||||
.inSingletonScope();
|
||||
bind(BrowserMainMenuFactory).toSelf().inSingletonScope();
|
||||
bind(MainMenuManager).toService(BrowserMainMenuFactory);
|
||||
rebind(TheiaBrowserMainMenuFactory).toService(BrowserMainMenuFactory);
|
||||
rebind(BrowserMenuBarContribution)
|
||||
.to(ArduinoMenuContribution)
|
||||
.inSingletonScope();
|
||||
});
|
||||
|
||||
@@ -4,8 +4,8 @@ import { BrowserMenuBarContribution } from '@theia/core/lib/browser/menu/browser
|
||||
|
||||
@injectable()
|
||||
export class ArduinoMenuContribution extends BrowserMenuBarContribution {
|
||||
onStart(app: FrontendApplication): void {
|
||||
const menu = this.factory.createMenuBar();
|
||||
app.shell.addWidget(menu, { area: 'top' });
|
||||
}
|
||||
onStart(app: FrontendApplication): void {
|
||||
const menu = this.factory.createMenuBar();
|
||||
app.shell.addWidget(menu, { area: 'top' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { CommonFrontendContribution as TheiaCommonFrontendContribution, CommonCommands } from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
import {
|
||||
CommonFrontendContribution as TheiaCommonFrontendContribution,
|
||||
CommonCommands,
|
||||
} from '@theia/core/lib/browser/common-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
for (const command of [
|
||||
CommonCommands.SAVE,
|
||||
CommonCommands.SAVE_ALL,
|
||||
CommonCommands.CUT,
|
||||
CommonCommands.COPY,
|
||||
CommonCommands.PASTE,
|
||||
CommonCommands.COPY_PATH,
|
||||
CommonCommands.FIND,
|
||||
CommonCommands.REPLACE,
|
||||
CommonCommands.AUTO_SAVE,
|
||||
CommonCommands.OPEN_PREFERENCES,
|
||||
CommonCommands.SELECT_ICON_THEME,
|
||||
CommonCommands.SELECT_COLOR_THEME,
|
||||
CommonCommands.ABOUT_COMMAND,
|
||||
CommonCommands.CLOSE_TAB,
|
||||
CommonCommands.CLOSE_OTHER_TABS,
|
||||
CommonCommands.CLOSE_ALL_TABS,
|
||||
CommonCommands.COLLAPSE_PANEL,
|
||||
CommonCommands.SAVE_WITHOUT_FORMATTING // Patched for https://github.com/eclipse-theia/theia/pull/8877
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
for (const command of [
|
||||
CommonCommands.SAVE,
|
||||
CommonCommands.SAVE_ALL,
|
||||
CommonCommands.CUT,
|
||||
CommonCommands.COPY,
|
||||
CommonCommands.PASTE,
|
||||
CommonCommands.COPY_PATH,
|
||||
CommonCommands.FIND,
|
||||
CommonCommands.REPLACE,
|
||||
CommonCommands.AUTO_SAVE,
|
||||
CommonCommands.OPEN_PREFERENCES,
|
||||
CommonCommands.SELECT_ICON_THEME,
|
||||
CommonCommands.SELECT_COLOR_THEME,
|
||||
CommonCommands.ABOUT_COMMAND,
|
||||
CommonCommands.CLOSE_TAB,
|
||||
CommonCommands.CLOSE_OTHER_TABS,
|
||||
CommonCommands.CLOSE_ALL_TABS,
|
||||
CommonCommands.COLLAPSE_PANEL,
|
||||
CommonCommands.SAVE_WITHOUT_FORMATTING, // Patched for https://github.com/eclipse-theia/theia/pull/8877
|
||||
]) {
|
||||
registry.unregisterMenuAction(command);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,83 +2,81 @@ import { inject, injectable, postConstruct } from 'inversify';
|
||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
||||
import { StatusBarAlignment } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
import {
|
||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||
ConnectionStatus,
|
||||
FrontendConnectionStatusService as TheiaFrontendConnectionStatusService,
|
||||
ApplicationConnectionStatusContribution as TheiaApplicationConnectionStatusContribution,
|
||||
ConnectionStatus,
|
||||
} from '@theia/core/lib/browser/connection-status-service';
|
||||
import { ArduinoDaemon } from '../../../common/protocol';
|
||||
import { NotificationCenter } from '../../notification-center';
|
||||
|
||||
@injectable()
|
||||
export class FrontendConnectionStatusService extends TheiaFrontendConnectionStatusService {
|
||||
@inject(ArduinoDaemon)
|
||||
protected readonly daemon: ArduinoDaemon;
|
||||
@inject(ArduinoDaemon)
|
||||
protected readonly daemon: ArduinoDaemon;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
protected isRunning = false;
|
||||
protected isRunning = false;
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
this.schedulePing();
|
||||
try {
|
||||
this.isRunning = await this.daemon.isRunning();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
|
||||
this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
|
||||
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
||||
this.updateStatus(this.isRunning);
|
||||
this.schedulePing();
|
||||
});
|
||||
}
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
this.schedulePing();
|
||||
try {
|
||||
this.isRunning = await this.daemon.isRunning();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
|
||||
this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
|
||||
this.wsConnectionProvider.onIncomingMessageActivity(() => {
|
||||
this.updateStatus(this.isRunning);
|
||||
this.schedulePing();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ApplicationConnectionStatusContribution extends TheiaApplicationConnectionStatusContribution {
|
||||
@inject(ArduinoDaemon)
|
||||
protected readonly daemon: ArduinoDaemon;
|
||||
@inject(ArduinoDaemon)
|
||||
protected readonly daemon: ArduinoDaemon;
|
||||
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
@inject(NotificationCenter)
|
||||
protected readonly notificationCenter: NotificationCenter;
|
||||
|
||||
protected isRunning = false;
|
||||
protected isRunning = false;
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
try {
|
||||
this.isRunning = await this.daemon.isRunning();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
|
||||
this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
try {
|
||||
this.isRunning = await this.daemon.isRunning();
|
||||
} catch {}
|
||||
this.notificationCenter.onDaemonStarted(() => (this.isRunning = true));
|
||||
this.notificationCenter.onDaemonStopped(() => (this.isRunning = false));
|
||||
}
|
||||
|
||||
protected onStateChange(state: ConnectionStatus): void {
|
||||
if (!this.isRunning && state === ConnectionStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
super.onStateChange(state);
|
||||
}
|
||||
|
||||
protected onStateChange(state: ConnectionStatus): void {
|
||||
if (!this.isRunning && state === ConnectionStatus.ONLINE) {
|
||||
return;
|
||||
}
|
||||
super.onStateChange(state);
|
||||
}
|
||||
|
||||
protected handleOffline(): void {
|
||||
this.statusBar.setElement('connection-status', {
|
||||
alignment: StatusBarAlignment.LEFT,
|
||||
text: this.isRunning ? 'Offline' : '$(bolt) CLI Daemon Offline',
|
||||
tooltip: this.isRunning
|
||||
? 'Cannot connect to the backend.'
|
||||
: 'Cannot connect to the CLI daemon.',
|
||||
priority: 5000,
|
||||
});
|
||||
this.toDisposeOnOnline.push(
|
||||
Disposable.create(() =>
|
||||
this.statusBar.removeElement('connection-status')
|
||||
)
|
||||
);
|
||||
document.body.classList.add('theia-mod-offline');
|
||||
this.toDisposeOnOnline.push(
|
||||
Disposable.create(() =>
|
||||
document.body.classList.remove('theia-mod-offline')
|
||||
)
|
||||
);
|
||||
}
|
||||
protected handleOffline(): void {
|
||||
this.statusBar.setElement('connection-status', {
|
||||
alignment: StatusBarAlignment.LEFT,
|
||||
text: this.isRunning ? 'Offline' : '$(bolt) CLI Daemon Offline',
|
||||
tooltip: this.isRunning
|
||||
? 'Cannot connect to the backend.'
|
||||
: 'Cannot connect to the CLI daemon.',
|
||||
priority: 5000,
|
||||
});
|
||||
this.toDisposeOnOnline.push(
|
||||
Disposable.create(() => this.statusBar.removeElement('connection-status'))
|
||||
);
|
||||
document.body.classList.add('theia-mod-offline');
|
||||
this.toDisposeOnOnline.push(
|
||||
Disposable.create(() =>
|
||||
document.body.classList.remove('theia-mod-offline')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,32 +8,30 @@ import { ArduinoCommands } from '../../arduino-commands';
|
||||
|
||||
@injectable()
|
||||
export class FrontendApplication extends TheiaFrontendApplication {
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
protected async initializeLayout(): Promise<void> {
|
||||
await super.initializeLayout();
|
||||
const roots = await this.workspaceService.roots;
|
||||
for (const root of roots) {
|
||||
const exists = await this.fileService.exists(root.resource);
|
||||
if (exists) {
|
||||
this.sketchesService.markAsRecentlyOpened(
|
||||
root.resource.toString()
|
||||
); // no await, will get the notification later and rebuild the menu
|
||||
await this.commandService.executeCommand(
|
||||
ArduinoCommands.OPEN_SKETCH_FILES.id,
|
||||
root.resource
|
||||
);
|
||||
}
|
||||
}
|
||||
protected async initializeLayout(): Promise<void> {
|
||||
await super.initializeLayout();
|
||||
const roots = await this.workspaceService.roots;
|
||||
for (const root of roots) {
|
||||
const exists = await this.fileService.exists(root.resource);
|
||||
if (exists) {
|
||||
this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
|
||||
await this.commandService.executeCommand(
|
||||
ArduinoCommands.OPEN_SKETCH_FILES.id,
|
||||
root.resource
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,29 @@ import { injectable } from 'inversify';
|
||||
import { Command } from '@theia/core/lib/common/command';
|
||||
import { Keybinding } from '@theia/core/lib/common/keybinding';
|
||||
import {
|
||||
KeybindingRegistry as TheiaKeybindingRegistry,
|
||||
KeybindingScope,
|
||||
KeybindingRegistry as TheiaKeybindingRegistry,
|
||||
KeybindingScope,
|
||||
} from '@theia/core/lib/browser/keybinding';
|
||||
|
||||
@injectable()
|
||||
export class KeybindingRegistry extends TheiaKeybindingRegistry {
|
||||
// https://github.com/eclipse-theia/theia/issues/8209
|
||||
unregisterKeybinding(key: string): void;
|
||||
unregisterKeybinding(keybinding: Keybinding): void;
|
||||
unregisterKeybinding(command: Command): void;
|
||||
unregisterKeybinding(arg: string | Keybinding | Command): void {
|
||||
const keymap = this.keymaps[KeybindingScope.DEFAULT];
|
||||
const filter = Command.is(arg)
|
||||
? ({ command }: Keybinding) => command === arg.id
|
||||
: ({ keybinding }: Keybinding) =>
|
||||
Keybinding.is(arg)
|
||||
? keybinding === arg.keybinding
|
||||
: keybinding === arg;
|
||||
for (const binding of keymap.filter(filter)) {
|
||||
const idx = keymap.indexOf(binding);
|
||||
if (idx !== -1) {
|
||||
keymap.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
// https://github.com/eclipse-theia/theia/issues/8209
|
||||
unregisterKeybinding(key: string): void;
|
||||
unregisterKeybinding(keybinding: Keybinding): void;
|
||||
unregisterKeybinding(command: Command): void;
|
||||
unregisterKeybinding(arg: string | Keybinding | Command): void {
|
||||
const keymap = this.keymaps[KeybindingScope.DEFAULT];
|
||||
const filter = Command.is(arg)
|
||||
? ({ command }: Keybinding) => command === arg.id
|
||||
: ({ keybinding }: Keybinding) =>
|
||||
Keybinding.is(arg)
|
||||
? keybinding === arg.keybinding
|
||||
: keybinding === arg;
|
||||
for (const binding of keymap.filter(filter)) {
|
||||
const idx = keymap.indexOf(binding);
|
||||
if (idx !== -1) {
|
||||
keymap.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,27 +4,22 @@ import { ShellLayoutRestorer as TheiaShellLayoutRestorer } from '@theia/core/lib
|
||||
|
||||
@injectable()
|
||||
export class ShellLayoutRestorer extends TheiaShellLayoutRestorer {
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
|
||||
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
|
||||
if (this.shouldStoreLayout) {
|
||||
try {
|
||||
this.logger.info('>>> Storing the layout...');
|
||||
const layoutData = app.shell.getLayoutData();
|
||||
const serializedLayoutData = this.deflate(layoutData);
|
||||
await this.storageService.setData(
|
||||
this.storageKey,
|
||||
serializedLayoutData
|
||||
);
|
||||
this.logger.info(
|
||||
'<<< The layout has been successfully stored.'
|
||||
);
|
||||
} catch (error) {
|
||||
await this.storageService.setData(this.storageKey, undefined);
|
||||
this.logger.error(
|
||||
'Error during serialization of layout data',
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/6579.
|
||||
async storeLayoutAsync(app: FrontendApplication): Promise<void> {
|
||||
if (this.shouldStoreLayout) {
|
||||
try {
|
||||
this.logger.info('>>> Storing the layout...');
|
||||
const layoutData = app.shell.getLayoutData();
|
||||
const serializedLayoutData = this.deflate(layoutData);
|
||||
await this.storageService.setData(
|
||||
this.storageKey,
|
||||
serializedLayoutData
|
||||
);
|
||||
this.logger.info('<<< The layout has been successfully stored.');
|
||||
} catch (error) {
|
||||
await this.storageService.setData(this.storageKey, undefined);
|
||||
this.logger.error('Error during serialization of layout data', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,36 +9,31 @@ import { ConfigService } from '../../../common/protocol/config-service';
|
||||
|
||||
@injectable()
|
||||
export class TabBarDecoratorService extends TheiaTabBarDecoratorService {
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
protected dataDirUri: URI | undefined;
|
||||
protected dataDirUri: URI | undefined;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.configService
|
||||
.getConfiguration()
|
||||
.then(({ dataDirUri }) => (this.dataDirUri = new URI(dataDirUri)))
|
||||
.catch((err) =>
|
||||
this.logger.error(
|
||||
`Failed to determine the data directory: ${err}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getDecorations(title: Title<Widget>): WidgetDecoration.Data[] {
|
||||
if (title.owner instanceof EditorWidget) {
|
||||
const editor = title.owner.editor;
|
||||
if (
|
||||
this.dataDirUri &&
|
||||
this.dataDirUri.isEqualOrParent(editor.uri)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return super.getDecorations(title);
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.configService
|
||||
.getConfiguration()
|
||||
.then(({ dataDirUri }) => (this.dataDirUri = new URI(dataDirUri)))
|
||||
.catch((err) =>
|
||||
this.logger.error(`Failed to determine the data directory: ${err}`)
|
||||
);
|
||||
}
|
||||
|
||||
getDecorations(title: Title<Widget>): WidgetDecoration.Data[] {
|
||||
if (title.owner instanceof EditorWidget) {
|
||||
const editor = title.owner.editor;
|
||||
if (this.dataDirUri && this.dataDirUri.isEqualOrParent(editor.uri)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return super.getDecorations(title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,62 +2,60 @@ import * as React from 'react';
|
||||
import { injectable } from 'inversify';
|
||||
import { LabelIcon } from '@theia/core/lib/browser/label-parser';
|
||||
import {
|
||||
TabBarToolbar as TheiaTabBarToolbar,
|
||||
TabBarToolbarItem,
|
||||
TabBarToolbar as TheiaTabBarToolbar,
|
||||
TabBarToolbarItem,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
|
||||
@injectable()
|
||||
export class TabBarToolbar extends TheiaTabBarToolbar {
|
||||
/**
|
||||
* Copied over from Theia. Added an ID to the parent of the toolbar item (`--container`).
|
||||
* CSS3 does not support parent selectors but we want to style the parent of the toolbar item.
|
||||
*/
|
||||
protected renderItem(item: TabBarToolbarItem): React.ReactNode {
|
||||
let innerText = '';
|
||||
const classNames = [];
|
||||
if (item.text) {
|
||||
for (const labelPart of this.labelParser.parse(item.text)) {
|
||||
if (typeof labelPart !== 'string' && LabelIcon.is(labelPart)) {
|
||||
const className = `fa fa-${labelPart.name}${
|
||||
labelPart.animation ? ' fa-' + labelPart.animation : ''
|
||||
}`;
|
||||
classNames.push(...className.split(' '));
|
||||
} else {
|
||||
innerText = labelPart;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Copied over from Theia. Added an ID to the parent of the toolbar item (`--container`).
|
||||
* CSS3 does not support parent selectors but we want to style the parent of the toolbar item.
|
||||
*/
|
||||
protected renderItem(item: TabBarToolbarItem): React.ReactNode {
|
||||
let innerText = '';
|
||||
const classNames = [];
|
||||
if (item.text) {
|
||||
for (const labelPart of this.labelParser.parse(item.text)) {
|
||||
if (typeof labelPart !== 'string' && LabelIcon.is(labelPart)) {
|
||||
const className = `fa fa-${labelPart.name}${
|
||||
labelPart.animation ? ' fa-' + labelPart.animation : ''
|
||||
}`;
|
||||
classNames.push(...className.split(' '));
|
||||
} else {
|
||||
innerText = labelPart;
|
||||
}
|
||||
const command = this.commands.getCommand(item.command);
|
||||
const iconClass =
|
||||
(typeof item.icon === 'function' && item.icon()) ||
|
||||
item.icon ||
|
||||
(command && command.iconClass);
|
||||
if (iconClass) {
|
||||
classNames.push(iconClass);
|
||||
}
|
||||
const tooltip = item.tooltip || (command && command.label);
|
||||
return (
|
||||
<div
|
||||
id={`${item.id}--container`}
|
||||
key={item.id}
|
||||
className={`${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM}${
|
||||
command && this.commandIsEnabled(command.id)
|
||||
? ' enabled'
|
||||
: ''
|
||||
}`}
|
||||
onMouseDown={this.onMouseDownEvent}
|
||||
onMouseUp={this.onMouseUpEvent}
|
||||
onMouseOut={this.onMouseUpEvent}
|
||||
>
|
||||
<div
|
||||
id={item.id}
|
||||
className={classNames.join(' ')}
|
||||
onClick={this.executeCommand}
|
||||
title={tooltip}
|
||||
>
|
||||
{innerText}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
const command = this.commands.getCommand(item.command);
|
||||
const iconClass =
|
||||
(typeof item.icon === 'function' && item.icon()) ||
|
||||
item.icon ||
|
||||
(command && command.iconClass);
|
||||
if (iconClass) {
|
||||
classNames.push(iconClass);
|
||||
}
|
||||
const tooltip = item.tooltip || (command && command.label);
|
||||
return (
|
||||
<div
|
||||
id={`${item.id}--container`}
|
||||
key={item.id}
|
||||
className={`${TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM}${
|
||||
command && this.commandIsEnabled(command.id) ? ' enabled' : ''
|
||||
}`}
|
||||
onMouseDown={this.onMouseDownEvent}
|
||||
onMouseUp={this.onMouseUpEvent}
|
||||
onMouseOut={this.onMouseUpEvent}
|
||||
>
|
||||
<div
|
||||
id={item.id}
|
||||
className={classNames.join(' ')}
|
||||
onClick={this.executeCommand}
|
||||
title={tooltip}
|
||||
>
|
||||
{innerText}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ import { Saveable } from '@theia/core/lib/browser/saveable';
|
||||
import { TabBarRenderer as TheiaTabBarRenderer } from '@theia/core/lib/browser/shell/tab-bars';
|
||||
|
||||
export class TabBarRenderer extends TheiaTabBarRenderer {
|
||||
createTabClass(data: TabBar.IRenderData<any>): string {
|
||||
let className = super.createTabClass(data);
|
||||
if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
|
||||
className += ' p-mod-closable';
|
||||
}
|
||||
return className;
|
||||
createTabClass(data: TabBar.IRenderData<any>): string {
|
||||
let className = super.createTabClass(data);
|
||||
if (!data.title.closable && Saveable.isDirty(data.title.owner)) {
|
||||
className += ' p-mod-closable';
|
||||
}
|
||||
return className;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,138 +10,128 @@ import { SketchesService } from '../../../common/protocol';
|
||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||
import { DebugConfigurationModel } from './debug-configuration-model';
|
||||
import {
|
||||
FileOperationError,
|
||||
FileOperationResult,
|
||||
FileOperationError,
|
||||
FileOperationResult,
|
||||
} from '@theia/filesystem/lib/common/files';
|
||||
|
||||
@injectable()
|
||||
export class DebugConfigurationManager extends TheiaDebugConfigurationManager {
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
protected onTempContentDidChangeEmitter =
|
||||
new Emitter<TheiaDebugConfigurationModel.JsonContent>();
|
||||
get onTempContentDidChange(): Event<TheiaDebugConfigurationModel.JsonContent> {
|
||||
return this.onTempContentDidChangeEmitter.event;
|
||||
}
|
||||
protected onTempContentDidChangeEmitter =
|
||||
new Emitter<TheiaDebugConfigurationModel.JsonContent>();
|
||||
get onTempContentDidChange(): Event<TheiaDebugConfigurationModel.JsonContent> {
|
||||
return this.onTempContentDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
super.init();
|
||||
this.appStateService.reachedState('ready').then(async () => {
|
||||
const tempContent = await this.getTempLaunchJsonContent();
|
||||
if (!tempContent) {
|
||||
// No active sketch.
|
||||
return;
|
||||
}
|
||||
// Watch the file of the container folder.
|
||||
this.fileService.watch(
|
||||
tempContent instanceof URI ? tempContent : tempContent.uri
|
||||
);
|
||||
// Use the normalized temp folder name. We cannot compare Theia URIs here.
|
||||
// /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-a0337d47f86b24a51df3dbcf2cc17925/launch.json
|
||||
// /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925/launch.json
|
||||
const tempFolderName = (
|
||||
tempContent instanceof URI
|
||||
? tempContent
|
||||
: tempContent.uri.parent
|
||||
).path.base.toLowerCase();
|
||||
this.fileService.onDidFilesChange((event) => {
|
||||
for (const { resource } of event.changes) {
|
||||
if (
|
||||
resource.path.base === 'launch.json' &&
|
||||
resource.parent.path.base.toLowerCase() ===
|
||||
tempFolderName
|
||||
) {
|
||||
this.getTempLaunchJsonContent().then((config) => {
|
||||
if (config && !(config instanceof URI)) {
|
||||
this.onTempContentDidChangeEmitter.fire(config);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@postConstruct()
|
||||
protected async init(): Promise<void> {
|
||||
super.init();
|
||||
this.appStateService.reachedState('ready').then(async () => {
|
||||
const tempContent = await this.getTempLaunchJsonContent();
|
||||
if (!tempContent) {
|
||||
// No active sketch.
|
||||
return;
|
||||
}
|
||||
// Watch the file of the container folder.
|
||||
this.fileService.watch(
|
||||
tempContent instanceof URI ? tempContent : tempContent.uri
|
||||
);
|
||||
// Use the normalized temp folder name. We cannot compare Theia URIs here.
|
||||
// /var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-a0337d47f86b24a51df3dbcf2cc17925/launch.json
|
||||
// /private/var/folders/k3/d2fkvv1j16v3_rz93k7f74180000gn/T/arduino-ide2-A0337D47F86B24A51DF3DBCF2CC17925/launch.json
|
||||
const tempFolderName = (
|
||||
tempContent instanceof URI ? tempContent : tempContent.uri.parent
|
||||
).path.base.toLowerCase();
|
||||
this.fileService.onDidFilesChange((event) => {
|
||||
for (const { resource } of event.changes) {
|
||||
if (
|
||||
resource.path.base === 'launch.json' &&
|
||||
resource.parent.path.base.toLowerCase() === tempFolderName
|
||||
) {
|
||||
this.getTempLaunchJsonContent().then((config) => {
|
||||
if (config && !(config instanceof URI)) {
|
||||
this.onTempContentDidChangeEmitter.fire(config);
|
||||
}
|
||||
});
|
||||
this.updateModels();
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.updateModels();
|
||||
});
|
||||
}
|
||||
|
||||
protected updateModels = debounce(async () => {
|
||||
await this.appStateService.reachedState('ready');
|
||||
const roots = await this.workspaceService.roots;
|
||||
const toDelete = new Set(this.models.keys());
|
||||
for (const rootStat of roots) {
|
||||
const key = rootStat.resource.toString();
|
||||
toDelete.delete(key);
|
||||
if (!this.models.has(key)) {
|
||||
const tempContent = await this.getTempLaunchJsonContent();
|
||||
if (!tempContent) {
|
||||
continue;
|
||||
}
|
||||
const configurations: DebugConfiguration[] =
|
||||
tempContent instanceof URI
|
||||
? []
|
||||
: tempContent.configurations;
|
||||
const uri =
|
||||
tempContent instanceof URI ? undefined : tempContent.uri;
|
||||
const model = new DebugConfigurationModel(
|
||||
key,
|
||||
this.preferences,
|
||||
configurations,
|
||||
uri,
|
||||
this.onTempContentDidChange
|
||||
);
|
||||
model.onDidChange(() => this.updateCurrent());
|
||||
model.onDispose(() => this.models.delete(key));
|
||||
this.models.set(key, model);
|
||||
}
|
||||
}
|
||||
for (const uri of toDelete) {
|
||||
const model = this.models.get(uri);
|
||||
if (model) {
|
||||
model.dispose();
|
||||
}
|
||||
}
|
||||
this.updateCurrent();
|
||||
}, 500);
|
||||
|
||||
protected async getTempLaunchJsonContent(): Promise<
|
||||
| (TheiaDebugConfigurationModel.JsonContent & { uri: URI })
|
||||
| URI
|
||||
| undefined
|
||||
> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return undefined;
|
||||
}
|
||||
const uri = await this.sketchesService.getIdeTempFolderUri(sketch);
|
||||
const tempFolderUri = new URI(uri);
|
||||
await this.fileService.createFolder(tempFolderUri);
|
||||
try {
|
||||
const uri = tempFolderUri.resolve('launch.json');
|
||||
const { value } = await this.fileService.read(uri);
|
||||
const configurations = DebugConfigurationModel.parse(
|
||||
JSON.parse(value)
|
||||
);
|
||||
return { uri, configurations };
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof FileOperationError &&
|
||||
err.fileOperationResult === FileOperationResult.FILE_NOT_FOUND
|
||||
) {
|
||||
return tempFolderUri;
|
||||
}
|
||||
console.error(
|
||||
'Could not load debug configuration from IDE2 temp folder.',
|
||||
err
|
||||
);
|
||||
throw err;
|
||||
protected updateModels = debounce(async () => {
|
||||
await this.appStateService.reachedState('ready');
|
||||
const roots = await this.workspaceService.roots;
|
||||
const toDelete = new Set(this.models.keys());
|
||||
for (const rootStat of roots) {
|
||||
const key = rootStat.resource.toString();
|
||||
toDelete.delete(key);
|
||||
if (!this.models.has(key)) {
|
||||
const tempContent = await this.getTempLaunchJsonContent();
|
||||
if (!tempContent) {
|
||||
continue;
|
||||
}
|
||||
const configurations: DebugConfiguration[] =
|
||||
tempContent instanceof URI ? [] : tempContent.configurations;
|
||||
const uri = tempContent instanceof URI ? undefined : tempContent.uri;
|
||||
const model = new DebugConfigurationModel(
|
||||
key,
|
||||
this.preferences,
|
||||
configurations,
|
||||
uri,
|
||||
this.onTempContentDidChange
|
||||
);
|
||||
model.onDidChange(() => this.updateCurrent());
|
||||
model.onDispose(() => this.models.delete(key));
|
||||
this.models.set(key, model);
|
||||
}
|
||||
}
|
||||
for (const uri of toDelete) {
|
||||
const model = this.models.get(uri);
|
||||
if (model) {
|
||||
model.dispose();
|
||||
}
|
||||
}
|
||||
this.updateCurrent();
|
||||
}, 500);
|
||||
|
||||
protected async getTempLaunchJsonContent(): Promise<
|
||||
(TheiaDebugConfigurationModel.JsonContent & { uri: URI }) | URI | undefined
|
||||
> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return undefined;
|
||||
}
|
||||
const uri = await this.sketchesService.getIdeTempFolderUri(sketch);
|
||||
const tempFolderUri = new URI(uri);
|
||||
await this.fileService.createFolder(tempFolderUri);
|
||||
try {
|
||||
const uri = tempFolderUri.resolve('launch.json');
|
||||
const { value } = await this.fileService.read(uri);
|
||||
const configurations = DebugConfigurationModel.parse(JSON.parse(value));
|
||||
return { uri, configurations };
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof FileOperationError &&
|
||||
err.fileOperationResult === FileOperationResult.FILE_NOT_FOUND
|
||||
) {
|
||||
return tempFolderUri;
|
||||
}
|
||||
console.error(
|
||||
'Could not load debug configuration from IDE2 temp folder.',
|
||||
err
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,50 +5,50 @@ import { DebugConfiguration } from '@theia/debug/lib/common/debug-common';
|
||||
import { DebugConfigurationModel as TheiaDebugConfigurationModel } from '@theia/debug/lib/browser/debug-configuration-model';
|
||||
|
||||
export class DebugConfigurationModel extends TheiaDebugConfigurationModel {
|
||||
constructor(
|
||||
readonly workspaceFolderUri: string,
|
||||
protected readonly preferences: PreferenceService,
|
||||
protected readonly config: DebugConfiguration[],
|
||||
protected configUri: URI | undefined,
|
||||
protected readonly onConfigDidChange: Event<TheiaDebugConfigurationModel.JsonContent>
|
||||
) {
|
||||
super(workspaceFolderUri, preferences);
|
||||
this.toDispose.push(
|
||||
onConfigDidChange((content) => {
|
||||
const { uri, configurations } = content;
|
||||
this.configUri = uri;
|
||||
this.config.length = 0;
|
||||
this.config.push(...configurations);
|
||||
this.reconcile();
|
||||
})
|
||||
);
|
||||
constructor(
|
||||
readonly workspaceFolderUri: string,
|
||||
protected readonly preferences: PreferenceService,
|
||||
protected readonly config: DebugConfiguration[],
|
||||
protected configUri: URI | undefined,
|
||||
protected readonly onConfigDidChange: Event<TheiaDebugConfigurationModel.JsonContent>
|
||||
) {
|
||||
super(workspaceFolderUri, preferences);
|
||||
this.toDispose.push(
|
||||
onConfigDidChange((content) => {
|
||||
const { uri, configurations } = content;
|
||||
this.configUri = uri;
|
||||
this.config.length = 0;
|
||||
this.config.push(...configurations);
|
||||
this.reconcile();
|
||||
}
|
||||
})
|
||||
);
|
||||
this.reconcile();
|
||||
}
|
||||
|
||||
protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent {
|
||||
return {
|
||||
uri: this.configUri,
|
||||
configurations: this.config,
|
||||
};
|
||||
}
|
||||
protected parseConfigurations(): TheiaDebugConfigurationModel.JsonContent {
|
||||
return {
|
||||
uri: this.configUri,
|
||||
configurations: this.config,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DebugConfigurationModel {
|
||||
export function parse(launchConfig: any): DebugConfiguration[] {
|
||||
const configurations: DebugConfiguration[] = [];
|
||||
if (
|
||||
launchConfig &&
|
||||
typeof launchConfig === 'object' &&
|
||||
'configurations' in launchConfig
|
||||
) {
|
||||
if (Array.isArray(launchConfig.configurations)) {
|
||||
for (const configuration of launchConfig.configurations) {
|
||||
if (DebugConfiguration.is(configuration)) {
|
||||
configurations.push(configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function parse(launchConfig: any): DebugConfiguration[] {
|
||||
const configurations: DebugConfiguration[] = [];
|
||||
if (
|
||||
launchConfig &&
|
||||
typeof launchConfig === 'object' &&
|
||||
'configurations' in launchConfig
|
||||
) {
|
||||
if (Array.isArray(launchConfig.configurations)) {
|
||||
for (const configuration of launchConfig.configurations) {
|
||||
if (DebugConfiguration.is(configuration)) {
|
||||
configurations.push(configuration);
|
||||
}
|
||||
}
|
||||
return configurations;
|
||||
}
|
||||
}
|
||||
return configurations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import debounce from 'p-debounce';
|
||||
import {
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
interfaces,
|
||||
Container,
|
||||
inject,
|
||||
injectable,
|
||||
postConstruct,
|
||||
interfaces,
|
||||
Container,
|
||||
} from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { MonacoConfigurationService } from '@theia/monaco/lib/browser/monaco-frontend-module';
|
||||
import { INLINE_VALUE_DECORATION_KEY } from '@theia/debug/lib/browser/editor//debug-inline-value-decorator';
|
||||
@@ -22,95 +22,93 @@ import { createDebugHoverWidgetContainer } from './debug-hover-widget';
|
||||
// TODO: Remove after https://github.com/eclipse-theia/theia/pull/9256/
|
||||
@injectable()
|
||||
export class DebugEditorModel extends TheiaDebugEditorModel {
|
||||
static createContainer(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): Container {
|
||||
const child = createDebugHoverWidgetContainer(parent, editor);
|
||||
child.bind(DebugEditorModel).toSelf();
|
||||
child.bind(DebugBreakpointWidget).toSelf();
|
||||
child.bind(DebugExceptionWidget).toSelf();
|
||||
return child;
|
||||
}
|
||||
static createContainer(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): Container {
|
||||
const child = createDebugHoverWidgetContainer(parent, editor);
|
||||
child.bind(DebugEditorModel).toSelf();
|
||||
child.bind(DebugBreakpointWidget).toSelf();
|
||||
child.bind(DebugExceptionWidget).toSelf();
|
||||
return child;
|
||||
}
|
||||
|
||||
static createModel(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): DebugEditorModel {
|
||||
return DebugEditorModel.createContainer(parent, editor).get(
|
||||
DebugEditorModel
|
||||
);
|
||||
}
|
||||
static createModel(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): DebugEditorModel {
|
||||
return DebugEditorModel.createContainer(parent, editor).get(
|
||||
DebugEditorModel
|
||||
);
|
||||
}
|
||||
|
||||
@inject(MonacoConfigurationService)
|
||||
readonly configurationService: monaco.services.IConfigurationService;
|
||||
@inject(MonacoConfigurationService)
|
||||
readonly configurationService: monaco.services.IConfigurationService;
|
||||
|
||||
protected readonly toDisposeOnRenderFrames = new DisposableCollection();
|
||||
protected readonly toDisposeOnRenderFrames = new DisposableCollection();
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDispose.push(this.toDisposeOnRenderFrames);
|
||||
super.init();
|
||||
}
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.toDispose.push(this.toDisposeOnRenderFrames);
|
||||
super.init();
|
||||
}
|
||||
|
||||
protected async updateEditorHover(): Promise<void> {
|
||||
if (this.isCurrentEditorFrame(this.uri)) {
|
||||
const codeEditor = this.editor.getControl();
|
||||
codeEditor.updateOptions({ hover: { enabled: false } });
|
||||
this.toDisposeOnRenderFrames.push(
|
||||
Disposable.create(() => {
|
||||
const model = codeEditor.getModel()!;
|
||||
const overrides = {
|
||||
resource: model.uri,
|
||||
overrideIdentifier: (
|
||||
model as any
|
||||
).getLanguageIdentifier().language,
|
||||
};
|
||||
const { enabled, delay, sticky } =
|
||||
this.configurationService._configuration.getValue(
|
||||
'editor.hover',
|
||||
overrides,
|
||||
undefined
|
||||
);
|
||||
codeEditor.updateOptions({
|
||||
hover: {
|
||||
enabled,
|
||||
delay,
|
||||
sticky,
|
||||
},
|
||||
});
|
||||
})
|
||||
protected async updateEditorHover(): Promise<void> {
|
||||
if (this.isCurrentEditorFrame(this.uri)) {
|
||||
const codeEditor = this.editor.getControl();
|
||||
codeEditor.updateOptions({ hover: { enabled: false } });
|
||||
this.toDisposeOnRenderFrames.push(
|
||||
Disposable.create(() => {
|
||||
const model = codeEditor.getModel()!;
|
||||
const overrides = {
|
||||
resource: model.uri,
|
||||
overrideIdentifier: (model as any).getLanguageIdentifier().language,
|
||||
};
|
||||
const { enabled, delay, sticky } =
|
||||
this.configurationService._configuration.getValue(
|
||||
'editor.hover',
|
||||
overrides,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
codeEditor.updateOptions({
|
||||
hover: {
|
||||
enabled,
|
||||
delay,
|
||||
sticky,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private isCurrentEditorFrame(uri: URI): boolean {
|
||||
return (
|
||||
this.sessions.currentFrame?.source?.uri.toString() ===
|
||||
uri.toString()
|
||||
);
|
||||
private isCurrentEditorFrame(uri: URI): boolean {
|
||||
return (
|
||||
this.sessions.currentFrame?.source?.uri.toString() === uri.toString()
|
||||
);
|
||||
}
|
||||
|
||||
protected readonly renderFrames = debounce(async () => {
|
||||
if (this.toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
this.toDisposeOnRenderFrames.dispose();
|
||||
|
||||
protected readonly renderFrames = debounce(async () => {
|
||||
if (this.toDispose.disposed) {
|
||||
return;
|
||||
}
|
||||
this.toDisposeOnRenderFrames.dispose();
|
||||
|
||||
this.toggleExceptionWidget();
|
||||
const [newFrameDecorations, inlineValueDecorations] = await Promise.all(
|
||||
[this.createFrameDecorations(), this.createInlineValueDecorations()]
|
||||
);
|
||||
const codeEditor = this.editor.getControl();
|
||||
codeEditor.removeDecorations(INLINE_VALUE_DECORATION_KEY);
|
||||
codeEditor.setDecorations(
|
||||
INLINE_VALUE_DECORATION_KEY,
|
||||
inlineValueDecorations
|
||||
);
|
||||
this.frameDecorations = this.deltaDecorations(
|
||||
this.frameDecorations,
|
||||
newFrameDecorations
|
||||
);
|
||||
this.updateEditorHover();
|
||||
}, 100);
|
||||
this.toggleExceptionWidget();
|
||||
const [newFrameDecorations, inlineValueDecorations] = await Promise.all([
|
||||
this.createFrameDecorations(),
|
||||
this.createInlineValueDecorations(),
|
||||
]);
|
||||
const codeEditor = this.editor.getControl();
|
||||
codeEditor.removeDecorations(INLINE_VALUE_DECORATION_KEY);
|
||||
codeEditor.setDecorations(
|
||||
INLINE_VALUE_DECORATION_KEY,
|
||||
inlineValueDecorations
|
||||
);
|
||||
this.frameDecorations = this.deltaDecorations(
|
||||
this.frameDecorations,
|
||||
newFrameDecorations
|
||||
);
|
||||
this.updateEditorHover();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import {
|
||||
DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution,
|
||||
DebugMenus,
|
||||
DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution,
|
||||
DebugMenus,
|
||||
} from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||
import { unregisterSubmenu } from '../../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class DebugFrontendApplicationContribution extends TheiaDebugFrontendApplicationContribution {
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions.rank = 4;
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions.rank = 4;
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
unregisterSubmenu(DebugMenus.DEBUG, registry);
|
||||
}
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
unregisterSubmenu(DebugMenus.DEBUG, registry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { injectable } from 'inversify';
|
||||
import {
|
||||
ExpressionItem,
|
||||
DebugVariable,
|
||||
ExpressionItem,
|
||||
DebugVariable,
|
||||
} from '@theia/debug/lib/browser/console/debug-console-items';
|
||||
import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source';
|
||||
|
||||
// TODO: remove after https://github.com/eclipse-theia/theia/pull/9256/.
|
||||
@injectable()
|
||||
export class DebugHoverSource extends TheiaDebugHoverSource {
|
||||
async evaluate2(
|
||||
expression: string
|
||||
): Promise<ExpressionItem | DebugVariable | undefined> {
|
||||
const evaluated = await this.doEvaluate(expression);
|
||||
const elements = evaluated && (await evaluated.getElements());
|
||||
this._expression = evaluated;
|
||||
this.elements = elements ? [...elements] : [];
|
||||
this.fireDidChange();
|
||||
return evaluated;
|
||||
}
|
||||
async evaluate2(
|
||||
expression: string
|
||||
): Promise<ExpressionItem | DebugVariable | undefined> {
|
||||
const evaluated = await this.doEvaluate(expression);
|
||||
const elements = evaluated && (await evaluated.getElements());
|
||||
this._expression = evaluated;
|
||||
this.elements = elements ? [...elements] : [];
|
||||
this.fireDidChange();
|
||||
return evaluated;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,116 +7,113 @@ import { DebugVariable } from '@theia/debug/lib/browser/console/debug-console-it
|
||||
import { DebugExpressionProvider } from '@theia/debug/lib/browser/editor/debug-expression-provider';
|
||||
import { DebugHoverSource as TheiaDebugHoverSource } from '@theia/debug/lib/browser/editor/debug-hover-source';
|
||||
import {
|
||||
DebugHoverWidget as TheiaDebugHoverWidget,
|
||||
ShowDebugHoverOptions,
|
||||
DebugHoverWidget as TheiaDebugHoverWidget,
|
||||
ShowDebugHoverOptions,
|
||||
} from '@theia/debug/lib/browser/editor/debug-hover-widget';
|
||||
import { DebugHoverSource } from './debug-hover-source';
|
||||
|
||||
export function createDebugHoverWidgetContainer(
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
parent: interfaces.Container,
|
||||
editor: DebugEditor
|
||||
): Container {
|
||||
const child = SourceTreeWidget.createContainer(parent, {
|
||||
virtualized: false,
|
||||
});
|
||||
child.bind(DebugEditor).toConstantValue(editor);
|
||||
child.bind(TheiaDebugHoverSource).toSelf();
|
||||
child.bind(DebugHoverSource).toSelf();
|
||||
child.rebind(TheiaDebugHoverSource).to(DebugHoverSource);
|
||||
child.unbind(SourceTreeWidget);
|
||||
child.bind(DebugExpressionProvider).toSelf();
|
||||
child.bind(TheiaDebugHoverWidget).toSelf();
|
||||
child.bind(DebugHoverWidget).toSelf();
|
||||
child.rebind(TheiaDebugHoverWidget).to(DebugHoverWidget);
|
||||
return child;
|
||||
const child = SourceTreeWidget.createContainer(parent, {
|
||||
virtualized: false,
|
||||
});
|
||||
child.bind(DebugEditor).toConstantValue(editor);
|
||||
child.bind(TheiaDebugHoverSource).toSelf();
|
||||
child.bind(DebugHoverSource).toSelf();
|
||||
child.rebind(TheiaDebugHoverSource).to(DebugHoverSource);
|
||||
child.unbind(SourceTreeWidget);
|
||||
child.bind(DebugExpressionProvider).toSelf();
|
||||
child.bind(TheiaDebugHoverWidget).toSelf();
|
||||
child.bind(DebugHoverWidget).toSelf();
|
||||
child.rebind(TheiaDebugHoverWidget).to(DebugHoverWidget);
|
||||
return child;
|
||||
}
|
||||
|
||||
// TODO: remove patch after https://github.com/eclipse-theia/theia/pull/9256/
|
||||
@injectable()
|
||||
export class DebugHoverWidget extends TheiaDebugHoverWidget {
|
||||
protected async doShow(
|
||||
options: ShowDebugHoverOptions | undefined = this.options
|
||||
): Promise<void> {
|
||||
if (!this.isEditorFrame()) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
if (!options) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.options &&
|
||||
this.options.selection.equalsRange(options.selection)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!this.isAttached) {
|
||||
Widget.attach(this, this.contentNode);
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
const matchingExpression = this.expressionProvider.get(
|
||||
this.editor.getControl().getModel()!,
|
||||
options.selection
|
||||
);
|
||||
if (!matchingExpression) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
const toFocus = new DisposableCollection();
|
||||
if (this.options.focus === true) {
|
||||
toFocus.push(
|
||||
this.model.onNodeRefreshed(() => {
|
||||
toFocus.dispose();
|
||||
this.activate();
|
||||
})
|
||||
);
|
||||
}
|
||||
const expression = await (
|
||||
this.hoverSource as DebugHoverSource
|
||||
).evaluate2(matchingExpression);
|
||||
if (!expression || !expression.value) {
|
||||
toFocus.dispose();
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.contentNode.hidden = false;
|
||||
['number', 'boolean', 'string'].forEach((token) =>
|
||||
this.titleNode.classList.remove(token)
|
||||
);
|
||||
this.domNode.classList.remove('complex-value');
|
||||
if (expression.hasElements) {
|
||||
this.domNode.classList.add('complex-value');
|
||||
} else {
|
||||
this.contentNode.hidden = true;
|
||||
if (
|
||||
expression.type === 'number' ||
|
||||
expression.type === 'boolean' ||
|
||||
expression.type === 'string'
|
||||
) {
|
||||
this.titleNode.classList.add(expression.type);
|
||||
} else if (!isNaN(+expression.value)) {
|
||||
this.titleNode.classList.add('number');
|
||||
} else if (DebugVariable.booleanRegex.test(expression.value)) {
|
||||
this.titleNode.classList.add('boolean');
|
||||
} else if (DebugVariable.stringRegex.test(expression.value)) {
|
||||
this.titleNode.classList.add('string');
|
||||
}
|
||||
}
|
||||
|
||||
// super.show(); // Here we cannot call `super.show()` but have to call `show` on the `Widget` prototype.
|
||||
Widget.prototype.show.call(this);
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(
|
||||
() =>
|
||||
window.requestAnimationFrame(() => {
|
||||
this.editor.getControl().layoutContentWidget(this);
|
||||
resolve();
|
||||
}),
|
||||
0
|
||||
);
|
||||
});
|
||||
protected async doShow(
|
||||
options: ShowDebugHoverOptions | undefined = this.options
|
||||
): Promise<void> {
|
||||
if (!this.isEditorFrame()) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
if (!options) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
if (this.options && this.options.selection.equalsRange(options.selection)) {
|
||||
return;
|
||||
}
|
||||
if (!this.isAttached) {
|
||||
Widget.attach(this, this.contentNode);
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
const matchingExpression = this.expressionProvider.get(
|
||||
this.editor.getControl().getModel()!,
|
||||
options.selection
|
||||
);
|
||||
if (!matchingExpression) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
const toFocus = new DisposableCollection();
|
||||
if (this.options.focus === true) {
|
||||
toFocus.push(
|
||||
this.model.onNodeRefreshed(() => {
|
||||
toFocus.dispose();
|
||||
this.activate();
|
||||
})
|
||||
);
|
||||
}
|
||||
const expression = await (this.hoverSource as DebugHoverSource).evaluate2(
|
||||
matchingExpression
|
||||
);
|
||||
if (!expression || !expression.value) {
|
||||
toFocus.dispose();
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.contentNode.hidden = false;
|
||||
['number', 'boolean', 'string'].forEach((token) =>
|
||||
this.titleNode.classList.remove(token)
|
||||
);
|
||||
this.domNode.classList.remove('complex-value');
|
||||
if (expression.hasElements) {
|
||||
this.domNode.classList.add('complex-value');
|
||||
} else {
|
||||
this.contentNode.hidden = true;
|
||||
if (
|
||||
expression.type === 'number' ||
|
||||
expression.type === 'boolean' ||
|
||||
expression.type === 'string'
|
||||
) {
|
||||
this.titleNode.classList.add(expression.type);
|
||||
} else if (!isNaN(+expression.value)) {
|
||||
this.titleNode.classList.add('number');
|
||||
} else if (DebugVariable.booleanRegex.test(expression.value)) {
|
||||
this.titleNode.classList.add('boolean');
|
||||
} else if (DebugVariable.stringRegex.test(expression.value)) {
|
||||
this.titleNode.classList.add('string');
|
||||
}
|
||||
}
|
||||
|
||||
// super.show(); // Here we cannot call `super.show()` but have to call `show` on the `Widget` prototype.
|
||||
Widget.prototype.show.call(this);
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(
|
||||
() =>
|
||||
window.requestAnimationFrame(() => {
|
||||
this.editor.getControl().layoutContentWidget(this);
|
||||
resolve();
|
||||
}),
|
||||
0
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,54 +6,48 @@ import { DebugSessionManager as TheiaDebugSessionManager } from '@theia/debug/li
|
||||
|
||||
@injectable()
|
||||
export class DebugSessionManager extends TheiaDebugSessionManager {
|
||||
async start(
|
||||
options: DebugSessionOptions
|
||||
): Promise<DebugSession | undefined> {
|
||||
return this.progressService.withProgress(
|
||||
'Start...',
|
||||
'debug',
|
||||
async () => {
|
||||
try {
|
||||
// Only save when dirty. To avoid saving temporary sketches.
|
||||
// This is a quick fix for not saving the editor when there are no dirty editors.
|
||||
// // https://github.com/bcmi-labs/arduino-editor/pull/172#issuecomment-741831888
|
||||
if (this.shell.canSaveAll()) {
|
||||
await this.shell.saveAll();
|
||||
}
|
||||
await this.fireWillStartDebugSession();
|
||||
const resolved = await this.resolveConfiguration(options);
|
||||
async start(options: DebugSessionOptions): Promise<DebugSession | undefined> {
|
||||
return this.progressService.withProgress('Start...', 'debug', async () => {
|
||||
try {
|
||||
// Only save when dirty. To avoid saving temporary sketches.
|
||||
// This is a quick fix for not saving the editor when there are no dirty editors.
|
||||
// // https://github.com/bcmi-labs/arduino-editor/pull/172#issuecomment-741831888
|
||||
if (this.shell.canSaveAll()) {
|
||||
await this.shell.saveAll();
|
||||
}
|
||||
await this.fireWillStartDebugSession();
|
||||
const resolved = await this.resolveConfiguration(options);
|
||||
|
||||
// preLaunchTask isn't run in case of auto restart as well as postDebugTask
|
||||
if (!options.configuration.__restart) {
|
||||
const taskRun = await this.runTask(
|
||||
options.workspaceFolderUri,
|
||||
resolved.configuration.preLaunchTask,
|
||||
true
|
||||
);
|
||||
if (!taskRun) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
// preLaunchTask isn't run in case of auto restart as well as postDebugTask
|
||||
if (!options.configuration.__restart) {
|
||||
const taskRun = await this.runTask(
|
||||
options.workspaceFolderUri,
|
||||
resolved.configuration.preLaunchTask,
|
||||
true
|
||||
);
|
||||
if (!taskRun) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const sessionId = await this.debug.createDebugSession(
|
||||
resolved.configuration
|
||||
);
|
||||
return this.doStart(sessionId, resolved);
|
||||
} catch (e) {
|
||||
if (DebugError.NotFound.is(e)) {
|
||||
this.messageService.error(
|
||||
`The debug session type "${e.data.type}" is not supported.`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.messageService.error(
|
||||
'There was an error starting the debug session, check the logs for more details.'
|
||||
);
|
||||
console.error('Error starting the debug session', e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
const sessionId = await this.debug.createDebugSession(
|
||||
resolved.configuration
|
||||
);
|
||||
}
|
||||
return this.doStart(sessionId, resolved);
|
||||
} catch (e) {
|
||||
if (DebugError.NotFound.is(e)) {
|
||||
this.messageService.error(
|
||||
`The debug session type "${e.data.type}" is not supported.`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.messageService.error(
|
||||
'There was an error starting the debug session, check the logs for more details.'
|
||||
);
|
||||
console.error('Error starting the debug session', e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,19 @@ import { EditorCommandContribution as TheiaEditorCommandContribution } from '@th
|
||||
|
||||
@injectable()
|
||||
export class EditorCommandContribution extends TheiaEditorCommandContribution {
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/8722.
|
||||
this.editorPreferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
if (preferenceName === 'editor.autoSave') {
|
||||
const autoSaveWasOnBeforeChange =
|
||||
!oldValue || oldValue === 'on';
|
||||
const autoSaveIsOnAfterChange =
|
||||
!newValue || newValue === 'on';
|
||||
if (!autoSaveWasOnBeforeChange && autoSaveIsOnAfterChange) {
|
||||
this.shell.saveAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
// Workaround for https://github.com/eclipse-theia/theia/issues/8722.
|
||||
this.editorPreferences.onPreferenceChanged(
|
||||
({ preferenceName, newValue, oldValue }) => {
|
||||
if (preferenceName === 'editor.autoSave') {
|
||||
const autoSaveWasOnBeforeChange = !oldValue || oldValue === 'on';
|
||||
const autoSaveIsOnAfterChange = !newValue || newValue === 'on';
|
||||
if (!autoSaveWasOnBeforeChange && autoSaveIsOnAfterChange) {
|
||||
this.shell.saveAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,18 @@ import { StatusBarAlignment } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class EditorContribution extends TheiaEditorContribution {
|
||||
protected updateLanguageStatus(editor: TextEditor | undefined): void {}
|
||||
protected updateLanguageStatus(editor: TextEditor | undefined): void {}
|
||||
|
||||
protected setCursorPositionStatus(editor: TextEditor | undefined): void {
|
||||
if (!editor) {
|
||||
this.statusBar.removeElement('editor-status-cursor-position');
|
||||
return;
|
||||
}
|
||||
const { cursor } = editor;
|
||||
this.statusBar.setElement('editor-status-cursor-position', {
|
||||
text: `${cursor.line + 1}`,
|
||||
alignment: StatusBarAlignment.LEFT,
|
||||
priority: 100,
|
||||
});
|
||||
protected setCursorPositionStatus(editor: TextEditor | undefined): void {
|
||||
if (!editor) {
|
||||
this.statusBar.removeElement('editor-status-cursor-position');
|
||||
return;
|
||||
}
|
||||
const { cursor } = editor;
|
||||
this.statusBar.setElement('editor-status-cursor-position', {
|
||||
text: `${cursor.line + 1}`,
|
||||
alignment: StatusBarAlignment.LEFT,
|
||||
priority: 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,33 +8,31 @@ import { SketchesService, Sketch } from '../../../common/protocol';
|
||||
|
||||
@injectable()
|
||||
export class EditorWidgetFactory extends TheiaEditorWidgetFactory {
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchesService: SketchesService;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
|
||||
protected async createEditor(uri: URI): Promise<EditorWidget> {
|
||||
const widget = await super.createEditor(uri);
|
||||
return this.maybeUpdateCaption(widget);
|
||||
}
|
||||
|
||||
protected async maybeUpdateCaption(
|
||||
widget: EditorWidget
|
||||
): Promise<EditorWidget> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
const { uri } = widget.editor;
|
||||
if (sketch && Sketch.isInSketch(uri, sketch)) {
|
||||
const isTemp = await this.sketchesService.isTemp(sketch);
|
||||
if (isTemp) {
|
||||
widget.title.caption = `Unsaved – ${this.labelProvider.getName(
|
||||
uri
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
return widget;
|
||||
protected async createEditor(uri: URI): Promise<EditorWidget> {
|
||||
const widget = await super.createEditor(uri);
|
||||
return this.maybeUpdateCaption(widget);
|
||||
}
|
||||
|
||||
protected async maybeUpdateCaption(
|
||||
widget: EditorWidget
|
||||
): Promise<EditorWidget> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
const { uri } = widget.editor;
|
||||
if (sketch && Sketch.isInSketch(uri, sketch)) {
|
||||
const isTemp = await this.sketchesService.isTemp(sketch);
|
||||
if (isTemp) {
|
||||
widget.title.caption = `Unsaved – ${this.labelProvider.getName(uri)}`;
|
||||
}
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core';
|
||||
import {
|
||||
KeymapsFrontendContribution as TheiaKeymapsFrontendContribution,
|
||||
KeymapsCommands,
|
||||
KeymapsFrontendContribution as TheiaKeymapsFrontendContribution,
|
||||
KeymapsCommands,
|
||||
} from '@theia/keymaps/lib/browser/keymaps-frontend-contribution';
|
||||
import { ArduinoMenus } from '../../menu/arduino-menus';
|
||||
|
||||
@injectable()
|
||||
export class KeymapsFrontendContribution extends TheiaKeymapsFrontendContribution {
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, {
|
||||
commandId: KeymapsCommands.OPEN_KEYMAPS.id,
|
||||
label: 'Keyboard Shortcuts',
|
||||
order: '1',
|
||||
});
|
||||
}
|
||||
registerMenus(menus: MenuModelRegistry): void {
|
||||
menus.registerMenuAction(ArduinoMenus.FILE__ADVANCED_SUBMENU, {
|
||||
commandId: KeymapsCommands.OPEN_KEYMAPS.id,
|
||||
label: 'Keyboard Shortcuts',
|
||||
order: '1',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,20 @@ import { ProblemContribution as TheiaProblemContribution } from '@theia/markers/
|
||||
|
||||
@injectable()
|
||||
export class ProblemContribution extends TheiaProblemContribution {
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
protected setStatusBarElement(problemStat: ProblemStat): void {
|
||||
// NOOP
|
||||
}
|
||||
protected setStatusBarElement(problemStat: ProblemStat): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
if (this.toggleCommand) {
|
||||
keybindings.registerKeybinding({
|
||||
command: this.toggleCommand.id,
|
||||
keybinding: 'ctrlcmd+alt+shift+m',
|
||||
});
|
||||
}
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
if (this.toggleCommand) {
|
||||
keybindings.registerKeybinding({
|
||||
command: this.toggleCommand.id,
|
||||
keybinding: 'ctrlcmd+alt+shift+m',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,35 +8,33 @@ import { ConfigService } from '../../../common/protocol/config-service';
|
||||
|
||||
@injectable()
|
||||
export class ProblemManager extends TheiaProblemManager {
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
@inject(ILogger)
|
||||
protected readonly logger: ILogger;
|
||||
|
||||
protected dataDirUri: URI | undefined;
|
||||
protected dataDirUri: URI | undefined;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.configService
|
||||
.getConfiguration()
|
||||
.then(({ dataDirUri }) => (this.dataDirUri = new URI(dataDirUri)))
|
||||
.catch((err) =>
|
||||
this.logger.error(
|
||||
`Failed to determine the data directory: ${err}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setMarkers(
|
||||
uri: URI,
|
||||
owner: string,
|
||||
data: Diagnostic[]
|
||||
): Marker<Diagnostic>[] {
|
||||
if (this.dataDirUri && this.dataDirUri.isEqualOrParent(uri)) {
|
||||
return [];
|
||||
}
|
||||
return super.setMarkers(uri, owner, data);
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.configService
|
||||
.getConfiguration()
|
||||
.then(({ dataDirUri }) => (this.dataDirUri = new URI(dataDirUri)))
|
||||
.catch((err) =>
|
||||
this.logger.error(`Failed to determine the data directory: ${err}`)
|
||||
);
|
||||
}
|
||||
|
||||
setMarkers(
|
||||
uri: URI,
|
||||
owner: string,
|
||||
data: Diagnostic[]
|
||||
): Marker<Diagnostic>[] {
|
||||
if (this.dataDirUri && this.dataDirUri.isEqualOrParent(uri)) {
|
||||
return [];
|
||||
}
|
||||
return super.setMarkers(uri, owner, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,46 +5,44 @@ import { NotificationCenterComponent as TheiaNotificationCenterComponent } from
|
||||
const PerfectScrollbar = require('react-perfect-scrollbar');
|
||||
|
||||
export class NotificationCenterComponent extends TheiaNotificationCenterComponent {
|
||||
render(): React.ReactNode {
|
||||
const empty = this.state.notifications.length === 0;
|
||||
const title = empty ? 'NO NEW NOTIFICATIONS' : 'NOTIFICATIONS';
|
||||
return (
|
||||
<div
|
||||
className={`theia-notifications-container theia-notification-center ${
|
||||
this.state.visibilityState === 'center' ? 'open' : 'closed'
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-center-header">
|
||||
<div className="theia-notification-center-header-title">
|
||||
{title}
|
||||
</div>
|
||||
<div className="theia-notification-center-header-actions">
|
||||
<ul className="theia-notification-actions">
|
||||
<li
|
||||
className="collapse"
|
||||
title="Hide Notification Center"
|
||||
onClick={this.onHide}
|
||||
/>
|
||||
<li
|
||||
className="clear-all"
|
||||
title="Clear All"
|
||||
onClick={this.onClearAll}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<PerfectScrollbar className="theia-notification-list-scroll-container">
|
||||
<div className="theia-notification-list">
|
||||
{this.state.notifications.map((notification) => (
|
||||
<NotificationComponent
|
||||
key={notification.messageId}
|
||||
notification={notification}
|
||||
manager={this.props.manager}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PerfectScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render(): React.ReactNode {
|
||||
const empty = this.state.notifications.length === 0;
|
||||
const title = empty ? 'NO NEW NOTIFICATIONS' : 'NOTIFICATIONS';
|
||||
return (
|
||||
<div
|
||||
className={`theia-notifications-container theia-notification-center ${
|
||||
this.state.visibilityState === 'center' ? 'open' : 'closed'
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-center-header">
|
||||
<div className="theia-notification-center-header-title">{title}</div>
|
||||
<div className="theia-notification-center-header-actions">
|
||||
<ul className="theia-notification-actions">
|
||||
<li
|
||||
className="collapse"
|
||||
title="Hide Notification Center"
|
||||
onClick={this.onHide}
|
||||
/>
|
||||
<li
|
||||
className="clear-all"
|
||||
title="Clear All"
|
||||
onClick={this.onClearAll}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<PerfectScrollbar className="theia-notification-list-scroll-container">
|
||||
<div className="theia-notification-list">
|
||||
{this.state.notifications.map((notification) => (
|
||||
<NotificationComponent
|
||||
key={notification.messageId}
|
||||
notification={notification}
|
||||
manager={this.props.manager}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</PerfectScrollbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,105 +2,96 @@ import * as React from 'react';
|
||||
import { NotificationComponent as TheiaNotificationComponent } from '@theia/messages/lib/browser/notification-component';
|
||||
|
||||
export class NotificationComponent extends TheiaNotificationComponent {
|
||||
render(): React.ReactNode {
|
||||
const {
|
||||
messageId,
|
||||
message,
|
||||
type,
|
||||
collapsed,
|
||||
expandable,
|
||||
source,
|
||||
actions,
|
||||
} = this.props.notification;
|
||||
return (
|
||||
<div key={messageId} className="theia-notification-list-item">
|
||||
<div
|
||||
className={`theia-notification-list-item-content ${
|
||||
collapsed ? 'collapsed' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-list-item-content-main">
|
||||
<div
|
||||
className={`theia-notification-icon theia-notification-icon-${type}`}
|
||||
/>
|
||||
<div className="theia-notification-message">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: message }}
|
||||
onClick={this.onMessageClick}
|
||||
/>
|
||||
</div>
|
||||
<ul className="theia-notification-actions">
|
||||
{expandable && (
|
||||
<li
|
||||
className={
|
||||
collapsed ? 'expand' : 'collapse'
|
||||
}
|
||||
title={collapsed ? 'Expand' : 'Collapse'}
|
||||
data-message-id={messageId}
|
||||
onClick={this.onToggleExpansion}
|
||||
/>
|
||||
)}
|
||||
{!this.isProgress && (
|
||||
<li
|
||||
className="clear"
|
||||
title="Clear"
|
||||
data-message-id={messageId}
|
||||
onClick={this.onClear}
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
{(source || !!actions.length) && (
|
||||
<div className="theia-notification-list-item-content-bottom">
|
||||
<div className="theia-notification-source">
|
||||
{source && <span>{source}</span>}
|
||||
</div>
|
||||
<div className="theia-notification-buttons">
|
||||
{actions &&
|
||||
actions.map((action, index) => (
|
||||
<button
|
||||
key={messageId + `-action-${index}`}
|
||||
className="theia-button"
|
||||
data-message-id={messageId}
|
||||
data-action={action}
|
||||
onClick={this.onAction}
|
||||
>
|
||||
{action}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{this.renderProgressBar()}
|
||||
render(): React.ReactNode {
|
||||
const { messageId, message, type, collapsed, expandable, source, actions } =
|
||||
this.props.notification;
|
||||
return (
|
||||
<div key={messageId} className="theia-notification-list-item">
|
||||
<div
|
||||
className={`theia-notification-list-item-content ${
|
||||
collapsed ? 'collapsed' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-list-item-content-main">
|
||||
<div
|
||||
className={`theia-notification-icon theia-notification-icon-${type}`}
|
||||
/>
|
||||
<div className="theia-notification-message">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: message }}
|
||||
onClick={this.onMessageClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderProgressBar(): React.ReactNode {
|
||||
if (!this.isProgress) {
|
||||
return undefined;
|
||||
}
|
||||
if (!Number.isNaN(this.props.notification.progress)) {
|
||||
return (
|
||||
<div className="theia-notification-item-progress">
|
||||
<div
|
||||
className="theia-notification-item-progressbar"
|
||||
style={{
|
||||
width: `${this.props.notification.progress}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="theia-progress-bar-container">
|
||||
<div className="theia-progress-bar" />
|
||||
<ul className="theia-notification-actions">
|
||||
{expandable && (
|
||||
<li
|
||||
className={collapsed ? 'expand' : 'collapse'}
|
||||
title={collapsed ? 'Expand' : 'Collapse'}
|
||||
data-message-id={messageId}
|
||||
onClick={this.onToggleExpansion}
|
||||
/>
|
||||
)}
|
||||
{!this.isProgress && (
|
||||
<li
|
||||
className="clear"
|
||||
title="Clear"
|
||||
data-message-id={messageId}
|
||||
onClick={this.onClear}
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
{(source || !!actions.length) && (
|
||||
<div className="theia-notification-list-item-content-bottom">
|
||||
<div className="theia-notification-source">
|
||||
{source && <span>{source}</span>}
|
||||
</div>
|
||||
<div className="theia-notification-buttons">
|
||||
{actions &&
|
||||
actions.map((action, index) => (
|
||||
<button
|
||||
key={messageId + `-action-${index}`}
|
||||
className="theia-button"
|
||||
data-message-id={messageId}
|
||||
data-action={action}
|
||||
onClick={this.onAction}
|
||||
>
|
||||
{action}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
{this.renderProgressBar()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private get isProgress(): boolean {
|
||||
return typeof this.props.notification.progress === 'number';
|
||||
private renderProgressBar(): React.ReactNode {
|
||||
if (!this.isProgress) {
|
||||
return undefined;
|
||||
}
|
||||
if (!Number.isNaN(this.props.notification.progress)) {
|
||||
return (
|
||||
<div className="theia-notification-item-progress">
|
||||
<div
|
||||
className="theia-notification-item-progressbar"
|
||||
style={{
|
||||
width: `${this.props.notification.progress}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="theia-progress-bar-container">
|
||||
<div className="theia-progress-bar" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private get isProgress(): boolean {
|
||||
return typeof this.props.notification.progress === 'number';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,23 @@ import { NotificationComponent } from './notification-component';
|
||||
import { NotificationToastsComponent as TheiaNotificationToastsComponent } from '@theia/messages/lib/browser/notification-toasts-component';
|
||||
|
||||
export class NotificationToastsComponent extends TheiaNotificationToastsComponent {
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<div
|
||||
className={`theia-notifications-container theia-notification-toasts ${
|
||||
this.state.visibilityState === 'toasts' ? 'open' : 'closed'
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-list">
|
||||
{this.state.toasts.map((notification) => (
|
||||
<NotificationComponent
|
||||
key={notification.messageId}
|
||||
notification={notification}
|
||||
manager={this.props.manager}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render(): React.ReactNode {
|
||||
return (
|
||||
<div
|
||||
className={`theia-notifications-container theia-notification-toasts ${
|
||||
this.state.visibilityState === 'toasts' ? 'open' : 'closed'
|
||||
}`}
|
||||
>
|
||||
<div className="theia-notification-list">
|
||||
{this.state.toasts.map((notification) => (
|
||||
<NotificationComponent
|
||||
key={notification.messageId}
|
||||
notification={notification}
|
||||
manager={this.props.manager}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,46 @@
|
||||
import { injectable } from 'inversify';
|
||||
import { CancellationToken } from '@theia/core/lib/common/cancellation';
|
||||
import {
|
||||
ProgressMessage,
|
||||
ProgressUpdate,
|
||||
ProgressMessage,
|
||||
ProgressUpdate,
|
||||
} from '@theia/core/lib/common/message-service-protocol';
|
||||
import { NotificationManager as TheiaNotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||
|
||||
@injectable()
|
||||
export class NotificationManager extends TheiaNotificationManager {
|
||||
async reportProgress(
|
||||
messageId: string,
|
||||
update: ProgressUpdate,
|
||||
originalMessage: ProgressMessage,
|
||||
cancellationToken: CancellationToken
|
||||
): Promise<void> {
|
||||
const notification = this.find(messageId);
|
||||
if (!notification) {
|
||||
return;
|
||||
}
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
this.clear(messageId);
|
||||
} else {
|
||||
notification.message =
|
||||
originalMessage.text && update.message
|
||||
? `${originalMessage.text}: ${update.message}`
|
||||
: originalMessage.text ||
|
||||
update?.message ||
|
||||
notification.message;
|
||||
|
||||
// Unlike in Theia, we allow resetting the progress monitor to NaN to enforce unknown progress.
|
||||
const candidate = this.toPlainProgress(update);
|
||||
notification.progress =
|
||||
typeof candidate === 'number'
|
||||
? candidate
|
||||
: notification.progress;
|
||||
}
|
||||
this.fireUpdatedEvent();
|
||||
async reportProgress(
|
||||
messageId: string,
|
||||
update: ProgressUpdate,
|
||||
originalMessage: ProgressMessage,
|
||||
cancellationToken: CancellationToken
|
||||
): Promise<void> {
|
||||
const notification = this.find(messageId);
|
||||
if (!notification) {
|
||||
return;
|
||||
}
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
this.clear(messageId);
|
||||
} else {
|
||||
notification.message =
|
||||
originalMessage.text && update.message
|
||||
? `${originalMessage.text}: ${update.message}`
|
||||
: originalMessage.text || update?.message || notification.message;
|
||||
|
||||
protected toPlainProgress(update: ProgressUpdate): number | undefined {
|
||||
if (!update.work) {
|
||||
return undefined;
|
||||
}
|
||||
if (Number.isNaN(update.work.done) || Number.isNaN(update.work.total)) {
|
||||
return Number.NaN; // This should trigger the unknown monitor.
|
||||
}
|
||||
return Math.min((update.work.done / update.work.total) * 100, 100);
|
||||
// Unlike in Theia, we allow resetting the progress monitor to NaN to enforce unknown progress.
|
||||
const candidate = this.toPlainProgress(update);
|
||||
notification.progress =
|
||||
typeof candidate === 'number' ? candidate : notification.progress;
|
||||
}
|
||||
this.fireUpdatedEvent();
|
||||
}
|
||||
|
||||
protected toPlainProgress(update: ProgressUpdate): number | undefined {
|
||||
if (!update.work) {
|
||||
return undefined;
|
||||
}
|
||||
if (Number.isNaN(update.work.done) || Number.isNaN(update.work.total)) {
|
||||
return Number.NaN; // This should trigger the unknown monitor.
|
||||
}
|
||||
return Math.min((update.work.done / update.work.total) * 100, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,16 @@ import { NotificationsRenderer as TheiaNotificationsRenderer } from '@theia/mess
|
||||
|
||||
@injectable()
|
||||
export class NotificationsRenderer extends TheiaNotificationsRenderer {
|
||||
protected render(): void {
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<NotificationToastsComponent
|
||||
manager={this.manager}
|
||||
corePreferences={this.corePreferences}
|
||||
/>
|
||||
<NotificationCenterComponent manager={this.manager} />
|
||||
</div>,
|
||||
this.container
|
||||
);
|
||||
}
|
||||
protected render(): void {
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<NotificationToastsComponent
|
||||
manager={this.manager}
|
||||
corePreferences={this.corePreferences}
|
||||
/>
|
||||
<NotificationCenterComponent manager={this.manager} />
|
||||
</div>,
|
||||
this.container
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +1,97 @@
|
||||
import { inject, injectable } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
|
||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||
|
||||
type CancelablePromise = Promise<monaco.referenceSearch.ReferencesModel> & {
|
||||
cancel: () => void;
|
||||
cancel: () => void;
|
||||
};
|
||||
interface EditorFactory {
|
||||
(
|
||||
override: monaco.editor.IEditorOverrideServices,
|
||||
toDispose: DisposableCollection
|
||||
): Promise<MonacoEditor>;
|
||||
(
|
||||
override: monaco.editor.IEditorOverrideServices,
|
||||
toDispose: DisposableCollection
|
||||
): Promise<MonacoEditor>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class MonacoEditorProvider extends TheiaMonacoEditorProvider {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
protected async doCreateEditor(
|
||||
uri: URI,
|
||||
factory: EditorFactory
|
||||
): Promise<MonacoEditor> {
|
||||
const editor = await super.doCreateEditor(uri, factory);
|
||||
const toDispose = new DisposableCollection();
|
||||
toDispose.push(this.installCustomReferencesController(editor));
|
||||
toDispose.push(editor.onDispose(() => toDispose.dispose()));
|
||||
return editor;
|
||||
}
|
||||
protected async doCreateEditor(
|
||||
uri: URI,
|
||||
factory: EditorFactory
|
||||
): Promise<MonacoEditor> {
|
||||
const editor = await super.doCreateEditor(uri, factory);
|
||||
const toDispose = new DisposableCollection();
|
||||
toDispose.push(this.installCustomReferencesController(editor));
|
||||
toDispose.push(editor.onDispose(() => toDispose.dispose()));
|
||||
return editor;
|
||||
}
|
||||
|
||||
private installCustomReferencesController(
|
||||
editor: MonacoEditor
|
||||
): Disposable {
|
||||
const control = editor.getControl();
|
||||
const referencesController =
|
||||
control._contributions['editor.contrib.referencesController'];
|
||||
const originalToggleWidget = referencesController.toggleWidget;
|
||||
const toDispose = new DisposableCollection();
|
||||
const toDisposeBeforeToggleWidget = new DisposableCollection();
|
||||
referencesController.toggleWidget = (
|
||||
range: monaco.Range,
|
||||
modelPromise: CancelablePromise,
|
||||
peekMode: boolean
|
||||
) => {
|
||||
toDisposeBeforeToggleWidget.dispose();
|
||||
originalToggleWidget.bind(referencesController)(
|
||||
range,
|
||||
modelPromise,
|
||||
peekMode
|
||||
);
|
||||
if (referencesController._widget) {
|
||||
if ('onDidClose' in referencesController._widget) {
|
||||
toDisposeBeforeToggleWidget.push(
|
||||
(referencesController._widget as any).onDidClose(() =>
|
||||
toDisposeBeforeToggleWidget.dispose()
|
||||
)
|
||||
);
|
||||
}
|
||||
const preview = (referencesController._widget as any)
|
||||
._preview as monaco.editor.ICodeEditor;
|
||||
if (preview) {
|
||||
toDisposeBeforeToggleWidget.push(
|
||||
preview.onDidChangeModel(() =>
|
||||
this.updateReadOnlyState(preview)
|
||||
)
|
||||
);
|
||||
this.updateReadOnlyState(preview);
|
||||
}
|
||||
}
|
||||
};
|
||||
toDispose.push(
|
||||
Disposable.create(() => toDisposeBeforeToggleWidget.dispose())
|
||||
);
|
||||
toDispose.push(
|
||||
Disposable.create(
|
||||
() => (referencesController.toggleWidget = originalToggleWidget)
|
||||
private installCustomReferencesController(editor: MonacoEditor): Disposable {
|
||||
const control = editor.getControl();
|
||||
const referencesController =
|
||||
control._contributions['editor.contrib.referencesController'];
|
||||
const originalToggleWidget = referencesController.toggleWidget;
|
||||
const toDispose = new DisposableCollection();
|
||||
const toDisposeBeforeToggleWidget = new DisposableCollection();
|
||||
referencesController.toggleWidget = (
|
||||
range: monaco.Range,
|
||||
modelPromise: CancelablePromise,
|
||||
peekMode: boolean
|
||||
) => {
|
||||
toDisposeBeforeToggleWidget.dispose();
|
||||
originalToggleWidget.bind(referencesController)(
|
||||
range,
|
||||
modelPromise,
|
||||
peekMode
|
||||
);
|
||||
if (referencesController._widget) {
|
||||
if ('onDidClose' in referencesController._widget) {
|
||||
toDisposeBeforeToggleWidget.push(
|
||||
(referencesController._widget as any).onDidClose(() =>
|
||||
toDisposeBeforeToggleWidget.dispose()
|
||||
)
|
||||
);
|
||||
return toDispose;
|
||||
}
|
||||
);
|
||||
}
|
||||
const preview = (referencesController._widget as any)
|
||||
._preview as monaco.editor.ICodeEditor;
|
||||
if (preview) {
|
||||
toDisposeBeforeToggleWidget.push(
|
||||
preview.onDidChangeModel(() => this.updateReadOnlyState(preview))
|
||||
);
|
||||
this.updateReadOnlyState(preview);
|
||||
}
|
||||
}
|
||||
};
|
||||
toDispose.push(
|
||||
Disposable.create(() => toDisposeBeforeToggleWidget.dispose())
|
||||
);
|
||||
toDispose.push(
|
||||
Disposable.create(
|
||||
() => (referencesController.toggleWidget = originalToggleWidget)
|
||||
)
|
||||
);
|
||||
return toDispose;
|
||||
}
|
||||
|
||||
private updateReadOnlyState(
|
||||
editor: monaco.editor.ICodeEditor | undefined
|
||||
): void {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const readOnly = this.sketchesServiceClient.isReadOnly(model.uri);
|
||||
editor.updateOptions({ readOnly });
|
||||
private updateReadOnlyState(
|
||||
editor: monaco.editor.ICodeEditor | undefined
|
||||
): void {
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const readOnly = this.sketchesServiceClient.isReadOnly(model.uri);
|
||||
editor.updateOptions({ readOnly });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from
|
||||
|
||||
@injectable()
|
||||
export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution {
|
||||
protected setConfigTabSizeWidget() {}
|
||||
protected setConfigTabSizeWidget() {}
|
||||
|
||||
protected setLineEndingWidget() {}
|
||||
protected setLineEndingWidget() {}
|
||||
}
|
||||
|
||||
@@ -10,73 +10,71 @@ import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-ser
|
||||
|
||||
@injectable()
|
||||
export class MonacoTextModelService extends TheiaMonacoTextModelService {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
protected async createModel(
|
||||
resource: Resource
|
||||
): Promise<MonacoEditorModel> {
|
||||
const factory = this.factories
|
||||
.getContributions()
|
||||
.find(({ scheme }) => resource.uri.scheme === scheme);
|
||||
const readOnly = this.sketchesServiceClient.isReadOnly(resource.uri);
|
||||
return factory
|
||||
? factory.createModel(resource)
|
||||
: new MaybeReadonlyMonacoEditorModel(
|
||||
resource,
|
||||
this.m2p,
|
||||
this.p2m,
|
||||
this.logger,
|
||||
undefined,
|
||||
readOnly
|
||||
);
|
||||
}
|
||||
protected async createModel(resource: Resource): Promise<MonacoEditorModel> {
|
||||
const factory = this.factories
|
||||
.getContributions()
|
||||
.find(({ scheme }) => resource.uri.scheme === scheme);
|
||||
const readOnly = this.sketchesServiceClient.isReadOnly(resource.uri);
|
||||
return factory
|
||||
? factory.createModel(resource)
|
||||
: new MaybeReadonlyMonacoEditorModel(
|
||||
resource,
|
||||
this.m2p,
|
||||
this.p2m,
|
||||
this.logger,
|
||||
undefined,
|
||||
readOnly
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/eclipse-theia/theia/pull/8491
|
||||
class SilentMonacoEditorModel extends MonacoEditorModel {
|
||||
protected trace(loggable: Loggable): void {
|
||||
if (this.logger) {
|
||||
this.logger.trace((log: Log) =>
|
||||
loggable((message, ...params) =>
|
||||
log(message, ...params, this.resource.uri.toString(true))
|
||||
)
|
||||
);
|
||||
}
|
||||
protected trace(loggable: Loggable): void {
|
||||
if (this.logger) {
|
||||
this.logger.trace((log: Log) =>
|
||||
loggable((message, ...params) =>
|
||||
log(message, ...params, this.resource.uri.toString(true))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MaybeReadonlyMonacoEditorModel extends SilentMonacoEditorModel {
|
||||
constructor(
|
||||
protected readonly resource: Resource,
|
||||
protected readonly m2p: MonacoToProtocolConverter,
|
||||
protected readonly p2m: ProtocolToMonacoConverter,
|
||||
protected readonly logger?: ILogger,
|
||||
protected readonly editorPreferences?: EditorPreferences,
|
||||
protected readonly _readOnly?: boolean
|
||||
) {
|
||||
super(resource, m2p, p2m, logger, editorPreferences);
|
||||
}
|
||||
constructor(
|
||||
protected readonly resource: Resource,
|
||||
protected readonly m2p: MonacoToProtocolConverter,
|
||||
protected readonly p2m: ProtocolToMonacoConverter,
|
||||
protected readonly logger?: ILogger,
|
||||
protected readonly editorPreferences?: EditorPreferences,
|
||||
protected readonly _readOnly?: boolean
|
||||
) {
|
||||
super(resource, m2p, p2m, logger, editorPreferences);
|
||||
}
|
||||
|
||||
get readOnly(): boolean {
|
||||
if (typeof this._readOnly === 'boolean') {
|
||||
return this._readOnly;
|
||||
}
|
||||
return this.resource.saveContents === undefined;
|
||||
get readOnly(): boolean {
|
||||
if (typeof this._readOnly === 'boolean') {
|
||||
return this._readOnly;
|
||||
}
|
||||
return this.resource.saveContents === undefined;
|
||||
}
|
||||
|
||||
protected setDirty(dirty: boolean): void {
|
||||
if (this._readOnly === true) {
|
||||
// NOOP
|
||||
return;
|
||||
}
|
||||
if (dirty === this._dirty) {
|
||||
return;
|
||||
}
|
||||
this._dirty = dirty;
|
||||
if (dirty === false) {
|
||||
(this as any).updateSavedVersionId();
|
||||
}
|
||||
this.onDirtyChangedEmitter.fire(undefined);
|
||||
protected setDirty(dirty: boolean): void {
|
||||
if (this._readOnly === true) {
|
||||
// NOOP
|
||||
return;
|
||||
}
|
||||
if (dirty === this._dirty) {
|
||||
return;
|
||||
}
|
||||
this._dirty = dirty;
|
||||
if (dirty === false) {
|
||||
(this as any).updateSavedVersionId();
|
||||
}
|
||||
this.onDirtyChangedEmitter.fire(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,35 +11,35 @@ import { WorkspacePreferences } from '@theia/workspace/lib/browser/workspace-pre
|
||||
|
||||
@injectable()
|
||||
export class FileNavigatorContribution extends TheiaFileNavigatorContribution {
|
||||
constructor(
|
||||
@inject(FileNavigatorPreferences)
|
||||
protected readonly fileNavigatorPreferences: FileNavigatorPreferences,
|
||||
@inject(OpenerService) protected readonly openerService: OpenerService,
|
||||
@inject(FileNavigatorFilter)
|
||||
protected readonly fileNavigatorFilter: FileNavigatorFilter,
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService,
|
||||
@inject(WorkspacePreferences)
|
||||
protected readonly workspacePreferences: WorkspacePreferences
|
||||
) {
|
||||
super(
|
||||
fileNavigatorPreferences,
|
||||
openerService,
|
||||
fileNavigatorFilter,
|
||||
workspaceService,
|
||||
workspacePreferences
|
||||
);
|
||||
this.options.defaultWidgetOptions.rank = 1;
|
||||
}
|
||||
constructor(
|
||||
@inject(FileNavigatorPreferences)
|
||||
protected readonly fileNavigatorPreferences: FileNavigatorPreferences,
|
||||
@inject(OpenerService) protected readonly openerService: OpenerService,
|
||||
@inject(FileNavigatorFilter)
|
||||
protected readonly fileNavigatorFilter: FileNavigatorFilter,
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService,
|
||||
@inject(WorkspacePreferences)
|
||||
protected readonly workspacePreferences: WorkspacePreferences
|
||||
) {
|
||||
super(
|
||||
fileNavigatorPreferences,
|
||||
openerService,
|
||||
fileNavigatorFilter,
|
||||
workspaceService,
|
||||
workspacePreferences
|
||||
);
|
||||
this.options.defaultWidgetOptions.rank = 1;
|
||||
}
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
[WorkspaceCommands.FILE_RENAME, WorkspaceCommands.FILE_DELETE].forEach(
|
||||
registry.unregisterKeybinding.bind(registry)
|
||||
);
|
||||
}
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
[WorkspaceCommands.FILE_RENAME, WorkspaceCommands.FILE_DELETE].forEach(
|
||||
registry.unregisterKeybinding.bind(registry)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import { NavigatorTabBarDecorator as TheiaNavigatorTabBarDecorator } from '@thei
|
||||
*/
|
||||
@injectable()
|
||||
export class NavigatorTabBarDecorator extends TheiaNavigatorTabBarDecorator {
|
||||
onStart(): void {
|
||||
// NOOP
|
||||
}
|
||||
onStart(): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
decorate(): WidgetDecoration.Data[] {
|
||||
// Does not decorate anything.
|
||||
return [];
|
||||
}
|
||||
decorate(): WidgetDecoration.Data[] {
|
||||
// Does not decorate anything.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import { OutlineViewContribution as TheiaOutlineViewContribution } from '@theia/
|
||||
|
||||
@injectable()
|
||||
export class OutlineViewContribution extends TheiaOutlineViewContribution {
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions = {
|
||||
area: 'left',
|
||||
rank: 500,
|
||||
};
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions = {
|
||||
area: 'left',
|
||||
rank: 500,
|
||||
};
|
||||
}
|
||||
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
async initializeLayout(app: FrontendApplication): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,55 +5,50 @@ import { OutputUri } from '@theia/output/lib/common/output-uri';
|
||||
import { IReference } from '@theia/monaco/lib/browser/monaco-text-model-service';
|
||||
import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
|
||||
import {
|
||||
OutputChannelManager as TheiaOutputChannelManager,
|
||||
OutputChannel as TheiaOutputChannel,
|
||||
OutputChannelManager as TheiaOutputChannelManager,
|
||||
OutputChannel as TheiaOutputChannel,
|
||||
} from '@theia/output/lib/common/output-channel';
|
||||
|
||||
@injectable()
|
||||
export class OutputChannelManager extends TheiaOutputChannelManager {
|
||||
getChannel(name: string): TheiaOutputChannel {
|
||||
const existing = this.channels.get(name);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
// We have to register the resource first, because `textModelService#createModelReference` will require it
|
||||
// right after creating the monaco.editor.ITextModel.
|
||||
// All `append` and `appendLine` will be deferred until the underlying text-model instantiation.
|
||||
let resource = this.resources.get(name);
|
||||
if (!resource) {
|
||||
const uri = OutputUri.create(name);
|
||||
const editorModelRef = new Deferred<
|
||||
IReference<MonacoEditorModel>
|
||||
>();
|
||||
resource = this.createResource({ uri, editorModelRef });
|
||||
this.resources.set(name, resource);
|
||||
this.textModelService
|
||||
.createModelReference(uri)
|
||||
.then((ref) => editorModelRef.resolve(ref));
|
||||
}
|
||||
|
||||
const channel = new OutputChannel(resource, this.preferences);
|
||||
this.channels.set(name, channel);
|
||||
this.toDisposeOnChannelDeletion.set(
|
||||
name,
|
||||
this.registerListeners(channel)
|
||||
);
|
||||
this.channelAddedEmitter.fire(channel);
|
||||
if (!this.selectedChannel) {
|
||||
this.selectedChannel = channel;
|
||||
}
|
||||
return channel;
|
||||
getChannel(name: string): TheiaOutputChannel {
|
||||
const existing = this.channels.get(name);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
// We have to register the resource first, because `textModelService#createModelReference` will require it
|
||||
// right after creating the monaco.editor.ITextModel.
|
||||
// All `append` and `appendLine` will be deferred until the underlying text-model instantiation.
|
||||
let resource = this.resources.get(name);
|
||||
if (!resource) {
|
||||
const uri = OutputUri.create(name);
|
||||
const editorModelRef = new Deferred<IReference<MonacoEditorModel>>();
|
||||
resource = this.createResource({ uri, editorModelRef });
|
||||
this.resources.set(name, resource);
|
||||
this.textModelService
|
||||
.createModelReference(uri)
|
||||
.then((ref) => editorModelRef.resolve(ref));
|
||||
}
|
||||
|
||||
const channel = new OutputChannel(resource, this.preferences);
|
||||
this.channels.set(name, channel);
|
||||
this.toDisposeOnChannelDeletion.set(name, this.registerListeners(channel));
|
||||
this.channelAddedEmitter.fire(channel);
|
||||
if (!this.selectedChannel) {
|
||||
this.selectedChannel = channel;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
export class OutputChannel extends TheiaOutputChannel {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
if ((this as any).disposed) {
|
||||
const textModifyQueue: PQueue = (this as any).textModifyQueue;
|
||||
textModifyQueue.pause();
|
||||
textModifyQueue.clear();
|
||||
}
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
if ((this as any).disposed) {
|
||||
const textModifyQueue: PQueue = (this as any).textModifyQueue;
|
||||
textModifyQueue.pause();
|
||||
textModifyQueue.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { injectable } from 'inversify';
|
||||
import {
|
||||
ReactTabBarToolbarItem,
|
||||
TabBarToolbarItem,
|
||||
TabBarToolbarRegistry,
|
||||
ReactTabBarToolbarItem,
|
||||
TabBarToolbarItem,
|
||||
TabBarToolbarRegistry,
|
||||
} from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { OutputToolbarContribution as TheiaOutputToolbarContribution } from '@theia/output/lib/browser/output-toolbar-contribution';
|
||||
|
||||
@injectable()
|
||||
export class OutputToolbarContribution extends TheiaOutputToolbarContribution {
|
||||
async registerToolbarItems(registry: TabBarToolbarRegistry): Promise<void> {
|
||||
await super.registerToolbarItems(registry); // Why is it async?
|
||||
// It's a hack. Currently, it's not possible to unregister a toolbar contribution via API.
|
||||
(
|
||||
(registry as any).items as Map<
|
||||
string,
|
||||
TabBarToolbarItem | ReactTabBarToolbarItem
|
||||
>
|
||||
).delete('channels');
|
||||
(registry as any).fireOnDidChange();
|
||||
}
|
||||
async registerToolbarItems(registry: TabBarToolbarRegistry): Promise<void> {
|
||||
await super.registerToolbarItems(registry); // Why is it async?
|
||||
// It's a hack. Currently, it's not possible to unregister a toolbar contribution via API.
|
||||
(
|
||||
(registry as any).items as Map<
|
||||
string,
|
||||
TabBarToolbarItem | ReactTabBarToolbarItem
|
||||
>
|
||||
).delete('channels');
|
||||
(registry as any).fireOnDidChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/out
|
||||
// Remove this module after ATL-222 and the Theia update.
|
||||
@injectable()
|
||||
export class OutputWidget extends TheiaOutputWidget {
|
||||
protected onAfterShow(msg: Message): void {
|
||||
super.onAfterShow(msg);
|
||||
this.onResize(Widget.ResizeMessage.UnknownSize);
|
||||
}
|
||||
protected onAfterShow(msg: Message): void {
|
||||
super.onAfterShow(msg);
|
||||
this.onResize(Widget.ResizeMessage.UnknownSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,41 +6,41 @@ import { OutputChannelRegistryMainImpl as TheiaOutputChannelRegistryMainImpl } f
|
||||
|
||||
@injectable()
|
||||
export class OutputChannelRegistryMainImpl extends TheiaOutputChannelRegistryMainImpl {
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
$append(
|
||||
name: string,
|
||||
text: string,
|
||||
pluginInfo: PluginInfo
|
||||
): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.APPEND.id, {
|
||||
name,
|
||||
text,
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
$append(
|
||||
name: string,
|
||||
text: string,
|
||||
pluginInfo: PluginInfo
|
||||
): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.APPEND.id, {
|
||||
name,
|
||||
text,
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
$clear(name: string): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.CLEAR.id, { name });
|
||||
return Promise.resolve();
|
||||
}
|
||||
$clear(name: string): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.CLEAR.id, { name });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
$dispose(name: string): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.DISPOSE.id, { name });
|
||||
return Promise.resolve();
|
||||
}
|
||||
$dispose(name: string): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.DISPOSE.id, { name });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async $reveal(name: string, preserveFocus: boolean): Promise<void> {
|
||||
const options = { preserveFocus };
|
||||
this.commandService.executeCommand(OutputCommands.SHOW.id, {
|
||||
name,
|
||||
options,
|
||||
});
|
||||
}
|
||||
async $reveal(name: string, preserveFocus: boolean): Promise<void> {
|
||||
const options = { preserveFocus };
|
||||
this.commandService.executeCommand(OutputCommands.SHOW.id, {
|
||||
name,
|
||||
options,
|
||||
});
|
||||
}
|
||||
|
||||
$close(name: string): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.HIDE.id, { name });
|
||||
return Promise.resolve();
|
||||
}
|
||||
$close(name: string): PromiseLike<void> {
|
||||
this.commandService.executeCommand(OutputCommands.HIDE.id, { name });
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ import { PreferencesContribution as TheiaPreferencesContribution } from '@theia/
|
||||
|
||||
@injectable()
|
||||
export class PreferencesContribution extends TheiaPreferencesContribution {
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
// The settings group: preferences, CLI config is not part of the `File` menu on macOS.
|
||||
// On Windows and Linux, we rebind it to `Preferences...`. It is safe to remove here.
|
||||
registry.unregisterMenuAction(
|
||||
CommonCommands.OPEN_PREFERENCES.id,
|
||||
CommonMenus.FILE_SETTINGS_SUBMENU_OPEN
|
||||
);
|
||||
}
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
// The settings group: preferences, CLI config is not part of the `File` menu on macOS.
|
||||
// On Windows and Linux, we rebind it to `Preferences...`. It is safe to remove here.
|
||||
registry.unregisterMenuAction(
|
||||
CommonCommands.OPEN_PREFERENCES.id,
|
||||
CommonMenus.FILE_SETTINGS_SUBMENU_OPEN
|
||||
);
|
||||
}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id);
|
||||
}
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
registry.unregisterKeybinding(CommonCommands.OPEN_PREFERENCES.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import { StatusBarEntry } from '@theia/core/lib/browser/status-bar/status-bar';
|
||||
|
||||
@injectable()
|
||||
export class ScmContribution extends TheiaScmContribution {
|
||||
async initializeLayout(): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
async initializeLayout(): Promise<void> {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||
// NOOP
|
||||
}
|
||||
protected setStatusBarEntry(id: string, entry: StatusBarEntry): void {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,28 +2,24 @@ import { injectable } from 'inversify';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import {
|
||||
SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution,
|
||||
SearchInWorkspaceCommands,
|
||||
SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution,
|
||||
SearchInWorkspaceCommands,
|
||||
} from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class SearchInWorkspaceFrontendContribution extends TheiaSearchInWorkspaceFrontendContribution {
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions.rank = 5;
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.options.defaultWidgetOptions.rank = 5;
|
||||
}
|
||||
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
registry.unregisterMenuAction(
|
||||
SearchInWorkspaceCommands.OPEN_SIW_WIDGET
|
||||
);
|
||||
}
|
||||
registerMenus(registry: MenuModelRegistry): void {
|
||||
super.registerMenus(registry);
|
||||
registry.unregisterMenuAction(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
|
||||
}
|
||||
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
super.registerKeybindings(keybindings);
|
||||
keybindings.unregisterKeybinding(
|
||||
SearchInWorkspaceCommands.OPEN_SIW_WIDGET
|
||||
);
|
||||
}
|
||||
registerKeybindings(keybindings: KeybindingRegistry): void {
|
||||
super.registerKeybindings(keybindings);
|
||||
keybindings.unregisterKeybinding(SearchInWorkspaceCommands.OPEN_SIW_WIDGET);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { injectable } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { MEMORY_TEXT } from '@theia/search-in-workspace/lib/browser/in-memory-text-resource';
|
||||
import {
|
||||
SearchInWorkspaceFileNode,
|
||||
SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget,
|
||||
SearchInWorkspaceFileNode,
|
||||
SearchInWorkspaceResultTreeWidget as TheiaSearchInWorkspaceResultTreeWidget,
|
||||
} from '@theia/search-in-workspace/lib/browser/search-in-workspace-result-tree-widget';
|
||||
|
||||
/**
|
||||
@@ -11,36 +11,34 @@ import {
|
||||
*/
|
||||
@injectable()
|
||||
export class SearchInWorkspaceResultTreeWidget extends TheiaSearchInWorkspaceResultTreeWidget {
|
||||
protected async createReplacePreview(
|
||||
node: SearchInWorkspaceFileNode
|
||||
): Promise<URI> {
|
||||
const fileUri = new URI(node.fileUri).withScheme('file');
|
||||
const openedEditor = this.editorManager.all.find(
|
||||
({ editor }) => editor.uri.toString() === fileUri.toString()
|
||||
);
|
||||
let content: string;
|
||||
if (openedEditor) {
|
||||
content = openedEditor.editor.document.getText();
|
||||
} else {
|
||||
const resource = await this.fileResourceResolver.resolve(fileUri);
|
||||
content = await resource.readContents();
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
node.children.map((l) => {
|
||||
const leftPositionedNodes = node.children.filter(
|
||||
(rl) => rl.line === l.line && rl.character < l.character
|
||||
);
|
||||
const diff =
|
||||
(this._replaceTerm.length - this.searchTerm.length) *
|
||||
leftPositionedNodes.length;
|
||||
const start = lines[l.line - 1].substr(0, l.character - 1 + diff);
|
||||
const end = lines[l.line - 1].substr(
|
||||
l.character - 1 + diff + l.length
|
||||
);
|
||||
lines[l.line - 1] = start + this._replaceTerm + end;
|
||||
});
|
||||
|
||||
return fileUri.withScheme(MEMORY_TEXT).withQuery(lines.join('\n'));
|
||||
protected async createReplacePreview(
|
||||
node: SearchInWorkspaceFileNode
|
||||
): Promise<URI> {
|
||||
const fileUri = new URI(node.fileUri).withScheme('file');
|
||||
const openedEditor = this.editorManager.all.find(
|
||||
({ editor }) => editor.uri.toString() === fileUri.toString()
|
||||
);
|
||||
let content: string;
|
||||
if (openedEditor) {
|
||||
content = openedEditor.editor.document.getText();
|
||||
} else {
|
||||
const resource = await this.fileResourceResolver.resolve(fileUri);
|
||||
content = await resource.readContents();
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
node.children.map((l) => {
|
||||
const leftPositionedNodes = node.children.filter(
|
||||
(rl) => rl.line === l.line && rl.character < l.character
|
||||
);
|
||||
const diff =
|
||||
(this._replaceTerm.length - this.searchTerm.length) *
|
||||
leftPositionedNodes.length;
|
||||
const start = lines[l.line - 1].substr(0, l.character - 1 + diff);
|
||||
const end = lines[l.line - 1].substr(l.character - 1 + diff + l.length);
|
||||
lines[l.line - 1] = start + this._replaceTerm + end;
|
||||
});
|
||||
|
||||
return fileUri.withScheme(MEMORY_TEXT).withQuery(lines.join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,76 +8,67 @@ import { SearchInWorkspaceWidget as TheiaSearchInWorkspaceWidget } from '@theia/
|
||||
*/
|
||||
@injectable()
|
||||
export class SearchInWorkspaceWidget extends TheiaSearchInWorkspaceWidget {
|
||||
protected renderGlobField(kind: 'include' | 'exclude'): React.ReactNode {
|
||||
const currentValue = this.searchInWorkspaceOptions[kind];
|
||||
const value = (currentValue && currentValue.join(', ')) || '';
|
||||
return (
|
||||
<div className="glob-field">
|
||||
<div className="label">{'files to ' + kind}</div>
|
||||
<input
|
||||
className="theia-input"
|
||||
type="text"
|
||||
size={1}
|
||||
defaultValue={value}
|
||||
id={kind + '-glob-field'}
|
||||
onKeyUp={(e) => {
|
||||
if (e.target) {
|
||||
const targetValue =
|
||||
(e.target as HTMLInputElement).value || '';
|
||||
let shouldSearch =
|
||||
Key.ENTER.keyCode ===
|
||||
KeyCode.createKeyCode(e.nativeEvent).key
|
||||
?.keyCode;
|
||||
const currentOptions = (
|
||||
this.searchInWorkspaceOptions[kind] || []
|
||||
)
|
||||
.slice()
|
||||
.map((s) => s.trim())
|
||||
.sort();
|
||||
const candidateOptions = this.splitOnComma(
|
||||
targetValue
|
||||
)
|
||||
.map((s) => s.trim())
|
||||
.sort();
|
||||
const sameAs = (
|
||||
left: string[],
|
||||
right: string[]
|
||||
) => {
|
||||
if (left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
if (left[i] !== right[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!sameAs(currentOptions, candidateOptions)) {
|
||||
this.searchInWorkspaceOptions[kind] =
|
||||
this.splitOnComma(targetValue);
|
||||
shouldSearch = true;
|
||||
}
|
||||
if (shouldSearch) {
|
||||
this.resultTreeWidget.search(
|
||||
this.searchTerm,
|
||||
this.searchInWorkspaceOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onFocus={
|
||||
kind === 'include'
|
||||
? this.handleFocusIncludesInputBox
|
||||
: this.handleFocusExcludesInputBox
|
||||
}
|
||||
onBlur={
|
||||
kind === 'include'
|
||||
? this.handleBlurIncludesInputBox
|
||||
: this.handleBlurExcludesInputBox
|
||||
}
|
||||
></input>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
protected renderGlobField(kind: 'include' | 'exclude'): React.ReactNode {
|
||||
const currentValue = this.searchInWorkspaceOptions[kind];
|
||||
const value = (currentValue && currentValue.join(', ')) || '';
|
||||
return (
|
||||
<div className="glob-field">
|
||||
<div className="label">{'files to ' + kind}</div>
|
||||
<input
|
||||
className="theia-input"
|
||||
type="text"
|
||||
size={1}
|
||||
defaultValue={value}
|
||||
id={kind + '-glob-field'}
|
||||
onKeyUp={(e) => {
|
||||
if (e.target) {
|
||||
const targetValue = (e.target as HTMLInputElement).value || '';
|
||||
let shouldSearch =
|
||||
Key.ENTER.keyCode ===
|
||||
KeyCode.createKeyCode(e.nativeEvent).key?.keyCode;
|
||||
const currentOptions = (this.searchInWorkspaceOptions[kind] || [])
|
||||
.slice()
|
||||
.map((s) => s.trim())
|
||||
.sort();
|
||||
const candidateOptions = this.splitOnComma(targetValue)
|
||||
.map((s) => s.trim())
|
||||
.sort();
|
||||
const sameAs = (left: string[], right: string[]) => {
|
||||
if (left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
if (left[i] !== right[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!sameAs(currentOptions, candidateOptions)) {
|
||||
this.searchInWorkspaceOptions[kind] =
|
||||
this.splitOnComma(targetValue);
|
||||
shouldSearch = true;
|
||||
}
|
||||
if (shouldSearch) {
|
||||
this.resultTreeWidget.search(
|
||||
this.searchTerm,
|
||||
this.searchInWorkspaceOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onFocus={
|
||||
kind === 'include'
|
||||
? this.handleFocusIncludesInputBox
|
||||
: this.handleFocusExcludesInputBox
|
||||
}
|
||||
onBlur={
|
||||
kind === 'include'
|
||||
? this.handleBlurIncludesInputBox
|
||||
: this.handleBlurExcludesInputBox
|
||||
}
|
||||
></input>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,14 @@ import { inject, injectable } from 'inversify';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { open } from '@theia/core/lib/browser/opener-service';
|
||||
import { FileStat } from '@theia/filesystem/lib/common/files';
|
||||
import { CommandRegistry, CommandService } from '@theia/core/lib/common/command';
|
||||
import { WorkspaceCommandContribution as TheiaWorkspaceCommandContribution, WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import {
|
||||
CommandRegistry,
|
||||
CommandService,
|
||||
} from '@theia/core/lib/common/command';
|
||||
import {
|
||||
WorkspaceCommandContribution as TheiaWorkspaceCommandContribution,
|
||||
WorkspaceCommands,
|
||||
} from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import { Sketch, SketchesService } from '../../../common/protocol';
|
||||
import { WorkspaceInputDialog } from './workspace-input-dialog';
|
||||
import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
|
||||
@@ -12,138 +18,158 @@ import { SingleTextInputDialog } from '@theia/core/lib/browser';
|
||||
|
||||
@injectable()
|
||||
export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribution {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
|
||||
@inject(CommandService)
|
||||
protected readonly commandService: CommandService;
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchService: SketchesService;
|
||||
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchService: SketchesService;
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
registry.unregisterCommand(WorkspaceCommands.NEW_FILE);
|
||||
registry.registerCommand(
|
||||
WorkspaceCommands.NEW_FILE,
|
||||
this.newWorkspaceRootUriAwareCommandHandler({
|
||||
execute: (uri) => this.newFile(uri),
|
||||
})
|
||||
);
|
||||
registry.unregisterCommand(WorkspaceCommands.FILE_RENAME);
|
||||
registry.registerCommand(
|
||||
WorkspaceCommands.FILE_RENAME,
|
||||
this.newUriAwareCommandHandler({
|
||||
execute: (uri) => this.renameFile(uri),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
registry.unregisterCommand(WorkspaceCommands.NEW_FILE);
|
||||
registry.registerCommand(WorkspaceCommands.NEW_FILE, this.newWorkspaceRootUriAwareCommandHandler({
|
||||
execute: uri => this.newFile(uri)
|
||||
}));
|
||||
registry.unregisterCommand(WorkspaceCommands.FILE_RENAME);
|
||||
registry.registerCommand(WorkspaceCommands.FILE_RENAME, this.newUriAwareCommandHandler({
|
||||
execute: uri => this.renameFile(uri)
|
||||
}));
|
||||
protected async newFile(uri: URI | undefined): Promise<void> {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
const parent = await this.getDirectory(uri);
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
protected async newFile(uri: URI | undefined): Promise<void> {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
const parent = await this.getDirectory(uri);
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
const parentUri = parent.resource;
|
||||
const dialog = new WorkspaceInputDialog(
|
||||
{
|
||||
title: 'Name for new file',
|
||||
parentUri,
|
||||
validate: (name) => this.validateFileName(name, parent, true),
|
||||
},
|
||||
this.labelProvider
|
||||
);
|
||||
|
||||
const parentUri = parent.resource;
|
||||
const dialog = new WorkspaceInputDialog({
|
||||
title: 'Name for new file',
|
||||
parentUri,
|
||||
validate: name => this.validateFileName(name, parent, true)
|
||||
}, this.labelProvider);
|
||||
const name = await dialog.open();
|
||||
const nameWithExt = this.maybeAppendInoExt(name);
|
||||
if (nameWithExt) {
|
||||
const fileUri = parentUri.resolve(nameWithExt);
|
||||
await this.fileService.createFile(fileUri);
|
||||
this.fireCreateNewFile({ parent: parentUri, uri: fileUri });
|
||||
open(this.openerService, fileUri);
|
||||
}
|
||||
}
|
||||
|
||||
const name = await dialog.open();
|
||||
const nameWithExt = this.maybeAppendInoExt(name);
|
||||
if (nameWithExt) {
|
||||
const fileUri = parentUri.resolve(nameWithExt);
|
||||
await this.fileService.createFile(fileUri);
|
||||
this.fireCreateNewFile({ parent: parentUri, uri: fileUri });
|
||||
open(this.openerService, fileUri);
|
||||
}
|
||||
protected async validateFileName(
|
||||
name: string,
|
||||
parent: FileStat,
|
||||
recursive = false
|
||||
): Promise<string> {
|
||||
// In the Java IDE the followings are the rules:
|
||||
// - `name` without an extension should default to `name.ino`.
|
||||
// - `name` with a single trailing `.` also defaults to `name.ino`.
|
||||
const nameWithExt = this.maybeAppendInoExt(name);
|
||||
const errorMessage = await super.validateFileName(
|
||||
nameWithExt,
|
||||
parent,
|
||||
recursive
|
||||
);
|
||||
if (errorMessage) {
|
||||
return errorMessage;
|
||||
}
|
||||
const extension = nameWithExt.split('.').pop();
|
||||
if (!extension) {
|
||||
return 'Invalid filename.'; // XXX: this should not happen as we forcefully append `.ino` if it's not there.
|
||||
}
|
||||
if (Sketch.Extensions.ALL.indexOf(`.${extension}`) === -1) {
|
||||
return `.${extension} is not a valid extension.`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected maybeAppendInoExt(name: string | undefined): string {
|
||||
if (!name) {
|
||||
return '';
|
||||
}
|
||||
if (name.trim().length) {
|
||||
if (name.indexOf('.') === -1) {
|
||||
return `${name}.ino`;
|
||||
}
|
||||
if (name.lastIndexOf('.') === name.length - 1) {
|
||||
return `${name.slice(0, -1)}.ino`;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
protected async renameFile(uri: URI | undefined): Promise<void> {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
|
||||
protected async validateFileName(name: string, parent: FileStat, recursive = false): Promise<string> {
|
||||
// In the Java IDE the followings are the rules:
|
||||
// - `name` without an extension should default to `name.ino`.
|
||||
// - `name` with a single trailing `.` also defaults to `name.ino`.
|
||||
const nameWithExt = this.maybeAppendInoExt(name);
|
||||
const errorMessage = await super.validateFileName(nameWithExt, parent, recursive);
|
||||
if (errorMessage) {
|
||||
return errorMessage;
|
||||
}
|
||||
const extension = nameWithExt.split('.').pop();
|
||||
if (!extension) {
|
||||
return 'Invalid filename.'; // XXX: this should not happen as we forcefully append `.ino` if it's not there.
|
||||
}
|
||||
if (Sketch.Extensions.ALL.indexOf(`.${extension}`) === -1) {
|
||||
return `.${extension} is not a valid extension.`;
|
||||
}
|
||||
return '';
|
||||
// file belongs to another sketch, do not allow rename
|
||||
const parentsketch = await this.sketchService.getSketchFolder(
|
||||
uri.toString()
|
||||
);
|
||||
if (parentsketch && parentsketch.uri !== sketch.uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
protected maybeAppendInoExt(name: string | undefined): string {
|
||||
if (!name) {
|
||||
return '';
|
||||
}
|
||||
if (name.trim().length) {
|
||||
if (name.indexOf('.') === -1) {
|
||||
return `${name}.ino`
|
||||
}
|
||||
if (name.lastIndexOf('.') === name.length - 1) {
|
||||
return `${name.slice(0, -1)}.ino`
|
||||
}
|
||||
}
|
||||
return name;
|
||||
if (uri.toString() === sketch.mainFileUri) {
|
||||
const options = {
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true,
|
||||
wipeOriginal: true,
|
||||
};
|
||||
await this.commandService.executeCommand(
|
||||
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
|
||||
options
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
protected async renameFile(uri: URI | undefined): Promise<void> {
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// file belongs to another sketch, do not allow rename
|
||||
const parentsketch = await this.sketchService.getSketchFolder(uri.toString());
|
||||
if (parentsketch && parentsketch.uri !== sketch.uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uri.toString() === sketch.mainFileUri) {
|
||||
const options = {
|
||||
execOnlyIfTemp: false,
|
||||
openAfterMove: true,
|
||||
wipeOriginal: true
|
||||
};
|
||||
await this.commandService.executeCommand(SaveAsSketch.Commands.SAVE_AS_SKETCH.id, options);
|
||||
return;
|
||||
}
|
||||
const parent = await this.getParent(uri);
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
const initialValue = uri.path.base;
|
||||
const dialog = new SingleTextInputDialog({
|
||||
title: 'New name for file',
|
||||
initialValue,
|
||||
initialSelectionRange: {
|
||||
start: 0,
|
||||
end: uri.path.name.length
|
||||
},
|
||||
validate: (name, mode) => {
|
||||
if (initialValue === name && mode === 'preview') {
|
||||
return false;
|
||||
}
|
||||
return this.validateFileName(name, parent, false);
|
||||
}
|
||||
});
|
||||
const newName = await dialog.open();
|
||||
const newNameWithExt = this.maybeAppendInoExt(newName);
|
||||
if (newNameWithExt) {
|
||||
const oldUri = uri;
|
||||
const newUri = uri.parent.resolve(newNameWithExt);
|
||||
this.fileService.move(oldUri, newUri);
|
||||
}
|
||||
const parent = await this.getParent(uri);
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialValue = uri.path.base;
|
||||
const dialog = new SingleTextInputDialog({
|
||||
title: 'New name for file',
|
||||
initialValue,
|
||||
initialSelectionRange: {
|
||||
start: 0,
|
||||
end: uri.path.name.length,
|
||||
},
|
||||
validate: (name, mode) => {
|
||||
if (initialValue === name && mode === 'preview') {
|
||||
return false;
|
||||
}
|
||||
return this.validateFileName(name, parent, false);
|
||||
},
|
||||
});
|
||||
const newName = await dialog.open();
|
||||
const newNameWithExt = this.maybeAppendInoExt(newName);
|
||||
if (newNameWithExt) {
|
||||
const oldUri = uri;
|
||||
const newUri = uri.parent.resolve(newNameWithExt);
|
||||
this.fileService.move(oldUri, newUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,40 +6,40 @@ import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-ser
|
||||
|
||||
@injectable()
|
||||
export class WorkspaceDeleteHandler extends TheiaWorkspaceDeleteHandler {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
async execute(uris: URI[]): Promise<void> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
// Deleting the main sketch file.
|
||||
if (
|
||||
uris
|
||||
.map((uri) => uri.toString())
|
||||
.some((uri) => uri === sketch.mainFileUri)
|
||||
) {
|
||||
const { response } = await remote.dialog.showMessageBox({
|
||||
title: 'Delete',
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
message: 'Do you want to delete the current sketch?',
|
||||
});
|
||||
if (response === 1) {
|
||||
// OK
|
||||
await Promise.all(
|
||||
[
|
||||
...sketch.additionalFileUris,
|
||||
...sketch.otherSketchFileUris,
|
||||
sketch.mainFileUri,
|
||||
].map((uri) => this.closeWithoutSaving(new URI(uri)))
|
||||
);
|
||||
await this.fileService.delete(new URI(sketch.uri));
|
||||
window.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
return super.execute(uris);
|
||||
async execute(uris: URI[]): Promise<void> {
|
||||
const sketch = await this.sketchesServiceClient.currentSketch();
|
||||
if (!sketch) {
|
||||
return;
|
||||
}
|
||||
// Deleting the main sketch file.
|
||||
if (
|
||||
uris
|
||||
.map((uri) => uri.toString())
|
||||
.some((uri) => uri === sketch.mainFileUri)
|
||||
) {
|
||||
const { response } = await remote.dialog.showMessageBox({
|
||||
title: 'Delete',
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'OK'],
|
||||
message: 'Do you want to delete the current sketch?',
|
||||
});
|
||||
if (response === 1) {
|
||||
// OK
|
||||
await Promise.all(
|
||||
[
|
||||
...sketch.additionalFileUris,
|
||||
...sketch.otherSketchFileUris,
|
||||
sketch.mainFileUri,
|
||||
].map((uri) => this.closeWithoutSaving(new URI(uri)))
|
||||
);
|
||||
await this.fileService.delete(new URI(sketch.uri));
|
||||
window.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
return super.execute(uris);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,48 +3,48 @@ import { CommandRegistry } from '@theia/core/lib/common/command';
|
||||
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
|
||||
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
||||
import {
|
||||
WorkspaceCommands,
|
||||
FileMenuContribution,
|
||||
WorkspaceCommands,
|
||||
FileMenuContribution,
|
||||
} from '@theia/workspace/lib/browser/workspace-commands';
|
||||
import { WorkspaceFrontendContribution as TheiaWorkspaceFrontendContribution } from '@theia/workspace/lib/browser/workspace-frontend-contribution';
|
||||
|
||||
@injectable()
|
||||
export class WorkspaceFrontendContribution extends TheiaWorkspaceFrontendContribution {
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
// TODO: instead of blacklisting commands to remove, it would be more robust to whitelist the ones we want to keep
|
||||
const commands = new Set(registry.commands);
|
||||
[
|
||||
WorkspaceCommands.OPEN,
|
||||
WorkspaceCommands.OPEN_FILE,
|
||||
WorkspaceCommands.OPEN_FOLDER,
|
||||
WorkspaceCommands.OPEN_WORKSPACE,
|
||||
WorkspaceCommands.OPEN_RECENT_WORKSPACE,
|
||||
WorkspaceCommands.SAVE_WORKSPACE_AS,
|
||||
WorkspaceCommands.SAVE_AS,
|
||||
WorkspaceCommands.CLOSE,
|
||||
]
|
||||
.filter(commands.has.bind(commands))
|
||||
.forEach(registry.unregisterCommand.bind(registry));
|
||||
}
|
||||
registerCommands(registry: CommandRegistry): void {
|
||||
super.registerCommands(registry);
|
||||
// TODO: instead of blacklisting commands to remove, it would be more robust to whitelist the ones we want to keep
|
||||
const commands = new Set(registry.commands);
|
||||
[
|
||||
WorkspaceCommands.OPEN,
|
||||
WorkspaceCommands.OPEN_FILE,
|
||||
WorkspaceCommands.OPEN_FOLDER,
|
||||
WorkspaceCommands.OPEN_WORKSPACE,
|
||||
WorkspaceCommands.OPEN_RECENT_WORKSPACE,
|
||||
WorkspaceCommands.SAVE_WORKSPACE_AS,
|
||||
WorkspaceCommands.SAVE_AS,
|
||||
WorkspaceCommands.CLOSE,
|
||||
]
|
||||
.filter(commands.has.bind(commands))
|
||||
.forEach(registry.unregisterCommand.bind(registry));
|
||||
}
|
||||
|
||||
registerMenus(_: MenuModelRegistry): void {}
|
||||
registerMenus(_: MenuModelRegistry): void {}
|
||||
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
[
|
||||
WorkspaceCommands.NEW_FILE,
|
||||
WorkspaceCommands.FILE_RENAME,
|
||||
WorkspaceCommands.FILE_DELETE,
|
||||
]
|
||||
.map(({ id }) => id)
|
||||
.forEach(registry.unregisterKeybinding.bind(registry));
|
||||
}
|
||||
registerKeybindings(registry: KeybindingRegistry): void {
|
||||
super.registerKeybindings(registry);
|
||||
[
|
||||
WorkspaceCommands.NEW_FILE,
|
||||
WorkspaceCommands.FILE_RENAME,
|
||||
WorkspaceCommands.FILE_DELETE,
|
||||
]
|
||||
.map(({ id }) => id)
|
||||
.forEach(registry.unregisterKeybinding.bind(registry));
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ArduinoFileMenuContribution extends FileMenuContribution {
|
||||
registerMenus(_: MenuModelRegistry): void {
|
||||
// NOOP
|
||||
}
|
||||
registerMenus(_: MenuModelRegistry): void {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,39 +3,39 @@ import { MaybePromise } from '@theia/core/lib/common/types';
|
||||
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
|
||||
import { DialogError, DialogMode } from '@theia/core/lib/browser/dialogs';
|
||||
import {
|
||||
WorkspaceInputDialog as TheiaWorkspaceInputDialog,
|
||||
WorkspaceInputDialogProps,
|
||||
WorkspaceInputDialog as TheiaWorkspaceInputDialog,
|
||||
WorkspaceInputDialogProps,
|
||||
} from '@theia/workspace/lib/browser/workspace-input-dialog';
|
||||
|
||||
export class WorkspaceInputDialog extends TheiaWorkspaceInputDialog {
|
||||
protected wasTouched = false;
|
||||
protected wasTouched = false;
|
||||
|
||||
constructor(
|
||||
@inject(WorkspaceInputDialogProps)
|
||||
protected readonly props: WorkspaceInputDialogProps,
|
||||
@inject(LabelProvider) protected readonly labelProvider: LabelProvider
|
||||
) {
|
||||
super(props, labelProvider);
|
||||
this.appendCloseButton('Cancel');
|
||||
}
|
||||
constructor(
|
||||
@inject(WorkspaceInputDialogProps)
|
||||
protected readonly props: WorkspaceInputDialogProps,
|
||||
@inject(LabelProvider) protected readonly labelProvider: LabelProvider
|
||||
) {
|
||||
super(props, labelProvider);
|
||||
this.appendCloseButton('Cancel');
|
||||
}
|
||||
|
||||
protected appendParentPath(): void {
|
||||
// NOOP
|
||||
}
|
||||
protected appendParentPath(): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
isValid(value: string, mode: DialogMode): MaybePromise<DialogError> {
|
||||
if (value !== '') {
|
||||
this.wasTouched = true;
|
||||
}
|
||||
return super.isValid(value, mode);
|
||||
isValid(value: string, mode: DialogMode): MaybePromise<DialogError> {
|
||||
if (value !== '') {
|
||||
this.wasTouched = true;
|
||||
}
|
||||
return super.isValid(value, mode);
|
||||
}
|
||||
|
||||
protected setErrorMessage(error: DialogError): void {
|
||||
if (this.acceptButton) {
|
||||
this.acceptButton.disabled = !DialogError.getResult(error);
|
||||
}
|
||||
if (this.wasTouched) {
|
||||
this.errorMessageNode.innerText = DialogError.getMessage(error);
|
||||
}
|
||||
protected setErrorMessage(error: DialogError): void {
|
||||
if (this.acceptButton) {
|
||||
this.acceptButton.disabled = !DialogError.getResult(error);
|
||||
}
|
||||
if (this.wasTouched) {
|
||||
this.errorMessageNode.innerText = DialogError.getMessage(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import { FrontendApplicationStateService } from '@theia/core/lib/browser/fronten
|
||||
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { ConfigService } from '../../../common/protocol/config-service';
|
||||
import {
|
||||
SketchesService,
|
||||
Sketch,
|
||||
SketchContainer,
|
||||
SketchesService,
|
||||
Sketch,
|
||||
SketchContainer,
|
||||
} from '../../../common/protocol/sketches-service';
|
||||
import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver';
|
||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||
@@ -20,133 +20,131 @@ import { BoardsConfig } from '../../boards/boards-config';
|
||||
|
||||
@injectable()
|
||||
export class WorkspaceService extends TheiaWorkspaceService {
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchService: SketchesService;
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchService: SketchesService;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
@inject(LabelProvider)
|
||||
protected readonly labelProvider: LabelProvider;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
@inject(ApplicationServer)
|
||||
protected readonly applicationServer: ApplicationServer;
|
||||
@inject(ApplicationServer)
|
||||
protected readonly applicationServer: ApplicationServer;
|
||||
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
@inject(FrontendApplicationStateService)
|
||||
protected readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
@inject(BoardsServiceProvider)
|
||||
protected readonly boardsServiceProvider: BoardsServiceProvider;
|
||||
|
||||
private application: FrontendApplication;
|
||||
private workspaceUri?: Promise<string | undefined>;
|
||||
private version?: string;
|
||||
private application: FrontendApplication;
|
||||
private workspaceUri?: Promise<string | undefined>;
|
||||
private version?: string;
|
||||
|
||||
async onStart(application: FrontendApplication): Promise<void> {
|
||||
this.application = application;
|
||||
const info = await this.applicationServer.getApplicationInfo();
|
||||
this.version = info?.version;
|
||||
application.shell.onDidChangeCurrentWidget(
|
||||
this.onCurrentWidgetChange.bind(this)
|
||||
async onStart(application: FrontendApplication): Promise<void> {
|
||||
this.application = application;
|
||||
const info = await this.applicationServer.getApplicationInfo();
|
||||
this.version = info?.version;
|
||||
application.shell.onDidChangeCurrentWidget(
|
||||
this.onCurrentWidgetChange.bind(this)
|
||||
);
|
||||
const newValue = application.shell.currentWidget
|
||||
? application.shell.currentWidget
|
||||
: null;
|
||||
this.onCurrentWidgetChange({ newValue, oldValue: null });
|
||||
}
|
||||
|
||||
protected getDefaultWorkspaceUri(): Promise<string | undefined> {
|
||||
if (this.workspaceUri) {
|
||||
// Avoid creating a new sketch twice
|
||||
return this.workspaceUri;
|
||||
}
|
||||
this.workspaceUri = (async () => {
|
||||
try {
|
||||
const hash = window.location.hash;
|
||||
const [recentWorkspaces, recentSketches] = await Promise.all([
|
||||
this.server.getRecentWorkspaces(),
|
||||
this.sketchService
|
||||
.getSketches({})
|
||||
.then((container) =>
|
||||
SketchContainer.toArray(container).map((s) => s.uri)
|
||||
),
|
||||
]);
|
||||
const toOpen = await new ArduinoWorkspaceRootResolver({
|
||||
isValid: this.isValid.bind(this),
|
||||
}).resolve({ hash, recentWorkspaces, recentSketches });
|
||||
if (toOpen) {
|
||||
const { uri } = toOpen;
|
||||
await this.server.setMostRecentlyUsedWorkspace(uri);
|
||||
return toOpen.uri;
|
||||
}
|
||||
return (await this.sketchService.createNewSketch()).uri;
|
||||
} catch (err) {
|
||||
this.appStateService
|
||||
.reachedState('ready')
|
||||
.then(() => this.application.shell.update());
|
||||
this.logger.fatal(`Failed to determine the sketch directory: ${err}`);
|
||||
this.messageService.error(
|
||||
'There was an error creating the sketch directory. ' +
|
||||
'See the log for more details. ' +
|
||||
'The application will probably not work as expected.'
|
||||
);
|
||||
const newValue = application.shell.currentWidget
|
||||
? application.shell.currentWidget
|
||||
: null;
|
||||
this.onCurrentWidgetChange({ newValue, oldValue: null });
|
||||
}
|
||||
return super.getDefaultWorkspaceUri();
|
||||
}
|
||||
})();
|
||||
return this.workspaceUri;
|
||||
}
|
||||
|
||||
protected getDefaultWorkspaceUri(): Promise<string | undefined> {
|
||||
if (this.workspaceUri) {
|
||||
// Avoid creating a new sketch twice
|
||||
return this.workspaceUri;
|
||||
}
|
||||
this.workspaceUri = (async () => {
|
||||
try {
|
||||
const hash = window.location.hash;
|
||||
const [recentWorkspaces, recentSketches] = await Promise.all([
|
||||
this.server.getRecentWorkspaces(),
|
||||
this.sketchService
|
||||
.getSketches({})
|
||||
.then((container) =>
|
||||
SketchContainer.toArray(container).map((s) => s.uri)
|
||||
),
|
||||
]);
|
||||
const toOpen = await new ArduinoWorkspaceRootResolver({
|
||||
isValid: this.isValid.bind(this),
|
||||
}).resolve({ hash, recentWorkspaces, recentSketches });
|
||||
if (toOpen) {
|
||||
const { uri } = toOpen;
|
||||
await this.server.setMostRecentlyUsedWorkspace(uri);
|
||||
return toOpen.uri;
|
||||
}
|
||||
return (await this.sketchService.createNewSketch()).uri;
|
||||
} catch (err) {
|
||||
this.appStateService
|
||||
.reachedState('ready')
|
||||
.then(() => this.application.shell.update());
|
||||
this.logger.fatal(
|
||||
`Failed to determine the sketch directory: ${err}`
|
||||
);
|
||||
this.messageService.error(
|
||||
'There was an error creating the sketch directory. ' +
|
||||
'See the log for more details. ' +
|
||||
'The application will probably not work as expected.'
|
||||
);
|
||||
return super.getDefaultWorkspaceUri();
|
||||
}
|
||||
})();
|
||||
return this.workspaceUri;
|
||||
}
|
||||
protected openNewWindow(workspacePath: string): void {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
const url = BoardsConfig.Config.setConfig(
|
||||
boardsConfig,
|
||||
new URL(window.location.href)
|
||||
); // Set the current boards config for the new browser window.
|
||||
url.hash = workspacePath;
|
||||
this.windowService.openNewWindow(url.toString());
|
||||
}
|
||||
|
||||
protected openNewWindow(workspacePath: string): void {
|
||||
const { boardsConfig } = this.boardsServiceProvider;
|
||||
const url = BoardsConfig.Config.setConfig(
|
||||
boardsConfig,
|
||||
new URL(window.location.href)
|
||||
); // Set the current boards config for the new browser window.
|
||||
url.hash = workspacePath;
|
||||
this.windowService.openNewWindow(url.toString());
|
||||
private async isValid(uri: string): Promise<boolean> {
|
||||
const exists = await this.fileService.exists(new URI(uri));
|
||||
if (!exists) {
|
||||
return false;
|
||||
}
|
||||
return this.sketchService.isSketchFolder(uri);
|
||||
}
|
||||
|
||||
private async isValid(uri: string): Promise<boolean> {
|
||||
const exists = await this.fileService.exists(new URI(uri));
|
||||
if (!exists) {
|
||||
return false;
|
||||
}
|
||||
return this.sketchService.isSketchFolder(uri);
|
||||
protected onCurrentWidgetChange({
|
||||
newValue,
|
||||
}: FocusTracker.IChangedArgs<Widget>): void {
|
||||
if (newValue instanceof EditorWidget) {
|
||||
const { uri } = newValue.editor;
|
||||
if (Sketch.isSketchFile(uri.toString())) {
|
||||
this.updateTitle();
|
||||
} else {
|
||||
const title = this.workspaceTitle;
|
||||
const fileName = this.labelProvider.getName(uri);
|
||||
document.title = this.formatTitle(
|
||||
title ? `${title} - ${fileName}` : fileName
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.updateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
protected onCurrentWidgetChange({
|
||||
newValue,
|
||||
}: FocusTracker.IChangedArgs<Widget>): void {
|
||||
if (newValue instanceof EditorWidget) {
|
||||
const { uri } = newValue.editor;
|
||||
if (Sketch.isSketchFile(uri.toString())) {
|
||||
this.updateTitle();
|
||||
} else {
|
||||
const title = this.workspaceTitle;
|
||||
const fileName = this.labelProvider.getName(uri);
|
||||
document.title = this.formatTitle(
|
||||
title ? `${title} - ${fileName}` : fileName
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.updateTitle();
|
||||
}
|
||||
}
|
||||
protected formatTitle(title?: string): string {
|
||||
const version = this.version ? ` ${this.version}` : '';
|
||||
const name = `${this.applicationName} ${version}`;
|
||||
return title ? `${title} | ${name}` : name;
|
||||
}
|
||||
|
||||
protected formatTitle(title?: string): string {
|
||||
const version = this.version ? ` ${this.version}` : '';
|
||||
const name = `${this.applicationName} ${version}`;
|
||||
return title ? `${title} | ${name}` : name;
|
||||
}
|
||||
|
||||
protected get workspaceTitle(): string | undefined {
|
||||
if (this.workspace) {
|
||||
return this.labelProvider.getName(this.workspace.resource);
|
||||
}
|
||||
protected get workspaceTitle(): string | undefined {
|
||||
if (this.workspace) {
|
||||
return this.labelProvider.getName(this.workspace.resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,26 +6,26 @@ import { SketchesServiceClientImpl } from '../../../common/protocol/sketches-ser
|
||||
|
||||
@injectable()
|
||||
export class WorkspaceVariableContribution extends TheiaWorkspaceVariableContribution {
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
@inject(SketchesServiceClientImpl)
|
||||
protected readonly sketchesServiceClient: SketchesServiceClientImpl;
|
||||
|
||||
protected currentSketch?: Sketch;
|
||||
protected currentSketch?: Sketch;
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.sketchesServiceClient
|
||||
.currentSketch()
|
||||
.then()
|
||||
.then((sketch) => (this.currentSketch = sketch));
|
||||
}
|
||||
|
||||
getResourceUri(): URI | undefined {
|
||||
const resourceUri = super.getResourceUri();
|
||||
// https://github.com/arduino/arduino-ide/issues/46
|
||||
// `currentWidget` can be an editor representing a file outside of the workspace. The current sketch should be a fallback.
|
||||
if (!resourceUri && this.currentSketch?.uri) {
|
||||
return new URI(this.currentSketch.uri);
|
||||
}
|
||||
return resourceUri;
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this.sketchesServiceClient
|
||||
.currentSketch()
|
||||
.then()
|
||||
.then((sketch) => (this.currentSketch = sketch));
|
||||
}
|
||||
|
||||
getResourceUri(): URI | undefined {
|
||||
const resourceUri = super.getResourceUri();
|
||||
// https://github.com/arduino/arduino-ide/issues/46
|
||||
// `currentWidget` can be an editor representing a file outside of the workspace. The current sketch should be a fallback.
|
||||
if (!resourceUri && this.currentSketch?.uri) {
|
||||
return new URI(this.currentSketch.uri);
|
||||
}
|
||||
return resourceUri;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user