From e2765fd982b0bf8b6e1b1c37ae6959d7fce89812 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Thu, 25 Jun 2020 14:43:48 +0100 Subject: [PATCH] improve code completion list add method parameters order list items by scope --- langserver/java/body-parser3.js | 25 +-- .../java/expressiontypes/MemberExpression.js | 8 +- langserver/java/source-types.js | 69 ++++++-- langserver/java/tokenizer.js | 3 +- langserver/server.js | 153 +++++++++++------- 5 files changed, 172 insertions(+), 86 deletions(-) diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index 27219b3..9dbe074 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -93,11 +93,11 @@ function flattenBlocks(blocks, isMethod) { * @param {Map} typemap */ function parseBody(method, imports, typemap) { - const body = method.body; - if (!body || body[0].value !== '{') { + const body_tokens = method.body.tokens; + if (!body_tokens || body_tokens[0].value !== '{') { return null; } - const tokenlist = new TokenList(flattenBlocks(body, true)); + const tokenlist = new TokenList(flattenBlocks(body_tokens, true)); let block = null; let mdecls = new MethodDeclarations(); try { @@ -320,7 +320,7 @@ function parseUnit(tokens, unit, typemap) { */ function packageDeclaration(tokens) { tokens.mark(); - tokens.current.loc = 'pkgname:'; + tokens.current.loc = { key:'pkgname' }; tokens.expectValue('package'); let pkg_name_parts = [], dot; for (;;) { @@ -330,11 +330,11 @@ function packageDeclaration(tokens) { addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`)); } if (name) { - name.loc = `pkgname:${pkg_name_parts.join('/')}`; + name.loc = { key: `pkgname:${pkg_name_parts.join('/')}` }; pkg_name_parts.push(name.value); } if (dot = tokens.getIfValue('.')) { - dot.loc = `pkgname:${pkg_name_parts.join('/')}`; + dot.loc = { key :`pkgname:${pkg_name_parts.join('/')}` }; continue; } const decl_tokens = tokens.markEnd(); @@ -349,7 +349,7 @@ function packageDeclaration(tokens) { */ function importDeclaration(tokens, typemap) { tokens.mark(); - tokens.current.loc = 'fqdi:'; + tokens.current.loc = { key: 'fqdi:' }; tokens.expectValue('import'); const static_token = tokens.getIfValue('static'); let asterisk_token = null, dot; @@ -361,12 +361,12 @@ function importDeclaration(tokens, typemap) { addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`)); } if (name) { - name.loc = `fqdi:${pkg_name_parts.join('.')}`; + name.loc = { key: `fqdi:${pkg_name_parts.join('.')}` }; pkg_token_parts.push(name); pkg_name_parts.push(name.value); } if (dot = tokens.getIfValue('.')) { - dot.loc = `fqdi:${pkg_name_parts.join('.')}`; + dot.loc = name && name.loc; if (!(asterisk_token = tokens.getIfValue('*'))) { continue; } @@ -382,10 +382,11 @@ function importDeclaration(tokens, typemap) { } /** + * @param {SourceMethodLike} method * @param {MethodDeclarations} mdecls * @param {Local[]} new_locals */ -function addLocals(tokens, mdecls, new_locals) { +function addLocals(method, mdecls, new_locals) { for (let local of new_locals) { mdecls.locals.unshift(local); } @@ -417,7 +418,7 @@ function statement(tokens, mdecls, method, imports, typemap) { if (modifiers.length) { const type = typeIdent(tokens, method, imports, typemap); const locals = var_ident_list(modifiers, type, null, tokens, mdecls, method, imports, typemap) - addLocals(tokens, mdecls, locals); + addLocals(method, mdecls, locals); semicolon(tokens); return new LocalDeclStatement(method, locals); } @@ -441,7 +442,7 @@ function statement(tokens, mdecls, method, imports, typemap) { case 'primitive-type': const exp_or_vardecl = expression_or_var_decl(tokens, mdecls, method, imports, typemap); if (Array.isArray(exp_or_vardecl)) { - addLocals(tokens, mdecls, exp_or_vardecl); + addLocals(method, mdecls, exp_or_vardecl); s = new LocalDeclStatement(method, exp_or_vardecl); } else { s = new ExpressionStatement(method, exp_or_vardecl); diff --git a/langserver/java/expressiontypes/MemberExpression.js b/langserver/java/expressiontypes/MemberExpression.js index 33fa9d2..9f6a87c 100644 --- a/langserver/java/expressiontypes/MemberExpression.js +++ b/langserver/java/expressiontypes/MemberExpression.js @@ -35,7 +35,7 @@ class MemberExpression extends Expression { } if (instance instanceof PackageNameType) { - this.dot.loc = `fqdi:${instance.package_name}`; + this.dot.loc = { key: `fqdi:${instance.package_name}` }; if (!this.member) { return instance; } @@ -50,9 +50,9 @@ class MemberExpression extends Expression { : AnyType.Instance; } - let loc = `fqi`; + let loc_key = `fqi`; if (instance instanceof TypeIdentType) { - loc = 'fqs'; + loc_key = 'fqs'; instance = instance.type; } @@ -60,7 +60,7 @@ class MemberExpression extends Expression { return AnyType.Instance; } - this.dot.loc = `${loc}:${instance.typeSignature}` + this.dot.loc = { key: `${loc_key}:${instance.typeSignature}` }; if (!this.member) { ri.problems.push(ParseProblem.Error(this.dot, `Identifier expected`)); return instance; diff --git a/langserver/java/source-types.js b/langserver/java/source-types.js index 9dd932e..bc3d609 100644 --- a/langserver/java/source-types.js +++ b/langserver/java/source-types.js @@ -359,16 +359,20 @@ class SourceConstructor extends Constructor { * @param {Token[]} modifiers * @param {SourceParameter[]} parameters * @param {JavaType[]} throws - * @param {Token[]} body + * @param {Token[]} body_tokens */ - constructor(owner, docs, type_vars, modifiers, parameters, throws, body) { + constructor(owner, docs, type_vars, modifiers, parameters, throws, body_tokens) { super(owner, modifiers.map(m => m.value), docs); this.owner = owner; this.typeVars = type_vars; this.modifierTokens = modifiers; this.sourceParameters = parameters; this.throws = throws; - this.body = body; + this.body = { + tokens: body_tokens, + /** @type {import('./body-types').Local[]} */ + locals: [], + } this.parsed = null; } @@ -403,9 +407,9 @@ class SourceMethod extends Method { * @param {Token} name_token * @param {SourceParameter[]} parameters * @param {JavaType[]} throws - * @param {Token[]} body + * @param {Token[]} body_tokens */ - constructor(owner, docs, type_vars, modifiers, annotations, method_type_ident, name_token, parameters, throws, body) { + constructor(owner, docs, type_vars, modifiers, annotations, method_type_ident, name_token, parameters, throws, body_tokens) { super(owner, name_token ? name_token.value : '', modifiers.map(m => m.value), docs); this.annotations = annotations; this.owner = owner; @@ -415,7 +419,11 @@ class SourceMethod extends Method { this.nameToken = name_token; this.sourceParameters = parameters; this.throws = throws; - this.body = body; + this.body = { + tokens: body_tokens, + /** @type {import('./body-types').Local[]} */ + locals: [], + } this.parsed = null; } @@ -448,14 +456,18 @@ class SourceInitialiser extends MethodBase { * @param {SourceType} owner * @param {string} docs * @param {Token[]} modifiers - * @param {Token[]} body + * @param {Token[]} body_tokens */ - constructor(owner, docs, modifiers, body) { + constructor(owner, docs, modifiers, body_tokens) { super(owner, modifiers.map(m => m.value), docs); /** @type {SourceType} */ this.owner = owner; this.modifierTokens = modifiers; - this.body = body; + this.body = { + tokens: body_tokens, + /** @type {import('./body-types').Local[]} */ + locals: [], + } this.parsed = null; } @@ -553,12 +565,27 @@ class SourceUnit { types = []; /** - * + * @param {Token} token + */ + getSourceMethodAtToken(token) { + if (!token) { + return null; + } + for (let type of this.types) { + for (let method of type.sourceMethods) { + if (method.body && method.body.tokens && method.body.tokens.includes(token)) { + return method; + } + } + } + return null; + } + + /** * @param {number} char_index */ - getCompletionOptionsAt(char_index) { + getTokenAt(char_index) { let i = 0; - let loc = ''; for (let tok of this.tokens) { if (char_index > tok.range.start + tok.range.length) { i++; @@ -567,12 +594,24 @@ class SourceUnit { while (i > 0 && tok.kind === 'wsc') { tok = this.tokens[--i]; } - loc = tok.loc; - break; + return tok; } + return null; + } + + /** + * + * @param {number} char_index + */ + getCompletionOptionsAt(char_index) { + const token = this.getTokenAt(char_index); + const method = this.getSourceMethodAtToken(token); + // we should also include local variables here, but + // it's currently difficult to map an individual token to a scope return { index: char_index, - loc: loc, + loc: token && token.loc, + method, }; } } diff --git a/langserver/java/tokenizer.js b/langserver/java/tokenizer.js index e3adc02..7df4a67 100644 --- a/langserver/java/tokenizer.js +++ b/langserver/java/tokenizer.js @@ -49,7 +49,8 @@ class Token extends TextBlock { constructor(text, start, length, kind) { super(new BlockRange(text, start, length), tokenKindToSimplified(text, start, length, kind)); this.kind = kind; - this.loc = ''; + /** @type {{key:string}} */ + this.loc = null; } get value() { diff --git a/langserver/server.js b/langserver/server.js index bb88163..fe357e1 100644 --- a/langserver/server.js +++ b/langserver/server.js @@ -344,6 +344,15 @@ connection.onDidChangeWatchedFiles((_change) => { connection.console.log('We received a file change event'); }); +/** + * + * @param {{name:string}} a + * @param {{name:string}} b + */ +function sortByName(a,b) { + return a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}) +} + /** * @param {Map} typemap * @param {string} type_signature @@ -395,9 +404,6 @@ function getTypedNameCompletion(typemap, type_signature, opts, typelist) { // @ts-ignore return t.packageName === type.packageName; } - function sortByName(a,b) { - return a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}) - } types.forEach((t,idx) => { t.fields.sort(sortByName) @@ -510,7 +516,7 @@ function getRootPackageCompletions() { return [...pkgs].filter(x => x).sort().map(pkg => ({ label: pkg, kind: CompletionItemKind.Unit, - data: -1, + sortText: pkg, })); } @@ -535,8 +541,57 @@ function getPackageCompletion(pkg) { })); } +let defaultCompletionTypes = null; +const typeKindMap = { + class: CompletionItemKind.Class, + interface: CompletionItemKind.Interface, + '@interface': CompletionItemKind.Interface, + enum: CompletionItemKind.Enum, +}; +function initDefaultCompletionTypes(lib) { + defaultCompletionTypes = { + instances: 'this super'.split(' ').map(t => ({ + label: t, + kind: CompletionItemKind.Value, + sortText: t + })), + // primitive types + primitiveTypes:'boolean byte char double float int long short void'.split(' ').map((t) => ({ + label: t, + kind: CompletionItemKind.Keyword, + sortText: t, + })), + // modifiers + modifiers: 'public private protected static final abstract volatile native transient strictfp synchronized'.split(' ').map((t) => ({ + label: t, + kind: CompletionItemKind.Keyword, + sortText: t, + })), + // literals + literals: 'false true null super'.split(' ').map((t) => ({ + label: t, + kind: CompletionItemKind.Value, + sortText: t + })), + // type names + types: [...lib.values()].map( + t => + /** @type {CompletionItem} */ + ({ + label: t.dottedTypeName, + kind: typeKindMap[t.typeKind], + data: { type:t.shortSignature }, + sortText: t.dottedTypeName, + }) + ).sort((a,b) => a.label.localeCompare(b.label, undefined, {sensitivity:'base'})), + + // package names + packageNames: getRootPackageCompletions(), + } +} + + // This handler provides the initial list of the completion items. -let allCompletionTypes = null; connection.onCompletion( /** * @param {*} _textDocumentPosition TextDocumentPositionParams @@ -550,62 +605,52 @@ connection.onCompletion( } const lib = (parsed && parsed.typemap) || androidLibrary; if (!lib) return []; + let locals = [], sortIdx = 10000; if (parsed.result && parsed.result.unit) { const index = parsed.indexAt(_textDocumentPosition.position); const options = parsed.result.unit.getCompletionOptionsAt(index); console.log(options); - if (/^pkgname:/.test(options.loc)) { - return getPackageCompletion(options.loc.split(':').pop()); + if (options.loc) { + if (/^pkgname:/.test(options.loc.key)) { + return getPackageCompletion(options.loc.key.split(':').pop()); + } + if (/^fqdi:/.test(options.loc.key)) { + // fully-qualified type/field name + return getFullyQualifiedDottedIdentCompletion(parsed.typemap, options.loc.key.split(':').pop(), { statics: true }); + } + if (/^fqs:/.test(options.loc.key)) { + // fully-qualified expression + return getTypedNameCompletion(parsed.typemap, options.loc.key.split(':').pop(), { statics: true }); + } + if (/^fqi:/.test(options.loc.key)) { + // fully-qualified expression + return getTypedNameCompletion(parsed.typemap, options.loc.key.split(':').pop(), { statics: false }); + } } - if (/^fqdi:/.test(options.loc)) { - // fully-qualified type/field name - return getFullyQualifiedDottedIdentCompletion(parsed.typemap, options.loc.split(':').pop(), { statics: true }); - } - if (/^fqs:/.test(options.loc)) { - // fully-qualified expression - return getTypedNameCompletion(parsed.typemap, options.loc.split(':').pop(), { statics: true }); - } - if (/^fqi:/.test(options.loc)) { - // fully-qualified expression - return getTypedNameCompletion(parsed.typemap, options.loc.split(':').pop(), { statics: false }); + if (options.method) { + locals = options.method.parameters.sort(sortByName).map(p => ({ + label: p.name, + kind: CompletionItemKind.Variable, + sortText: p.name, + })) } } - const typeKindMap = { - class: CompletionItemKind.Class, - interface: CompletionItemKind.Interface, - '@interface': CompletionItemKind.Interface, - enum: CompletionItemKind.Enum, - }; - 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 transient strictfp'.split(' ').map((t) => ({ - label: t, - kind: CompletionItemKind.Keyword, - data: -1, - })), - ...'false true null this super'.split(' ').map((t) => ({ - label: t, - kind: CompletionItemKind.Value, - data: -1, - })), - ...[...lib.values()].map( - (t, idx) => - /** @type {CompletionItem} */ - ({ - label: t.dottedTypeName, - kind: typeKindMap[t.typeKind], - data: {type:t.shortSignature}, - }) - ), - ...getRootPackageCompletions() - ]) - ); + + if (!defaultCompletionTypes) { + initDefaultCompletionTypes(androidLibrary); + } + return [ + ...locals, + ...defaultCompletionTypes.instances, + ...defaultCompletionTypes.primitiveTypes, + ...defaultCompletionTypes.literals, + ...defaultCompletionTypes.modifiers, + ...defaultCompletionTypes.types, + ...defaultCompletionTypes.packageNames, + ].map((x,idx) => { + x.sortText = `${10000+idx}-${x.label}`; + return x; + }) } );