mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-22 00:26:09 +00:00
feat: configure sketchbook location without restart
Closes #1764 Closes #796 Closes #569 Closes #655 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -6,10 +6,12 @@ export interface ConfigService {
|
||||
getVersion(): Promise<
|
||||
Readonly<{ version: string; commit: string; status?: string }>
|
||||
>;
|
||||
getCliConfigFileUri(): Promise<string>;
|
||||
getConfiguration(): Promise<Config>;
|
||||
getConfiguration(): Promise<ConfigState>;
|
||||
setConfiguration(config: Config): Promise<void>;
|
||||
}
|
||||
export type ConfigState =
|
||||
| { config: undefined; messages: string[] }
|
||||
| { config: Config; messages?: string[] };
|
||||
|
||||
export interface Daemon {
|
||||
readonly port: string | number;
|
||||
@@ -119,7 +121,16 @@ export interface Config {
|
||||
readonly network: Network;
|
||||
}
|
||||
export namespace Config {
|
||||
export function sameAs(left: Config, right: Config): boolean {
|
||||
export function sameAs(
|
||||
left: Config | undefined,
|
||||
right: Config | undefined
|
||||
): boolean {
|
||||
if (!left) {
|
||||
return !right;
|
||||
}
|
||||
if (!right) {
|
||||
return false;
|
||||
}
|
||||
const leftUrls = left.additionalUrls.sort();
|
||||
const rightUrls = right.additionalUrls.sort();
|
||||
if (leftUrls.length !== rightUrls.length) {
|
||||
@@ -150,7 +161,16 @@ export namespace AdditionalUrls {
|
||||
export function stringify(additionalUrls: AdditionalUrls): string {
|
||||
return additionalUrls.join(',');
|
||||
}
|
||||
export function sameAs(left: AdditionalUrls, right: AdditionalUrls): boolean {
|
||||
export function sameAs(
|
||||
left: AdditionalUrls | undefined,
|
||||
right: AdditionalUrls | undefined
|
||||
): boolean {
|
||||
if (!left) {
|
||||
return !right;
|
||||
}
|
||||
if (!right) {
|
||||
return false;
|
||||
}
|
||||
if (left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-facto
|
||||
import type {
|
||||
AttachedBoardsChangeEvent,
|
||||
BoardsPackage,
|
||||
Config,
|
||||
ConfigState,
|
||||
ProgressMessage,
|
||||
Sketch,
|
||||
IndexType,
|
||||
@@ -39,6 +39,11 @@ export interface IndexUpdateDidFailParams extends IndexUpdateParams {
|
||||
}
|
||||
|
||||
export interface NotificationServiceClient {
|
||||
// The cached state of the core client. Libraries, examples, etc. has been updated.
|
||||
// This can happen without an index update. For example, changing the `directories.user` location.
|
||||
// An index update always implicitly involves a re-initialization without notifying via this method.
|
||||
notifyDidReinitialize(): void;
|
||||
|
||||
// Index
|
||||
notifyIndexUpdateWillStart(params: IndexUpdateWillStartParams): void;
|
||||
notifyIndexUpdateDidProgress(progressMessage: ProgressMessage): void;
|
||||
@@ -50,7 +55,7 @@ export interface NotificationServiceClient {
|
||||
notifyDaemonDidStop(): void;
|
||||
|
||||
// CLI config
|
||||
notifyConfigDidChange(event: { config: Config | undefined }): void;
|
||||
notifyConfigDidChange(event: ConfigState): void;
|
||||
|
||||
// Platforms
|
||||
notifyPlatformDidInstall(event: { item: BoardsPackage }): void;
|
||||
|
||||
@@ -3,13 +3,15 @@ import URI from '@theia/core/lib/common/uri';
|
||||
import { Emitter } from '@theia/core/lib/common/event';
|
||||
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||
import { FileChangeType } from '@theia/filesystem/lib/common/files';
|
||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||
import { DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||
import {
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
} from '@theia/core/lib/common/disposable';
|
||||
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
|
||||
import { Sketch, SketchesService } from '../../common/protocol';
|
||||
import { ConfigService } from './config-service';
|
||||
import { Sketch, SketchesService } from '.';
|
||||
import { ConfigServiceClient } from '../../browser/config/config-service-client';
|
||||
import { SketchContainer, SketchesError, SketchRef } from './sketches-service';
|
||||
import {
|
||||
ARDUINO_CLOUD_FOLDER,
|
||||
@@ -34,139 +36,143 @@ export class SketchesServiceClientImpl
|
||||
implements FrontendApplicationContribution
|
||||
{
|
||||
@inject(FileService)
|
||||
protected readonly fileService: FileService;
|
||||
|
||||
@inject(MessageService)
|
||||
protected readonly messageService: MessageService;
|
||||
|
||||
private readonly fileService: FileService;
|
||||
@inject(SketchesService)
|
||||
protected readonly sketchService: SketchesService;
|
||||
|
||||
private readonly sketchService: SketchesService;
|
||||
@inject(WorkspaceService)
|
||||
protected readonly workspaceService: WorkspaceService;
|
||||
|
||||
@inject(ConfigService)
|
||||
protected readonly configService: ConfigService;
|
||||
|
||||
private readonly workspaceService: WorkspaceService;
|
||||
@inject(ConfigServiceClient)
|
||||
private readonly configService: ConfigServiceClient;
|
||||
@inject(FrontendApplicationStateService)
|
||||
private readonly appStateService: FrontendApplicationStateService;
|
||||
|
||||
protected sketches = new Map<string, SketchRef>();
|
||||
// TODO: rename this + event to the `onBlabla` pattern
|
||||
protected sketchbookDidChangeEmitter = new Emitter<{
|
||||
private sketches = new Map<string, SketchRef>();
|
||||
private onSketchbookDidChangeEmitter = new Emitter<{
|
||||
created: SketchRef[];
|
||||
removed: SketchRef[];
|
||||
}>();
|
||||
readonly onSketchbookDidChange = this.sketchbookDidChangeEmitter.event;
|
||||
protected currentSketchDidChangeEmitter = new Emitter<CurrentSketch>();
|
||||
readonly onSketchbookDidChange = this.onSketchbookDidChangeEmitter.event;
|
||||
private currentSketchDidChangeEmitter = new Emitter<CurrentSketch>();
|
||||
readonly onCurrentSketchDidChange = this.currentSketchDidChangeEmitter.event;
|
||||
|
||||
protected toDispose = new DisposableCollection(
|
||||
this.sketchbookDidChangeEmitter,
|
||||
this.currentSketchDidChangeEmitter
|
||||
private toDisposeBeforeWatchSketchbookDir = new DisposableCollection();
|
||||
private toDispose = new DisposableCollection(
|
||||
this.onSketchbookDidChangeEmitter,
|
||||
this.currentSketchDidChangeEmitter,
|
||||
this.toDisposeBeforeWatchSketchbookDir
|
||||
);
|
||||
|
||||
private _currentSketch: CurrentSketch | undefined;
|
||||
private currentSketchLoaded = new Deferred<CurrentSketch>();
|
||||
|
||||
onStart(): void {
|
||||
this.configService.getConfiguration().then(({ sketchDirUri }) => {
|
||||
this.sketchService
|
||||
.getSketches({ uri: sketchDirUri })
|
||||
.then((container) => {
|
||||
const sketchbookUri = new URI(sketchDirUri);
|
||||
for (const sketch of SketchContainer.toArray(container)) {
|
||||
this.sketches.set(sketch.uri, sketch);
|
||||
}
|
||||
this.toDispose.push(
|
||||
// Watch changes in the sketchbook to update `File` > `Sketchbook` menu items.
|
||||
this.fileService.watch(new URI(sketchDirUri), {
|
||||
recursive: true,
|
||||
excludes: [],
|
||||
})
|
||||
);
|
||||
this.toDispose.push(
|
||||
this.fileService.onDidFilesChange(async (event) => {
|
||||
for (const { type, resource } of event.changes) {
|
||||
// The file change events have higher precedence in the current sketch over the sketchbook.
|
||||
if (
|
||||
CurrentSketch.isValid(this._currentSketch) &&
|
||||
new URI(this._currentSketch.uri).isEqualOrParent(resource)
|
||||
) {
|
||||
// https://github.com/arduino/arduino-ide/pull/1351#pullrequestreview-1086666656
|
||||
// On a sketch file rename, the FS watcher will contain two changes:
|
||||
// - Deletion of the original file,
|
||||
// - Update of the new file,
|
||||
// Hence, `UPDATE` events must be processed but only and if only there is a `DELETED` change in the same event.
|
||||
// Otherwise, IDE2 would ask CLI to reload the sketch content on every save event in IDE2.
|
||||
if (
|
||||
type === FileChangeType.UPDATED &&
|
||||
event.changes.length === 1
|
||||
) {
|
||||
// If the event contains only one `UPDATE` change, it cannot be a rename.
|
||||
return;
|
||||
}
|
||||
|
||||
let reloadedSketch: Sketch | undefined = undefined;
|
||||
try {
|
||||
reloadedSketch = await this.sketchService.loadSketch(
|
||||
this._currentSketch.uri
|
||||
);
|
||||
} catch (err) {
|
||||
if (!SketchesError.NotFound.is(err)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reloadedSketch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Sketch.sameAs(this._currentSketch, reloadedSketch)) {
|
||||
this.useCurrentSketch(reloadedSketch, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We track main sketch files changes only. // TODO: check sketch folder changes. One can rename the folder without renaming the `.ino` file.
|
||||
if (sketchbookUri.isEqualOrParent(resource)) {
|
||||
if (Sketch.isSketchFile(resource)) {
|
||||
if (type === FileChangeType.ADDED) {
|
||||
try {
|
||||
const toAdd = await this.sketchService.loadSketch(
|
||||
resource.parent.toString()
|
||||
);
|
||||
if (!this.sketches.has(toAdd.uri)) {
|
||||
console.log(
|
||||
`New sketch '${toAdd.name}' was created in sketchbook '${sketchDirUri}'.`
|
||||
);
|
||||
this.sketches.set(toAdd.uri, toAdd);
|
||||
this.fireSoon(toAdd, 'created');
|
||||
}
|
||||
} catch {}
|
||||
} else if (type === FileChangeType.DELETED) {
|
||||
const uri = resource.parent.toString();
|
||||
const toDelete = this.sketches.get(uri);
|
||||
if (toDelete) {
|
||||
console.log(
|
||||
`Sketch '${toDelete.name}' was removed from sketchbook '${sketchbookUri}'.`
|
||||
);
|
||||
this.sketches.delete(uri);
|
||||
this.fireSoon(toDelete, 'removed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
const sketchDirUri = this.configService.tryGetSketchDirUri();
|
||||
this.watchSketchbookDir(sketchDirUri);
|
||||
const refreshCurrentSketch = async () => {
|
||||
const currentSketch = await this.loadCurrentSketch();
|
||||
this.useCurrentSketch(currentSketch);
|
||||
};
|
||||
this.toDispose.push(
|
||||
this.configService.onDidChangeSketchDirUri((sketchDirUri) => {
|
||||
this.watchSketchbookDir(sketchDirUri);
|
||||
refreshCurrentSketch();
|
||||
})
|
||||
);
|
||||
this.appStateService
|
||||
.reachedState('started_contributions')
|
||||
.then(async () => {
|
||||
const currentSketch = await this.loadCurrentSketch();
|
||||
this.useCurrentSketch(currentSketch);
|
||||
});
|
||||
.then(refreshCurrentSketch);
|
||||
}
|
||||
|
||||
private async watchSketchbookDir(
|
||||
sketchDirUri: URI | undefined
|
||||
): Promise<void> {
|
||||
this.toDisposeBeforeWatchSketchbookDir.dispose();
|
||||
if (!sketchDirUri) {
|
||||
return;
|
||||
}
|
||||
const container = await this.sketchService.getSketches({
|
||||
uri: sketchDirUri.toString(),
|
||||
});
|
||||
for (const sketch of SketchContainer.toArray(container)) {
|
||||
this.sketches.set(sketch.uri, sketch);
|
||||
}
|
||||
this.toDisposeBeforeWatchSketchbookDir.pushAll([
|
||||
Disposable.create(() => this.sketches.clear()),
|
||||
// Watch changes in the sketchbook to update `File` > `Sketchbook` menu items.
|
||||
this.fileService.watch(sketchDirUri, {
|
||||
recursive: true,
|
||||
excludes: [],
|
||||
}),
|
||||
this.fileService.onDidFilesChange(async (event) => {
|
||||
for (const { type, resource } of event.changes) {
|
||||
// The file change events have higher precedence in the current sketch over the sketchbook.
|
||||
if (
|
||||
CurrentSketch.isValid(this._currentSketch) &&
|
||||
new URI(this._currentSketch.uri).isEqualOrParent(resource)
|
||||
) {
|
||||
// https://github.com/arduino/arduino-ide/pull/1351#pullrequestreview-1086666656
|
||||
// On a sketch file rename, the FS watcher will contain two changes:
|
||||
// - Deletion of the original file,
|
||||
// - Update of the new file,
|
||||
// Hence, `UPDATE` events must be processed but only and if only there is a `DELETED` change in the same event.
|
||||
// Otherwise, IDE2 would ask CLI to reload the sketch content on every save event in IDE2.
|
||||
if (type === FileChangeType.UPDATED && event.changes.length === 1) {
|
||||
// If the event contains only one `UPDATE` change, it cannot be a rename.
|
||||
return;
|
||||
}
|
||||
|
||||
let reloadedSketch: Sketch | undefined = undefined;
|
||||
try {
|
||||
reloadedSketch = await this.sketchService.loadSketch(
|
||||
this._currentSketch.uri
|
||||
);
|
||||
} catch (err) {
|
||||
if (!SketchesError.NotFound.is(err)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reloadedSketch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Sketch.sameAs(this._currentSketch, reloadedSketch)) {
|
||||
this.useCurrentSketch(reloadedSketch, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We track main sketch files changes only. // TODO: check sketch folder changes. One can rename the folder without renaming the `.ino` file.
|
||||
if (sketchDirUri.isEqualOrParent(resource)) {
|
||||
if (Sketch.isSketchFile(resource)) {
|
||||
if (type === FileChangeType.ADDED) {
|
||||
try {
|
||||
const toAdd = await this.sketchService.loadSketch(
|
||||
resource.parent.toString()
|
||||
);
|
||||
if (!this.sketches.has(toAdd.uri)) {
|
||||
console.log(
|
||||
`New sketch '${toAdd.name}' was created in sketchbook '${sketchDirUri}'.`
|
||||
);
|
||||
this.sketches.set(toAdd.uri, toAdd);
|
||||
this.fireSoon(toAdd, 'created');
|
||||
}
|
||||
} catch {}
|
||||
} else if (type === FileChangeType.DELETED) {
|
||||
const uri = resource.parent.toString();
|
||||
const toDelete = this.sketches.get(uri);
|
||||
if (toDelete) {
|
||||
console.log(
|
||||
`Sketch '${toDelete.name}' was removed from sketchbook '${sketchDirUri}'.`
|
||||
);
|
||||
this.sketches.delete(uri);
|
||||
this.fireSoon(toDelete, 'removed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
private useCurrentSketch(
|
||||
@@ -249,7 +255,7 @@ export class SketchesServiceClientImpl
|
||||
event.removed.push(sketch);
|
||||
}
|
||||
}
|
||||
this.sketchbookDidChangeEmitter.fire(event);
|
||||
this.onSketchbookDidChangeEmitter.fire(event);
|
||||
this.bufferedSketchbookEvents.length = 0;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user