brighterscript
Advanced tools
Comparing version 0.12.2 to 0.12.3
@@ -9,3 +9,9 @@ # Changelog | ||
## [0.12.2] | ||
## [0.12.3] - 2020-08-03 | ||
### Fixed | ||
- bug in the language server that would provide stale completions due to the file throttling introduced in v0.11.2. Now the language server will wait for the throttled parsing to complete before serving completion results. | ||
## [0.12.2] - 2020-07-16 | ||
### Added | ||
@@ -433,2 +439,3 @@ - Expose `ProgramBuilder.transpile()` method to make it easier for tools to transpile programmatically. [#154](https://github.com/rokucommunity/brighterscript/issues/154) | ||
[0.12.3]: https://github.com/rokucommunity/brighterscript/compare/v0.12.2...v0.12.3 | ||
[0.12.2]: https://github.com/rokucommunity/brighterscript/compare/v0.12.1...v0.12.2 | ||
@@ -435,0 +442,0 @@ [0.12.1]: https://github.com/rokucommunity/brighterscript/compare/v0.12.0...v0.12.1 |
import 'array-flat-polyfill'; | ||
import { FileChangeType, InitializeParams, ServerCapabilities, ExecuteCommandParams } from 'vscode-languageserver'; | ||
import { ProgramBuilder } from './ProgramBuilder'; | ||
import { Throttler } from './Throttler'; | ||
export declare class LanguageServer { | ||
@@ -32,4 +33,6 @@ private connection; | ||
private loggerSubscription; | ||
private debouncer; | ||
private throttledValidation; | ||
private keyedThrottler; | ||
validateThrottler: Throttler; | ||
private boundValidateAll; | ||
private validateAllThrottled; | ||
run(): void; | ||
@@ -113,2 +116,8 @@ /** | ||
}[]): Promise<void>; | ||
/** | ||
* This only operates on files that match the specified files globs, so it is safe to throw | ||
* any file changes you receive with no unexpected side-effects | ||
* @param changes | ||
*/ | ||
private handleFileChange; | ||
private onHover; | ||
@@ -115,0 +124,0 @@ private onDocumentClose; |
@@ -16,4 +16,4 @@ "use strict"; | ||
const Logger_1 = require("./Logger"); | ||
const KeyedDebouncer_1 = require("./KeyedDebouncer"); | ||
const TaskThrottler_1 = require("./TaskThrottler"); | ||
const Throttler_1 = require("./Throttler"); | ||
const KeyedThrottler_1 = require("./KeyedThrottler"); | ||
class LanguageServer { | ||
@@ -45,4 +45,5 @@ constructor() { | ||
this.documents = new vscode_languageserver_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument); | ||
this.debouncer = new KeyedDebouncer_1.KeyedDebouncer(); | ||
this.throttledValidation = new TaskThrottler_1.TaskThrottler(this.validateAll.bind(this)); | ||
this.keyedThrottler = new KeyedThrottler_1.KeyedThrottler(this.debounceTimeout); | ||
this.validateThrottler = new Throttler_1.Throttler(0); | ||
this.boundValidateAll = this.validateAll.bind(this); | ||
/** | ||
@@ -57,2 +58,5 @@ * The list of all issues, indexed by file. This allows us to keep track of which buckets of | ||
} | ||
validateAllThrottled() { | ||
return this.validateThrottler.run(this.boundValidateAll); | ||
} | ||
//run the server | ||
@@ -403,2 +407,4 @@ run() { | ||
let filePath = util_1.util.uriToPath(uri); | ||
//wait until the fle has settled | ||
await this.keyedThrottler.onIdleOnce(filePath, true); | ||
let workspaceCompletionPromises = []; | ||
@@ -478,3 +484,3 @@ let workspaces = this.getWorkspaces(); | ||
// valdiate all workspaces | ||
this.throttledValidation.run(); | ||
this.validateAllThrottled(); //eslint-disable-line | ||
} | ||
@@ -557,6 +563,2 @@ getRootDir(workspace) { | ||
let keys = changes.map(x => x.pathAbsolute); | ||
//Debounce the list of keys so we avoid duplicate parsing. This will return only the keys | ||
//that made it through the debounce (i.e. no other method debounced those keys again). | ||
//Any that are not present will be handled by the later listener that debouced it. | ||
keys = await this.debouncer.debounce(keys, this.debounceTimeout); | ||
//filter the list of changes to only the ones that made it through the debounce unscathed | ||
@@ -614,3 +616,3 @@ changes = changes.filter(x => keys.includes(x.pathAbsolute)); | ||
//valdiate all workspaces | ||
this.throttledValidation.run(); | ||
await this.validateAllThrottled(); | ||
} | ||
@@ -625,49 +627,58 @@ this.connection.sendNotification('build-status', 'success'); | ||
async handleFileChanges(workspace, changes) { | ||
//this loop assumes paths are both file paths and folder paths, which eliminates the need to detect. | ||
//All functions below can handle being given a file path AND a folder path, and will only operate on the one they are looking for | ||
await Promise.all(changes.map(async (change) => { | ||
await this.keyedThrottler.run(change.pathAbsolute, async () => { | ||
await this.handleFileChange(workspace, change); | ||
}); | ||
})); | ||
} | ||
/** | ||
* This only operates on files that match the specified files globs, so it is safe to throw | ||
* any file changes you receive with no unexpected side-effects | ||
* @param changes | ||
*/ | ||
async handleFileChange(workspace, change) { | ||
const program = workspace.builder.program; | ||
const options = workspace.builder.options; | ||
const rootDir = workspace.builder.rootDir; | ||
//this loop assumes paths are both file paths and folder paths, | ||
//Which eliminates the need to detect. All functions below can handle being given | ||
//a file path AND a folder path, and will only operate on the one they are looking for | ||
for (let change of changes) { | ||
//deleted | ||
if (change.type === vscode_languageserver_1.FileChangeType.Deleted) { | ||
//try to act on this path as a directory | ||
workspace.builder.removeFilesInFolder(change.pathAbsolute); | ||
//if this is a file loaded in the program, remove it | ||
if (program.hasFile(change.pathAbsolute)) { | ||
program.removeFile(change.pathAbsolute); | ||
} | ||
//created | ||
//deleted | ||
if (change.type === vscode_languageserver_1.FileChangeType.Deleted) { | ||
//try to act on this path as a directory | ||
workspace.builder.removeFilesInFolder(change.pathAbsolute); | ||
//if this is a file loaded in the program, remove it | ||
if (program.hasFile(change.pathAbsolute)) { | ||
program.removeFile(change.pathAbsolute); | ||
} | ||
else if (change.type === vscode_languageserver_1.FileChangeType.Created) { | ||
// thanks to `onDidChangeWatchedFiles`, we can safely assume that all "Created" changes are file paths, (not directories) | ||
//get the dest path for this file. | ||
let destPath = rokuDeploy.getDestPath(change.pathAbsolute, options.files, rootDir); | ||
//if we got a dest path, then the program wants this file | ||
if (destPath) { | ||
await program.addOrReplaceFile({ | ||
src: change.pathAbsolute, | ||
dest: rokuDeploy.getDestPath(change.pathAbsolute, options.files, rootDir) | ||
}); | ||
} | ||
else { | ||
//no dest path means the program doesn't want this file | ||
} | ||
//changed | ||
//created | ||
} | ||
else if (change.type === vscode_languageserver_1.FileChangeType.Created) { | ||
// thanks to `onDidChangeWatchedFiles`, we can safely assume that all "Created" changes are file paths, (not directories) | ||
//get the dest path for this file. | ||
let destPath = rokuDeploy.getDestPath(change.pathAbsolute, options.files, rootDir); | ||
//if we got a dest path, then the program wants this file | ||
if (destPath) { | ||
await program.addOrReplaceFile({ | ||
src: change.pathAbsolute, | ||
dest: rokuDeploy.getDestPath(change.pathAbsolute, options.files, rootDir) | ||
}); | ||
} | ||
else if (program.hasFile(change.pathAbsolute)) { | ||
//sometimes "changed" events are emitted on files that were actually deleted, | ||
//so determine file existance and act accordingly | ||
if (await util_1.util.fileExists(change.pathAbsolute)) { | ||
await program.addOrReplaceFile({ | ||
src: change.pathAbsolute, | ||
dest: rokuDeploy.getDestPath(change.pathAbsolute, options.files, rootDir) | ||
}); | ||
} | ||
else { | ||
program.removeFile(change.pathAbsolute); | ||
} | ||
else { | ||
//no dest path means the program doesn't want this file | ||
} | ||
//changed | ||
} | ||
else if (program.hasFile(change.pathAbsolute)) { | ||
//sometimes "changed" events are emitted on files that were actually deleted, | ||
//so determine file existance and act accordingly | ||
if (await util_1.util.fileExists(change.pathAbsolute)) { | ||
await program.addOrReplaceFile({ | ||
src: change.pathAbsolute, | ||
dest: rokuDeploy.getDestPath(change.pathAbsolute, options.files, rootDir) | ||
}); | ||
} | ||
else { | ||
program.removeFile(change.pathAbsolute); | ||
} | ||
} | ||
} | ||
@@ -708,26 +719,24 @@ async onHover(params) { | ||
let filePath = vscode_uri_1.URI.parse(textDocument.uri).fsPath; | ||
let keys = await this.debouncer.debounce([textDocument.uri], this.debounceTimeout); | ||
//skip parsing the file because some other iteration debounced the file after us so they will handle it | ||
if (keys.length === 0) { | ||
return; | ||
} | ||
this.connection.sendNotification('build-status', 'building'); | ||
let documentText = textDocument.getText(); | ||
let workspaces = this.getWorkspaces(); | ||
try { | ||
await Promise.all(workspaces.map(async (x) => { | ||
var _a; | ||
//only add or replace existing files. All of the files in the project should | ||
//have already been loaded by other means | ||
if (x.builder.program.hasFile(filePath)) { | ||
let rootDir = (_a = x.builder.program.options.rootDir) !== null && _a !== void 0 ? _a : x.builder.program.options.cwd; | ||
let dest = rokuDeploy.getDestPath(filePath, x.builder.program.options.files, rootDir); | ||
await x.builder.program.addOrReplaceFile({ | ||
src: filePath, | ||
dest: dest | ||
}, documentText); | ||
} | ||
})); | ||
//throttle file processing. first call is run immediately, and then the last call is processed. | ||
await this.keyedThrottler.run(filePath, async () => { | ||
this.connection.sendNotification('build-status', 'building'); | ||
let documentText = textDocument.getText(); | ||
let workspaces = this.getWorkspaces(); | ||
await Promise.all(workspaces.map(async (x) => { | ||
var _a; | ||
//only add or replace existing files. All of the files in the project should | ||
//have already been loaded by other means | ||
if (x.builder.program.hasFile(filePath)) { | ||
let rootDir = (_a = x.builder.program.options.rootDir) !== null && _a !== void 0 ? _a : x.builder.program.options.cwd; | ||
let dest = rokuDeploy.getDestPath(filePath, x.builder.program.options.files, rootDir); | ||
await x.builder.program.addOrReplaceFile({ | ||
src: filePath, | ||
dest: dest | ||
}, documentText); | ||
} | ||
})); | ||
}); | ||
// valdiate all workspaces | ||
this.throttledValidation.run(); | ||
await this.validateAllThrottled(); | ||
} | ||
@@ -738,2 +747,3 @@ catch (e) { | ||
} | ||
console.log('Validate done'); | ||
} | ||
@@ -856,2 +866,3 @@ async validateAll() { | ||
(_a = this.loggerSubscription) === null || _a === void 0 ? void 0 : _a.call(this); | ||
this.validateThrottler.dispose(); | ||
} | ||
@@ -858,0 +869,0 @@ } |
@@ -729,3 +729,9 @@ "use strict"; | ||
return new Promise((resolve) => { | ||
setTimeout(resolve, milliseconds); | ||
//if milliseconds is 0, don't actually timeout (improves unit test throughput) | ||
if (milliseconds === 0) { | ||
process.nextTick(resolve); | ||
} | ||
else { | ||
setTimeout(resolve, milliseconds); | ||
} | ||
}); | ||
@@ -732,0 +738,0 @@ } |
{ | ||
"name": "brighterscript", | ||
"version": "0.12.2", | ||
"version": "0.12.3", | ||
"description": "A superset of Roku's BrightScript language.", | ||
@@ -83,3 +83,3 @@ "scripts": { | ||
"@types/request": "^2.47.0", | ||
"@types/sinon": "^7.0.4", | ||
"@types/sinon": "^9.0.4", | ||
"@typescript-eslint/eslint-plugin": "^2.25.0", | ||
@@ -90,3 +90,2 @@ "@typescript-eslint/parser": "^2.25.0", | ||
"chai-files": "^1.4.0", | ||
"codecov": "^3.1.0", | ||
"coveralls": "^3.0.0", | ||
@@ -101,3 +100,3 @@ "eslint": "^6.8.0", | ||
"semver-extra": "^3.0.0", | ||
"sinon": "^7.2.2", | ||
"sinon": "^9.0.2", | ||
"source-map-support": "^0.5.13", | ||
@@ -104,0 +103,0 @@ "testdouble": "^3.5.2", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2203902
32
31422