Skip to content

Commit

Permalink
feat: configure sketchbook location without restart
Browse files Browse the repository at this point in the history
Closes #1764
Closes #796
Closes #569
Closes #655

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta authored and kittaakos committed Dec 21, 2022
1 parent 3f05396 commit 76f9f63
Show file tree
Hide file tree
Showing 28 changed files with 651 additions and 262 deletions.
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

0 comments on commit 76f9f63

Please sign in to comment.