From c0b6335751288347bec1955d633219f025651779 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Thu, 18 Apr 2024 07:56:15 -0700 Subject: [PATCH 01/38] Experimental extensions to UI in VScode plugin --- .vscode/launch.json | 1 + package.json | 26 +++++++++++++++++++++++++- src/extension.ts | 5 +++++ tsconfig.json | 2 +- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 14afd7061..705d2ebbd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,7 @@ "outFiles": [ "${workspaceFolder}/out/**/*.js" ], + "sourceMaps": true, "request": "launch", "type": "extensionHost" }, diff --git a/package.json b/package.json index 63a3ced31..4d18ada92 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,8 @@ }, { "command": "linguafranca.buildAndRun", - "title": "Lingua Franca: Build and Run" + "title": "Lingua Franca: Build and Run", + "icon": "$(play)" }, { "command": "linguafranca.createNewFile", @@ -104,6 +105,11 @@ "when": "resourceLangId == lflang", "command": "klighd-vscode.diagram.open", "group": "navigation" + }, + { + "when": "resourceLangId == lflang", + "command": "linguafranca.buildAndRun", + "group": "navigation" } ], "editor/context": [ @@ -125,6 +131,24 @@ "command": "linguafranca.createNewFile" } ] + }, + "viewsContainers": { + "activitybar": [ + { + "id": "lf-lang", + "title": "Lingua Franca", + "icon": "$(flame)" + } + ] + }, + "views": { + "lf-lang": [ + { + "type": "tree", + "id": "lf-lang-explorer", + "name": "Lingua Franca Explorer" + } + ] } }, "devDependencies": { diff --git a/src/extension.ts b/src/extension.ts index c1199d5ff..1e2eb0e2b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,6 +11,7 @@ import { legend, semanticTokensProvider } from './highlight'; import * as config from './config'; import { registerBuildCommands, registerNewFileCommand } from './build_commands'; import * as checkDependencies from './check_dependencies'; +import { LFDataProvider } from './lfview/lf-data-provider'; let client: LanguageClient; let socket: Socket @@ -56,6 +57,10 @@ export async function activate(context: vscode.ExtensionContext) { registerBuildCommands(context, client); registerNewFileCommand(context); + + const lfDataProvider = new LFDataProvider(client, context); + vscode.window.registerTreeDataProvider('lf-lang-explorer', lfDataProvider); + vscode.window.createTreeView('lf-lang-explorer', {treeDataProvider: lfDataProvider}); } /** diff --git a/tsconfig.json b/tsconfig.json index 5e734adb8..5eb2ba157 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "module": "commonjs", "moduleResolution": "node", "outDir": "./out", - "sourceMap": false, + "sourceMap": true, "target": "esnext", "rootDir": "./src", }, From c793eb053c9175883d48917dfbf9af5398481e67 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Sat, 20 Apr 2024 18:48:52 -0700 Subject: [PATCH 02/38] Push LF data provider --- src/lfview/lf-data-provider.ts | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/lfview/lf-data-provider.ts diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts new file mode 100644 index 000000000..02b17a098 --- /dev/null +++ b/src/lfview/lf-data-provider.ts @@ -0,0 +1,44 @@ +import * as vscode from 'vscode' +import { TreeItemCollapsibleState } from 'vscode' +import { LanguageClient } from 'vscode-languageclient' + +export class LFDataProvider implements vscode.TreeDataProvider { + constructor( + private client: LanguageClient, + private context: vscode.ExtensionContext + ) { + // vscode.workspace.onDidChangeWorkspaceFolders(() + } + onDidChangeTreeData?: vscode.Event + getTreeItem(element: LFDataProviderNode): vscode.TreeItem | Thenable { + return element + } + getChildren(element?: LFDataProviderNode): vscode.ProviderResult { + if (!element) { + let result = [] + return vscode.workspace.findFiles('**/lib/*.lf').then(files => { + files.forEach(file => { + result.push(new LFDataProviderNode(file)) + }) + return result + }) + } + return [] + } + getParent?(element: LFDataProviderNode): vscode.ProviderResult { + throw new Error('Method not implemented.') + } + resolveTreeItem?(item: vscode.TreeItem, element: LFDataProviderNode, token: vscode.CancellationToken): vscode.ProviderResult { + throw new Error('Method not implemented.') + } +} + +export class LFDataProviderNode extends vscode.TreeItem { + constructor(resourceUri: vscode.Uri) { + if (resourceUri.path.endsWith('.lf')) { + super(resourceUri, TreeItemCollapsibleState.None) + } else { + super(resourceUri, TreeItemCollapsibleState.Collapsed) + } + } +} From f5b9fc6f8a77d67ec0d616c09bf1415fae72265e Mon Sep 17 00:00:00 2001 From: Soeren Domroes Date: Mon, 22 Apr 2024 17:32:16 -0700 Subject: [PATCH 03/38] Add LSP message. --- .vscode/launch.json | 21 ++++++++++++++++++++- src/lfview/lf-data-provider.ts | 4 ++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 705d2ebbd..a6bccdc18 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "type": "extensionHost" }, { - "name": "Launch VS Code Extension (Socket) LF", + "name": "Launch VS Code Extension (Socket) LF with Klighd", "type": "extensionHost", "request": "launch", "args": [ @@ -39,6 +39,25 @@ "${workspaceFolder}/../klighd-vscode/applications/klighd-vscode/dist/**/*.js" ], }, + { + "name": "Launch VS Code Extension (Socket) LF", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/", + ], + "env": { + "LF_LS_PORT": "7670" + }, + "skipFiles": [ + "/**" + ], + "sourceMaps": true, + "smartStep": true, + "outFiles": [ + "${workspaceFolder}/out/**/*.js", + ], + }, { "args": [ "--extensionDevelopmentPath=${workspaceFolder}", diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 02b17a098..49a4e1ded 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -15,6 +15,10 @@ export class LFDataProvider implements vscode.TreeDataProvider { if (!element) { + this.client.sendRequest('generator/getLibraryReactors').then(message => { + + console.log("+++++++++++++++++++++++++" + message) + }) let result = [] return vscode.workspace.findFiles('**/lib/*.lf').then(files => { files.forEach(file => { From 6428a3d7a60072342ad2eefbeee48124494ccfda Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto Date: Tue, 23 Apr 2024 10:34:12 -0700 Subject: [PATCH 04/38] Bug Fix - wait until LS is ready to handle requests --- .gitignore | 1 + src/lfview/lf-data-provider.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 183a375d3..13c4b9cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules/* out/* .gradle/* __pycache__/* +.DS_Store diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 49a4e1ded..43e035eb0 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -15,9 +15,10 @@ export class LFDataProvider implements vscode.TreeDataProvider { if (!element) { - this.client.sendRequest('generator/getLibraryReactors').then(message => { - - console.log("+++++++++++++++++++++++++" + message) + this.client.onReady().then(() => { + this.client.sendRequest('generator/getLibraryReactors').then(message => { + console.log("+++++++++++++++++++++++++" + message) + }) }) let result = [] return vscode.workspace.findFiles('**/lib/*.lf').then(files => { From f1279d6989db11ef263c425ab6c81db91eabacb1 Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto Date: Tue, 23 Apr 2024 11:36:51 -0700 Subject: [PATCH 05/38] Set-up notification handler from server to client --- src/lfview/lf-data-provider.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 43e035eb0..befb007e4 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -8,6 +8,11 @@ export class LFDataProvider implements vscode.TreeDataProvider { + client.onNotification('update/example', (message) => { + console.log("*******" + message + "*******") + }) + }) } onDidChangeTreeData?: vscode.Event getTreeItem(element: LFDataProviderNode): vscode.TreeItem | Thenable { From f4687647371a90272afaeae779392604a0a761de Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto Date: Mon, 29 Apr 2024 16:17:56 -0700 Subject: [PATCH 06/38] Improved Tree View: hierarchical structure, go to file --- images/icon.png | Bin 12442 -> 0 bytes images/icons/code/code-dark.svg | 3 + images/icons/code/code-light.svg | 3 + images/icons/file/file-dark.svg | 3 + images/icons/file/file-light.svg | 3 + images/icons/root/root-dark.svg | 6 + images/icons/root/root-light.svg | 5 + images/icons/root/root-open-dark.svg | 5 + images/icons/root/root-open-light.svg | 5 + images/logo/lf-logo-dark.svg | 37 +++++ images/logo/lf-logo-light.svg | 34 +++++ package.json | 37 ++++- src/build_commands.ts | 1 + src/extension.ts | 13 +- src/lfview/lf-data-provider.ts | 203 +++++++++++++++++++++++--- 15 files changed, 328 insertions(+), 30 deletions(-) delete mode 100644 images/icon.png create mode 100644 images/icons/code/code-dark.svg create mode 100644 images/icons/code/code-light.svg create mode 100644 images/icons/file/file-dark.svg create mode 100644 images/icons/file/file-light.svg create mode 100644 images/icons/root/root-dark.svg create mode 100644 images/icons/root/root-light.svg create mode 100644 images/icons/root/root-open-dark.svg create mode 100644 images/icons/root/root-open-light.svg create mode 100644 images/logo/lf-logo-dark.svg create mode 100644 images/logo/lf-logo-light.svg diff --git a/images/icon.png b/images/icon.png deleted file mode 100644 index 40fdd41e45080eaea176c53b21c6c6db1da6f32c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12442 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 diff --git a/images/icons/code/code-dark.svg b/images/icons/code/code-dark.svg new file mode 100644 index 000000000..d53777300 --- /dev/null +++ b/images/icons/code/code-dark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/icons/code/code-light.svg b/images/icons/code/code-light.svg new file mode 100644 index 000000000..d7a79a343 --- /dev/null +++ b/images/icons/code/code-light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/icons/file/file-dark.svg b/images/icons/file/file-dark.svg new file mode 100644 index 000000000..e57f1ece8 --- /dev/null +++ b/images/icons/file/file-dark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/icons/file/file-light.svg b/images/icons/file/file-light.svg new file mode 100644 index 000000000..3c8ac05b9 --- /dev/null +++ b/images/icons/file/file-light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/icons/root/root-dark.svg b/images/icons/root/root-dark.svg new file mode 100644 index 000000000..6b872b0c0 --- /dev/null +++ b/images/icons/root/root-dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/images/icons/root/root-light.svg b/images/icons/root/root-light.svg new file mode 100644 index 000000000..1fa17b1d2 --- /dev/null +++ b/images/icons/root/root-light.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/images/icons/root/root-open-dark.svg b/images/icons/root/root-open-dark.svg new file mode 100644 index 000000000..f100434a5 --- /dev/null +++ b/images/icons/root/root-open-dark.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/images/icons/root/root-open-light.svg b/images/icons/root/root-open-light.svg new file mode 100644 index 000000000..120025b06 --- /dev/null +++ b/images/icons/root/root-open-light.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/images/logo/lf-logo-dark.svg b/images/logo/lf-logo-dark.svg new file mode 100644 index 000000000..e3fc241f6 --- /dev/null +++ b/images/logo/lf-logo-dark.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/logo/lf-logo-light.svg b/images/logo/lf-logo-light.svg new file mode 100644 index 000000000..2032efd22 --- /dev/null +++ b/images/logo/lf-logo-light.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 4d18ada92..accf2fdc9 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,18 @@ "command": "linguafranca.createNewFile", "title": "New Lingua Franca File", "shortTitle": "Lingua Franca File" + }, + { + "command": "linguafranca.refreshEntry", + "title": "Refresh Tree View", + "icon": "$(refresh)" + }, + { + "command": "linguafranca.goToFile", + "title": "Go To Selected File", + "icon": "$(go-to-file)" } + ], "configuration": { "title": "LinguaFranca", @@ -130,14 +141,27 @@ { "command": "linguafranca.createNewFile" } + ], + "view/title": [ + { + "command": "linguafranca.refreshEntry", + "group": "navigation" + } + ], + "view/item/context" : [ + { + "command": "linguafranca.goToFile", + "group": "inline", + "when": "viewItem != root" + } ] }, "viewsContainers": { "activitybar": [ { "id": "lf-lang", - "title": "Lingua Franca", - "icon": "$(flame)" + "title": "Lingua Franca Package Explorer", + "icon": "images/logo/lf-logo-dark.svg" } ] }, @@ -145,8 +169,13 @@ "lf-lang": [ { "type": "tree", - "id": "lf-lang-explorer", - "name": "Lingua Franca Explorer" + "id": "lf-lang-local", + "name": "Local" + }, + { + "type": "tree", + "id": "lf-lang-remote", + "name": "Remote" } ] } diff --git a/src/build_commands.ts b/src/build_commands.ts index 8adbad1b8..d2d93f954 100644 --- a/src/build_commands.ts +++ b/src/build_commands.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { LanguageClient } from 'vscode-languageclient'; import { getTerminal, MessageDisplayHelper } from './utils'; +import { LFDataProvider } from './lfview/lf-data-provider'; /** * Return the URI of the given document, if the document is a Lingua Franca file; else, return diff --git a/src/extension.ts b/src/extension.ts index 1e2eb0e2b..c3965a08b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,7 +11,7 @@ import { legend, semanticTokensProvider } from './highlight'; import * as config from './config'; import { registerBuildCommands, registerNewFileCommand } from './build_commands'; import * as checkDependencies from './check_dependencies'; -import { LFDataProvider } from './lfview/lf-data-provider'; +import { LFDataProvider, registerGoToFileCommand } from './lfview/lf-data-provider'; let client: LanguageClient; let socket: Socket @@ -59,8 +59,15 @@ export async function activate(context: vscode.ExtensionContext) { registerNewFileCommand(context); const lfDataProvider = new LFDataProvider(client, context); - vscode.window.registerTreeDataProvider('lf-lang-explorer', lfDataProvider); - vscode.window.createTreeView('lf-lang-explorer', {treeDataProvider: lfDataProvider}); + vscode.window.registerTreeDataProvider('lf-lang-local', lfDataProvider); + vscode.window.createTreeView('lf-lang-local', {treeDataProvider: lfDataProvider}); + await lfDataProvider.refreshTree(); + registerGoToFileCommand(context) + + // vscode.window.registerTreeDataProvider('lf-lang-remote', lfDataProvider); + // vscode.window.createTreeView('lf-lang-remote', {treeDataProvider: lfDataProvider}); + + // lfDataProvider.refreshTree(); } /** diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index befb007e4..7ebc6c4fb 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -1,54 +1,211 @@ import * as vscode from 'vscode' +import * as path from 'path'; import { TreeItemCollapsibleState } from 'vscode' import { LanguageClient } from 'vscode-languageclient' export class LFDataProvider implements vscode.TreeDataProvider { + + // Local library list to display in the tree view. + // This will contain LFDataProviderNode elements representing the LF libraries. + data: LFDataProviderNode[] = [] + + /// An event emitter and event that is fired when the tree data changes. + /// This event can be used to notify the tree view that the data has changed and it should refresh. + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + private watcher = vscode.workspace.createFileSystemWatcher('**/lib/*.lf') + + constructor( private client: LanguageClient, - private context: vscode.ExtensionContext + private context: vscode.ExtensionContext, ) { + // Register commands + vscode.commands.registerCommand('linguafranca.refreshEntry', async () => await this.refreshTree()) + + // Register to file system changes: Create, Delete, Rename, Change + this.onLFFileChanges(this.context); + // vscode.workspace.onDidChangeWorkspaceFolders(() + client.onReady().then(() => { - client.onNotification('update/example', (message) => { - console.log("*******" + message + "*******") + client.onNotification('notify/sendLibraryReactors', (node) => { + this.addDataItem(node); }) }) + } - onDidChangeTreeData?: vscode.Event + getTreeItem(element: LFDataProviderNode): vscode.TreeItem | Thenable { return element } getChildren(element?: LFDataProviderNode): vscode.ProviderResult { if (!element) { - this.client.onReady().then(() => { - this.client.sendRequest('generator/getLibraryReactors').then(message => { - console.log("+++++++++++++++++++++++++" + message) - }) - }) - let result = [] - return vscode.workspace.findFiles('**/lib/*.lf').then(files => { - files.forEach(file => { - result.push(new LFDataProviderNode(file)) - }) - return result - }) + return this.data + } + else { + return element.children } - return [] } getParent?(element: LFDataProviderNode): vscode.ProviderResult { throw new Error('Method not implemented.') } resolveTreeItem?(item: vscode.TreeItem, element: LFDataProviderNode, token: vscode.CancellationToken): vscode.ProviderResult { - throw new Error('Method not implemented.') + if (element.children.length > 0) { + element.collapsibleState = + element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.Collapsed; + + if (element.role == 'root') { + const imagePathOpen = element.collapsibleState === vscode.TreeItemCollapsibleState.Expanded ? '-open' : ''; + element.iconPath = { + light: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-light.svg`), + dark: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-dark.svg`) + }; + } + + this._onDidChangeTreeData.fire(element); + return element; + } + } + + onLFFileChanges(context: vscode.ExtensionContext): void { + context.subscriptions.push( + vscode.workspace.onDidDeleteFiles(async (e) => { + if(e.files.length > 0){ + e.files.forEach(async (file) => { + if(file.path.endsWith('.lf')) { + await this.refreshTree() + } + }) + } + }), + vscode.workspace.onDidRenameFiles(async (e) => { + if(e.files.length > 0){ + e.files.forEach(async (file) => { + if(file.newUri.path.endsWith('.lf')) { + await this.refreshTree() + } + }) + } + }), + vscode.workspace.onDidCreateFiles(async (e) => { + if(e.files.length > 0){ + e.files.forEach(async (file) => { + if(file.path.endsWith('.lf')) { + await this.refreshTree() + } + }) + } + }) + ) + context.subscriptions.push( + this.watcher.onDidChange(async uri => { + await this.refreshTree() + }) + ) + } + + // public async refreshTree() { + // console.log("Fetching LF libraries...") + // if (vscode.workspace.workspaceFolders) { + // this.data = []; + // vscode.workspace.findFiles('**/lib/*.lf').then(files => { + // files.forEach(file => { + // this.data.push(new LFDataProviderNode(file.toString())) + // }) + // this.data.sort((a: LFDataProviderNode, b: LFDataProviderNode) => { + // const labelA = typeof a.label === 'string' ? a.label : a.resourceUri.fsPath.split('/').pop() || ''; + // const labelB = typeof b.label === 'string' ? b.label : b.resourceUri.fsPath.split('/').pop() || ''; + // return labelB.localeCompare(labelA); + // }); + // this._onDidChangeTreeData.fire(undefined); + // }) + // } + // } + + public async refreshTree(){ + console.log("Refreshing LF libraries...") + this.data = []; + if (vscode.workspace.workspaceFolders) { + this.client.onReady().then(() => { + vscode.workspace.findFiles('**/lib/*.lf').then(uris => { + uris.forEach(uri => { + this.client.sendRequest('generator/getLibraryReactors',uri.toString()).then(message => { + console.log(message) + }) + }) + }) + }) + } + } + + public async addDataItem(data: any) { + // Build Root Node + const root = this.buildRoot(data.uri); + let node = new LFDataProviderNode(data.label,data.uri,'file',[]); + root.children.push(node); + if(data.children.length > 0){ + data.children.forEach(child => { + node.children.push(new LFDataProviderNode(child.label,child.uri,'reactor',[])); + }); + } + this.data.sort((a: LFDataProviderNode, b: LFDataProviderNode) => { + const labelA = typeof a.label === 'string' ? a.label : a.resourceUri.fsPath.split('/').pop() || ''; + const labelB = typeof b.label === 'string' ? b.label : b.resourceUri.fsPath.split('/').pop() || ''; + return labelA.localeCompare(labelB); + }); + this._onDidChangeTreeData.fire(undefined); + } + + public buildRoot(uri: string) : LFDataProviderNode { + const splittedUri = uri.split('/'); + const projectLabel = splittedUri[splittedUri.length - 3]; + + // Check if the project already exists in the data + const existingProject = this.data.find(item => item.label === projectLabel); + if (!existingProject) { + // Construct projectUri + const projectUri = splittedUri.slice(0, -3).join('/') + '/'; + // Create the root + const root = new LFDataProviderNode(projectLabel, projectUri, 'root', []); + // Construct project node + this.data.push(root); + return root; + } + return existingProject; } } export class LFDataProviderNode extends vscode.TreeItem { - constructor(resourceUri: vscode.Uri) { - if (resourceUri.path.endsWith('.lf')) { - super(resourceUri, TreeItemCollapsibleState.None) - } else { - super(resourceUri, TreeItemCollapsibleState.Collapsed) + + children: LFDataProviderNode[] | undefined + role: string + + constructor(label: string, uri: string, role: string, children?: LFDataProviderNode[] | undefined) { + let newLabel = label.replace('.lf',''); + super(newLabel, role === 'reactor'? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); + this.resourceUri = vscode.Uri.parse(uri); + this.children = children; + this.role = role; + let folder = role === 'root'? 'root' : role === 'file' ? 'file' : 'code'; + this.iconPath = { + light: path.join(__filename, '..','..', 'images', 'icons', folder,folder + '-light.svg'), + dark: path.join(__filename, '..','..', 'images', 'icons', folder, folder + '-dark.svg') } + this.contextValue = role; + } } + +export function registerGoToFileCommand(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.goToFile', (node: LFDataProviderNode) => { + vscode.workspace.openTextDocument(node.resourceUri).then(doc => { + vscode.window.showTextDocument(doc); + }); + } + )); +} From 1e31c7006f37aa7c27e41d5395a0c55356ddb0cf Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto Date: Tue, 30 Apr 2024 17:48:29 -0700 Subject: [PATCH 07/38] Tree View: improved got-to-file command; added open-in-slit-view command; added import-selected-reactor command --- package.json | 24 ++- src/extension.ts | 12 +- src/lfview/lf-data-provider.ts | 303 +++++++++++++++++++++++++-------- 3 files changed, 266 insertions(+), 73 deletions(-) diff --git a/package.json b/package.json index accf2fdc9..136bfe186 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "shortTitle": "Lingua Franca File" }, { - "command": "linguafranca.refreshEntry", + "command": "linguafranca.refreshEntries", "title": "Refresh Tree View", "icon": "$(refresh)" }, @@ -92,6 +92,16 @@ "command": "linguafranca.goToFile", "title": "Go To Selected File", "icon": "$(go-to-file)" + }, + { + "command": "linguafranca.importReactor", + "title": "Import Selected Reactor", + "icon": "$(insert)" + }, + { + "command": "linguafranca.openInSplitView", + "title": "Open in Split View", + "icon": "$(split-horizontal)" } ], @@ -144,7 +154,7 @@ ], "view/title": [ { - "command": "linguafranca.refreshEntry", + "command": "linguafranca.refreshEntries", "group": "navigation" } ], @@ -153,6 +163,16 @@ "command": "linguafranca.goToFile", "group": "inline", "when": "viewItem != root" + }, + { + "command": "linguafranca.openInSplitView", + "group": "inline", + "when": "viewItem == file" + }, + { + "command": "linguafranca.importReactor", + "group": "inline", + "when": "viewItem == reactor" } ] }, diff --git a/src/extension.ts b/src/extension.ts index c3965a08b..be6387458 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,7 +11,7 @@ import { legend, semanticTokensProvider } from './highlight'; import * as config from './config'; import { registerBuildCommands, registerNewFileCommand } from './build_commands'; import * as checkDependencies from './check_dependencies'; -import { LFDataProvider, registerGoToFileCommand } from './lfview/lf-data-provider'; +import { LFDataProvider} from './lfview/lf-data-provider'; let client: LanguageClient; let socket: Socket @@ -58,11 +58,15 @@ export async function activate(context: vscode.ExtensionContext) { registerBuildCommands(context, client); registerNewFileCommand(context); + /** + * Registers a tree data provider and creates a tree view for the 'lf-lang-local' view in the Visual Studio Code extension. + * The `LFDataProvider` instance is responsible for providing the data for the tree view. + * The `refreshTree()` method is called to update the contents of the tree view. + */ const lfDataProvider = new LFDataProvider(client, context); vscode.window.registerTreeDataProvider('lf-lang-local', lfDataProvider); - vscode.window.createTreeView('lf-lang-local', {treeDataProvider: lfDataProvider}); - await lfDataProvider.refreshTree(); - registerGoToFileCommand(context) + vscode.window.createTreeView('lf-lang-local', { treeDataProvider: lfDataProvider }); + lfDataProvider.refreshTree(); // vscode.window.registerTreeDataProvider('lf-lang-remote', lfDataProvider); // vscode.window.createTreeView('lf-lang-remote', {treeDataProvider: lfDataProvider}); diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 7ebc6c4fb..13f3f5aea 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -9,26 +9,34 @@ export class LFDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; private watcher = vscode.workspace.createFileSystemWatcher('**/lib/*.lf') - + constructor( private client: LanguageClient, private context: vscode.ExtensionContext, ) { // Register commands - vscode.commands.registerCommand('linguafranca.refreshEntry', async () => await this.refreshTree()) + this.registerRefreshCommand(context) + this.registerGoToFileCommand(context) + this.registerImportReactorCommand(context) + this.registerOpenInSplitViewCommand(context) // Register to file system changes: Create, Delete, Rename, Change this.onLFFileChanges(this.context); // vscode.workspace.onDidChangeWorkspaceFolders(() - + + /** + * Registers a notification handler for the 'notify/sendLibraryReactors' event, which is used to add a new data item to the tree. + * This code is executed when the Language Server is ready, and it listens for the 'notify/sendLibraryReactors' notification from the server. + * When the notification is received, the `addDataItem` method is called with the received node data. + */ client.onReady().then(() => { client.onNotification('notify/sendLibraryReactors', (node) => { this.addDataItem(node); @@ -36,7 +44,7 @@ export class LFDataProvider implements vscode.TreeDataProvider { return element } @@ -53,48 +61,53 @@ export class LFDataProvider implements vscode.TreeDataProvider { if (element.children.length > 0) { - element.collapsibleState = - element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed - ? vscode.TreeItemCollapsibleState.Expanded - : vscode.TreeItemCollapsibleState.Collapsed; - + element.collapsibleState = + element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.Collapsed; + if (element.role == 'root') { - const imagePathOpen = element.collapsibleState === vscode.TreeItemCollapsibleState.Expanded ? '-open' : ''; - element.iconPath = { - light: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-light.svg`), - dark: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-dark.svg`) - }; + const imagePathOpen = element.collapsibleState === vscode.TreeItemCollapsibleState.Expanded ? '-open' : ''; + element.iconPath = { + light: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-light.svg`), + dark: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-dark.svg`) + }; } - + this._onDidChangeTreeData.fire(element); return element; - } + } } + /** + * Subscribes to various file change events (delete, rename, create) and refreshes the LF data provider tree when an .lf file is affected. + * Also subscribes to changes in the LF watcher and refreshes the tree when a change is detected. + * @param context - The extension context, used to manage the subscriptions. + */ onLFFileChanges(context: vscode.ExtensionContext): void { context.subscriptions.push( vscode.workspace.onDidDeleteFiles(async (e) => { - if(e.files.length > 0){ + if (e.files.length > 0) { e.files.forEach(async (file) => { - if(file.path.endsWith('.lf')) { + if (file.path.endsWith('.lf')) { await this.refreshTree() } }) } }), vscode.workspace.onDidRenameFiles(async (e) => { - if(e.files.length > 0){ + if (e.files.length > 0) { e.files.forEach(async (file) => { - if(file.newUri.path.endsWith('.lf')) { + if (file.newUri.path.endsWith('.lf')) { await this.refreshTree() } }) } }), vscode.workspace.onDidCreateFiles(async (e) => { - if(e.files.length > 0){ + if (e.files.length > 0) { e.files.forEach(async (file) => { - if(file.path.endsWith('.lf')) { + if (file.path.endsWith('.lf')) { await this.refreshTree() } }) @@ -107,33 +120,20 @@ export class LFDataProvider implements vscode.TreeDataProvider { - // files.forEach(file => { - // this.data.push(new LFDataProviderNode(file.toString())) - // }) - // this.data.sort((a: LFDataProviderNode, b: LFDataProviderNode) => { - // const labelA = typeof a.label === 'string' ? a.label : a.resourceUri.fsPath.split('/').pop() || ''; - // const labelB = typeof b.label === 'string' ? b.label : b.resourceUri.fsPath.split('/').pop() || ''; - // return labelB.localeCompare(labelA); - // }); - // this._onDidChangeTreeData.fire(undefined); - // }) - // } - // } - - public async refreshTree(){ + + /** + * Refreshes the LF libraries tree view by fetching the latest library reactor information from the Language Server. + * This method is called at the very beginning when a LFDataProvider object is instaciated in {@code enxtension.ts} and when + * the user triggers the {@code'linguafranca.refreshEntries'} command. + */ + public async refreshTree() { console.log("Refreshing LF libraries...") this.data = []; if (vscode.workspace.workspaceFolders) { this.client.onReady().then(() => { vscode.workspace.findFiles('**/lib/*.lf').then(uris => { uris.forEach(uri => { - this.client.sendRequest('generator/getLibraryReactors',uri.toString()).then(message => { + this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(message => { console.log(message) }) }) @@ -142,14 +142,23 @@ export class LFDataProvider implements vscode.TreeDataProvider 0){ - data.children.forEach(child => { - node.children.push(new LFDataProviderNode(child.label,child.uri,'reactor',[])); + if (dataNode.children.length > 0) { + dataNode.children.forEach(child => { + node.children.push(new LFDataProviderNode(child.label, child.uri, 'reactor', [], child.position)); }); } this.data.sort((a: LFDataProviderNode, b: LFDataProviderNode) => { @@ -160,7 +169,17 @@ export class LFDataProvider implements vscode.TreeDataProvider { + this.refreshTree(); + } + )); + } + + /** + * Registers a command that allows the user to navigate to a file associated with a node in the LFDataProvider. + * + * When the {@code'linguafranca.goToFile'} command is executed, it will open the text document associated with the + * provided {@code LFDataProviderNode} and position the editor at the start of the node's position, if available. + * + * @param context The extension context, used to register the command. + */ + registerGoToFileCommand(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.goToFile', (node: LFDataProviderNode) => { + vscode.workspace.openTextDocument(node.resourceUri).then(doc => { + vscode.window.showTextDocument(doc /*, vscode.ViewColumn.Beside*/).then(e => { + if (node.position) { + e.selection = new vscode.Selection(node.position.start - 1, 0, node.position.end, 0); + e.revealRange(new vscode.Range(node.position.start - 1, 0, node.position.end, 0), vscode.TextEditorRevealType.InCenter); + } + }); + }); + } + )); + } + + /** + * Registers a command to open a Lingua Franca resource in a split view. + * + * When the command is executed, it opens the Lingua Franca resource associated with the + * provided {@code LFDataProviderNode} in a new editor view, positioned beside the current active + * editor view. + * + * @param context - The extension context. + */ + registerOpenInSplitViewCommand(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.openInSplitView', (node: LFDataProviderNode) => { + vscode.workspace.openTextDocument(node.resourceUri).then(doc => { + vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside) + }); + } + )); + } + + /** + * Registers a command to import a Reactor from the LFDataProviderNode. + * When the command is executed, it finds the relative path of the Reactor's resource URI + * compared to the active editor's document URI, and inserts an import statement for the + * Reactor at the appropriate position in the active editor. + * + * @param context - The extension context. + */ + registerImportReactorCommand(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.importReactor', async (node: LFDataProviderNode) => { + const editor = vscode.window.activeTextEditor; + if (editor) { + if(!editor.document.uri.fsPath.endsWith('.lf')) { + vscode.window.showErrorMessage('The active editor must be a Ligua Franca program.'); + return; + } + const relativePath = this.getRelativePath(editor.document.uri.fsPath, node.resourceUri.fsPath); + const importText = `import ${node.label.toString()} from "${relativePath}"\n`; + const position = await this.getTargetPosition(node.resourceUri); + this.addTextOnActiveEditor(editor, position.end, importText); + } + + } + )); + } + + /** + * Gets the relative path between a source and target file. + * @param source The path to the source file. + * @param target The path to the target file. + * @returns The relative path between the source and target files. + */ + getRelativePath(source: string, target: string) { + return path.relative(path.dirname(source), target); + } + + /** + * Adds the provided text to the active editor at the specified end position, inserting it on the next empty line. + * + * @param editor - The active text editor to add the text to. + * @param end - The position to start searching for the next empty line from. + * @param importText - The text to insert on the next empty line. + */ + addTextOnActiveEditor(editor: vscode.TextEditor, end: number, importText: string): void { + const document = editor.document; + const totalLines = document.lineCount; + + let startIndex = end; + let foundEmptyLine = false; + + while (startIndex < totalLines) { + const line = document.lineAt(startIndex); + if (line.isEmptyOrWhitespace) { + foundEmptyLine = true; + break; + } + startIndex++; + } + + if (foundEmptyLine) { + editor.edit(editBuilder => { + editBuilder.insert(new vscode.Position(startIndex, 0), importText); + }); + } + } + + /** + * Retrieves the target position for the specified URI. + * + * @param uri - The URI for which to retrieve the target position. + * @returns A Promise that resolves to the {@code NodePosition} for the target, or {@code undefined} if the target position could not be determined. + */ + async getTargetPosition(uri: vscode.Uri): Promise { + return this.client.onReady().then(() => { + return this.client.sendRequest('generator/getTargetPosition', uri.toString()); + }); + } } +/** +* Represents a node in the LFDataProvider tree. +* +* @param label - The label to display for the node. +* @param uri - The URI of the resource associated with the node. +* @param role - The role of the node, such as 'reactor', 'file', or 'root'. +* @param children - The child nodes of this node, if any. +* @param position - The start and end positions of the node in the source code, if available. +*/ export class LFDataProviderNode extends vscode.TreeItem { children: LFDataProviderNode[] | undefined role: string + position: NodePosition | undefined - constructor(label: string, uri: string, role: string, children?: LFDataProviderNode[] | undefined) { - let newLabel = label.replace('.lf',''); - super(newLabel, role === 'reactor'? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); + constructor(label: string, uri: string, role: string, + children?: LFDataProviderNode[] | undefined, position?: NodePosition | undefined) { + let newLabel = label.replace('.lf', ''); + super(newLabel, role === 'reactor' ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); this.resourceUri = vscode.Uri.parse(uri); this.children = children; this.role = role; - let folder = role === 'root'? 'root' : role === 'file' ? 'file' : 'code'; + let folder = role === 'root' ? 'root' : role === 'file' ? 'file' : 'code'; this.iconPath = { - light: path.join(__filename, '..','..', 'images', 'icons', folder,folder + '-light.svg'), - dark: path.join(__filename, '..','..', 'images', 'icons', folder, folder + '-dark.svg') + light: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-light.svg'), + dark: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-dark.svg') } this.contextValue = role; - + if (position) { this.position = position; } } } -export function registerGoToFileCommand(context: vscode.ExtensionContext) { - context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.goToFile', (node: LFDataProviderNode) => { - vscode.workspace.openTextDocument(node.resourceUri).then(doc => { - vscode.window.showTextDocument(doc); - }); - } - )); +/** +* Represents the start and end positions of a node in the source code. +* +* @param start - The starting position of the node. +* @param end - The ending position of the node. +*/ +class NodePosition { + start: number; + end: number; + + constructor(start: number, end: number) { + this.start = start; + this.end = end; + } } From 9ac3296e9abb95cb6e57dfbb12ca04e019896e9b Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Thu, 2 May 2024 16:28:39 -0700 Subject: [PATCH 08/38] Implemented remote library management functionality --- .../reactor-dark.svg} | 0 .../reactor-light.svg} | 0 package.json | 72 ++- src/extension.ts | 32 +- src/lfview/lf-data-provider-commands.ts | 132 +++++ src/lfview/lf-data-provider.ts | 488 +++++++++--------- 6 files changed, 472 insertions(+), 252 deletions(-) rename images/icons/{code/code-dark.svg => reactor/reactor-dark.svg} (100%) rename images/icons/{code/code-light.svg => reactor/reactor-light.svg} (100%) create mode 100644 src/lfview/lf-data-provider-commands.ts diff --git a/images/icons/code/code-dark.svg b/images/icons/reactor/reactor-dark.svg similarity index 100% rename from images/icons/code/code-dark.svg rename to images/icons/reactor/reactor-dark.svg diff --git a/images/icons/code/code-light.svg b/images/icons/reactor/reactor-light.svg similarity index 100% rename from images/icons/code/code-light.svg rename to images/icons/reactor/reactor-light.svg diff --git a/package.json b/package.json index 136bfe186..d4a919920 100644 --- a/package.json +++ b/package.json @@ -88,20 +88,50 @@ "title": "Refresh Tree View", "icon": "$(refresh)" }, + { + "command": "linguafranca.refreshRemoteEntries", + "title": "Refresh Tree View", + "icon": "$(refresh)" + }, { "command": "linguafranca.goToFile", "title": "Go To Selected File", "icon": "$(go-to-file)" }, + { + "command": "linguafranca.goToRemoteFile", + "title": "Go To Selected File", + "icon": "$(go-to-file)" + }, { "command": "linguafranca.importReactor", "title": "Import Selected Reactor", "icon": "$(insert)" }, + { + "command": "linguafranca.importRemoteReactor", + "title": "Import Selected Reactor", + "icon": "$(insert)" + }, { "command": "linguafranca.openInSplitView", "title": "Open in Split View", "icon": "$(split-horizontal)" + }, + { + "command": "linguafranca.openRemoteInSplitView", + "title": "Open in Split View", + "icon": "$(split-horizontal)" + }, + { + "command": "linguafranca.collapseAll", + "title": "Collapse All", + "icon": "$(collapse-all)" + }, + { + "command": "linguafranca.collapseAllRemote", + "title": "Collapse All", + "icon": "$(collapse-all)" } ], @@ -155,24 +185,56 @@ "view/title": [ { "command": "linguafranca.refreshEntries", - "group": "navigation" + "group": "navigation@1", + "when": "view == lf-lang-local" + }, + { + "command": "linguafranca.refreshRemoteEntries", + "group": "navigation@1", + "when": "view == lf-lang-remote" + }, + { + "command": "linguafranca.collapseAll", + "group": "navigation@2", + "when": "view == lf-lang-local" + }, + { + "command": "linguafranca.collapseAllRemote", + "group": "navigation@2", + "when": "view == lf-lang-remote" } + ], "view/item/context" : [ { "command": "linguafranca.goToFile", "group": "inline", - "when": "viewItem != root" + "when": "view == lf-lang-local && viewItem == file" + }, + { + "command": "linguafranca.goToRemoteFile", + "group": "inline", + "when": "view == lf-lang-remote && viewItem == file" }, { "command": "linguafranca.openInSplitView", "group": "inline", - "when": "viewItem == file" + "when": "view == lf-lang-local && viewItem != root" + }, + { + "command": "linguafranca.openRemoteInSplitView", + "group": "inline", + "when": "view == lf-lang-remote && viewItem != root" }, { "command": "linguafranca.importReactor", "group": "inline", - "when": "viewItem == reactor" + "when": "view == lf-lang-local && viewItem == reactor" + }, + { + "command": "linguafranca.importRemoteReactor", + "group": "inline", + "when": "view == lf-lang-remote && viewItem == reactor" } ] }, @@ -188,12 +250,10 @@ "views": { "lf-lang": [ { - "type": "tree", "id": "lf-lang-local", "name": "Local" }, { - "type": "tree", "id": "lf-lang-remote", "name": "Remote" } diff --git a/src/extension.ts b/src/extension.ts index be6387458..675f2d276 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,7 +11,8 @@ import { legend, semanticTokensProvider } from './highlight'; import * as config from './config'; import { registerBuildCommands, registerNewFileCommand } from './build_commands'; import * as checkDependencies from './check_dependencies'; -import { LFDataProvider} from './lfview/lf-data-provider'; +import { LFDataProvider, LFDataProviderNodeType} from './lfview/lf-data-provider'; +import { registerCollapseAllCommand, registerCollapseAllRemoteCommand, registerGoToFileCommand, registerGoToRemoteFileCommand, registerImportReactorCommand, registerImportRemoteReactorCommand, registerOpenInSplitViewCommand, registerOpenRemoteInSplitViewCommand, registerRefreshCommand, registerRefreshRemoteCommand } from './lfview/lf-data-provider-commands'; let client: LanguageClient; let socket: Socket @@ -63,15 +64,26 @@ export async function activate(context: vscode.ExtensionContext) { * The `LFDataProvider` instance is responsible for providing the data for the tree view. * The `refreshTree()` method is called to update the contents of the tree view. */ - const lfDataProvider = new LFDataProvider(client, context); - vscode.window.registerTreeDataProvider('lf-lang-local', lfDataProvider); - vscode.window.createTreeView('lf-lang-local', { treeDataProvider: lfDataProvider }); - lfDataProvider.refreshTree(); - - // vscode.window.registerTreeDataProvider('lf-lang-remote', lfDataProvider); - // vscode.window.createTreeView('lf-lang-remote', {treeDataProvider: lfDataProvider}); - - // lfDataProvider.refreshTree(); + const lfDataProviderLocal = new LFDataProvider(LFDataProviderNodeType.LOCAL, client, context); + context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-local', lfDataProviderLocal)); + context.subscriptions.push(vscode.window.createTreeView('lf-lang-local', { treeDataProvider: lfDataProviderLocal })); + + const lfDataProviderRemote = new LFDataProvider(LFDataProviderNodeType.REMOTE, client, context); + context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-remote', lfDataProviderRemote)); + context.subscriptions.push(vscode.window.createTreeView('lf-lang-remote', { treeDataProvider: lfDataProviderRemote })); + + // Register all teh commands + registerRefreshCommand(context, lfDataProviderLocal); + registerRefreshRemoteCommand(context, lfDataProviderRemote); + registerGoToFileCommand(context, lfDataProviderLocal); + registerGoToRemoteFileCommand(context, lfDataProviderRemote); + registerOpenInSplitViewCommand(context, lfDataProviderLocal); + registerOpenRemoteInSplitViewCommand(context, lfDataProviderRemote); + registerImportReactorCommand(context, lfDataProviderLocal); + registerImportRemoteReactorCommand(context, lfDataProviderRemote); + registerCollapseAllCommand(context); + registerCollapseAllRemoteCommand(context); + } /** diff --git a/src/lfview/lf-data-provider-commands.ts b/src/lfview/lf-data-provider-commands.ts new file mode 100644 index 000000000..b98f99e26 --- /dev/null +++ b/src/lfview/lf-data-provider-commands.ts @@ -0,0 +1,132 @@ +import * as vscode from 'vscode'; +import { LFDataProvider, LFDataProviderNode, LFDataProviderNodeType } from './lf-data-provider'; + +/** + * Registers a command to refresh the local LF libraries tree view. + * @param context - The extension context. + * @param local - The LFDataProvider instance managing local LF libraries. + */ +export function registerRefreshCommand(context: vscode.ExtensionContext, local: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.refreshEntries', () => { + local.refreshTree(); + } + )); +} + +/** + * Registers a command to refresh the remote LF libraries tree view. + * @param context - The extension context. + * @param remote - The LFDataProvider instance managing remote LF libraries. + */ +export function registerRefreshRemoteCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.refreshRemoteEntries', () => { + remote.refreshTree(); + } + )); +} + +/** + * Registers a command to navigate to a file in the local LF libraries tree view. + * @param context - The extension context. + * @param local - The LFDataProvider instance managing local LF libraries. + */ +export function registerGoToFileCommand(context: vscode.ExtensionContext, local: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.goToFile', (node: LFDataProviderNode) => { + local.goToFileCommand(node,false); + } + )); +} + +/** + * Registers a command to navigate to a file in the remote LF libraries tree view. + * @param context - The extension context. + * @param remote - The LFDataProvider instance managing remote LF libraries. + */ +export function registerGoToRemoteFileCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.goToRemoteFile', (node: LFDataProviderNode) => { + remote.goToFileCommand(node,false); + } + )); +} + +/** + * Registers a command to open a file in split view in the local LF libraries tree view. + * @param context - The extension context. + * @param local - The LFDataProvider instance managing local LF libraries. + */ +export function registerOpenInSplitViewCommand(context: vscode.ExtensionContext, local: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.openInSplitView', (node: LFDataProviderNode) => { + local.goToFileCommand(node,true); + } + )); +} + +/** + * Registers a command to open a file in split view in the remote LF libraries tree view. + * @param context - The extension context. + * @param remote - The LFDataProvider instance managing remote LF libraries. + */ +export function registerOpenRemoteInSplitViewCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.openRemoteInSplitView', (node: LFDataProviderNode) => { + remote.goToFileCommand(node,true); + } + )); +} + +/** + * Registers a command to import a reactor from the local LF libraries into the active LF program. + * @param context - The extension context. + * @param local - The LFDataProvider instance managing local LF libraries. + */ +export function registerImportReactorCommand(context: vscode.ExtensionContext, local: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.importReactor', async (node: LFDataProviderNode) => { + await local.importReactorCommand(node); + } + )); +} + +/** + * Registers a command to import a reactor from the remote LF libraries into the active LF program. + * @param context - The extension context. + * @param remote - The LFDataProvider instance managing remote LF libraries. + */ +export function registerImportRemoteReactorCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.importRemoteReactor', async (node: LFDataProviderNode) => { + await remote.importReactorCommand(node); + } + )); +} + +/** + * Registers a command to collapse all nodes in the local LF libraries tree view. + * @param context - The extension context. + * @param local - The LFDataProvider instance managing local LF libraries. + */ +export function registerCollapseAllCommand(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.collapseAll', () => { + vscode.commands.executeCommand('workbench.actions.treeView.lf-lang-local.collapseAll'); + } + )); +} + +/** + * Registers a command to collapse all nodes in the remote LF libraries tree view. + * @param context - The extension context. + * @param remote - The LFDataProvider instance managing remote LF libraries. + */ +export function registerCollapseAllRemoteCommand(context: vscode.ExtensionContext) { + context.subscriptions.push(vscode.commands.registerCommand( + 'linguafranca.collapseAllRemote', () => { + vscode.commands.executeCommand('workbench.actions.treeView.lf-lang-remote.collapseAll'); + } + )); +} diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 13f3f5aea..110ca9267 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -1,164 +1,246 @@ -import * as vscode from 'vscode' +import * as vscode from 'vscode'; import * as path from 'path'; -import { TreeItemCollapsibleState } from 'vscode' -import { LanguageClient } from 'vscode-languageclient' +import { LanguageClient } from 'vscode-languageclient'; +/** + * Defines the different roles of nodes that can be displayed in the LFDataProvider tree view. + * {@code ROOT}: Represents the root node of the tree view. + * {@code FILE}: Represents a file node in the tree view. + * {@code REACTOR}: Represents a reactor node in the tree view. + */ +export enum LFDataProviderNodeRole { + ROOT = 'root', + FILE = 'file', + REACTOR = 'reactor' +} + +/** + * Defines the types of the displayed data provider tree view. + * + * {@code LOCAL} represents nodes for local LF libraries. + * {@code REMOTE} represents nodes for remote LF libraries. + */ +export enum LFDataProviderNodeType { + LOCAL = 1, + REMOTE = 2 +} + +/** + * Represents a node in the LFDataProvider tree. + * + * @param label - The label to display for the node. + * @param uri - The URI of the resource associated with the node. + * @param role - The role of the node, such as LFDataProviderNodeType.REACTOR, LFDataProviderNodeType.FILE, or LFDataProviderNodeType.ROOT. + * @param children - The child nodes of this node, if any. + * @param position - The start and end positions of the node in the source code, if available. + */ +export class LFDataProviderNode extends vscode.TreeItem { + + children: LFDataProviderNode[] | undefined; + role: string; + position: NodePosition | undefined; + type: LFDataProviderNodeType; + + constructor(label: string, uri: string, role: string, type: LFDataProviderNodeType, + children?: LFDataProviderNode[] | undefined, position?: NodePosition | undefined) { + let newLabel = label.replace('.lf', ''); + super(newLabel, role === LFDataProviderNodeRole.REACTOR ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); + this.resourceUri = vscode.Uri.parse(uri); + this.children = children; + this.role = role; + this.type = type; + let folder = role === LFDataProviderNodeRole.ROOT ? 'root' : role === LFDataProviderNodeRole.FILE ? 'file' : 'reactor'; + this.iconPath = { + light: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-light.svg'), + dark: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-dark.svg') + } + this.contextValue = role; + if (position) { this.position = position; } + + this.registerNodeCommand(); + + } + + /** + * Registers a command for the node based on its role and type. + */ + registerNodeCommand(): void { + this.command = this.role === LFDataProviderNodeRole.REACTOR ? { + title: "", + command: this.type == LFDataProviderNodeType.LOCAL ? "linguafranca.goToFile" : "linguafranca.goToRemoteFile", + arguments: [this] + } : undefined; + } +} + +/** + * Represents the start and end positions of a node in the source code. + * + * @param start - The starting position of the node. + * @param end - The ending position of the node. + */ +export class NodePosition { + start: number; + end: number; + + constructor(start: number, end: number) { + this.start = start; + this.end = end; + } +} + +/** + * Represents the LFDataProvider tree view. + */ export class LFDataProvider implements vscode.TreeDataProvider { - // Local library list to display in the tree view. - // This will contain LFDataProviderNode elements representing the LF libraries. - data: LFDataProviderNode[] = [] + // Offset used to highlight text in goToFile Command + private HIGHLIGHT_OFFSET = 100; + + // LF libraries data + private data: LFDataProviderNode[] = []; + // Type of the data provider + private type: LFDataProviderNodeType; - // An event emitter and event that is fired when the tree data changes. - // This event can be used to notify the tree view that the data has changed and it should refresh. + // Utility properties + private pathOffset: number; + private searchPath: string; + + // Event emitter for tree data change private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - private watcher = vscode.workspace.createFileSystemWatcher('**/lib/*.lf') - + // Watches for changes to .lf files + private watcher: vscode.FileSystemWatcher; constructor( + type: LFDataProviderNodeType, private client: LanguageClient, private context: vscode.ExtensionContext, ) { - // Register commands - this.registerRefreshCommand(context) - this.registerGoToFileCommand(context) - this.registerImportReactorCommand(context) - this.registerOpenInSplitViewCommand(context) + + this.type = type; + this.pathOffset = type === LFDataProviderNodeType.LOCAL ? 3 : 6; + this.searchPath = type === LFDataProviderNodeType.LOCAL ? '**/lib/*.lf' : '**/target/lfc_include/**/src/*.lf'; + this.watcher = vscode.workspace.createFileSystemWatcher(this.searchPath); // Register to file system changes: Create, Delete, Rename, Change this.onLFFileChanges(this.context); - // vscode.workspace.onDidChangeWorkspaceFolders(() - - /** - * Registers a notification handler for the 'notify/sendLibraryReactors' event, which is used to add a new data item to the tree. - * This code is executed when the Language Server is ready, and it listens for the 'notify/sendLibraryReactors' notification from the server. - * When the notification is received, the `addDataItem` method is called with the received node data. - */ - client.onReady().then(() => { - client.onNotification('notify/sendLibraryReactors', (node) => { - this.addDataItem(node); - }) - }) - + // Refresh the tree view + this.refreshTree(); } + /** + * Gets the tree item representation of the node. + */ getTreeItem(element: LFDataProviderNode): vscode.TreeItem | Thenable { - return element + return element; } + + /** + * Gets the children of a node. + */ getChildren(element?: LFDataProviderNode): vscode.ProviderResult { if (!element) { - return this.data - } - else { - return element.children + return this.data; + } else { + return element.children; } } + getParent?(element: LFDataProviderNode): vscode.ProviderResult { - throw new Error('Method not implemented.') + throw new Error('Method not implemented.'); } + resolveTreeItem?(item: vscode.TreeItem, element: LFDataProviderNode, token: vscode.CancellationToken): vscode.ProviderResult { - if (element.children.length > 0) { - element.collapsibleState = - element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed - ? vscode.TreeItemCollapsibleState.Expanded - : vscode.TreeItemCollapsibleState.Collapsed; - - if (element.role == 'root') { - const imagePathOpen = element.collapsibleState === vscode.TreeItemCollapsibleState.Expanded ? '-open' : ''; - element.iconPath = { - light: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-light.svg`), - dark: path.join(__filename, '..', '..', 'images', 'icons', element.role, `${element.role}${imagePathOpen}-dark.svg`) - }; - } + return item; + } - this._onDidChangeTreeData.fire(element); - return element; - } + /** + * Gets the type of the data provider. + */ + getType(): LFDataProviderNodeType { + return this.type; } /** - * Subscribes to various file change events (delete, rename, create) and refreshes the LF data provider tree when an .lf file is affected. - * Also subscribes to changes in the LF watcher and refreshes the tree when a change is detected. - * @param context - The extension context, used to manage the subscriptions. - */ + * Subscribes to various file change events (delete, rename, create) and refreshes the LF data provider tree when an .lf file is affected. + * Also subscribes to changes in the LF watcher and refreshes the tree when a change is detected. + * @param context - The extension context, used to manage the subscriptions. + */ onLFFileChanges(context: vscode.ExtensionContext): void { context.subscriptions.push( vscode.workspace.onDidDeleteFiles(async (e) => { if (e.files.length > 0) { e.files.forEach(async (file) => { if (file.path.endsWith('.lf')) { - await this.refreshTree() + await this.refreshTree(); } - }) + }); } }), vscode.workspace.onDidRenameFiles(async (e) => { if (e.files.length > 0) { e.files.forEach(async (file) => { if (file.newUri.path.endsWith('.lf')) { - await this.refreshTree() + await this.refreshTree(); } - }) + }); } }), vscode.workspace.onDidCreateFiles(async (e) => { if (e.files.length > 0) { e.files.forEach(async (file) => { if (file.path.endsWith('.lf')) { - await this.refreshTree() + await this.refreshTree(); } - }) + }); } }) - ) + ); context.subscriptions.push( this.watcher.onDidChange(async uri => { - await this.refreshTree() + await this.refreshTree(); }) - ) + ); } /** - * Refreshes the LF libraries tree view by fetching the latest library reactor information from the Language Server. - * This method is called at the very beginning when a LFDataProvider object is instaciated in {@code enxtension.ts} and when - * the user triggers the {@code'linguafranca.refreshEntries'} command. - */ + * Refreshes the LF libraries tree view by fetching the latest library reactor information from the Language Server. + */ public async refreshTree() { - console.log("Refreshing LF libraries...") + console.log("Refreshing LF libraries..."); this.data = []; if (vscode.workspace.workspaceFolders) { this.client.onReady().then(() => { - vscode.workspace.findFiles('**/lib/*.lf').then(uris => { + vscode.workspace.findFiles(this.searchPath).then(uris => { uris.forEach(uri => { - this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(message => { - console.log(message) - }) - }) - }) - }) + this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(node => { + this.addDataItem(node); + }); + }); + }); + }); } } /** - * Adds a new data item to the LFDataProvider tree. - * - * This method constructs the root node for the data item, creates a new node for the data item, and adds it to the root node's children. - * If the data item has any child items, it also creates nodes for those and adds them to the data item's node. - * The data is then sorted alphabetically by the node label or resource URI path, and the tree data change event is fired to update the UI. - * - * @param dataNode - The data node to add to the tree. - */ + * Adds a new data item to the LFDataProvider tree. + * @param dataNode - The data node to add to the tree. + */ async addDataItem(dataNode: any) { - // Build Root Node const root = this.buildRoot(dataNode.uri); - let node = new LFDataProviderNode(dataNode.label, dataNode.uri, 'file', []); + let node = new LFDataProviderNode(dataNode.label, dataNode.uri, LFDataProviderNodeRole.FILE, this.type,[]); root.children.push(node); if (dataNode.children.length > 0) { dataNode.children.forEach(child => { - node.children.push(new LFDataProviderNode(child.label, child.uri, 'reactor', [], child.position)); + node.children.push(new LFDataProviderNode(child.label, + child.uri, + LFDataProviderNodeRole.REACTOR, + this.type, [], + child.position + )); }); } this.data.sort((a: LFDataProviderNode, b: LFDataProviderNode) => { @@ -166,31 +248,38 @@ export class LFDataProvider implements vscode.TreeDataProvider this.sortNodes(n)); this._onDidChangeTreeData.fire(undefined); } /** - * Builds the root node of the LFDataProviderNode tree based on the provided URI. - * - * If the project represented by the URI does not exist in the `data` array, a new - * `LFDataProviderNode` is created with the project label and URI, and added to the `data` array. - * If the project already exists, the existing `LFDataProviderNode` is returned. - * - * @param uri The URI of the project to build the root node for. - * @returns The root `LFDataProviderNode` for the project. - */ + * Recursively sorts the children of a node. + * @param node - The node whose children need to be sorted. + */ + sortNodes(node: LFDataProviderNode) { + if (node.children.length > 0) { + node.children.sort((a, b) => { + const labelA = typeof a.label === 'string' ? a.label : a.resourceUri.fsPath.split('/').pop() || ''; + const labelB = typeof b.label === 'string' ? b.label : b.resourceUri.fsPath.split('/').pop() || ''; + return labelA.localeCompare(labelB); + }); + node.children.forEach(n => this.sortNodes(n)); + } + } + + /** + * Builds the root node of the LFDataProviderNode tree based on the provided URI. + * @param uri The URI of the project to build the root node for. + * @returns The root `LFDataProviderNode` for the project. + */ buildRoot(uri: string): LFDataProviderNode { const splittedUri = uri.split('/'); const projectLabel = splittedUri[splittedUri.length - 3]; - // Check if the project already exists in the data const existingProject = this.data.find(item => item.label === projectLabel); if (!existingProject) { - // Construct projectUri const projectUri = splittedUri.slice(0, -3).join('/') + '/'; - // Create the root - const root = new LFDataProviderNode(projectLabel, projectUri, 'root', []); - // Construct project node + const root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.ROOT, this.type, []); this.data.push(root); return root; } @@ -198,103 +287,79 @@ export class LFDataProvider implements vscode.TreeDataProvider { - this.refreshTree(); - } - )); - } - - /** - * Registers a command that allows the user to navigate to a file associated with a node in the LFDataProvider. - * - * When the {@code'linguafranca.goToFile'} command is executed, it will open the text document associated with the - * provided {@code LFDataProviderNode} and position the editor at the start of the node's position, if available. - * - * @param context The extension context, used to register the command. - */ - registerGoToFileCommand(context: vscode.ExtensionContext) { - context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.goToFile', (node: LFDataProviderNode) => { - vscode.workspace.openTextDocument(node.resourceUri).then(doc => { - vscode.window.showTextDocument(doc /*, vscode.ViewColumn.Beside*/).then(e => { - if (node.position) { - e.selection = new vscode.Selection(node.position.start - 1, 0, node.position.end, 0); - e.revealRange(new vscode.Range(node.position.start - 1, 0, node.position.end, 0), vscode.TextEditorRevealType.InCenter); - } - }); - }); - } - )); + * Opens the file associated with the node in the Tree View. + * @param node - The node whose file is to be opened. + * @param beside - Whether to open the file beside the current editor. + */ + goToFileCommand(node: LFDataProviderNode, beside: boolean) { + vscode.workspace.openTextDocument(node.resourceUri).then(doc => { + vscode.window.showTextDocument(doc, beside == false ? undefined : vscode.ViewColumn.Beside).then(e => { + if (node.position) { + const selection = this.getHighlightSelection(node); + e.selection = selection; + e.revealRange(new vscode.Range( + selection.anchor.line, + selection.anchor.character, + selection.active.line, + selection.active.character), + vscode.TextEditorRevealType.InCenter); + } + }); + }); } /** - * Registers a command to open a Lingua Franca resource in a split view. - * - * When the command is executed, it opens the Lingua Franca resource associated with the - * provided {@code LFDataProviderNode} in a new editor view, positioned beside the current active - * editor view. - * - * @param context - The extension context. - */ - registerOpenInSplitViewCommand(context: vscode.ExtensionContext) { - context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.openInSplitView', (node: LFDataProviderNode) => { - vscode.workspace.openTextDocument(node.resourceUri).then(doc => { - vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside) - }); + * Imports the reactor associated with the selected LFDataProviderNode. + * @param node - The node representing the reactor to import. + */ + async importReactorCommand(node: LFDataProviderNode) { + const editor = vscode.window.activeTextEditor; + if (editor) { + if (!editor.document.uri.fsPath.endsWith('.lf')) { + vscode.window.showErrorMessage('The active editor must be a Ligua Franca program.'); + return; } - )); + const relativePath = this.getRelativePath(editor.document.uri.fsPath, node.resourceUri.fsPath); + const importText = `import ${node.label.toString()} from "${relativePath}"\n`; + const position = await this.getTargetPosition(node.resourceUri); + this.addTextOnActiveEditor(editor, position.end, importText); + } } /** - * Registers a command to import a Reactor from the LFDataProviderNode. - * When the command is executed, it finds the relative path of the Reactor's resource URI - * compared to the active editor's document URI, and inserts an import statement for the - * Reactor at the appropriate position in the active editor. - * - * @param context - The extension context. - */ - registerImportReactorCommand(context: vscode.ExtensionContext) { - context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.importReactor', async (node: LFDataProviderNode) => { - const editor = vscode.window.activeTextEditor; - if (editor) { - if(!editor.document.uri.fsPath.endsWith('.lf')) { - vscode.window.showErrorMessage('The active editor must be a Ligua Franca program.'); - return; - } - const relativePath = this.getRelativePath(editor.document.uri.fsPath, node.resourceUri.fsPath); - const importText = `import ${node.label.toString()} from "${relativePath}"\n`; - const position = await this.getTargetPosition(node.resourceUri); - this.addTextOnActiveEditor(editor, position.end, importText); - } - - } - )); + * Gets the selection to highlight in the editor for the node. + * @param node - The node for which to get the selection. + * @returns The selection to highlight. + */ + getHighlightSelection(node: LFDataProviderNode): vscode.Selection { + const editor = vscode.window.activeTextEditor; + if(editor){ + const sel = new vscode.Selection(node.position.start - 1, 0, node.position.start - 1, this.HIGHLIGHT_OFFSET); + const selectionRange = new vscode.Range(sel.start.line, sel.start.character, sel.end.line, sel.end.character); + const highlighted = editor.document.getText(selectionRange); + const idx = highlighted.indexOf(node.label.toString()); + const endIdx = idx + node.label.toString().length; + return new vscode.Selection(node.position.start - 1, idx, node.position.start - 1, endIdx); + } + return new vscode.Selection(node.position.start - 1, 0, node.position.start - 1, this.HIGHLIGHT_OFFSET); } /** - * Gets the relative path between a source and target file. - * @param source The path to the source file. - * @param target The path to the target file. - * @returns The relative path between the source and target files. - */ + * Gets the relative path between a source and target file. + * @param source The path to the source file. + * @param target The path to the target file. + * @returns The relative path between the source and target files. + */ getRelativePath(source: string, target: string) { return path.relative(path.dirname(source), target); } /** - * Adds the provided text to the active editor at the specified end position, inserting it on the next empty line. - * - * @param editor - The active text editor to add the text to. - * @param end - The position to start searching for the next empty line from. - * @param importText - The text to insert on the next empty line. - */ + * Adds text to the active editor at the specified position. + * @param editor - The active text editor. + * @param end - The position to start searching for the next empty line from. + * @param importText - The text to insert. + */ addTextOnActiveEditor(editor: vscode.TextEditor, end: number, importText: string): void { const document = editor.document; const totalLines = document.lineCount; @@ -319,62 +384,13 @@ export class LFDataProvider implements vscode.TreeDataProvider { return this.client.onReady().then(() => { return this.client.sendRequest('generator/getTargetPosition', uri.toString()); }); } -} - -/** -* Represents a node in the LFDataProvider tree. -* -* @param label - The label to display for the node. -* @param uri - The URI of the resource associated with the node. -* @param role - The role of the node, such as 'reactor', 'file', or 'root'. -* @param children - The child nodes of this node, if any. -* @param position - The start and end positions of the node in the source code, if available. -*/ -export class LFDataProviderNode extends vscode.TreeItem { - - children: LFDataProviderNode[] | undefined - role: string - position: NodePosition | undefined - - constructor(label: string, uri: string, role: string, - children?: LFDataProviderNode[] | undefined, position?: NodePosition | undefined) { - let newLabel = label.replace('.lf', ''); - super(newLabel, role === 'reactor' ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); - this.resourceUri = vscode.Uri.parse(uri); - this.children = children; - this.role = role; - let folder = role === 'root' ? 'root' : role === 'file' ? 'file' : 'code'; - this.iconPath = { - light: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-light.svg'), - dark: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-dark.svg') - } - this.contextValue = role; - if (position) { this.position = position; } - } -} - -/** -* Represents the start and end positions of a node in the source code. -* -* @param start - The starting position of the node. -* @param end - The ending position of the node. -*/ -class NodePosition { - start: number; - end: number; - - constructor(start: number, end: number) { - this.start = start; - this.end = end; - } -} +} \ No newline at end of file From 9ddeb4bc7cce4bc3771f7d60a3929088ecd36ef7 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Fri, 10 May 2024 15:58:19 -0700 Subject: [PATCH 09/38] Refactored Remote view to Liingo Libraries; enhanced tree view refresh post file modifications; optimized item icons for better UI coherence --- images/icons/file/file-dark.svg | 3 - images/icons/file/file-light.svg | 3 - images/icons/reactor/reactor-dark.svg | 3 - images/icons/reactor/reactor-light.svg | 3 - images/icons/root/root-dark.svg | 6 - images/icons/root/root-light.svg | 5 - images/icons/root/root-open-dark.svg | 5 - images/icons/root/root-open-light.svg | 5 - package.json | 36 ++-- src/extension.ts | 103 ++++++++--- src/lfview/lf-data-provider-commands.ts | 63 +++---- src/lfview/lf-data-provider.ts | 219 +++++++++++++++++------- 12 files changed, 286 insertions(+), 168 deletions(-) delete mode 100644 images/icons/file/file-dark.svg delete mode 100644 images/icons/file/file-light.svg delete mode 100644 images/icons/reactor/reactor-dark.svg delete mode 100644 images/icons/reactor/reactor-light.svg delete mode 100644 images/icons/root/root-dark.svg delete mode 100644 images/icons/root/root-light.svg delete mode 100644 images/icons/root/root-open-dark.svg delete mode 100644 images/icons/root/root-open-light.svg diff --git a/images/icons/file/file-dark.svg b/images/icons/file/file-dark.svg deleted file mode 100644 index e57f1ece8..000000000 --- a/images/icons/file/file-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/images/icons/file/file-light.svg b/images/icons/file/file-light.svg deleted file mode 100644 index 3c8ac05b9..000000000 --- a/images/icons/file/file-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/images/icons/reactor/reactor-dark.svg b/images/icons/reactor/reactor-dark.svg deleted file mode 100644 index d53777300..000000000 --- a/images/icons/reactor/reactor-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/images/icons/reactor/reactor-light.svg b/images/icons/reactor/reactor-light.svg deleted file mode 100644 index d7a79a343..000000000 --- a/images/icons/reactor/reactor-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/images/icons/root/root-dark.svg b/images/icons/root/root-dark.svg deleted file mode 100644 index 6b872b0c0..000000000 --- a/images/icons/root/root-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/images/icons/root/root-light.svg b/images/icons/root/root-light.svg deleted file mode 100644 index 1fa17b1d2..000000000 --- a/images/icons/root/root-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/images/icons/root/root-open-dark.svg b/images/icons/root/root-open-dark.svg deleted file mode 100644 index f100434a5..000000000 --- a/images/icons/root/root-open-dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/images/icons/root/root-open-light.svg b/images/icons/root/root-open-light.svg deleted file mode 100644 index 120025b06..000000000 --- a/images/icons/root/root-open-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/package.json b/package.json index d4a919920..053eb46d3 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "icon": "$(refresh)" }, { - "command": "linguafranca.refreshRemoteEntries", + "command": "linguafranca.refreshLibraryEntries", "title": "Refresh Tree View", "icon": "$(refresh)" }, @@ -99,7 +99,7 @@ "icon": "$(go-to-file)" }, { - "command": "linguafranca.goToRemoteFile", + "command": "linguafranca.goToLibraryFile", "title": "Go To Selected File", "icon": "$(go-to-file)" }, @@ -109,7 +109,7 @@ "icon": "$(insert)" }, { - "command": "linguafranca.importRemoteReactor", + "command": "linguafranca.importLibraryReactor", "title": "Import Selected Reactor", "icon": "$(insert)" }, @@ -119,7 +119,7 @@ "icon": "$(split-horizontal)" }, { - "command": "linguafranca.openRemoteInSplitView", + "command": "linguafranca.openLibraryInSplitView", "title": "Open in Split View", "icon": "$(split-horizontal)" }, @@ -129,7 +129,7 @@ "icon": "$(collapse-all)" }, { - "command": "linguafranca.collapseAllRemote", + "command": "linguafranca.collapseAllLibrary", "title": "Collapse All", "icon": "$(collapse-all)" } @@ -189,9 +189,9 @@ "when": "view == lf-lang-local" }, { - "command": "linguafranca.refreshRemoteEntries", + "command": "linguafranca.refreshLibraryEntries", "group": "navigation@1", - "when": "view == lf-lang-remote" + "when": "view == lf-lang-library" }, { "command": "linguafranca.collapseAll", @@ -199,9 +199,9 @@ "when": "view == lf-lang-local" }, { - "command": "linguafranca.collapseAllRemote", + "command": "linguafranca.collapseAllLibrary", "group": "navigation@2", - "when": "view == lf-lang-remote" + "when": "view == lf-lang-library" } ], @@ -212,9 +212,9 @@ "when": "view == lf-lang-local && viewItem == file" }, { - "command": "linguafranca.goToRemoteFile", + "command": "linguafranca.goToLibraryFile", "group": "inline", - "when": "view == lf-lang-remote && viewItem == file" + "when": "view == lf-lang-library && viewItem == file" }, { "command": "linguafranca.openInSplitView", @@ -222,9 +222,9 @@ "when": "view == lf-lang-local && viewItem != root" }, { - "command": "linguafranca.openRemoteInSplitView", + "command": "linguafranca.openLibraryInSplitView", "group": "inline", - "when": "view == lf-lang-remote && viewItem != root" + "when": "view == lf-lang-library && viewItem != root" }, { "command": "linguafranca.importReactor", @@ -232,9 +232,9 @@ "when": "view == lf-lang-local && viewItem == reactor" }, { - "command": "linguafranca.importRemoteReactor", + "command": "linguafranca.importLibraryReactor", "group": "inline", - "when": "view == lf-lang-remote && viewItem == reactor" + "when": "view == lf-lang-library && viewItem == reactor" } ] }, @@ -251,11 +251,11 @@ "lf-lang": [ { "id": "lf-lang-local", - "name": "Local" + "name": "Local Libraries" }, { - "id": "lf-lang-remote", - "name": "Remote" + "id": "lf-lang-library", + "name": "Lingo Libraries" } ] } diff --git a/src/extension.ts b/src/extension.ts index 675f2d276..bb51d0af2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -11,22 +11,40 @@ import { legend, semanticTokensProvider } from './highlight'; import * as config from './config'; import { registerBuildCommands, registerNewFileCommand } from './build_commands'; import * as checkDependencies from './check_dependencies'; -import { LFDataProvider, LFDataProviderNodeType} from './lfview/lf-data-provider'; -import { registerCollapseAllCommand, registerCollapseAllRemoteCommand, registerGoToFileCommand, registerGoToRemoteFileCommand, registerImportReactorCommand, registerImportRemoteReactorCommand, registerOpenInSplitViewCommand, registerOpenRemoteInSplitViewCommand, registerRefreshCommand, registerRefreshRemoteCommand } from './lfview/lf-data-provider-commands'; +import { LFDataProvider, LFDataProviderNode, LFDataProviderNodeType} from './lfview/lf-data-provider'; +import { registerCollapseAllCommand, + registerCollapseAllLibraryCommand, + registerGoToFileCommand, + registerGoToLibraryFileCommand, + registerImportReactorCommand, + registerImportLibraryReactorCommand, + registerOpenInSplitViewCommand, + registerOpenLibraryInSplitViewCommand, + registerRefreshCommand, + registerRefreshLibraryCommand } from './lfview/lf-data-provider-commands'; let client: LanguageClient; let socket: Socket +/** + * Activates the LF Language extension. + * Registers semantic tokens provider, checks dependencies, sets up language client, + * registers commands, and creates tree views for local and library components. + * @param context The extension context. + */ export async function activate(context: vscode.ExtensionContext) { + // Register semantic tokens provider context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider( { language: 'lflang', scheme: 'file' }, semanticTokensProvider, legend )); + // Check dependencies and register dependency watcher checkDependencies.registerDependencyWatcher(); + // Check Java dependency if (!( await checkDependencies.checkerFor (checkDependencies.Dependency.Java) @@ -34,15 +52,14 @@ export async function activate(context: vscode.ExtensionContext) { () )) return; + // Set up language client const serverOptions: ServerOptions = createServerOptions(context); - const clientOptions: LanguageClientOptions = { documentSelector: ['lflang'], synchronize: { fileEvents: vscode.workspace.createFileSystemWatcher('**/*.*') } }; - client = new LanguageClient('LF Language Server', serverOptions, clientOptions); // enable tracing (.Off, .Messages, Verbose) client.trace = Trace.Verbose; @@ -56,33 +73,77 @@ export async function activate(context: vscode.ExtensionContext) { client.start(); + // Register build commands and new file command registerBuildCommands(context, client); registerNewFileCommand(context); - /** - * Registers a tree data provider and creates a tree view for the 'lf-lang-local' view in the Visual Studio Code extension. - * The `LFDataProvider` instance is responsible for providing the data for the tree view. - * The `refreshTree()` method is called to update the contents of the tree view. - */ + // Registers a tree data provider and creates a tree view for the 'lf-lang-local' view const lfDataProviderLocal = new LFDataProvider(LFDataProviderNodeType.LOCAL, client, context); context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-local', lfDataProviderLocal)); - context.subscriptions.push(vscode.window.createTreeView('lf-lang-local', { treeDataProvider: lfDataProviderLocal })); - - const lfDataProviderRemote = new LFDataProvider(LFDataProviderNodeType.REMOTE, client, context); - context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-remote', lfDataProviderRemote)); - context.subscriptions.push(vscode.window.createTreeView('lf-lang-remote', { treeDataProvider: lfDataProviderRemote })); - - // Register all teh commands + const localTreeView = vscode.window.createTreeView('lf-lang-local', { treeDataProvider: lfDataProviderLocal }); + context.subscriptions.push(localTreeView); + localTreeView.onDidExpandElement(element => { + lfDataProviderLocal.onExpandEvent(element.element); + }); + localTreeView.onDidCollapseElement(element => { + lfDataProviderLocal.onCollapseEvent(element.element); + }); + + // Registers a tree data provider and creates a tree view for the 'lf-lang-library' view + const lfDataProviderLibrary = new LFDataProvider(LFDataProviderNodeType.LIBRARY, client, context); + context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-library', lfDataProviderLibrary)); + const libraryTreeView = vscode.window.createTreeView('lf-lang-library', { treeDataProvider: lfDataProviderLibrary }); + context.subscriptions.push(libraryTreeView); + libraryTreeView.onDidExpandElement(element => { + lfDataProviderLibrary.onExpandEvent(element.element); + }); + libraryTreeView.onDidCollapseElement(element => { + lfDataProviderLibrary.onCollapseEvent(element.element); + }); + + // Register all the commands registerRefreshCommand(context, lfDataProviderLocal); - registerRefreshRemoteCommand(context, lfDataProviderRemote); + registerRefreshLibraryCommand(context, lfDataProviderLibrary); registerGoToFileCommand(context, lfDataProviderLocal); - registerGoToRemoteFileCommand(context, lfDataProviderRemote); + registerGoToLibraryFileCommand(context, lfDataProviderLibrary); registerOpenInSplitViewCommand(context, lfDataProviderLocal); - registerOpenRemoteInSplitViewCommand(context, lfDataProviderRemote); + registerOpenLibraryInSplitViewCommand(context, lfDataProviderLibrary); registerImportReactorCommand(context, lfDataProviderLocal); - registerImportRemoteReactorCommand(context, lfDataProviderRemote); + registerImportLibraryReactorCommand(context, lfDataProviderLibrary); registerCollapseAllCommand(context); - registerCollapseAllRemoteCommand(context); + registerCollapseAllLibraryCommand(context); + + // context.subscriptions.push( + // vscode.workspace.onDidDeleteFiles(async (e) => { + // if (e.files.length > 0) { + // e.files.forEach( (file) => { + // if (file.path.endsWith('.lf')) { + // lfDataProviderLocal.refreshTree(); + // lfDataProviderLibrary.refreshTree(); + // } + // }); + // } + // }), + // vscode.workspace.onDidRenameFiles((e) => { + // if (e.files.length > 0) { + // e.files.forEach(async (file) => { + // if (file.newUri.path.endsWith('.lf')) { + // lfDataProviderLocal.refreshTree(); + // lfDataProviderLibrary.refreshTree(); + // } + // }); + // } + // }), + // vscode.workspace.onDidCreateFiles((e) => { + // if (e.files.length > 0) { + // e.files.forEach(async (file) => { + // if (file.path.endsWith('.lf')) { + // lfDataProviderLibrary.refreshTree(); + // } + // }); + // } + // }) + // ); } diff --git a/src/lfview/lf-data-provider-commands.ts b/src/lfview/lf-data-provider-commands.ts index b98f99e26..1712ff3e5 100644 --- a/src/lfview/lf-data-provider-commands.ts +++ b/src/lfview/lf-data-provider-commands.ts @@ -14,15 +14,16 @@ export function registerRefreshCommand(context: vscode.ExtensionContext, local: )); } + /** - * Registers a command to refresh the remote LF libraries tree view. - * @param context - The extension context. - * @param remote - The LFDataProvider instance managing remote LF libraries. - */ -export function registerRefreshRemoteCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { +* Registers a command to refresh the Lingo downloaded LF libraries tree view. +* @param context - The extension context. +* @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. +*/ +export function registerRefreshLibraryCommand(context: vscode.ExtensionContext, library: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.refreshRemoteEntries', () => { - remote.refreshTree(); + 'linguafranca.refreshLibraryEntries', () => { + library.refreshTree(); } )); } @@ -35,20 +36,20 @@ export function registerRefreshRemoteCommand(context: vscode.ExtensionContext, r export function registerGoToFileCommand(context: vscode.ExtensionContext, local: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( 'linguafranca.goToFile', (node: LFDataProviderNode) => { - local.goToFileCommand(node,false); + local.goToFileCommand(node, false); } )); } /** - * Registers a command to navigate to a file in the remote LF libraries tree view. + * Registers a command to navigate to a file in the Lingo downloaded LF libraries tree view. * @param context - The extension context. - * @param remote - The LFDataProvider instance managing remote LF libraries. + * @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. */ -export function registerGoToRemoteFileCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { +export function registerGoToLibraryFileCommand(context: vscode.ExtensionContext, library: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.goToRemoteFile', (node: LFDataProviderNode) => { - remote.goToFileCommand(node,false); + 'linguafranca.goToLibraryFile', (node: LFDataProviderNode) => { + library.goToFileCommand(node, false); } )); } @@ -61,21 +62,21 @@ export function registerGoToRemoteFileCommand(context: vscode.ExtensionContext, export function registerOpenInSplitViewCommand(context: vscode.ExtensionContext, local: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( 'linguafranca.openInSplitView', (node: LFDataProviderNode) => { - local.goToFileCommand(node,true); - } + local.goToFileCommand(node, true); + } )); } /** - * Registers a command to open a file in split view in the remote LF libraries tree view. + * Registers a command to open a file in split view in the Lingo downloaded LF libraries tree view. * @param context - The extension context. - * @param remote - The LFDataProvider instance managing remote LF libraries. + * @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. */ -export function registerOpenRemoteInSplitViewCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { +export function registerOpenLibraryInSplitViewCommand(context: vscode.ExtensionContext, library: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.openRemoteInSplitView', (node: LFDataProviderNode) => { - remote.goToFileCommand(node,true); - } + 'linguafranca.openLibraryInSplitView', (node: LFDataProviderNode) => { + library.goToFileCommand(node, true); + } )); } @@ -93,14 +94,14 @@ export function registerImportReactorCommand(context: vscode.ExtensionContext, l } /** - * Registers a command to import a reactor from the remote LF libraries into the active LF program. + * Registers a command to import a reactor from the Lingo downloaded LF libraries into the active LF program. * @param context - The extension context. - * @param remote - The LFDataProvider instance managing remote LF libraries. + * @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. */ -export function registerImportRemoteReactorCommand(context: vscode.ExtensionContext, remote: LFDataProvider) { +export function registerImportLibraryReactorCommand(context: vscode.ExtensionContext, library: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.importRemoteReactor', async (node: LFDataProviderNode) => { - await remote.importReactorCommand(node); + 'linguafranca.importLibraryReactor', async (node: LFDataProviderNode) => { + await library.importReactorCommand(node); } )); } @@ -119,14 +120,14 @@ export function registerCollapseAllCommand(context: vscode.ExtensionContext) { } /** - * Registers a command to collapse all nodes in the remote LF libraries tree view. + * Registers a command to collapse all nodes in the Lingo downloaded LF libraries tree view. * @param context - The extension context. - * @param remote - The LFDataProvider instance managing remote LF libraries. */ -export function registerCollapseAllRemoteCommand(context: vscode.ExtensionContext) { +export function registerCollapseAllLibraryCommand(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.collapseAllRemote', () => { - vscode.commands.executeCommand('workbench.actions.treeView.lf-lang-remote.collapseAll'); + 'linguafranca.collapseAllLibrary', () => { + vscode.commands.executeCommand('workbench.actions.treeView.lf-lang-library.collapseAll'); } )); } + diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 110ca9267..4607aef03 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -18,11 +18,11 @@ export enum LFDataProviderNodeRole { * Defines the types of the displayed data provider tree view. * * {@code LOCAL} represents nodes for local LF libraries. - * {@code REMOTE} represents nodes for remote LF libraries. + * {@code LIBRARY} represents nodes for LF libraries downloaded using Lingo. */ export enum LFDataProviderNodeType { LOCAL = 1, - REMOTE = 2 + LIBRARY = 2 } /** @@ -41,22 +41,25 @@ export class LFDataProviderNode extends vscode.TreeItem { position: NodePosition | undefined; type: LFDataProviderNodeType; + /** + * Represents the URI of the data provider node. + * Avoid using TreeItem.resourceUri to maintain a clear separation between error detection and Tree Item rendering. + */ + uri: vscode.Uri; + constructor(label: string, uri: string, role: string, type: LFDataProviderNodeType, children?: LFDataProviderNode[] | undefined, position?: NodePosition | undefined) { let newLabel = label.replace('.lf', ''); super(newLabel, role === LFDataProviderNodeRole.REACTOR ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); - this.resourceUri = vscode.Uri.parse(uri); + this.uri = vscode.Uri.parse(uri); this.children = children; this.role = role; this.type = type; - let folder = role === LFDataProviderNodeRole.ROOT ? 'root' : role === LFDataProviderNodeRole.FILE ? 'file' : 'reactor'; - this.iconPath = { - light: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-light.svg'), - dark: path.join(__filename, '..', '..', 'images', 'icons', folder, folder + '-dark.svg') - } + let icon = role === LFDataProviderNodeRole.ROOT ? 'root-folder' : role === LFDataProviderNodeRole.FILE ? 'file-code' : 'json'; + this.iconPath = new vscode.ThemeIcon(icon); this.contextValue = role; if (position) { this.position = position; } - + this.registerNodeCommand(); } @@ -67,7 +70,7 @@ export class LFDataProviderNode extends vscode.TreeItem { registerNodeCommand(): void { this.command = this.role === LFDataProviderNodeRole.REACTOR ? { title: "", - command: this.type == LFDataProviderNodeType.LOCAL ? "linguafranca.goToFile" : "linguafranca.goToRemoteFile", + command: this.type == LFDataProviderNodeType.LOCAL ? "linguafranca.goToFile" : "linguafranca.goToLibraryFile", arguments: [this] } : undefined; } @@ -103,8 +106,8 @@ export class LFDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter(); @@ -113,14 +116,24 @@ export class LFDataProvider implements vscode.TreeDataProvider { - if (e.files.length > 0) { - e.files.forEach(async (file) => { - if (file.path.endsWith('.lf')) { - await this.refreshTree(); - } - }); - } + this.watcher.onDidChange(() => { + this.refreshTree(); }), - vscode.workspace.onDidRenameFiles(async (e) => { - if (e.files.length > 0) { - e.files.forEach(async (file) => { - if (file.newUri.path.endsWith('.lf')) { - await this.refreshTree(); - } - }); - } + this.watcher.onDidCreate(() => { + this.refreshTree(); }), - vscode.workspace.onDidCreateFiles(async (e) => { - if (e.files.length > 0) { - e.files.forEach(async (file) => { - if (file.path.endsWith('.lf')) { - await this.refreshTree(); - } - }); - } - }) - ); - context.subscriptions.push( - this.watcher.onDidChange(async uri => { - await this.refreshTree(); + this.watcher.onDidDelete(() => { + this.refreshTree(); }) ); } + /** + * Handles the expand event for a node in the LFDataProvider tree. + * When the root node is expanded, its collapsible state is set to Expanded, + * the icon path is updated to the root folder icon opened, and the tree data is refreshed. + * @param element - The LFDataProviderNode that was expanded. + */ + onExpandEvent(element: LFDataProviderNode): void { + if (element.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed && + element.role === LFDataProviderNodeRole.ROOT + ) { + element.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; + element.iconPath = new vscode.ThemeIcon('root-folder-opened'); + this._onDidChangeTreeData.fire(element); + } + } + + /** + * Handles the collapse event for a node in the LFDataProvider tree. + * When the root node is collapsed, its collapsible state is set to Collapsed, + * the icon path is updated to the root folder icon, and the tree data is refreshed. + * @param element - The LFDataProviderNode that was collapsed. + */ + onCollapseEvent(element: LFDataProviderNode): void { + if (element.collapsibleState === vscode.TreeItemCollapsibleState.Expanded && + element.role === LFDataProviderNodeRole.ROOT + ) { + element.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; + element.iconPath = new vscode.ThemeIcon('root-folder'); + this._onDidChangeTreeData.fire(element); + } + } + /** * Refreshes the LF libraries tree view by fetching the latest library reactor information from the Language Server. */ - public async refreshTree() { + public refreshTree() { console.log("Refreshing LF libraries..."); this.data = []; if (vscode.workspace.workspaceFolders) { @@ -223,29 +245,75 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { dataNode.children.forEach(child => { - node.children.push(new LFDataProviderNode(child.label, - child.uri, - LFDataProviderNodeRole.REACTOR, - this.type, [], + node.children.push(new LFDataProviderNode(child.label, + child.uri, + LFDataProviderNodeRole.REACTOR, + this.type, [], child.position )); }); } + this.sortData(); + } + + /** + * Adds a data item to the Lingo Libraries view. + * @param dataNode - The data node to add. + */ + addDataItemLibrary(dataNode: any) { + const root = this.buildRoot(dataNode.uri); + const library_root = this.buildLibraryRoot(dataNode.uri, root); + let node = new LFDataProviderNode(dataNode.label, dataNode.uri, LFDataProviderNodeRole.FILE, this.type, []); + if (dataNode.children.length > 0) { + dataNode.children.forEach(child => { + node.children.push(new LFDataProviderNode(child.label, + child.uri, + LFDataProviderNodeRole.REACTOR, + this.type, [], + child.position + )); + }); + } + if (library_root.children.find(n => n.label === node.label) === undefined) { + library_root.children.push(node); + } + this.sortData(); + } + + /** + * Sorts the data nodes in the LFDataProvider tree and fires a change event. + * The nodes are sorted alphabetically by their label or the last segment of their URI path. + * After sorting the data nodes, the children of each node are also recursively sorted. + */ + sortData() { this.data.sort((a: LFDataProviderNode, b: LFDataProviderNode) => { - const labelA = typeof a.label === 'string' ? a.label : a.resourceUri.fsPath.split('/').pop() || ''; - const labelB = typeof b.label === 'string' ? b.label : b.resourceUri.fsPath.split('/').pop() || ''; + const labelA = typeof a.label === 'string' ? a.label : a.uri.fsPath.split('/').pop() || ''; + const labelB = typeof b.label === 'string' ? b.label : b.uri.fsPath.split('/').pop() || ''; return labelA.localeCompare(labelB); }); this.data.forEach(n => this.sortNodes(n)); @@ -259,8 +327,8 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { node.children.sort((a, b) => { - const labelA = typeof a.label === 'string' ? a.label : a.resourceUri.fsPath.split('/').pop() || ''; - const labelB = typeof b.label === 'string' ? b.label : b.resourceUri.fsPath.split('/').pop() || ''; + const labelA = typeof a.label === 'string' ? a.label : a.uri.fsPath.split('/').pop() || ''; + const labelB = typeof b.label === 'string' ? b.label : b.uri.fsPath.split('/').pop() || ''; return labelA.localeCompare(labelB); }); node.children.forEach(n => this.sortNodes(n)); @@ -268,17 +336,17 @@ export class LFDataProvider implements vscode.TreeDataProvider item.label === projectLabel); if (!existingProject) { - const projectUri = splittedUri.slice(0, -3).join('/') + '/'; + const projectUri = splittedUri.slice(0, - this.path_offset).join('/') + '/'; const root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.ROOT, this.type, []); this.data.push(root); return root; @@ -286,23 +354,43 @@ export class LFDataProvider implements vscode.TreeDataProvider item.label === projectLabel); + if (!existingLibraryRoot) { + const projectUri = splittedUri.slice(0, - this.path_offset + 3).join('/') + '/'; + const library_root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.ROOT, this.type, []); + root.children.push(library_root); + return library_root; + } + return existingLibraryRoot; + } + /** * Opens the file associated with the node in the Tree View. * @param node - The node whose file is to be opened. * @param beside - Whether to open the file beside the current editor. */ goToFileCommand(node: LFDataProviderNode, beside: boolean) { - vscode.workspace.openTextDocument(node.resourceUri).then(doc => { + vscode.workspace.openTextDocument(node.uri).then(doc => { vscode.window.showTextDocument(doc, beside == false ? undefined : vscode.ViewColumn.Beside).then(e => { if (node.position) { const selection = this.getHighlightSelection(node); e.selection = selection; e.revealRange(new vscode.Range( - selection.anchor.line, - selection.anchor.character, - selection.active.line, - selection.active.character), - vscode.TextEditorRevealType.InCenter); + selection.anchor.line, + selection.anchor.character, + selection.active.line, + selection.active.character), + vscode.TextEditorRevealType.InCenter); } }); }); @@ -319,9 +407,9 @@ export class LFDataProvider implements vscode.TreeDataProvider Date: Tue, 4 Jun 2024 11:45:05 -0700 Subject: [PATCH 10/38] Updated Lingo Libraries path --- src/lfview/lf-data-provider.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 4607aef03..e3c8a765b 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -108,6 +108,7 @@ export class LFDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter(); @@ -134,7 +135,8 @@ export class LFDataProvider implements vscode.TreeDataProvider { - vscode.workspace.findFiles(this.searchPath).then(uris => { + vscode.workspace.findFiles(this.searchPath, this.exclude_path ? this.exclude_path : null).then(uris => { uris.forEach(uri => { this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(node => { this.addDataItem(node); From 9ff17fc7a9cf18e8ec615224fc3cd029ff6ec05e Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Thu, 20 Jun 2024 16:12:23 -0700 Subject: [PATCH 11/38] Solved bug in importReactorCommand function --- src/lfview/lf-data-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index e3c8a765b..577f1e996 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -411,7 +411,7 @@ export class LFDataProvider implements vscode.TreeDataProvider Date: Mon, 8 Jul 2024 10:22:02 -0700 Subject: [PATCH 12/38] Updated README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f80077a96..ae08592cd 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ This extension adds language support for [Lingua Franca (LF)](https://www.lf-lan * target code validation upon build or file save * user-triggered build (Ctrl + Shift + P, then `Lingua Franca: Build`) * user-triggered build and run (Ctrl + Shift + P, then `Lingua Franca: Build and Run`) +* Lingua Franca Package Explorer (click on the LF icon in the Activity Bar on the left), which features two distinct tree-view sections: + * **Local Libraries:** This section showcases a catalog of Local Libraries, which are libraries personally defined by the programmer. + * **Lingo Libraries:** Here, you'll find a catalog of all libraries downloaded in the personal workspace using the Lingo package manager. ## Quick Start 1. Install this plugin from the [VSCode From 3d41e4ce3160689fe6d2b75f5ef4a5f9e4c468a4 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Mon, 8 Jul 2024 10:53:50 -0700 Subject: [PATCH 13/38] Updated icon.png --- images/icon.png | Bin 0 -> 12442 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/icon.png diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..40fdd41e45080eaea176c53b21c6c6db1da6f32c GIT binary patch literal 12442 zcmW+-Wk6fa62*!ZhvM#1q`0@ZyO!bwin~LQ;$Gan&=!Y6@D%spQY^(=JV>w*UcUEZ zbCcY?yOW(cJ2Ph|3A)-U__)-#C@3iSYO0F*$m=r-3MvXVCh}cqcUcSag5#-b=7WMl z(DPqI&F=A5Lw-r+t7PhH;NjpKVB>9%5)cr;=j86E)je^36qNe!H zFtF&fIH1UI={~O1=RE7Qo7X;<8yj1d*tDMnFN)0{-nqoH z!p?@mtn4rXN?x+u7Z{3(*Y>%my;D;%Ai33;*FvoU{u9OdGBa5I4remAGUpqe5OZ+Q zObABes{Ok(AE{3sER7Tru&?!nRKB&A{L9>2I^6#Z&0h&X0HxljbdLQ|1Cj%i4@Z2uXh z?iv3UtuTUt$Xh0ZVfGq-Z+hLO*(y=H>EV~&Psag~Cn9DnMFLC-Y8T%Al$Sio-Jhqf;>5jmh zP?Z7}2ZmVon#dwdTin?N=Um<@?EXW_9hT@vgUe@2*EWSs95#ffkXWQ>vaH|wk3jLDRsCFlL<@BAJdn1m;&g3UD{Lx#|Zw=3A-@`UG-sB zA-mA1!^;%}Y+yLQ*cii6uiMwk#cgTg!x`IFOrL<$#!{sGoKUREJ)V#qZ_Ai;pY-IW zwPN>QV%YcGu-F&8;k^^ijh_rc;qbF;-~e*d{cWN&=D_v4CK0hVjJHysb7nO&t(sQ!T-~&8!1-4mdkjkh1|i z_NJ(!Z8iv|&`9e2Rp#uu-^!*|ZsRP8uOdZQPA<)lbaKwbLr zRL;5HpZ&e=4%S${oeH%HOHjlrtzxBs3To6N)%*JjXG+7JIGk1;%zF%hHD?nt;$5t& zp3h7C0P8juB&gX#DCh+WqmsFWK zQef{RVtB%jA-NWN_sS)(V#XO80y!|&J*{W_QbRP27HJh5M@C78YOpi2jDCiymKy80 z)2Iz_+jmvfS8~=bg*}%h&BmHCV}zYz$uA|#Xi3*tPsPmW^|1X?hqTvod=K$BznM&U z!cwYY;u#a78FUhP7L3tPTWf(sfuk)I_GV#hLX)lAMA9tZN&H0ASF6nFeNAHl`)pVb z+R1N|epr-#`ze>hWJ6e%;i4%#WiU$d676Q>6n>}QwgiqYyrX!&b8%!G5*pg(?A}x4 zKyP^?Ugg2x@@8T72CTG24h;!;{k_g%I<4ER*_}r2j8lFhklKp}h~7FD4#lgL!Qp`N zAc`b<1*G|-#7CV?@2B5=2ooOc>ROUMz4S=EuW#4oKxYfqNQ08Q(5!03Wu3d|uTf9IT$sepA|t>aLD z)2~!|XKoq3VK&5b4ww$0@i*<&Q0SLZy`2iA;w58v+SqNBA=P>$qO@(}>9<|*i@iP{ zj_(gs)Ap`NcmggUtOU)#w=M$f4=gSeBl<}`~$aL2dGPv1$$JpyvXUVct22zEZA{?_1B)zyFs9Q{1G+kXmU+()1=b!&|}0l$1H zP@K8`80=6r+h~HxH4aV(zW7w2tYAk<2)ZLKe*8fr{+r?4n=L|K;W#*ED<^D*jeVd}yUCtMBIR%YZ#5FiopVrgxwTZLQU;dW;h(Mu)PBlo*^ zUV#~rbAAV-sJ*+YEuH=A9p^ctOxZ8FQ8c)AzmAli$W{YR>Sz(YM{X-U7fUn?(jg44 z=7_6$(bv#F3?3lyC(kj^H`5Sj`1S~!u-+MaM=OQUjlDjm`+4W5)#R|??hbH8&0Z3u z(`Y5^@2o2TO*yMfkx1<@$UVM_hVuw+g*Q4;qqn|M)zW_X<53H{e=9>B3AFNWKj$+r zf0m;M3;A7gYUc?QRVk|E4kPA2%m%)>kbIQzz+U6kH2Gxc5O?;&l2f2(guEGh!MZ&j8mq#hIj};#ZD#W(6RL z92NYdU%n68t6DaIz9sM-OYkr#+*AfN$t4Te*j()_1#Hkz^@SxBY!)F#T5K3(33!q zUt}J+^TeHrwBuyT@}jB~Icn&H3TASi-r8$TiK5|RI4WM@Fg^^)1;?VxY?uDJH|{3E z4Yse!`1=!!HCPLqx^N>D6hx@>Rd6LNG{Wr#8}IwhFT`;lzijfzltonh+{4f=5YBk! z1=lolIdr2|K41`8?93n?i_RUP{>U&T{CZI_gLHc2kRkMSW1anJj?AK1R~-WPoq;)I zs0M;~s~`2bdGG;?Oe-(HWa=+dSkUdHNVD{tCZzYVA&j}Nt@E!I(PKq?NI6MdiLf#~ zRpUhZ22~xkmv@O)ruyU67LV!5Q}icv>8i#J5O80Mb1HoRr>KQkuTCt%U#+M?@H~9l zI6TFg2;1w`Z_;+%yyxB}@J}L{ol@~@FQV2ue-^S;S4o-qOtkJDmQ`cjfwR@KL)c_> zu~OZ-{x7vmMDk))M1#BhFIcZVS?;J7E^jFrnMwNduC{iG^n@?4rrN1U04nebkfY+Jph<|c zOtkai;b2d%!#QaQY|4*xkN|XP>o%HlX0ACkc}9z1?Dq55Uxoc2+t91O{Y*5X3GhA= ztn@1AL08rG6@aJnZdsBUrA=GB!va^H-wl%;oVTwLALj{#NM4``5<9I*h+1$A`=X^j zHHN5jcV%Cgb8^?(E~Lr>04I4*U&7g8OBe6V$WChiJiZR8^OrNFWdv8w+y!t>@V8Ci z9PjM;9B5+Pb9*s|Mj*lZc3*?%RwsVW;y(Dsn7-iI=Iy$)=U~varVc@-3h`HtE*Np9 zgM;~ggop{ks}+-R&lcvWlz{i%pL5UpGgtY?=}gw>s(l-74Nl!rU@B3sU+(q!BmyIQGz>te@ub*&0= zR2S(n(}zo4Xe$@NVHo&wJkd|i3$d6`eOBUYVdn|5gkQCu*ZIpR#tU8-nvQQ75l_D$ zGJ~cuz{0WJlRp*PD91lJHBD`unJg|E=vg?nt?Q}SYsoC#;1~JsVH-Sp-<8RRHixo= zB<`~F?R{DiEhI07fuCjgT6Vh1|4T+?>%=c_}&-t zr_|D@(u@Hq#a!N2aVIC=jZRIVbw)gy>Fi>m2BYlo^EHkLR0OCtEK$bp^&M^v+Ig>D zN^sEkH${4hJUhRV#G{g7X>zaRBZ*u`;ss&kfvyq_Y8b0H9`I$gvdb)-U!^Oh7gXnV z{tMywEI9o+0xtXLLY<(E#Q-1|*}g2dMrj{Xgf%?yZji@3PsrH>@Oi4Q9yg;{2!S3a zmVEl04*ELd*>gKJl_*L@6SAuEz=vzZTpo5ib>Fue*n9rl{!RY`*wha)zmKghC@-H= z3R5^?-EAs}+QEGl+EbCK|9BhbU6JXdP0IkNVNQzOGh(;;E!2}8o#bW0%95PhEEz4X znMW@B*4}LjtZ7GewS{1y&pc(xEe06o^)^Q%I%uy3%--D%HA*p{3rf4fUXPs6f!5uI z3~?Jim6vryDe(ZV4R)-f7k`ddB7Yy?d<{vQsY#;L4bkw$Ig41 zM@V?DgGQas%|64pwDAmx&+tH=1M&2)13~*HB(Y3_y*5g$x`9cG>!K$hTT8UTTrxJG z{TAo5Vo_hhFULugHkd8Pd!PV3wDn4Ubt zhlBu37gq5(?K~N}Jy4Wi1rIX8$UvXUq2WS$d9O|uNF2ZxU$d2eWnL%>$FE6mB!h9w~0MtQf$ccKew5efOlvO1)!(qwn3Qd#aaKC*B984M}p2 zsdR&;ShUObH19m;zGntreUUIW+~hjkguOiHHKT(DLyFo9e6Su%=AAW+-sFbLruyxF zr(34Wct1m9up-cGastMs)qvX>gdgzI(G?N*AJ-Lx2bqYSKe&NZz$47tJdzn@G)2sRr$8#e_P0Y_SJ$9x_dK!{E)hkW|TV3 z&GB2O<7^XxUsER9-RYFx3NI&4OyPx#efd$RHBv&L2b( zPnn$wHm@i?w)9F08oF~0%)A`2kgDdP!Q?}xO8!;JNmPEj34P;zm3N}XtP6hxpA8zh zZV;c!;?;$H=e~Ad{2D%oet2Q)X~Jy~O;}T!6AUCa?QYN*x#_42+>`e{NPg*C`3tbm z)hQa?!85B)wq5hH|IBA|_NSzEr3k`{7R_BKCBVDa6k?EQQW**r$mBcaK`9BvlA#x3lB8^(+$3B`Mg-q#? zjbKnSTH+@a%h3)VO)O^&fv_BZ1Cx#Bf*)(a)e!muvQOu7K|XH{yi^KuMG5|Vlg*Z0 zDHUwWga3nE4BdXdch%ufJfgk!?E25%LG-ODgKywEyq6oXkv$-VlykxtF*i2(pIAv> zZ|nbgArv!BH!k7xU`x6189KrJ!JV*_|Cx!ked^!~Fj1`idOxRGO1vlTeEcmItJuY1 zr5L7bcN2JAhKCSOl-`2a9VS;WGpY#wx3SO3moH8N*{(Q6 zJnY-WV_n5T;e_Z4w#T6L)()S1Y7n`3{1v5K>jGD@<_0W@M`1ga*;^F{7$%k2Z9*_g zZtZL3LX*}Yku~`$Ww4$ZVVi;>{8RzPy?-{ko$2%OAN!!M_>yaz-z839zqj)4hd>iX z5rn+PX96p8?8)8Kbd8XV_-&&0;oTKvjbDc1v#vsqZJrUEQ_*EVtEIRNT`u9dbj{OF zlqMK{rwT$v6 zG*hG6jPanBvLHesFrBUg-9LG=f#tfs{3Nvg^oXVVy62UrjnHvn&;S-?UiP?KZt1{5 z0R7IEgk#*Nt#`X5;Vxk-e@H-Y?e~p1(f-RJa;89^pSurU(!zc*%Nt}AZG=Bb@II`c zUV~L}=1B$fSQ?7|xYT<<@w_b4MR>?IQ%_3%@rL~a(u%Nmm?rIV5FAcYglnV;KL@-l zu1L5FYq;$4ovP#g^Y@Rl5oZ^Lc3cYK!PZpyQ%Ky<);DCdewF4E6@o#8!3n@RB>C~Z zhb+;3K&ExZ7MV0MwFMy~)%(+_(tyo~k1ux{sJ7j*1E3n;-S5d0QIJH64j{znkU3lb zau+6_bKv1{1!gvAKQFrX34U<}_aHqCw%D*4@8HQO)_z$4YC)n{S7Y7n09~} zt6BrGJV{BT-1#h4n%37=xD@RBSlE-D*@b+unPwGc=TCh9UZ0Ie*agej3-RiPx_^rmzt2+d84nqZ7L@_rjc_F^>l6Y{+z&)I}I8 zDi`<#Vi9131x=%sycRBW|4HR~5{5MfV~~1<`d%_6Oycx)Q~AOJ>$SRA0Q;gye_5R- z=%&qkAtY?t4>uESLQ|{4qtQ2=uI{Jk%JyTM8S8rEzrWv1Cg(#^aI_&3i1)iq|2|f? zfw7p(5kbQ%2wJI7l%&J&tTQOFOq~aaT(b-4CBd=($!mKUdSi?ClYdFyv3{v2pY*-z z8Q=j_pK~W`xmLFsK1R=0Gc{#BK1p6b=_@{rCtc$UvnRui2`YJi@d8iQavM0+1AHEZ zK%UkPACOdz!}Tn{!sl&;fm7)+zzYlN(&aC|;;hR_cIwqB1OJzurj;G$iNFFVgmNTf z{}6=k!SL4N!~fm`Z;7KKuEKf9aL+@504e+I1O4)IZeF`Vosh7G^Og00)DbYz80k8{ z3kv`%K~Cidv=CS68z(QG>+&u#5G*|ocC3D}JE7{$=aHFPSwH)Nt^|(^S83(noSJ5M zNO1L|cAvQ(`Qk@zw6v?mW#pVzq}(Dvd17_;6nf2y@sgaWpC3mEkX*R(JFoQ0r|4ye zvMp33SGCjpQEMR<%h94-_b0o=+_)cwZ{P-OVmep*iiYQ6&>wP&%AUXaFo+5jTQ;O; zvu6x~ljFuvopqlTX=6QT77J%cHAqh%K2)@D8`rJB%3_coYu=lZ@TBp63m<6u9x08h zM5*e#c7K;|<+FKBD)n;SYt?Nd_%~P2)mak+*Ig{t zaPXGg*z>G@M%Tz30{>?%UJms1x+2cp!VPR)Z;Ky?-QZ7vKlU>?J|?4Sse4{5dhlq0dO?Y-n{LEpx z{$*@ZhC75F9lnnbmttqu{i8e`*VmOj4=eKL^xrj0b9RB6A(nw5Tym#;1fOV11Bdo< zfYiVKQgw2n5wA7J<0^^miiJOMAjt`NXP@2n-i=$hRJjnRw4M2uGYb7bY7VsG26N=v zmREG8CY?Rd>@OiWq(wB;l<*HX9)z<+Zi*>HlNJfwf@)E*zcUa^9^)$A+D}(+7Two! zaPi_a=m3!T)-Jl?Xg|d{Eyj?q_8z)$9Y|XxCTY zdhlf%1&2m!NlmdmV$;r0e(GGxRfpp$asDE=e!ze?$T+v4@ zp0IPD0kY&w!M+!Xo8q(u=8rMe_;^-YNLltT<&445%}IBT9u6j)k@{q3{tS1%gYeR1+v|pO(c^Q z&=Qp4%d9Lha80L3BT>_w#&j!oC*d1aXz{jOcj#4=EDH7CIMdT4`SN&9s^o4NvrO;j z*AepNf0e~2Fh9xv;ppr_-qP4uW7HxI{;>q$Bum7u6nvYh$_9w3=(dS>#mDw1@r;ot z_8B9iq2zLYTRNcL(ESJFh~~p|DLlFHR>;kC6kijZ@>gg)F5&CeL9NigwXXV*F0x04 zH^TW2r1Ba~VDuO?gVyyCn#R88`nDZ0)j5G=l(m6NWk*ix_EYORxAly zw49cT1{OY5*+&9D@sKq%PaZ(eX=i_Y*ev_ z_Wl55q>2H@5f*Z>VJ`LdUw*BpEwl%&b3BNuRFK8G9XIS?8k8tgAw6{U1l7ECfL@U5 z(FrfQ3*P>cA6X8jL~d|=&?hA;`uB4yAOEd^lY9pz@YnM_#~Gs9NnLtie|Y(%2A{Jm zkQ=X8h;(rd9z%;I`jmw{Ij)klUgc1uuCEH~61DYyZ=8I26WY7BSk`2N&q%fXRWx?O zc52wyMCzST-q3|H%3}?=#LY2eM-#}0G+6DaV|8~-IJ@Y)(_-22>ireUbrmVK&X(Vj znVurYiWiXQpGEmk?IC*FTRoEar z8^i4FiU#92r}a`@Cdgf<#Hk;p40scsu;Mj>z^Z+ut#q?x&w<%p2rW#f0o&nnV?)rUn}%9a+||GN{2m_`s05ovukiSmrw!EN!>&$%jEPE0IN2=yUYy! z`SEtmzyz|$^iJ`SpMU*yLzJx-d)0g(0mhLF15fxfg#OmlQM*Q*u9f4wLvBvRtJmJ9 zA>( zE1U!hP`<=;>DA*T`|>VJFQ*B;U^skdjwOf5)BHF~z8qUjzbX5*4^6olJXcfdhbgvJ z+Z_)XGD+OG6l7jWm4s#r7axu&e!wYrS#%8l@y0)eDSQ_ohIM1q#xs=U)!-6~{`Ma9Tn}4*c#a~yk4hH_?iJC{NGM#&{D>Mrh zGRW*ptP?}p5wq$`+J}tgq_ucC$Hg+juSCw63^B}GM7BJWEdlM8i^}kT4}K4#h1JhQ zW{pVLcMwz8G6JV#dQ6_31W~PU$Qs}M?>8o zhudLzU>Tdmn((mRCVBxeYfECGm;WHhSd)GPNdS(WJ`FN`g;8r zENU#^9oIlIyK#n?hsWbE-OzJVW*WDQ6U^?O4|upb7FdlfW{^tTpF*L%KQ>4RH9tc- z&1OUmT1nukkL(@uJ(EmE-)k>vz>!b5Ng4J@ z&a&fH{XgRPb=qrd6TKix8AC-hi`A{0<(QT2dHV)PKS5^Y=nD5`YUb z5Sa?m*_x`F`>b%B$6TS>&%QF7v9z@u#LR<8LO~&-`L7m${8^zDZ05LdEwb&WLChCR zII+`)q#JmIUUN#xEM%HpT+p(o3g>3&6Nh1s+rHwv4m3YBg3DvG$V}hA3*6L?{#=ED zHG?>N6I_))>ApdQ488AfEtDXZ|CpL<;&}(yWnpdL+B;NsI@v$?sFpL7iGTMSYIPj{ zMr<*){YIV%5pD%qs=9GAN2GVl*muibwyn8*X85()3WnU>Uyd4gZjMx41ik_#(-vu2 zd~=VpICpVuep-1|9R{9s~eJiJm1)HQssvQGfDpcInu2>8*?cjN9q*|F>tgy820F*(cT zaBR1$Z$eBMj->*rSRT@l^H6ND4}l~mklS1A;5uxy@9QZm@V2$bo>MGwXL!1pkul`% z*FH?(Y72C=hBYafT%4&pE>F30uq!Jlp zaxVPl6gZM~hl+W9{#7#2E^oa-e95rW$}0Gcf8m+W)7o^m!hQzx-WV>++*Q=?`Rd{4 z%U+Y-N*uS=EE20{r)MhueFEOfdyp6n*2;aW6QI=Bp|zg>=l3|{N!#3oaM9R8zWf8= zR7?%+_>@m*+8$qRo&A27n;SFHem`CJ^H&~l+gj89ow#+Y-{tWWIlVZ+`y4uW;6*^C z)|X{b!U9Rt0FBqv&+G7wEjN13#iP|vq+VR>fm;&}>%lz-tmlgkk4>`&_EBPH54X0B zkE?jHEbuj!XipA?kSBCLvFa{F4?RXPJ=FnWv0fZZ$O!C4>AJeil%Pi893APnk$_zm z`sowH&~YJ+gXT8kc;==P(LSkTGc^m4hK^9U-u<)20;dCdJD?Srd0rD=0H@E#NzyPf zlf280@a)d57ZEK#-Xr5fty~Xt15YK}{j4psZ(?K00`%BJ)lN|KMHqkB=lXvjl{d`k zrvs6r=P&u|zjSPrQMa;J` zwRH-I3*E{RbTopS_iKCKm(0dzyl1l_hOFX7V~_0)7y5GXiFL^? zk*F7S1(2`iy$uO+9wm4~R$V{qnVAtu-fD5q)pe0ITLC1PIXjG^woWFD* zc*b{NReA_rS&=oWNW({f*Kz?TM3bA1O z8K1+k`@%bG{yZ9|vov@XbLoLT${H!LVNxpMev-^DPT!s6D zq|B7m%{CT)wQbk2xFYMJLazD<3qG9H44B2q6UV7Cb$lTLo&h=PcCbwEyB@{d2U~eJ zZaB%HQNn?pPQyJh+YKBiJ8>T>%e5^TWiVuj-j&CP?**I0?B;&?#)Y;MCxUmrwXnJ< zOd1`GFyRbkWHTC2%>(CjRv)b4<-}{h_1Uv0ls#)Y2D#tUYDQbtqSP6$`!h{UB-KAv z%YU5o_*u|+9yV)SXtc#VMX;J_Ini#uwjC0j&~;62vz-5$R4<6mjZT>CTYc)Jt;~Q9 zrG7A9>d+|wkGS&^c(HkW1__idX=FC5ktfEZ9P9i-CaY))Pj?YE?w)~B#!^4k=>D|6 zh;a#mxqWF69oUImdY-QT)+nYe^pYHD$O(EJL3v9t_j&ixRh#n-VJ3 z-_}Y`xJrhMid6G{qLWsQEfv9=iR!4hV!T^(j@Qr-SJ8C|#g1*g@+MlfVsq~tmtja$ zp)nUWFR9$Gt@GSEFtv!tyOm+6@xy<45HQlK(`ERI!TewJ*Bz|YZudEg-*z?0iz05x zOMRSV+bWd$V>E?1Pt%vlD>+FFW!9%Vz}Arx-;RcaIpfC)-Du)}UoLO8CQHKSomGBS zV9N22E!v!E$hhx@)zay44#WuW4Qfr6h7UQ*y^ntdZDkc(DVE*{>s#M#zLroe$Htns zlU{4ux$mxrRf_0&twRQ2Y#PZ`sCI~bO-)HX(lsXI$4cF3G<3v92mD?N*-J)`4G_`t znnzmgX|!isRkNT1M{vwJoGj5gJp&}n8*)Za`*fkaxB&w?Ose?M9Mj0ugh-F9{3on0 zu@iQX&Qpt{2XKwpO7ZI*>N5XS{g?4DZST%1b&MbJB2`R#z4#F6+p?w9JC;|>$3sIJ z^nr8k3k}8!!Rw1#;1cS28hnM|uR}3pz{S@g2ND%U-$D*zQ(i|qYK-Of`A0{`x}T4ZT+Hpw`8C%v23C(@e{+RHrEWEBi}H{qT@H}0;UF0m z`d-zQlbY}TC`ujaV@iwvI9BS5y8E52*0rG~f3 zW{#*U7qcNqO!N(}XG4CO9tnOxigyDP@m1(rLJr7Alk3WVB(v|)oAGIpNQId|8H;;| zhU@o%GWfgY!!=z)agdVnqaFDsYywiyoV&F&=qYj5iWeqItyb0mKmBpCVXXP#-u|o2 z-!$XprKkL!AigdMb$C6c%x;jt9>U42*-Sm7ob**wacLV8TxocK9f~} zl6BYnX6FPecC|>WQ?&4ur0ORRw6KXhWhb|#pJktieq2UG!_H^bOPy@b-=;}SEWkaA)1`OMcr}$-yt_Mu|C^N8(A zUDp>`hpa1TpXjDI0tL+qH_QtQW*b*xXWyCMZ@z$PjLW78BP!Kq;2CT>;c?8YoY1() zIU#R@4<*$}A_^eY1qpi3cFV>g4|%wL4y8OGYW>Q1JXc08Vk1v0i>e;kJ-^=oP;8^2 z@eos;)at9HNgPkF4&NH)$M0N^NUQ3X)5xvj*N-CPSBDd`nb7#>JYtkjUd0fN9#h+) z@V`@>4&Swa>o2RCx>H{I0Jnocq2mU}NREUJfJf#3pyd>s;n*B74A zpfuQ_(25;hWsYeE3z*udc6NN6Bfb9_@Xm%x9j*s%TJZ4hqT||yj^Pxf@4i^`WTNDa z7DcH-=cNE?4DkIEZC7IgMwp(?OY^9!D~B~U9L*t9*Ar&U;-D3x?VQT0k#czMx_jDjKR)DNb;6IiZ*_eKn3u u{~E}tLfu`EPNUwZx?VwjV4L}jMUU;5T6E!|i~L^!ikgzPVuQSO)c*hp@3`Rr literal 0 HcmV?d00001 From 4a391033eadb264ce5664b51119967bc9f121d24 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Tue, 16 Jul 2024 11:30:30 -0700 Subject: [PATCH 14/38] Added 'GoToFile' inline action for Reactor view elements; Moved 'openInSplitView' action for Reactor view elements --- package.json | 16 ++++++++++++---- src/lfview/lf-data-provider.ts | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 053eb46d3..0b1a38666 100644 --- a/package.json +++ b/package.json @@ -209,22 +209,30 @@ { "command": "linguafranca.goToFile", "group": "inline", - "when": "view == lf-lang-local && viewItem == file" + "when": "view == lf-lang-local && viewItem != root" }, { "command": "linguafranca.goToLibraryFile", "group": "inline", - "when": "view == lf-lang-library && viewItem == file" + "when": "view == lf-lang-library && viewItem != root" }, { "command": "linguafranca.openInSplitView", "group": "inline", - "when": "view == lf-lang-local && viewItem != root" + "when": "view == lf-lang-local && viewItem == file" }, { "command": "linguafranca.openLibraryInSplitView", "group": "inline", - "when": "view == lf-lang-library && viewItem != root" + "when": "view == lf-lang-library && viewItem == file" + }, + { + "command": "linguafranca.openInSplitView", + "when": "view == lf-lang-local && viewItem == reactor" + }, + { + "command": "linguafranca.openLibraryInSplitView", + "when": "view == lf-lang-library && viewItem == reactor" }, { "command": "linguafranca.importReactor", diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 577f1e996..83fbb330b 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -60,7 +60,7 @@ export class LFDataProviderNode extends vscode.TreeItem { this.contextValue = role; if (position) { this.position = position; } - this.registerNodeCommand(); + // this.registerNodeCommand(); } From 52e636da2d9602599a8db09c48b181c752855484 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Tue, 16 Jul 2024 12:29:32 -0700 Subject: [PATCH 15/38] Added LF_PACKAGE_EXPLORER.md --- LF_PACKAGE_EXPLORED.md | 101 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 LF_PACKAGE_EXPLORED.md diff --git a/LF_PACKAGE_EXPLORED.md b/LF_PACKAGE_EXPLORED.md new file mode 100644 index 000000000..8a9d3b91c --- /dev/null +++ b/LF_PACKAGE_EXPLORED.md @@ -0,0 +1,101 @@ +## Overview + +The idea involves displaying a Tree View within VS Code, presenting a comprehensive list of Libraries, specifically LF programs comprising multiple reactors, facilitating their seamless integration into our individual LF Programs. These libraries can originate from two sources: those crafted by the programmer within their local workspace (Local Libraries), and those downloaded using Lingo Package Manager (Lingo Libraries). + +## Tree View Composition + +The Tree View is organized within a view container named the 'Lingua Franca Package Explorer,' featuring two distinct sections: + +1. **Local Libraries**: This section showcases a catalog of Local Libraries, which are libraries personally defined by the programmer. +2. **Lingo Libraries**: Here, you'll find a catalog of all libraries downloaded in the personal workspace using the Lingo package manager. + +## Local Libraries View + +The Local Libraries view exclusively presents a listing of LF programs defined by the user and positioned within the designated path. After creating a LF Project, developers can incorporate LF Programs, akin to libraries of reactors, into a folder located at `{project_name}/lib/.` The Local Libraries view will then enumerate all LF Programs within this directory, typically represented by the pattern `/{project_name}/lib/*.lf` (though the path specification may vary). + +The view's hierarchy is structured as follows: + +``` +├── LF Project +│ ├── LF Program 1 +│ │ ├── Reactor 1 +│ │ └── Reactor 2 +│ ├── LF Program 2 +│ │ ├── Reactor 1 +└── └── └── Reactor 2 +``` + +The image below illustrates the Local view. In this depiction, the "root folder" icon represents the LF Project folder. The "code file" icon symbolizes the LF Program, and the "bracket" icon denotes individual reactors within the LF Program. + +Within the hierarchy, Tree Items can be classified into three types: + +1. **`root`**: Represents the LF Project folder. +2. **`file`**: Represents the LF Program. +3. **`reactor`**: Represents a reactor within the LF Program. + +Upon selection, both **`file`** and **`reactor`** items offer specific actions. From right to left, for the **`file`** item, we have the "Open in Split View" and "Go To File" commands. Similarly, for the **`reactor`** item, we have the "Import Selected Reactor" and "Go To File" options. While the "Open in Split View" and "Go To File" commands are self-explanatory, the "Import Selected Reactor" function inserts an import statement at the beginning of our LF program. However, this action is only available if there's an LF Program open in our active VS Code editor. Otherwise, a popup notification will inform us that there is no LF Program opened. Additionally, **`reactor`** items offer the ‘Open in Split View’ command, which can be accessed by right-clicking on the item + +### Lingo Libraries View + +If the developer instead of defining its own library, already found a ready-to-use LF Program on GitHub that exploit the same functionalities needed by the developer, the it can utilize the [Lingo Package Manager](https://github.com/lf-lang/lingo) to retrieve the remote LF Program and install it into a specified path within the developer's workspace. + +### Using Lingo Package Manager for GitHub Repo Downloads + +To effectively utilize Lingo for downloading a remote LF Project (GitHub repo), proper installation and configuration of Lingo for your LF Project are prerequisites. Detailed guidelines for this setup can be found at the following [link](https://www.lf-lang.org/blog/) and on [Lingo GitHub repo](https://github.com/lf-lang/lingo). Both the local and remote projects must be initialized with Lingo. + +Once Lingo has been properly initialized on the local side, programmers will discover a **`Lingo.toml`** file within its folder. An example of this file is the following: + +```makefile +[package] +name = "firstproject" +version = "0.1.0" + +[properties] + +[lib] +name = "firstproject" +main = "./src/first.lf" +target = "C" +platform = "Native" + +[lib.properties] + +[dependencies] + +library_1 = {version=">=0.1",git="https://github.com/path/to/repo.git"} +library_2 = {version="<=2.3",git="https://github.com/path/to/repo.git"} + +``` + +The **`Lingo.toml`** specifies a set of libs that are executable LF programs, crucial for remote files as it directs where to find the LF program containing the reactors. These apps can be configured with additional build and target properties. Under the **`[dependencies]`** section, developers can introduce the remote GitHub repos they deem useful for their project. Once all necessary remote repos are listed, simply execute **`lingo build`**, and Lingo will initiate the download of the listed dependencies. Developers can then find the libraries in **`{project_name}/target/lfc_include/`**, now accessible in the Local view of the VS Code Extension Tree View. + +### Lingo Libraries View Structure + +After being downloaded into the local workspace, the Lingo Libraries view is responsible for listing all the downloaded libraries, accessible at the following path: **`{project_name}/target/lfc_include/*.lf`**. The hierarchy resembles that of the Local Libraries view but introduces an additional level: + +``` +├── LF Project +│ ├── library_1 +│ │ ├── LF Program 1 +│ │ | ├── Reactor 1 +│ │ └── └── Reactor 2 +│ │ ├── LF Program 2 +│ │ | ├── Reactor 1 +│ │ └── └── Reactor 2 +│ ├── library_2 +│ │ ├── LF Program 1 +│ │ | ├── Reactor 1 +└── └── └── └── Reactor 2 +``` + +In this structure, the **`LF Project`** serves as the root LF Project, while **`library_x`** refer to the libraries the developer has inserted in the **`[dependencies]`** section of the **`.toml`** file. Each library functions as a conventional LF Project containing one or more LF Programs intended as libraries, i.e., they encompass lists of reactors that developers could include (Refer to the [RFC](https://github.com/lf-lang/rfcs/pull/11) to review the proposed LF project structure, including the libraries). + +The image below illustrates the Lingo Libraries view: the 'root folder' icon represents the LF Project folder and downloaded libraries (in the picture, the 'edgeai' library). The 'code file' icon symbolizes the LF Program within the downloaded library, while the 'bracket' icon denotes individual reactors within the LF Program. + +Within the hierarchy, similar to the Local View, Tree Items can be categorized into three types: + +1. **`root`**: Represents the LF Root Project folder and the downloaded library. +2. **`file`**: Represents the LF Program within the downloaded library. +3. **`reactor`**: Denotes a reactor within the LF Program. + +Upon selection, both **`file`** and **`reactor`** items offer specific actions, consistent with those described in the [Local Libraries view](#local-libraries-view). For **`file`** items, these actions include the 'Open in Split View' and 'Go To File' commands. For **`reactor`** items, the options include 'Go To File' and 'Import Selected Reactor'. Additionally, **`reactor`** items offer the 'Open in Split View' command, which can be accessed by right-clicking on the item. \ No newline at end of file From d3f849ea6e7a91ab1d269f4853d8e190d3900e8f Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:40:01 +0200 Subject: [PATCH 16/38] Update and rename LF_PACKAGE_EXPLORED.md to LF_PACKAGE_EXPLORER.md --- LF_PACKAGE_EXPLORED.md => LF_PACKAGE_EXPLORER.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) rename LF_PACKAGE_EXPLORED.md => LF_PACKAGE_EXPLORER.md (95%) diff --git a/LF_PACKAGE_EXPLORED.md b/LF_PACKAGE_EXPLORER.md similarity index 95% rename from LF_PACKAGE_EXPLORED.md rename to LF_PACKAGE_EXPLORER.md index 8a9d3b91c..4ea149ab4 100644 --- a/LF_PACKAGE_EXPLORED.md +++ b/LF_PACKAGE_EXPLORER.md @@ -27,6 +27,8 @@ The view's hierarchy is structured as follows: The image below illustrates the Local view. In this depiction, the "root folder" icon represents the LF Project folder. The "code file" icon symbolizes the LF Program, and the "bracket" icon denotes individual reactors within the LF Program. +Screenshot 2024-07-16 alle 12 36 19 + Within the hierarchy, Tree Items can be classified into three types: 1. **`root`**: Represents the LF Project folder. @@ -92,10 +94,12 @@ In this structure, the **`LF Project`** serves as the root LF Project, while **` The image below illustrates the Lingo Libraries view: the 'root folder' icon represents the LF Project folder and downloaded libraries (in the picture, the 'edgeai' library). The 'code file' icon symbolizes the LF Program within the downloaded library, while the 'bracket' icon denotes individual reactors within the LF Program. +Screenshot 2024-07-16 alle 12 26 39 + Within the hierarchy, similar to the Local View, Tree Items can be categorized into three types: 1. **`root`**: Represents the LF Root Project folder and the downloaded library. 2. **`file`**: Represents the LF Program within the downloaded library. 3. **`reactor`**: Denotes a reactor within the LF Program. -Upon selection, both **`file`** and **`reactor`** items offer specific actions, consistent with those described in the [Local Libraries view](#local-libraries-view). For **`file`** items, these actions include the 'Open in Split View' and 'Go To File' commands. For **`reactor`** items, the options include 'Go To File' and 'Import Selected Reactor'. Additionally, **`reactor`** items offer the 'Open in Split View' command, which can be accessed by right-clicking on the item. \ No newline at end of file +Upon selection, both **`file`** and **`reactor`** items offer specific actions, consistent with those described in the [Local Libraries view](#local-libraries-view). For **`file`** items, these actions include the 'Open in Split View' and 'Go To File' commands. For **`reactor`** items, the options include 'Go To File' and 'Import Selected Reactor'. Additionally, **`reactor`** items offer the 'Open in Split View' command, which can be accessed by right-clicking on the item. From 1d67e6924e8c65ad48cbda536dafa2dabebef842 Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:43:19 +0200 Subject: [PATCH 17/38] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae08592cd..63798ba08 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This extension adds language support for [Lingua Franca (LF)](https://www.lf-lan * target code validation upon build or file save * user-triggered build (Ctrl + Shift + P, then `Lingua Franca: Build`) * user-triggered build and run (Ctrl + Shift + P, then `Lingua Franca: Build and Run`) -* Lingua Franca Package Explorer (click on the LF icon in the Activity Bar on the left), which features two distinct tree-view sections: +* [Lingua Franca Package Explorer](LF_PACKAGE_EXPLORER.md) (click on the LF icon in the Activity Bar on the left), which features two distinct tree-view sections: * **Local Libraries:** This section showcases a catalog of Local Libraries, which are libraries personally defined by the programmer. * **Lingo Libraries:** Here, you'll find a catalog of all libraries downloaded in the personal workspace using the Lingo package manager. From 7ca960c8248cc77b1381fb96f10715026edfdccf Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:39:41 +0200 Subject: [PATCH 18/38] Update src/lfview/lf-data-provider.ts Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- src/lfview/lf-data-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 83fbb330b..89e393bed 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -108,7 +108,7 @@ export class LFDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter(); From c10ec70b3ccfa270bab1c2c4b0e6cbc11a7ceac1 Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:39:51 +0200 Subject: [PATCH 19/38] Update src/lfview/lf-data-provider.ts Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- src/lfview/lf-data-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 89e393bed..70f9dc7c9 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -269,7 +269,7 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { dataNode.children.forEach(child => { node.children.push(new LFDataProviderNode(child.label, From 0afcf177b2de1f19323ec4cd50bbe2a71165dd3c Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:40:07 +0200 Subject: [PATCH 20/38] Update src/build_commands.ts Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- src/build_commands.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/build_commands.ts b/src/build_commands.ts index 180397622..aabe469ed 100644 --- a/src/build_commands.ts +++ b/src/build_commands.ts @@ -1,7 +1,6 @@ import * as vscode from 'vscode'; import { LanguageClient } from 'vscode-languageclient'; import { getTerminal, MessageDisplayHelper } from './utils'; -import { LFDataProvider } from './lfview/lf-data-provider'; /** * Return the URI of the given document, if the document is a Lingua Franca file; else, return From f311bf3d41cd3b6d3d0fe93200641c3a852d7717 Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:40:38 +0200 Subject: [PATCH 21/38] Update LF_PACKAGE_EXPLORER.md Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- LF_PACKAGE_EXPLORER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LF_PACKAGE_EXPLORER.md b/LF_PACKAGE_EXPLORER.md index 4ea149ab4..b0138e922 100644 --- a/LF_PACKAGE_EXPLORER.md +++ b/LF_PACKAGE_EXPLORER.md @@ -47,7 +47,7 @@ To effectively utilize Lingo for downloading a remote LF Project (GitHub repo), Once Lingo has been properly initialized on the local side, programmers will discover a **`Lingo.toml`** file within its folder. An example of this file is the following: -```makefile +```toml [package] name = "firstproject" version = "0.1.0" From ec3e44d07f6da1cb2034c60c8a41204b5c52ea4e Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:41:28 +0200 Subject: [PATCH 22/38] Update src/extension.ts Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- src/extension.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index bff747b8b..8358d61ad 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -116,38 +116,6 @@ export async function activate(context: vscode.ExtensionContext) { registerCollapseAllCommand(context); registerCollapseAllLibraryCommand(context); - // context.subscriptions.push( - // vscode.workspace.onDidDeleteFiles(async (e) => { - // if (e.files.length > 0) { - // e.files.forEach( (file) => { - // if (file.path.endsWith('.lf')) { - // lfDataProviderLocal.refreshTree(); - // lfDataProviderLibrary.refreshTree(); - // } - // }); - // } - // }), - // vscode.workspace.onDidRenameFiles((e) => { - // if (e.files.length > 0) { - // e.files.forEach(async (file) => { - // if (file.newUri.path.endsWith('.lf')) { - // lfDataProviderLocal.refreshTree(); - // lfDataProviderLibrary.refreshTree(); - // } - // }); - // } - // }), - // vscode.workspace.onDidCreateFiles((e) => { - // if (e.files.length > 0) { - // e.files.forEach(async (file) => { - // if (file.path.endsWith('.lf')) { - // lfDataProviderLibrary.refreshTree(); - // } - // }); - // } - // }) - // ); - context.subscriptions.push(vscode.commands.registerCommand( "linguafranca.checkDocker", checkDependencies.checkDocker )); From d18dad35e67089da483e9c2876055c48b18757b9 Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:41:59 +0200 Subject: [PATCH 23/38] Update src/extension.ts Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- src/extension.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 8358d61ad..d00d99d51 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,15 +12,15 @@ import * as config from './config'; import { registerBuildCommands, registerNewFileCommand } from './build_commands'; import * as checkDependencies from './check_dependencies'; import { LFDataProvider, LFDataProviderNode, LFDataProviderNodeType} from './lfview/lf-data-provider'; -import { registerCollapseAllCommand, - registerCollapseAllLibraryCommand, - registerGoToFileCommand, - registerGoToLibraryFileCommand, - registerImportReactorCommand, - registerImportLibraryReactorCommand, - registerOpenInSplitViewCommand, - registerOpenLibraryInSplitViewCommand, - registerRefreshCommand, +import { registerCollapseAllCommand, + registerCollapseAllLibraryCommand, + registerGoToFileCommand, + registerGoToLibraryFileCommand, + registerImportReactorCommand, + registerImportLibraryReactorCommand, + registerOpenInSplitViewCommand, + registerOpenLibraryInSplitViewCommand, + registerRefreshCommand, registerRefreshLibraryCommand } from './lfview/lf-data-provider-commands'; import * as extensionVersion from './extension_version'; From 9a7438346e2b76ced7e35182250416808de989b3 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Wed, 17 Jul 2024 10:52:17 -0700 Subject: [PATCH 24/38] Resolved merge conflicts and fixed type errors --- package.json | 6 +++--- src/extension_version.ts | 2 +- src/lfview/lf-data-provider.ts | 36 +++++++++++++++++----------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 79f80fc30..f1732854d 100644 --- a/package.json +++ b/package.json @@ -281,14 +281,14 @@ "id": "lf-lang-library", "name": "Lingo Libraries" } - ], - "commandPalette": [ + ] + }, + "commandPalette": [ { "command": "linguafranca.getVersion", "when": "false" } ] - } }, "devDependencies": { "@types/chai": "^4.3.1", diff --git a/src/extension_version.ts b/src/extension_version.ts index f9ceee912..bde13d800 100644 --- a/src/extension_version.ts +++ b/src/extension_version.ts @@ -1,3 +1,3 @@ 'use strict'; // This is a generated file. Do not edit. -export const version = "85d09cd0ed28b3b85528b8a1ae6dfc5438cae07d"; +export const version = "df4ce25f16c01173e9d4c0ce64a3b2299b52505d"; diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 70f9dc7c9..4ec2edd1e 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -271,8 +271,8 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { - dataNode.children.forEach(child => { - node.children.push(new LFDataProviderNode(child.label, + dataNode.children.forEach((child: any) => { + node.children!.push(new LFDataProviderNode(child.label, child.uri, LFDataProviderNodeRole.REACTOR, this.type, [], @@ -292,8 +292,8 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { - dataNode.children.forEach(child => { - node.children.push(new LFDataProviderNode(child.label, + dataNode.children.forEach((child: any) => { + node.children!.push(new LFDataProviderNode(child.label, child.uri, LFDataProviderNodeRole.REACTOR, this.type, [], @@ -301,8 +301,8 @@ export class LFDataProvider implements vscode.TreeDataProvider n.label === node.label) === undefined) { - library_root.children.push(node); + if (library_root.children!.find(n => n.label === node.label) === undefined) { + library_root.children!.push(node); } this.sortData(); } @@ -327,13 +327,13 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { - node.children.sort((a, b) => { + if (node.children!.length > 0) { + node.children!.sort((a, b) => { const labelA = typeof a.label === 'string' ? a.label : a.uri.fsPath.split('/').pop() || ''; const labelB = typeof b.label === 'string' ? b.label : b.uri.fsPath.split('/').pop() || ''; return labelA.localeCompare(labelB); }); - node.children.forEach(n => this.sortNodes(n)); + node.children!.forEach(n => this.sortNodes(n)); } } @@ -366,11 +366,11 @@ export class LFDataProvider implements vscode.TreeDataProvider item.label === projectLabel); + const existingLibraryRoot = root.children!.find(item => item.label === projectLabel); if (!existingLibraryRoot) { const projectUri = splittedUri.slice(0, - this.path_offset + 3).join('/') + '/'; const library_root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.ROOT, this.type, []); - root.children.push(library_root); + root.children!.push(library_root); return library_root; } return existingLibraryRoot; @@ -410,9 +410,9 @@ export class LFDataProvider implements vscode.TreeDataProvider Date: Thu, 18 Jul 2024 13:54:58 -0700 Subject: [PATCH 25/38] Add proper types instead of using any; Moved commandPalette in the right position --- package.json | 14 +++++----- src/lfview/lf-data-provider.ts | 50 +++++++++++++--------------------- 2 files changed, 26 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index f1732854d..84c392988 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,12 @@ "command": "linguafranca.createNewFile" } ], + "commandPalette": [ + { + "command": "linguafranca.getVersion", + "when": "false" + } + ], "view/title": [ { "command": "linguafranca.refreshEntries", @@ -282,13 +288,7 @@ "name": "Lingo Libraries" } ] - }, - "commandPalette": [ - { - "command": "linguafranca.getVersion", - "when": "false" - } - ] + } }, "devDependencies": { "@types/chai": "^4.3.1", diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 4ec2edd1e..1262176d6 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -59,20 +59,6 @@ export class LFDataProviderNode extends vscode.TreeItem { this.iconPath = new vscode.ThemeIcon(icon); this.contextValue = role; if (position) { this.position = position; } - - // this.registerNodeCommand(); - - } - - /** - * Registers a command for the node based on its role and type. - */ - registerNodeCommand(): void { - this.command = this.role === LFDataProviderNodeRole.REACTOR ? { - title: "", - command: this.type == LFDataProviderNodeType.LOCAL ? "linguafranca.goToFile" : "linguafranca.goToLibraryFile", - arguments: [this] - } : undefined; } } @@ -241,7 +227,9 @@ export class LFDataProvider implements vscode.TreeDataProvider { uris.forEach(uri => { this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(node => { - this.addDataItem(node); + if (node) { + this.addDataItem(node as LFDataProviderNode); + } }); }); }); @@ -254,7 +242,7 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { - dataNode.children.forEach((child: any) => { - node.children!.push(new LFDataProviderNode(child.label, - child.uri, + if (dataNode.children!.length > 0) { + dataNode.children!.forEach((child: LFDataProviderNode) => { + node.children!.push(new LFDataProviderNode(child.label!.toString(), + child.uri.toString(), LFDataProviderNodeRole.REACTOR, this.type, [], child.position @@ -287,14 +275,14 @@ export class LFDataProvider implements vscode.TreeDataProvider 0) { - dataNode.children.forEach((child: any) => { - node.children!.push(new LFDataProviderNode(child.label, - child.uri, + addDataItemLibrary(dataNode: LFDataProviderNode) { + const root = this.buildRoot(dataNode.uri.toString()); + const library_root = this.buildLibraryRoot(dataNode.uri.toString(), root); + let node = new LFDataProviderNode(dataNode.label!.toString(), dataNode.uri.toString(), LFDataProviderNodeRole.FILE, this.type, []); + if (dataNode.children!.length > 0) { + dataNode.children!.forEach((child: LFDataProviderNode) => { + node.children!.push(new LFDataProviderNode(child.label!.toString(), + child.uri.toString(), LFDataProviderNodeRole.REACTOR, this.type, [], child.position From dfdb927c96f1602a5ecfc9de8e9e97cf835d06cd Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Fri, 19 Jul 2024 12:21:35 -0700 Subject: [PATCH 26/38] =?UTF-8?q?Improve=20reactor=20highlighting=20for=20?= =?UTF-8?q?=E2=80=9CGoToFile=E2=80=9D=20action;=20Enhanced=20insertion=20o?= =?UTF-8?q?f=20input=20statements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lfview/lf-data-provider.ts | 49 +++++++++++++--------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 1262176d6..9acf9d9fe 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -212,7 +212,9 @@ export class LFDataProvider implements vscode.TreeDataProvider { + this._onDidChangeTreeData.fire(element); + }) } } @@ -410,16 +412,7 @@ export class LFDataProvider implements vscode.TreeDataProvider { const document = editor.document; - const totalLines = document.lineCount; - - let startIndex = end; - let foundEmptyLine = false; - - while (startIndex < totalLines) { - const line = document.lineAt(startIndex); - if (line.isEmptyOrWhitespace) { - foundEmptyLine = true; - break; - } - startIndex++; - } - - if (foundEmptyLine) { - editor.edit(editBuilder => { - editBuilder.insert(new vscode.Position(startIndex, 0), importText); + try { + const success = await editor.edit(editBuilder => { + editBuilder.insert(new vscode.Position(idx, 0), importText); }); + if (success) { + await document.save(); + } + } catch (error) { + console.error('Failed to add text or save document:', error); } } From 7c60934d826c87cb782508669bfae26ce455eff6 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Tue, 20 Aug 2024 12:43:37 +0200 Subject: [PATCH 27/38] Refactored: Moved searchPath from lib/ to src/lib/; Added new error handlers for improved robustness --- src/extension_version.ts | 2 +- src/lfview/lf-data-provider.ts | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/extension_version.ts b/src/extension_version.ts index bde13d800..66e1d8dd3 100644 --- a/src/extension_version.ts +++ b/src/extension_version.ts @@ -1,3 +1,3 @@ 'use strict'; // This is a generated file. Do not edit. -export const version = "df4ce25f16c01173e9d4c0ce64a3b2299b52505d"; +export const version = "2e65200594b16d40b836b81aed3a244a488df151"; diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 9acf9d9fe..96b7186ef 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -120,8 +120,8 @@ export class LFDataProvider implements vscode.TreeDataProvider { - this._onDidChangeTreeData.fire(element); - }) + this._onDidChangeTreeData.fire(element); } } @@ -229,9 +227,17 @@ export class LFDataProvider implements vscode.TreeDataProvider { uris.forEach(uri => { this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(node => { - if (node) { + if(node){ this.addDataItem(node as LFDataProviderNode); } + else if(node === null){ + vscode.window.showErrorMessage('Error retrieving data from the Language Server', ...['Try again', 'Cancel']).then(selection => { + if(selection === 'Try again'){ + this.refreshTree(); + } + }); + return; + } }); }); }); @@ -455,7 +461,17 @@ export class LFDataProvider implements vscode.TreeDataProvider { return this.client.onReady().then(() => { - return this.client.sendRequest('generator/getTargetPosition', uri.toString()); + return this.client.sendRequest('generator/getTargetPosition', uri.toString()).then(position => { + if (!position) { + vscode.window.showErrorMessage('Error retrieving data from the Language Server', ...['Try Again', 'Cancel']).then(selection => { + if (selection == 'Try Again') { + return this.getTargetPosition(uri); + } + }); + return undefined; + } + return position as NodePosition; + }); }); } From 65a500efcfd25907b789b14d329ecffe6741722f Mon Sep 17 00:00:00 2001 From: Vincenzo Barbuto <63100303+vinzbarbuto@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:16:46 +0200 Subject: [PATCH 28/38] Update LF_PACKAGE_EXPLORER.md Co-authored-by: Marten Lohstroh --- LF_PACKAGE_EXPLORER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LF_PACKAGE_EXPLORER.md b/LF_PACKAGE_EXPLORER.md index b0138e922..2313bd5dd 100644 --- a/LF_PACKAGE_EXPLORER.md +++ b/LF_PACKAGE_EXPLORER.md @@ -11,7 +11,7 @@ The Tree View is organized within a view container named the 'Lingua Franca Pack ## Local Libraries View -The Local Libraries view exclusively presents a listing of LF programs defined by the user and positioned within the designated path. After creating a LF Project, developers can incorporate LF Programs, akin to libraries of reactors, into a folder located at `{project_name}/lib/.` The Local Libraries view will then enumerate all LF Programs within this directory, typically represented by the pattern `/{project_name}/lib/*.lf` (though the path specification may vary). +The Local Libraries view exclusively presents a listing of LF programs defined by the user and positioned within the designated path. After creating a LF Project, developers can incorporate LF Programs, akin to libraries of reactors, into a folder located at `{project_name}/src/lib/.` The Local Libraries view will then enumerate all LF Programs within this directory, typically represented by the pattern `/{project_name}/src/lib/*.lf` (though the path specification may vary). The view's hierarchy is structured as follows: From ef4da34275104ecc51ccafb85ab7953d1524736a Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Wed, 28 Aug 2024 10:45:59 +0200 Subject: [PATCH 29/38] Update Package Explorer description to reflect recent changes --- LF_PACKAGE_EXPLORER.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/LF_PACKAGE_EXPLORER.md b/LF_PACKAGE_EXPLORER.md index 2313bd5dd..dd3589734 100644 --- a/LF_PACKAGE_EXPLORER.md +++ b/LF_PACKAGE_EXPLORER.md @@ -11,7 +11,7 @@ The Tree View is organized within a view container named the 'Lingua Franca Pack ## Local Libraries View -The Local Libraries view exclusively presents a listing of LF programs defined by the user and positioned within the designated path. After creating a LF Project, developers can incorporate LF Programs, akin to libraries of reactors, into a folder located at `{project_name}/src/lib/.` The Local Libraries view will then enumerate all LF Programs within this directory, typically represented by the pattern `/{project_name}/src/lib/*.lf` (though the path specification may vary). +The Local Libraries view exclusively presents a listing of LF programs defined by the user and positioned within the designated path. After creating a LF Project, developers can incorporate LF Programs, akin to libraries of reactors, into a folder located at `{project_name}/src/lib/.` The Local Libraries view will then enumerate all LF Programs within this directory, typically represented by the pattern `/{project_name}/src/lib/*.lf` (check the [RFC](https://github.com/lf-lang/rfcs/blob/main/rfcs/0011-reusable-reactors-folder.md) for the proposed LF project structure, including the libraries). The view's hierarchy is structured as follows: @@ -35,7 +35,7 @@ Within the hierarchy, Tree Items can be classified into three types: 2. **`file`**: Represents the LF Program. 3. **`reactor`**: Represents a reactor within the LF Program. -Upon selection, both **`file`** and **`reactor`** items offer specific actions. From right to left, for the **`file`** item, we have the "Open in Split View" and "Go To File" commands. Similarly, for the **`reactor`** item, we have the "Import Selected Reactor" and "Go To File" options. While the "Open in Split View" and "Go To File" commands are self-explanatory, the "Import Selected Reactor" function inserts an import statement at the beginning of our LF program. However, this action is only available if there's an LF Program open in our active VS Code editor. Otherwise, a popup notification will inform us that there is no LF Program opened. Additionally, **`reactor`** items offer the ‘Open in Split View’ command, which can be accessed by right-clicking on the item +Upon selection, both `file` and `reactor` items offer specific actions. From right to left, for the `file` item, we have the "Open in Split View" and "Go To File" commands. Similarly, for the `reactor` item, we have the "Import Selected Reactor" and "Go To File" options. While the "Open in Split View" and "Go To File" commands are self-explanatory, the "Import Selected Reactor" function inserts an import statement at the beginning of a LF program. However, this action is only available if there's an LF Program open in our active VS Code editor. Otherwise, a popup notification will inform us that there is no LF Program opened. Additionally, `reactor` items offer the ‘Open in Split View’ command, which can be accessed by right-clicking on the item ### Lingo Libraries View @@ -45,7 +45,7 @@ If the developer instead of defining its own library, already found a ready-to-u To effectively utilize Lingo for downloading a remote LF Project (GitHub repo), proper installation and configuration of Lingo for your LF Project are prerequisites. Detailed guidelines for this setup can be found at the following [link](https://www.lf-lang.org/blog/) and on [Lingo GitHub repo](https://github.com/lf-lang/lingo). Both the local and remote projects must be initialized with Lingo. -Once Lingo has been properly initialized on the local side, programmers will discover a **`Lingo.toml`** file within its folder. An example of this file is the following: +Once Lingo has been properly initialized on the local side, programmers will discover a `Lingo.toml` file within its folder. An example of this file is the following: ```toml [package] @@ -69,11 +69,11 @@ library_2 = {version="<=2.3",git="https://github.com/path/to/repo.git"} ``` -The **`Lingo.toml`** specifies a set of libs that are executable LF programs, crucial for remote files as it directs where to find the LF program containing the reactors. These apps can be configured with additional build and target properties. Under the **`[dependencies]`** section, developers can introduce the remote GitHub repos they deem useful for their project. Once all necessary remote repos are listed, simply execute **`lingo build`**, and Lingo will initiate the download of the listed dependencies. Developers can then find the libraries in **`{project_name}/target/lfc_include/`**, now accessible in the Local view of the VS Code Extension Tree View. +The `Lingo.toml` specifies a set of libs that are executable LF programs, crucial for remote files as it directs where to find the LF program containing the reactors. These apps can be configured with additional build and target properties. Under the `[dependencies]` section, developers can introduce the remote GitHub repos they deem useful for their project. Once all necessary remote repos are listed, simply execute `lingo build`, and Lingo will initiate the download of the listed dependencies. Developers can then find the libraries at the following path `{project_name}/target/lfc_include/` in the local workspace. ### Lingo Libraries View Structure -After being downloaded into the local workspace, the Lingo Libraries view is responsible for listing all the downloaded libraries, accessible at the following path: **`{project_name}/target/lfc_include/*.lf`**. The hierarchy resembles that of the Local Libraries view but introduces an additional level: +After being downloaded into the local workspace, the Lingo Libraries view is responsible for listing all the downloaded libraries, accessible at the following path: `{project_name}/target/lfc_include/{library_name}/src/lib/*.lf`. The hierarchy resembles that of the Local Libraries view but introduces an additional level: ``` ├── LF Project @@ -90,16 +90,16 @@ After being downloaded into the local workspace, the Lingo Libraries view is res └── └── └── └── Reactor 2 ``` -In this structure, the **`LF Project`** serves as the root LF Project, while **`library_x`** refer to the libraries the developer has inserted in the **`[dependencies]`** section of the **`.toml`** file. Each library functions as a conventional LF Project containing one or more LF Programs intended as libraries, i.e., they encompass lists of reactors that developers could include (Refer to the [RFC](https://github.com/lf-lang/rfcs/pull/11) to review the proposed LF project structure, including the libraries). +In this structure, the `LF Project` serves as the root LF Project, while `library_x` refer to the root folder of the libraries the developer has inserted in the `[dependencies]` section of the `.toml` file. Each library functions as a conventional LF Project containing one or more LF Programs intended as libraries, i.e., they encompass lists of reactors that developers could include. -The image below illustrates the Lingo Libraries view: the 'root folder' icon represents the LF Project folder and downloaded libraries (in the picture, the 'edgeai' library). The 'code file' icon symbolizes the LF Program within the downloaded library, while the 'bracket' icon denotes individual reactors within the LF Program. +The image below illustrates the Lingo Libraries view: the ‘root folder’ icon represents the LF Project folder and the downloaded libraries, shown here as `AudioClassification` and the `edgeai` library, respectively.. The 'code file' icon symbolizes the LF Program within the downloaded library, while the 'bracket' icon denotes individual reactors within the LF Program. Screenshot 2024-07-16 alle 12 26 39 Within the hierarchy, similar to the Local View, Tree Items can be categorized into three types: -1. **`root`**: Represents the LF Root Project folder and the downloaded library. +1. **`root`**: Represents the LF Project’s root folder and the root folder of the downloaded library. 2. **`file`**: Represents the LF Program within the downloaded library. 3. **`reactor`**: Denotes a reactor within the LF Program. -Upon selection, both **`file`** and **`reactor`** items offer specific actions, consistent with those described in the [Local Libraries view](#local-libraries-view). For **`file`** items, these actions include the 'Open in Split View' and 'Go To File' commands. For **`reactor`** items, the options include 'Go To File' and 'Import Selected Reactor'. Additionally, **`reactor`** items offer the 'Open in Split View' command, which can be accessed by right-clicking on the item. +Upon selection, both `file` and `reactor` items offer specific actions, consistent with those described in the [Local Libraries view](#local-libraries-view). For `file` items, these actions include the 'Open in Split View' and 'Go To File' commands. For `reactor` items, the options include 'Go To File' and 'Import Selected Reactor'. Additionally, `reactor` items offer the 'Open in Split View' command, which can be accessed by right-clicking on the item. From 3deb8534d2d0ce863f10575b2c2b34b9ddd0873c Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Thu, 12 Sep 2024 10:49:31 -0700 Subject: [PATCH 30/38] Add support to new import statement --- syntaxes/lflang.tmLanguage.json | 136 +++++++++++++++++++++----------- 1 file changed, 89 insertions(+), 47 deletions(-) diff --git a/syntaxes/lflang.tmLanguage.json b/syntaxes/lflang.tmLanguage.json index f84312d83..9481ab7dd 100644 --- a/syntaxes/lflang.tmLanguage.json +++ b/syntaxes/lflang.tmLanguage.json @@ -118,15 +118,19 @@ }, "c-target": { "begin": "(?=\\btarget\\s+C\\b)", - "patterns": [{ - "include": "#c-or-cpp-target" - }] + "patterns": [ + { + "include": "#c-or-cpp-target" + } + ] }, "cpp-target": { "begin": "(?=\\btarget\\s+C?Cpp\\b)", - "patterns": [{ - "include": "#c-or-cpp-target" - }] + "patterns": [ + { + "include": "#c-or-cpp-target" + } + ] }, "c-or-cpp-target": { "patterns": [ @@ -181,9 +185,11 @@ "begin": "\\{=", "end": "(([<>]?[<>+\\-*/%&\\^|]?=\\}))", "name": "meta.embedded.block.cpp", - "patterns": [{ - "include": "source.cpp" - }] + "patterns": [ + { + "include": "source.cpp" + } + ] } ] }, @@ -197,9 +203,11 @@ "begin": "\\{=(?!=\\})", "end": "([<>]?[<>+\\-*/%&\\^|]?=\\})", "name": "meta.embedded.block.ts", - "patterns": [{ - "include": "source.ts" - }] + "patterns": [ + { + "include": "source.ts" + } + ] } ] }, @@ -217,10 +225,12 @@ { "begin": "\\b(?=import\\b)", "end": "(?==\\})", - "patterns": [{ - "name": "keyword.control.import.python", - "match": "\\b(import|as)\\b" - }] + "patterns": [ + { + "name": "keyword.control.import.python", + "match": "\\b(import|as)\\b" + } + ] }, { "include": "source.python" @@ -239,9 +249,11 @@ "begin": "\\{=(?!=\\})", "end": "([<>]?[<>+\\-*/%&\\^|]?=\\})", "name": "meta.embedded.block.rs", - "patterns": [{ - "include": "source.rust" - }] + "patterns": [ + { + "include": "source.rust" + } + ] } ] }, @@ -269,7 +281,7 @@ }, "import-statement": { "begin": "(?=^\\s*import\\b)", - "end": ";|(?<=\")\\s*$", + "end": ";|(?<=\")\\s*$|(?<=>)\\s*$", "patterns": [ { "include": "#context-insensitive" @@ -287,6 +299,20 @@ "match": "\\b\\w+\\b" } ] + }, + { + "begin": "(?<=from)\\s*(\"|<)", + "end": "(\"|>)", + "patterns": [ + { + "name": "string.quoted.double.lflang", + "match": "(?<=\").*?(?=\")" + }, + { + "name": "string.quoted.angle.lflang", + "match": "(?<=<).*?(?=>)" + } + ] } ] }, @@ -401,21 +427,27 @@ ] }, "nested-member-id": { - "patterns": [{ - "begin": "(?<=(\\binput\\b |\\boutput\\b|\\baction\\b|\\bstate\\b ))", - "end": "(?=[:\\(\\{;=\\n])", - "patterns": [{ - "name": "variable.other.lflang", - "match": "\\b(\\w+)\\b" - }] - }] + "patterns": [ + { + "begin": "(?<=(\\binput\\b |\\boutput\\b|\\baction\\b|\\bstate\\b ))", + "end": "(?=[:\\(\\{;=\\n])", + "patterns": [ + { + "name": "variable.other.lflang", + "match": "\\b(\\w+)\\b" + } + ] + } + ] }, "nested-member-type": { "begin": "(?<=:)", "end": "(?=[\\(\\{;=\\n])", - "patterns": [{ - "include": "#type" - }] + "patterns": [ + { + "include": "#type" + } + ] }, "type": { "patterns": [ @@ -447,33 +479,41 @@ "name": "string.quoted.triple.lflang", "begin": "'''", "end": "'''", - "patterns": [{ - "include": "#string-helper" - }] + "patterns": [ + { + "include": "#string-helper" + } + ] }, { "name": "string.quoted.triple.lflang", "begin": "\"\"\"", "end": "\"\"\"", - "patterns": [{ - "include": "#string-helper" - }] + "patterns": [ + { + "include": "#string-helper" + } + ] }, { "name": "string.quoted.double.lflang", "begin": "\"", "end": "(\"|$)", - "patterns": [{ - "include": "#string-helper" - }] + "patterns": [ + { + "include": "#string-helper" + } + ] }, { "name": "string.quoted.single.lflang", "begin": "'", "end": "('|$)", - "patterns": [{ - "include": "#string-helper" - }] + "patterns": [ + { + "include": "#string-helper" + } + ] } ] }, @@ -522,9 +562,11 @@ { "begin": "(?<=\\s*=\\s*new\\s*(\\[[^\\]]*\\])?\\s+\\w+\\s*)<", "end": ">", - "patterns": [{ - "include": "#type" - }] + "patterns": [ + { + "include": "#type" + } + ] } ] }, @@ -555,4 +597,4 @@ } ], "uuid": "cd57a55d-0af5-4750-a6a0-3b7108553c2a" -} +} \ No newline at end of file From a58711b731911a7d1c08504563fc25e200c10aa0 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Thu, 12 Sep 2024 11:17:17 -0700 Subject: [PATCH 31/38] Add support to new import statement v2 --- syntaxes/lflang.tmLanguage.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syntaxes/lflang.tmLanguage.json b/syntaxes/lflang.tmLanguage.json index 9481ab7dd..af4ae3827 100644 --- a/syntaxes/lflang.tmLanguage.json +++ b/syntaxes/lflang.tmLanguage.json @@ -281,7 +281,7 @@ }, "import-statement": { "begin": "(?=^\\s*import\\b)", - "end": ";|(?<=\")\\s*$|(?<=>)\\s*$", + "end": ";|(?<=\")\\s*$", "patterns": [ { "include": "#context-insensitive" From 6b5b2f7c277255b2f27149934ed865e6553f955f Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Thu, 12 Sep 2024 11:50:02 -0700 Subject: [PATCH 32/38] Add support to new import statement v3; Modify importReactorCommand string to support new statement; --- src/lfview/lf-data-provider.ts | 2 +- syntaxes/lflang.tmLanguage.json | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 96b7186ef..a3898bf8f 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -406,7 +406,7 @@ export class LFDataProvider implements vscode.TreeDataProvider\n`; const position = await this.getTargetPosition(editor.document.uri); this.addTextOnActiveEditor(editor, position!.end, importText); } diff --git a/syntaxes/lflang.tmLanguage.json b/syntaxes/lflang.tmLanguage.json index af4ae3827..c0c506124 100644 --- a/syntaxes/lflang.tmLanguage.json +++ b/syntaxes/lflang.tmLanguage.json @@ -281,7 +281,7 @@ }, "import-statement": { "begin": "(?=^\\s*import\\b)", - "end": ";|(?<=\")\\s*$", + "end": ";|(?<=\")\\s*$|(?<=>)\\s*$", "patterns": [ { "include": "#context-insensitive" @@ -301,13 +301,9 @@ ] }, { - "begin": "(?<=from)\\s*(\"|<)", - "end": "(\"|>)", + "begin": "(?<=from)\\s*(<)", + "end": "(>)", "patterns": [ - { - "name": "string.quoted.double.lflang", - "match": "(?<=\").*?(?=\")" - }, { "name": "string.quoted.angle.lflang", "match": "(?<=<).*?(?=>)" From 87b6bdf8bfa3540f86a65a7a7f119c69b3110ec5 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Tue, 24 Sep 2024 15:29:02 -0700 Subject: [PATCH 33/38] Added support for the new Lingo libraries import statement --- src/lfview/lf-data-provider-commands.ts | 2 +- src/lfview/lf-data-provider.ts | 49 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/lfview/lf-data-provider-commands.ts b/src/lfview/lf-data-provider-commands.ts index 1712ff3e5..6d1f541f1 100644 --- a/src/lfview/lf-data-provider-commands.ts +++ b/src/lfview/lf-data-provider-commands.ts @@ -101,7 +101,7 @@ export function registerImportReactorCommand(context: vscode.ExtensionContext, l export function registerImportLibraryReactorCommand(context: vscode.ExtensionContext, library: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( 'linguafranca.importLibraryReactor', async (node: LFDataProviderNode) => { - await library.importReactorCommand(node); + await library.importLibraryReactorCommand(node); } )); } diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index a3898bf8f..05f4b05f4 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -395,8 +395,11 @@ export class LFDataProvider implements vscode.TreeDataProvider\n`; const position = await this.getTargetPosition(editor.document.uri); this.addTextOnActiveEditor(editor, position!.end, importText); @@ -431,6 +455,29 @@ export class LFDataProvider implements vscode.TreeDataProvider Date: Sun, 29 Sep 2024 21:17:39 -0700 Subject: [PATCH 34/38] Removed LF_PACKAGE_EXPLORER.md --- LF_PACKAGE_EXPLORER.md | 105 ----------------------------------------- README.md | 2 +- 2 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 LF_PACKAGE_EXPLORER.md diff --git a/LF_PACKAGE_EXPLORER.md b/LF_PACKAGE_EXPLORER.md deleted file mode 100644 index dd3589734..000000000 --- a/LF_PACKAGE_EXPLORER.md +++ /dev/null @@ -1,105 +0,0 @@ -## Overview - -The idea involves displaying a Tree View within VS Code, presenting a comprehensive list of Libraries, specifically LF programs comprising multiple reactors, facilitating their seamless integration into our individual LF Programs. These libraries can originate from two sources: those crafted by the programmer within their local workspace (Local Libraries), and those downloaded using Lingo Package Manager (Lingo Libraries). - -## Tree View Composition - -The Tree View is organized within a view container named the 'Lingua Franca Package Explorer,' featuring two distinct sections: - -1. **Local Libraries**: This section showcases a catalog of Local Libraries, which are libraries personally defined by the programmer. -2. **Lingo Libraries**: Here, you'll find a catalog of all libraries downloaded in the personal workspace using the Lingo package manager. - -## Local Libraries View - -The Local Libraries view exclusively presents a listing of LF programs defined by the user and positioned within the designated path. After creating a LF Project, developers can incorporate LF Programs, akin to libraries of reactors, into a folder located at `{project_name}/src/lib/.` The Local Libraries view will then enumerate all LF Programs within this directory, typically represented by the pattern `/{project_name}/src/lib/*.lf` (check the [RFC](https://github.com/lf-lang/rfcs/blob/main/rfcs/0011-reusable-reactors-folder.md) for the proposed LF project structure, including the libraries). - -The view's hierarchy is structured as follows: - -``` -├── LF Project -│ ├── LF Program 1 -│ │ ├── Reactor 1 -│ │ └── Reactor 2 -│ ├── LF Program 2 -│ │ ├── Reactor 1 -└── └── └── Reactor 2 -``` - -The image below illustrates the Local view. In this depiction, the "root folder" icon represents the LF Project folder. The "code file" icon symbolizes the LF Program, and the "bracket" icon denotes individual reactors within the LF Program. - -Screenshot 2024-07-16 alle 12 36 19 - -Within the hierarchy, Tree Items can be classified into three types: - -1. **`root`**: Represents the LF Project folder. -2. **`file`**: Represents the LF Program. -3. **`reactor`**: Represents a reactor within the LF Program. - -Upon selection, both `file` and `reactor` items offer specific actions. From right to left, for the `file` item, we have the "Open in Split View" and "Go To File" commands. Similarly, for the `reactor` item, we have the "Import Selected Reactor" and "Go To File" options. While the "Open in Split View" and "Go To File" commands are self-explanatory, the "Import Selected Reactor" function inserts an import statement at the beginning of a LF program. However, this action is only available if there's an LF Program open in our active VS Code editor. Otherwise, a popup notification will inform us that there is no LF Program opened. Additionally, `reactor` items offer the ‘Open in Split View’ command, which can be accessed by right-clicking on the item - -### Lingo Libraries View - -If the developer instead of defining its own library, already found a ready-to-use LF Program on GitHub that exploit the same functionalities needed by the developer, the it can utilize the [Lingo Package Manager](https://github.com/lf-lang/lingo) to retrieve the remote LF Program and install it into a specified path within the developer's workspace. - -### Using Lingo Package Manager for GitHub Repo Downloads - -To effectively utilize Lingo for downloading a remote LF Project (GitHub repo), proper installation and configuration of Lingo for your LF Project are prerequisites. Detailed guidelines for this setup can be found at the following [link](https://www.lf-lang.org/blog/) and on [Lingo GitHub repo](https://github.com/lf-lang/lingo). Both the local and remote projects must be initialized with Lingo. - -Once Lingo has been properly initialized on the local side, programmers will discover a `Lingo.toml` file within its folder. An example of this file is the following: - -```toml -[package] -name = "firstproject" -version = "0.1.0" - -[properties] - -[lib] -name = "firstproject" -main = "./src/first.lf" -target = "C" -platform = "Native" - -[lib.properties] - -[dependencies] - -library_1 = {version=">=0.1",git="https://github.com/path/to/repo.git"} -library_2 = {version="<=2.3",git="https://github.com/path/to/repo.git"} - -``` - -The `Lingo.toml` specifies a set of libs that are executable LF programs, crucial for remote files as it directs where to find the LF program containing the reactors. These apps can be configured with additional build and target properties. Under the `[dependencies]` section, developers can introduce the remote GitHub repos they deem useful for their project. Once all necessary remote repos are listed, simply execute `lingo build`, and Lingo will initiate the download of the listed dependencies. Developers can then find the libraries at the following path `{project_name}/target/lfc_include/` in the local workspace. - -### Lingo Libraries View Structure - -After being downloaded into the local workspace, the Lingo Libraries view is responsible for listing all the downloaded libraries, accessible at the following path: `{project_name}/target/lfc_include/{library_name}/src/lib/*.lf`. The hierarchy resembles that of the Local Libraries view but introduces an additional level: - -``` -├── LF Project -│ ├── library_1 -│ │ ├── LF Program 1 -│ │ | ├── Reactor 1 -│ │ └── └── Reactor 2 -│ │ ├── LF Program 2 -│ │ | ├── Reactor 1 -│ │ └── └── Reactor 2 -│ ├── library_2 -│ │ ├── LF Program 1 -│ │ | ├── Reactor 1 -└── └── └── └── Reactor 2 -``` - -In this structure, the `LF Project` serves as the root LF Project, while `library_x` refer to the root folder of the libraries the developer has inserted in the `[dependencies]` section of the `.toml` file. Each library functions as a conventional LF Project containing one or more LF Programs intended as libraries, i.e., they encompass lists of reactors that developers could include. - -The image below illustrates the Lingo Libraries view: the ‘root folder’ icon represents the LF Project folder and the downloaded libraries, shown here as `AudioClassification` and the `edgeai` library, respectively.. The 'code file' icon symbolizes the LF Program within the downloaded library, while the 'bracket' icon denotes individual reactors within the LF Program. - -Screenshot 2024-07-16 alle 12 26 39 - -Within the hierarchy, similar to the Local View, Tree Items can be categorized into three types: - -1. **`root`**: Represents the LF Project’s root folder and the root folder of the downloaded library. -2. **`file`**: Represents the LF Program within the downloaded library. -3. **`reactor`**: Denotes a reactor within the LF Program. - -Upon selection, both `file` and `reactor` items offer specific actions, consistent with those described in the [Local Libraries view](#local-libraries-view). For `file` items, these actions include the 'Open in Split View' and 'Go To File' commands. For `reactor` items, the options include 'Go To File' and 'Import Selected Reactor'. Additionally, `reactor` items offer the 'Open in Split View' command, which can be accessed by right-clicking on the item. diff --git a/README.md b/README.md index 74a3fbea4..d7a0f316f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This extension adds language support for [Lingua Franca (LF)](https://www.lf-lan * target code validation upon build or file save * user-triggered build (Ctrl + Shift + P, then `Lingua Franca: Build`) * user-triggered build and run (Ctrl + Shift + P, then `Lingua Franca: Build and Run`) -* [Lingua Franca Package Explorer](LF_PACKAGE_EXPLORER.md) (click on the LF icon in the Activity Bar on the left), which features two distinct tree-view sections: +* Lingua Franca Package Explorer (click on the LF icon in the Activity Bar on the left), which features two distinct tree-view sections: * **Local Libraries:** This section showcases a catalog of Local Libraries, which are libraries personally defined by the programmer. * **Lingo Libraries:** Here, you'll find a catalog of all libraries downloaded in the personal workspace using the Lingo package manager. From 9295b1e325d1ff8806890703da6af97377daf7c8 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Wed, 2 Oct 2024 17:57:46 -0700 Subject: [PATCH 35/38] User interface redesign --- package.json | 120 +++--- src/extension.ts | 62 ++- src/extension_version.ts | 2 +- src/lfview/lf-data-provider-commands.ts | 110 ++--- src/lfview/lf-data-provider.ts | 526 ++++++++++++++++++------ 5 files changed, 513 insertions(+), 307 deletions(-) diff --git a/package.json b/package.json index ca815b8c1..098a25856 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,24 @@ } } ], + "colors": [ + { + "id": "editorIcon.currentProject", + "description": "Color for a TreeIteem label", + "defaults": { + "dark": "#57cc99", + "light": "#57cc99" + } + }, + { + "id": "editorIcon.notCurrentProject", + "description": "Color for a TreeIteem label", + "defaults": { + "dark": "#fcbf49", + "light": "#fcbf49" + } + } + ], "semanticTokenScopes": [ { "scopes": { @@ -100,54 +118,44 @@ "title": "Refresh Tree View", "icon": "$(refresh)" }, - { - "command": "linguafranca.refreshLibraryEntries", - "title": "Refresh Tree View", - "icon": "$(refresh)" - }, { "command": "linguafranca.goToFile", "title": "Go To Selected File", "icon": "$(go-to-file)" }, - { - "command": "linguafranca.goToLibraryFile", - "title": "Go To Selected File", - "icon": "$(go-to-file)" - }, { "command": "linguafranca.importReactor", "title": "Import Selected Reactor", "icon": "$(insert)" }, - { - "command": "linguafranca.importLibraryReactor", - "title": "Import Selected Reactor", - "icon": "$(insert)" - }, { "command": "linguafranca.openInSplitView", "title": "Open in Split View", "icon": "$(split-horizontal)" }, - { - "command": "linguafranca.openLibraryInSplitView", - "title": "Open in Split View", - "icon": "$(split-horizontal)" - }, { "command": "linguafranca.collapseAll", "title": "Collapse All", "icon": "$(collapse-all)" }, - { - "command": "linguafranca.collapseAllLibrary", - "title": "Collapse All", - "icon": "$(collapse-all)" - }, { "command": "linguafranca.getVersion", "title": "Lingua Franca: Get Version" + }, + { + "command": "linguafranca.includeProject", + "title": "Include in current project", + "icon": "$(desktop-download)" + }, + { + "command": "linguafranca.goToLingoToml", + "title": "Go to Lingo.toml", + "icon": "$(edit)" + }, + { + "command": "linguafranca.openInTerminal", + "title": "Open in Terminal", + "icon": "$(terminal)" } ], @@ -208,63 +216,49 @@ { "command": "linguafranca.refreshEntries", "group": "navigation@1", - "when": "view == lf-lang-local" - }, - { - "command": "linguafranca.refreshLibraryEntries", - "group": "navigation@1", - "when": "view == lf-lang-library" + "when": "view == lf-lang-projects" }, { "command": "linguafranca.collapseAll", "group": "navigation@2", - "when": "view == lf-lang-local" - }, - { - "command": "linguafranca.collapseAllLibrary", - "group": "navigation@2", - "when": "view == lf-lang-library" + "when": "view == lf-lang-projects" } ], "view/item/context" : [ { - "command": "linguafranca.goToFile", - "group": "inline", - "when": "view == lf-lang-local && viewItem != root" + "command": "linguafranca.openInTerminal", + "group": "inline@1", + "when": "viewItem == project" }, { - "command": "linguafranca.goToLibraryFile", - "group": "inline", - "when": "view == lf-lang-library && viewItem != root" - }, - { - "command": "linguafranca.openInSplitView", - "group": "inline", - "when": "view == lf-lang-local && viewItem == file" + "command": "linguafranca.includeProject", + "group": "inline@1", + "when": "viewItem == root || viewItem == file-local" }, { - "command": "linguafranca.openLibraryInSplitView", - "group": "inline", - "when": "view == lf-lang-library && viewItem == file" + "command": "linguafranca.goToFile", + "group": "inline@2", + "when": "viewItem == file-local || viewItem == file-lingo || viewItem == file-local-included || viewItem == reactor || viewItem == reactor-included" }, { "command": "linguafranca.openInSplitView", - "when": "view == lf-lang-local && viewItem == reactor" + "group": "inline@3", + "when": "viewItem == file-local || viewItem == file-lingo || viewItem == file-local-included" }, { - "command": "linguafranca.openLibraryInSplitView", - "when": "view == lf-lang-library && viewItem == reactor" + "command": "linguafranca.openInSplitView", + "when": "viewItem == reactor || viewItem == reactor-included" }, { "command": "linguafranca.importReactor", - "group": "inline", - "when": "view == lf-lang-local && viewItem == reactor" + "group": "inline@3", + "when": "viewItem == reactor-included" }, { - "command": "linguafranca.importLibraryReactor", + "command": "linguafranca.goToLingoToml", "group": "inline", - "when": "view == lf-lang-library && viewItem == reactor" + "when": "viewItem == lingo" } ] }, @@ -272,7 +266,7 @@ "activitybar": [ { "id": "lf-lang", - "title": "Lingua Franca Package Explorer", + "title": "Lingua Franca Projects", "icon": "images/logo/lf-logo-dark.svg" } ] @@ -280,12 +274,8 @@ "views": { "lf-lang": [ { - "id": "lf-lang-local", - "name": "Local Libraries" - }, - { - "id": "lf-lang-library", - "name": "Lingo Libraries" + "id": "lf-lang-projects", + "name": "" } ] } diff --git a/src/extension.ts b/src/extension.ts index d00d99d51..983aa67ab 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,15 +13,13 @@ import { registerBuildCommands, registerNewFileCommand } from './build_commands' import * as checkDependencies from './check_dependencies'; import { LFDataProvider, LFDataProviderNode, LFDataProviderNodeType} from './lfview/lf-data-provider'; import { registerCollapseAllCommand, - registerCollapseAllLibraryCommand, registerGoToFileCommand, - registerGoToLibraryFileCommand, + registerGoToLingoTomlCommand, registerImportReactorCommand, - registerImportLibraryReactorCommand, + registerIncludeProjectCommand, registerOpenInSplitViewCommand, - registerOpenLibraryInSplitViewCommand, - registerRefreshCommand, - registerRefreshLibraryCommand } from './lfview/lf-data-provider-commands'; + registerOpenInTerminalCommand, + registerRefreshCommand} from './lfview/lf-data-provider-commands'; import * as extensionVersion from './extension_version'; let client: LanguageClient; @@ -81,40 +79,26 @@ export async function activate(context: vscode.ExtensionContext) { registerNewFileCommand(context); // Registers a tree data provider and creates a tree view for the 'lf-lang-local' view - const lfDataProviderLocal = new LFDataProvider(LFDataProviderNodeType.LOCAL, client, context); - context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-local', lfDataProviderLocal)); - const localTreeView = vscode.window.createTreeView('lf-lang-local', { treeDataProvider: lfDataProviderLocal }); - context.subscriptions.push(localTreeView); - localTreeView.onDidExpandElement(element => { - lfDataProviderLocal.onExpandEvent(element.element); + const lfDataProvider = new LFDataProvider(client, context); + context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-projects', lfDataProvider)); + const projectsTreeView = vscode.window.createTreeView('lf-lang-projects', { treeDataProvider: lfDataProvider }); + context.subscriptions.push(projectsTreeView); + projectsTreeView.onDidExpandElement(element => { + lfDataProvider.onExpandEvent(element.element); }); - localTreeView.onDidCollapseElement(element => { - lfDataProviderLocal.onCollapseEvent(element.element); + projectsTreeView.onDidCollapseElement(element => { + lfDataProvider.onCollapseEvent(element.element); }); - // Registers a tree data provider and creates a tree view for the 'lf-lang-library' view - const lfDataProviderLibrary = new LFDataProvider(LFDataProviderNodeType.LIBRARY, client, context); - context.subscriptions.push(vscode.window.registerTreeDataProvider('lf-lang-library', lfDataProviderLibrary)); - const libraryTreeView = vscode.window.createTreeView('lf-lang-library', { treeDataProvider: lfDataProviderLibrary }); - context.subscriptions.push(libraryTreeView); - libraryTreeView.onDidExpandElement(element => { - lfDataProviderLibrary.onExpandEvent(element.element); - }); - libraryTreeView.onDidCollapseElement(element => { - lfDataProviderLibrary.onCollapseEvent(element.element); - }); - - // Register all the commands - registerRefreshCommand(context, lfDataProviderLocal); - registerRefreshLibraryCommand(context, lfDataProviderLibrary); - registerGoToFileCommand(context, lfDataProviderLocal); - registerGoToLibraryFileCommand(context, lfDataProviderLibrary); - registerOpenInSplitViewCommand(context, lfDataProviderLocal); - registerOpenLibraryInSplitViewCommand(context, lfDataProviderLibrary); - registerImportReactorCommand(context, lfDataProviderLocal); - registerImportLibraryReactorCommand(context, lfDataProviderLibrary); + // // Register all the commands + registerRefreshCommand(context, lfDataProvider); + registerGoToFileCommand(context, lfDataProvider); + registerOpenInSplitViewCommand(context, lfDataProvider); + registerImportReactorCommand(context, lfDataProvider); registerCollapseAllCommand(context); - registerCollapseAllLibraryCommand(context); + registerGoToLingoTomlCommand(context, lfDataProvider); + registerIncludeProjectCommand(context, lfDataProvider); + registerOpenInTerminalCommand(context); context.subscriptions.push(vscode.commands.registerCommand( "linguafranca.checkDocker", checkDependencies.checkDocker @@ -122,6 +106,12 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand( "linguafranca.getVersion", () => extensionVersion.version )); + + vscode.window.onDidChangeActiveTextEditor( editor => { + if (editor){ + lfDataProvider.onChangeActiveEditor(); + } + }) } /** diff --git a/src/extension_version.ts b/src/extension_version.ts index 66e1d8dd3..72bec68db 100644 --- a/src/extension_version.ts +++ b/src/extension_version.ts @@ -1,3 +1,3 @@ 'use strict'; // This is a generated file. Do not edit. -export const version = "2e65200594b16d40b836b81aed3a244a488df151"; +export const version = "0aa241b1a6750887e50c4ae04a1a4b74826a7f31"; diff --git a/src/lfview/lf-data-provider-commands.ts b/src/lfview/lf-data-provider-commands.ts index 6d1f541f1..af6c6ddc8 100644 --- a/src/lfview/lf-data-provider-commands.ts +++ b/src/lfview/lf-data-provider-commands.ts @@ -1,29 +1,16 @@ import * as vscode from 'vscode'; import { LFDataProvider, LFDataProviderNode, LFDataProviderNodeType } from './lf-data-provider'; +import { getTerminal } from '../utils'; /** * Registers a command to refresh the local LF libraries tree view. * @param context - The extension context. - * @param local - The LFDataProvider instance managing local LF libraries. + * @param provider - The LFDataProvider instance. */ -export function registerRefreshCommand(context: vscode.ExtensionContext, local: LFDataProvider) { +export function registerRefreshCommand(context: vscode.ExtensionContext, provider: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( 'linguafranca.refreshEntries', () => { - local.refreshTree(); - } - )); -} - - -/** -* Registers a command to refresh the Lingo downloaded LF libraries tree view. -* @param context - The extension context. -* @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. -*/ -export function registerRefreshLibraryCommand(context: vscode.ExtensionContext, library: LFDataProvider) { - context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.refreshLibraryEntries', () => { - library.refreshTree(); + provider.refreshTree(); } )); } @@ -31,25 +18,12 @@ export function registerRefreshLibraryCommand(context: vscode.ExtensionContext, /** * Registers a command to navigate to a file in the local LF libraries tree view. * @param context - The extension context. - * @param local - The LFDataProvider instance managing local LF libraries. + * @param provider - The LFDataProvider instance. */ -export function registerGoToFileCommand(context: vscode.ExtensionContext, local: LFDataProvider) { +export function registerGoToFileCommand(context: vscode.ExtensionContext, provider: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( 'linguafranca.goToFile', (node: LFDataProviderNode) => { - local.goToFileCommand(node, false); - } - )); -} - -/** - * Registers a command to navigate to a file in the Lingo downloaded LF libraries tree view. - * @param context - The extension context. - * @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. - */ -export function registerGoToLibraryFileCommand(context: vscode.ExtensionContext, library: LFDataProvider) { - context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.goToLibraryFile', (node: LFDataProviderNode) => { - library.goToFileCommand(node, false); + provider.goToFileCommand(node, false); } )); } @@ -57,76 +31,74 @@ export function registerGoToLibraryFileCommand(context: vscode.ExtensionContext, /** * Registers a command to open a file in split view in the local LF libraries tree view. * @param context - The extension context. - * @param local - The LFDataProvider instance managing local LF libraries. + * @param provider - The LFDataProvider instance. */ -export function registerOpenInSplitViewCommand(context: vscode.ExtensionContext, local: LFDataProvider) { +export function registerOpenInSplitViewCommand(context: vscode.ExtensionContext, provider: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( 'linguafranca.openInSplitView', (node: LFDataProviderNode) => { - local.goToFileCommand(node, true); + provider.goToFileCommand(node, true); } )); } /** - * Registers a command to open a file in split view in the Lingo downloaded LF libraries tree view. + * Registers a command to import a reactor from the local LF libraries into the active LF program. * @param context - The extension context. - * @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. + * @param provider - The LFDataProvider instance. */ -export function registerOpenLibraryInSplitViewCommand(context: vscode.ExtensionContext, library: LFDataProvider) { +export function registerImportReactorCommand(context: vscode.ExtensionContext, provider: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.openLibraryInSplitView', (node: LFDataProviderNode) => { - library.goToFileCommand(node, true); + 'linguafranca.importReactor', async (node: LFDataProviderNode) => { + if(node.type === LFDataProviderNodeType.LOCAL) { + await provider.importReactorCommand(node); + } + else { + await provider.importLibraryReactorCommand(node); + } } )); } /** - * Registers a command to import a reactor from the local LF libraries into the active LF program. + * Registers a command to collapse all nodes in the local LF libraries tree view. * @param context - The extension context. - * @param local - The LFDataProvider instance managing local LF libraries. */ -export function registerImportReactorCommand(context: vscode.ExtensionContext, local: LFDataProvider) { +export function registerCollapseAllCommand(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.importReactor', async (node: LFDataProviderNode) => { - await local.importReactorCommand(node); + 'linguafranca.collapseAll', () => { + vscode.commands.executeCommand('workbench.actions.treeView.lf-lang-projects.collapseAll'); } )); } -/** - * Registers a command to import a reactor from the Lingo downloaded LF libraries into the active LF program. - * @param context - The extension context. - * @param library - The LFDataProvider instance managing Lingo downloaded LF libraries. - */ -export function registerImportLibraryReactorCommand(context: vscode.ExtensionContext, library: LFDataProvider) { +export function registerGoToLingoTomlCommand(context: vscode.ExtensionContext, provider: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.importLibraryReactor', async (node: LFDataProviderNode) => { - await library.importLibraryReactorCommand(node); + 'linguafranca.goToLingoToml', (node: LFDataProviderNode) => { + provider.goToLingoTomlCommand(node); } )); } -/** - * Registers a command to collapse all nodes in the local LF libraries tree view. - * @param context - The extension context. - * @param local - The LFDataProvider instance managing local LF libraries. - */ -export function registerCollapseAllCommand(context: vscode.ExtensionContext) { +export function registerIncludeProjectCommand(context: vscode.ExtensionContext, provider: LFDataProvider) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.collapseAll', () => { - vscode.commands.executeCommand('workbench.actions.treeView.lf-lang-local.collapseAll'); + 'linguafranca.includeProject', (node: LFDataProviderNode) => { + vscode.window.showInformationMessage('The "Include Project" feature is not implemented yet.', 'Details').then(selection => { + if (selection === "Details") { + vscode.window.showInformationMessage('Please use the Lingo command line to include the selected library in your current project. Once included, the library will appear under the "Lingo Packages" section.'); + } + }); } )); } -/** - * Registers a command to collapse all nodes in the Lingo downloaded LF libraries tree view. - * @param context - The extension context. - */ -export function registerCollapseAllLibraryCommand(context: vscode.ExtensionContext) { +export function registerOpenInTerminalCommand(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand( - 'linguafranca.collapseAllLibrary', () => { - vscode.commands.executeCommand('workbench.actions.treeView.lf-lang-library.collapseAll'); + 'linguafranca.openInTerminal', (node: LFDataProviderNode) => { + const terminal = getTerminal("Lingua Franca") + if (terminal) { + terminal.show(true); + terminal.sendText(`cd ${node.uri.fsPath} && clear`); + } } )); } diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 05f4b05f4..274bb0276 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -4,25 +4,34 @@ import { LanguageClient } from 'vscode-languageclient'; /** * Defines the different roles of nodes that can be displayed in the LFDataProvider tree view. - * {@code ROOT}: Represents the root node of the tree view. - * {@code FILE}: Represents a file node in the tree view. - * {@code REACTOR}: Represents a reactor node in the tree view. + * + * @property PROJECT - Represents the project node of the tree view. + * @property ROOT - Represents the root node of a lingo library + * @property SUB - Represents a sub-directory within the project, which may contain local libraries, Lingo packages, or source files. + * @property SRC - Represents a Lingua Franca file located in the project’s `src` directory. + * @property FILE - Represents a file node in the tree view. + * @property REACTOR - Represents a reactor node in the tree view. */ export enum LFDataProviderNodeRole { + PROJECT = 'project', ROOT = 'root', + SUB = 'sub', + SRC = 'src', FILE = 'file', REACTOR = 'reactor' } /** - * Defines the types of the displayed data provider tree view. + * Defines the different types of nodes that can be displayed in the LFDataProvider tree view. * - * {@code LOCAL} represents nodes for local LF libraries. - * {@code LIBRARY} represents nodes for LF libraries downloaded using Lingo. + * @property LOCAL - Represents a local library node. + * @property LIBRARY - Represents a library node. + * @property SOURCE - Represents a source file node. */ export enum LFDataProviderNodeType { LOCAL = 1, - LIBRARY = 2 + LIBRARY = 2, + SOURCE = 3 } /** @@ -39,7 +48,7 @@ export class LFDataProviderNode extends vscode.TreeItem { children: LFDataProviderNode[] | undefined; role: string; position: NodePosition | undefined; - type: LFDataProviderNodeType; + type: LFDataProviderNodeType | undefined; /** * Represents the URI of the data provider node. @@ -47,18 +56,141 @@ export class LFDataProviderNode extends vscode.TreeItem { */ uri: vscode.Uri; - constructor(label: string, uri: string, role: string, type: LFDataProviderNodeType, - children?: LFDataProviderNode[] | undefined, position?: NodePosition | undefined) { - let newLabel = label.replace('.lf', ''); - super(newLabel, role === LFDataProviderNodeRole.REACTOR ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); + constructor(label: string, uri: string, role: string, + type?: LFDataProviderNodeType | undefined, + children?: LFDataProviderNode[] | undefined, + position?: NodePosition | undefined) { + let newLabel = type === LFDataProviderNodeType.SOURCE ? label : label.replace('.lf', ''); + super(newLabel, role === LFDataProviderNodeRole.REACTOR || + (role === LFDataProviderNodeRole.FILE && type === LFDataProviderNodeType.SOURCE) + ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); this.uri = vscode.Uri.parse(uri); this.children = children; this.role = role; this.type = type; - let icon = role === LFDataProviderNodeRole.ROOT ? 'root-folder' : role === LFDataProviderNodeRole.FILE ? 'file-code' : 'json'; - this.iconPath = new vscode.ThemeIcon(icon); - this.contextValue = role; + this.updateIcon(role, type); + this.updateContextValue(role, type); if (position) { this.position = position; } + if(role === LFDataProviderNodeRole.FILE && type === LFDataProviderNodeType.SOURCE){ + this.command = { + title: "Go to File", + command: "vscode.open", + arguments: [this.uri] + } + } + } + + /** + * Determines the appropriate icon to display for a node in the LFDataProvider tree based on its role and type. + * + * @param role - The role of the node (e.g. project, root, file, reactor, sub). + * @param type - The type of the node (e.g. local, library, source). + * @returns The name of the icon to display for the node. + */ + updateIcon(role: string, type?: LFDataProviderNodeType): void { + const sameRootAsEditor = this.haveSameRootWithActiveEditor(); + let newIcon = ''; + + switch (role) { + case LFDataProviderNodeRole.PROJECT: + newIcon = 'project'; + break; + case LFDataProviderNodeRole.ROOT: + newIcon = 'root-folder'; + break; + case LFDataProviderNodeRole.FILE: + newIcon = 'file-code'; + break; + case LFDataProviderNodeRole.REACTOR: + newIcon = 'json'; + break; + case LFDataProviderNodeRole.SUB: + switch (type) { + case LFDataProviderNodeType.LOCAL: + newIcon = 'book'; + break; + case LFDataProviderNodeType.LIBRARY: + newIcon = 'library'; + break; + case LFDataProviderNodeType.SOURCE: + newIcon = 'circuit-board'; + break; + default: + newIcon = 'default-icon'; // fallback for unknown types + } + break; + default: + newIcon = 'default-icon'; // fallback for unknown roles + } + + this.iconPath = new vscode.ThemeIcon( + newIcon, + sameRootAsEditor + ? new vscode.ThemeColor('editorIcon.currentProject') + : new vscode.ThemeColor('editorIcon.notCurrentProject') + ); + } + + /** + * Updates the context value of the LFDataProviderNode based on its role and type. + * The context value is used to determine the appropriate visual representation of the node in the tree view. + * + * @param role - The role of the node (e.g. project, root, file, reactor, sub). + * @param type - The type of the node (e.g. local, library, source). + */ + updateContextValue(role: string, type?: LFDataProviderNodeType): void { + const sameRootAsEditor = this.haveSameRootWithActiveEditor(); + + let value: string = role; + + switch (role) { + case LFDataProviderNodeRole.ROOT: + value = sameRootAsEditor ? 'root-included' : 'root'; + break; + case LFDataProviderNodeRole.SUB: + if (type === LFDataProviderNodeType.LIBRARY) { + value = 'lingo'; + } + break; + case LFDataProviderNodeRole.FILE: + if (type === LFDataProviderNodeType.LOCAL) { + value = sameRootAsEditor ? 'file-local-included' : 'file-local'; + } else if (type === LFDataProviderNodeType.LIBRARY) { + value = 'file-lingo'; + } + break; + case LFDataProviderNodeRole.REACTOR: + value = sameRootAsEditor ? 'reactor-included' : 'reactor'; + break; + } + + this.contextValue = value; + } + + /** + * Determines whether the current node's root path is the same as the active editor's root path. + * This is used to determine the appropriate context value for the node in the tree view. + * + * @returns `true` if the current node's root path is the same as the active editor's root path, `false` otherwise. + */ + haveSameRootWithActiveEditor(): boolean { + const editor = vscode.window.activeTextEditor; + + if (!editor || !editor.document) { + return false; + } + if(this.role === LFDataProviderNodeRole.PROJECT){ + return editor.document.uri.fsPath.startsWith(this.uri.fsPath); + } + const pathSegments = this.uri.fsPath.split('/'); + const srcOrBuildIndex = pathSegments.indexOf(this.type === LFDataProviderNodeType.LIBRARY ? 'build' : 'src'); + + if (srcOrBuildIndex === -1) { + return false; + } + + const rootPath = pathSegments.slice(0, srcOrBuildIndex).join('/'); + return editor.document.uri.fsPath.startsWith(rootPath); } } @@ -83,25 +215,22 @@ export class NodePosition { */ export class LFDataProvider implements vscode.TreeDataProvider { - // Offset used to highlight text in goToFile Command - private HIGHLIGHT_OFFSET = 100; - // LF libraries data private data: LFDataProviderNode[] = []; - // Type of the data provider - private type: LFDataProviderNodeType; // Utility properties - private searchPath: string; - private path_offset: number; - private exclude_path: vscode.GlobPattern | null = null; + private searchSourceFiles: string = '**/src/*.lf'; + private searchPathLocal: string = '**/src/lib/*.lf'; + private searchPathLibrary: vscode.GlobPattern = '**/build/lfc_include/**/src/lib/*.lf'; + private exclude_path_local: vscode.GlobPattern = '**/build/**'; // only for local LF libraries + private exclude_path_src: vscode.GlobPattern = `{${this.exclude_path_local},**/fed-gen/**,**/src-gen/***}` // Event emitter for tree data change private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; // Watches for changes to .lf files - private watcher: vscode.FileSystemWatcher; + private watcher: vscode.FileSystemWatcher | undefined; /** * Constructs a new LFDataProvider instance with the given type, client, and extension context. @@ -109,24 +238,16 @@ export class LFDataProvider implements vscode.TreeDataProvider { - this.refreshTree(); - }), - this.watcher.onDidCreate(() => { - this.refreshTree(); - }), - this.watcher.onDidDelete(() => { - this.refreshTree(); - }) - ); + watchFileChanges(context: vscode.ExtensionContext): void { + this.watcher = vscode.workspace.createFileSystemWatcher(`**/*.lf`, false, false, false); + this.watcher.onDidChange(() => { + this.refreshTree(); + }), + this.watcher.onDidCreate(() => { + this.refreshTree(); + }), + this.watcher.onDidDelete(() => { + this.refreshTree(); + }) + context.subscriptions.push(this.watcher); } /** @@ -195,11 +309,14 @@ export class LFDataProvider implements vscode.TreeDataProvider { + if (root.children?.length) { + root.updateIcon(root.role, root.type); + const updateIfExists = (type: LFDataProviderNodeType) => { + const node = root.children?.find(n => n.role === LFDataProviderNodeRole.SUB && n.type === type); + node?.updateIcon(node.role, node.type); + if (node?.children?.length) { + this.updateContextValueAndIcon(node.children); + } + }; + + updateIfExists(LFDataProviderNodeType.LOCAL); + updateIfExists(LFDataProviderNodeType.LIBRARY); + updateIfExists(LFDataProviderNodeType.SOURCE); + } + }); + + this._onDidChangeTreeData.fire(undefined); + } + + /** + * Recursively updates the context value for the given LFDataProviderNode elements. + * This method iterates through the provided elements and updates the context value + * for each node based on its role and type. If a node has children, the method + * is called recursively to update the context value for the child nodes as well. + * + * @param elements - An array of LFDataProviderNode elements to update. + */ + updateContextValueAndIcon(elements: LFDataProviderNode[]): void { + elements.forEach((node: LFDataProviderNode) => { + node.updateContextValue(node.role, node.type); + node.updateIcon(node.role, node.type); + if (node.children?.length) { + this.updateContextValueAndIcon(node.children); + } + }); + } + /** * Refreshes the LF libraries tree view by fetching the latest library reactor information from the Language Server. */ @@ -224,83 +391,153 @@ export class LFDataProvider implements vscode.TreeDataProvider { - vscode.workspace.findFiles(this.searchPath, this.exclude_path ? this.exclude_path : null).then(uris => { - uris.forEach(uri => { - this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(node => { - if(node){ - this.addDataItem(node as LFDataProviderNode); - } - else if(node === null){ - vscode.window.showErrorMessage('Error retrieving data from the Language Server', ...['Try again', 'Cancel']).then(selection => { - if(selection === 'Try again'){ - this.refreshTree(); - } - }); - return; - } - }); - }); - }); + // Find all source files + this.findFiles(this.searchSourceFiles, this.exclude_path_src, LFDataProviderNodeType.SOURCE); + // Find all local reusable reactor libraries + this.findFiles(this.searchPathLocal, this.exclude_path_local, LFDataProviderNodeType.LOCAL); + // Find all lingo downloaded reactor libraries + this.findFiles(this.searchPathLibrary, null, LFDataProviderNodeType.LIBRARY); }); } this._onDidChangeTreeData.fire(undefined); } + findFiles(searchPath: string | vscode.GlobPattern, exclude_path: vscode.GlobPattern | null, type: LFDataProviderNodeType): void { + vscode.workspace.findFiles(searchPath, exclude_path ? exclude_path : null).then(uris => { + uris.forEach(uri => { + this.client.sendRequest('generator/getLibraryReactors', uri.toString()).then(node => { + if(node){ + this.addDataItem(node as LFDataProviderNode, type); + } + else if(node === null){ + vscode.window.showErrorMessage('Error retrieving data from the Language Server'); + return; + } + }); + }); + }); + } + /** * Adds a new data item to the LFDataProvider tree. * @param dataNode - The data node to add to the tree. + * @param type - The type of the node (e.g., LOCAL, LIBRARY, SOURCE). */ - addDataItem(dataNode: LFDataProviderNode) { - if (this.type === LFDataProviderNodeType.LOCAL) { - this.addDataItemLocal(dataNode); - } else { - this.addDataItemLibrary(dataNode); + addDataItem(dataNode: LFDataProviderNode, type: LFDataProviderNodeType) { + const root = this.buildRoot(dataNode.uri.toString(), type); + const node = this.createNode(dataNode, type, LFDataProviderNodeRole.FILE); + + // Add child nodes if applicable + this.addChildNodes(dataNode, node, type); + + switch (type) { + case LFDataProviderNodeType.LIBRARY: + this.handleLibraryNode(root, node, dataNode); + break; + case LFDataProviderNodeType.LOCAL: + this.handleLocalNode(root, node, dataNode); + break; + case LFDataProviderNodeType.SOURCE: + this.handleSourceNode(root, node, dataNode); + break; } + + // Sort data after adding the new node + this.sortData(); } /** - * Adds a data item to the Local Libraries view. - * @param dataNode - The data node to add. + * Creates a new LFDataProviderNode. + * @param dataNode - The data node to create. + * @param type - The type of the node. + * @param role - The role of the node (e.g., FILE, REACTOR). */ - addDataItemLocal(dataNode: LFDataProviderNode) { - const root = this.buildRoot(dataNode.uri.toString()); - let node = new LFDataProviderNode(dataNode.label!.toString(), dataNode.uri.toString(), LFDataProviderNodeRole.FILE, this.type, []); - root.children!.push(node); - if (dataNode.children!.length > 0) { - dataNode.children!.forEach((child: LFDataProviderNode) => { - node.children!.push(new LFDataProviderNode(child.label!.toString(), - child.uri.toString(), - LFDataProviderNodeRole.REACTOR, - this.type, [], - child.position - )); + createNode(dataNode: LFDataProviderNode, type: LFDataProviderNodeType, role: LFDataProviderNodeRole): LFDataProviderNode { + return new LFDataProviderNode( + dataNode.label!.toString(), + dataNode.uri.toString(), + role, + type, + [], + dataNode.position + ); + } + + /** + * Adds child nodes to a given node if applicable. + * @param dataNode - The parent data node. + * @param node - The node to which children will be added. + * @param type - The type of the parent node. + */ + addChildNodes(dataNode: LFDataProviderNode, node: LFDataProviderNode, type: LFDataProviderNodeType) { + if (type !== LFDataProviderNodeType.SOURCE && dataNode.children?.length) { + dataNode.children.forEach((child: LFDataProviderNode) => { + node.children!.push(this.createNode(child, type, LFDataProviderNodeRole.REACTOR)); }); } - this.sortData(); } /** - * Adds a data item to the Lingo Libraries view. - * @param dataNode - The data node to add. + * Handles the addition of a LIBRARY type node. + * @param root - The root node of the tree. + * @param node - The node to add to the tree. + * @param dataNode - The data node being added. */ - addDataItemLibrary(dataNode: LFDataProviderNode) { - const root = this.buildRoot(dataNode.uri.toString()); - const library_root = this.buildLibraryRoot(dataNode.uri.toString(), root); - let node = new LFDataProviderNode(dataNode.label!.toString(), dataNode.uri.toString(), LFDataProviderNodeRole.FILE, this.type, []); - if (dataNode.children!.length > 0) { - dataNode.children!.forEach((child: LFDataProviderNode) => { - node.children!.push(new LFDataProviderNode(child.label!.toString(), - child.uri.toString(), - LFDataProviderNodeRole.REACTOR, - this.type, [], - child.position - )); - }); + handleLibraryNode(root: LFDataProviderNode, node: LFDataProviderNode, dataNode: LFDataProviderNode) { + const libraryRoot = this.buildLibraryRoot(dataNode.uri.toString(), root, dataNode); + if (!libraryRoot.children?.some(n => n.label === node.label)) { + libraryRoot.children!.push(node); } - if (library_root.children!.find(n => n.label === node.label) === undefined) { - library_root.children!.push(node); + } + + /** + * Handles the addition of a LOCAL type node. + * @param root - The root node of the tree. + * @param node - The node to add to the tree. + * @param dataNode - The data node being added. + */ + handleLocalNode(root: LFDataProviderNode, node: LFDataProviderNode, dataNode: LFDataProviderNode) { + let localNode = this.findOrCreateSubNode(root, "Local Libraries", LFDataProviderNodeRole.SUB, LFDataProviderNodeType.LOCAL, dataNode); + if(!localNode.children?.some(n => n.label === node.label)) + localNode.children!.push(node); + } + + /** + * Handles the addition of a SOURCE type node. + * @param root - The root node of the tree. + * @param node - The node to add to the tree. + * @param dataNode - The data node being added. + */ + handleSourceNode(root: LFDataProviderNode, node: LFDataProviderNode, dataNode: LFDataProviderNode) { + let srcNode = this.findOrCreateSubNode(root, "Source Files", LFDataProviderNodeRole.SUB, LFDataProviderNodeType.SOURCE, dataNode); + if(!srcNode.children?.some(n => n.label === node.label)) + srcNode.children!.push(node); + } + + /** + * Finds or creates a sub-node with a given label, role, and type. + * @param root - The root node to search in. + * @param label - The label of the sub-node to find or create. + * @param role - The role of the sub-node. + * @param type - The type of the sub-node. + * @param dataNode - The data node associated with the sub-node. + * @returns The found or newly created sub-node. + */ + findOrCreateSubNode(root: LFDataProviderNode, label: string, role: LFDataProviderNodeRole, type: LFDataProviderNodeType, dataNode: LFDataProviderNode): LFDataProviderNode { + let subNode = root.children?.find(n => n.role === role && n.type === type); + + if (!subNode) { + subNode = new LFDataProviderNode( + label, + dataNode.uri.toString(), + role, + type, + [] + ); + root.children!.push(subNode); } - this.sortData(); + + return subNode; } /** @@ -338,38 +575,41 @@ export class LFDataProvider implements vscode.TreeDataProvider item.label === projectLabel); - if (!existingProject) { - const projectUri = splittedUri.slice(0, - this.path_offset).join('/') + '/'; - const root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.ROOT, this.type, []); - this.data.push(root); - return root; - } - return existingProject; + const srcIdx = splittedUri.indexOf(!type || type == LFDataProviderNodeType.LIBRARY ? 'build' : 'src'); + const projectLabel = splittedUri[srcIdx - 1]; + + const existingProject = this.data.find(item => item.label === projectLabel); + if (!existingProject) { + const projectUri = splittedUri.slice(0, srcIdx).join('/') + '/'; + const root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.PROJECT, type!, []); + this.data.push(root); + return root; + } + return existingProject; } - + /** - * Builds the root node of the Lingo Library tree for the given URI. - * @param uri - The URI of the item to build. - * @param root - The root node of the library tree. - * @returns The root node of the library tree, either a new node or an existing one. + * Builds or retrieves the root node for a library project based on the URI. + * @param uri - The URI of the data node. + * @param root - The root node of the tree. + * @returns The root node for the library project. */ - buildLibraryRoot(uri: string, root: LFDataProviderNode): LFDataProviderNode { + buildLibraryRoot(uri: string, root: LFDataProviderNode, dataNode: LFDataProviderNode): LFDataProviderNode { const splittedUri = uri.split('/'); - const projectLabel = splittedUri[splittedUri.length - this.path_offset + 3]; - - const existingLibraryRoot = root.children!.find(item => item.label === projectLabel); - if (!existingLibraryRoot) { - const projectUri = splittedUri.slice(0, - this.path_offset + 3).join('/') + '/'; - const library_root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.ROOT, this.type, []); - root.children!.push(library_root); - return library_root; + const srcIdx = splittedUri.indexOf('src'); + const projectLabel = splittedUri[srcIdx - 1]; + + let lingo = this.findOrCreateSubNode(root, "Lingo Packages", LFDataProviderNodeRole.SUB, LFDataProviderNodeType.LIBRARY, dataNode); + const existingProject = lingo.children!.find(item => item.label === projectLabel); + if (!existingProject) { + const projectUri = splittedUri.slice(0, srcIdx).join('/') + '/'; + const root = new LFDataProviderNode(projectLabel, projectUri, LFDataProviderNodeRole.ROOT, LFDataProviderNodeType.LIBRARY, []); + lingo.children!.push(root); + return root; } - return existingLibraryRoot; + return existingProject; } /** @@ -522,4 +762,18 @@ export class LFDataProvider implements vscode.TreeDataProvider { + vscode.window.showTextDocument(doc); + }); + } + } \ No newline at end of file From 2ac798e3aa3a7ac8746b2aba4b298bc5169e89e8 Mon Sep 17 00:00:00 2001 From: vinzbarbuto Date: Thu, 3 Oct 2024 12:06:10 -0700 Subject: [PATCH 36/38] Prevent file save on import addition to avoid tree view refreshes --- src/lfview/lf-data-provider.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lfview/lf-data-provider.ts b/src/lfview/lf-data-provider.ts index 274bb0276..96e6c960b 100644 --- a/src/lfview/lf-data-provider.ts +++ b/src/lfview/lf-data-provider.ts @@ -720,7 +720,6 @@ export class LFDataProvider implements vscode.TreeDataProvider { - const document = editor.document; try { - const success = await editor.edit(editBuilder => { + await editor.edit(editBuilder => { editBuilder.insert(new vscode.Position(idx, 0), importText); }); - if (success) { - await document.save(); - } } catch (error) { console.error('Failed to add text or save document:', error); } From 662eff429f45ed3130ff5620f89e752afbe25731 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 8 Oct 2024 22:46:12 -0700 Subject: [PATCH 37/38] Apply suggestions from code review --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7a0f316f..5f76cc2ed 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ This extension adds language support for [Lingua Franca (LF)](https://www.lf-lan * user-triggered build (Ctrl + Shift + P, then `Lingua Franca: Build`) * user-triggered build and run (Ctrl + Shift + P, then `Lingua Franca: Build and Run`) * Lingua Franca Package Explorer (click on the LF icon in the Activity Bar on the left), which features two distinct tree-view sections: - * **Local Libraries:** This section showcases a catalog of Local Libraries, which are libraries personally defined by the programmer. - * **Lingo Libraries:** Here, you'll find a catalog of all libraries downloaded in the personal workspace using the Lingo package manager. + * **Local Libraries:** Unpublished libraries found in the local filesystem under `./src/lib`; + * **Lingo Libraries:** Published libraries downloaded into the workspace using the Lingo package manager. ## Quick Start 1. Install this plugin from the [VSCode From 2e7a5bec78fff17fe4c926f9ca7cf5e9e6e2ad88 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Tue, 8 Oct 2024 22:50:14 -0700 Subject: [PATCH 38/38] Update README.md --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5f76cc2ed..7a7e49b01 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,14 @@ ![Open VSX Downloads](https://img.shields.io/open-vsx/dt/lf-lang/vscode-lingua-franca?label=Open%20VSX%20Registry%20%E2%A4%93) ![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/lf-lang.vscode-lingua-franca?label=VS%20Marketplace%20%E2%A4%93) -This extension adds language support for [Lingua Franca (LF)](https://www.lf-lang.org/). It is based on the LF Language and Diagram Server and provides: +This extension adds language support for [Lingua Franca (LF)](https://www.lf-lang.org/). + +## ✨ Check out the new Lingua Franca Package Explorer! ✨ +Click on the LF icon in the Activity Bar on the left, which features two distinct tree-view sections: + * **Local Libraries:** Unpublished libraries found in the local filesystem under `./src/lib`; + * **Lingo Libraries:** Published libraries downloaded into the workspace using the [Lingo package manager](https://github.com/lf-lang/lingo/). + +## Other features * find references * folding ranges * get workspace symbols @@ -15,9 +22,6 @@ This extension adds language support for [Lingua Franca (LF)](https://www.lf-lan * target code validation upon build or file save * user-triggered build (Ctrl + Shift + P, then `Lingua Franca: Build`) * user-triggered build and run (Ctrl + Shift + P, then `Lingua Franca: Build and Run`) -* Lingua Franca Package Explorer (click on the LF icon in the Activity Bar on the left), which features two distinct tree-view sections: - * **Local Libraries:** Unpublished libraries found in the local filesystem under `./src/lib`; - * **Lingo Libraries:** Published libraries downloaded into the workspace using the Lingo package manager. ## Quick Start 1. Install this plugin from the [VSCode