Skip to content
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

[WIP] Add opened examples to Open Recent menu #1376

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
14 changes: 9 additions & 5 deletions arduino-ide-extension/src/browser/contributions/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import {
SketchContribution,
CommandRegistry,
MenuModelRegistry,
URI,
} from './contribution';
import { NotificationCenter } from '../notification-center';
import { Board, SketchRef, SketchContainer } from '../../common/protocol';
import { nls } from '@theia/core/lib/common';
import { nls } from '@theia/core/lib/common/nls';

@injectable()
export abstract class Examples extends SketchContribution {
Expand Down Expand Up @@ -150,10 +151,13 @@ export abstract class Examples extends SketchContribution {
return {
execute: async () => {
const sketch = await this.sketchService.cloneExample(uri);
return this.commandService.executeCommand(
OpenSketch.Commands.OPEN_SKETCH.id,
sketch
);
return this.commandService
.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch)
.then((result) => {
const name = new URI(uri).path.base;
this.sketchService.markAsRecentlyOpened({ name, sourceUri: uri }); // no await
return result;
});
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { MainMenuManager } from '../../common/main-menu-manager';
import { OpenSketch } from './open-sketch';
import { NotificationCenter } from '../notification-center';
import { nls } from '@theia/core/lib/common';
import { ExampleRef } from '../../common/protocol';

@injectable()
export class OpenRecentSketch extends SketchContribution {
Expand Down Expand Up @@ -55,26 +56,30 @@ export class OpenRecentSketch extends SketchContribution {
);
}

private refreshMenu(sketches: Sketch[]): void {
private refreshMenu(sketches: (Sketch | ExampleRef)[]): void {
this.register(sketches);
this.mainMenuManager.update();
}

protected register(sketches: Sketch[]): void {
protected register(sketches: (Sketch | ExampleRef)[]): void {
const order = 0;
for (const sketch of sketches) {
const { uri } = sketch;
const uri = Sketch.is(sketch) ? sketch.uri : sketch.sourceUri;
const toDispose = this.toDisposeBeforeRegister.get(uri);
if (toDispose) {
toDispose.dispose();
}
const command = { id: `arduino-open-recent--${uri}` };
const handler = {
execute: () =>
execute: async () => {
const toOpen = Sketch.is(sketch)
? sketch
: await this.sketchService.cloneExample(sketch.sourceUri);
this.commandRegistry.executeCommand(
OpenSketch.Commands.OPEN_SKETCH.id,
sketch
),
toOpen
);
},
};
this.commandRegistry.registerCommand(command, handler);
this.menuRegistry.registerMenuAction(
Expand All @@ -86,7 +91,7 @@ export class OpenRecentSketch extends SketchContribution {
}
);
this.toDisposeBeforeRegister.set(
sketch.uri,
uri,
new DisposableCollection(
Disposable.create(() =>
this.commandRegistry.unregisterCommand(command)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
Config,
ProgressMessage,
Sketch,
ExampleRef,
} from '../protocol';
import type { LibraryPackage } from './library-service';

Expand All @@ -27,7 +28,9 @@ export interface NotificationServiceClient {
notifyLibraryDidInstall(event: { item: LibraryPackage }): void;
notifyLibraryDidUninstall(event: { item: LibraryPackage }): void;
notifyAttachedBoardsDidChange(event: AttachedBoardsChangeEvent): void;
notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void;
notifyRecentSketchesDidChange(event: {
sketches: (Sketch | ExampleRef)[];
}): void;
}

export const NotificationServicePath = '/services/notification-service';
Expand Down
25 changes: 23 additions & 2 deletions arduino-ide-extension/src/common/protocol/sketches-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ export interface SketchesService {
/**
* Marks the sketch with the given URI as recently opened. It does nothing if the sketch is temp or not valid.
*/
markAsRecentlyOpened(uri: string): Promise<void>;
markAsRecentlyOpened(uriOrRef: string | ExampleRef): Promise<void>;

/**
* Resolves to an array of sketches in inverse chronological order. The newest is the first.
*/
recentlyOpenedSketches(): Promise<Sketch[]>;
recentlyOpenedSketches(): Promise<(Sketch | ExampleRef)[]>;

/**
* Archives the sketch, resolves to the archive URI.
Expand All @@ -102,6 +102,27 @@ export interface SketchesService {
deleteSketch(sketch: Sketch): Promise<void>;
}

export interface ExampleRef {
/**
* Name of the example.
*/
readonly name: string;
/**
* This is the location where the example is. IDE2 will clone the sketch from this location.
*/
readonly sourceUri: string;
}
export namespace ExampleRef {
export function is(arg: unknown): arg is ExampleRef {
return (
(arg as ExampleRef).name !== undefined &&
typeof (arg as ExampleRef).name === 'string' &&
(arg as ExampleRef).sourceUri !== undefined &&
typeof (arg as ExampleRef).sourceUri === 'string'
);
}
}

export interface SketchRef {
readonly name: string;
readonly uri: string; // `LocationPath`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
Config,
Sketch,
ProgressMessage,
ExampleRef,
} from '../common/protocol';

@injectable()
Expand Down Expand Up @@ -76,7 +77,9 @@ export class NotificationServiceServerImpl
this.clients.forEach((client) => client.notifyConfigDidChange(event));
}

notifyRecentSketchesDidChange(event: { sketches: Sketch[] }): void {
notifyRecentSketchesDidChange(event: {
sketches: (Sketch | ExampleRef)[];
}): void {
this.clients.forEach((client) =>
client.notifyRecentSketchesDidChange(event)
);
Expand Down
65 changes: 45 additions & 20 deletions arduino-ide-extension/src/node/sketches-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
SketchRef,
SketchContainer,
SketchesError,
ExampleRef,
} from '../common/protocol/sketches-service';
import { NotificationServiceServerImpl } from './notification-service-server';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
Expand Down Expand Up @@ -257,9 +258,7 @@ export class SketchesServiceImpl
.then((uri) => path.join(FileUri.fsPath(uri), 'recent-sketches.json'));
}

private async loadRecentSketches(
fsPath: string
): Promise<Record<string, number>> {
private async loadRecentSketches(fsPath: string): Promise<RecentSketches> {
let data: Record<string, number> = {};
try {
const raw = await promisify(fs.readFile)(fsPath, {
Expand All @@ -270,7 +269,9 @@ export class SketchesServiceImpl
return data;
}

async markAsRecentlyOpened(uri: string): Promise<void> {
async markAsRecentlyOpened(uriOrRef: string | ExampleRef): Promise<void> {
const isExample = typeof uriOrRef !== 'string';
const uri = isExample ? uriOrRef.sourceUri : uriOrRef;
let sketch: Sketch | undefined = undefined;
try {
sketch = await this.loadSketch(uri);
Expand All @@ -284,15 +285,23 @@ export class SketchesServiceImpl
const fsPath = await this.recentSketchesFsPath;
const data = await this.loadRecentSketches(fsPath);
const now = Date.now();
data[sketch.uri] = now;
data[sketch.uri] = isExample ? { type: 'example', mtimeMs: now } : now;

let toDeleteUri: string | undefined = undefined;
if (Object.keys(data).length > 10) {
let min = Number.MAX_SAFE_INTEGER;
for (const uri of Object.keys(data)) {
if (min > data[uri]) {
min = data[uri];
toDeleteUri = uri;
const value = data[uri];
if (typeof value === 'number') {
if (min > value) {
min = value;
toDeleteUri = uri;
}
} else {
if (min > value.mtimeMs) {
min = value.mtimeMs;
toDeleteUri = uri;
}
}
}
}
Expand All @@ -307,28 +316,39 @@ export class SketchesServiceImpl
);
}

async recentlyOpenedSketches(): Promise<Sketch[]> {
async recentlyOpenedSketches(): Promise<(Sketch | ExampleRef)[]> {
const configDirUri = await this.envVariableServer.getConfigDirUri();
const fsPath = path.join(
FileUri.fsPath(configDirUri),
'recent-sketches.json'
);
let data: Record<string, number> = {};
let data: RecentSketches = {};
try {
const raw = await promisify(fs.readFile)(fsPath, {
encoding: 'utf8',
});
data = JSON.parse(raw);
} catch {}

const sketches: SketchWithDetails[] = [];
for (const uri of Object.keys(data).sort(
(left, right) => data[right] - data[left]
)) {
try {
const sketch = await this.loadSketch(uri);
sketches.push(sketch);
} catch {}
const sketches: (Sketch | ExampleRef)[] = [];
for (const uri of Object.keys(data).sort((left, right) => {
const leftValue = data[left];
const rightValue = data[right];
const leftMtimeMs =
typeof leftValue === 'number' ? leftValue : leftValue.mtimeMs;
const rightMtimeMs =
typeof rightValue === 'number' ? rightValue : rightValue.mtimeMs;
return leftMtimeMs - rightMtimeMs;
})) {
const value = data[uri];
if (typeof value === 'number') {
try {
const sketch = await this.loadSketch(uri);
sketches.push(sketch);
} catch {}
} else {
sketches.push({ name: new URI(uri).path.base, sourceUri: uri });
}
}

return sketches;
Expand Down Expand Up @@ -417,9 +437,9 @@ void loop() {
* For example, on Windows, instead of getting an [8.3 filename](https://en.wikipedia.org/wiki/8.3_filename), callers will get a fully resolved path.
* `C:\\Users\\KITTAA~1\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2022615-21100-iahybb.yyvh\\sketch_jul15a` will be `C:\\Users\\kittaakos\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2022615-21100-iahybb.yyvh\\sketch_jul15a`
*/
private createTempFolder(): Promise<string> {
private createTempFolder(prefix: string = TempSketchPrefix): Promise<string> {
return new Promise<string>((resolve, reject) => {
temp.mkdir({ prefix: TempSketchPrefix }, (createError, dirPath) => {
temp.mkdir({ prefix }, (createError, dirPath) => {
if (createError) {
reject(createError);
return;
Expand Down Expand Up @@ -634,3 +654,8 @@ function sketchIndexToLetters(num: number): string {
} while (pow > 0);
return out;
}

type RecentSketches = Record<
string,
number | { type: 'example'; mtimeMs: number }
>;