From db865825d0c28b68c5f9f73071077d139a819a34 Mon Sep 17 00:00:00 2001 From: Dave Holoway Date: Tue, 23 Jun 2020 18:57:07 +0100 Subject: [PATCH] support package, type and static field import completion --- langserver/java/body-parser3.js | 19 +++-- langserver/server.js | 137 +++++++++++++++++++++++++------- 2 files changed, 121 insertions(+), 35 deletions(-) diff --git a/langserver/java/body-parser3.js b/langserver/java/body-parser3.js index ad00350..c710714 100644 --- a/langserver/java/body-parser3.js +++ b/langserver/java/body-parser3.js @@ -318,8 +318,7 @@ function parseUnit(tokens, unit, typemap) { */ function packageDeclaration(tokens) { tokens.mark(); - const package_token = tokens.current; - package_token.loc = 'pkgname:'; + tokens.current.loc = 'pkgname:'; tokens.expectValue('package'); let pkg_name_parts = [], dot; for (;;) { @@ -348,10 +347,11 @@ function packageDeclaration(tokens) { */ function importDeclaration(tokens, typemap) { tokens.mark(); + tokens.current.loc = 'fqn:'; tokens.expectValue('import'); const static_token = tokens.getIfValue('static'); - let asterisk_token = null; - const pkg_name_parts = []; + let asterisk_token = null, dot; + const pkg_token_parts = [], pkg_name_parts = []; for (;;) { let name = tokens.current; if (!tokens.isKind('ident')) { @@ -359,9 +359,12 @@ function importDeclaration(tokens, typemap) { addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`)); } if (name) { - pkg_name_parts.push(name); + name.loc = `fqn:${pkg_name_parts.join('.')}`; + pkg_token_parts.push(name); + pkg_name_parts.push(name.value); } - if (tokens.isValue('.')) { + if (dot = tokens.getIfValue('.')) { + dot.loc = `fqn:${pkg_name_parts.join('.')}`; if (!(asterisk_token = tokens.getIfValue('*'))) { continue; } @@ -369,10 +372,10 @@ function importDeclaration(tokens, typemap) { const decl_tokens = tokens.markEnd(); semicolon(tokens); - const pkg_name = pkg_name_parts.map(x => x.source).join('.'); + const pkg_name = pkg_name_parts.join('.'); const resolved = resolveSingleImport(typemap, pkg_name, !!static_token, !!asterisk_token, 'import'); - return new SourceImport(decl_tokens, pkg_name_parts, pkg_name, static_token, asterisk_token, resolved); + return new SourceImport(decl_tokens, pkg_token_parts, pkg_name, static_token, asterisk_token, resolved); } } diff --git a/langserver/server.js b/langserver/server.js index f1af448..2e9c7eb 100644 --- a/langserver/server.js +++ b/langserver/server.js @@ -17,7 +17,7 @@ const { const { TextDocument } = require('vscode-languageserver-textdocument'); -const { loadAndroidLibrary, CEIType } = require('java-mti'); +const { loadAndroidLibrary, JavaType, CEIType } = require('java-mti'); const { ParseProblem } = require('./java/parser'); const { parse } = require('./java/body-parser3'); @@ -343,6 +343,110 @@ connection.onDidChangeWatchedFiles((_change) => { connection.console.log('We received a file change event'); }); +function getFullyQualifiedNameCompletion(name) { + if (name === '') { + return getPackageCompletion(''); + } + // name is a fully dotted name, possibly including a static member + let typelist = [...parsed.typemap.keys()]; + + const split_name = name.split('.'); + let pkgname = ''; + /** @type {JavaType} */ + let type = null, typename = ''; + for (let name_part of split_name) { + if (type) { + if (typelist.includes(`${typename}$${name_part}`)) { + type = parsed.typemap.get(typename = `${typename}$${name_part}`); + continue; + } + return []; + } + typename = pkgname + name_part; + if (typelist.includes(typename)) { + type = parsed.typemap.get(typename); + continue; + } + pkgname = `${pkgname}${name_part}/`; + } + + if (type) { + // add inner types and static fields + return [ + ...typelist.map(t => { + if (!t.startsWith(typename)) return; + const m = t.slice(typename.length).match(/^\$.+/); + if (!m) return; + return { + label: m[0].slice(1).replace(/\$/g,'.'), + kind: CompletionItemKind.Class, + data: -1, + } + }).filter(x => x), + ...type.fields.filter(m => m.modifiers.includes('static')).map(f => ({ + label: f.name, + kind: CompletionItemKind.Field, + data: -1, + })) + ] + } + + // sub-package or type + const search_pkg = pkgname; + return typelist.reduce((arr,typename) => { + if (typename.startsWith(search_pkg)) { + const m = typename.slice(search_pkg.length).match(/^(.+?)(\/|$)/); + if (m) { + if (m[2]) { + // package name + if (!arr.find(x => x.label === m[1])) { + arr.push({ + label: m[1], + kind: CompletionItemKind.Unit, + data: -1, + }) + } + } else { + // type name + arr.push({ + label: m[1].replace(/\$/g,'.'), + kind: CompletionItemKind.Class, + data: -1, + }) + } + } + } + return arr; + }, []); +} + +function getPackageCompletion(pkg) { + let pkgs; + if (pkg === '') { + // root packages + pkgs = [...parsed.typemap.keys()].reduce((set,typename) => { + const m = typename.match(/(.+?)\//); + m && set.add(m[1]); + return set; + }, new Set()); + } else { + // sub-package + const search_pkg = pkg + '/'; + pkgs = [...parsed.typemap.keys()].reduce((arr,typename) => { + if (typename.startsWith(search_pkg)) { + const m = typename.slice(search_pkg.length).match(/^(.+?)\//); + if (m) arr.add(m[1]); + } + return arr; + }, new Set()); + } + return [...pkgs].filter(x => x).sort().map(pkg => ({ + label: pkg, + kind: CompletionItemKind.Unit, + data: -1, + })); +} + // This handler provides the initial list of the completion items. let allCompletionTypes = null; connection.onCompletion( @@ -363,32 +467,11 @@ connection.onCompletion( const options = parsed.result.unit.getCompletionOptionsAt(index); console.log(options); if (/^pkgname:/.test(options.loc)) { - const pkg = options.loc.split(':').pop(); - let pkgs; - if (pkg === '') { - // root packages - pkgs = [...parsed.typemap.keys()].reduce((set,typename) => { - const m = typename.match(/(.+?)\//); - m && set.add(m[1]); - return set; - }, new Set()); - } else { - // sub-package - const search_pkg = pkg + '/'; - pkgs = [...parsed.typemap.keys()].reduce((arr,typename) => { - if (typename.startsWith(search_pkg)) { - const m = typename.slice(search_pkg.length).match(/^(.+?)\//); - if (m) arr.add(m[1]); - } - return arr; - }, new Set()); - } - return [...pkgs].filter(x => x).sort().map(pkg => ({ - label: pkg, - kind: CompletionItemKind.Unit, - data: -1, - })); - } + return getPackageCompletion(options.loc.split(':').pop()); + } + if (/^fqn:/.test(options.loc)) { + return getFullyQualifiedNameCompletion(options.loc.split(':').pop()); + } } const typeKindMap = { class: CompletionItemKind.Class,