94
94
cancelFileCheck* : Future[void ]
95
95
checkInProgress* : bool
96
96
needsChecking* : bool
97
+ textDocument* : TextDocumentItem
97
98
98
99
CommandLineParams* = object
99
100
clientProcessId* : Option[int ]
134
135
onExit* : OnExitCallback
135
136
projectFiles* : Table[string , Project]
136
137
openFiles* : Table[string , NlsFileInfo]
138
+ idleOpenFiles* : Table[string , NlsFileInfo] # We close the file when its inactive and store it here.
137
139
workspaceConfiguration* : Future[JsonNode]
138
140
prevWorkspaceConfiguration* : Future[JsonNode]
139
141
inlayHintsRefreshRequest* : Future[JsonNode]
@@ -193,6 +195,7 @@ proc initLs*(params: CommandLineParams, storageDir: string): LanguageServer =
193
195
filesWithDiags: initHashSet[string ](),
194
196
transportMode: params.transport.get(),
195
197
openFiles: initTable[string , NlsFileInfo](),
198
+ # idleOpenFiles: initTable[string , NlsFileInfo](),
196
199
responseMap: newTable[string , Future[JsonNode]](),
197
200
storageDir: storageDir,
198
201
cmdLineClientProcessId: params.clientProcessId,
@@ -472,7 +475,8 @@ proc getNimSuggestPathAndVersion(
472
475
nimsuggestPath = findExe " nimsuggest"
473
476
else :
474
477
nimVersion = getNimVersion(nimsuggestPath.parentDir)
475
- ls.showMessage(fmt " Using {nimVersion}" , MessageType.Info)
478
+ # ls.showMessage(fmt "Using {nimVersion}", MessageType.Info)
479
+ debug " Using {nimVersion}" , nimVersion = nimVersion
476
480
(nimsuggestPath, nimVersion)
477
481
478
482
proc getNimPath* (conf: NlsConfig): Option[string ] =
@@ -693,9 +697,146 @@ proc sendDiagnostics*(ls: LanguageServer, diagnostics: seq[Suggest] | seq[CheckR
693
697
else :
694
698
ls.filesWithDiags.excl path
695
699
700
+
701
+ proc warnIfUnknown* (
702
+ ls: LanguageServer, ns: Nimsuggest, uri: string , projectFile: string
703
+ ): Future[void ] {.async, gcsafe.} =
704
+ let path = uri.uriToPath
705
+ let isFileKnown = await ns.isKnown(path)
706
+ if not isFileKnown and not ns.canHandleUnknown:
707
+ ls.showMessage(
708
+ fmt """ {path} is not compiled as part of project {projectFile}.
709
+ In orde to get the IDE features working you must either configure nim.projectMapping or import the module. """ ,
710
+ MessageType.Warning,
711
+ )
712
+
713
+ proc createOrRestartNimsuggest* (
714
+ ls: LanguageServer, projectFile: string , uri = " "
715
+ ) {.gcsafe, raises: [].}
716
+
717
+ proc getNimsuggestInner(ls: LanguageServer, uri: string ): Future[Nimsuggest] {.async.} =
718
+ assert uri in ls.openFiles, " File not open"
719
+
720
+ let projectFile = await ls.openFiles[uri].projectFile
721
+ if not ls.projectFiles.hasKey(projectFile):
722
+ debug " Creating new nimsuggest instance" , uri = uri, projectFile = projectFile
723
+ ls.createOrRestartNimsuggest(projectFile, uri)
724
+ # Wait a bit to allow nimsuggest to start
725
+ await sleepAsync(10 )
726
+
727
+ # Check multiple times with small delays
728
+ var attempts = 0
729
+ const maxAttempts = 10
730
+ while attempts < maxAttempts:
731
+ if projectFile in ls.projectFiles:
732
+ ls.lastNimsuggest = ls.projectFiles[projectFile].ns
733
+ return await ls.projectFiles[projectFile].ns
734
+
735
+ inc attempts
736
+ if attempts < maxAttempts:
737
+ await sleepAsync(100 )
738
+ debug " Waiting for nimsuggest to initialize" ,
739
+ uri = uri,
740
+ projectFile = projectFile,
741
+ attempt = attempts
742
+
743
+ debug " Failed to get nimsuggest after waiting" , uri = uri, projectFile = projectFile
744
+ return nil
745
+
746
+ proc tryGetNimsuggest* (
747
+ ls: LanguageServer, uri: string
748
+ ): Future[Option[Nimsuggest]] {.raises:[], gcsafe.}
749
+
750
+ proc checkFile* (ls: LanguageServer, uri: string ): Future[void ] {.raises:[], gcsafe.}
751
+
752
+ proc didCloseFile* (
753
+ ls: LanguageServer, uri: string
754
+ ): Future[void ] {.async, gcsafe.} =
755
+ debug " Closed the following document:" , uri = uri
756
+
757
+ if ls.openFiles[uri].changed:
758
+ # check the file if it is closed but not saved.
759
+ traceAsyncErrors ls.checkFile(uri)
760
+
761
+ ls.openFiles.del uri
762
+
763
+ proc makeIdleFile* (
764
+ ls: LanguageServer, file: NlsFileInfo
765
+ ): Future[void ] {.async, gcsafe.} =
766
+ let uri = file.textDocument.uri
767
+ if uri in ls.openFiles:
768
+ await ls.didCloseFile(uri)
769
+ ls.idleOpenFiles[uri] = file
770
+ ls.openFiles.del(uri)
771
+
772
+ proc getProjectFile* (fileUri: string , ls: LanguageServer): Future[string ] {.raises:[], gcsafe.}
773
+
774
+ proc didOpenFile* (
775
+ ls: LanguageServer, textDocument: TextDocumentItem
776
+ ): Future[void ] {.async, gcsafe.} =
777
+ with textDocument:
778
+ debug " New document opened for URI:" , uri = uri
779
+ let
780
+ file = open(ls.uriStorageLocation(uri), fmWrite)
781
+ projectFileFuture = getProjectFile(uriToPath(uri), ls)
782
+
783
+ ls.openFiles[uri] =
784
+ NlsFileInfo(projectFile: projectFileFuture, changed: false , fingerTable: @ [], textDocument: textDocument)
785
+
786
+ if uri in ls.idleOpenFiles:
787
+ ls.idleOpenFiles.del(uri)
788
+
789
+ let projectFile = await projectFileFuture
790
+ debug " Document associated with the following projectFile" ,
791
+ uri = uri, projectFile = projectFile
792
+ if not ls.projectFiles.hasKey(projectFile):
793
+ debug " Will create nimsuggest for this file" , uri = uri
794
+ ls.createOrRestartNimsuggest(projectFile, uri)
795
+
796
+ for line in text.splitLines:
797
+ if uri in ls.openFiles:
798
+ ls.openFiles[uri].fingerTable.add line.createUTFMapping()
799
+ file.writeLine line
800
+ file.close()
801
+ ls.tryGetNimSuggest(uri).addCallback do (fut: Future[Option[Nimsuggest]]) -> void :
802
+ if not fut.failed and fut.read.isSome:
803
+ discard ls.warnIfUnknown(fut.read.get(), uri, projectFile)
804
+
805
+ let projectFileUri = projectFile.pathToUri
806
+ if projectFileUri notin ls.openFiles:
807
+ var textDocument = textDocument
808
+ textDocument.uri = projectFileUri
809
+ await didOpenFile(ls, textDocument)
810
+
811
+ debug " Opening project file" , uri = projectFile, file = uri
812
+ ls.showMessage(fmt " Opening {uri}" , MessageType.Info)
813
+
696
814
proc tryGetNimsuggest* (
697
815
ls: LanguageServer, uri: string
698
- ): Future[Option[Nimsuggest]] {.async.}
816
+ ): Future[Option[Nimsuggest]] {.async.} =
817
+
818
+ if uri in ls.idleOpenFiles:
819
+ let idleFile = ls.idleOpenFiles[uri]
820
+ await didOpenFile(ls, idleFile.textDocument)
821
+
822
+ if uri notin ls.openFiles:
823
+ return none(NimSuggest)
824
+
825
+ var retryCount = 0
826
+ const maxRetries = 3
827
+ while retryCount < maxRetries:
828
+ let ns = await getNimsuggestInner(ls, uri)
829
+ if not ns.isNil:
830
+ return some ns
831
+
832
+ # If nimsuggest is nil, wait a bit and retry
833
+ inc retryCount
834
+ if retryCount < maxRetries:
835
+ debug " Nimsuggest not ready, retrying..." , uri = uri, attempt = retryCount
836
+ await sleepAsync(10000 * retryCount) # Exponential backoff
837
+
838
+ debug " Nimsuggest not found after retries" , uri = uri
839
+ return none(NimSuggest)
699
840
700
841
proc checkProject* (ls: LanguageServer, uri: string ): Future[void ] {.async, gcsafe.} =
701
842
if not ls.getWorkspaceConfiguration.await().autoCheckProject.get(true ):
@@ -781,9 +922,7 @@ proc checkProject*(ls: LanguageServer, uri: string): Future[void] {.async, gcsaf
781
922
debug " Running delayed check project..." , uri = uri
782
923
traceAsyncErrors ls.checkProject(uri)
783
924
784
- proc createOrRestartNimsuggest* (
785
- ls: LanguageServer, projectFile: string , uri = " "
786
- ) {.gcsafe, raises: [].}
925
+
787
926
788
927
proc onErrorCallback(args: (LanguageServer, string ), project: Project) =
789
928
let
@@ -816,6 +955,7 @@ proc createOrRestartNimsuggest*(
816
955
ls: LanguageServer, projectFile: string , uri = " "
817
956
) {.gcsafe, raises: [].} =
818
957
try :
958
+ debug " Starting createOrRestartNimsuggest" , projectFile = projectFile, uri = uri
819
959
let
820
960
configuration = ls.getWorkspaceConfiguration().waitFor()
821
961
workingDir = ls.getWorkingDir(projectFile).waitFor()
@@ -832,64 +972,43 @@ proc createOrRestartNimsuggest*(
832
972
ls.createOrRestartNimsuggest(projectFile, uri)
833
973
ls.sendStatusChanged()
834
974
errorCallback = partial(onErrorCallback, (ls, uri))
835
- # TODO instead of waiting here, this whole function should be async.
836
- projectNext = waitFor createNimsuggest(
837
- projectFile,
838
- nimsuggestPath,
839
- version,
840
- timeout,
841
- restartCallback,
842
- errorCallback,
843
- workingDir,
844
- configuration.logNimsuggest.get(false ),
845
- configuration.exceptionHintsEnabled,
846
- )
847
- token = fmt " Creating nimsuggest for {projectFile}"
848
-
849
- ls.workDoneProgressCreate(token)
850
-
851
- if ls.projectFiles.hasKey(projectFile):
975
+
976
+ debug " Creating new nimsuggest project" , projectFile = projectFile
977
+ let projectNext = waitFor createNimsuggest(
978
+ projectFile,
979
+ nimsuggestPath,
980
+ version,
981
+ timeout,
982
+ restartCallback,
983
+ errorCallback,
984
+ workingDir,
985
+ configuration.logNimsuggest.get(false ),
986
+ configuration.exceptionHintsEnabled,
987
+ )
988
+
989
+ if projectFile in ls.projectFiles:
852
990
var project = ls.projectFiles[projectFile]
853
991
project.stop()
854
- ls.projectFiles[projectFile] = projectNext
855
- ls.progress(token, " begin" , fmt " Restarting nimsuggest for {projectFile}" )
856
- else :
857
- ls.progress(token, " begin" , fmt " Creating nimsuggest for {projectFile}" )
858
- ls.projectFiles[projectFile] = projectNext
859
-
992
+ ls.projectFiles[projectFile] = projectNext
993
+
860
994
projectNext.ns.addCallback do (fut: Future[Nimsuggest]) :
861
- if fut.read.project.failed:
862
- let msg = fut.read.project.errorMessage
995
+ if fut.failed:
996
+ let msg = fut.error.msg
997
+ error " Nimsuggest initialization failed" , projectFile = projectFile, error = msg
863
998
ls.showMessage(
864
999
fmt " Nimsuggest initialization for {projectFile} failed with: {msg}" ,
865
1000
MessageType.Error,
866
1001
)
867
1002
else :
1003
+ debug " Nimsuggest initialized successfully" , projectFile = projectFile
868
1004
ls.showMessage(fmt " Nimsuggest initialized for {projectFile}" , MessageType.Info)
869
1005
traceAsyncErrors ls.checkProject(uri)
870
1006
fut.read().openFiles.incl uri
871
- ls.progress(token, " end" )
872
1007
ls.sendStatusChanged()
873
- except CatchableError:
874
- discard
875
-
876
- proc getNimsuggestInner(ls: LanguageServer, uri: string ): Future[Nimsuggest] {.async.} =
877
- assert uri in ls.openFiles, " File not open"
878
-
879
- let projectFile = await ls.openFiles[uri].projectFile
880
- if not ls.projectFiles.hasKey(projectFile):
881
- ls.createOrRestartNimsuggest(projectFile, uri)
882
-
883
- ls.lastNimsuggest = ls.projectFiles[projectFile].ns
884
- return await ls.projectFiles[projectFile].ns
885
-
886
- proc tryGetNimsuggest* (
887
- ls: LanguageServer, uri: string
888
- ): Future[Option[Nimsuggest]] {.async.} =
889
- if uri notin ls.openFiles:
890
- none(NimSuggest)
891
- else :
892
- some await getNimsuggestInner(ls, uri)
1008
+ except CatchableError as ex:
1009
+ error " Failed to create/restart nimsuggest" ,
1010
+ projectFile = projectFile,
1011
+ error = ex.msg
893
1012
894
1013
proc restartAllNimsuggestInstances(ls: LanguageServer) =
895
1014
debug " Restarting all nimsuggest instances"
@@ -1006,17 +1125,7 @@ proc getProjectFile*(fileUri: string, ls: LanguageServer): Future[string] {.asyn
1006
1125
1007
1126
debug " getProjectFile " , project = result , fileUri = fileUri
1008
1127
1009
- proc warnIfUnknown* (
1010
- ls: LanguageServer, ns: Nimsuggest, uri: string , projectFile: string
1011
- ): Future[void ] {.async, gcsafe.} =
1012
- let path = uri.uriToPath
1013
- let isFileKnown = await ns.isKnown(path)
1014
- if not isFileKnown and not ns.canHandleUnknown:
1015
- ls.showMessage(
1016
- fmt """ {path} is not compiled as part of project {projectFile}.
1017
- In orde to get the IDE features working you must either configure nim.projectMapping or import the module. """ ,
1018
- MessageType.Warning,
1019
- )
1128
+
1020
1129
1021
1130
proc checkFile* (ls: LanguageServer, uri: string ): Future[void ] {.async.} =
1022
1131
let conf = await ls.getAndWaitForWorkspaceConfiguration()
@@ -1072,15 +1181,26 @@ proc removeIdleNimsuggests*(ls: LanguageServer) {.async.} =
1072
1181
for project in toStop:
1073
1182
debug "Removing idle nimsuggest", project = project.file
1074
1183
project.errorCallback = none(ProjectCallback)
1184
+
1185
+ let ns = await project.ns
1186
+ for uri in ns.openFiles:
1187
+ debug "Removing idle nimsuggest open file", uri = uri
1188
+ await ls.makeIdleFile(ls.openFiles[uri])
1075
1189
project.stop()
1076
1190
ls.projectFiles.del(project.file)
1191
+
1077
1192
ls.showMessage(
1078
1193
fmt"Nimsuggest for {project.file} was stopped because it was idle for too long",
1079
1194
MessageType.Info,
1080
1195
)
1081
1196
1082
1197
proc tick*(ls: LanguageServer): Future[void ] {.async.} =
1083
1198
# debug "Ticking at ", now = now(), prs = ls.pendingRequests.len
1084
- ls.removeCompletedPendingRequests()
1085
- await ls.removeIdleNimsuggests()
1086
- ls.sendStatusChanged
1199
+ try:
1200
+ ls.removeCompletedPendingRequests()
1201
+ await ls.removeIdleNimsuggests()
1202
+ ls.sendStatusChanged
1203
+ except CatchableError as ex:
1204
+ error "Error in tick", msg = ex.msg
1205
+ writeStacktrace(ex)
1206
+
0 commit comments