From 43dbcb4d47cd223a55b8c7f0f3d1ffa685f0f9fd Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Fri, 26 Jun 2020 21:35:57 +0100 Subject: [PATCH] implement shceduleReparse to reduce parsing load while typing --- langserver/server.js | 65 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/langserver/server.js b/langserver/server.js index 9c49d3f..1920d15 100644 --- a/langserver/server.js +++ b/langserver/server.js @@ -92,7 +92,49 @@ class JavaDocInfo { this.version = version; /** @type {ParsedInfo} */ this.parsed = null; + /** @type {Promise} */ + this.reparseWaiter = Promise.resolve(); + /** @type {{ resolve: () => void, timer: * }} */ + this.waitInfo = null; } + + /** + * Schedule this document for reparsing. + * + * To prevent redundant parsing while typing, a small delay is required + * before the reparse happens. + * When a key is pressed, `scheduleReparse()` starts a timer. If more + * keys are typed before the timer expires, the timer is restarted. + * Once typing pauses, the timer expires and the content reparsed. + * + * A `reparseWaiter` promise is used to delay the completion items + * retrieval until the reparse is complete. + */ + scheduleReparse() { + const createWaitTimer = () => { + return setTimeout(() => { + // reparse the content, resolve the reparseWaiter promise + // and reset the fields + reparse([this.uri], { includeMethods: true }); + this.waitInfo.resolve(); + this.waitInfo = null; + }, 250); + } + if (this.waitInfo) { + // we already have a promise pending - just restart the timer + trace('restart timer'); + clearTimeout(this.waitInfo.timer); + this.waitInfo.timer = createWaitTimer(); + return; + } + // create a new pending promise and start the timer + trace('start timer'); + this.waitInfo = { + resolve: null, + timer: createWaitTimer(), + } + this.reparseWaiter = new Promise(resolve => this.waitInfo.resolve = resolve); + } } class ParsedInfo { @@ -129,6 +171,7 @@ let first_parse_waiting = new Set(); * @param {{includeMethods: boolean, first_parse?: boolean}} [opts] */ function reparse(uris, opts) { + trace('reparse'); if (androidLibrary instanceof Promise) { return; } @@ -233,7 +276,10 @@ let documents = new TextDocuments({ docinfo.content = `${docinfo.content.slice(0, start_index)}${change.text}${docinfo.content.slice(end_index)}`; } }); - reparse([document.uri], { includeMethods: true }); + + docinfo.version = version; + docinfo.scheduleReparse(); + return document; }, }); @@ -789,6 +835,18 @@ connection.onCompletion( if (!docinfo || !docinfo.parsed) { return []; } + trace('reparse waiter - ' + docinfo.version); + const preversion = docinfo.version; + await docinfo.reparseWaiter; + trace('retrieving completion items - ' + docinfo.version); + if (docinfo.version !== preversion) { + // if the content has changed, ignore the current request + /** @type {import('vscode-languageserver').CompletionList} */ + return { + isIncomplete: true, + items: [], + } + } const parsed = docinfo.parsed; lastCompletionTypeMap = (parsed && parsed.typemap) || androidLibrary; let locals = [], sourceTypes = [], show_instances = false; @@ -849,7 +907,10 @@ connection.onCompletion( ...[ ...defaultCompletionTypes.types, ...sourceTypes, - ].sort(sortBy.label), + ] // exclude dotted (inner) types because they result in useless + // matches in the intellisense filter when . is pressed + .filter(x => !x.label.includes('.')) + .sort(sortBy.label), ...defaultCompletionTypes.packageNames, ].map((x,idx) => { x.sortText = `${10000+idx}-${x.label}`;