Skip to content

feat: configure sketchbook location without restart #1774

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
Dec 21, 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 @@ -343,6 +343,7 @@ import { DebugWidget } from '@theia/debug/lib/browser/view/debug-widget';
import { DebugViewModel } from '@theia/debug/lib/browser/view/debug-view-model';
import { DebugSessionWidget } from '@theia/debug/lib/browser/view/debug-session-widget';
import { DebugConfigurationWidget } from '@theia/debug/lib/browser/view/debug-configuration-widget';
import { ConfigServiceClient } from './config/config-service-client';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
// Commands and toolbar items
Expand Down Expand Up @@ -404,6 +405,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
)
)
.inSingletonScope();
bind(ConfigServiceClient).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(ConfigServiceClient);

// Boards service
bind(BoardsService)
Expand Down
106 changes: 106 additions & 0 deletions arduino-ide-extension/src/browser/config/config-service-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import { deepClone } from '@theia/core/lib/common/objects';
import URI from '@theia/core/lib/common/uri';
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { ConfigService, ConfigState } from '../../common/protocol';
import { NotificationCenter } from '../notification-center';

@injectable()
export class ConfigServiceClient implements FrontendApplicationContribution {
@inject(ConfigService)
private readonly delegate: ConfigService;
@inject(NotificationCenter)
private readonly notificationCenter: NotificationCenter;
@inject(FrontendApplicationStateService)
private readonly appStateService: FrontendApplicationStateService;
@inject(MessageService)
private readonly messageService: MessageService;

private readonly didChangeSketchDirUriEmitter = new Emitter<
URI | undefined
>();
private readonly didChangeDataDirUriEmitter = new Emitter<URI | undefined>();
private readonly toDispose = new DisposableCollection(
this.didChangeSketchDirUriEmitter,
this.didChangeDataDirUriEmitter
);

private config: ConfigState | undefined;

@postConstruct()
protected init(): void {
this.appStateService.reachedState('ready').then(async () => {
const config = await this.fetchConfig();
this.use(config);
});
}

onStart(): void {
this.notificationCenter.onConfigDidChange((config) => this.use(config));
}

onStop(): void {
this.toDispose.dispose();
}

get onDidChangeSketchDirUri(): Event<URI | undefined> {
return this.didChangeSketchDirUriEmitter.event;
}

get onDidChangeDataDirUri(): Event<URI | undefined> {
return this.didChangeDataDirUriEmitter.event;
}

async fetchConfig(): Promise<ConfigState> {
return this.delegate.getConfiguration();
}

/**
* CLI config related error messages if any.
*/
tryGetMessages(): string[] | undefined {
return this.config?.messages;
}

/**
* `directories.user`
*/
tryGetSketchDirUri(): URI | undefined {
return this.config?.config?.sketchDirUri
? new URI(this.config?.config?.sketchDirUri)
: undefined;
}

/**
* `directories.data`
*/
tryGetDataDirUri(): URI | undefined {
return this.config?.config?.dataDirUri
? new URI(this.config?.config?.dataDirUri)
: undefined;
}

private use(config: ConfigState): void {
const oldConfig = deepClone(this.config);
this.config = config;
if (oldConfig?.config?.sketchDirUri !== this.config?.config?.sketchDirUri) {
this.didChangeSketchDirUriEmitter.fire(this.tryGetSketchDirUri());
}
if (oldConfig?.config?.dataDirUri !== this.config?.config?.dataDirUri) {
this.didChangeDataDirUriEmitter.fire(this.tryGetDataDirUri());
}
if (this.config.messages?.length) {
const message = this.config.messages.join(' ');
// toast the error later otherwise it might not show up in IDE2
setTimeout(() => this.messageService.error(message), 1_000);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { inject, injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import URI from '@theia/core/lib/common/uri';
import { ConfirmDialog } from '@theia/core/lib/browser/dialogs';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { ArduinoMenus } from '../menu/arduino-menus';
import { LibraryService, ResponseServiceClient } from '../../common/protocol';
import { ExecuteWithProgress } from '../../common/protocol/progressible';
Expand All @@ -16,9 +15,6 @@ import { nls } from '@theia/core/lib/common';

@injectable()
export class AddZipLibrary extends SketchContribution {
@inject(EnvVariablesServer)
private readonly envVariableServer: EnvVariablesServer;

@inject(ResponseServiceClient)
private readonly responseService: ResponseServiceClient;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import * as dateFormat from 'dateformat';
import URI from '@theia/core/lib/common/uri';
import { ArduinoMenus } from '../menu/arduino-menus';
import {
SketchContribution,
Expand Down Expand Up @@ -29,20 +28,17 @@ export class ArchiveSketch extends SketchContribution {
}

private async archiveSketch(): Promise<void> {
const [sketch, config] = await Promise.all([
this.sketchServiceClient.currentSketch(),
this.configService.getConfiguration(),
]);
const sketch = await this.sketchServiceClient.currentSketch();
if (!CurrentSketch.isValid(sketch)) {
return;
}
const archiveBasename = `${sketch.name}-${dateFormat(
new Date(),
'yymmdd'
)}a.zip`;
const defaultPath = await this.fileService.fsPath(
new URI(config.sketchDirUri).resolve(archiveBasename)
);
const defaultContainerUri = await this.defaultUri();
const defaultUri = defaultContainerUri.resolve(archiveBasename);
const defaultPath = await this.fileService.fsPath(defaultUri);
const { filePath, canceled } = await remote.dialog.showSaveDialog(
remote.getCurrentWindow(),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,7 @@ PID: ${PID}`;
);

// Ports submenu
const portsSubmenuPath = [
...ArduinoMenus.TOOLS__BOARD_SELECTION_GROUP,
'2_ports',
];
const portsSubmenuPath = ArduinoMenus.TOOLS__PORTS_SUBMENU;
const portsSubmenuLabel = config.selectedPort?.address;
this.menuModelRegistry.registerSubmenu(
portsSubmenuPath,
Expand Down
29 changes: 26 additions & 3 deletions arduino-ide-extension/src/browser/contributions/contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MaybePromise } from '@theia/core/lib/common/types';
import { LabelProvider } from '@theia/core/lib/browser/label-provider';
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
import { MessageService } from '@theia/core/lib/common/message-service';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { open, OpenerService } from '@theia/core/lib/browser/opener-service';

import {
Expand Down Expand Up @@ -43,7 +44,6 @@ import {
} from '../../common/protocol/sketches-service-client-impl';
import {
SketchesService,
ConfigService,
FileSystemExt,
Sketch,
CoreService,
Expand All @@ -62,6 +62,7 @@ import { NotificationManager } from '../theia/messages/notifications-manager';
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
import { WorkspaceService } from '../theia/workspace/workspace-service';
import { MainMenuManager } from '../../common/main-menu-manager';
import { ConfigServiceClient } from '../config/config-service-client';

export {
Command,
Expand Down Expand Up @@ -142,8 +143,8 @@ export abstract class SketchContribution extends Contribution {
@inject(FileSystemExt)
protected readonly fileSystemExt: FileSystemExt;

@inject(ConfigService)
protected readonly configService: ConfigService;
@inject(ConfigServiceClient)
protected readonly configService: ConfigServiceClient;

@inject(SketchesService)
protected readonly sketchService: SketchesService;
Expand All @@ -160,6 +161,9 @@ export abstract class SketchContribution extends Contribution {
@inject(OutputChannelManager)
protected readonly outputChannelManager: OutputChannelManager;

@inject(EnvVariablesServer)
protected readonly envVariableServer: EnvVariablesServer;

protected async sourceOverride(): Promise<Record<string, string>> {
const override: Record<string, string> = {};
const sketch = await this.sketchServiceClient.currentSketch();
Expand All @@ -173,6 +177,25 @@ export abstract class SketchContribution extends Contribution {
}
return override;
}

/**
* Defaults to `directories.user` if defined and not CLI config errors were detected.
* Otherwise, the URI of the user home directory.
*/
protected async defaultUri(): Promise<URI> {
const errors = this.configService.tryGetMessages();
let defaultUri = this.configService.tryGetSketchDirUri();
if (!defaultUri || errors?.length) {
// Fall back to user home when the `directories.user` is not available or there are known CLI config errors
defaultUri = new URI(await this.envVariableServer.getHomeDirUri());
}
return defaultUri;
}

protected async defaultPath(): Promise<string> {
const defaultUri = await this.defaultUri();
return this.fileService.fsPath(defaultUri);
}
}

@injectable()
Expand Down
20 changes: 16 additions & 4 deletions arduino-ide-extension/src/browser/contributions/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ import {
CoreService,
} from '../../common/protocol';
import { nls } from '@theia/core/lib/common';
import { unregisterSubmenu } from '../menu/arduino-menus';

@injectable()
export abstract class Examples extends SketchContribution {
@inject(CommandRegistry)
private readonly commandRegistry: CommandRegistry;

@inject(MenuModelRegistry)
private readonly menuRegistry: MenuModelRegistry;
protected readonly menuRegistry: MenuModelRegistry;

@inject(ExamplesService)
protected readonly examplesService: ExamplesService;
Expand All @@ -47,13 +48,22 @@ export abstract class Examples extends SketchContribution {
@inject(BoardsServiceProvider)
protected readonly boardsServiceClient: BoardsServiceProvider;

@inject(NotificationCenter)
protected readonly notificationCenter: NotificationCenter;

protected readonly toDispose = new DisposableCollection();

protected override init(): void {
super.init();
this.boardsServiceClient.onBoardsConfigChanged(({ selectedBoard }) =>
this.handleBoardChanged(selectedBoard)
);
this.notificationCenter.onDidReinitialize(() =>
this.update({
board: this.boardsServiceClient.boardsConfig.selectedBoard,
// No force refresh. The core client was already refreshed.
})
);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
Expand Down Expand Up @@ -120,6 +130,11 @@ export abstract class Examples extends SketchContribution {
const { label } = sketchContainerOrPlaceholder;
submenuPath = [...menuPath, label];
this.menuRegistry.registerSubmenu(submenuPath, label, subMenuOptions);
this.toDispose.push(
Disposable.create(() =>
unregisterSubmenu(submenuPath, this.menuRegistry)
)
);
sketches.push(...sketchContainerOrPlaceholder.sketches);
children.push(...sketchContainerOrPlaceholder.children);
} else {
Expand Down Expand Up @@ -239,9 +254,6 @@ export class BuiltInExamples extends Examples {

@injectable()
export class LibraryExamples extends Examples {
@inject(NotificationCenter)
private readonly notificationCenter: NotificationCenter;

private readonly queue = new PQueue({ autoStart: true, concurrency: 1 });

override onStart(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class IncludeLibrary extends SketchContribution {
this.notificationCenter.onLibraryDidUninstall(() =>
this.updateMenuActions()
);
this.notificationCenter.onDidReinitialize(() => this.updateMenuActions());
}

override async onReady(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ export class OpenSketch extends SketchContribution {
}

private async selectSketch(): Promise<Sketch | undefined> {
const config = await this.configService.getConfiguration();
const defaultPath = await this.fileService.fsPath(
new URI(config.sketchDirUri)
);
const defaultPath = await this.defaultPath();
const { filePaths } = await remote.dialog.showOpenDialog(
remote.getCurrentWindow(),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ export class SaveAsSketch extends SketchContribution {
markAsRecentlyOpened,
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
): Promise<boolean> {
const [sketch, configuration] = await Promise.all([
this.sketchServiceClient.currentSketch(),
this.configService.getConfiguration(),
]);
const sketch = await this.sketchServiceClient.currentSketch();
if (!CurrentSketch.isValid(sketch)) {
return false;
}
Expand All @@ -72,7 +69,7 @@ export class SaveAsSketch extends SketchContribution {
}

const sketchUri = new URI(sketch.uri);
const sketchbookDirUri = new URI(configuration.sketchDirUri);
const sketchbookDirUri = await this.defaultUri();
// If the sketch is temp, IDE2 proposes the default sketchbook folder URI.
// If the sketch is not temp, but not contained in the default sketchbook folder, IDE2 proposes the default location.
// Otherwise, it proposes the parent folder of the current sketch.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { nls } from '@theia/core/lib/common/nls';
export class Sketchbook extends Examples {
override onStart(): void {
this.sketchServiceClient.onSketchbookDidChange(() => this.update());
this.configService.onDidChangeSketchDirUri(() => this.update());
}

override async onReady(): Promise<void> {
Expand Down
Loading