Skip to content

Commit c5c6b6f

Browse files
committed
Replace single-file tracking with whole-project type-checking
- Remove the `openFile` field and `loadIfNeeded` logic; add `ipkgPath` config option so the server knows which .ipkg to use - `loadURI` now builds the full project via `check pkg` instead of `buildDeps`, then reloads the main module's TTM on success - `withURI` loads metadata from TTM directly without re-type-checking - `sendDiagnostics` now groups errors by source file and sends per-file diagnostics across the whole project; clears stale diagnostics first - Drop the "must match currently open file" guard from Definition, DocumentHighlight, and DocumentSymbol handlers
1 parent 800fe94 commit c5c6b6f

9 files changed

Lines changed: 207 additions & 133 deletions

File tree

src/Language/LSP/Definition.idr

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,6 @@ gotoDefinition : Ref Ctxt Defs
6565
=> DefinitionParams -> Core (Maybe Location)
6666
gotoDefinition params = do
6767
logI GotoDefinition "Checking for \{show params.textDocument.uri} at \{show params.position}"
68-
-- Check actual doc
69-
Just (actualUri, _) <- gets LSPConf openFile
70-
| Nothing => logE GotoDefinition "No open file" >> pure Nothing
71-
let True = actualUri == params.textDocument.uri
72-
| False => do
73-
logD GotoDefinition "Expected request for the currently opened file \{show actualUri}, instead received \{show params.textDocument.uri}"
74-
pure Nothing
7568

7669
let line = params.position.line
7770
let col = params.position.character

src/Language/LSP/DocumentHighlight.idr

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,8 @@ documentHighlights : Ref Ctxt Defs
2525
=> Ref LSPConf LSPConfiguration
2626
=> DocumentHighlightParams -> Core (List DocumentHighlight)
2727
documentHighlights params = do
28-
logI DocumentHighlight "Searching for \{show params.textDocument.uri}"
29-
Just (uri, _) <- gets LSPConf openFile
30-
| Nothing => logE DocumentHighlight "No open file" >> pure []
31-
let True = uri == params.textDocument.uri
32-
| False => do
33-
logD DocumentHighlight "Expected request for the currently opened file \{show uri}, instead received \{show params.textDocument.uri}"
34-
pure []
28+
let uri = show params.textDocument.uri
29+
logI DocumentHighlight "Searching for \{uri}"
3530

3631
let line = params.position.line
3732
let col = params.position.character

src/Language/LSP/DocumentSymbol.idr

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,9 @@ documentSymbol : Ref Ctxt Defs
6060
=> Ref LSPConf LSPConfiguration
6161
=> DocumentSymbolParams -> Core (List SymbolInformation)
6262
documentSymbol params = do
63-
logI DocumentSymbol "Making for \{show params.textDocument.uri}"
64-
Just (uri, _) <- gets LSPConf openFile
65-
| Nothing => logE DocumentSymbol "No open file" >> pure []
66-
let True = uri == params.textDocument.uri
67-
| False => do
68-
logD DocumentSymbol "Expected request for the currently opened file \{show uri}, instead received \{show params.textDocument.uri}"
69-
pure []
63+
let uri = params.textDocument.uri
64+
logI DocumentSymbol "Making for \{show uri}"
65+
7066
defs <- get Ctxt
7167
-- Get the current and visible namespaces from the context
7268
let currentNamespaces = defs.currentNS :: defs.nestedNS

src/Server/Configuration.idr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ record LSPConfiguration where
4747
||| True if the client has completed the shutdown protocol.
4848
||| @see https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#shutdown
4949
isShutdown : Bool
50-
||| The currently loaded file, if any, and its version.
51-
openFile : Maybe (DocumentURI, Int)
5250
||| Files with modification not saved. Command will fail on these files.
5351
dirtyFiles : SortedSet DocumentURI
5452
||| Files with errors
@@ -71,6 +69,8 @@ record LSPConfiguration where
7169
virtualDocuments : SortedMap DocumentURI (Int, String) -- Version content
7270
||| Insert only function name for completions
7371
briefCompletions : Bool
72+
||| Maybe specified path to the .ipkg file
73+
ipkgPath : Maybe String
7474

7575
||| Server default configuration. Uses standard input and standard output for input/output.
7676
export
@@ -83,7 +83,6 @@ defaultConfig =
8383
, logSeverity = Debug
8484
, initialized = Nothing
8585
, isShutdown = False
86-
, openFile = Nothing
8786
, dirtyFiles = empty
8887
, errorFiles = empty
8988
, semanticTokensSentFiles = empty
@@ -94,5 +93,6 @@ defaultConfig =
9493
, nextRequestId = 0
9594
, completionCache = empty
9695
, virtualDocuments = empty
97-
, briefCompletions = False
96+
, briefCompletions = False
97+
, ipkgPath = Nothing
9898
}

src/Server/Diagnostics.idr

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,15 @@ warningToDiagnostic : Ref Ctxt Defs
113113
=> Ref Syn SyntaxInfo
114114
=> Ref ROpts REPLOpts
115115
=> (caps : Maybe PublishDiagnosticsClientCapabilities)
116-
-> (uri : URI)
117116
-> (warning : Warning)
118117
-> Core Diagnostic
119-
warningToDiagnostic caps uri warning = do
118+
warningToDiagnostic caps warning = do
120119
defs <- get Ctxt
121120
warningAnn <- pwarning (killWarningLoc warning)
122121
let loc = Just (getWarningLoc warning)
123122
let wdir = defs.options.dirs.working_dir
124-
p <- maybe (pure uri.path) (pure . (wdir </>) <=< nsToSource replFC)
125-
((\case PhysicalIdrSrc ident => Just ident; _ => Nothing) . fst <=< isNonEmptyFC =<< loc)
126-
if System.Path.parse (uriPathToSystemPath uri.path) == System.Path.parse p
127-
then do let related = Nothing -- TODO related diagnostics?
128-
pure $ buildDiagnostic Warning loc warningAnn related
129-
else pure $ buildDiagnostic Warning (toStart <$> loc) ("In" <++> pretty0 p <+> colon <++> warningAnn) Nothing
123+
let related = Nothing -- TODO related diagnostics?
124+
pure (buildDiagnostic Warning loc warningAnn related)
130125

131126

132127
||| Computes a LSP `Diagnostic` from a compiler error.
@@ -136,20 +131,15 @@ warningToDiagnostic caps uri warning = do
136131
||| @err The compiler error.
137132
export
138133
errorToDiagnostic : Ref Ctxt Defs
139-
=> Ref Syn SyntaxInfo
140-
=> Ref ROpts REPLOpts
141-
=> (caps : Maybe PublishDiagnosticsClientCapabilities)
142-
-> (uri : URI)
143-
-> (error : Error)
144-
-> Core Diagnostic
145-
errorToDiagnostic caps uri err = do
134+
=> Ref Syn SyntaxInfo
135+
=> Ref ROpts REPLOpts
136+
=> (caps : Maybe PublishDiagnosticsClientCapabilities)
137+
-> (error : Error)
138+
-> Core Diagnostic
139+
errorToDiagnostic caps err = do
146140
defs <- get Ctxt
147141
error <- perror (killErrorLoc err)
148142
let loc = getErrorLoc err
149143
let wdir = defs.options.dirs.working_dir
150-
p <- maybe (pure uri.path) (pure . (wdir </>) <=< nsToSource replFC)
151-
((\case PhysicalIdrSrc ident => Just ident; _ => Nothing) . fst <=< isNonEmptyFC =<< loc)
152-
if System.Path.parse (uriPathToSystemPath uri.path) == System.Path.parse p
153-
then do let related = (flip toMaybe (getRelatedErrors uri err) <=< relatedInformation) =<< caps
154-
pure $ buildDiagnostic Error loc error related
155-
else pure $ buildDiagnostic Error (toStart <$> loc) ("In" <++> pretty0 p <+> colon <++> error) Nothing
144+
let related = Nothing -- TODO
145+
pure (buildDiagnostic Error loc error related)

src/Server/ProcessMessage.idr

Lines changed: 108 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Data.OneOf
1717
import Data.SortedMap
1818
import Data.SortedSet
1919
import Data.String
20+
import Idris.CommandLine
2021
import Idris.Doc.String
2122
import Idris.Error
2223
import Idris.IDEMode.Holes
@@ -176,15 +177,28 @@ processSettings (JObject xs) = do
176177
when (pp.showMachineNames /= b) $ do
177178
setPPrint ({ showMachineNames := b } pp)
178179
update LSPConf ({ cachedHovers := empty })
180+
logI Configuration "Show machine names set to \{show b}"
179181
Just _ => logE Configuration "Incorrect type for show machine names, expected boolean"
180182
Nothing => pure ()
181183
case lookup "logSeverity" xs of
182184
Just (JString ll) =>
183-
whenJust (parseSeverity ll) $ \l => update LSPConf ({ logSeverity := l})
185+
case parseSeverity ll of
186+
Just l => update LSPConf ({ logSeverity := l}) >> logI Configuration "Log severity set to \{show l}"
187+
Nothing => logE Configuration "Incorrect string for log severity"
184188
Just _ => logE Configuration "Incorrect type for log severity, expected string"
185189
Nothing => pure ()
186190
case lookup "briefCompletions" xs of
187-
Just (JBoolean b) => update LSPConf ({ briefCompletions := b})
191+
Just (JBoolean b) => do
192+
update LSPConf ({ briefCompletions := b})
193+
logI Configuration "Brief completions set to \{show b}"
194+
_ => pure ()
195+
case lookup "ipkgPath" xs of
196+
Just (JString path) => do
197+
update LSPConf ({ ipkgPath := Just path})
198+
logI Channel "Ipkg path set to \{path}"
199+
Just JNull => do
200+
update LSPConf ({ ipkgPath := Nothing})
201+
logI Channel "Ipkg path unset"
188202
_ => pure ()
189203
processSettings _ = logE Configuration "Incorrect type for options"
190204

@@ -200,83 +214,91 @@ loadURI : Ref LSPConf LSPConfiguration
200214
=> Ref Syn SyntaxInfo
201215
=> Ref MD Metadata
202216
=> Ref ROpts REPLOpts
203-
=> InitializeParams -> URI -> Maybe Int -> Core (Either String ())
217+
=> InitializeParams -> URI -> Maybe Int -> Core (Either () ())
204218
loadURI conf uri version = do
205-
logI Server "Loading file \{show uri}"
219+
logI Channel "Loading file \{show uri}"
206220
defs <- get Ctxt
207221
let extraDirs = defs.options.dirs.extra_dirs
208-
update LSPConf ({ openFile := Just (uri, fromMaybe 0 version) })
209222
update ROpts { evalResultName := Nothing }
210223
resetContext (Virtual Interactive)
211224
let fpath = uriPathToSystemPath uri.path
212-
let Just (startFolder, startFile) = splitParent fpath
213-
| Nothing => do let msg = "Cannot find the parent folder for \{show uri}"
214-
logE Server msg
215-
pure $ Left msg
216-
True <- coreLift $ changeDir startFolder
217-
| False => do let msg = "Cannot change current directory to \{show startFolder}, folder of \{show startFile}"
218-
logE Server msg
219-
pure $ Left msg
220-
Right fname <- catch (maybe (Left "Cannot find the ipkg file") Right <$> findIpkg (Just fpath))
221-
(pure . Left . show)
222-
| Left err => do let msg = "Cannot load ipkg file for \{show uri}: \{show err}"
223-
logE Server msg
224-
pure $ Left msg
225-
Right res <- coreLift $ File.ReadWrite.readFile fname
226-
| Left err => do let msg = "Cannot read file at \{show uri}"
227-
logE Server msg
228-
pure $ Left msg
229-
update LSPConf ({ virtualDocuments $= insert uri (fromMaybe 0 version, res ++ "\n") })
230-
-- A hack to solve some interesting edge-cases around missing newlines ^^^^^^^
231-
setSource res
225+
-- Save CWD
226+
cwd <- getWorkingDir
227+
Right packageFilePath <- catch
228+
(maybe
229+
(Left (InternalError "Cannot find the .ipkg file"))
230+
Right
231+
<$>
232+
[| gets LSPConf ipkgPath <+> coreLift (findIpkgFile <&> (<&> (\(dir, name, _) => dir </> name))) |]
233+
)
234+
(pure . Left)
235+
| Left err => do let msg = "Cannot load .ipkg file: \{show err}"
236+
logE Channel msg
237+
pure $ Left ()
238+
logI Channel ".ipkg file configured to: \{packageFilePath}"
239+
Right packageFileSource <- coreLift $ File.ReadWrite.readFile packageFilePath
240+
| Left err => do let msg = "Cannot read .ipkg at \{packageFilePath} with CWD \{!getWorkingDir}"
241+
logE Channel msg
242+
pure $ Left ()
243+
logI Channel ".ipkg file read!"
244+
let Just (packageFileDir, packageFileName) = splitParent packageFilePath
245+
| _ => throw $ InternalError "Tried to split empty string"
246+
let True = isSuffixOf ".ipkg" packageFileName
247+
| _ => do let msg = "Packages must have an '.ipkg' extension: \{packageFilePath}"
248+
logE Channel msg
249+
pure $ Left ()
250+
-- The CWD should be set to that of the .ipkg file
251+
setWorkingDir packageFileDir
252+
-- Using local packageFileName as we are now in the directory containing that file
253+
-- Idris assumes that CWD and the directory of the .ipkg file coincide
254+
pkg <- parsePkgFile True packageFileName
255+
logI Channel ".ipkg file parsed!"
256+
whenJust (builddir pkg) setBuildDir
257+
setOutputDir (outputdir pkg)
258+
logI Channel "Type checking..."
232259
errs <- catch
233-
(buildDeps fname)
260+
(do
261+
(duration, errs) <- clock $ check pkg []
262+
logI Channel "Type-checking finished in \{show duration.milliseconds}ms with \{show (length errs)} errors"
263+
pure errs
264+
)
265+
-- FIXME: the compiler sometimes dumps the errors on stdout, requires
266+
-- a compiler change.
234267
(\err => do
235268
logE Server "Caught error which shouldn't be leaked while loading file: \{show err}"
236269
pure [err])
237-
-- FIXME: the compiler always dumps the errors on stdout, requires
238-
-- a compiler change.
239-
-- NOTE on catch: It seems the compiler sometimes throws errors instead
240-
-- of accumulating them in the buildDeps.
241-
unless (null errs) $ do
242-
update LSPConf ({ errorFiles $= insert uri })
243-
-- ModTree 397--308 loads data into context from ttf/ttm if no errors
244-
-- In case of error, we reprocess fname to populate metadata and syntax
245-
logI Channel "Rebuild \{fname} due to errors"
246-
modIdent <- ctxtPathToNS fname
247-
let msgPrefix : Doc IdrisAnn = pretty0 ""
248-
let buildMsg : Doc IdrisAnn = pretty0 modIdent
249-
clearCtxt; addPrimitives
250-
put MD (initMetadata (PhysicalIdrSrc modIdent))
251-
ignore $ ProcessIdr.process msgPrefix buildMsg fname modIdent
270+
271+
errs <- case null errs of
272+
True => do -- On success, reload the main ttc in a clean context
273+
clearCtxt; addPrimitives
274+
modIdent <- ctxtPathToNS fpath
275+
mainttc <- getTTCFileName fpath "ttc"
276+
log "import" 10 $ "Reloading " ++ show mainttc ++ " from " ++ fpath
277+
catch ([] <$ readAsMain mainttc) $ (\err => pure [err])
278+
False => pure errs
279+
252280

253281
let caps = (publishDiagnostics <=< textDocument) . capabilities $ conf
254282
update LSPConf ({ quickfixes := [], cachedActions := empty, cachedHovers := empty })
255283
traverse_ (findQuickfix caps uri) errs
256284
defs <- get Ctxt
257285
session <- getSession
258286
let warnings = if session.warningsAsErrors then [] else reverse (warnings defs)
259-
sendDiagnostics caps uri version warnings errs
287+
-- Clear out all previously issued diagnostics
288+
for_ !(Prelude.toList <$> gets LSPConf errorFiles) sendEmptyDiagnostic
289+
filesWithErrors <- sendDiagnostics caps uri warnings errs
290+
logI Channel "Files containing errors: \{show filesWithErrors}"
291+
update LSPConf { errorFiles := SortedSet.fromList filesWithErrors }
260292
defs <- get Ctxt
261293
put Ctxt ({ options->dirs->extra_dirs := extraDirs } defs)
262-
cNames <- completionNames
294+
(duration, cNames) <- clock completionNames
295+
logI Channel "Populating completion cache took \{show duration.milliseconds}ms"
263296
update LSPConf ({completionCache $= insert uri cNames})
297+
logI Channel "File loaded!"
298+
-- Reset CWD
299+
setWorkingDir cwd
264300
pure $ Right ()
265301

266-
loadIfNeeded : Ref LSPConf LSPConfiguration
267-
=> Ref Ctxt Defs
268-
=> Ref UST UState
269-
=> Ref Syn SyntaxInfo
270-
=> Ref MD Metadata
271-
=> Ref ROpts REPLOpts
272-
=> InitializeParams -> URI -> Maybe Int -> Core (Either String ())
273-
loadIfNeeded conf uri version = do
274-
Just (oldUri, oldVersion) <- gets LSPConf openFile
275-
| Nothing => loadURI conf uri version
276-
if (oldUri == uri && (isNothing version || (Just oldVersion) == version))
277-
then pure $ Right ()
278-
else loadURI conf uri version
279-
280302
withURI : Ref LSPConf LSPConfiguration
281303
=> Ref Ctxt Defs
282304
=> Ref UST UState
@@ -285,13 +307,25 @@ withURI : Ref LSPConf LSPConfiguration
285307
=> Ref ROpts REPLOpts
286308
=> InitializeParams
287309
-> URI -> Maybe Int -> Core (Either ResponseError a) -> Core (Either ResponseError a) -> Core (Either ResponseError a)
288-
withURI conf uri version d k = do
289-
when !(isError uri) $ ignore $ logW Server "Trying to load \{show uri} which has errors" >> d
290-
case !(loadIfNeeded conf uri version) of
291-
Right () => k
292-
Left err => do
293-
logE Server "Error while loading \{show uri}: \{show err}"
294-
pure $ Left (MkResponseError (Custom 3) err JNull)
310+
withURI conf uri version d k =
311+
case !(isError uri) of
312+
True => logI Server "Trying to load \{show uri} which has errors" >> d
313+
False => do
314+
logI Channel "Loading metadata for file: \{show uri}"
315+
clock0 <- coreLift (clockTime Monotonic)
316+
let fpath = uriPathToSystemPath uri.path
317+
setMainFile (Just fpath)
318+
Right src <- coreLift $ File.ReadWrite.readFile fpath
319+
| Left err => pure $ Left (MkResponseError RequestCancelled "Couldn't read the file source file" JNull)
320+
setSource src
321+
modIdent <- ctxtPathToNS fpath
322+
mainttm <- getTTCFileName fpath "ttm"
323+
[] <- catch ([] <$ readFromTTM mainttm) (\err => pure [err])
324+
| _ => pure $ Left (MkResponseError RequestCancelled "Couldn't load TTM for the file" JNull)
325+
clock1 <- coreLift (clockTime Monotonic)
326+
let dif = timeDifference clock1 clock0
327+
logI Channel "Loading metadata finished in \{show dif.milliseconds}ms"
328+
k
295329

296330
||| Guard for requests that requires a successful initialization before being allowed.
297331
whenInitializedRequest : Ref LSPConf LSPConfiguration => (InitializeParams -> Core (Either ResponseError a)) -> Core (Either ResponseError a)
@@ -498,12 +532,19 @@ handleRequest TextDocumentSemanticTokensFull params = whenActiveRequest $ \conf
498532
Nothing <- gets LSPConf (lookup params.textDocument.uri . semanticTokensSentFiles)
499533
| Just tokens => pure $ pure $ (make $ tokens)
500534
withURI conf params.textDocument.uri Nothing (pure $ Left (MkResponseError RequestCancelled "Document Errors" JNull)) $ do
535+
logI Channel "Loading semantic tokens for file: \{show (params.textDocument.uri)}"
536+
clock0 <- coreLift (clockTime Monotonic)
537+
let fpath = uriPathToSystemPath params.textDocument.uri.path
538+
Right src <- coreLift $ File.ReadWrite.readFile fpath
539+
| Left err => pure $ Left (MkResponseError RequestCancelled "Couldn't read the file" JNull)
501540
md <- get MD
502-
src <- getSource
503541
let srcLines = lines src
504542
let getLineLength = \lineNum => maybe 0 (cast . length) $ elemAt srcLines (integerToNat (cast lineNum))
505543
tokens <- getSemanticTokens md getLineLength
506544
update LSPConf ({ semanticTokensSentFiles $= insert params.textDocument.uri tokens })
545+
clock1 <- coreLift (clockTime Monotonic)
546+
let dif = timeDifference clock1 clock0
547+
logI Channel "Loading semantic tokens finished in \{show dif.milliseconds}ms"
507548
pure $ pure $ (make $ tokens)
508549
handleRequest WorkspaceExecuteCommand
509550
(MkExecuteCommandParams partialResultToken "repl" (Just [json])) = whenActiveRequest $ \conf => do
@@ -578,7 +619,6 @@ handleNotification TextDocumentDidSave params = whenActiveNotification $ \conf =
578619
logI Channel "Received didSave notification for \{show params.textDocument.uri}"
579620
update LSPConf (
580621
{ dirtyFiles $= delete params.textDocument.uri
581-
, errorFiles $= delete params.textDocument.uri
582622
, semanticTokensSentFiles $= delete params.textDocument.uri
583623
})
584624
ignore $ loadURI conf params.textDocument.uri Nothing
@@ -600,8 +640,7 @@ handleNotification TextDocumentDidChange params = whenActiveNotification $ \conf
600640

601641
handleNotification TextDocumentDidClose params = whenActiveNotification $ \conf => do
602642
logI Channel "Received didClose notification for \{show params.textDocument.uri}"
603-
update LSPConf ({ openFile := Nothing
604-
, quickfixes := []
643+
update LSPConf ({ quickfixes := []
605644
, cachedActions := empty
606645
, cachedHovers := empty
607646
, dirtyFiles $= delete params.textDocument.uri
@@ -614,3 +653,4 @@ handleNotification TextDocumentDidClose params = whenActiveNotification $ \conf
614653

615654
handleNotification method params = whenActiveNotification $ \conf =>
616655
logW Channel "Received unhandled notification for method \{stringify $ toJSON method}"
656+

0 commit comments

Comments
 (0)