@@ -179,13 +179,7 @@ async function getPythonLibraryLocation(uri: string): Promise<string> {
179179}
180180
181181connection . 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
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+
410442function 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.
804836connection . 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.
865953connection . onCompletionResolve (
0 commit comments