Skip to content
This repository was archived by the owner on Feb 20, 2024. It is now read-only.

Commit ae4b25b

Browse files
committed
code completion for imports WIP
1 parent cab6707 commit ae4b25b

File tree

1 file changed

+142
-54
lines changed

1 file changed

+142
-54
lines changed

server/src/server.ts

Lines changed: 142 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,7 @@ async function getPythonLibraryLocation(uri: string): Promise<string> {
179179
}
180180

181181
connection.onDefinition(async (params) => {
182-
183-
if (packageSearchPaths === undefined) {
184-
const packageLocation = await getPythonLibraryLocation(params.textDocument.uri);
185-
packageSearchPaths = delimitedWorkspaceFolders + ";" + packageLocation;
186-
connection.console.log(`Package search paths: ${packageSearchPaths}`);
187-
188-
}
182+
initPackageSearchPaths(params.textDocument.uri);
189183

190184
// from contracts.Initializable import initialized, initialize
191185

@@ -259,20 +253,7 @@ end
259253

260254
let position = params.position
261255
if (textDocumentFromURI !== undefined) {
262-
let start = {
263-
line: position.line,
264-
character: 0,
265-
};
266-
let end = {
267-
line: position.line + 1,
268-
character: 0,
269-
};
270-
let text = textDocumentFromURI.getText({ start, end });
271-
let index = textDocumentFromURI.offsetAt(position) - textDocumentFromURI.offsetAt(start);
272-
let wordWithDot = getWord(text, index, true);
273-
connection.console.log(`Current word with dot: ${wordWithDot}`);
274-
let word = getWord(text, index, false);
275-
connection.console.log(`Current word: ${word}`);
256+
let { wordWithDot, word } = getWordAtPosition(position, textDocumentFromURI);
276257

277258
connection.console.log(`Imports map size: ${imports.size}`);
278259

@@ -407,6 +388,57 @@ connection.onDidChangeConfiguration(change => {
407388
documents.all().forEach(validateTextDocument);
408389
});
409390

391+
392+
function getImportAtOrBeforePosition(position: Position, textDocumentFromURI: TextDocument, ) {
393+
let start = {
394+
line: position.line,
395+
character: 0,
396+
};
397+
let end =
398+
{
399+
line: position.line,
400+
character: position.character,
401+
}
402+
403+
let text = textDocumentFromURI.getText({ start, end });
404+
let split = text.split(/\s+/);
405+
if (split !== undefined && split[0] !== undefined && split[0] === 'from') {
406+
let textBeforeCursor = split[split.length - 1];
407+
connection.console.log(`found import text: ${textBeforeCursor}`);
408+
return textBeforeCursor;
409+
} else {
410+
connection.console.log(`not an import`);
411+
412+
return undefined;
413+
}
414+
}
415+
416+
function getWordAtPosition(position: Position, textDocumentFromURI: TextDocument, cutoffAtPosition?: boolean) {
417+
let start = {
418+
line: position.line,
419+
character: 0,
420+
};
421+
let end = cutoffAtPosition ?
422+
{
423+
// right after actual cursor
424+
line: position.line,
425+
character: position.character + 1,
426+
}
427+
:
428+
{
429+
// next line
430+
line: position.line + 1,
431+
character: 0,
432+
};
433+
let text = textDocumentFromURI.getText({ start, end });
434+
let index = textDocumentFromURI.offsetAt(cutoffAtPosition? end : position) - textDocumentFromURI.offsetAt(start);
435+
let wordWithDot = getWord(text, index, true);
436+
connection.console.log(`Current word with dot: ${wordWithDot}`);
437+
let word = getWord(text, index, false);
438+
connection.console.log(`Current word: ${word}`);
439+
return { wordWithDot, word };
440+
}
441+
410442
function getModuleURI(moduleName: string) {
411443
let moduleRelativePath = moduleName.split('.').join('/') + ".cairo";
412444
let moduleUrl = undefined;
@@ -802,64 +834,120 @@ function getQuickFix(diagnostic: Diagnostic, title: string, range: Range, replac
802834

803835
// This handler provides the initial list of the completion items.
804836
connection.onCompletion(
805-
async (_textDocumentPosition: TextDocumentPositionParams): Promise<CompletionItem[]> => {
837+
async (textDocPositionParams: TextDocumentPositionParams): Promise<CompletionItem[]> => {
806838
// The passed parameter contains the position of the text document in
807839
// which code complete got requested.
808840

809841
let completionItems: CompletionItem[] = [];
810842

811-
// TODO parse snippets from a file instead of hardcoding
812-
{
813-
let sampleSnippet: string =
814-
"func main():\n"+
815-
" [ap] = 1000; ap++\n"+
816-
" [ap] = 2000; ap++\n"+
817-
" [ap] = [ap - 2] + [ap - 1]; ap++\n"+
818-
" ret\n"+
819-
"end";
820-
insertSnippet(_textDocumentPosition, sampleSnippet, completionItems, undefined, "Cairo template", 0);
821-
822-
let pythonSnippet: string =
823-
"%[ %]"
824-
insertSnippet(_textDocumentPosition, pythonSnippet, completionItems, undefined, "Python literal", 0);
843+
let textDocumentFromURI = documents.get(textDocPositionParams.textDocument.uri)
844+
if (textDocumentFromURI != null) {
845+
let truncatedImport = getImportAtOrBeforePosition(textDocPositionParams.position, textDocumentFromURI);
846+
if (truncatedImport === undefined) {
847+
// return empty since it's not an import statement
848+
return completionItems;
849+
}
850+
const packages = await getAllPackagesStartingWith(textDocPositionParams.textDocument.uri, truncatedImport);
851+
for (const packageString of packages) {
852+
connection.console.log(`handling package ${packageString}`);
853+
854+
completionItems.push(getNewCompletionItem(textDocPositionParams, packageString /* actual import - truncated import prefix */, packageString, 0));
855+
}
856+
825857
}
826858

859+
827860
return completionItems;
828861
}
829862
);
830863

831-
function insertSnippet(_textDocumentPosition: TextDocumentPositionParams, snippetText: string, completionItems: CompletionItem[], imports: string | undefined, label: string, sortOrder: number) {
864+
function isFolder(dirPath: string) {
865+
return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
866+
}
867+
868+
async function getAllPackagesStartingWith(uri: string, prefix: string) : Promise<string[]> {
869+
await initPackageSearchPaths(uri);
870+
871+
let packages: string[] = [];
872+
873+
// TODO get modules relative to folders in actual CAIRO_PATH as well
874+
875+
for (let element of packageSearchPaths.split(';')) {
876+
877+
const lastDotIndex = prefix.lastIndexOf('.');
878+
const parentFolderOfPrefix = prefix.substring(0, lastDotIndex);
879+
const parentFolderAsPath = parentFolderOfPrefix.split('.').join('/');
880+
881+
let possibleImportFolder = path.join(element, parentFolderAsPath);
882+
connection.console.log(`Possible import folder: ${possibleImportFolder}`);
883+
884+
if (!isFolder(possibleImportFolder)) {
885+
continue;
886+
}
887+
fs.readdirSync(possibleImportFolder).forEach((file: any) => {
888+
//files?.forEach(file => {
889+
const fileFullPath = path.join(possibleImportFolder, file);
890+
connection.console.log(`CHECKING ${fileFullPath}`);
891+
if (isFolder(fileFullPath)) {
892+
connection.console.log(`Adding package path: ${fileFullPath}`);
893+
packages.push(convertPathToImport(fileFullPath, element));
894+
} else if (fileFullPath.endsWith(".cairo")) {
895+
const withoutFileExtension = fileFullPath.substring(0, fileFullPath.lastIndexOf(".cairo"));
896+
connection.console.log(`Adding package path for cairo file: ${withoutFileExtension}`);
897+
packages.push(convertPathToImport(withoutFileExtension, element));
898+
}
899+
// });
900+
});
901+
902+
// let possibleModulePath = path.join(element, moduleRelativePath);
903+
// connection.console.log(`Possible module path: ${possibleModulePath}`);
904+
905+
// if (fs.existsSync(possibleModulePath)) {
906+
// connection.console.log(`Module exists: ${possibleModulePath}`);
907+
// moduleUrl = url.pathToFileURL(possibleModulePath);
908+
// modulePath = possibleModulePath;
909+
// connection.console.log(`Module URL: ${moduleUrl}`);
910+
// break;
911+
// }
912+
}
913+
914+
connection.console.log(`all ${packages.length} packages: ${packages}`);
915+
916+
return packages;
917+
}
918+
919+
920+
function convertPathToImport(fileFullPath: any, element: string): string {
921+
return fileFullPath.substring(element.length + 1).split('/').join('.');
922+
}
923+
924+
async function initPackageSearchPaths(uri: string) {
925+
if (packageSearchPaths === undefined) {
926+
const packageLocation = await getPythonLibraryLocation(uri);
927+
packageSearchPaths = delimitedWorkspaceFolders + ";" + packageLocation;
928+
connection.console.log(`Package search paths: ${packageSearchPaths}`);
929+
}
930+
}
931+
932+
function getNewCompletionItem(_textDocumentPosition: TextDocumentPositionParams, newText: string, label: string, sortOrder: number) {
832933
let textEdit: TextEdit = {
833934
range: {
834935
start: _textDocumentPosition.position,
835936
end: _textDocumentPosition.position
836937
},
837-
newText: snippetText
938+
newText: newText
838939
};
839940
let completionItem: CompletionItem = {
840941
label: label,
841-
kind: CompletionItemKind.Snippet,
942+
kind: CompletionItemKind.Module,
842943
data: undefined,
843944
textEdit: textEdit,
844945
sortText: String(sortOrder)
845946
};
846-
// check if imports should be added
847-
let textDocument = documents.get(_textDocumentPosition.textDocument.uri)
848-
let textDocumentContents = textDocument?.getText()
849-
if (imports !== undefined && (textDocumentContents === undefined || !String(textDocumentContents).includes(imports))) {
850-
let additionalTextEdit = {
851-
range: {
852-
start: { line: 0, character: 0 },
853-
end: { line: 0, character: 0 }
854-
},
855-
newText: imports
856-
};
857-
completionItem.additionalTextEdits = [additionalTextEdit]
858-
}
859-
860-
completionItems.push(completionItem);
947+
return completionItem;
861948
}
862949

950+
863951
// This handler resolves additional information for the item selected in
864952
// the completion list.
865953
connection.onCompletionResolve(

0 commit comments

Comments
 (0)