Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ import { CoreErrorHandler } from './contributions/core-error-handler';
import { CompilerErrors } from './contributions/compiler-errors';
import { WidgetManager } from './theia/core/widget-manager';
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
import { EncodedCommandsContribution } from './widgets/sketchbook/encoded-commands-contribution';

MonacoThemingService.register({
id: 'arduino-theme',
Expand Down Expand Up @@ -698,6 +699,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, PlotterFrontendContribution);
Contribution.configure(bind, Format);
Contribution.configure(bind, CompilerErrors);
Contribution.configure(bind, EncodedCommandsContribution);

// Disabled the quick-pick customization from Theia when multiple formatters are available.
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
Expand Down Expand Up @@ -832,6 +834,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {

bind(CloudSketchbookWidget).toSelf();
rebind(SketchbookWidget).toService(CloudSketchbookWidget);
bind(CommandContribution).toService(CloudSketchbookWidget);
bind(CloudSketchbookTreeWidget).toDynamicValue(({ container }) =>
createCloudSketchbookTreeWidget(container)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ export class SketchControl extends SketchContribution {
});
}

protected isCloudSketch(uri: string): boolean {
isCloudSketch(uri: string): boolean {
try {
const cloudCacheLocation = this.localCacheFsProvider.from(new URI(uri));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@ import { FrontendApplication } from '@theia/core/lib/browser/frontend-applicatio
import { FocusTracker, Widget } from '@theia/core/lib/browser';
import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import {
WorkspaceInput,
WorkspaceService as TheiaWorkspaceService,
} from '@theia/workspace/lib/browser/workspace-service';
import { ConfigService } from '../../../common/protocol/config-service';
import {
SketchesService,
Sketch,
} from '../../../common/protocol/sketches-service';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { BoardsConfig } from '../../boards/boards-config';
import { Command } from '@theia/core';

interface WorkspaceOptions extends WorkspaceInput {
commands: Command[];
}

@injectable()
export class WorkspaceService extends TheiaWorkspaceService {
Expand Down Expand Up @@ -82,13 +90,68 @@ export class WorkspaceService extends TheiaWorkspaceService {
}
}

protected override openNewWindow(workspacePath: string): void {
/*
This method mostly duplicates super.doOpen and super.openWindow because they didn't let pass any custom
option to openNewWindow
*/
async openWithCommands(uri: URI, options?: WorkspaceOptions): Promise<void> {
const stat = await this.toFileStat(uri);
if (stat) {
if (!stat.isDirectory && !this.isWorkspaceFile(stat)) {
const message = `Not a valid workspace: ${uri.path.toString()}`;
this.messageService.error(message);
throw new Error(message);
}
// The same window has to be preserved too (instead of opening a new one), if the workspace root is not yet available and we are setting it for the first time.
// Option passed as parameter has the highest priority (for api developers), then the preference, then the default.
await this.roots;
const { preserveWindow } = {
preserveWindow:
this.preferences['workspace.preserveWindow'] || !this.opened,
...options,
};
await this.server.setMostRecentlyUsedWorkspace(uri.toString());
if (preserveWindow) {
this._workspace = stat;
}

const workspacePath = stat.resource.path.toString();

if (this.shouldPreserveWindow(options)) {
this.reloadWindow();
} else {
try {
this.openNewWindow(workspacePath, options);
return;
} catch (error) {
// Fall back to reloading the current window in case the browser has blocked the new window
this._workspace = stat;
this.logger.error(error.toString()).then(() => this.reloadWindow());
}
}
}
throw new Error(
'Invalid workspace root URI. Expected an existing directory or workspace file.'
);
}

protected override openNewWindow(
workspacePath: string,
options?: WorkspaceOptions
): 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;
if (options?.commands) {
url.searchParams.set(
'commands',
encodeURIComponent(JSON.stringify(options.commands))
);
}

this.windowService.openNewWindow(url.toString());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
} from '@theia/core/lib/browser/preferences/preference-service';
import { ArduinoMenus, PlaceholderMenuNode } from '../../menu/arduino-menus';
import { SketchbookCommands } from '../sketchbook/sketchbook-commands';
import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
import {
CurrentSketch,
SketchesServiceClientImpl,
} from '../../../common/protocol/sketches-service-client-impl';
import { Contribution } from '../../contributions/contribution';
import { ArduinoPreferences } from '../../arduino-preferences';
import { MainMenuManager } from '../../../common/main-menu-manager';
Expand Down Expand Up @@ -61,6 +64,14 @@ export namespace CloudSketchbookCommands {
}
}

export const SHOW_CLOUD_SKETCHBOOK_WIDGET = Command.toLocalizedCommand(
{
id: 'arduino-cloud-sketchbook--show-cloud-sketchbook-widget',
label: 'Show Cloud Sketchbook Widget',
},
'arduino/sketch/showCloudSketchbookWidget'
);

export const TOGGLE_CLOUD_SKETCHBOOK = Command.toLocalizedCommand(
{
id: 'arduino-cloud-sketchbook--disable',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { CloudSketchbookCompositeWidget } from './cloud-sketchbook-composite-widget';
import { SketchbookWidget } from '../sketchbook/sketchbook-widget';
import { ArduinoPreferences } from '../../arduino-preferences';
import { CommandContribution, CommandRegistry } from '@theia/core';
import { ApplicationShell } from '@theia/core/lib/browser';
import { CloudSketchbookCommands } from './cloud-sketchbook-contributions';
import { EditorManager } from '@theia/editor/lib/browser';
import { SketchbookWidgetContribution } from '../sketchbook/sketchbook-widget-contribution';

@injectable()
export class CloudSketchbookWidget extends SketchbookWidget {
export class CloudSketchbookWidget
extends SketchbookWidget
implements CommandContribution
{
@inject(CloudSketchbookCompositeWidget)
protected readonly widget: CloudSketchbookCompositeWidget;
private readonly cloudSketchbookCompositeWidget: CloudSketchbookCompositeWidget;

@inject(ArduinoPreferences)
protected readonly arduinoPreferences: ArduinoPreferences;
private readonly arduinoPreferences: ArduinoPreferences;

@inject(ApplicationShell)
private readonly shell: ApplicationShell;

@inject(SketchbookWidgetContribution)
private readonly sketchbookWidgetContribution: SketchbookWidgetContribution;

@inject(EditorManager)
private readonly editorManager: EditorManager;

@postConstruct()
protected override init(): void {
Expand All @@ -27,7 +48,9 @@ export class CloudSketchbookWidget extends SketchbookWidget {

checkCloudEnabled() {
if (this.arduinoPreferences['arduino.cloud.enabled']) {
this.sketchbookTreesContainer.activateWidget(this.widget);
this.sketchbookTreesContainer.activateWidget(
this.cloudSketchbookCompositeWidget
);
} else {
this.sketchbookTreesContainer.activateWidget(
this.localSketchbookTreeWidget
Expand All @@ -45,7 +68,9 @@ export class CloudSketchbookWidget extends SketchbookWidget {
}

protected override onAfterAttach(msg: any): void {
this.sketchbookTreesContainer.addWidget(this.widget);
this.sketchbookTreesContainer.addWidget(
this.cloudSketchbookCompositeWidget
);
this.setDocumentMode();
this.arduinoPreferences.onPreferenceChanged((event) => {
if (event.preferenceName === 'arduino.cloud.enabled') {
Expand All @@ -54,4 +79,29 @@ export class CloudSketchbookWidget extends SketchbookWidget {
});
super.onAfterAttach(msg);
}

registerCommands(registry: CommandRegistry): void {
this.sketchbookTreesContainer.addWidget(
this.cloudSketchbookCompositeWidget
);
registry.registerCommand(
CloudSketchbookCommands.SHOW_CLOUD_SKETCHBOOK_WIDGET,
{
execute: () => this.showCloudSketchbookWidget(),
}
);
}

showCloudSketchbookWidget(): void {
if (this.arduinoPreferences['arduino.cloud.enabled']) {
this.shell.activateWidget(this.id).then((widget) => {
if (widget instanceof CloudSketchbookWidget) {
widget.activateTreeWidget(this.cloudSketchbookCompositeWidget.id);
}
this.sketchbookWidgetContribution.selectWidgetFileNode(
this.editorManager.currentEditor
);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Command, CommandRegistry, MaybePromise } from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import { Contribution } from '../../contributions/contribution';

@injectable()
export class EncodedCommandsContribution extends Contribution {
@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry;

override onReady(): MaybePromise<void> {
const params = new URLSearchParams(window.location.search);
const encoded = params.get('commands');
if (!encoded) return;

const commands = JSON.parse(decodeURIComponent(encoded));

if (Array.isArray(commands)) {
commands.forEach((c: Command) => {
this.commandRegistry.executeCommand(c.id);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { Command } from '@theia/core/lib/common/command';

export namespace SketchbookCommands {
export const TOGGLE_SKETCHBOOK_WIDGET: Command = {
id: 'arduino-sketchbook-widget:toggle',
};

export const SHOW_SKETCHBOOK_WIDGET = Command.toLocalizedCommand(
{
id: 'arduino-sketchbook--show-sketchbook-widget',
label: 'Show Sketchbook Widget',
},
'arduino/sketch/showSketchbookWidget'
);

export const OPEN_NEW_WINDOW = Command.toLocalizedCommand(
{
id: 'arduino-sketchbook--open-sketch-new-window',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import {
} from '../../../common/protocol/sketches-service-client-impl';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { URI } from '../../contributions/contribution';
import { EditorManager } from '@theia/editor/lib/browser';
import { SketchControl } from '../../contributions/sketch-control';
import { CloudSketchbookCommands } from '../cloud-sketchbook/cloud-sketchbook-contributions';

export const SKETCHBOOK__CONTEXT = ['arduino-sketchbook--context'];

Expand Down Expand Up @@ -67,6 +70,12 @@ export class SketchbookWidgetContribution
@inject(FileService)
protected readonly fileService: FileService;

@inject(EditorManager)
protected readonly editorManager: EditorManager;

@inject(SketchControl)
protected readonly sketchControl: SketchControl;

protected readonly toDisposeBeforeNewContextMenu = new DisposableCollection();

constructor() {
Expand All @@ -77,7 +86,7 @@ export class SketchbookWidgetContribution
area: 'left',
rank: 1,
},
toggleCommandId: 'arduino-sketchbook-widget:toggle',
toggleCommandId: SketchbookCommands.TOGGLE_SKETCHBOOK_WIDGET.id,
toggleKeybinding: 'CtrlCmd+Shift+B',
});
}
Expand All @@ -100,11 +109,11 @@ export class SketchbookWidgetContribution

override registerCommands(registry: CommandRegistry): void {
super.registerCommands(registry);

registry.registerCommand(SketchbookCommands.SHOW_SKETCHBOOK_WIDGET, {
execute: () => this.showLocalSketchbookWidget(),
});
registry.registerCommand(SketchbookCommands.OPEN_NEW_WINDOW, {
execute: async (arg) => {
return this.workspaceService.open(arg.node.uri);
},
execute: (arg) => this.openSketchInNewWindow(arg),
isEnabled: (arg) =>
!!arg && 'node' in arg && SketchbookTree.SketchDirNode.is(arg.node),
isVisible: (arg) =>
Expand Down Expand Up @@ -192,12 +201,21 @@ export class SketchbookWidgetContribution
});
}

openSketchInNewWindow(arg: any): any {
const openSketchbookCommand = this.sketchControl.isCloudSketch(arg.node.uri)
? CloudSketchbookCommands.SHOW_CLOUD_SKETCHBOOK_WIDGET
: SketchbookCommands.SHOW_SKETCHBOOK_WIDGET;
return this.workspaceService.openWithCommands(arg.node.uri, {
commands: [openSketchbookCommand],
});
}

override registerMenus(registry: MenuModelRegistry): void {
super.registerMenus(registry);

// unregister main menu action
registry.unregisterMenuAction({
commandId: 'arduino-sketchbook-widget:toggle',
commandId: SketchbookCommands.TOGGLE_SKETCHBOOK_WIDGET.id,
});

registry.registerMenuAction(SKETCHBOOK__CONTEXT__MAIN_GROUP, {
Expand Down Expand Up @@ -230,4 +248,15 @@ export class SketchbookWidgetContribution
protected onCurrentWidgetChangedHandler(): void {
this.selectWidgetFileNode(this.shell.currentWidget);
}

protected async showLocalSketchbookWidget(): Promise<void> {
this.widget
.then((widget) => this.shell.activateWidget(widget.id))
.then((widget) => {
if (widget instanceof SketchbookWidget) {
widget.activateTreeWidget(widget.getTreeWidget().id);
this.selectWidgetFileNode(this.editorManager.currentEditor);
}
});
}
}
Loading