Files
newt/src/Commands.newt

252 lines
9.0 KiB
Agda
Raw Blame History

-- For shared code between REPL and LSP
module Commands
import Prelude
import Lib.ProcessModule
import Lib.Types
import Lib.TopContext
import Lib.Common
import Data.List1
import Lib.Tokenizer
import Lib.Token
import Lib.Elab
import Data.String
import Lib.Eval
import Data.SortedMap
-- For now we cheat and assume capitalized directories are a module component
decomposeName : String String × String
decomposeName fn =
go Nil $ Lin <>< split (fst $ splitFileName fn) "/"
where
go : List String SnocList String String × String
go acc Lin = (".", joinBy "." acc)
go acc (xs :< x) = if isUpper $ strIndex x 0
then go (x :: acc) xs
else (joinBy "/" (xs :< x <>> Nil), joinBy "." acc)
switchModule : FileSource String M (Maybe ModContext)
switchModule repo modns = do
top <- getTop
let (Just mod) = lookupMap' modns top.modules | Nothing => pure Nothing
modifyTop [ currentMod := mod; ops := mod.modOps ]
pure $ Just mod
-- The cheap version of type at point, find the token, lookup in global context
-- Later we will either get good FC for entries or scan them all and build a cache.
getHoverInfo : FileSource String Int Int M (Maybe (String × FC))
getHoverInfo repo modns row col = do
Just mod <- switchModule repo modns | _ => pure Nothing
top <- getTop
-- Find the token at the point
let lines = split mod.modSource "\n"
let line = fromMaybe "" (getAt' row lines)
let (Right toks) = tokenise "" line | Left _ => pure Nothing
let (Just name) = getTok toks | _ => pure Nothing
-- Lookup the name
let (Just e) = lookupRaw name top | _ => pure Nothing
ty <- nf Nil e.type
pure $ Just ("\{show e.name} : \{rpprint Nil ty}", e.fc)
where
getTok : List BTok Maybe String
getTok Nil = Nothing
getTok (tok :: toks) =
if tok.bounds.startCol <= col && (col <= tok.bounds.endCol)
then Just $ value tok else getTok toks
data FileEdit = MkEdit FC String
data CodeAction
= CaseSplitAction (List FileEdit)
| AddMissingAction (List FileEdit)
| Intro String FileEdit
applyDCon : QName × Int × Tm List String
applyDCon (QN _ nm, _, tm) = go (Lin :< nm) tm
where
go : SnocList String Tm List String
go acc (Pi _ nm Explicit _ _ u) = go (acc :< nm) u
go acc (Pi _ _ _ _ _ u) = go acc u
go acc _ = acc <>> Nil
data Flavor = EqSplit | FatArrowSplit | MonadSplit
-- Not quite right, we also need to check for let...
-- But it's a first pass
splitEquals : SnocList Char List Char (Flavor × String × String)
splitEquals acc Nil = (EqSplit, pack (acc <>> Nil), "")
splitEquals acc xs@('=' :: '>' :: _) = (FatArrowSplit, pack (acc <>> Nil), pack xs)
splitEquals acc xs@('=' :: _) = (EqSplit, pack (acc <>> Nil), pack xs)
splitEquals acc xs@('<' :: '-' :: _) = (MonadSplit, pack (acc <>> Nil), pack xs)
splitEquals acc (x :: xs) = splitEquals (acc :< x) xs
needParens : SnocList Char List Char Bool
needParens (xs :< ' ') ys = needParens xs ys
needParens xs (' ' :: ys) = needParens xs ys
needParens (xs :< '(') (')' :: ys) = False
needParens _ _ = True
addParens : Bool List String String
addParens _ (x :: Nil) = x
addParens False s = unwords s
addParens True s = "(\{unwords s})"
-- resugar operator applications
-- assumes the components are simple identifiers
resugarOper : List String List String
resugarOper Nil = Nil
resugarOper (x :: xs) = go Lin (split x "_") xs
where
go : SnocList String List String List String List String
go acc Nil xs = acc <>> xs
go acc ("" :: rest) (x :: xs) = go (acc :< x) rest xs
-- If there are not enough parts, bail and fall back to `_+_ x`
go acc ("" :: rest) Nil = (x :: xs)
go acc (x :: xs) ys = go (acc :< x) xs ys
-- REVIEW - maybe pass in QName and use applyDCon in here, especially if we want to get better names?
makeEdits : FC List QName Bool M (List FileEdit)
makeEdits fc@(MkFC uri (MkBounds sr sc er ec)) names inPlace = do
cons <- map applyDCon <$> traverse lookupDCon names
top <- getTop
let (Just mod) = lookupMap' top.currentMod.modName top.modules | _ => pure Nil
let lines = split mod.modSource "\n"
let (Just line) = getAt' sr lines | _ => pure Nil
let cs = unpack line
let head = take (cast sc) cs
let tail = drop (S $ cast (ec - 1)) cs
let (splitKind, before, after) = splitEquals Lin tail
let np = needParens (Lin <>< head) tail
let cons = map (addParens np resugarOper) cons
let phead = pack head
let indent = getIndent 0 head
let nextrow = scan indent lines (sr + 1)
-- No init or first :: rest for add missing case
let (edits, rest) = doFirst inPlace cons
let phead = pack head
let fc' = MkFC uri (MkBounds nextrow 0 nextrow 0)
let srest =
case splitKind of
EqSplit => joinBy "" $ map (\ap => phead ++ ap ++ before ++ "= ?\n") rest
FatArrowSplit => joinBy "" $ map (\ap => phead ++ ap ++ before ++ "=> ?\n") rest
MonadSplit => joinBy "" $ map (\ap => " | \{pack head}\{ap}\{before}=> ?\n") rest
putStrLn "Split \{show line} HD '\{show head}' TL '\{show tail}'"
putStrLn srest
let edits = MkEdit fc' (srest) :: edits
putStrLn "edits \{debugStr edits}"
pure edits
where
getIndent : Int List Char Int
getIndent acc (' ' :: rest) = getIndent (1 + acc) rest
getIndent acc _ = acc
scan : Int List String Int Int
scan indent lines row =
let x = getIndent 0 $ unpack $ fromMaybe "" $ getAt' row lines in
if x <= indent then row else scan indent lines (row + 1)
doFirst : Bool List String (List FileEdit × List String)
doFirst True (first :: rest) = (MkEdit fc first :: Nil, rest)
doFirst _ cons = (Nil, cons)
addMissingCases : Int Int FC Context List QName M (Maybe CodeAction)
addMissingCases _ _ fc@(MkFC uri (MkBounds sr sc er ec)) ctx names = do
top <- getTop
edits <- makeEdits fc names False
putStrLn "Add Missing \{show fc} \{show names}"
pure $ Just $ AddMissingAction edits
getCaseSplit : Int Int FC Context String Val M (Maybe CodeAction)
getCaseSplit row col fc@(MkFC uri (MkBounds sr sc er ec)) ctx nm scty = do
top <- getTop
-- It's getting vars for the type
scty <- unlet ctx.env scty
cons <- getConstructors ctx fc scty
ty <- quote (length' ctx.env) scty
cons <- filterM (checkCase ctx nm scty) cons
let names = map fst cons
putStrLn "Make splits for \{show names}"
edits <- makeEdits fc names True
pure $ Just $ CaseSplitAction edits
posInFC : Int Int FC Bool
-- FIXME ec + 1 again...
posInFC row col (MkFC _ (MkBounds sr sc er ec)) = (sr <= row && row <= er) && (sc <= col && col <= ec + 1)
getHole : ModContext Int Int Maybe MetaEntry
getHole mod row col =
find isUserMeta $ listValues mod.modMetaCtx.metas
where
isUserMeta : MetaEntry Bool
isUserMeta (Unsolved fc _ _ _ User _) = posInFC row col fc
isUserMeta _ = False
introActions : Maybe MetaEntry M (List CodeAction)
introActions (Just $ Unsolved fc qn ctx vty User constraints) =
catchError (do
-- Are there ever any constraints?
top <- getTop
vty <- forceMeta vty
putStrLn "intros for \{show vty}"
case vty of
VPi _ nm Explicit _ a b => do
let str = "(\\ \{nm} => ?)"
pure $ Intro str (MkEdit fc str) :: Nil
_ => do
-- Prelude.Nat not a vref?
-- also need to handle pi types
cons <- getConstructors ctx fc vty
putStrLn "constructors \{show cons}"
pure $ map makeEdit cons
) $ \ err => do
putStrLn "Got error in introActions:"
putStrLn $ showError "" err
pure Nil
where
introDCon : QName × Int × Tm List String
introDCon (QN _ nm, _, tm) = go (Lin :< nm) tm
where
go : SnocList String Tm List String
go acc (Pi _ nm Explicit _ _ u) = go (acc :< "?") u
go acc (Pi _ _ _ _ _ u) = go acc u
go acc _ = acc <>> Nil
makeEdit : (QName × Int × Tm) CodeAction
makeEdit con@(QN _ nm, _, _) =
let str = unwords $ resugarOper $ introDCon con
in Intro str $ MkEdit fc $ str
introActions _ = pure Nil
getActions : FileSource String Int Int M (List CodeAction)
getActions repo modns row col = do
Just mod <- switchModule repo modns | _ => pure Nil
top <- getTop
let infos = filter (posInFC row col getFC) top.currentMod.modInfos
putStrLn "Filter got \{show $ length' infos}"
actions <- go Nil $ infos
let hole = getHole mod row col
putStrLn "Hole \{debugStr hole}"
intros <- introActions $ getHole mod row col
pure $ actions ++ intros
where
getAction : EditorInfo M (Maybe CodeAction)
getAction (CaseSplit fc ctx nm scty) = getCaseSplit row col fc ctx nm scty
getAction (MissingCases fc ctx names) = addMissingCases row col fc ctx names
go : List CodeAction List EditorInfo M (List CodeAction)
go acc Nil = pure acc
go acc (x :: xs) = do
Right (Just res) <- tryError $ getAction x
| _ => go acc xs
go (res :: acc) xs