mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-23 01:48:18 +00:00
first hacky version of source parsing and type checking
This commit is contained in:
@@ -11,327 +11,498 @@ const {
|
||||
CompletionItem,
|
||||
CompletionItemKind,
|
||||
TextDocumentSyncKind,
|
||||
Position,
|
||||
//TextDocumentPositionParams
|
||||
} = require('vscode-languageserver');
|
||||
|
||||
const { TextDocument } = require('vscode-languageserver-textdocument');
|
||||
} = require('vscode-languageserver');
|
||||
|
||||
const MTI = require('./java/mti');
|
||||
let androidLibrary = null;
|
||||
function loadAndroidLibrary(retry) {
|
||||
const { TextDocument } = require('vscode-languageserver-textdocument');
|
||||
|
||||
const MTI = require('./java/mti');
|
||||
const { parse, ParseProblem, ProblemSeverity, ParseResult } = require('./java/parser');
|
||||
const { resolveImports } = require('./java/import-resolver');
|
||||
|
||||
let androidLibrary = null;
|
||||
function loadAndroidLibrary(retry) {
|
||||
try {
|
||||
androidLibrary = MTI.unpackJSON('/tmp/jarscanner/android-25/android-25.json');
|
||||
connection.console.log(`Android type cache loaded: ${androidLibrary.types.length} types from ${androidLibrary.packages.length} packages.`);
|
||||
androidLibrary = MTI.unpackJSON('/tmp/jarscanner/android-25/android-25.json');
|
||||
connection.console.log(`Android type cache loaded: ${androidLibrary.types.length} types from ${androidLibrary.packages.length} packages.`);
|
||||
} catch (e) {
|
||||
connection.console.log(`Failed to load android type cache`);
|
||||
if (retry) {
|
||||
return;
|
||||
}
|
||||
connection.console.log(`Rebuilding type cache...`);
|
||||
const jarscanner = require(`jarscanner/jarscanner`);
|
||||
fs.mkdir('/tmp/jarscanner', err => {
|
||||
if (err) {
|
||||
connection.console.log(`Cannot create type cache folder. ${err.message}.`);
|
||||
return
|
||||
connection.console.log(`Failed to load android type cache`);
|
||||
if (retry) {
|
||||
return;
|
||||
}
|
||||
jarscanner.process_android_sdk_source({
|
||||
destpath: '/tmp/jarscanner',
|
||||
sdkpath: process.env['ANDROID_SDK'],
|
||||
api: 25,
|
||||
cleandest: true,
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
connection.console.log(`Android cache build failed. ${err.message}.`);
|
||||
return
|
||||
}
|
||||
loadAndroidLibrary(true);
|
||||
})
|
||||
})
|
||||
connection.console.log(`Rebuilding type cache...`);
|
||||
const jarscanner = require(`jarscanner/jarscanner`);
|
||||
fs.mkdir('/tmp/jarscanner', (err) => {
|
||||
if (err && err.errno !== -17) {
|
||||
connection.console.log(`Cannot create type cache folder. ${err.message}.`);
|
||||
return;
|
||||
}
|
||||
jarscanner.process_android_sdk_source(
|
||||
{
|
||||
destpath: '/tmp/jarscanner',
|
||||
sdkpath: process.env['ANDROID_SDK'],
|
||||
api: 25,
|
||||
cleandest: true,
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
connection.console.log(`Android cache build failed. ${err.message}.`);
|
||||
return;
|
||||
}
|
||||
loadAndroidLibrary(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a connection for the server. The connection uses Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
let connection = createConnection(ProposedFeatures.all);
|
||||
|
||||
// Create a simple text document manager. The text document manager
|
||||
// supports full document sync only
|
||||
let documents = new TextDocuments({
|
||||
// Create a connection for the server. The connection uses Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
let connection = createConnection(ProposedFeatures.all);
|
||||
|
||||
/**
|
||||
* @typedef LiveParseInfo
|
||||
* @property {string} uri
|
||||
* @property {JavaTokenizer.LineInfo[]} lines
|
||||
* @property {{startState: string, states: string[], endState: string}[]} states
|
||||
*/
|
||||
|
||||
///** @type {LiveParseInfo[]} */
|
||||
//const liveParsers = [];
|
||||
/** @type {{content: string, uri: string, result: ParseResult, positionAt:(n) => Position, indexAt:(p:Position) => number}} */
|
||||
let parsed = null;
|
||||
|
||||
function reparse(uri, content) {
|
||||
const result = parse(content);
|
||||
parsed = {
|
||||
content,
|
||||
uri,
|
||||
result,
|
||||
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
|
||||
// supports full document sync only
|
||||
let documents = new TextDocuments({
|
||||
/**
|
||||
*
|
||||
* @param {string} uri
|
||||
* @param {string} languageId
|
||||
* @param {number} version
|
||||
* @param {string} content
|
||||
*
|
||||
* @param {string} uri
|
||||
* @param {string} languageId
|
||||
* @param {number} version
|
||||
* @param {string} content
|
||||
*/
|
||||
create(uri, languageId, version, content) {
|
||||
connection.console.log(JSON.stringify({what:'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
|
||||
connection.console.log(`create parse ${version}`);
|
||||
reparse(uri, content);
|
||||
//connection.console.log(res.imports.length.toString());
|
||||
// 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 };
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} document
|
||||
* @param {import('vscode-languageserver').TextDocumentContentChangeEvent[]} changes
|
||||
* @param {number} version
|
||||
*
|
||||
* @param {TextDocument} document
|
||||
* @param {import('vscode-languageserver').TextDocumentContentChangeEvent[]} changes
|
||||
* @param {number} 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}`);
|
||||
//return document;
|
||||
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);
|
||||
}
|
||||
return document;
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
let hasConfigurationCapability = false;
|
||||
let hasWorkspaceFolderCapability = false;
|
||||
let hasDiagnosticRelatedInformationCapability = false;
|
||||
|
||||
connection.onInitialize((params) => {
|
||||
let hasConfigurationCapability = false;
|
||||
let hasWorkspaceFolderCapability = false;
|
||||
let hasDiagnosticRelatedInformationCapability = false;
|
||||
|
||||
connection.onInitialize((params) => {
|
||||
process.nextTick(loadAndroidLibrary);
|
||||
let capabilities = params.capabilities;
|
||||
|
||||
|
||||
// Does the client support the `workspace/configuration` request?
|
||||
// If not, we will fall back using global settings
|
||||
hasConfigurationCapability =
|
||||
capabilities.workspace && !!capabilities.workspace.configuration;
|
||||
hasConfigurationCapability = capabilities.workspace && !!capabilities.workspace.configuration;
|
||||
|
||||
hasWorkspaceFolderCapability =
|
||||
capabilities.workspace && !!capabilities.workspace.workspaceFolders;
|
||||
hasWorkspaceFolderCapability = capabilities.workspace && !!capabilities.workspace.workspaceFolders;
|
||||
|
||||
hasDiagnosticRelatedInformationCapability =
|
||||
capabilities.textDocument &&
|
||||
capabilities.textDocument.publishDiagnostics &&
|
||||
capabilities.textDocument.publishDiagnostics.relatedInformation;
|
||||
|
||||
capabilities.textDocument && capabilities.textDocument.publishDiagnostics && capabilities.textDocument.publishDiagnostics.relatedInformation;
|
||||
|
||||
return {
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
// Tell the client that the server supports code completion
|
||||
completionProvider: {
|
||||
resolveProvider: true
|
||||
}
|
||||
}
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
// Tell the client that the server supports code completion
|
||||
completionProvider: {
|
||||
resolveProvider: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
connection.onInitialized(() => {
|
||||
});
|
||||
|
||||
connection.onInitialized(() => {
|
||||
if (hasConfigurationCapability) {
|
||||
// Register for all configuration changes.
|
||||
connection.client.register(DidChangeConfigurationNotification.type, undefined);
|
||||
// Register for all configuration changes.
|
||||
connection.client.register(DidChangeConfigurationNotification.type, undefined);
|
||||
}
|
||||
if (hasWorkspaceFolderCapability) {
|
||||
connection.workspace.onDidChangeWorkspaceFolders(_event => {
|
||||
connection.console.log('Workspace folder change event received.');
|
||||
});
|
||||
connection.workspace.onDidChangeWorkspaceFolders((_event) => {
|
||||
connection.console.log('Workspace folder change event received.');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// The example settings
|
||||
/**
|
||||
* @typedef ExampleSettings
|
||||
* @property {number} maxNumberOfProblems
|
||||
*/
|
||||
|
||||
// The global settings, used when the `workspace/configuration` request is not supported by the client.
|
||||
// Please note that this is not the case when using this server with the client provided in this example
|
||||
// but could happen with other clients.
|
||||
const defaultSettings = { maxNumberOfProblems: 1000 };
|
||||
let globalSettings = defaultSettings;
|
||||
|
||||
// Cache the settings of all open documents
|
||||
/** @type {Map<string, Thenable<ExampleSettings>>} */
|
||||
let documentSettings = new Map();
|
||||
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
});
|
||||
|
||||
// The example settings
|
||||
/**
|
||||
* @typedef ExampleSettings
|
||||
* @property {number} maxNumberOfProblems
|
||||
*/
|
||||
|
||||
// The global settings, used when the `workspace/configuration` request is not supported by the client.
|
||||
// Please note that this is not the case when using this server with the client provided in this example
|
||||
// but could happen with other clients.
|
||||
const defaultSettings = { maxNumberOfProblems: 1000 };
|
||||
let globalSettings = defaultSettings;
|
||||
|
||||
// Cache the settings of all open documents
|
||||
/** @type {Map<string, Thenable<ExampleSettings>>} */
|
||||
let documentSettings = new Map();
|
||||
|
||||
connection.onDidChangeConfiguration((change) => {
|
||||
if (hasConfigurationCapability) {
|
||||
// Reset all cached document settings
|
||||
documentSettings.clear();
|
||||
// Reset all cached document settings
|
||||
documentSettings.clear();
|
||||
} else {
|
||||
globalSettings = (
|
||||
(change.settings.androidJavaLanguageServer || defaultSettings)
|
||||
);
|
||||
globalSettings = change.settings.androidJavaLanguageServer || defaultSettings;
|
||||
}
|
||||
|
||||
|
||||
// Revalidate all open text documents
|
||||
documents.all().forEach(validateTextDocument);
|
||||
});
|
||||
|
||||
function getDocumentSettings(resource) {
|
||||
});
|
||||
|
||||
function getDocumentSettings(resource) {
|
||||
if (!hasConfigurationCapability) {
|
||||
return Promise.resolve(globalSettings);
|
||||
return Promise.resolve(globalSettings);
|
||||
}
|
||||
let result = documentSettings.get(resource);
|
||||
if (!result) {
|
||||
result = connection.workspace.getConfiguration({
|
||||
scopeUri: resource,
|
||||
section: 'androidJavaLanguageServer'
|
||||
});
|
||||
documentSettings.set(resource, result);
|
||||
result = connection.workspace.getConfiguration({
|
||||
scopeUri: resource,
|
||||
section: 'androidJavaLanguageServer',
|
||||
});
|
||||
documentSettings.set(resource, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Only keep settings for open documents
|
||||
documents.onDidClose(e => {
|
||||
}
|
||||
|
||||
// Only keep settings for open documents
|
||||
documents.onDidClose((e) => {
|
||||
connection.console.log('doc closed');
|
||||
parsed = null;
|
||||
documentSettings.delete(e.document.uri);
|
||||
});
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
// documents.onDidChangeContent(change => {
|
||||
// connection.console.log(JSON.stringify(change));
|
||||
//validateTextDocument(change.document);
|
||||
// });
|
||||
|
||||
/**
|
||||
* @param {TextDocument} textDocument
|
||||
*/
|
||||
async function validateTextDocument(textDocument) {
|
||||
});
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent((change) => {
|
||||
connection.console.log(JSON.stringify(change));
|
||||
validateTextDocument(change.document);
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {{uri}} textDocument
|
||||
*/
|
||||
async function validateTextDocument(textDocument) {
|
||||
/** @type {ParseProblem[]} */
|
||||
let problems = [];
|
||||
connection.console.log('validateTextDocument');
|
||||
|
||||
if (parsed && parsed.result) {
|
||||
// package problem
|
||||
if (parsed.result.package) {
|
||||
problems = [...problems, ...parsed.result.package.validate()];
|
||||
}
|
||||
|
||||
// import problems
|
||||
problems = parsed.result.imports.reduce((problems, import_decl) => {
|
||||
return [...problems, ...import_decl.validate()];
|
||||
}, problems);
|
||||
|
||||
// type problems
|
||||
problems = parsed.result.types.reduce((problems, type_decl) => {
|
||||
return [...problems, ...type_decl.validate()];
|
||||
}, problems);
|
||||
|
||||
// syntax problems
|
||||
problems = parsed.result.invalids.reduce((problems, invalid) => {
|
||||
return [...problems, ...invalid.validate()];
|
||||
}, problems);
|
||||
|
||||
const package_name = parsed.result.package ? parsed.result.package.dottedName() : '';
|
||||
const source_mtis = parsed.result.types.map(type_decl => {
|
||||
return new MTI().addType(package_name, type_decl.getDocString(), type_decl.getAccessModifierValues(), type_decl.kind, type_decl.qualifiedName());
|
||||
})
|
||||
const imports = resolveImports(androidLibrary, parsed.result.imports, package_name, source_mtis);
|
||||
|
||||
// missing/invalid imports
|
||||
problems = imports.unresolved.reduce((problems, unresolved) => {
|
||||
const fqn = unresolved.nameparts.join('.');
|
||||
return [...problems, new ParseProblem(unresolved.nameparts, `Unresolved import: ${fqn}`, ProblemSeverity.Warning)];
|
||||
}, problems);
|
||||
|
||||
// resolved types
|
||||
problems = parsed.result.types.reduce((problems, type_decl) => {
|
||||
return [...problems, ...type_decl.validateTypes(package_name, imports.resolved, imports.typemap)];
|
||||
}, problems);
|
||||
|
||||
// duplicate type names
|
||||
/** @type {Map<string,import('./java/parsetypes/type')[]>} */
|
||||
const typenames = new Map();
|
||||
parsed.result.types.forEach(type_decl => {
|
||||
const qname = type_decl.qualifiedName();
|
||||
let list = typenames.get(qname);
|
||||
if (!list) typenames.set(qname, list = []);
|
||||
list.push(type_decl);
|
||||
});
|
||||
[...typenames.values()]
|
||||
.filter(list => list.length > 1)
|
||||
.forEach(list => {
|
||||
problems = [...problems, ...list.map(type_decl => new ParseProblem(type_decl.name, `Duplicate type: ${type_decl.qualifiedDottedName()}`, ProblemSeverity.Error))];
|
||||
});
|
||||
}
|
||||
|
||||
const diagnostics = problems
|
||||
.filter((p) => p)
|
||||
.map((p) => {
|
||||
const start = parsed.positionAt(p.startIdx);
|
||||
const end = parsed.positionAt(p.endIdx);
|
||||
/** @type {Diagnostic} */
|
||||
let diagnostic = {
|
||||
severity: p.severity,
|
||||
range: {
|
||||
start,
|
||||
end,
|
||||
},
|
||||
message: p.message,
|
||||
source: 'java-android',
|
||||
};
|
||||
return diagnostic;
|
||||
});
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
|
||||
async function validateTextDocument2(textDocument) {
|
||||
// In this simple example we get the settings for every validate run.
|
||||
//let settings = await getDocumentSettings(textDocument.uri);
|
||||
|
||||
|
||||
// The validator creates diagnostics for all uppercase words length 2 and more
|
||||
let text = textDocument.getText();
|
||||
let pattern = /\b[A-Z]{2,}\b/g;
|
||||
let m;
|
||||
|
||||
|
||||
let problems = 0;
|
||||
let diagnostics = [];
|
||||
while ((m = pattern.exec(text)) /* && problems < settings.maxNumberOfProblems */) {
|
||||
problems++;
|
||||
/** @type {Diagnostic} */
|
||||
let diagnostic = {
|
||||
severity: DiagnosticSeverity.Warning,
|
||||
range: {
|
||||
start: textDocument.positionAt(m.index),
|
||||
end: textDocument.positionAt(m.index + m[0].length)
|
||||
},
|
||||
message: `${m[0]} is all uppercase.`,
|
||||
source: 'ex'
|
||||
};
|
||||
if (hasDiagnosticRelatedInformationCapability) {
|
||||
diagnostic.relatedInformation = [
|
||||
{
|
||||
location: {
|
||||
uri: textDocument.uri,
|
||||
range: Object.assign({}, diagnostic.range)
|
||||
problems++;
|
||||
/** @type {Diagnostic} */
|
||||
let diagnostic = {
|
||||
severity: DiagnosticSeverity.Warning,
|
||||
range: {
|
||||
start: textDocument.positionAt(m.index),
|
||||
end: textDocument.positionAt(m.index + m[0].length),
|
||||
},
|
||||
message: 'Spelling matters'
|
||||
},
|
||||
{
|
||||
location: {
|
||||
uri: textDocument.uri,
|
||||
range: Object.assign({}, diagnostic.range)
|
||||
},
|
||||
message: 'Particularly for names'
|
||||
}
|
||||
];
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
message: `${m[0]} is all uppercase.`,
|
||||
source: 'ex',
|
||||
};
|
||||
if (hasDiagnosticRelatedInformationCapability) {
|
||||
diagnostic.relatedInformation = [
|
||||
{
|
||||
location: {
|
||||
uri: textDocument.uri,
|
||||
range: Object.assign({}, diagnostic.range),
|
||||
},
|
||||
message: 'Spelling matters',
|
||||
},
|
||||
{
|
||||
location: {
|
||||
uri: textDocument.uri,
|
||||
range: Object.assign({}, diagnostic.range),
|
||||
},
|
||||
message: 'Particularly for names',
|
||||
},
|
||||
];
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
|
||||
// Send the computed diagnostics to VS Code.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
|
||||
connection.onDidChangeWatchedFiles(_change => {
|
||||
}
|
||||
|
||||
connection.onDidChangeWatchedFiles((_change) => {
|
||||
// Monitored files have change in VS Code
|
||||
connection.console.log('We received a file change event');
|
||||
});
|
||||
|
||||
// This handler provides the initial list of the completion items.
|
||||
let allCompletionTypes = null;
|
||||
connection.onCompletion(
|
||||
/**
|
||||
* @param {*} _textDocumentPosition TextDocumentPositionParams
|
||||
*/
|
||||
});
|
||||
|
||||
// This handler provides the initial list of the completion items.
|
||||
let allCompletionTypes = null;
|
||||
connection.onCompletion(
|
||||
/**
|
||||
* @param {*} _textDocumentPosition TextDocumentPositionParams
|
||||
*/
|
||||
(_textDocumentPosition) => {
|
||||
// The pass parameter contains the position of the text document in
|
||||
// which code complete got requested. For the example we ignore this
|
||||
// info and always provide the same completion items.
|
||||
const lib = androidLibrary;
|
||||
if (!lib) return [];
|
||||
const typeKindMap = {
|
||||
'class':CompletionItemKind.Class,
|
||||
'interface': CompletionItemKind.Interface,
|
||||
'@interface': CompletionItemKind.Interface,
|
||||
'enum': CompletionItemKind.Enum,
|
||||
// The pass parameter contains the position of the text document in
|
||||
// which code complete got requested. For the example we ignore this
|
||||
// info and always provide the same completion items.
|
||||
const lib = androidLibrary;
|
||||
if (!lib) return [];
|
||||
const typeKindMap = {
|
||||
class: CompletionItemKind.Class,
|
||||
interface: CompletionItemKind.Interface,
|
||||
'@interface': CompletionItemKind.Interface,
|
||||
enum: CompletionItemKind.Enum,
|
||||
};
|
||||
return allCompletionTypes || (allCompletionTypes = lib.types.map((t,idx) =>
|
||||
/** @type {CompletionItem} */
|
||||
({
|
||||
label: t.dottedRawName,
|
||||
kind: typeKindMap[t.typeKind],
|
||||
data: idx
|
||||
})
|
||||
));
|
||||
return [
|
||||
{
|
||||
label: 'TypeScript',
|
||||
kind: CompletionItemKind.Text,
|
||||
data: 1
|
||||
},
|
||||
{
|
||||
label: 'JavaScript',
|
||||
kind: CompletionItemKind.Text,
|
||||
data: 2
|
||||
}
|
||||
];
|
||||
return (
|
||||
allCompletionTypes ||
|
||||
(allCompletionTypes = [
|
||||
...'boolean byte char double float int long short void'.split(' ').map((t) => ({
|
||||
label: t,
|
||||
kind: CompletionItemKind.Keyword,
|
||||
data: -1,
|
||||
})),
|
||||
...'public private protected static final abstract volatile native'.split(' ').map((t) => ({
|
||||
label: t,
|
||||
kind: CompletionItemKind.Keyword,
|
||||
data: -1,
|
||||
})),
|
||||
...'false true null'.split(' ').map((t) => ({
|
||||
label: t,
|
||||
kind: CompletionItemKind.Value,
|
||||
data: -1,
|
||||
})),
|
||||
...lib.types.map(
|
||||
(t, idx) =>
|
||||
/** @type {CompletionItem} */
|
||||
({
|
||||
label: t.dottedRawName,
|
||||
kind: typeKindMap[t.typeKind],
|
||||
data: idx,
|
||||
})
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// This handler resolves additional information for the item selected in
|
||||
// the completion list.
|
||||
connection.onCompletionResolve(
|
||||
);
|
||||
|
||||
// This handler resolves additional information for the item selected in
|
||||
// the completion list.
|
||||
connection.onCompletionResolve(
|
||||
/**
|
||||
* @param {CompletionItem} item
|
||||
*/
|
||||
(item) => {
|
||||
const t = androidLibrary.types[item.data];
|
||||
item.detail = `${t.package}.${t.dottedRawName}`;
|
||||
item.documentation = t.docs && {
|
||||
kind: "markdown",
|
||||
value: `${t.typeKind} **${t.dottedName}**\n\n${
|
||||
t.docs
|
||||
.replace(/(<p ?.*?>)|(<\/?i>|<\/?em>)|(<\/?b>|<\/?strong>|<\/?dt>)|(<\/?tt>)|(<\/?code>|<\/?pre>)|(\{@link.+?\}|\{@code.+?\})|(<li>)|(<a href="\{@docRoot\}.*?">.+?<\/a>)|(<h\d>)|<\/?dd ?.*?>|<\/p ?.*?>|<\/h\d ?.*?>|<\/?div ?.*?>|<\/?[uo]l ?.*?>/gim, (_,p,i,b,tt,c,lc,li,a,h) => {
|
||||
return p ? '\n\n'
|
||||
: i ? '*'
|
||||
: b ? '**'
|
||||
: tt ? '`'
|
||||
: c ? '\n```'
|
||||
: lc ? lc.replace(/\{@\w+\s*(.+)\}/, (_,x) => `\`${x.trim()}\``)
|
||||
: li ? '\n- '
|
||||
: a ? a.replace(/.+?\{@docRoot\}(.*?)">(.+?)<\/a>/m, (_,p,t) => `[${t}](https://developer.android.com/${p})`)
|
||||
: h ? `\n${'#'.repeat(1 + parseInt(h.slice(2,-1),10))} `
|
||||
: '';
|
||||
})
|
||||
const t = androidLibrary.types[item.data];
|
||||
if (!t) {
|
||||
return item;
|
||||
}
|
||||
item.detail = `${t.package}.${t.dottedRawName}`;
|
||||
item.documentation = t.docs && {
|
||||
kind: 'markdown',
|
||||
value: `${t.typeKind} **${t.dottedName}**\n\n${
|
||||
t.docs
|
||||
.replace(/(<p ?.*?>)|(<\/?i>|<\/?em>)|(<\/?b>|<\/?strong>|<\/?dt>)|(<\/?tt>)|(<\/?code>|<\/?pre>)|(\{@link.+?\}|\{@code.+?\})|(<li>)|(<a href="\{@docRoot\}.*?">.+?<\/a>)|(<h\d>)|<\/?dd ?.*?>|<\/p ?.*?>|<\/h\d ?.*?>|<\/?div ?.*?>|<\/?[uo]l ?.*?>/gim, (_,p,i,b,tt,c,lc,li,a,h) => {
|
||||
return p ? '\n\n'
|
||||
: i ? '*'
|
||||
: b ? '**'
|
||||
: tt ? '`'
|
||||
: c ? '\n```'
|
||||
: lc ? lc.replace(/\{@\w+\s*(.+)\}/, (_,x) => `\`${x.trim()}\``)
|
||||
: li ? '\n- '
|
||||
: a ? a.replace(/.+?\{@docRoot\}(.*?)">(.+?)<\/a>/m, (_,p,t) => `[${t}](https://developer.android.com/${p})`)
|
||||
: h ? `\n${'#'.repeat(1 + parseInt(h.slice(2,-1),10))} `
|
||||
: '';
|
||||
})
|
||||
}`,
|
||||
}
|
||||
return item;
|
||||
};
|
||||
return item;
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
connection.onDidOpenTextDocument((params) => {
|
||||
// A text document got opened in VS Code.
|
||||
// params.uri uniquely identifies the document. For documents store on disk this is a file URI.
|
||||
// params.text the initial full content of the document.
|
||||
connection.console.log(`${params.textDocument.uri} opened.`);
|
||||
});
|
||||
connection.onDidChangeTextDocument((params) => {
|
||||
// The content of a text document did change in VS Code.
|
||||
// params.uri uniquely identifies the document.
|
||||
// params.contentChanges describe the content changes to the document.
|
||||
connection.console.log(`${params.textDocument.uri} changed: ${JSON.stringify(params.contentChanges)}`);
|
||||
});
|
||||
connection.onDidCloseTextDocument((params) => {
|
||||
// A text document got closed in VS Code.
|
||||
// params.uri uniquely identifies the document.
|
||||
connection.console.log(`${params.textDocument.uri} closed.`);
|
||||
});
|
||||
*/
|
||||
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
|
||||
);
|
||||
|
||||
/*
|
||||
connection.onDidOpenTextDocument((params) => {
|
||||
// A text document got opened in VS Code.
|
||||
// params.uri uniquely identifies the document. For documents store on disk this is a file URI.
|
||||
// params.text the initial full content of the document.
|
||||
connection.console.log(`${params.textDocument.uri} opened.`);
|
||||
});
|
||||
connection.onDidChangeTextDocument((params) => {
|
||||
// The content of a text document did change in VS Code.
|
||||
// params.uri uniquely identifies the document.
|
||||
// params.contentChanges describe the content changes to the document.
|
||||
connection.console.log(`${params.textDocument.uri} changed: ${JSON.stringify(params.contentChanges)}`);
|
||||
});
|
||||
connection.onDidCloseTextDocument((params) => {
|
||||
// A text document got closed in VS Code.
|
||||
// params.uri uniquely identifies the document.
|
||||
connection.console.log(`${params.textDocument.uri} closed.`);
|
||||
});
|
||||
*/
|
||||
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
|
||||
Reference in New Issue
Block a user