mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 01:48:18 +00:00
update initial file loading to use URIs passed from the client
changes to the appSourceRoot now require an extension restart
This commit is contained in:
28
extension.js
28
extension.js
@@ -11,7 +11,7 @@ const { selectTargetDevice } = require('./src/utils/device');
|
|||||||
/**
|
/**
|
||||||
* @param {vscode.ExtensionContext} context
|
* @param {vscode.ExtensionContext} context
|
||||||
*/
|
*/
|
||||||
function createLanguageClient(context) {
|
async function createLanguageClient(context) {
|
||||||
// The server is implemented in node
|
// The server is implemented in node
|
||||||
let serverModule = context.asAbsolutePath(path.join('langserver', 'server.js'));
|
let serverModule = context.asAbsolutePath(path.join('langserver', 'server.js'));
|
||||||
// The debug options for the server
|
// The debug options for the server
|
||||||
@@ -33,6 +33,15 @@ function createLanguageClient(context) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appSourceRoot = vscode.workspace.getConfiguration('android-dev-ext').get('appSourceRoot', '');
|
||||||
|
let globSearchRoot = appSourceRoot;
|
||||||
|
if (globSearchRoot) {
|
||||||
|
// for findFiles to work properly, the path cannot begin with slash or have any relative components
|
||||||
|
globSearchRoot = path.normalize(appSourceRoot.replace(/(^[\\/]+)|([\\/]+$)/,''));
|
||||||
|
if (globSearchRoot) globSearchRoot += '/';
|
||||||
|
}
|
||||||
|
const sourceFiles = (await vscode.workspace.findFiles(`${globSearchRoot}**/*.java`, null, 1000, null)).map(uri => uri.toString());
|
||||||
|
|
||||||
// Options to control the language client
|
// Options to control the language client
|
||||||
/** @type {import('vscode-languageclient').LanguageClientOptions} */
|
/** @type {import('vscode-languageclient').LanguageClientOptions} */
|
||||||
let clientOptions = {
|
let clientOptions = {
|
||||||
@@ -43,6 +52,9 @@ function createLanguageClient(context) {
|
|||||||
initializationOptions: {
|
initializationOptions: {
|
||||||
// extensionPath points to the root of the extension (the folder where this file is)
|
// extensionPath points to the root of the extension (the folder where this file is)
|
||||||
extensionPath: context.extensionPath,
|
extensionPath: context.extensionPath,
|
||||||
|
appSourceRoot,
|
||||||
|
sourceFiles,
|
||||||
|
workspaceFolders: (vscode.workspace.workspaceFolders || []).map(z => z.uri.toString()),
|
||||||
},
|
},
|
||||||
synchronize: {
|
synchronize: {
|
||||||
// Notify the server about file changes to '.java files contained in the workspace
|
// Notify the server about file changes to '.java files contained in the workspace
|
||||||
@@ -63,6 +75,9 @@ function createLanguageClient(context) {
|
|||||||
let languageClient;
|
let languageClient;
|
||||||
let languageSupportEnabled = false;
|
let languageSupportEnabled = false;
|
||||||
function refreshLanguageServerEnabledState() {
|
function refreshLanguageServerEnabledState() {
|
||||||
|
if (!languageClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let langSupport = vscode.workspace.getConfiguration('android-dev-ext').get('languageSupport', false);
|
let langSupport = vscode.workspace.getConfiguration('android-dev-ext').get('languageSupport', false);
|
||||||
if (langSupport === languageSupportEnabled) {
|
if (langSupport === languageSupportEnabled) {
|
||||||
return;
|
return;
|
||||||
@@ -83,15 +98,18 @@ function refreshLanguageServerEnabledState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// this method is called when your extension is activated
|
/**
|
||||||
// your extension is activated the very first time the command is executed
|
* @param {vscode.ExtensionContext} context
|
||||||
|
*/
|
||||||
function activate(context) {
|
function activate(context) {
|
||||||
|
|
||||||
/* Only the logcat stuff is configured here. The debugger is launched from src/debugMain.js */
|
/* Only the logcat stuff is configured here. The debugger is launched from src/debugMain.js */
|
||||||
AndroidContentProvider.register(context, vscode.workspace);
|
AndroidContentProvider.register(context, vscode.workspace);
|
||||||
|
|
||||||
languageClient = createLanguageClient(context);
|
createLanguageClient(context).then(client => {
|
||||||
|
languageClient = client;
|
||||||
refreshLanguageServerEnabledState();
|
refreshLanguageServerEnabledState();
|
||||||
|
});
|
||||||
|
|
||||||
// The commandId parameter must match the command field in package.json
|
// The commandId parameter must match the command field in package.json
|
||||||
const disposables = [
|
const disposables = [
|
||||||
@@ -136,8 +154,6 @@ function activate(context) {
|
|||||||
// trying to shut down the language server in the middle of a change-configuration request
|
// trying to shut down the language server in the middle of a change-configuration request
|
||||||
process.nextTick(() => refreshLanguageServerEnabledState());
|
process.nextTick(() => refreshLanguageServerEnabledState());
|
||||||
}),
|
}),
|
||||||
|
|
||||||
languageClient,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
context.subscriptions.splice(context.subscriptions.length, 0, ...disposables);
|
context.subscriptions.splice(context.subscriptions.length, 0, ...disposables);
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const os = require('os');
|
|
||||||
const { CEIType } = require('java-mti');
|
const { CEIType } = require('java-mti');
|
||||||
const { Settings } = require('./settings');
|
|
||||||
const ParseProblem = require('./java/parsetypes/parse-problem');
|
const ParseProblem = require('./java/parsetypes/parse-problem');
|
||||||
const { parse } = require('./java/body-parser');
|
const { parse } = require('./java/body-parser');
|
||||||
const { SourceUnit } = require('./java/source-types');
|
const { SourceUnit } = require('./java/source-types');
|
||||||
@@ -211,8 +207,7 @@ class ParsedInfo {
|
|||||||
*/
|
*/
|
||||||
function reparse(uris, liveParsers, androidLibrary, opts) {
|
function reparse(uris, liveParsers, androidLibrary, opts) {
|
||||||
trace(`reparse`);
|
trace(`reparse`);
|
||||||
// we must have at least one URI
|
if (!Array.isArray(uris)) {
|
||||||
if (!uris || !uris.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (first_parse_waiting) {
|
if (first_parse_waiting) {
|
||||||
@@ -239,9 +234,6 @@ function reparse(uris, liveParsers, androidLibrary, opts) {
|
|||||||
cached_units.push(docinfo.parsed.unit);
|
cached_units.push(docinfo.parsed.unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!parsers.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each parse uses a unique typemap, initialised from the android library
|
// Each parse uses a unique typemap, initialised from the android library
|
||||||
const typemap = new Map(androidLibrary);
|
const typemap = new Map(androidLibrary);
|
||||||
@@ -283,150 +275,9 @@ function reparse(uris, liveParsers, androidLibrary, opts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called during initialization and whenever the App Source Root setting is changed to scan
|
|
||||||
* for source files
|
|
||||||
*
|
|
||||||
* @param {string} src_folder absolute path to the source root
|
|
||||||
* @param {Map<string,JavaDocInfo>} liveParsers
|
|
||||||
*/
|
|
||||||
async function rescanSourceFolders(src_folder, liveParsers) {
|
|
||||||
if (!src_folder) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// create live parsers for all the java files, but don't replace any existing ones which
|
|
||||||
// have been loaded (and may be edited) before we reach here
|
|
||||||
for (let file of files) {
|
|
||||||
if (!/\.java$/i.test(file.fpn)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const uri = `file://${file.fpn}`; // todo - handle case-differences on Windows
|
|
||||||
unused_keys.delete(uri);
|
|
||||||
|
|
||||||
if (liveParsers.has(uri)) {
|
|
||||||
trace(`already loaded: ${uri}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// read the full file content
|
|
||||||
const file_content = await new Promise((res, rej) => fs.readFile(file.fpn, 'UTF8', (err,data) => err ? rej(err) : res(data)));
|
|
||||||
// construct a new JavaDoc instance for the source file
|
|
||||||
liveParsers.set(uri, new JavaDocInfo(uri, file_content, 0));
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any parsers that are no longer part of the working set
|
|
||||||
unused_keys.forEach(uri => liveParsers.delete(uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to locate the app root folder using workspace folders and the appSourceRoot setting
|
|
||||||
* @param {*} workspace
|
|
||||||
* @returns Absolute path to app root folder or null
|
|
||||||
*/
|
|
||||||
async function getAppSourceRootFolder(workspace) {
|
|
||||||
/** @type {string} */
|
|
||||||
let src_folder = null;
|
|
||||||
|
|
||||||
const folders = await workspace.getWorkspaceFolders();
|
|
||||||
if (!folders || !folders.length) {
|
|
||||||
trace('No workspace folders');
|
|
||||||
return src_folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
folders.find(folder => {
|
|
||||||
const main_folder = path.join(folder.uri.replace(/^\w+:\/\//, ''), Settings.appSourceRoot);
|
|
||||||
try {
|
|
||||||
if (fs.statSync(main_folder).isDirectory()) {
|
|
||||||
src_folder = main_folder;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!src_folder) {
|
|
||||||
console.log([
|
|
||||||
`Failed to find source root from workspace folders:`,
|
|
||||||
...folders.map(f => ` - ${f.uri}`),
|
|
||||||
'Configure the Android App Source Root value in your workspace settings to point to your source folder containing AndroidManifest.xml',
|
|
||||||
].join(os.EOL));
|
|
||||||
}
|
|
||||||
|
|
||||||
return src_folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadWorkingFileList(src_folder) {
|
|
||||||
if (!src_folder) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
trace(`Using src root folder: ${src_folder}. Searching for Android project source files...`);
|
|
||||||
time('source file search')
|
|
||||||
const files = scanSourceFiles(src_folder);
|
|
||||||
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 Source Root value in your workspace settings.`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return files;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} base_folder
|
|
||||||
* @returns {{fpn:string, relfpn: string, stat:fs.Stats}[]}
|
|
||||||
*/
|
|
||||||
function scanSourceFiles(base_folder) {
|
|
||||||
// strip any trailing slash
|
|
||||||
base_folder = base_folder.replace(/[\\/]+$/, '');
|
|
||||||
const done = new Set(), folders = [base_folder], files = [];
|
|
||||||
const max_folders = 100;
|
|
||||||
while (folders.length) {
|
|
||||||
const folder = folders.shift();
|
|
||||||
if (done.has(folder)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
done.add(folder);
|
|
||||||
if (done.size > max_folders) {
|
|
||||||
console.log(`Max folder limit reached - cancelling file search`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
trace(`scan source folder ${folder}`)
|
|
||||||
fs.readdirSync(folder)
|
|
||||||
.forEach(name => {
|
|
||||||
const fpn = path.join(folder, name);
|
|
||||||
const stat = fs.statSync(fpn);
|
|
||||||
files.push({
|
|
||||||
fpn,
|
|
||||||
// relative path (without leading slash)
|
|
||||||
relfpn: fpn.slice(base_folder.length + 1),
|
|
||||||
stat,
|
|
||||||
});
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
folders.push(fpn)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
trace(`Failed to scan source folder ${folder}: ${err.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.indexAt = indexAt;
|
exports.indexAt = indexAt;
|
||||||
exports.positionAt = positionAt;
|
exports.positionAt = positionAt;
|
||||||
exports.FileURIMap = FileURIMap;
|
exports.FileURIMap = FileURIMap;
|
||||||
exports.JavaDocInfo = JavaDocInfo;
|
exports.JavaDocInfo = JavaDocInfo;
|
||||||
exports.ParsedInfo = ParsedInfo;
|
exports.ParsedInfo = ParsedInfo;
|
||||||
exports.reparse = reparse;
|
exports.reparse = reparse;
|
||||||
exports.getAppSourceRootFolder = getAppSourceRootFolder;
|
|
||||||
exports.rescanSourceFolders = rescanSourceFolders;
|
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"java-mti": "adelphes/java-mti#c8e1d3c",
|
"java-mti": "adelphes/java-mti#c8e1d3c",
|
||||||
"vscode-languageserver": "6.1.1",
|
"vscode-languageserver": "6.1.1",
|
||||||
"vscode-languageserver-textdocument": "1.0.1"
|
"vscode-languageserver-textdocument": "1.0.1",
|
||||||
|
"vscode-uri": "2.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^13.13.4"
|
"@types/node": "^13.13.4"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const {
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
const { TextDocument } = require('vscode-languageserver-textdocument');
|
const { TextDocument } = require('vscode-languageserver-textdocument');
|
||||||
|
const { URI } = require('vscode-uri');
|
||||||
|
|
||||||
const { loadAndroidSystemLibrary } = require('./java/java-libraries');
|
const { loadAndroidSystemLibrary } = require('./java/java-libraries');
|
||||||
const { CEIType } = require('java-mti');
|
const { CEIType } = require('java-mti');
|
||||||
@@ -16,7 +17,7 @@ const { Settings } = require('./settings');
|
|||||||
const { trace } = require('./logging');
|
const { trace } = require('./logging');
|
||||||
const { getCompletionItems, resolveCompletionItem } = require('./completions');
|
const { getCompletionItems, resolveCompletionItem } = require('./completions');
|
||||||
const { getSignatureHelp } = require('./method-signatures');
|
const { getSignatureHelp } = require('./method-signatures');
|
||||||
const { getAppSourceRootFolder, FileURIMap, JavaDocInfo, indexAt, reparse, rescanSourceFolders } = require('./document');
|
const { FileURIMap, JavaDocInfo, indexAt, reparse } = require('./document');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The global map of Android system types
|
* The global map of Android system types
|
||||||
@@ -118,6 +119,33 @@ connection.onInitialize((params) => {
|
|||||||
|
|
||||||
hasWorkspaceFolderCapability = capabilities.workspace && !!capabilities.workspace.workspaceFolders;
|
hasWorkspaceFolderCapability = capabilities.workspace && !!capabilities.workspace.workspaceFolders;
|
||||||
|
|
||||||
|
if (params.initializationOptions) {
|
||||||
|
/** @type {string[]} */
|
||||||
|
const file_uris = params.initializationOptions.sourceFiles || [];
|
||||||
|
for (let file_uri of file_uris) {
|
||||||
|
const file = URI.parse(file_uri, true);
|
||||||
|
const filePath = file.fsPath;
|
||||||
|
if (!/.java/i.test(filePath)) {
|
||||||
|
trace(`ignoring non-java file: ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (liveParsers.has(file_uri)) {
|
||||||
|
trace(`File already loaded: ${file_uri}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// it's fine to load the initial file set synchronously - the language server runs in a
|
||||||
|
// separate process and nothing (useful) can happen until the first parse is complete.
|
||||||
|
const content = fs.readFileSync(file.fsPath, 'utf8');
|
||||||
|
liveParsers.set(file_uri, new JavaDocInfo(file_uri, content, 0));
|
||||||
|
trace(`Added initial file: ${file_uri}`);
|
||||||
|
} catch (err) {
|
||||||
|
trace(`Failed to load initial source file: ${filePath}. ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reparse([...liveParsers.keys()], liveParsers, androidLibrary, { includeMethods: false, first_parse: true });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
capabilities: {
|
capabilities: {
|
||||||
// we support incremental updates
|
// we support incremental updates
|
||||||
@@ -155,18 +183,11 @@ connection.onInitialized(async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const src_folder = await getAppSourceRootFolder(connection.workspace);
|
|
||||||
if (src_folder) {
|
|
||||||
await rescanSourceFolders(src_folder, liveParsers);
|
|
||||||
reparse([...liveParsers.keys()], liveParsers, androidLibrary, { includeMethods: false, first_parse: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
trace('Initialization complete');
|
trace('Initialization complete');
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.onDidChangeConfiguration(async (change) => {
|
connection.onDidChangeConfiguration(async (change) => {
|
||||||
trace(`onDidChangeConfiguration: ${JSON.stringify(change)}`);
|
trace(`onDidChangeConfiguration: ${JSON.stringify(change)}`);
|
||||||
const old_app_root = Settings.appSourceRoot;
|
|
||||||
|
|
||||||
// fetch and update the settings
|
// fetch and update the settings
|
||||||
const newSettings = await connection.workspace.getConfiguration({
|
const newSettings = await connection.workspace.getConfiguration({
|
||||||
@@ -174,15 +195,6 @@ connection.onDidChangeConfiguration(async (change) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Settings.set(newSettings);
|
Settings.set(newSettings);
|
||||||
|
|
||||||
if (old_app_root !== Settings.appSourceRoot) {
|
|
||||||
// if the app root has changed, rescan the source folder
|
|
||||||
const src_folder = await getAppSourceRootFolder(connection.workspace);
|
|
||||||
if (src_folder) {
|
|
||||||
rescanSourceFolders(src_folder, liveParsers);
|
|
||||||
reparse([...liveParsers.keys()], liveParsers, androidLibrary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
documents.onDidClose((e) => {
|
documents.onDidClose((e) => {
|
||||||
@@ -201,13 +213,15 @@ connection.onDidChangeWatchedFiles(
|
|||||||
case 1: // create
|
case 1: // create
|
||||||
// if the user creates the file directly in vscode, the file will automatically open (and we receive an open callback)
|
// if the user creates the file directly in vscode, the file will automatically open (and we receive an open callback)
|
||||||
// - but if the user creates or copies a file into the workspace, we need to manually add it to the set.
|
// - but if the user creates or copies a file into the workspace, we need to manually add it to the set.
|
||||||
if (!liveParsers.has(change.uri) && /^file:\/\//.test(change.uri)) {
|
if (!liveParsers.has(change.uri)) {
|
||||||
trace(`file added: ${change.uri}`)
|
trace(`file added: ${change.uri}`)
|
||||||
try {
|
try {
|
||||||
const fname = change.uri.replace(/^file:\/\//, '');
|
const fname = URI.parse(change.uri, true).fsPath;
|
||||||
liveParsers.set(change.uri, new JavaDocInfo(change.uri, fs.readFileSync(fname, 'utf8'), 0));
|
liveParsers.set(change.uri, new JavaDocInfo(change.uri, fs.readFileSync(fname, 'utf8'), 0));
|
||||||
files_changed = true;
|
files_changed = true;
|
||||||
} catch {}
|
} catch (err) {
|
||||||
|
console.log(`Failed to add new file '${change.uri}' to working set. ${err.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2: // change
|
case 2: // change
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ const defaultSettings = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.updateCount += 1;
|
this.updateCount += 1;
|
||||||
console.log(`settings set: ${JSON.stringify(values)}`);
|
|
||||||
for (let key in defaultSettings) {
|
for (let key in defaultSettings) {
|
||||||
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
||||||
this[key] = values[key];
|
this[key] = values[key];
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
"scope": "resource",
|
"scope": "resource",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "app/src/main",
|
"default": "app/src/main",
|
||||||
"description": "Workspace-relative path to the app source files. The specified folder should contain AndroidManifest.xml."
|
"description": "Workspace-relative path to the app source files. The specified folder should contain AndroidManifest.xml.\r\nChanges to this field require the extension to be restarted."
|
||||||
},
|
},
|
||||||
"android-dev-ext.subscriptionKey": {
|
"android-dev-ext.subscriptionKey": {
|
||||||
"scope": "application",
|
"scope": "application",
|
||||||
|
|||||||
Reference in New Issue
Block a user