Skip to content

Commit 4e0f9f5

Browse files
committed
Add tests for hover info
1 parent 2448d98 commit 4e0f9f5

File tree

8 files changed

+198
-45
lines changed

8 files changed

+198
-45
lines changed

unison-cli/package.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies:
1010
- unison-parser-typechecker
1111
- unison-prelude
1212
- megaparsec
13+
- unliftio
1314
- directory
1415

1516
library:
@@ -98,7 +99,6 @@ library:
9899
- unison-util-base32hex
99100
- unison-util-recursion
100101
- unison-util-relation
101-
- unliftio
102102
- uuid
103103
- vector
104104
- wai
@@ -119,7 +119,9 @@ tests:
119119
- extra
120120
- here
121121
- lens
122+
- lsp
122123
- lsp-types
124+
- mtl
123125
- temporary
124126
- these
125127
- unison-cli

unison-cli/src/Unison/LSP/FileAnalysis.hs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
{-# LANGUAGE RecordWildCards #-}
22

3-
module Unison.LSP.FileAnalysis where
3+
module Unison.LSP.FileAnalysis
4+
( checkFileByUri,
5+
checkFileContents,
6+
getFileAnalysis,
7+
ppedForFile,
8+
getFileSummary,
9+
ppedForFileHelper,
10+
fileAnalysisWorker,
11+
getFileDefLocations,
12+
getFileNames,
13+
)
14+
where
415

516
import Control.Lens
617
import Control.Monad.Reader
@@ -76,14 +87,20 @@ import Unison.Var qualified as Var
7687
import UnliftIO.STM
7788
import Witherable
7889

79-
-- | Lex, parse, and typecheck a file.
80-
checkFile :: (HasUri d Uri) => d -> Lsp (Maybe FileAnalysis)
81-
checkFile doc = runMaybeT do
82-
pp <- lift getCurrentProjectPath
90+
-- | Lex, parse, and typecheck a file using a VFS URI
91+
checkFileByUri :: (HasUri d Uri, Lspish m) => d -> m (Maybe FileAnalysis)
92+
checkFileByUri doc = runMaybeT do
8393
let fileUri = doc ^. uri
8494
(fileVersion, contents) <- VFS.getFileContents fileUri
95+
let sourceName = getUri $ fileUri
96+
checkFileContents fileUri sourceName fileVersion contents
97+
98+
-- | Lex, parse, and typecheck a file.
99+
-- This is split off for easier testing without needing to mock the VFS.
100+
checkFileContents :: (Lspish m) => Uri -> Text -> FileVersion -> Text -> MaybeT m FileAnalysis
101+
checkFileContents fileUri sourceName fileVersion contents = do
102+
pp <- lift getCurrentProjectPath
85103
parseNames <- lift getCurrentNames
86-
let sourceName = getUri $ doc ^. uri
87104
let lexedSource@(srcText, tokens) = (contents, L.lexer (Text.unpack sourceName) (Text.unpack contents))
88105
let ambientAbilities = []
89106
cb <- asks codebase
@@ -131,8 +148,8 @@ checkFile doc = runMaybeT do
131148
_ -> mempty
132149
pure (localBindings, typecheckingNotes, Just parsedFile, maybeTypecheckedFile)
133150

134-
filePPED <- lift $ ppedForFileHelper parsedFile typecheckedFile
135-
(errDiagnostics, codeActions) <- lift $ analyseFile fileUri srcText filePPED notes
151+
filePPED <- ppedForFileHelper parsedFile typecheckedFile
152+
(errDiagnostics, codeActions) <- analyseFile fileUri srcText filePPED notes
136153
let codeActionRanges =
137154
codeActions
138155
& foldMap (\(RangedCodeAction {_codeActionRanges, _codeAction}) -> (,_codeAction) <$> _codeActionRanges)
@@ -143,7 +160,7 @@ checkFile doc = runMaybeT do
143160
let tokenMap = getTokenMap tokens
144161
conflictWarningDiagnostics <-
145162
fold <$> for fileSummary \fs ->
146-
lift $ computeConflictWarningDiagnostics fileUri fs
163+
computeConflictWarningDiagnostics fileUri fs
147164
let diagnosticRanges =
148165
(errDiagnostics <> conflictWarningDiagnostics <> unusedBindingDiagnostics)
149166
& fmap (\d -> (d ^. range, d))
@@ -183,7 +200,7 @@ fileAnalysisWorker = forever do
183200
pure dirty
184201
freshlyCheckedFiles <-
185202
Map.fromList <$> forMaybe (toList dirtyFileIDs) \docUri -> runMaybeT do
186-
fileInfo <- MaybeT (checkFile $ TextDocumentIdentifier docUri)
203+
fileInfo <- MaybeT (checkFileByUri $ TextDocumentIdentifier docUri)
187204
pure (docUri, fileInfo)
188205
Debug.debugM Debug.LSP "Freshly Typechecked " (Map.toList freshlyCheckedFiles)
189206
-- Overwrite any files we successfully checked
@@ -198,15 +215,15 @@ fileAnalysisWorker = forever do
198215
for freshlyCheckedFiles \(FileAnalysis {fileUri, fileVersion, diagnostics}) -> do
199216
reportDiagnostics fileUri (Just fileVersion) $ fold diagnostics
200217

201-
analyseFile :: (Foldable f) => Uri -> Text -> PPED.PrettyPrintEnvDecl -> f (Note Symbol Ann) -> Lsp ([Diagnostic], [RangedCodeAction])
218+
analyseFile :: (Lspish m) => (Foldable f) => Uri -> Text -> PPED.PrettyPrintEnvDecl -> f (Note Symbol Ann) -> m ([Diagnostic], [RangedCodeAction])
202219
analyseFile fileUri srcText pped notes = do
203220
let ppe = PPED.suffixifiedPPE pped
204221
(noteDiags, noteActions) <- analyseNotes fileUri ppe (Text.unpack srcText) notes
205222
pure (noteDiags, noteActions)
206223

207224
-- | Returns diagnostics which show a warning diagnostic when editing a term that's conflicted in the
208225
-- codebase.
209-
computeConflictWarningDiagnostics :: Uri -> FileSummary -> Lsp [Diagnostic]
226+
computeConflictWarningDiagnostics :: (Lspish m) => Uri -> FileSummary -> m [Diagnostic]
210227
computeConflictWarningDiagnostics fileUri fileSummary@FileSummary {fileNames} = do
211228
let defLocations = fileDefLocations fileSummary
212229
conflictedNames <- Names.conflicts <$> getCurrentNames
@@ -249,11 +266,11 @@ getTokenMap tokens =
249266
)
250267
& fold
251268

252-
analyseNotes :: (Foldable f) => Uri -> PrettyPrintEnv -> String -> f (Note Symbol Ann) -> Lsp ([Diagnostic], [RangedCodeAction])
269+
analyseNotes :: forall m f. (Lspish m, Foldable f) => Uri -> PrettyPrintEnv -> String -> f (Note Symbol Ann) -> m ([Diagnostic], [RangedCodeAction])
253270
analyseNotes fileUri ppe src notes = do
254271
foldMapM go notes
255272
where
256-
go :: Note Symbol Ann -> Lsp ([Diagnostic], [RangedCodeAction])
273+
go :: Note Symbol Ann -> m ([Diagnostic], [RangedCodeAction])
257274
go note = case note of
258275
Result.TypeError errNote@(Context.ErrorNote {cause}) -> do
259276
let typeErr = TypeError.typeErrorFromNote errNote
@@ -421,7 +438,7 @@ toRangeMap :: (Foldable f) => f (Range, a) -> IntervalMap Position [a]
421438
toRangeMap vs =
422439
IM.fromListWith (<>) (toList vs <&> \(r, a) -> (rangeToInterval r, [a]))
423440

424-
getFileAnalysis :: Uri -> MaybeT Lsp FileAnalysis
441+
getFileAnalysis :: (Lspish m) => Uri -> MaybeT m FileAnalysis
425442
getFileAnalysis uri = do
426443
checkedFilesV <- asks checkedFilesVar
427444
-- Try to get the file analysis, if there's a var, then read it, waiting if necessary
@@ -456,20 +473,20 @@ getFileNames fileUri = do
456473
FileAnalysis {typecheckedFile = tf, parsedFile = pf} <- getFileAnalysis fileUri
457474
hoistMaybe (fmap UF.typecheckedToNames tf <|> fmap UF.toNames pf)
458475

459-
getFileSummary :: Uri -> MaybeT Lsp FileSummary
476+
getFileSummary :: (Lspish m) => Uri -> MaybeT m FileSummary
460477
getFileSummary uri = do
461478
FileAnalysis {fileSummary} <- getFileAnalysis uri
462479
MaybeT . pure $ fileSummary
463480

464481
-- TODO memoize per file
465-
ppedForFile :: Uri -> Lsp PPED.PrettyPrintEnvDecl
482+
ppedForFile :: (Lspish m) => Uri -> m PPED.PrettyPrintEnvDecl
466483
ppedForFile fileUri = do
467484
runMaybeT (getFileAnalysis fileUri) >>= \case
468485
Just (FileAnalysis {typecheckedFile = tf, parsedFile = uf}) ->
469486
ppedForFileHelper uf tf
470487
_ -> ppedForFileHelper Nothing Nothing
471488

472-
ppedForFileHelper :: Maybe (UF.UnisonFile Symbol a) -> Maybe (UF.TypecheckedUnisonFile Symbol a) -> Lsp PPED.PrettyPrintEnvDecl
489+
ppedForFileHelper :: (Lspish m) => Maybe (UF.UnisonFile Symbol a) -> Maybe (UF.TypecheckedUnisonFile Symbol a) -> m PPED.PrettyPrintEnvDecl
473490
ppedForFileHelper uf tf = do
474491
codebasePPED <- currentPPED
475492
hashLen <- asks codebase >>= \codebase -> liftIO (Codebase.runTransaction codebase Codebase.hashLength)

unison-cli/src/Unison/LSP/Hover.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,19 @@ hoverHandler m respond = do
4646
_range = Nothing -- TODO add range info
4747
}
4848

49-
hoverInfo :: Uri -> Position -> MaybeT Lsp Text
49+
hoverInfo :: forall m. (Lspish m, MonadUnliftIO m) => Uri -> Position -> MaybeT m Text
5050
hoverInfo uri pos =
5151
(hoverInfoForRef <|> hoverInfoForLiteral <|> hoverInfoForLocalVar)
5252
where
5353
markdownify :: Text -> Text
5454
markdownify rendered = Text.unlines ["``` unison", rendered, "```"]
5555
prettyWidth :: Pretty.Width
5656
prettyWidth = 40
57-
hoverInfoForRef :: MaybeT Lsp Text
57+
hoverInfoForRef :: (MonadUnliftIO m) => MaybeT m Text
5858
hoverInfoForRef = do
5959
symAtCursor <- VFS.identifierAtPosition uri pos
6060
ref <- LSPQ.refAtPosition uri pos
61-
pped <- lift $ ppedForFile uri
61+
pped <- ppedForFile uri
6262
let unsuffixifiedPPE = PPED.unsuffixifiedPPE pped
6363
let fqn = case ref of
6464
LD.TypeReference ref -> PPE.typeName unsuffixifiedPPE ref
@@ -109,7 +109,7 @@ hoverInfo uri pos =
109109
let renderedType = Text.pack $ TypePrinter.prettyStr (Just prettyWidth) (PPED.suffixifiedPPE pped) typ
110110
in markdownify (name <> " : " <> renderedType)
111111

112-
hoverInfoForLiteral :: MaybeT Lsp Text
112+
hoverInfoForLiteral :: MaybeT m Text
113113
hoverInfoForLiteral =
114114
markdownify <$> do
115115
LSPQ.nodeAtPosition uri pos >>= \case
@@ -121,7 +121,7 @@ hoverInfo uri pos =
121121
typ <- hoistMaybe $ builtinTypeForPatternLiterals pat
122122
pure (": " <> typ)
123123

124-
hoverInfoForLocalVar :: MaybeT Lsp Text
124+
hoverInfoForLocalVar :: MaybeT m Text
125125
hoverInfoForLocalVar = do
126126
localVar <- LSPQ.nodeAtPositionMatching uri pos \case
127127
LSPQ.TypeNode {} -> empty
@@ -139,7 +139,7 @@ hoverInfo uri pos =
139139
_ -> tShow localVar
140140
pure $ renderTypeSigForHover pped varName typ
141141

142-
hoistMaybe :: Maybe a -> MaybeT Lsp a
142+
hoistMaybe :: Maybe a -> MaybeT m a
143143
hoistMaybe = MaybeT . pure
144144

145145
-- | Get the type for term literals.

unison-cli/src/Unison/LSP/Queries.hs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,29 @@ import Unison.UnisonFile.Summary (FileSummary (..))
6464
import Unison.Util.Pretty qualified as Pretty
6565

6666
-- | Returns a reference to whatever the symbol at the given position refers to.
67-
refAtPosition :: Uri -> Position -> MaybeT Lsp LabeledDependency
67+
refAtPosition :: forall m. (Lspish m) => Uri -> Position -> MaybeT m LabeledDependency
6868
refAtPosition uri pos = do
6969
findInNode <|> findInDecl
7070
where
71-
findInNode :: MaybeT Lsp LabeledDependency
71+
findInNode :: MaybeT m LabeledDependency
7272
findInNode =
7373
nodeAtPosition uri pos >>= \case
7474
TermNode term -> hoistMaybe $ refInTerm term
7575
TypeNode typ -> hoistMaybe $ fmap TypeReference (refInType typ)
7676
PatternNode pat -> hoistMaybe $ refInPattern pat
77-
findInDecl :: MaybeT Lsp LabeledDependency
77+
findInDecl :: MaybeT m LabeledDependency
7878
findInDecl =
7979
LD.TypeReference <$> do
8080
let uPos = lspToUPos pos
8181
(FileSummary {dataDeclsBySymbol, effectDeclsBySymbol}) <- getFileSummary uri
8282
( altMap (hoistMaybe . refInDecl uPos . Right . snd) dataDeclsBySymbol
8383
<|> altMap (hoistMaybe . refInDecl uPos . Left . snd) effectDeclsBySymbol
8484
)
85-
hoistMaybe :: Maybe a -> MaybeT Lsp a
85+
hoistMaybe :: Maybe a -> MaybeT m a
8686
hoistMaybe = MaybeT . pure
8787

8888
-- | Gets the type of a reference from either the parsed file or the codebase.
89-
getTypeOfReferent :: Uri -> Referent -> MaybeT Lsp (Type Symbol Ann)
89+
getTypeOfReferent :: (Lspish m) => Uri -> Referent -> MaybeT m (Type Symbol Ann)
9090
getTypeOfReferent fileUri ref = do
9191
getFromFile <|> getFromCodebase
9292
where
@@ -107,11 +107,11 @@ getTypeOfReferent fileUri ref = do
107107
MaybeT . liftIO $ Codebase.runTransaction codebase $ Codebase.getTypeOfReferent codebase ref
108108

109109
-- | Gets a decl from either the parsed file or the codebase.
110-
getTypeDeclaration :: Uri -> Reference.Id -> MaybeT Lsp (Decl Symbol Ann)
110+
getTypeDeclaration :: forall m. (Lspish m) => Uri -> Reference.Id -> MaybeT m (Decl Symbol Ann)
111111
getTypeDeclaration fileUri refId = do
112112
getFromFile <|> getFromCodebase
113113
where
114-
getFromFile :: MaybeT Lsp (Decl Symbol Ann)
114+
getFromFile :: MaybeT m (Decl Symbol Ann)
115115
getFromFile = do
116116
FileSummary {dataDeclsByReference, effectDeclsByReference} <- getFileSummary fileUri
117117
let datas = dataDeclsByReference ^.. ix refId . folded
@@ -393,15 +393,15 @@ refInDecl p (DD.asDataDecl -> dd) =
393393

394394
-- | Returns the ABT node at the provided position.
395395
-- Does not return Decl nodes.
396-
nodeAtPosition :: Uri -> Position -> MaybeT Lsp (SourceNode Ann)
396+
nodeAtPosition :: (Lspish m) => Uri -> Position -> MaybeT m (SourceNode Ann)
397397
nodeAtPosition uri pos = nodeAtPositionMatching uri pos pure
398398

399399
-- | Search the ABT for nodes which intersect at a given position, running the
400400
-- provided selector on them and aligning results to prefer smaller containing nodes first.
401401
-- The caller may use either 'pure' or 'empty' in the selector to select or ignore a given option.
402402
--
403403
-- Does not return Decl nodes.
404-
nodeAtPositionMatching :: Uri -> Position -> (SourceNode Ann -> MaybeT Lsp a) -> MaybeT Lsp a
404+
nodeAtPositionMatching :: (Lspish m) => Uri -> Position -> (SourceNode Ann -> MaybeT m a) -> MaybeT m a
405405
nodeAtPositionMatching uri (lspToUPos -> pos) pred = do
406406
(FileSummary {termsBySymbol, testWatchSummary, exprWatchSummary}) <- getFileSummary uri
407407

@@ -437,12 +437,12 @@ removeInferredTypeAnnotations =
437437
t -> t
438438

439439
-- | Renders all docs for a given FQN to markdown.
440-
markdownDocsForFQN :: Uri -> HQ.HashQualified Name -> Lsp [Text]
440+
markdownDocsForFQN :: (Lspish m) => Uri -> HQ.HashQualified Name -> m [Text]
441441
markdownDocsForFQN fileUri fqn =
442442
fromMaybe [] <$> runMaybeT do
443443
pped <- lift $ ppedForFile fileUri
444444
name <- MaybeT . pure $ HQ.toName fqn
445-
nameSearch <- lift $ getNameSearch
445+
nameSearch <- getNameSearch
446446
Env {codebase, runtime} <- ask
447447
liftIO $ do
448448
docRefs <- Codebase.runTransaction codebase $ Backend.docsForDefinitionName codebase nameSearch ExactName name

unison-cli/src/Unison/LSP/Types.hs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE ConstraintKinds #-}
12
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE RecordWildCards #-}
34
{-# LANGUAGE TypeOperators #-}
@@ -48,6 +49,10 @@ import Unison.UnisonFile qualified as UF
4849
import Unison.UnisonFile.Summary (FileSummary (..))
4950
import UnliftIO
5051

52+
-- | Constraints for the lsp monad, but using constraints so we can
53+
-- swap it out in tests.
54+
type Lspish m = (MonadReader Env m, MonadIO m)
55+
5156
-- | A custom LSP monad wrapper so we can provide our own environment.
5257
newtype Lsp a = Lsp {runLspM :: ReaderT Env (LspM Config) a}
5358
deriving newtype (Functor, Applicative, Monad, MonadIO, MonadUnliftIO, MonadReader Env, MonadLsp Config)
@@ -132,19 +137,19 @@ data FileAnalysis = FileAnalysis
132137
}
133138
deriving stock (Show)
134139

135-
getCurrentProjectPath :: Lsp PP.ProjectPath
140+
getCurrentProjectPath :: (Lspish m) => m PP.ProjectPath
136141
getCurrentProjectPath = asks currentProjectPathCache >>= liftIO
137142

138143
getCodebaseCompletions :: Lsp CompletionTree
139144
getCodebaseCompletions = asks completionsVar >>= atomically . readTMVar
140145

141-
currentPPED :: Lsp PrettyPrintEnvDecl
146+
currentPPED :: (Lspish m) => m PrettyPrintEnvDecl
142147
currentPPED = asks ppedCache >>= liftIO
143148

144-
getNameSearch :: Lsp (NameSearch Sqlite.Transaction)
149+
getNameSearch :: (Lspish m) => m (NameSearch Sqlite.Transaction)
145150
getNameSearch = asks nameSearchCache >>= liftIO
146151

147-
getCurrentNames :: Lsp Names
152+
getCurrentNames :: (MonadReader Env m, MonadIO m) => m Names
148153
getCurrentNames = asks currentNamesCache >>= liftIO
149154

150155
data Config = Config

unison-cli/src/Unison/LSP/VFS.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ usingVFS m = do
3434
vfsVar' <- asks vfsVar
3535
modifyMVar vfsVar' $ \vfs -> swap <$> runStateT m vfs
3636

37-
getVirtualFile :: Uri -> MaybeT Lsp VirtualFile
37+
getVirtualFile :: (Lspish m) => Uri -> MaybeT m VirtualFile
3838
getVirtualFile fileUri = do
3939
vfs <- asks vfsVar >>= readMVar
4040
MaybeT . pure $ vfs ^. vfsMap . at (toNormalizedUri $ fileUri)
4141

42-
getFileContents :: Uri -> MaybeT Lsp (FileVersion, Text)
42+
getFileContents :: (Lspish m) => Uri -> MaybeT m (FileVersion, Text)
4343
getFileContents fileUri = do
4444
vf <- getVirtualFile fileUri
4545
pure (vf ^. lsp_version, Rope.toText $ vf ^. file_text)
@@ -71,12 +71,12 @@ markAllFilesDirty = do
7171
markFilesDirty $ Map.keys (vfs ^. vfsMap)
7272

7373
-- | Returns the name or symbol which the provided position is contained in.
74-
identifierAtPosition :: Uri -> Position -> MaybeT Lsp Text
74+
identifierAtPosition :: (Lspish m) => Uri -> Position -> MaybeT m Text
7575
identifierAtPosition uri pos = do
7676
identifierSplitAtPosition uri pos <&> \(before, after) -> (before <> after)
7777

7878
-- | Returns the prefix and suffix of the symbol which the provided position is contained in.
79-
identifierSplitAtPosition :: Uri -> Position -> MaybeT Lsp (Text, Text)
79+
identifierSplitAtPosition :: (Lspish m) => Uri -> Position -> MaybeT m (Text, Text)
8080
identifierSplitAtPosition uri pos = do
8181
vf <- getVirtualFile uri
8282
PosPrefixInfo {fullLine, cursorPos} <- MaybeT (VFS.getCompletionPrefix pos vf)

0 commit comments

Comments
 (0)