Initial LSP implementation/vscode support
Some checks failed
Publish Playground / build (push) Has been cancelled
Publish Playground / deploy (push) Has been cancelled

This commit is contained in:
2026-02-12 20:14:14 -08:00
parent 01a05ba186
commit a9718621e3
36 changed files with 6909 additions and 76 deletions

144
src/LSP.newt Normal file
View File

@@ -0,0 +1,144 @@
module LSP
import Prelude
-- TODO pull this into its own file
import Lib.Common
import Lib.Types
import Lib.TopContext
import Lib.Tokenizer
import Lib.Parser
import Lib.Parser.Impl
import Lib.ProcessModule
import Data.SortedMap
import Data.IORef
import Node
import Commands
import Data.List1
pfunc js_castArray : Array JSObject JSObject := `x => x`
pfunc js_castInt : Int JSObject := `x => x`
pfunc js_castBool : Bool JSObject := `x => x`
pfunc js_castStr : String JSObject := `x => x`
pfunc js_null : JSObject := `null`
pfunc js_castObj : Array (String × JSObject) JSObject := `(data) => {
let rval = {}
for (let x of data) rval[x.h2] = x.h3
return rval
}`
-- need case split
jsonToJObject : Json JSObject
jsonToJObject (JsonInt x) = js_castInt x
jsonToJObject (JsonNull) = js_null
jsonToJObject (JsonArray xs) = js_castArray $ listToArray $ map jsonToJObject xs
jsonToJObject (JsonBool x) = js_castBool x
jsonToJObject (JsonStr x) = js_castStr x
-- IMPERROR - if I leave off the `map` I get an error that is hard to sort out
jsonToJObject (JsonObj xs) = js_castObj $ listToArray $ map (mapSnd jsonToJObject) xs
record LSPState where
topContext : TopContext
baseDir : String
files : SortedMap String String
state : IORef LSPState
state = unsafePerformIO $ newIORef $ MkLSPState emptyTop "" emptyMap
resetState : String IO Unit
resetState base = do
putStrLn "Reset base to \{base}"
writeIORef state $ MkLSPState emptyTop base emptyMap
lspFileSource : FileSource
lspFileSource = MkFileSource $ \fc fn => do
st <- readIORef state
let fn = st.baseDir ++ "/" ++ fn
let (Nothing) = lookupMap' fn st.files
| Just src => pure (fn,src)
let fn' = case split fn "file://" of
x :: fn :: _ => fn
_ => fn
(Right src) <- liftIO {M} $ readFile fn'
| Left err => throwError $ E fc "error reading \{fn}: \{show err}"
pure (fn,src)
updateFile : String String Unit
updateFile fn src = unsafePerformIO $ do
st <- readIORef state
modifyIORef state $ \a => [ files $= updateMap fn src ] a
let st = the LSPState $ [ files $= updateMap fn src ] st
-- module relative to base
let (Right toks) = tokenise fn src | Left err => writeIORef state st
let (Right ((nameFC, modName), _, _)) = partialParse fn parseModHeader emptyMap toks
| Left (err,toks) => writeIORef state st
Right (ctx,_) <- (invalidateModule $ split modName ".").runM st.topContext
| _ => writeIORef state st
-- TODO It doesn't have record type, but eta expanding resolves this. See if there is a quick fix.
-- modifyIORef state [ topContext := ctx ]
modifyIORef state $ \a => [ topContext := ctx ] a
hoverInfo : String Int Int JSObject
hoverInfo uri line col = unsafePerformIO $ do
let (base,modns) = decomposeName uri
putStrLn "Hover \{uri} base \{base} mod \{joinBy "." modns}"
st <- readIORef state
if (st.baseDir /= base)
then resetState base
else pure MkUnit
Right (_, Just msg) <- (getHoverInfo lspFileSource modns line col).runM st.topContext
| Right _ => do
putStrLn $ "Nothing to see here"
pure $ jsonToJObject JsonNull
| Left err => do
putStrLn $ showError "" err
pure $ jsonToJObject JsonNull
pure $ jsonToJObject $ JsonStr msg
errorToDiag : Error -> Json
errorToDiag (E (MkFC fn (MkBounds sr sc er ec)) msg) =
JsonObj
$ ("severity", JsonInt 1)
-- PARSER `$` is winning over `,`, which is not what I'm expecting Maybe `,` should be special...
:: ("range", (JsonObj $ ("start", range sr sc) :: ("end", range er (ec + 1)) :: Nil))
:: ("message", JsonStr msg)
:: ("source", JsonStr "newt") -- what is this key for?
:: Nil
where
range : Int Int Json
range l c = JsonObj $ ("line", JsonInt l) :: ("character", JsonInt c) :: Nil
-- These shouldn't escape
errorToDiag (Postpone fc qn msg) = errorToDiag $ E fc "Postpone \{show qn} \{msg}"
checkFile : String → JSObject
checkFile fn = unsafePerformIO $ do
let (base,modns) = decomposeName fn
putStrLn "Checking \{fn} base \{base} mod \{joinBy "." modns}"
st <- readIORef state
if (st.baseDir /= base)
then resetState base
else pure MkUnit
(Right (top, json)) <- (do
modifyTop [ errors := Nil ]
putStrLn "add prim"
addPrimitives
putStrLn "processModule"
_ <- processModule emptyFC lspFileSource Nil modns
pure MkUnit
-- pull out errors and infos
top <- getTop
pure $ map (errorToDiag) top.errors
).runM st.topContext
| Left err => do
putStrLn $ showError "" err
pure $ jsonToJObject $ JsonArray $ errorToDiag err :: Nil
-- Cache loaded modules
modifyIORef state $ \a => [ topContext := top ] a
pure $ jsonToJObject $ JsonArray json
-- This seems like a hack, but it works.
-- Dummy main function with references to force functions into ouput file.
-- but we don't get `export` on it..
pfunc main uses (updateFile checkFile hoverInfo) : IO Unit := `() => {}`