Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment: imported module field autocompletion #243

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
86 changes: 4 additions & 82 deletions src/server/imports.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import { pascalCase } from 'change-case';
import { MultiMap } from 'mnemonist';
import { AST, Node } from 'motoko/lib/ast';
import { Context, getContext } from './context';
import { Import, Program, matchNode } from './syntax';
import { Import } from './syntax';
import { formatMotoko, getRelativeUri } from './utils';

interface ResolvedField {
name: string;
visibility: string;
ast: AST;
}

export default class ImportResolver {
// module name -> uri
private readonly _moduleNameUriMap = new MultiMap<string, string>(Set);
// uri -> resolved field
private readonly _fieldMap = new MultiMap<string, ResolvedField>(Set);
// import path -> file system uri
private readonly _fileSystemMap = new Map<string, string>();

Expand All @@ -25,57 +16,14 @@ export default class ImportResolver {
this._moduleNameUriMap.clear();
}

update(uri: string, program: Program | undefined): boolean {
update(uri: string): boolean {
const info = getImportInfo(uri, this.context);
if (!info) {
return false;
}
const [name, importUri] = info;
this._moduleNameUriMap.set(name, importUri);
this._fileSystemMap.set(importUri, uri);
if (program?.export) {
// Resolve field names
const { ast } = program.export;
const node =
matchNode(ast, 'LetD', (_pat: Node, exp: Node) => exp) || // Named
matchNode(ast, 'ExpD', (exp: Node) => exp); // Unnamed
if (node) {
matchNode(
node,
'ObjBlockE',
(_type: string, ...fields: Node[]) => {
this._fieldMap.delete(uri);
fields.forEach((field) => {
if (field.name !== 'DecField') {
console.error(
'Error: expected `DecField`, received',
field.name,
);
return;
}
const [dec, visibility] = field.args!;
if (visibility !== 'Public') {
return;
}
matchNode(dec, 'LetD', (pat: Node, exp: Node) => {
const name = matchNode(
pat,
'VarP',
(field: string) => field,
);
if (name) {
this._fieldMap.set(uri, {
name,
visibility,
ast: exp,
});
}
});
});
},
);
}
}
return true;
}

Expand All @@ -92,9 +40,6 @@ export default class ImportResolver {
changed = true;
}
}
if (this._fieldMap.delete(uri)) {
changed = true;
}
return changed;
}

Expand All @@ -110,31 +55,8 @@ export default class ImportResolver {
* Finds all available module-level imports.
* @returns Array of `[name, path]` entries
*/
getNameEntries(uri: string): [string, string][] {
return [...this._moduleNameUriMap.entries()].map(([name, path]) => [
name,
getRelativeUri(uri, path),
]);
}

// /**
// * Finds all importable fields.
// * @returns Array of `[name, field, path]` entries
// */
// getFieldEntries(uri: string): [ResolvedField, string][] {
// return [...this._fieldMap.entries()].map(([path, field]) => [
// field,
// getRelativeUri(uri, path),
// ]);
// }

/**
* Finds all importable fields for a given document.
* @returns Array of `[name, field, path]` entries
*/
getFields(uri: string): ResolvedField[] {
const fields = this._fieldMap.get(uri);
return fields ? [...fields] : [];
getNameEntries(): [string, string][] {
return [...this._moduleNameUriMap.entries()];
}

/**
Expand Down
10 changes: 5 additions & 5 deletions src/server/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function findMostSpecificNodeForPosition(
ast: AST,
position: Position,
scoreFn: (node: Node) => number | boolean,
isMouseCursor = false,
deep = false,
): (Node & { start: Span; end: Span }) | undefined {
const nodes = findNodes(
ast,
Expand All @@ -38,7 +38,7 @@ export function findMostSpecificNodeForPosition(
(position.line !== node.start[0] - 1 ||
position.character >= node.start[1]) &&
(position.line !== node.end[0] - 1 ||
position.character < node.end[1] + (isMouseCursor ? 0 : 1)),
position.character < node.end[1] + (deep ? 0 : 1)),
);

// Find the most specific AST node for the cursor position
Expand Down Expand Up @@ -124,7 +124,7 @@ const nodePriorities: Record<string, number> = {
export function findDefinition(
uri: string,
position: Position,
isMouseCursor = false,
deep = false,
): Definition | undefined {
// Get relevant AST node
const context = getContext(uri);
Expand All @@ -141,7 +141,7 @@ export function findDefinition(
status.ast,
position,
(node) => nodePriorities[node.name] || 0,
isMouseCursor,
deep,
);
if (!node) {
return;
Expand Down Expand Up @@ -266,7 +266,7 @@ function followImport(
console.log('Missing export for', uri);
return;
}
if (status?.outdated) {
if (status.outdated) {
console.log('Outdated AST for', uri);
return;
}
Expand Down