From 61024e090c1f010c6adaa9c5bf36222f8d44327d Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Thu, 17 Sep 2020 06:19:25 -0400 Subject: [PATCH] add support for the language server project tree --- .vscode/settings.json | 5 --- src/LanguageServer.ts | 71 ++++++++++++++++++++++++++++++++++++--- src/Program.ts | 15 +++++++++ src/index.ts | 1 + src/parser/Parser.spec.ts | 2 +- 5 files changed, 83 insertions(+), 11 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4fcc94d4f..0c232e71f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,11 +10,6 @@ "**/coverage": true, "**/dist": true }, - "search.exclude": { - "**/node_modules": true, - "**/bower_components": true, - "**/dist": true - }, "mocha.logVerbose": true, "mocha.files.glob": "src/**/*.ts", "mocha.requires": [ diff --git a/src/LanguageServer.ts b/src/LanguageServer.ts index 6a72978f1..2e770d08d 100644 --- a/src/LanguageServer.ts +++ b/src/LanguageServer.ts @@ -29,7 +29,7 @@ import { BsConfig } from './BsConfig'; import { Deferred } from './deferred'; import { DiagnosticMessages } from './DiagnosticMessages'; import { ProgramBuilder } from './ProgramBuilder'; -import { standardizePath as s, util } from './util'; +import { standardizePath as s, standardizePath, util } from './util'; import { BsDiagnostic } from './interfaces'; import { Logger } from './Logger'; import { Throttler } from './Throttler'; @@ -183,7 +183,9 @@ export class LanguageServer { hoverProvider: true, executeCommandProvider: { commands: [ - CustomCommands.TranspileFile + CustomCommands.transpileFile, + CustomCommands.getProjectsInfo, + CustomCommands.reloadProject ] } } as ServerCapabilities @@ -231,6 +233,7 @@ export class LanguageServer { }); } await this.waitAllProgramFirstRuns(false); + this.sendProjectsChanged(); workspaceCreatedDeferred.resolve(); await this.sendDiagnostics(); } catch (e) { @@ -248,7 +251,11 @@ export class LanguageServer { * Send a critical failure notification to the client, which should show a notification of some kind */ private sendCriticalFailure(message: string) { - this.connection.sendNotification('critical-failure', message); + this.connection.sendNotification(CustomNotifications.criticalFailure, message); + } + + private sendProjectsChanged() { + this.connection.sendNotification(CustomNotifications.projectsChanged, null); } /** @@ -587,6 +594,7 @@ export class LanguageServer { // valdiate all workspaces this.validateAllThrottled(); //eslint-disable-line + this.sendProjectsChanged(); } private getRootDir(workspace: Workspace) { @@ -746,6 +754,12 @@ export class LanguageServer { //valdiate all workspaces await this.validateAllThrottled(); } + + //notify the client anytime the project creates or deletes files + if (changes.find(x => x.type === FileChangeType.Created || x.type === FileChangeType.Deleted)) { + this.sendProjectsChanged(); + } + this.connection.sendNotification('build-status', 'success'); } @@ -1019,10 +1033,21 @@ export class LanguageServer { this.latestDiagnosticsByFile = diagnosticsByFile; } + private getWorkspace(workspacePath: string) { + return this.workspaces.find(x => standardizePath(workspacePath) === standardizePath(x.workspacePath)); + } + public async onExecuteCommand(params: ExecuteCommandParams) { + this.connection.console.log('onExecuteCommand: ' + JSON.stringify(params)); await this.waitAllProgramFirstRuns(); - if (params.command === CustomCommands.TranspileFile) { + if (params.command === CustomCommands.transpileFile) { return this.transpileFile(params.arguments[0]); + } else if (params.command === CustomCommands.getProjectsInfo) { + return this.getProjectsInfo(); + } else if (params.command === CustomCommands.reloadProject) { + return this.reloadWorkspaces([ + this.getWorkspace(params.arguments[0]) + ]); } } @@ -1038,6 +1063,24 @@ export class LanguageServer { } } + /** + * Returns a list of project information for all projects + */ + private async getProjectsInfo() { + await this.waitAllProgramFirstRuns(); + let result = []; + for (let workspace of this.workspaces) { + const program = workspace.builder.program; + result.push({ + name: program.getName(), + workspaceFolder: program.options.cwd, + rootDir: program.options.rootDir, + files: Object.keys(program.files).map(src => ({ src: src, dest: program.files[src].pkgPath })) + }); + } + return result; + } + public dispose() { this.loggerSubscription?.(); this.validateThrottler.dispose(); @@ -1055,5 +1098,23 @@ export interface Workspace { } export enum CustomCommands { - TranspileFile = 'TranspileFile' + transpileFile = 'transpileFile', + getProjectsInfo = 'getProjectsInfo', + reloadProject = 'reloadProject' +} + +export interface ProjectInfo { + name: string; + workspaceFolder: string; + rootDir: string; + files: ProjectInfoFile[]; +} +export interface ProjectInfoFile { + src: string; + dest: string; +} + +export enum CustomNotifications { + criticalFailure = 'critical-failure', + projectsChanged = 'projects-changed' } diff --git a/src/Program.ts b/src/Program.ts index 4ec898f1b..2ddf50a57 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -48,6 +48,21 @@ export class Program { }); } + /** + * Get the name of the project. + * This is the name from the manifest (if manifest.title is set), + * or the folder name of cwd otherwise + */ + public getName() { + const manifest = this.getManifest(); + + if (manifest.has('title')) { + return manifest.get('title'); + } else { + return path.basename(this.options.cwd ?? this.options.rootDir); + } + } + public logger: Logger; private createGlobalScope() { diff --git a/src/index.ts b/src/index.ts index 127b1945b..391c0abc8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,3 +12,4 @@ export * from './lexer'; export * from './parser/Parser'; export * from './BsConfig'; export * from './deferred'; +export * from './Throttler'; diff --git a/src/parser/Parser.spec.ts b/src/parser/Parser.spec.ts index 2567146c4..c91fded3a 100644 --- a/src/parser/Parser.spec.ts +++ b/src/parser/Parser.spec.ts @@ -543,7 +543,7 @@ describe('parser', () => { it('catchs missing file path', () => { let { statements, diagnostics } = parse(` - import + import `, ParseMode.BrighterScript); expect(diagnostics[0]?.message).to.equal( DiagnosticMessages.expectedStringLiteralAfterKeyword('import').message