Skip to content

Commit ec150c8

Browse files
committed
Refactor version handling and sidebar setup; enhance command execution and caching mechanisms
1 parent 301245d commit ec150c8

File tree

5 files changed

+93
-40
lines changed

5 files changed

+93
-40
lines changed

client/src/extension.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11

22
import { ExtensionContext, Terminal } from 'vscode';
33

4-
import {
5-
LanguageClient
6-
} from 'vscode-languageclient/node';
4+
import { LanguageClient } from 'vscode-languageclient/node';
75
import { setupSelectedVersion } from './setupSelectedVersion';
86
import { setupCommands } from './setupCommands';
97
import { setupLanguageServer } from './setupLanguageServer';
108
import { setupSidebar } from './setupSidebar';
119
import { setupDecorators } from './setupDecorators';
1210
import { setupPDEFiles } from './setupPDEFiles';
11+
import { EventEmitter } from 'stream';
1312

1413

1514
export interface ProcessingVersion {
@@ -21,16 +20,19 @@ export const state = {
2120
terminal: undefined as Terminal | undefined,
2221
client: undefined as LanguageClient | undefined,
2322
selectedVersion: undefined as ProcessingVersion | undefined,
23+
onDidVersionChange: new EventEmitter<null>(),
2424
};
2525

2626
export async function activate(context: ExtensionContext) {
2727
// TODO: Detect other Processing extensions and warn the user
28+
// TODO: Add a open contribution manager action (Needs Processing integration)
29+
// TODO: Add checks for if a required Processing version is met (e.g, this specific feature works with 4.4.6+)
2830

29-
// TODO: Cache the selected version between sessions
31+
// Set up the selected Processing version, awaiting because we don't want to continue until we have a version
3032
await setupSelectedVersion(context);
3133
setupCommands(context);
3234
setupLanguageServer();
33-
setupSidebar();
35+
setupSidebar(context);
3436
setupDecorators(context);
3537
setupPDEFiles();
3638
}

client/src/setupCommands.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { dirname } from 'path/posix';
1+
import { dirname, join } from 'path';
22
import { ExtensionContext, commands, Uri, window, workspace } from 'vscode';
33
import { state } from './extension';
4+
import { tmpdir } from 'os';
45

56
export function setupCommands(context: ExtensionContext) {
67
const runSketch = commands.registerCommand('processing.sketch.run', (resource: Uri) => {
@@ -59,12 +60,22 @@ export function setupCommands(context: ExtensionContext) {
5960
state.terminal.sendText('\x03', false);
6061
});
6162

62-
const openSketch = commands.registerCommand('processing.sketch.open', async (folder) => {
63+
const openSketch = commands.registerCommand('processing.sketch.open', async (folder: string, isReadOnly: boolean) => {
6364
if (!folder) {
6465
window.showErrorMessage("No sketch folder provided.");
6566
return;
6667
}
67-
// TODO: Copy examples/readonly sketches to a temp location and open them there
68+
if (isReadOnly) {
69+
const path = join(tmpdir(), `processing-sketch-${new Date().getTime()}`, folder.split('/').pop() || 'sketch');
70+
try {
71+
await workspace.fs.copy(Uri.file(folder), Uri.file(path), { overwrite: true });
72+
folder = path;
73+
} catch (error) {
74+
window.showErrorMessage(`Could not open read-only sketch: ${error instanceof Error ? error.message : error}`);
75+
console.error(error);
76+
return;
77+
}
78+
}
6879

6980
const newWindow = workspace
7081
.getConfiguration('processing')

client/src/setupLanguageServer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ import { Executable, LanguageClient } from 'vscode-languageclient/node';
44
import { state } from './extension';
55

66
export function setupLanguageServer() {
7+
// TODO: Listen for version changes and restart the server
8+
79
const serverOptions: Executable = {
810
command: state.selectedVersion.path,
911
args: ["lsp"]
1012
};
1113

12-
// Options to control the language client
1314
const clientOptions: LanguageClientOptions = {
14-
// Register the server for plain text documents
1515
documentSelector: [{ scheme: 'file', language: 'Processing' }],
1616
synchronize: {
1717
// Notify the server about file changes to '.clientrc files contained in the workspace
18-
fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
18+
fileEvents: workspace.createFileSystemWatcher('**/*.{pde,java}')
1919
}
2020
};
2121

2222
// Create the language client and start the client.
2323
state.client = new LanguageClient(
24-
'languageServerExample',
24+
'processingLanguageServer',
2525
'Processing Language Server',
2626
serverOptions,
2727
clientOptions

client/src/setupSelectedVersion.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,24 @@ import { compareVersions } from './compareVersions';
55
import { ProcessingVersion, state } from './extension';
66

77
export async function setupSelectedVersion(context: ExtensionContext) {
8-
// TODO: Rerun this function when the user changes the version in the settings
8+
const version = context.globalState.get<string>('processing-version');
9+
const path = context.globalState.get<string>('processing-path');
910

11+
if (version && path) {
12+
state.selectedVersion = { version, path };
13+
selectProcessingVersion(context);
14+
} else {
15+
await selectProcessingVersion(context);
16+
}
17+
18+
workspace.onDidChangeConfiguration(async (e) => {
19+
if (e.affectsConfiguration('processing.version')) {
20+
selectProcessingVersion(context);
21+
}
22+
});
23+
}
24+
25+
async function selectProcessingVersion(context: ExtensionContext) {
1026
const config = workspace.getConfiguration('processing');
1127

1228
const versions = await fetchProcessingVersions(context);
@@ -55,7 +71,14 @@ export async function setupSelectedVersion(context: ExtensionContext) {
5571
return;
5672
}
5773
state.selectedVersion = selectedVersion;
74+
state.onDidVersionChange.emit(null);
75+
76+
// Set the cache for the next time
77+
context.globalState.update('processing-path', selectedVersion.path);
78+
context.globalState.update('processing-version', selectedVersion.version);
5879
}
80+
81+
5982
async function fetchProcessingVersions(context: ExtensionContext) {
6083
const binaryPath = await getBinaryPathWithPermissions(context);
6184

client/src/setupSidebar.ts

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { exec, spawn } from 'child_process';
1+
import { spawn } from 'child_process';
22
import { join } from 'path';
3-
import { Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState, window } from 'vscode';
3+
import { Event, EventEmitter, ExtensionContext, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState, window } from 'vscode';
44
import { state } from './extension';
55
import { existsSync } from 'fs';
66

@@ -21,41 +21,54 @@ export interface Folder {
2121
sketches?: Sketch[];
2222
}
2323

24-
export async function setupSidebar() {
24+
export async function setupSidebar(context: ExtensionContext) {
2525
// TODO: Show welcome screens whilst we are starting Processing
26-
// TODO: Open examples as read-only or in a temporary location
27-
// TODO: Reload examples and sketchbook when Processing version changes
28-
// TODO: Add cache to results to speed up loading
2926

30-
setupExamples();
31-
setupSketchbook();
27+
setupSketchTreeView('sketchbook list', 'processingSidebarSketchbookView', context);
28+
setupSketchTreeView('contributions examples list', 'processingSidebarExamplesView', context, true);
3229
}
3330

34-
async function setupExamples() {
35-
const examplesProvider = new ProcessingWindowDataProvider('contributions examples list');
36-
window.createTreeView('processingSidebarExamplesView', { treeDataProvider: examplesProvider });
37-
}
38-
39-
async function setupSketchbook() {
40-
const sketchbookProvider = new ProcessingWindowDataProvider('sketchbook list');
41-
window.createTreeView('processingSidebarSketchbookView', { treeDataProvider: sketchbookProvider });
31+
function setupSketchTreeView(command: string, viewId: string, context: ExtensionContext, readonly = false) {
32+
const provider = new ProcessingWindowDataProvider(command, context, readonly);
33+
window.createTreeView(viewId, { treeDataProvider: provider });
4234
}
4335

4436

4537
class ProcessingWindowDataProvider implements TreeDataProvider<FolderTreeItem | SketchTreeItem> {
4638
constructor(
47-
public readonly command: string
39+
public readonly command: string,
40+
public readonly context: ExtensionContext,
41+
public readonly readonly = false
4842
) {
49-
this._folders = [];
43+
this.cached();
5044
this.populate();
45+
this.listen();
5146
}
52-
private _folders: Folder[];
47+
private _folders: Folder[] = [];
48+
49+
private _onDidChangeTreeData: EventEmitter<null> = new EventEmitter<null>();
50+
readonly onDidChangeTreeData: Event<null> = this._onDidChangeTreeData.event;
5351

54-
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
55-
readonly onDidChangeTreeData: Event<any> = this._onDidChangeTreeData.event;
52+
async listen() {
53+
state.onDidVersionChange.on(null, async () => {
54+
this.populate();
55+
});
56+
}
57+
58+
async cached() {
59+
const data = await this.context.globalState.get<string>(`processing-tree-view-${this.command}-cache`);
60+
if (data) {
61+
try {
62+
this._folders = JSON.parse(data) as Folder[];
63+
} catch (e) {
64+
console.error(`Error parsing cached JSON: ${e}`);
65+
}
66+
}
67+
this._onDidChangeTreeData.fire(null);
68+
}
5669

5770
async populate() {
58-
this._folders = await this.grabSketchesWithCommand(this.command);
71+
this._folders = await this.grabSketches();
5972
this._onDidChangeTreeData.fire(null);
6073
}
6174

@@ -68,7 +81,7 @@ class ProcessingWindowDataProvider implements TreeDataProvider<FolderTreeItem |
6881
return this._folders.map((folder) => new FolderTreeItem(folder)) ?? [];
6982
} else {
7083
const sketches = element.folder.sketches?.map((sketch) => {
71-
return new SketchTreeItem(sketch);
84+
return new SketchTreeItem(sketch, this.readonly);
7285
}) ?? [];
7386
const folders = element.folder.children?.map((folder) => {
7487
return new FolderTreeItem(folder);
@@ -82,9 +95,9 @@ class ProcessingWindowDataProvider implements TreeDataProvider<FolderTreeItem |
8295
}
8396
}
8497

85-
grabSketchesWithCommand(command: string): Promise<Folder[]> {
98+
grabSketches(): Promise<Folder[]> {
8699
return new Promise<Folder[]>((resolve) => {
87-
const process = spawn(state.selectedVersion.path, command.split(' '));
100+
const process = spawn(state.selectedVersion.path, this.command.split(' '));
88101
let data = '';
89102
process.stdout.on('data', (chunk) => {
90103
data += chunk;
@@ -96,7 +109,9 @@ class ProcessingWindowDataProvider implements TreeDataProvider<FolderTreeItem |
96109
return;
97110
}
98111
try {
112+
this.context.globalState.update(`processing-tree-view-${this.command}-cache`, data);
99113
const folders = JSON.parse(data) as Folder[];
114+
100115
resolve(folders);
101116
} catch (e) {
102117
console.error(`Error parsing JSON: ${e}`);
@@ -119,7 +134,8 @@ class FolderTreeItem extends TreeItem {
119134

120135
class SketchTreeItem extends TreeItem {
121136
constructor(
122-
public readonly sketch: Sketch
137+
public readonly sketch: Sketch,
138+
public readonly readonly = false
123139
) {
124140
const label = sketch.name;
125141
super(label, TreeItemCollapsibleState.None);
@@ -128,8 +144,9 @@ class SketchTreeItem extends TreeItem {
128144
this.command = {
129145
command: 'processing.sketch.open',
130146
title: 'Open Sketch',
131-
arguments: [this.sketch.path]
147+
arguments: [this.sketch.path, this.readonly]
132148
};
149+
// TODO: add right-click menu to open in new window, open containing folder, etc.
133150

134151
// TODO: Make showing a preview a toggleable setting
135152
const preview = `${sketch.path}/${sketch.name}.png`;

0 commit comments

Comments
 (0)