Skip to content

Commit

Permalink
Further work towards issue #34, #153 (Auto-completion).
Browse files Browse the repository at this point in the history
  • Loading branch information
EliotVU committed Jan 29, 2023
1 parent 7f8ea66 commit 18926ba
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 52 deletions.
16 changes: 9 additions & 7 deletions server/src/UC/Parser/Parser.utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { ParserRuleContext, Token } from 'antlr4ts';
import { Parser, ParserRuleContext, Token } from 'antlr4ts';

export function getTokenDebugInfo(token?: Token): string {
export function getTokenDebugInfo(token: Token | undefined, parser?: Parser): string {
if (typeof token === 'undefined') {
return '';
return 'null';
}
return `(${token.line}:${token.charPositionInLine}) "${token.text}"`;
const typeTree = parser ? parser.vocabulary.getSymbolicName(token.type) : token.type;
return `(${token.line}:${token.charPositionInLine}) [${typeTree}] ${JSON.stringify(token.text)}`;
}

export function getCtxDebugInfo(ctx?: ParserRuleContext): string {
export function getCtxDebugInfo(ctx: ParserRuleContext | undefined, parser?: Parser): string {
if (typeof ctx === 'undefined') {
return '';
return 'null';
}
return `(${ctx.start.line}:${ctx.start.charPositionInLine}) "${ctx.text}"`;
const typeTree = parser ? parser.ruleNames[ctx.ruleIndex] : ctx.ruleIndex;
return `(${ctx.start.line}:${ctx.start.charPositionInLine}) [${typeTree}]`;
}
10 changes: 10 additions & 0 deletions server/src/UC/Symbols/ISymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,14 @@ export function getOuter<T extends ISymbol = ISymbol>(symbol: ISymbol, kind: UCS

export function hasNoKind(symbol: { kind: UCNodeKind }): boolean {
return typeof symbol.kind === 'undefined';
}

export function getDebugSymbolInfo(symbol?: ISymbol): string {
if (typeof symbol === 'undefined') {
return 'null';
}

const range = symbol.getRange();
const path = symbol.getName().text;
return `(${range.start.line + 1}:${range.start.character} - ${range.end.line + 1}:${range.end.character}) [${path}]`;
}
134 changes: 89 additions & 45 deletions server/src/completion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as c3 from 'antlr4-c3';
import { Parser } from 'antlr4ts';
import { Parser, ParserRuleContext } from 'antlr4ts';
import { Token } from 'antlr4ts/Token';
import { CompletionItem, CompletionItemKind, InsertTextFormat, InsertTextMode, SignatureHelp } from 'vscode-languageserver';
import { Position } from 'vscode-languageserver-textdocument';
Expand All @@ -19,11 +19,12 @@ import {
} from './UC/helpers';
import { config, getDocumentByURI, UCGeneration } from './UC/indexer';
import { toName } from './UC/name';
import { getTokenDebugInfo } from './UC/Parser/Parser.utils';
import { getCtxDebugInfo, getTokenDebugInfo } from './UC/Parser/Parser.utils';
import {
areMethodsCompatibleWith,
ContextKind,
DefaultArray,
getDebugSymbolInfo,
Identifier,
isClass,
isDelegateSymbol,
Expand Down Expand Up @@ -55,15 +56,15 @@ import {
UCTypeKind,
} from './UC/Symbols';

/** If the candidates collector hits any these it'll stop at the first occurance. */
const PreferredRulesSet = new Set([
UCParser.RULE_typeDecl,
UCParser.RULE_defaultIdentifierRef,
UCParser.RULE_defaultQualifiedIdentifierRef,
UCParser.RULE_qualifiedIdentifier, UCParser.RULE_identifier,
UCParser.RULE_codeBlockOptional,
// UCParser.RULE_codeBlockOptional,
UCParser.RULE_classPropertyAccessSpecifier,
// UCParser.RULE_objectLiteral,
// UCParser.RULE_member
]);

export const DefaultIgnoredTokensSet = new Set([
Expand Down Expand Up @@ -104,28 +105,31 @@ export function setIgnoredTokensSet(newSet: Set<number>) {
currentIgnoredTokensSet = newSet;
}

const TypeDeclSymbolTypes = 1 << UCSymbolKind.Enum
const TypeDeclSymbolKinds = 1 << UCSymbolKind.Enum
| 1 << UCSymbolKind.ScriptStruct
| 1 << UCSymbolKind.Class
| 1 << UCSymbolKind.Interface;
// For qualified classes "Package.Class"
// | 1 << UCSymbolKind.Package;

// ClassType.Identifier
const ClassTypeContextSymbolTypes = 1 << UCSymbolKind.Enum
const ClassTypeContextSymbolKinds = 1 << UCSymbolKind.Enum
| 1 << UCSymbolKind.ScriptStruct;

// PackageType.Identifier
const PackageTypeContextSymbolTypes = 1 << UCSymbolKind.Class
const PackageTypeContextSymbolKinds = 1 << UCSymbolKind.Class
| 1 << UCSymbolKind.Interface;

const TypeDeclContextSymbolTypes = 1 << UCSymbolKind.Class
const TypeDeclContextSymbolKinds = 1 << UCSymbolKind.Class
| 1 << UCSymbolKind.Interface;

// TODO: Also ScriptStruct such as Vector and Rotator
const GlobalCastTypes = 1 << UCSymbolKind.Class
const GlobalCastSymbolKinds = 1 << UCSymbolKind.Class
| 1 << UCSymbolKind.Interface;

const MethodSymbolKinds = 1 << UCSymbolKind.Function
| 1 << UCSymbolKind.Event;

export async function getSignatureHelp(uri: string, position: Position): Promise<SignatureHelp | undefined> {
// const document = getDocumentByURI(uri);
// if (!document || !data) {
Expand Down Expand Up @@ -210,31 +214,31 @@ function insertTextForFunction(symbol: UCMethodSymbol): string {
return text.filter(s => s).join(' ') + "\n{\n\t$0\n}";
}

function getOverridableFunctions(document: UCDocument, contextSymbol: UCStructSymbol): UCMethodSymbol[] {
function getOverridableMethods(document: UCDocument, contextSymbol: UCStructSymbol): UCMethodSymbol[] {
return contextSymbol
.getCompletionSymbols<UCMethodSymbol>(document, ContextKind.None, 1 << UCSymbolKind.Function)
.getCompletionSymbols<UCMethodSymbol>(document, ContextKind.None, MethodSymbolKinds)
.filter(method => {
return method.super == null // filter out overridden duplicates
&& (method.modifiers & ModifierFlags.NonOverridable) == 0
&& (method.specifiers & MethodFlags.NonOverridable) == 0;
});
}

function getCallableFunctions(document: UCDocument, contextSymbol: UCStructSymbol): UCMethodSymbol[] {
function getCallableMethods(document: UCDocument, contextSymbol: UCStructSymbol): UCMethodSymbol[] {
return contextSymbol
.getCompletionSymbols<UCMethodSymbol>(document, ContextKind.None, 1 << UCSymbolKind.Function)
.getCompletionSymbols<UCMethodSymbol>(document, ContextKind.None, MethodSymbolKinds)
.filter(method => {
return method.super == null
&& ((method.modifiers & ModifierFlags.Private) == 0 || (method.getHash() == contextSymbol.getHash()));
});
}

function insertOverridableFunctions(document: UCDocument, contextSymbol: UCStructSymbol, items: CompletionItem[]) {
function insertOverridableMethods(document: UCDocument, contextSymbol: UCStructSymbol, items: CompletionItem[]) {
if (!contextSymbol.super) {
return;
}

const symbolItems = getOverridableFunctions(document, contextSymbol.super)
const symbolItems = getOverridableMethods(document, contextSymbol.super)
.map(method => {
const item = symbolToCompletionItem(method);
item.insertText = insertTextForFunction(method);
Expand Down Expand Up @@ -286,14 +290,11 @@ async function buildCompletableSymbolItems(
// throw new Error(`No carret token at ${position}`);
return;
}
console.info('completion::carretToken', getTokenDebugInfo(carretToken));
console.info(
'completion::carretToken'.padEnd(42),
getTokenDebugInfo(carretToken, data.parser));

let leadingToken = carretToken;
while (leadingToken.channel === UCLexer.HIDDEN
|| leadingToken.channel === UCLexer.COMMENTS_CHANNEL) {
leadingToken = stream.get(leadingToken.tokenIndex + 1);
}

if (leadingToken.type == UCLexer.OPEN_PARENS
|| leadingToken.type == UCLexer.OPEN_BRACE
|| leadingToken.type == UCLexer.OPEN_BRACKET
Expand All @@ -306,38 +307,69 @@ async function buildCompletableSymbolItems(
leadingToken = stream.get(leadingToken.tokenIndex + 1);
}

while (leadingToken.channel === UCLexer.HIDDEN
|| leadingToken.channel === UCLexer.COMMENTS_CHANNEL) {
leadingToken = stream.get(leadingToken.tokenIndex + 1);
}

if (!leadingToken) {
console.warn(`No leading carret token at ${position.line}:${position.character}`);
// throw new Error(`No carret token at ${position}`);
return;
}
console.info('completion::leadingToken', getTokenDebugInfo(leadingToken));
console.info(
'completion::leadingToken'.padEnd(42),
getTokenDebugInfo(leadingToken, data.parser));

const candidates = cc.collectCandidates(leadingToken.tokenIndex, data.context);
const carretRuleContext = getIntersectingContext(data.context, position);
if (process.env.NODE_ENV === 'development') {
console.debug(
'completion::carretRuleContext'.padEnd(42),
getCtxDebugInfo(carretRuleContext, data.parser));
}

// No c++ support, + this leads to an infinite loop with cc.collectCandiates.
if (carretRuleContext?.ruleIndex === UCParser.RULE_exportBlockText) {
return undefined;
}

function getParentRule(ctx: ParserRuleContext | undefined, ruleIndex: number): ParserRuleContext | undefined {
while (ctx && (ctx = ctx.parent)) {
if (ctx.ruleIndex == ruleIndex) {
return ctx;
}
}
return undefined;
}

// Limit the context to RULE_member if possible
const scopeRuleContext = getParentRule(carretRuleContext, UCParser.RULE_member) ?? data.context;
console.info(
'completion::scopeRuleContext'.padEnd(42),
getCtxDebugInfo(scopeRuleContext, data.parser));
const candidates = cc.collectCandidates(leadingToken.tokenIndex, scopeRuleContext);
// if (process.env.NODE_ENV === 'development') {
// console.debug('completion::tokens', Array
// .from(candidates.tokens.keys())
// .map(type => data.parser.vocabulary.getLiteralName(type))
// .join("|"));
// }

const ruleContext = getIntersectingContext(data.context, position);
if (process.env.NODE_ENV === 'development') {
console.debug('completion::ruleContext', ruleContext?.toInfoString(data.parser));
}

/**
* Resolves to a symbol that contains the current context.
* For example in a function this will always resolve to the UCMethodSymbol that we are working in.
**/
const scopeSymbol = getDocumentContext(document, position);
console.info('completion::scopeSymbol', scopeSymbol?.getPath());
console.info(
'completion::scopeSymbol'.padEnd(42),
getDebugSymbolInfo(scopeSymbol));

/**
* Resolves to a symbol that is either at the left of a "." or "=".
* Useful for if we want to provide context sensitive completions for when we have an incomplete parser AST.
**/
let carretContextSymbol: ISymbol | undefined;
let carretSymbol: ISymbol | undefined;

let carretContextToken: Token | undefined;
if (isStruct(scopeSymbol)) {
Expand All @@ -353,24 +385,34 @@ async function buildCompletableSymbolItems(
carretContextSymbol = scopeSymbol.getContainedSymbolAtPos(rangeFromBound(carretContextToken).start);
UCCallExpression.hack_getTypeIfNoSymbol = false;
}
}
console.info('completion::carretContextToken', getTokenDebugInfo(carretContextToken));
console.info('completion::carretContextSymbol', carretContextSymbol?.getName());

carretSymbol = scopeSymbol.getSymbolAtPos(position);
}
console.info(
'completion::carretContextToken'.padEnd(42),
getTokenDebugInfo(carretContextToken, data.parser));
console.info(
'completion::carretContextSymbol'.padEnd(42),
getDebugSymbolInfo(carretContextSymbol));
console.info(
'completion::carretSymbol'.padEnd(42),
getDebugSymbolInfo(carretSymbol));
const items: CompletionItem[] = [];
const symbols: ISymbol[] = [];
let globalTypes: UCSymbolKind = UCSymbolKind.None;
let shouldIncludeTokenKeywords: boolean = true;

if (candidates.rules.has(UCParser.RULE_member) || ruleContext?.ruleIndex == UCParser.RULE_program) {
if (candidates.rules.has(UCParser.RULE_member) || carretRuleContext?.ruleIndex == UCParser.RULE_program) {
if (isStruct(scopeSymbol)) {
insertOverridableFunctions(document, scopeSymbol, items);
insertOverridableMethods(document, scopeSymbol, items);
insertOverridableStates(document, scopeSymbol, symbols);
}
}

for (const [rule, candiateRule] of candidates.rules) {
console.info('completion::candidates.rules::rule', data.parser.ruleNames[rule]);
console.info(
'completion::candidates.rules::rule'.padEnd(42),
data.parser.ruleNames[rule]);

const contextRule = candiateRule.ruleList.length
? candiateRule.ruleList[candiateRule.ruleList.length - 1]
Expand All @@ -380,9 +422,11 @@ async function buildCompletableSymbolItems(
return candiateRule.ruleList.indexOf(rule) !== -1;
}

console.info('completion::candidates.rules::contextRule', candiateRule.ruleList
.map(t => t && data.parser.ruleNames[t])
.join('.'));
console.info(
'completion::candidates.rules::contextRule'.padEnd(42),
candiateRule.ruleList
.map(t => t && data.parser.ruleNames[t])
.join('.'));

switch (contextRule) {
case UCParser.RULE_functionReturnParam: {
Expand All @@ -392,7 +436,7 @@ async function buildCompletableSymbolItems(

case UCParser.RULE_functionName: {
if (isStruct(scopeSymbol)) {
const symbolItems = getOverridableFunctions(document, scopeSymbol);
const symbolItems = getOverridableMethods(document, scopeSymbol);
symbols.push(...symbolItems);
}
break;
Expand All @@ -403,7 +447,7 @@ async function buildCompletableSymbolItems(
break;
}
// casting
globalTypes |= GlobalCastTypes;
globalTypes |= GlobalCastSymbolKinds;
}
}

Expand Down Expand Up @@ -737,12 +781,12 @@ async function buildCompletableSymbolItems(
// Only look for a class in a context of "ClassType.Type"
contextSymbol = ObjectsTable.getSymbol<UCClassSymbol>(id, UCSymbolKind.Class);
} else {
globalTypes |= TypeDeclSymbolTypes;
globalTypes |= TypeDeclSymbolKinds;
}

if (contextSymbol) {
if (isPackage(contextSymbol)) {
for (let symbol of ObjectsTable.getKinds(PackageTypeContextSymbolTypes)) {
for (let symbol of ObjectsTable.getKinds(PackageTypeContextSymbolKinds)) {
if (symbol.outer !== contextSymbol) {
continue;
}
Expand All @@ -752,7 +796,7 @@ async function buildCompletableSymbolItems(
const symbolItems = contextSymbol.getCompletionSymbols(
document,
ContextKind.None,
ClassTypeContextSymbolTypes);
ClassTypeContextSymbolKinds);
symbols.push(...symbolItems);
}
}
Expand All @@ -761,15 +805,15 @@ async function buildCompletableSymbolItems(

case UCParser.RULE_functionDecl: {
if (isStruct(scopeSymbol)) {
const symbolItems = getOverridableFunctions(document, scopeSymbol);
const symbolItems = getOverridableMethods(document, scopeSymbol);
symbols.push(...symbolItems);
}
break;
}

case UCParser.RULE_functionName: {
if (isStruct(scopeSymbol)) {
const symbolItems = getOverridableFunctions(document, scopeSymbol);
const symbolItems = getOverridableMethods(document, scopeSymbol);
symbols.push(...symbolItems);
}
break;
Expand Down

0 comments on commit 18926ba

Please sign in to comment.