Skip to content

Commit 5dc029d

Browse files
author
Akos Kitta
committed
Link resolved for lib/boards manager.
Closes #1442 Signed-off-by: Akos Kitta <[email protected]>
1 parent 6ed532c commit 5dc029d

File tree

8 files changed

+359
-27
lines changed

8 files changed

+359
-27
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ import { StartupTaskProvider } from '../electron-common/startup-task';
333333
import { DeleteSketch } from './contributions/delete-sketch';
334334
import { UserFields } from './contributions/user-fields';
335335
import { UpdateIndexes } from './contributions/update-indexes';
336+
import { OpenHandler } from '@theia/core/lib/browser/opener-service';
336337

337338
const registerArduinoThemes = () => {
338339
const themes: MonacoThemeJson[] = [
@@ -397,6 +398,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
397398
bind(FrontendApplicationContribution).toService(
398399
LibraryListWidgetFrontendContribution
399400
);
401+
bind(OpenHandler).toService(LibraryListWidgetFrontendContribution);
400402

401403
// Sketch list service
402404
bind(SketchesService)
@@ -463,6 +465,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
463465
bind(FrontendApplicationContribution).toService(
464466
BoardsListWidgetFrontendContribution
465467
);
468+
bind(OpenHandler).toService(BoardsListWidgetFrontendContribution);
466469

467470
// Board select dialog
468471
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();

Diff for: arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { injectable } from '@theia/core/shared/inversify';
2-
import { BoardsListWidget } from './boards-list-widget';
3-
import type {
2+
import {
43
BoardSearch,
54
BoardsPackage,
65
} from '../../common/protocol/boards-service';
6+
import { URI } from '../contributions/contribution';
77
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
8+
import { BoardsListWidget } from './boards-list-widget';
89

910
@injectable()
1011
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<
@@ -24,7 +25,16 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
2425
});
2526
}
2627

27-
override async initializeLayout(): Promise<void> {
28-
this.openView();
28+
protected canParse(uri: URI): boolean {
29+
try {
30+
BoardSearch.UriParser.parse(uri);
31+
return true;
32+
} catch {
33+
return false;
34+
}
35+
}
36+
37+
protected parse(uri: URI): BoardSearch | undefined {
38+
return BoardSearch.UriParser.parse(uri);
2939
}
3040
}
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
import { nls } from '@theia/core/lib/common';
2+
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
13
import { injectable } from '@theia/core/shared/inversify';
2-
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
3-
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
4-
import { MenuModelRegistry } from '@theia/core';
5-
import { LibraryListWidget } from './library-list-widget';
4+
import { LibraryPackage, LibrarySearch } from '../../common/protocol';
5+
import { URI } from '../contributions/contribution';
66
import { ArduinoMenus } from '../menu/arduino-menus';
7-
import { nls } from '@theia/core/lib/common';
7+
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
8+
import { LibraryListWidget } from './library-list-widget';
89

910
@injectable()
10-
export class LibraryListWidgetFrontendContribution
11-
extends AbstractViewContribution<LibraryListWidget>
12-
implements FrontendApplicationContribution
13-
{
11+
export class LibraryListWidgetFrontendContribution extends ListWidgetFrontendContribution<
12+
LibraryPackage,
13+
LibrarySearch
14+
> {
1415
constructor() {
1516
super({
1617
widgetId: LibraryListWidget.WIDGET_ID,
@@ -24,10 +25,6 @@ export class LibraryListWidgetFrontendContribution
2425
});
2526
}
2627

27-
async initializeLayout(): Promise<void> {
28-
this.openView();
29-
}
30-
3128
override registerMenus(menus: MenuModelRegistry): void {
3229
if (this.toggleCommand) {
3330
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
@@ -40,4 +37,17 @@ export class LibraryListWidgetFrontendContribution
4037
});
4138
}
4239
}
40+
41+
protected canParse(uri: URI): boolean {
42+
try {
43+
LibrarySearch.UriParser.parse(uri);
44+
return true;
45+
} catch {
46+
return false;
47+
}
48+
}
49+
50+
protected parse(uri: URI): LibrarySearch | undefined {
51+
return LibrarySearch.UriParser.parse(uri);
52+
}
4353
}
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,65 @@
1-
import { injectable } from '@theia/core/shared/inversify';
21
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
2+
import {
3+
OpenerOptions,
4+
OpenHandler,
5+
} from '@theia/core/lib/browser/opener-service';
36
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
7+
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
8+
import { URI } from '@theia/core/lib/common/uri';
9+
import { injectable } from '@theia/core/shared/inversify';
10+
import { Searchable } from '../../../common/protocol';
411
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
512
import { ListWidget } from './list-widget';
6-
import { Searchable } from '../../../common/protocol';
713

814
@injectable()
915
export abstract class ListWidgetFrontendContribution<
1016
T extends ArduinoComponent,
1117
S extends Searchable.Options
1218
>
1319
extends AbstractViewContribution<ListWidget<T, S>>
14-
implements FrontendApplicationContribution
20+
implements FrontendApplicationContribution, OpenHandler
1521
{
22+
readonly id: string = `http-opener-${this.viewId}`;
23+
1624
async initializeLayout(): Promise<void> {
17-
// TS requires at least one method from `FrontendApplicationContribution`.
18-
// Expected to be empty.
25+
this.openView();
1926
}
2027

21-
override registerMenus(): void {
28+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
29+
override registerMenus(_: MenuModelRegistry): void {
2230
// NOOP
2331
}
32+
33+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
34+
canHandle(uri: URI, _?: OpenerOptions): number {
35+
// `500` is the default HTTP opener in Theia. IDE2 has higher priority.
36+
// https://github.com/eclipse-theia/theia/blob/b75b6144b0ffea06a549294903c374fa642135e4/packages/core/src/browser/http-open-handler.ts#L39
37+
return this.canParse(uri) ? 501 : 0;
38+
}
39+
40+
async open(
41+
uri: URI,
42+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
43+
_?: OpenerOptions | undefined
44+
): Promise<void> {
45+
const searchOptions = this.parse(uri);
46+
if (!searchOptions) {
47+
console.warn(
48+
`Failed to parse URI into a search options. URI: ${uri.toString()}`
49+
);
50+
return;
51+
}
52+
const widget = await this.openView({
53+
activate: true,
54+
reveal: true,
55+
});
56+
if (!widget) {
57+
console.warn(`Failed to open view for URI: ${uri.toString()}`);
58+
return;
59+
}
60+
widget.refresh(searchOptions);
61+
}
62+
63+
protected abstract canParse(uri: URI): boolean;
64+
protected abstract parse(uri: URI): S | undefined;
2465
}

Diff for: arduino-ide-extension/src/common/protocol/boards-service.ts

+48-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ import { Searchable } from './searchable';
33
import { Installable } from './installable';
44
import { ArduinoComponent } from './arduino-component';
55
import { nls } from '@theia/core/lib/common/nls';
6-
import { All, Contributed, Partner, Type, Updatable } from '../nls';
6+
import {
7+
All,
8+
Contributed,
9+
Partner,
10+
Type as TypeLabel,
11+
Updatable,
12+
} from '../nls';
13+
import URI from '@theia/core/lib/common/uri';
714

815
export type AvailablePorts = Record<string, [Port, Array<Board>]>;
916
export namespace AvailablePorts {
@@ -151,6 +158,7 @@ export interface BoardSearch extends Searchable.Options {
151158
readonly type?: BoardSearch.Type;
152159
}
153160
export namespace BoardSearch {
161+
export const Default: BoardSearch = { type: 'All' };
154162
export const TypeLiterals = [
155163
'All',
156164
'Updatable',
@@ -161,6 +169,11 @@ export namespace BoardSearch {
161169
'Arduino@Heart',
162170
] as const;
163171
export type Type = typeof TypeLiterals[number];
172+
export namespace Type {
173+
export function is(arg: unknown): arg is Type {
174+
return typeof arg === 'string' && TypeLiterals.includes(arg as Type);
175+
}
176+
}
164177
export const TypeLabels: Record<Type, string> = {
165178
All: All,
166179
Updatable: Updatable,
@@ -177,8 +190,41 @@ export namespace BoardSearch {
177190
keyof Omit<BoardSearch, 'query'>,
178191
string
179192
> = {
180-
type: Type,
193+
type: TypeLabel,
181194
};
195+
export namespace UriParser {
196+
export const authority = 'boardsmanager';
197+
export function parse(uri: URI): BoardSearch | undefined {
198+
if (uri.scheme !== 'http') {
199+
throw new Error(
200+
`Invalid 'scheme'. Expected 'http'. URI was: ${uri.toString()}.`
201+
);
202+
}
203+
if (uri.authority !== authority) {
204+
throw new Error(
205+
`Invalid 'authority'. Expected: '${authority}'. URI was: ${uri.toString()}.`
206+
);
207+
}
208+
const segments = Searchable.UriParser.normalizedSegmentsOf(uri);
209+
if (segments.length !== 1) {
210+
return undefined;
211+
}
212+
let searchOptions: BoardSearch | undefined = undefined;
213+
const [type] = segments;
214+
if (!type) {
215+
searchOptions = BoardSearch.Default;
216+
} else if (BoardSearch.Type.is(type)) {
217+
searchOptions = { type };
218+
}
219+
if (searchOptions) {
220+
return {
221+
...searchOptions,
222+
...Searchable.UriParser.parseQuery(uri),
223+
};
224+
}
225+
return undefined;
226+
}
227+
}
182228
}
183229

184230
export interface Port {

Diff for: arduino-ide-extension/src/common/protocol/library-service.ts

+66-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
Partner,
99
Recommended,
1010
Retired,
11-
Type,
11+
Type as TypeLabel,
1212
Updatable,
1313
} from '../nls';
14+
import URI from '@theia/core/lib/common/uri';
1415

1516
export const LibraryServicePath = '/services/library-service';
1617
export const LibraryService = Symbol('LibraryService');
@@ -55,6 +56,7 @@ export interface LibrarySearch extends Searchable.Options {
5556
readonly topic?: LibrarySearch.Topic;
5657
}
5758
export namespace LibrarySearch {
59+
export const Default: LibrarySearch = { type: 'All', topic: 'All' };
5860
export const TypeLiterals = [
5961
'All',
6062
'Updatable',
@@ -66,6 +68,11 @@ export namespace LibrarySearch {
6668
'Retired',
6769
] as const;
6870
export type Type = typeof TypeLiterals[number];
71+
export namespace Type {
72+
export function is(arg: unknown): arg is Type {
73+
return typeof arg === 'string' && TypeLiterals.includes(arg as Type);
74+
}
75+
}
6976
export const TypeLabels: Record<Type, string> = {
7077
All: All,
7178
Updatable: Updatable,
@@ -90,6 +97,11 @@ export namespace LibrarySearch {
9097
'Uncategorized',
9198
] as const;
9299
export type Topic = typeof TopicLiterals[number];
100+
export namespace Topic {
101+
export function is(arg: unknown): arg is Topic {
102+
return typeof arg === 'string' && TopicLiterals.includes(arg as Topic);
103+
}
104+
}
93105
export const TopicLabels: Record<Topic, string> = {
94106
All: All,
95107
Communication: nls.localize(
@@ -126,8 +138,60 @@ export namespace LibrarySearch {
126138
string
127139
> = {
128140
topic: nls.localize('arduino/librarySearchProperty/topic', 'Topic'),
129-
type: Type,
141+
type: TypeLabel,
130142
};
143+
export namespace UriParser {
144+
export const authority = 'librarymanager';
145+
export function parse(uri: URI): LibrarySearch | undefined {
146+
if (uri.scheme !== 'http') {
147+
throw new Error(
148+
`Invalid 'scheme'. Expected 'http'. URI was: ${uri.toString()}.`
149+
);
150+
}
151+
if (uri.authority !== authority) {
152+
throw new Error(
153+
`Invalid 'authority'. Expected: '${authority}'. URI was: ${uri.toString()}.`
154+
);
155+
}
156+
const segments = Searchable.UriParser.normalizedSegmentsOf(uri);
157+
// Special magic handling for `Signal Input/Output`.
158+
// TODO: IDE2 deserves a better lib/boards URL spec.
159+
// https://github.com/arduino/arduino-ide/issues/1442#issuecomment-1252136377
160+
if (segments.length === 3) {
161+
const [type, topicHead, topicTail] = segments;
162+
const maybeTopic = `${topicHead}/${topicTail}`;
163+
if (
164+
LibrarySearch.Topic.is(maybeTopic) &&
165+
maybeTopic === 'Signal Input/Output' &&
166+
LibrarySearch.Type.is(type)
167+
) {
168+
return {
169+
type,
170+
topic: maybeTopic,
171+
...Searchable.UriParser.parseQuery(uri),
172+
};
173+
}
174+
}
175+
let searchOptions: LibrarySearch | undefined = undefined;
176+
const [type, topic] = segments;
177+
if (!type && !topic) {
178+
searchOptions = LibrarySearch.Default;
179+
} else if (LibrarySearch.Type.is(type)) {
180+
if (!topic) {
181+
searchOptions = { ...LibrarySearch.Default, type };
182+
} else if (LibrarySearch.Topic.is(topic)) {
183+
searchOptions = { type, topic };
184+
}
185+
}
186+
if (searchOptions) {
187+
return {
188+
...searchOptions,
189+
...Searchable.UriParser.parseQuery(uri),
190+
};
191+
}
192+
return undefined;
193+
}
194+
}
131195
}
132196

133197
export namespace LibraryService {

0 commit comments

Comments
 (0)