Make tab width 2 spaces (#445)

This commit is contained in:
Francesco Stasi
2021-07-09 10:14:42 +02:00
committed by GitHub
parent 40a73af82b
commit e10f0f1683
205 changed files with 19676 additions and 20141 deletions

View File

@@ -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
);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
});

View File

@@ -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' });
}
}

View File

@@ -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);
}
}
}

View File

@@ -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')
)
);
}
}

View File

@@ -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
);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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>
);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
);
});
}
}

View File

@@ -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;
}
});
}
}

View File

@@ -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();
}
}
}
);
}
}

View File

@@ -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,
});
}
}

View File

@@ -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;
}
}

View File

@@ -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',
});
}
}

View File

@@ -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',
});
}
}
}

View File

@@ -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);
}
}

View File

@@ -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>
);
}
}

View File

@@ -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';
}
}

View File

@@ -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>
);
}
}

View File

@@ -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);
}
}

View File

@@ -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
);
}
}

View File

@@ -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 });
}
}

View File

@@ -3,7 +3,7 @@ import { MonacoStatusBarContribution as TheiaMonacoStatusBarContribution } from
@injectable()
export class MonacoStatusBarContribution extends TheiaMonacoStatusBarContribution {
protected setConfigTabSizeWidget() {}
protected setConfigTabSizeWidget() {}
protected setLineEndingWidget() {}
protected setLineEndingWidget() {}
}

View File

@@ -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);
}
}

View File

@@ -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)
);
}
}

View File

@@ -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 [];
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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'));
}
}

View File

@@ -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>
);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}