Skip to content

#1442 Link resolver for lib/boards manager #1481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ import { DeleteSketch } from './contributions/delete-sketch';
import { UserFields } from './contributions/user-fields';
import { UpdateIndexes } from './contributions/update-indexes';
import { InterfaceScale } from './contributions/interface-scale';
import { OpenHandler } from '@theia/core/lib/browser/opener-service';

const registerArduinoThemes = () => {
const themes: MonacoThemeJson[] = [
Expand Down Expand Up @@ -398,6 +399,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(FrontendApplicationContribution).toService(
LibraryListWidgetFrontendContribution
);
bind(OpenHandler).toService(LibraryListWidgetFrontendContribution);

// Sketch list service
bind(SketchesService)
Expand Down Expand Up @@ -464,6 +466,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(FrontendApplicationContribution).toService(
BoardsListWidgetFrontendContribution
);
bind(OpenHandler).toService(BoardsListWidgetFrontendContribution);

// Board select dialog
bind(BoardsConfigDialogWidget).toSelf().inSingletonScope();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { injectable } from '@theia/core/shared/inversify';
import { BoardsListWidget } from './boards-list-widget';
import type {
import {
BoardSearch,
BoardsPackage,
} from '../../common/protocol/boards-service';
import { URI } from '../contributions/contribution';
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
import { BoardsListWidget } from './boards-list-widget';

@injectable()
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution<
Expand All @@ -24,7 +25,16 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont
});
}

override async initializeLayout(): Promise<void> {
this.openView();
protected canParse(uri: URI): boolean {
try {
BoardSearch.UriParser.parse(uri);
return true;
} catch {
return false;
}
}

protected parse(uri: URI): BoardSearch | undefined {
return BoardSearch.UriParser.parse(uri);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { nls } from '@theia/core/lib/common';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { injectable } from '@theia/core/shared/inversify';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { MenuModelRegistry } from '@theia/core';
import { LibraryListWidget } from './library-list-widget';
import { LibraryPackage, LibrarySearch } from '../../common/protocol';
import { URI } from '../contributions/contribution';
import { ArduinoMenus } from '../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
import { ListWidgetFrontendContribution } from '../widgets/component-list/list-widget-frontend-contribution';
import { LibraryListWidget } from './library-list-widget';

@injectable()
export class LibraryListWidgetFrontendContribution
extends AbstractViewContribution<LibraryListWidget>
implements FrontendApplicationContribution
{
export class LibraryListWidgetFrontendContribution extends ListWidgetFrontendContribution<
LibraryPackage,
LibrarySearch
> {
constructor() {
super({
widgetId: LibraryListWidget.WIDGET_ID,
Expand All @@ -24,10 +25,6 @@ export class LibraryListWidgetFrontendContribution
});
}

async initializeLayout(): Promise<void> {
this.openView();
}

override registerMenus(menus: MenuModelRegistry): void {
if (this.toggleCommand) {
menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, {
Expand All @@ -40,4 +37,17 @@ export class LibraryListWidgetFrontendContribution
});
}
}

protected canParse(uri: URI): boolean {
try {
LibrarySearch.UriParser.parse(uri);
return true;
} catch {
return false;
}
}

protected parse(uri: URI): LibrarySearch | undefined {
return LibrarySearch.UriParser.parse(uri);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,65 @@
import { injectable } from '@theia/core/shared/inversify';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import {
OpenerOptions,
OpenHandler,
} from '@theia/core/lib/browser/opener-service';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { MenuModelRegistry } from '@theia/core/lib/common/menu';
import { URI } from '@theia/core/lib/common/uri';
import { injectable } from '@theia/core/shared/inversify';
import { Searchable } from '../../../common/protocol';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
import { ListWidget } from './list-widget';
import { Searchable } from '../../../common/protocol';

@injectable()
export abstract class ListWidgetFrontendContribution<
T extends ArduinoComponent,
S extends Searchable.Options
>
extends AbstractViewContribution<ListWidget<T, S>>
implements FrontendApplicationContribution
implements FrontendApplicationContribution, OpenHandler
{
readonly id: string = `http-opener-${this.viewId}`;

async initializeLayout(): Promise<void> {
// TS requires at least one method from `FrontendApplicationContribution`.
// Expected to be empty.
this.openView();
}

override registerMenus(): void {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
override registerMenus(_: MenuModelRegistry): void {
// NOOP
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
canHandle(uri: URI, _?: OpenerOptions): number {
// `500` is the default HTTP opener in Theia. IDE2 has higher priority.
// https://github.com/eclipse-theia/theia/blob/b75b6144b0ffea06a549294903c374fa642135e4/packages/core/src/browser/http-open-handler.ts#L39
return this.canParse(uri) ? 501 : 0;
}

async open(
uri: URI,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_?: OpenerOptions | undefined
): Promise<void> {
const searchOptions = this.parse(uri);
if (!searchOptions) {
console.warn(
`Failed to parse URI into a search options. URI: ${uri.toString()}`
);
return;
}
const widget = await this.openView({
activate: true,
reveal: true,
});
if (!widget) {
console.warn(`Failed to open view for URI: ${uri.toString()}`);
return;
}
widget.refresh(searchOptions);
}

protected abstract canParse(uri: URI): boolean;
protected abstract parse(uri: URI): S | undefined;
}
50 changes: 48 additions & 2 deletions arduino-ide-extension/src/common/protocol/boards-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { Searchable } from './searchable';
import { Installable } from './installable';
import { ArduinoComponent } from './arduino-component';
import { nls } from '@theia/core/lib/common/nls';
import { All, Contributed, Partner, Type, Updatable } from '../nls';
import {
All,
Contributed,
Partner,
Type as TypeLabel,
Updatable,
} from '../nls';
import URI from '@theia/core/lib/common/uri';

export type AvailablePorts = Record<string, [Port, Array<Board>]>;
export namespace AvailablePorts {
Expand Down Expand Up @@ -151,6 +158,7 @@ export interface BoardSearch extends Searchable.Options {
readonly type?: BoardSearch.Type;
}
export namespace BoardSearch {
export const Default: BoardSearch = { type: 'All' };
export const TypeLiterals = [
'All',
'Updatable',
Expand All @@ -161,6 +169,11 @@ export namespace BoardSearch {
'Arduino@Heart',
] as const;
export type Type = typeof TypeLiterals[number];
export namespace Type {
export function is(arg: unknown): arg is Type {
return typeof arg === 'string' && TypeLiterals.includes(arg as Type);
}
}
export const TypeLabels: Record<Type, string> = {
All: All,
Updatable: Updatable,
Expand All @@ -177,8 +190,41 @@ export namespace BoardSearch {
keyof Omit<BoardSearch, 'query'>,
string
> = {
type: Type,
type: TypeLabel,
};
export namespace UriParser {
export const authority = 'boardsmanager';
export function parse(uri: URI): BoardSearch | undefined {
if (uri.scheme !== 'http') {
throw new Error(
`Invalid 'scheme'. Expected 'http'. URI was: ${uri.toString()}.`
);
}
if (uri.authority !== authority) {
throw new Error(
`Invalid 'authority'. Expected: '${authority}'. URI was: ${uri.toString()}.`
);
}
const segments = Searchable.UriParser.normalizedSegmentsOf(uri);
if (segments.length !== 1) {
return undefined;
}
let searchOptions: BoardSearch | undefined = undefined;
const [type] = segments;
if (!type) {
searchOptions = BoardSearch.Default;
} else if (BoardSearch.Type.is(type)) {
searchOptions = { type };
}
if (searchOptions) {
return {
...searchOptions,
...Searchable.UriParser.parseQuery(uri),
};
}
return undefined;
}
}
}

export interface Port {
Expand Down
68 changes: 66 additions & 2 deletions arduino-ide-extension/src/common/protocol/library-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
Partner,
Recommended,
Retired,
Type,
Type as TypeLabel,
Updatable,
} from '../nls';
import URI from '@theia/core/lib/common/uri';

export const LibraryServicePath = '/services/library-service';
export const LibraryService = Symbol('LibraryService');
Expand Down Expand Up @@ -55,6 +56,7 @@ export interface LibrarySearch extends Searchable.Options {
readonly topic?: LibrarySearch.Topic;
}
export namespace LibrarySearch {
export const Default: LibrarySearch = { type: 'All', topic: 'All' };
export const TypeLiterals = [
'All',
'Updatable',
Expand All @@ -66,6 +68,11 @@ export namespace LibrarySearch {
'Retired',
] as const;
export type Type = typeof TypeLiterals[number];
export namespace Type {
export function is(arg: unknown): arg is Type {
return typeof arg === 'string' && TypeLiterals.includes(arg as Type);
}
}
export const TypeLabels: Record<Type, string> = {
All: All,
Updatable: Updatable,
Expand All @@ -90,6 +97,11 @@ export namespace LibrarySearch {
'Uncategorized',
] as const;
export type Topic = typeof TopicLiterals[number];
export namespace Topic {
export function is(arg: unknown): arg is Topic {
return typeof arg === 'string' && TopicLiterals.includes(arg as Topic);
}
}
export const TopicLabels: Record<Topic, string> = {
All: All,
Communication: nls.localize(
Expand Down Expand Up @@ -126,8 +138,60 @@ export namespace LibrarySearch {
string
> = {
topic: nls.localize('arduino/librarySearchProperty/topic', 'Topic'),
type: Type,
type: TypeLabel,
};
export namespace UriParser {
export const authority = 'librarymanager';
export function parse(uri: URI): LibrarySearch | undefined {
if (uri.scheme !== 'http') {
throw new Error(
`Invalid 'scheme'. Expected 'http'. URI was: ${uri.toString()}.`
);
}
if (uri.authority !== authority) {
throw new Error(
`Invalid 'authority'. Expected: '${authority}'. URI was: ${uri.toString()}.`
);
}
const segments = Searchable.UriParser.normalizedSegmentsOf(uri);
// Special magic handling for `Signal Input/Output`.
// TODO: IDE2 deserves a better lib/boards URL spec.
// https://github.com/arduino/arduino-ide/issues/1442#issuecomment-1252136377
if (segments.length === 3) {
const [type, topicHead, topicTail] = segments;
const maybeTopic = `${topicHead}/${topicTail}`;
if (
LibrarySearch.Topic.is(maybeTopic) &&
maybeTopic === 'Signal Input/Output' &&
LibrarySearch.Type.is(type)
) {
return {
type,
topic: maybeTopic,
...Searchable.UriParser.parseQuery(uri),
};
}
}
let searchOptions: LibrarySearch | undefined = undefined;
const [type, topic] = segments;
if (!type && !topic) {
searchOptions = LibrarySearch.Default;
} else if (LibrarySearch.Type.is(type)) {
if (!topic) {
searchOptions = { ...LibrarySearch.Default, type };
} else if (LibrarySearch.Topic.is(topic)) {
searchOptions = { type, topic };
}
}
if (searchOptions) {
return {
...searchOptions,
...Searchable.UriParser.parseQuery(uri),
};
}
return undefined;
}
}
}

export namespace LibraryService {
Expand Down
Loading