Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 087cab1

Browse files
Alberto IannacconeAkos Kitta
Alberto Iannaccone
and
Akos Kitta
authored
Sketchbook sidebar state (#1102)
* add commands to open sketchbook widgets add commands to show sketchbook widgets * enable sending commands via query params * opening sketch in new window will open sketchbook * requested changes * add specific method WorkspaceService to open sketch with commands * add encoded commands contribution * try merge show sketchbook commands * pair session changes. Signed-off-by: Akos Kitta <a.kitta@arduino.cc> * i18n fixup. Signed-off-by: Akos Kitta <a.kitta@arduino.cc> * minimized scope of hacky code. Signed-off-by: Akos Kitta <a.kitta@arduino.cc> * clean up OPEN_NEW_WINDOW command * add comment on workspace-service.ts * reveal node with URI Co-authored-by: Akos Kitta <a.kitta@arduino.cc>
1 parent 5da558d commit 087cab1

File tree

7 files changed

+226
-10
lines changed

7 files changed

+226
-10
lines changed

‎arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ import { CoreErrorHandler } from './contributions/core-error-handler';
302302
import { CompilerErrors } from './contributions/compiler-errors';
303303
import { WidgetManager } from './theia/core/widget-manager';
304304
import { WidgetManager as TheiaWidgetManager } from '@theia/core/lib/browser/widget-manager';
305+
import { StartupTask } from './widgets/sketchbook/startup-task';
305306

306307
MonacoThemingService.register({
307308
id: 'arduino-theme',
@@ -698,6 +699,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
698699
Contribution.configure(bind, PlotterFrontendContribution);
699700
Contribution.configure(bind, Format);
700701
Contribution.configure(bind, CompilerErrors);
702+
Contribution.configure(bind, StartupTask);
701703

702704
// Disabled the quick-pick customization from Theia when multiple formatters are available.
703705
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.

‎arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts‎

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ import { FrontendApplication } from '@theia/core/lib/browser/frontend-applicatio
99
import { FocusTracker, Widget } from '@theia/core/lib/browser';
1010
import { DEFAULT_WINDOW_HASH } from '@theia/core/lib/common/window';
1111
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
12-
import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
12+
import {
13+
WorkspaceInput,
14+
WorkspaceService as TheiaWorkspaceService,
15+
} from '@theia/workspace/lib/browser/workspace-service';
1316
import { ConfigService } from '../../../common/protocol/config-service';
1417
import {
1518
SketchesService,
1619
Sketch,
1720
} from '../../../common/protocol/sketches-service';
1821
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
1922
import { BoardsConfig } from '../../boards/boards-config';
23+
import { FileStat } from '@theia/filesystem/lib/common/files';
24+
import { StartupTask } from '../../widgets/sketchbook/startup-task';
2025

2126
@injectable()
2227
export class WorkspaceService extends TheiaWorkspaceService {
@@ -82,13 +87,75 @@ export class WorkspaceService extends TheiaWorkspaceService {
8287
}
8388
}
8489

85-
protected override openNewWindow(workspacePath: string): void {
90+
/**
91+
* Copied from Theia as-is to be able to pass the original `options` down.
92+
*/
93+
protected override async doOpen(
94+
uri: URI,
95+
options?: WorkspaceInput
96+
): Promise<URI | undefined> {
97+
const stat = await this.toFileStat(uri);
98+
if (stat) {
99+
if (!stat.isDirectory && !this.isWorkspaceFile(stat)) {
100+
const message = `Not a valid workspace: ${uri.path.toString()}`;
101+
this.messageService.error(message);
102+
throw new Error(message);
103+
}
104+
// 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.
105+
// Option passed as parameter has the highest priority (for api developers), then the preference, then the default.
106+
await this.roots;
107+
const { preserveWindow } = {
108+
preserveWindow:
109+
this.preferences['workspace.preserveWindow'] || !this.opened,
110+
...options,
111+
};
112+
await this.server.setMostRecentlyUsedWorkspace(uri.toString());
113+
if (preserveWindow) {
114+
this._workspace = stat;
115+
}
116+
this.openWindow(stat, Object.assign(options ?? {}, { preserveWindow })); // Unlike Theia, IDE2 passes the whole `input` downstream and not only { preserveWindow }
117+
return;
118+
}
119+
throw new Error(
120+
'Invalid workspace root URI. Expected an existing directory or workspace file.'
121+
);
122+
}
123+
124+
/**
125+
* Copied from Theia. Can pass the `options` further down the chain.
126+
*/
127+
protected override openWindow(uri: FileStat, options?: WorkspaceInput): void {
128+
const workspacePath = uri.resource.path.toString();
129+
if (this.shouldPreserveWindow(options)) {
130+
this.reloadWindow();
131+
} else {
132+
try {
133+
this.openNewWindow(workspacePath, options); // Unlike Theia, IDE2 passes the `input` downstream.
134+
} catch (error) {
135+
// Fall back to reloading the current window in case the browser has blocked the new window
136+
this._workspace = uri;
137+
this.logger.error(error.toString()).then(() => this.reloadWindow());
138+
}
139+
}
140+
}
141+
142+
protected override openNewWindow(
143+
workspacePath: string,
144+
options?: WorkspaceInput
145+
): void {
86146
const { boardsConfig } = this.boardsServiceProvider;
87147
const url = BoardsConfig.Config.setConfig(
88148
boardsConfig,
89149
new URL(window.location.href)
90150
); // Set the current boards config for the new browser window.
91151
url.hash = workspacePath;
152+
if (StartupTask.WorkspaceInput.is(options)) {
153+
url.searchParams.set(
154+
StartupTask.QUERY_STRING,
155+
encodeURIComponent(JSON.stringify(options.tasks))
156+
);
157+
}
158+
92159
this.windowService.openNewWindow(url.toString());
93160
}
94161

‎arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-contributions.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import {
2323
} from '@theia/core/lib/browser/preferences/preference-service';
2424
import { ArduinoMenus, PlaceholderMenuNode } from '../../menu/arduino-menus';
2525
import { SketchbookCommands } from '../sketchbook/sketchbook-commands';
26-
import { CurrentSketch, SketchesServiceClientImpl } from '../../../common/protocol/sketches-service-client-impl';
26+
import {
27+
CurrentSketch,
28+
SketchesServiceClientImpl,
29+
} from '../../../common/protocol/sketches-service-client-impl';
2730
import { Contribution } from '../../contributions/contribution';
2831
import { ArduinoPreferences } from '../../arduino-preferences';
2932
import { MainMenuManager } from '../../../common/main-menu-manager';

‎arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-commands.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { Command } from '@theia/core/lib/common/command';
22

33
export namespace SketchbookCommands {
4+
export const TOGGLE_SKETCHBOOK_WIDGET: Command = {
5+
id: 'arduino-sketchbook-widget:toggle',
6+
};
7+
8+
export const REVEAL_SKETCH_NODE: Command = {
9+
id: 'arduino-sketchbook--reveal-sketch-node',
10+
};
11+
412
export const OPEN_NEW_WINDOW = Command.toLocalizedCommand(
513
{
614
id: 'arduino-sketchbook--open-sketch-new-window',

‎arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget-contribution.ts‎

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
} from '../../../common/protocol/sketches-service-client-impl';
3030
import { FileService } from '@theia/filesystem/lib/browser/file-service';
3131
import { URI } from '../../contributions/contribution';
32+
import { WorkspaceInput } from '@theia/workspace/lib/browser';
3233

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

@@ -77,7 +78,7 @@ export class SketchbookWidgetContribution
7778
area: 'left',
7879
rank: 1,
7980
},
80-
toggleCommandId: 'arduino-sketchbook-widget:toggle',
81+
toggleCommandId: SketchbookCommands.TOGGLE_SKETCHBOOK_WIDGET.id,
8182
toggleKeybinding: 'CtrlCmd+Shift+B',
8283
});
8384
}
@@ -100,11 +101,12 @@ export class SketchbookWidgetContribution
100101

101102
override registerCommands(registry: CommandRegistry): void {
102103
super.registerCommands(registry);
103-
104+
registry.registerCommand(SketchbookCommands.REVEAL_SKETCH_NODE, {
105+
execute: (treeWidgetId: string, nodeUri: string) =>
106+
this.revealSketchNode(treeWidgetId, nodeUri),
107+
});
104108
registry.registerCommand(SketchbookCommands.OPEN_NEW_WINDOW, {
105-
execute: async (arg) => {
106-
return this.workspaceService.open(arg.node.uri);
107-
},
109+
execute: (arg) => this.openNewWindow(arg.node),
108110
isEnabled: (arg) =>
109111
!!arg && 'node' in arg && SketchbookTree.SketchDirNode.is(arg.node),
110112
isVisible: (arg) =>
@@ -197,7 +199,7 @@ export class SketchbookWidgetContribution
197199

198200
// unregister main menu action
199201
registry.unregisterMenuAction({
200-
commandId: 'arduino-sketchbook-widget:toggle',
202+
commandId: SketchbookCommands.TOGGLE_SKETCHBOOK_WIDGET.id,
201203
});
202204

203205
registry.registerMenuAction(SKETCHBOOK__CONTEXT__MAIN_GROUP, {
@@ -207,6 +209,28 @@ export class SketchbookWidgetContribution
207209
});
208210
}
209211

212+
private openNewWindow(node: SketchbookTree.SketchDirNode): void {
213+
const widget = this.tryGetWidget();
214+
if (widget) {
215+
const treeWidgetId = widget.activeTreeWidgetId();
216+
if (!treeWidgetId) {
217+
console.warn(`Could not retrieve active sketchbook tree ID.`);
218+
return;
219+
}
220+
const nodeUri = node.uri.toString();
221+
const options: WorkspaceInput = {};
222+
Object.assign(options, {
223+
tasks: [
224+
{
225+
command: SketchbookCommands.REVEAL_SKETCH_NODE.id,
226+
args: [treeWidgetId, nodeUri],
227+
},
228+
],
229+
});
230+
return this.workspaceService.open(node.uri, options);
231+
}
232+
}
233+
210234
/**
211235
* Reveals and selects node in the file navigator to which given widget is related.
212236
* Does nothing if given widget undefined or doesn't have related resource.
@@ -230,4 +254,17 @@ export class SketchbookWidgetContribution
230254
protected onCurrentWidgetChangedHandler(): void {
231255
this.selectWidgetFileNode(this.shell.currentWidget);
232256
}
257+
258+
private async revealSketchNode(
259+
treeWidgetId: string,
260+
nodeUIri: string
261+
): Promise<void> {
262+
return this.widget
263+
.then((widget) => this.shell.activateWidget(widget.id))
264+
.then((widget) => {
265+
if (widget instanceof SketchbookWidget) {
266+
return widget.revealSketchNode(treeWidgetId, nodeUIri);
267+
}
268+
});
269+
}
233270
}

‎arduino-ide-extension/src/browser/widgets/sketchbook/sketchbook-widget.tsx‎

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
1+
import {
2+
inject,
3+
injectable,
4+
postConstruct,
5+
} from '@theia/core/shared/inversify';
26
import { toArray } from '@theia/core/shared/@phosphor/algorithm';
37
import { IDragEvent } from '@theia/core/shared/@phosphor/dragdrop';
48
import { DockPanel, Widget } from '@theia/core/shared/@phosphor/widgets';
@@ -7,6 +11,8 @@ import { Disposable } from '@theia/core/lib/common/disposable';
711
import { BaseWidget } from '@theia/core/lib/browser/widgets/widget';
812
import { SketchbookTreeWidget } from './sketchbook-tree-widget';
913
import { nls } from '@theia/core/lib/common';
14+
import { CloudSketchbookCompositeWidget } from '../cloud-sketchbook/cloud-sketchbook-composite-widget';
15+
import { URI } from '../../contributions/contribution';
1016

1117
@injectable()
1218
export class SketchbookWidget extends BaseWidget {
@@ -45,6 +51,57 @@ export class SketchbookWidget extends BaseWidget {
4551
return this.localSketchbookTreeWidget;
4652
}
4753

54+
activeTreeWidgetId(): string | undefined {
55+
const selectedTreeWidgets = toArray(
56+
this.sketchbookTreesContainer.selectedWidgets()
57+
).map(({ id }) => id);
58+
if (selectedTreeWidgets.length > 1) {
59+
console.warn(
60+
`Found multiple selected tree widgets: ${JSON.stringify(
61+
selectedTreeWidgets
62+
)}. Expected only one.`
63+
);
64+
}
65+
return selectedTreeWidgets.shift();
66+
}
67+
68+
async revealSketchNode(treeWidgetId: string, nodeUri: string): Promise<void> {
69+
const widget = toArray(this.sketchbookTreesContainer.widgets())
70+
.filter(({ id }) => id === treeWidgetId)
71+
.shift();
72+
if (!widget) {
73+
console.warn(`Could not find tree widget with ID: ${widget}`);
74+
return;
75+
}
76+
// TODO: remove this when the remote/local sketchbooks and their widgets are cleaned up.
77+
const findTreeWidget = (
78+
widget: Widget | undefined
79+
): SketchbookTreeWidget | undefined => {
80+
if (widget instanceof SketchbookTreeWidget) {
81+
return widget;
82+
}
83+
if (widget instanceof CloudSketchbookCompositeWidget) {
84+
return widget.getTreeWidget();
85+
}
86+
return undefined;
87+
};
88+
const treeWidget = findTreeWidget(
89+
toArray(this.sketchbookTreesContainer.widgets())
90+
.filter(({ id }) => id === treeWidgetId)
91+
.shift()
92+
);
93+
if (!treeWidget) {
94+
console.warn(`Could not find tree widget with ID: ${treeWidget}`);
95+
return;
96+
}
97+
this.sketchbookTreesContainer.activateWidget(widget);
98+
99+
const treeNode = await treeWidget.model.revealFile(new URI(nodeUri));
100+
if (!treeNode) {
101+
console.warn(`Could not find tree node with URI: ${nodeUri}`);
102+
}
103+
}
104+
48105
protected override onActivateRequest(message: Message): void {
49106
super.onActivateRequest(message);
50107

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { injectable } from '@theia/core/shared/inversify';
2+
import { WorkspaceInput as TheiaWorkspaceInput } from '@theia/workspace/lib/browser';
3+
import { Contribution } from '../../contributions/contribution';
4+
5+
export interface Task {
6+
command: string;
7+
/**
8+
* This must be JSON serializable.
9+
*/
10+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11+
args?: any[];
12+
}
13+
14+
@injectable()
15+
export class StartupTask extends Contribution {
16+
override onReady(): void {
17+
const params = new URLSearchParams(window.location.search);
18+
const encoded = params.get(StartupTask.QUERY_STRING);
19+
if (!encoded) return;
20+
21+
const commands = JSON.parse(decodeURIComponent(encoded));
22+
23+
if (Array.isArray(commands)) {
24+
commands.forEach(({ command, args }) => {
25+
this.commandService.executeCommand(command, ...args);
26+
});
27+
}
28+
}
29+
}
30+
export namespace StartupTask {
31+
export const QUERY_STRING = 'startupTasks';
32+
export interface WorkspaceInput extends TheiaWorkspaceInput {
33+
tasks: Task[];
34+
}
35+
export namespace WorkspaceInput {
36+
export function is(
37+
input: (TheiaWorkspaceInput & Partial<WorkspaceInput>) | undefined
38+
): input is WorkspaceInput {
39+
return !!input && !!input.tasks;
40+
}
41+
}
42+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /