diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index a428379..527de5d 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -18,6 +18,7 @@ const { Label, Local, MethodDeclarations, ResolvedIdent, ResolveInfo } = require const { resolveImports, resolveSingleImport } = require('../java/import-resolver'); const { checkAssignment, getTypeInheritanceList } = require('./expression-resolver'); const { checkStatementBlock } = require('./statement-validater'); +const { time, timeEnd } = require('../logging'); const { ArrayIndexExpression } = require("./expressiontypes/ArrayIndexExpression"); const { ArrayValueExpression } = require("./expressiontypes/ArrayValueExpression"); @@ -199,10 +200,6 @@ function extractSourceTypes(tokens, typemap) { */ function parse(docs, cached_units, typemap) { - const timers = new Set(); - const time = name => (timers.add(name), console.time(name)); - const timeEnd = name => (timers.delete(name), console.timeEnd(name)); - time('tokenize'); const sources = docs.reduce((arr, doc) => { try { diff --git a/langserver/java/validater.js b/langserver/java/validater.js index dc70294..06cad44 100644 --- a/langserver/java/validater.js +++ b/langserver/java/validater.js @@ -31,8 +31,6 @@ function parseMethodBodies(unit, typemap) { * @returns {import('./parsetypes/parse-problem')[]} */ function validate(unit, androidLibrary) { - console.time('validation'); - let probs = []; const module_validaters = [ @@ -54,7 +52,6 @@ function validate(unit, androidLibrary) { module_validaters.map(v => v(unit.types, unit)), ...probs, ]; - console.timeEnd('validation'); function flatten(arr) { let res = arr; diff --git a/langserver/logging.js b/langserver/logging.js new file mode 100644 index 0000000..7379f3b --- /dev/null +++ b/langserver/logging.js @@ -0,0 +1,43 @@ +const { Settings } = require('./settings'); + +const earlyTraceBuffer = []; + +/** + * @param {string} s + */ +function trace(s) { + if (Settings.updateCount > 0 && !Settings.trace) { + return; + } + const msg = `${Date.now()}: ${s}`; + // before we've retrieved the trace setting, buffer the messages + if (Settings.updateCount === 0) { + earlyTraceBuffer.push(msg); + return; + } + if (earlyTraceBuffer.length) { + earlyTraceBuffer.splice(0, earlyTraceBuffer.length).forEach(msg => console.log(msg)); + } + console.log(msg); +} + +function info(msg) { + console.log(msg); +} + +function time(label) { + if (Settings.trace) { + console.time(label); + } +} + +function timeEnd(label) { + if (Settings.trace) { + console.timeEnd(label); + } +} + +exports.info = info; +exports.trace = trace; +exports.time = time; +exports.timeEnd = timeEnd; diff --git a/langserver/server.js b/langserver/server.js index 7df6707..f08a3e3 100644 --- a/langserver/server.js +++ b/langserver/server.js @@ -4,30 +4,26 @@ const os = require('os'); const { createConnection, TextDocuments, - //TextDocument, Diagnostic, - DiagnosticSeverity, ProposedFeatures, - //InitializeParams, DidChangeConfigurationNotification, CompletionItem, CompletionItemKind, TextDocumentSyncKind, - Position, - //TextDocumentPositionParams } = require('vscode-languageserver'); const { TextDocument } = require('vscode-languageserver-textdocument'); -const { Settings } = require('./settings'); const { loadAndroidSystemLibrary } = require('./java/java-libraries'); const { JavaType, CEIType, ArrayType, PrimitiveType, Method } = require('java-mti'); const { ParseProblem } = require('./java/parser'); const { parse } = require('./java/body-parser3'); const { SourceUnit } = require('./java/source-types'); -const { validate, parseMethodBodies } = require('./java/validater'); +const { parseMethodBodies } = require('./java/validater'); const { getTypeInheritanceList } = require('./java/expression-resolver'); +const { Settings } = require('./settings'); +const { trace, info, time, timeEnd } = require('./logging'); /** * @typedef {Map} AndroidLibrary @@ -76,13 +72,6 @@ function positionAt(index, content) { } } -/** - * @param {string} s - */ -function trace(s) { - console.log(`${Date.now()}: ${s}`); -} - class JavaDocInfo { /** * @param {string} uri @@ -226,7 +215,7 @@ function reparse(uris, opts) { } if (method_body_uris.length) { - console.time('parse-methods'); + time('parse-methods'); method_body_uris.forEach(uri => { const doc = liveParsers.get(uri); if (!doc || !doc.parsed) { @@ -234,7 +223,7 @@ function reparse(uris, opts) { } parseMethodBodies(doc.parsed.unit, typemap); }) - console.timeEnd('parse-methods'); + timeEnd('parse-methods'); } } @@ -335,9 +324,12 @@ connection.onInitialize((params) => { connection.onInitialized(async () => { if (hasConfigurationCapability) { // Register for all configuration changes. - connection.client.register(DidChangeConfigurationNotification.type, undefined); + connection.client.register( + DidChangeConfigurationNotification.type, { + section: 'android-dev-ext', + }); const initialSettings = await connection.workspace.getConfiguration({ - section: Settings.ID, + section: "android-dev-ext" }); Settings.set(initialSettings); } @@ -348,7 +340,7 @@ connection.onInitialized(async () => { }); } - const src_folder = await getAppRootFolder(); + const src_folder = await getAppSourceRootFolder(); if (src_folder) { await rescanSourceFolders(src_folder); reparse([...liveParsers.keys()], { includeMethods: false, first_parse: true }); @@ -367,8 +359,8 @@ async function rescanSourceFolders(src_folder) { return; } - // when the appRoot config value changes and we rescan the folder, we need - // to delete any parsers that were from the old appRoot + // when the appSourceRoot config value changes and we rescan the folder, we need + // to delete any parsers that were from the old appSourceRoot const unused_keys = new Set(liveParsers.keys()); const files = await loadWorkingFileList(src_folder); @@ -398,10 +390,10 @@ async function rescanSourceFolders(src_folder) { } /** - * Attempts to locate the app root folder using workspace folders and the appRoot setting + * Attempts to locate the app root folder using workspace folders and the appSourceRoot setting * @returns Absolute path to app root folder or null */ -async function getAppRootFolder() { +async function getAppSourceRootFolder() { /** @type {string} */ let src_folder = null; @@ -412,7 +404,7 @@ async function getAppRootFolder() { } folders.find(folder => { - const main_folder = path.join(folder.uri.replace(/^\w+:\/\//, ''), Settings.appRoot); + const main_folder = path.join(folder.uri.replace(/^\w+:\/\//, ''), Settings.appSourceRoot); try { if (fs.statSync(main_folder).isDirectory()) { src_folder = main_folder; @@ -425,7 +417,7 @@ async function getAppRootFolder() { console.log([ `Failed to find source root from workspace folders:`, ...folders.map(f => ` - ${f.uri}`), - 'Configure the Android App Root value in your workspace settings to point to your source folder containing AndroidManifest.xml', + 'Configure the Android App Source Root value in your workspace settings to point to your source folder containing AndroidManifest.xml', ].join(os.EOL)); } @@ -438,9 +430,9 @@ async function loadWorkingFileList(src_folder) { } trace(`Using src root folder: ${src_folder}. Searching for Android project source files...`); - console.time('source file search') + time('source file search') const files = scanSourceFiles(src_folder); - console.timeEnd('source file search'); + timeEnd('source file search'); if (!files.find(file => /^androidmanifest.xml$/i.test(file.relfpn))) { console.log(`Warning: No AndroidManifest.xml found in app root folder. Check the Android App Root value in your workspace settings.`) @@ -492,16 +484,19 @@ async function loadWorkingFileList(src_folder) { } connection.onDidChangeConfiguration(async (change) => { - trace(`onDidChangeConfiguration`); - if (change && change.settings && change.settings[Settings.ID]) { - const old_app_root = Settings.appRoot; - Settings.onChange(change.settings[Settings.ID]); - if (old_app_root !== Settings.appRoot) { - const src_folder = await getAppRootFolder(); - if (src_folder) { - rescanSourceFolders(src_folder); - reparse([...liveParsers.keys()]); - } + trace(`onDidChangeConfiguration: ${JSON.stringify(change)}`); + const old_app_root = Settings.appSourceRoot; + const newSettings = await connection.workspace.getConfiguration({ + section: "android-dev-ext" + }); + + Settings.set(newSettings); + + if (old_app_root !== Settings.appSourceRoot) { + const src_folder = await getAppSourceRootFolder(); + if (src_folder) { + rescanSourceFolders(src_folder); + reparse([...liveParsers.keys()]); } } }) @@ -523,7 +518,7 @@ documents.onDidChangeContent((change) => { */ async function validateTextDocument(textDocument) { if (androidLibrary instanceof Promise) { - trace('Waiting for Android Library load'); + trace('waiting for Android Library load to complete'); androidLibrary = await androidLibrary; } /** @type {ParseProblem[]} */ diff --git a/langserver/settings.js b/langserver/settings.js index a5dcc73..5aafc6c 100644 --- a/langserver/settings.js +++ b/langserver/settings.js @@ -1,6 +1,7 @@ const defaultSettings = { - appRoot: 'app/src/main' + appSourceRoot: 'app/src/main', + trace: false, } class AndroidProjectSettings { @@ -8,24 +9,22 @@ const defaultSettings = { * The root of the app source folder. * This folder should contain AndroidManifest.xml as well as the asets, res, etc folders */ - appRoot = defaultSettings.appRoot; + appSourceRoot = defaultSettings.appSourceRoot; /** - * The identifier for the language server settings + * True if we log details */ - ID = 'androidJavaLanguageServer'; + trace = defaultSettings.trace; + + updateCount = 0; static Instance = new AndroidProjectSettings(); - /** - * Called when the user edits the settings - * @param {*} values - */ - onChange(values) { - this.set(values); - } - set(values) { + if (!values || typeof values !== 'object') { + return; + } + this.updateCount += 1; console.log(`settings set: ${JSON.stringify(values)}`); for (let key in defaultSettings) { if (Object.prototype.hasOwnProperty.call(values, key)) { @@ -35,20 +34,4 @@ const defaultSettings = { } } - -// function getDocumentSettings(resource) { -// if (!hasConfigurationCapability) { -// return Promise.resolve(projectSettings); -// } -// let result = documentSettings.get(resource); -// if (!result) { -// result = connection.workspace.getConfiguration({ -// scopeUri: resource, -// section: 'androidJavaLanguageServer', -// }); -// documentSettings.set(resource, result); -// } -// return result; -// } - exports.Settings = AndroidProjectSettings.Instance; diff --git a/package.json b/package.json index 6aebb61..00b7377 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,24 @@ "contributes": { "configuration": { "type": "object", - "title": "Java (Android)", + "title": "Android", "properties": { - "androidJavaLanguageServer.maxNumberOfProblems": { + "android-dev-ext.appSourceRoot": { "scope": "resource", - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server." + "type": "string", + "default": "app/src/main", + "description": "Relative path to the app source files. This folder should contain AndroidManifest.xml." + }, + "android-dev-ext.subscriptionKey": { + "scope": "application", + "type": "string", + "default": "" + }, + "android-dev-ext.trace": { + "scope": "resource", + "type": "boolean", + "default": false, + "description": "Enable detailed trace logging." } } },