mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 01:48:18 +00:00
add support for editing multiple files
This commit is contained in:
@@ -36,58 +36,98 @@ let androidLibrary = null;
|
|||||||
let connection = createConnection(ProposedFeatures.all);
|
let connection = createConnection(ProposedFeatures.all);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef LiveParseInfo
|
*
|
||||||
* @property {string} uri
|
* @param {{line:number,character:number}} pos
|
||||||
* @property {JavaTokenizer.LineInfo[]} lines
|
* @param {string} content
|
||||||
* @property {{startState: string, states: string[], endState: string}[]} states
|
|
||||||
*/
|
*/
|
||||||
|
function indexAt(pos, content) {
|
||||||
|
let idx = 0;
|
||||||
|
for (let i = 0; i < pos.line; i++) {
|
||||||
|
idx = content.indexOf('\n', idx) + 1;
|
||||||
|
if (idx === 0) {
|
||||||
|
return content.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.min(idx + pos.character, content.length);
|
||||||
|
}
|
||||||
|
|
||||||
///** @type {LiveParseInfo[]} */
|
/**
|
||||||
//const liveParsers = [];
|
* @param {number} index
|
||||||
/** @type {{content: string, uri: string, result: {unit:SourceUnit, problems:*[]}, typemap:Map<string,CEIType>, positionAt:(n) => Position, indexAt:(p:Position) => number}} */
|
* @param {string} content
|
||||||
let parsed = null;
|
*/
|
||||||
|
function positionAt(index, content) {
|
||||||
|
let line = 0,
|
||||||
|
last_nl_idx = 0,
|
||||||
|
character = 0;
|
||||||
|
if (index <= 0) return { line, character };
|
||||||
|
for (let idx = 0; ;) {
|
||||||
|
idx = content.indexOf('\n', idx) + 1;
|
||||||
|
if (idx === 0 || idx > index) {
|
||||||
|
if (idx === 0) index = content.length;
|
||||||
|
character = index - last_nl_idx;
|
||||||
|
return { line, character };
|
||||||
|
}
|
||||||
|
last_nl_idx = idx;
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function reparse(uri, content) {
|
class JavaDocInfo {
|
||||||
|
/**
|
||||||
|
* @param {string} uri
|
||||||
|
* @param {string} content
|
||||||
|
* @param {number} version
|
||||||
|
*/
|
||||||
|
constructor(uri, content, version) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.content = content;
|
||||||
|
this.version = version;
|
||||||
|
/** @type {ParsedInfo} */
|
||||||
|
this.parsed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParsedInfo {
|
||||||
|
/**
|
||||||
|
* @param {string} uri
|
||||||
|
* @param {string} content
|
||||||
|
* @param {number} version
|
||||||
|
* @param {Map<string,CEIType>} typemap
|
||||||
|
* @param {SourceUnit} unit
|
||||||
|
* @param {ParseProblem[]} problems
|
||||||
|
*/
|
||||||
|
constructor(uri, content, version, typemap, unit, problems) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.content = content;
|
||||||
|
this.version = version;
|
||||||
|
this.typemap = typemap;
|
||||||
|
this.unit = unit;
|
||||||
|
this.problems = problems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Map<string,JavaDocInfo>} */
|
||||||
|
const liveParsers = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} uri
|
||||||
|
*/
|
||||||
|
function reparse(uri) {
|
||||||
if (androidLibrary instanceof Promise) {
|
if (androidLibrary instanceof Promise) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const doc = liveParsers.get(uri);
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { content, version } = doc;
|
||||||
const typemap = new Map(androidLibrary);
|
const typemap = new Map(androidLibrary);
|
||||||
const result = parse(content, typemap);
|
const result = parse(content, typemap);
|
||||||
if (result) {
|
if (result) {
|
||||||
parseMethodBodies(result.unit, typemap);
|
parseMethodBodies(result.unit, typemap);
|
||||||
}
|
}
|
||||||
parsed = {
|
doc.parsed = new ParsedInfo(uri, content, version, typemap, result.unit, result.problems);
|
||||||
content,
|
|
||||||
uri,
|
|
||||||
result,
|
|
||||||
typemap,
|
|
||||||
positionAt(n) {
|
|
||||||
let line = 0,
|
|
||||||
last_nl_idx = 0,
|
|
||||||
character = 0;
|
|
||||||
if (n <= 0) return { line, character };
|
|
||||||
for (let idx = 0; ;) {
|
|
||||||
idx = this.content.indexOf('\n', idx) + 1;
|
|
||||||
if (idx === 0 || idx > n) {
|
|
||||||
if (idx === 0) n = content.length;
|
|
||||||
character = n - last_nl_idx;
|
|
||||||
return { line, character };
|
|
||||||
}
|
|
||||||
last_nl_idx = idx;
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
indexAt(pos) {
|
|
||||||
let idx = 0;
|
|
||||||
for (let i = 0; i < pos.line; i++) {
|
|
||||||
idx = this.content.indexOf('\n', idx) + 1;
|
|
||||||
if (idx === 0) {
|
|
||||||
return this.content.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.min(idx + pos.character, this.content.length);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a simple text document manager. The text document manager
|
// Create a simple text document manager. The text document manager
|
||||||
@@ -101,21 +141,10 @@ let documents = new TextDocuments({
|
|||||||
* @param {string} content
|
* @param {string} content
|
||||||
*/
|
*/
|
||||||
create(uri, languageId, version, content) {
|
create(uri, languageId, version, content) {
|
||||||
//connection.console.log(JSON.stringify({what:'create',uri,languageId,version,content}));
|
|
||||||
// tokenize the file content and build the initial parse state
|
// tokenize the file content and build the initial parse state
|
||||||
connection.console.log(`create parse ${version}`);
|
connection.console.log(`create parse ${version}`);
|
||||||
reparse(uri, content);
|
liveParsers.set(uri, new JavaDocInfo(uri, content, version));
|
||||||
//connection.console.log(res.imports.length.toString());
|
reparse(uri);
|
||||||
// const lines = JavaTokenizer.get().tokenizeSource(content);
|
|
||||||
// const initialParse = new JavaParser().parseLines(lines);
|
|
||||||
|
|
||||||
// liveParsers.push({
|
|
||||||
// uri,
|
|
||||||
// lines,
|
|
||||||
// states: initialParse,
|
|
||||||
// })
|
|
||||||
// console.log(initialParse.map(x => x.decls).filter(x => x.length).map(x => JSON.stringify(x, null, ' ')));
|
|
||||||
|
|
||||||
return { uri };
|
return { uri };
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -126,22 +155,25 @@ let documents = new TextDocuments({
|
|||||||
*/
|
*/
|
||||||
update(document, changes, version) {
|
update(document, changes, version) {
|
||||||
connection.console.log(JSON.stringify({ what: 'update', /* changes, */ version }));
|
connection.console.log(JSON.stringify({ what: 'update', /* changes, */ version }));
|
||||||
//connection.console.log(`update ${version}`);
|
if (!document || !liveParsers.has(document.uri)) {
|
||||||
//return document;
|
return;
|
||||||
if (parsed && document && parsed.uri === document.uri) {
|
|
||||||
changes.forEach((change) => {
|
|
||||||
/** @type {import('vscode-languageserver').Range} */
|
|
||||||
const r = change['range'];
|
|
||||||
if (r) {
|
|
||||||
const start_index = parsed.indexAt(r.start);
|
|
||||||
let end_index = start_index + (r.end.character - r.start.character);
|
|
||||||
if (r.end.line !== r.start.line) end_index = parsed.indexAt(r.end);
|
|
||||||
parsed.content = `${parsed.content.slice(0, start_index)}${change.text}${parsed.content.slice(end_index)}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//connection.console.log(JSON.stringify(parsed.content));
|
|
||||||
reparse(document.uri, parsed.content);
|
|
||||||
}
|
}
|
||||||
|
const docinfo = liveParsers.get(document.uri);
|
||||||
|
if (!docinfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
changes.forEach((change) => {
|
||||||
|
/** @type {import('vscode-languageserver').Range} */
|
||||||
|
const r = change['range'];
|
||||||
|
if (r) {
|
||||||
|
const start_index = indexAt(r.start, docinfo.content);
|
||||||
|
let end_index = start_index + (r.end.character - r.start.character);
|
||||||
|
if (r.end.line !== r.start.line) end_index = indexAt(r.end, docinfo.content);
|
||||||
|
docinfo.content = `${docinfo.content.slice(0, start_index)}${change.text}${docinfo.content.slice(end_index)}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reparse(document.uri);
|
||||||
return document;
|
return document;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -239,7 +271,7 @@ function getDocumentSettings(resource) {
|
|||||||
// Only keep settings for open documents
|
// Only keep settings for open documents
|
||||||
documents.onDidClose((e) => {
|
documents.onDidClose((e) => {
|
||||||
connection.console.log(`doc closed ${e.document.uri}`);
|
connection.console.log(`doc closed ${e.document.uri}`);
|
||||||
parsed = null;
|
liveParsers.delete(e.document.uri);
|
||||||
documentSettings.delete(e.document.uri);
|
documentSettings.delete(e.document.uri);
|
||||||
connection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] });
|
connection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] });
|
||||||
});
|
});
|
||||||
@@ -261,9 +293,10 @@ async function validateTextDocument(textDocument) {
|
|||||||
}
|
}
|
||||||
/** @type {ParseProblem[]} */
|
/** @type {ParseProblem[]} */
|
||||||
let problems = [];
|
let problems = [];
|
||||||
connection.console.log('validateTextDocument');
|
const parsed = liveParsers.get(textDocument.uri);
|
||||||
|
|
||||||
if (parsed && parsed.result) {
|
|
||||||
|
if (parsed) {
|
||||||
try {
|
try {
|
||||||
//problems = [...parsed.result.problems, ...validate(parsed.result.unit, parsed.typemap)];
|
//problems = [...parsed.result.problems, ...validate(parsed.result.unit, parsed.typemap)];
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@@ -274,8 +307,8 @@ async function validateTextDocument(textDocument) {
|
|||||||
const diagnostics = problems
|
const diagnostics = problems
|
||||||
.filter((p) => p)
|
.filter((p) => p)
|
||||||
.map((p) => {
|
.map((p) => {
|
||||||
const start = parsed.positionAt(p.startIdx);
|
const start = positionAt(p.startIdx, parsed.content);
|
||||||
const end = parsed.positionAt(p.endIdx);
|
const end = positionAt(p.endIdx, parsed.content);
|
||||||
/** @type {Diagnostic} */
|
/** @type {Diagnostic} */
|
||||||
let diagnostic = {
|
let diagnostic = {
|
||||||
severity: p.severity,
|
severity: p.severity,
|
||||||
@@ -449,10 +482,10 @@ function getTypedNameCompletion(typemap, type_signature, opts, typelist) {
|
|||||||
*/
|
*/
|
||||||
function getFullyQualifiedDottedIdentCompletion(typemap, dotted_name, opts) {
|
function getFullyQualifiedDottedIdentCompletion(typemap, dotted_name, opts) {
|
||||||
if (dotted_name === '') {
|
if (dotted_name === '') {
|
||||||
return getPackageCompletion('');
|
return getPackageCompletion(typemap, '');
|
||||||
}
|
}
|
||||||
// name is a fully dotted name, possibly including members and their fields
|
// name is a fully dotted name, possibly including members and their fields
|
||||||
let typelist = [...parsed.typemap.keys()];
|
let typelist = [...typemap.keys()];
|
||||||
|
|
||||||
const split_name = dotted_name.split('.');
|
const split_name = dotted_name.split('.');
|
||||||
let pkgname = '';
|
let pkgname = '';
|
||||||
@@ -461,14 +494,14 @@ function getFullyQualifiedDottedIdentCompletion(typemap, dotted_name, opts) {
|
|||||||
for (let name_part of split_name) {
|
for (let name_part of split_name) {
|
||||||
if (type) {
|
if (type) {
|
||||||
if (opts.statics && typelist.includes(`${typename}$${name_part}`)) {
|
if (opts.statics && typelist.includes(`${typename}$${name_part}`)) {
|
||||||
type = parsed.typemap.get(typename = `${typename}$${name_part}`);
|
type = typemap.get(typename = `${typename}$${name_part}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
typename = pkgname + name_part;
|
typename = pkgname + name_part;
|
||||||
if (typelist.includes(typename)) {
|
if (typelist.includes(typename)) {
|
||||||
type = parsed.typemap.get(typename);
|
type = typemap.get(typename);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pkgname = `${pkgname}${name_part}/`;
|
pkgname = `${pkgname}${name_part}/`;
|
||||||
@@ -507,8 +540,11 @@ function getFullyQualifiedDottedIdentCompletion(typemap, dotted_name, opts) {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRootPackageCompletions() {
|
/**
|
||||||
const pkgs = [...parsed.typemap.keys()].reduce((set,typename) => {
|
* @param {Map<string,CEIType>} typemap
|
||||||
|
*/
|
||||||
|
function getRootPackageCompletions(typemap) {
|
||||||
|
const pkgs = [...typemap.keys()].reduce((set,typename) => {
|
||||||
const m = typename.match(/(.+?)\//);
|
const m = typename.match(/(.+?)\//);
|
||||||
m && set.add(m[1]);
|
m && set.add(m[1]);
|
||||||
return set;
|
return set;
|
||||||
@@ -520,13 +556,17 @@ function getRootPackageCompletions() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPackageCompletion(pkg) {
|
/**
|
||||||
|
* @param {Map<string,CEIType>} typemap
|
||||||
|
* @param {string} pkg
|
||||||
|
*/
|
||||||
|
function getPackageCompletion(typemap, pkg) {
|
||||||
if (pkg === '') {
|
if (pkg === '') {
|
||||||
return getRootPackageCompletions();
|
return getRootPackageCompletions(typemap);
|
||||||
}
|
}
|
||||||
// sub-package
|
// sub-package
|
||||||
const search_pkg = pkg + '/';
|
const search_pkg = pkg + '/';
|
||||||
const pkgs = [...parsed.typemap.keys()].reduce((arr,typename) => {
|
const pkgs = [...typemap.keys()].reduce((arr,typename) => {
|
||||||
if (typename.startsWith(search_pkg)) {
|
if (typename.startsWith(search_pkg)) {
|
||||||
const m = typename.slice(search_pkg.length).match(/^(.+?)\//);
|
const m = typename.slice(search_pkg.length).match(/^(.+?)\//);
|
||||||
if (m) arr.add(m[1]);
|
if (m) arr.add(m[1]);
|
||||||
@@ -542,6 +582,8 @@ function getPackageCompletion(pkg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let defaultCompletionTypes = null;
|
let defaultCompletionTypes = null;
|
||||||
|
/** @type {Map<string,CEIType>} */
|
||||||
|
let lastCompletionTypeMap = null;
|
||||||
const typeKindMap = {
|
const typeKindMap = {
|
||||||
class: CompletionItemKind.Class,
|
class: CompletionItemKind.Class,
|
||||||
interface: CompletionItemKind.Interface,
|
interface: CompletionItemKind.Interface,
|
||||||
@@ -586,7 +628,7 @@ function initDefaultCompletionTypes(lib) {
|
|||||||
).sort((a,b) => a.label.localeCompare(b.label, undefined, {sensitivity:'base'})),
|
).sort((a,b) => a.label.localeCompare(b.label, undefined, {sensitivity:'base'})),
|
||||||
|
|
||||||
// package names
|
// package names
|
||||||
packageNames: getRootPackageCompletions(),
|
packageNames: getRootPackageCompletions(lib),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,16 +645,19 @@ connection.onCompletion(
|
|||||||
if (androidLibrary instanceof Promise) {
|
if (androidLibrary instanceof Promise) {
|
||||||
androidLibrary = await androidLibrary;
|
androidLibrary = await androidLibrary;
|
||||||
}
|
}
|
||||||
const lib = (parsed && parsed.typemap) || androidLibrary;
|
const docinfo = liveParsers.get(_textDocumentPosition.textDocument.uri);
|
||||||
if (!lib) return [];
|
if (!docinfo || !docinfo.parsed) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const parsed = docinfo.parsed;
|
||||||
|
const lib = lastCompletionTypeMap = (parsed && parsed.typemap) || androidLibrary;
|
||||||
let locals = [], sourceTypes = [], show_instances = false;
|
let locals = [], sourceTypes = [], show_instances = false;
|
||||||
if (parsed.result && parsed.result.unit) {
|
if (parsed.unit) {
|
||||||
const index = parsed.indexAt(_textDocumentPosition.position);
|
const index = indexAt(_textDocumentPosition.position, parsed.content);
|
||||||
const options = parsed.result.unit.getCompletionOptionsAt(index);
|
const options = parsed.unit.getCompletionOptionsAt(index);
|
||||||
console.log(options);
|
|
||||||
if (options.loc) {
|
if (options.loc) {
|
||||||
if (/^pkgname:/.test(options.loc.key)) {
|
if (/^pkgname:/.test(options.loc.key)) {
|
||||||
return getPackageCompletion(options.loc.key.split(':').pop());
|
return getPackageCompletion(parsed.typemap, options.loc.key.split(':').pop());
|
||||||
}
|
}
|
||||||
if (/^fqdi:/.test(options.loc.key)) {
|
if (/^fqdi:/.test(options.loc.key)) {
|
||||||
// fully-qualified type/field name
|
// fully-qualified type/field name
|
||||||
@@ -635,7 +680,7 @@ connection.onCompletion(
|
|||||||
sortText: p.name,
|
sortText: p.name,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
sourceTypes = parsed.result.unit.types.map(t => ({
|
sourceTypes = parsed.unit.types.map(t => ({
|
||||||
label: t.dottedTypeName,
|
label: t.dottedTypeName,
|
||||||
kind: typeKindMap[t.typeKind],
|
kind: typeKindMap[t.typeKind],
|
||||||
data: { type:t.shortSignature },
|
data: { type:t.shortSignature },
|
||||||
@@ -672,13 +717,13 @@ connection.onCompletionResolve(
|
|||||||
*/
|
*/
|
||||||
(item) => {
|
(item) => {
|
||||||
item.detail = item.documentation = '';
|
item.detail = item.documentation = '';
|
||||||
if (!parsed || !parsed.typemap) {
|
if (!lastCompletionTypeMap) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
if (typeof item.data !== 'object') {
|
if (typeof item.data !== 'object') {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
const t = parsed.typemap.get(item.data.type);
|
const t = lastCompletionTypeMap.get(item.data.type);
|
||||||
const field = t && t.fields[item.data.fidx];
|
const field = t && t.fields[item.data.fidx];
|
||||||
const method = t && t.methods[item.data.midx];
|
const method = t && t.methods[item.data.midx];
|
||||||
if (!t) {
|
if (!t) {
|
||||||
|
|||||||
Reference in New Issue
Block a user