Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8dba10b
typo: consumer
nvarner Aug 4, 2025
2c5a2f2
set up basic indexing infrastructure
nvarner Aug 5, 2025
2c0a0bf
look up indexed file to handle requests
nvarner Aug 7, 2025
c6348c2
rough edged indexer
nvarner Aug 12, 2025
78f6785
check that we don't miss Refs
nvarner Aug 13, 2025
6e77a52
respect arg info origin in indexer
nvarner Aug 15, 2025
7af7bd2
deduplicate simultaneous declaration/definition
nvarner Aug 15, 2025
f5bf5ef
write ASTs to file for debugging purposes
nvarner Aug 15, 2025
aef4fc1
don't reindex record type
nvarner Aug 15, 2025
2f51524
deduplicate decl/defs in let bindings
nvarner Aug 15, 2025
5b4f545
test functions without definitions/declarations
nvarner Aug 15, 2025
23c647b
deduplicate data/record params
nvarner Aug 18, 2025
63ae4b6
check for duplicate declarations
nvarner Aug 18, 2025
da1b1d0
rename ServerM to ServerT
nvarner Aug 18, 2025
8235f28
preliminary document symbol handler
nvarner Sep 14, 2025
800901d
respond to files opening, closing, and saving
nvarner Sep 28, 2025
a6754e1
refactor handlers, handle errors
nvarner Oct 27, 2025
269caa4
use unqualified symbol name
nvarner Oct 31, 2025
001d043
no anonymous function symbols
nvarner Nov 1, 2025
768f723
support Agda 2.8.0 indexing
nvarner Nov 1, 2025
094a883
support Agda 2.8.0 file management
nvarner Nov 1, 2025
91ec6d4
support Agda 2.8.0 in tests
nvarner Nov 1, 2025
6e86f66
Agda 2.7.0.1 compatibility fixes
nvarner Nov 1, 2025
b4b4172
better support for include paths
nvarner Nov 9, 2025
958e6b7
clear state before loading files
nvarner Nov 9, 2025
688a1a8
test reloading is compatible with changes
nvarner Nov 9, 2025
811d5da
index module params as children of module
nvarner Nov 9, 2025
8505671
show fewer document symbols
nvarner Nov 9, 2025
f29df32
Agda 2.6.4.3 compatibility
nvarner Nov 9, 2025
58be9bd
Broken library resolution unit tests
nvarner Nov 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
cabal-dev
dist
dist-*
test/data/AST/
node_modules/
*.o
*.hi
Expand Down
55 changes: 51 additions & 4 deletions agda-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,21 @@ library
exposed-modules:
Agda
Agda.Convert
Agda.Interaction.Imports.More
Agda.Interaction.Imports.Virtual
Agda.Interaction.Library.More
Agda.IR
Agda.Parser
Agda.Position
Agda.Syntax.Abstract.More
Agda.TypeChecking.Monad.Options.More
Control.Concurrent.SizedChan
Indexer
Indexer.Indexer
Indexer.Monad
Indexer.Postprocess
Language.LSP.Protocol.Types.More
Language.LSP.Protocol.Types.Uri.More
Monad
Options
Render
Expand All @@ -70,6 +81,14 @@ library
Server
Server.CommandController
Server.Handler
Server.Handler.TextDocument.DocumentSymbol
Server.Handler.TextDocument.FileManagement
Server.Model
Server.Model.AgdaFile
Server.Model.AgdaLib
Server.Model.Handler
Server.Model.Monad
Server.Model.Symbol
Server.ResponseController
Switchboard
other-modules:
Expand Down Expand Up @@ -160,23 +179,44 @@ executable als
if arch(wasm32)
build-depends:
unix >=2.8.0.0 && <2.9
ghc-options: -with-rtsopts=-V1
else
if !arch(wasm32)
ghc-options: -threaded -with-rtsopts=-N

test-suite als-test
type: exitcode-stdio-1.0
main-is: Test.hs
other-modules:
Test.Indexer
Test.Indexer.Invariants
Test.Indexer.Invariants.NoDuplicateDecl
Test.Indexer.Invariants.NoMissing
Test.Indexer.Invariants.NoOverlap
Test.Indexer.NoAnonFunSymbol
Test.Indexer.Reload
Test.LSP
Test.Model
Test.ModelMonad
Test.SrcLoc
Test.Uri
Test.WASM
TestData
Agda
Agda.Convert
Agda.Interaction.Imports.More
Agda.Interaction.Imports.Virtual
Agda.Interaction.Library.More
Agda.IR
Agda.Parser
Agda.Position
Agda.Syntax.Abstract.More
Agda.TypeChecking.Monad.Options.More
Control.Concurrent.SizedChan
Indexer
Indexer.Indexer
Indexer.Monad
Indexer.Postprocess
Language.LSP.Protocol.Types.More
Language.LSP.Protocol.Types.Uri.More
Monad
Options
Render
Expand All @@ -194,6 +234,14 @@ test-suite als-test
Server
Server.CommandController
Server.Handler
Server.Handler.TextDocument.DocumentSymbol
Server.Handler.TextDocument.FileManagement
Server.Model
Server.Model.AgdaFile
Server.Model.AgdaLib
Server.Model.Handler
Server.Model.Monad
Server.Model.Symbol
Server.ResponseController
Switchboard
Paths_agda_language_server
Expand Down Expand Up @@ -242,6 +290,5 @@ test-suite als-test
if arch(wasm32)
build-depends:
unix >=2.8.0.0 && <2.9
ghc-options: -with-rtsopts=-V1
else
if !arch(wasm32)
ghc-options: -threaded -with-rtsopts=-N
14 changes: 7 additions & 7 deletions src/Agda.hs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ import Agda.Interaction.JSONTop ()
getAgdaVersion :: String
getAgdaVersion = versionWithCommitInfo

start :: ServerM IO ()
start :: ServerT IO ()
start = do
env <- ask

Expand Down Expand Up @@ -136,7 +136,7 @@ start = do
Left _err -> return ()
Right _val -> return ()
where
loop :: Env -> ServerM CommandM ()
loop :: Env -> ServerT CommandM ()
loop env = do
Bench.reset
done <- Bench.billTo [] $ do
Expand All @@ -163,7 +163,7 @@ start = do

-- | Convert "CommandReq" to "CommandRes"

sendCommand :: MonadIO m => Value -> ServerM m Value
sendCommand :: MonadIO m => Value -> ServerT m Value
sendCommand value = do
-- JSON Value => Request => Response
case fromJSON value of
Expand All @@ -179,7 +179,7 @@ sendCommand value = do
JSON.Success request -> toJSON <$> handleCommandReq request


handleCommandReq :: MonadIO m => CommandReq -> ServerM m CommandRes
handleCommandReq :: MonadIO m => CommandReq -> ServerT m CommandRes
handleCommandReq CmdReqSYN = return $ CmdResACK Agda.getAgdaVersion versionNumber
handleCommandReq (CmdReq cmd) = do
case parseIOTCM cmd of
Expand All @@ -194,7 +194,7 @@ handleCommandReq (CmdReq cmd) = do
--------------------------------------------------------------------------------

getCommandLineOptions
:: (HasOptions m, MonadIO m) => ServerM m CommandLineOptions
:: (HasOptions m, MonadIO m) => ServerT m CommandLineOptions
getCommandLineOptions = do
-- command line options from ARGV
argv <- asks (optRawAgdaOptions . envOptions)
Expand All @@ -215,10 +215,10 @@ getCommandLineOptions = do

-- | Run a TCM action in IO and throw away all of the errors
-- TODO: handle the caught errors
runAgda :: MonadIO m => ServerM TCM a -> ServerM m (Either String a)
runAgda :: MonadIO m => ServerT TCM a -> ServerT m (Either String a)
runAgda p = do
env <- ask
let p' = runServerM env p
let p' = runServerT env p
liftIO
$ runTCMTop'
( (Right <$> p')
Expand Down
209 changes: 209 additions & 0 deletions src/Agda/Interaction/Imports/More.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
{-# LANGUAGE CPP #-}

module Agda.Interaction.Imports.More
( setOptionsFromSourcePragmas,
checkModuleName',
runPMDropWarnings,
moduleName,
runPM,
beginningOfFile,
)
where

import Agda.Interaction.FindFile (
SourceFile (SourceFile),
checkModuleName,
#if MIN_VERSION_Agda(2,8,0)
rootNameModule,
#else
moduleName,
#endif
)
import Agda.Interaction.Imports (Source (..))
import qualified Agda.Interaction.Imports as Imp
import Agda.Interaction.Library (OptionsPragma (..), _libPragmas)
import Agda.Interaction.Library.More ()
import Agda.Syntax.Common (TopLevelModuleName')
import qualified Agda.Syntax.Concrete as C
import Agda.Syntax.Parser (
moduleParser,
parseFile,
#if MIN_VERSION_Agda(2,8,0)
parse,
moduleNameParser,
#else
PM,
runPMIO,
#endif
)
import Agda.Syntax.Position
( Range,
Range' (Range),
RangeFile,
getRange,
intervalToRange,
mkRangeFile,
posToRange,
posToRange',
startPos,
#if MIN_VERSION_Agda(2,8,0)
beginningOfFile,
rangeFromAbsolutePath,
#endif
)
import Agda.Syntax.TopLevelModuleName (
TopLevelModuleName,
RawTopLevelModuleName (..),
#if MIN_VERSION_Agda(2,8,0)
rawTopLevelModuleNameForModule,
#endif
)
import Agda.Syntax.Common.Pretty (Pretty, pretty, text, prettyAssign, (<+>))
import Agda.TypeChecking.Monad
( Interface,
TCM,
setCurrentRange,
setOptionsFromPragma,
setTCLens,
stPragmaOptions,
useTC,
#if MIN_VERSION_Agda(2,7,0)
checkAndSetOptionsFromPragma,
#endif
#if MIN_VERSION_Agda(2,8,0)
runPM,
runPMDropWarnings,
#endif
)
import qualified Agda.TypeChecking.Monad as TCM
import qualified Agda.TypeChecking.Monad.Benchmark as Bench
#if MIN_VERSION_Agda(2,8,0)
#else
import Agda.TypeChecking.Warnings (runPM)
#endif
import Agda.Utils.FileName (AbsolutePath)
import Agda.Utils.Monad (bracket_)
#if MIN_VERSION_Agda(2,8,0)
import qualified Data.Text as T
#endif
import qualified Data.Text.Lazy as TL
import Control.Monad.Error.Class (
#if MIN_VERSION_Agda(2,8,0)
catchError,
#else
throwError,
#endif
)
#if MIN_VERSION_Agda(2,8,0)
import Agda.Utils.Singleton (singleton)
#endif

srcFilePath :: SourceFile -> TCM AbsolutePath
#if MIN_VERSION_Agda(2,8,0)
srcFilePath = TCM.srcFilePath
#else
srcFilePath (SourceFile f) = return f
#endif

#if MIN_VERSION_Agda(2,8,0)
-- beginningOfFile was generalized in Agda 2.8.0 to support the features we
-- need, so we just import it
#else
beginningOfFile :: RangeFile -> Range
beginningOfFile rf = posToRange (startPos $ Just rf) (startPos $ Just rf)
#endif

#if MIN_VERSION_Agda(2,8,0)
-- runPMDropWarnings was introduced in Agda 2.8.0, so we just import it
#else
runPMDropWarnings :: PM a -> TCM a
runPMDropWarnings m = do
(res, _ws) <- runPMIO m
case res of
Left e -> throwError $ TCM.Exception (getRange e) (pretty e)
Right a -> return a
#endif

-- Unexported Agda functions

srcDefaultPragmas :: Imp.Source -> [OptionsPragma]
srcDefaultPragmas src = map _libPragmas (Imp.srcProjectLibs src)

srcFilePragmas :: Imp.Source -> [OptionsPragma]
srcFilePragmas src = pragmas
where
cpragmas = C.modPragmas (Imp.srcModule src)
pragmas =
[ OptionsPragma
{ pragmaStrings = opts,
pragmaRange = r
}
| C.OptionsPragma r opts <- cpragmas
]

-- | Set options from a 'Source' pragma, using the source
-- ranges of the pragmas for error reporting. Flag to check consistency.
setOptionsFromSourcePragmas :: Bool -> Imp.Source -> TCM ()
setOptionsFromSourcePragmas checkOpts src = do
mapM_ setOpts (srcDefaultPragmas src)
mapM_ setOpts (srcFilePragmas src)
where
#if MIN_VERSION_Agda(2,7,0)
setOpts
| checkOpts = checkAndSetOptionsFromPragma
| otherwise = setOptionsFromPragma
#else
setOpts = setOptionsFromPragma
#endif

-- Andreas, 2016-07-11, issue 2092
-- The error range should be set to the file with the wrong module name
-- not the importing one (which would be the default).
checkModuleName' :: TopLevelModuleName' Range -> SourceFile -> TCM ()
checkModuleName' m f =
setCurrentRange m $ checkModuleName m f Nothing

#if MIN_VERSION_Agda(2,8,0)
-- moduleName was exported until 2.8.0

-- | Computes the module name of the top-level module in the given file.
--
-- If no top-level module name is given, then an attempt is made to
-- use the file name as a module name.

moduleName ::
AbsolutePath
-- ^ The path to the file.
-> C.Module
-- ^ The parsed module.
-> TCM TopLevelModuleName
moduleName file parsedModule = Bench.billTo [Bench.ModuleName] $ do
let defaultName = rootNameModule file
raw = rawTopLevelModuleNameForModule parsedModule
TCM.topLevelModuleName =<< if C.isNoName raw
then setCurrentRange (rangeFromAbsolutePath file) $ do
m <- runPM (fst <$> parse moduleNameParser defaultName)
`catchError` \_ ->
TCM.typeError $ TCM.InvalidFileName file TCM.DoesNotCorrespondToValidModuleName
case m of
C.Qual{} ->
TCM.typeError $ TCM.InvalidFileName file $
TCM.RootNameModuleNotAQualifiedModuleName $ T.pack defaultName
C.QName{} ->
return $ RawTopLevelModuleName
{ rawModuleNameRange = getRange m
, rawModuleNameParts = singleton (T.pack defaultName)
, rawModuleNameInferred = True
-- Andreas, 2025-06-21, issue #7953:
-- Remember we made up this module name to improve errors.
}
else return raw
#endif

-- Orphan instances

instance Pretty Imp.Source where
pretty src =
text "Source"
<+> pretty (Imp.srcModuleName src)
<+> pretty (Imp.srcProjectLibs src)
Loading
Loading