improve code completion list

add method parameters
order list items by scope
This commit is contained in:
Dave Holoway
2020-06-25 14:43:48 +01:00
parent f67c03bb34
commit e2765fd982
5 changed files with 172 additions and 86 deletions

View File

@@ -93,11 +93,11 @@ function flattenBlocks(blocks, isMethod) {
* @param {Map<string,CEIType>} typemap * @param {Map<string,CEIType>} typemap
*/ */
function parseBody(method, imports, typemap) { function parseBody(method, imports, typemap) {
const body = method.body; const body_tokens = method.body.tokens;
if (!body || body[0].value !== '{') { if (!body_tokens || body_tokens[0].value !== '{') {
return null; return null;
} }
const tokenlist = new TokenList(flattenBlocks(body, true)); const tokenlist = new TokenList(flattenBlocks(body_tokens, true));
let block = null; let block = null;
let mdecls = new MethodDeclarations(); let mdecls = new MethodDeclarations();
try { try {
@@ -320,7 +320,7 @@ function parseUnit(tokens, unit, typemap) {
*/ */
function packageDeclaration(tokens) { function packageDeclaration(tokens) {
tokens.mark(); tokens.mark();
tokens.current.loc = 'pkgname:'; tokens.current.loc = { key:'pkgname' };
tokens.expectValue('package'); tokens.expectValue('package');
let pkg_name_parts = [], dot; let pkg_name_parts = [], dot;
for (;;) { for (;;) {
@@ -330,11 +330,11 @@ function packageDeclaration(tokens) {
addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`)); addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`));
} }
if (name) { if (name) {
name.loc = `pkgname:${pkg_name_parts.join('/')}`; name.loc = { key: `pkgname:${pkg_name_parts.join('/')}` };
pkg_name_parts.push(name.value); pkg_name_parts.push(name.value);
} }
if (dot = tokens.getIfValue('.')) { if (dot = tokens.getIfValue('.')) {
dot.loc = `pkgname:${pkg_name_parts.join('/')}`; dot.loc = { key :`pkgname:${pkg_name_parts.join('/')}` };
continue; continue;
} }
const decl_tokens = tokens.markEnd(); const decl_tokens = tokens.markEnd();
@@ -349,7 +349,7 @@ function packageDeclaration(tokens) {
*/ */
function importDeclaration(tokens, typemap) { function importDeclaration(tokens, typemap) {
tokens.mark(); tokens.mark();
tokens.current.loc = 'fqdi:'; tokens.current.loc = { key: 'fqdi:' };
tokens.expectValue('import'); tokens.expectValue('import');
const static_token = tokens.getIfValue('static'); const static_token = tokens.getIfValue('static');
let asterisk_token = null, dot; let asterisk_token = null, dot;
@@ -361,12 +361,12 @@ function importDeclaration(tokens, typemap) {
addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`)); addproblem(tokens, ParseProblem.Error(tokens.current, `Package identifier expected`));
} }
if (name) { if (name) {
name.loc = `fqdi:${pkg_name_parts.join('.')}`; name.loc = { key: `fqdi:${pkg_name_parts.join('.')}` };
pkg_token_parts.push(name); pkg_token_parts.push(name);
pkg_name_parts.push(name.value); pkg_name_parts.push(name.value);
} }
if (dot = tokens.getIfValue('.')) { if (dot = tokens.getIfValue('.')) {
dot.loc = `fqdi:${pkg_name_parts.join('.')}`; dot.loc = name && name.loc;
if (!(asterisk_token = tokens.getIfValue('*'))) { if (!(asterisk_token = tokens.getIfValue('*'))) {
continue; continue;
} }
@@ -382,10 +382,11 @@ function importDeclaration(tokens, typemap) {
} }
/** /**
* @param {SourceMethodLike} method
* @param {MethodDeclarations} mdecls * @param {MethodDeclarations} mdecls
* @param {Local[]} new_locals * @param {Local[]} new_locals
*/ */
function addLocals(tokens, mdecls, new_locals) { function addLocals(method, mdecls, new_locals) {
for (let local of new_locals) { for (let local of new_locals) {
mdecls.locals.unshift(local); mdecls.locals.unshift(local);
} }
@@ -417,7 +418,7 @@ function statement(tokens, mdecls, method, imports, typemap) {
if (modifiers.length) { if (modifiers.length) {
const type = typeIdent(tokens, method, imports, typemap); const type = typeIdent(tokens, method, imports, typemap);
const locals = var_ident_list(modifiers, type, null, tokens, mdecls, 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); semicolon(tokens);
return new LocalDeclStatement(method, locals); return new LocalDeclStatement(method, locals);
} }
@@ -441,7 +442,7 @@ function statement(tokens, mdecls, method, imports, typemap) {
case 'primitive-type': case 'primitive-type':
const exp_or_vardecl = expression_or_var_decl(tokens, mdecls, method, imports, typemap); const exp_or_vardecl = expression_or_var_decl(tokens, mdecls, method, imports, typemap);
if (Array.isArray(exp_or_vardecl)) { 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); s = new LocalDeclStatement(method, exp_or_vardecl);
} else { } else {
s = new ExpressionStatement(method, exp_or_vardecl); s = new ExpressionStatement(method, exp_or_vardecl);

View File

@@ -35,7 +35,7 @@ class MemberExpression extends Expression {
} }
if (instance instanceof PackageNameType) { if (instance instanceof PackageNameType) {
this.dot.loc = `fqdi:${instance.package_name}`; this.dot.loc = { key: `fqdi:${instance.package_name}` };
if (!this.member) { if (!this.member) {
return instance; return instance;
} }
@@ -50,9 +50,9 @@ class MemberExpression extends Expression {
: AnyType.Instance; : AnyType.Instance;
} }
let loc = `fqi`; let loc_key = `fqi`;
if (instance instanceof TypeIdentType) { if (instance instanceof TypeIdentType) {
loc = 'fqs'; loc_key = 'fqs';
instance = instance.type; instance = instance.type;
} }
@@ -60,7 +60,7 @@ class MemberExpression extends Expression {
return AnyType.Instance; return AnyType.Instance;
} }
this.dot.loc = `${loc}:${instance.typeSignature}` this.dot.loc = { key: `${loc_key}:${instance.typeSignature}` };
if (!this.member) { if (!this.member) {
ri.problems.push(ParseProblem.Error(this.dot, `Identifier expected`)); ri.problems.push(ParseProblem.Error(this.dot, `Identifier expected`));
return instance; return instance;

View File

@@ -359,16 +359,20 @@ class SourceConstructor extends Constructor {
* @param {Token[]} modifiers * @param {Token[]} modifiers
* @param {SourceParameter[]} parameters * @param {SourceParameter[]} parameters
* @param {JavaType[]} throws * @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); super(owner, modifiers.map(m => m.value), docs);
this.owner = owner; this.owner = owner;
this.typeVars = type_vars; this.typeVars = type_vars;
this.modifierTokens = modifiers; this.modifierTokens = modifiers;
this.sourceParameters = parameters; this.sourceParameters = parameters;
this.throws = throws; this.throws = throws;
this.body = body; this.body = {
tokens: body_tokens,
/** @type {import('./body-types').Local[]} */
locals: [],
}
this.parsed = null; this.parsed = null;
} }
@@ -403,9 +407,9 @@ class SourceMethod extends Method {
* @param {Token} name_token * @param {Token} name_token
* @param {SourceParameter[]} parameters * @param {SourceParameter[]} parameters
* @param {JavaType[]} throws * @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); super(owner, name_token ? name_token.value : '', modifiers.map(m => m.value), docs);
this.annotations = annotations; this.annotations = annotations;
this.owner = owner; this.owner = owner;
@@ -415,7 +419,11 @@ class SourceMethod extends Method {
this.nameToken = name_token; this.nameToken = name_token;
this.sourceParameters = parameters; this.sourceParameters = parameters;
this.throws = throws; this.throws = throws;
this.body = body; this.body = {
tokens: body_tokens,
/** @type {import('./body-types').Local[]} */
locals: [],
}
this.parsed = null; this.parsed = null;
} }
@@ -448,14 +456,18 @@ class SourceInitialiser extends MethodBase {
* @param {SourceType} owner * @param {SourceType} owner
* @param {string} docs * @param {string} docs
* @param {Token[]} modifiers * @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); super(owner, modifiers.map(m => m.value), docs);
/** @type {SourceType} */ /** @type {SourceType} */
this.owner = owner; this.owner = owner;
this.modifierTokens = modifiers; this.modifierTokens = modifiers;
this.body = body; this.body = {
tokens: body_tokens,
/** @type {import('./body-types').Local[]} */
locals: [],
}
this.parsed = null; this.parsed = null;
} }
@@ -553,12 +565,27 @@ class SourceUnit {
types = []; 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 * @param {number} char_index
*/ */
getCompletionOptionsAt(char_index) { getTokenAt(char_index) {
let i = 0; let i = 0;
let loc = '';
for (let tok of this.tokens) { for (let tok of this.tokens) {
if (char_index > tok.range.start + tok.range.length) { if (char_index > tok.range.start + tok.range.length) {
i++; i++;
@@ -567,12 +594,24 @@ class SourceUnit {
while (i > 0 && tok.kind === 'wsc') { while (i > 0 && tok.kind === 'wsc') {
tok = this.tokens[--i]; tok = this.tokens[--i];
} }
loc = tok.loc; return tok;
break;
} }
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 { return {
index: char_index, index: char_index,
loc: loc, loc: token && token.loc,
method,
}; };
} }
} }

View File

@@ -49,7 +49,8 @@ class Token extends TextBlock {
constructor(text, start, length, kind) { constructor(text, start, length, kind) {
super(new BlockRange(text, start, length), tokenKindToSimplified(text, start, length, kind)); super(new BlockRange(text, start, length), tokenKindToSimplified(text, start, length, kind));
this.kind = kind; this.kind = kind;
this.loc = ''; /** @type {{key:string}} */
this.loc = null;
} }
get value() { get value() {

View File

@@ -344,6 +344,15 @@ connection.onDidChangeWatchedFiles((_change) => {
connection.console.log('We received a file change event'); 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<string,CEIType>} typemap * @param {Map<string,CEIType>} typemap
* @param {string} type_signature * @param {string} type_signature
@@ -395,9 +404,6 @@ function getTypedNameCompletion(typemap, type_signature, opts, typelist) {
// @ts-ignore // @ts-ignore
return t.packageName === type.packageName; return t.packageName === type.packageName;
} }
function sortByName(a,b) {
return a.name.localeCompare(b.name, undefined, {sensitivity: 'base'})
}
types.forEach((t,idx) => { types.forEach((t,idx) => {
t.fields.sort(sortByName) t.fields.sort(sortByName)
@@ -510,7 +516,7 @@ function getRootPackageCompletions() {
return [...pkgs].filter(x => x).sort().map(pkg => ({ return [...pkgs].filter(x => x).sort().map(pkg => ({
label: pkg, label: pkg,
kind: CompletionItemKind.Unit, 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. // This handler provides the initial list of the completion items.
let allCompletionTypes = null;
connection.onCompletion( connection.onCompletion(
/** /**
* @param {*} _textDocumentPosition TextDocumentPositionParams * @param {*} _textDocumentPosition TextDocumentPositionParams
@@ -550,62 +605,52 @@ connection.onCompletion(
} }
const lib = (parsed && parsed.typemap) || androidLibrary; const lib = (parsed && parsed.typemap) || androidLibrary;
if (!lib) return []; if (!lib) return [];
let locals = [], sortIdx = 10000;
if (parsed.result && parsed.result.unit) { if (parsed.result && parsed.result.unit) {
const index = parsed.indexAt(_textDocumentPosition.position); const index = parsed.indexAt(_textDocumentPosition.position);
const options = parsed.result.unit.getCompletionOptionsAt(index); const options = parsed.result.unit.getCompletionOptionsAt(index);
console.log(options); console.log(options);
if (/^pkgname:/.test(options.loc)) { if (options.loc) {
return getPackageCompletion(options.loc.split(':').pop()); 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)) { if (options.method) {
// fully-qualified type/field name locals = options.method.parameters.sort(sortByName).map(p => ({
return getFullyQualifiedDottedIdentCompletion(parsed.typemap, options.loc.split(':').pop(), { statics: true }); label: p.name,
} kind: CompletionItemKind.Variable,
if (/^fqs:/.test(options.loc)) { sortText: p.name,
// 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 });
} }
} }
const typeKindMap = {
class: CompletionItemKind.Class, if (!defaultCompletionTypes) {
interface: CompletionItemKind.Interface, initDefaultCompletionTypes(androidLibrary);
'@interface': CompletionItemKind.Interface, }
enum: CompletionItemKind.Enum, return [
}; ...locals,
return ( ...defaultCompletionTypes.instances,
allCompletionTypes || ...defaultCompletionTypes.primitiveTypes,
(allCompletionTypes = [ ...defaultCompletionTypes.literals,
...'boolean byte char double float int long short void'.split(' ').map((t) => ({ ...defaultCompletionTypes.modifiers,
label: t, ...defaultCompletionTypes.types,
kind: CompletionItemKind.Keyword, ...defaultCompletionTypes.packageNames,
data: -1, ].map((x,idx) => {
})), x.sortText = `${10000+idx}-${x.label}`;
...'public private protected static final abstract volatile native transient strictfp'.split(' ').map((t) => ({ return x;
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()
])
);
} }
); );