Skip to content

Commit a1d6980

Browse files
committed
Fix context not ignoring prefix for calculating indentation
1 parent 738a833 commit a1d6980

File tree

3 files changed

+44
-35
lines changed

3 files changed

+44
-35
lines changed

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,12 @@ completion recorder _ide _ complParams = do
284284
let (TextDocumentIdentifier uri) = complParams ^. JL.textDocument
285285
position = complParams ^. JL.position
286286
contents <- getVirtualFile $ toNormalizedUri uri
287-
fmap (Right . InL) $ case (contents, uriToFilePath' uri) of
287+
case (contents, uriToFilePath' uri) of
288288
(Just cnts, Just path) -> do
289289
pref <- VFS.getCompletionPrefix position cnts
290-
liftIO $ result pref path cnts
291-
_ -> return []
290+
let res = result pref path cnts
291+
liftIO $ fmap (Right . InL) res
292+
_ -> pure . Right . InR $ InR Null
292293
where
293294
result :: Maybe VFS.PosPrefixInfo -> FilePath -> VFS.VirtualFile -> IO [CompletionItem]
294295
result Nothing _ _ = pure []

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completions.hs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import qualified Data.List as List
1010
import Data.Map (Map)
1111
import qualified Data.Map as Map
1212
import Data.Maybe (fromMaybe)
13+
import Data.Ord (Down (..))
1314
import qualified Data.Text as T
1415
import Data.Text.Utf16.Rope (Rope)
1516
import qualified Data.Text.Utf16.Rope as Rope
16-
import Debug.Trace (traceShowM)
1717
import Development.IDE as D
1818
import Distribution.CabalSpecVersion (CabalSpecVersion (CabalSpecV2_2),
1919
showCabalSpecVersion)
@@ -26,7 +26,6 @@ import qualified Language.LSP.Protocol.Types as Compls (CompletionItem
2626
import qualified Language.LSP.Protocol.Types as LSP
2727
import qualified Language.LSP.VFS as VFS
2828
import qualified Text.Fuzzy.Parallel as Fuzzy
29-
import Data.Ord (Down(..))
3029

3130
-- ----------------------------------------------------------------
3231
-- Public API for Completions
@@ -76,30 +75,30 @@ contextToCompleter (Stanza s, KeyWord kw) =
7675
TODO: first line can only have cabal-version: keyword
7776
-}
7877
getContext :: (MonadIO m) => Recorder (WithPriority Log) -> CabalPrefixInfo -> Rope -> MaybeT m Context
79-
getContext recorder ctx ls =
78+
getContext recorder prefInfo ls =
8079
case prevLinesM of
8180
Just prevLines -> do
8281
let lvlContext =
83-
if pos ^. JL.character == 0
82+
if completionIndentation prefInfo == 0
8483
then TopLevel
8584
else currentLevel prevLines
8685
case lvlContext of
8786
TopLevel -> do
88-
kwContext <- MaybeT . pure $ getKeyWordContext ctx prevLines (cabalVersionKeyword <> cabalKeywords)
87+
kwContext <- MaybeT . pure $ getKeyWordContext prefInfo prevLines (cabalVersionKeyword <> cabalKeywords)
8988
pure (TopLevel, kwContext)
9089
Stanza s ->
9190
case Map.lookup s stanzaKeywordMap of
9291
Nothing -> do
9392
pure (Stanza s, None)
9493
Just m -> do
95-
kwContext <- MaybeT . pure $ getKeyWordContext ctx prevLines m
94+
kwContext <- MaybeT . pure $ getKeyWordContext prefInfo prevLines m
9695
pure (Stanza s, kwContext)
9796
Nothing -> do
9897
logWith recorder Warning $ LogFileSplitError pos
9998
-- basically returns nothing
10099
fail "Abort computation"
101100
where
102-
pos = completionCursorPosition ctx
101+
pos = completionCursorPosition prefInfo
103102
prevLinesM = splitAtPosition pos ls
104103

105104
-- ----------------------------------------------------------------
@@ -112,13 +111,13 @@ getContext recorder ctx ls =
112111
previously written keyword matches one in the map.
113112
-}
114113
getKeyWordContext :: CabalPrefixInfo -> [T.Text] -> Map KeyWordName a -> Maybe KeyWordContext
115-
getKeyWordContext ctx ls keywords = do
114+
getKeyWordContext prefInfo ls keywords = do
116115
case lastNonEmptyLineM of
117116
Nothing -> Just None
118117
Just lastLine' -> do
119118
let (whiteSpaces, lastLine) = T.span (== ' ') lastLine'
120119
let keywordIndentation = T.length whiteSpaces
121-
let cursorIndentation = fromIntegral (pos ^. JL.character) - (T.length $ completionPrefix ctx)
120+
let cursorIndentation = completionIndentation prefInfo
122121
-- in order to be in a keyword context the cursor needs
123122
-- to be indented more than the keyword
124123
if cursorIndentation > keywordIndentation
@@ -128,7 +127,6 @@ getKeyWordContext ctx ls keywords = do
128127
Just kw -> Just $ KeyWord kw
129128
else Just None
130129
where
131-
pos = completionCursorPosition ctx
132130
lastNonEmptyLineM :: Maybe T.Text
133131
lastNonEmptyLineM = do
134132
(curLine, rest) <- List.uncons ls
@@ -222,6 +220,12 @@ getCabalPrefixInfo dir prefixInfo =
222220
-- otherwise we parse until a space occurs
223221
stopConditionChars = apostropheOrSpaceSeparator : [',', ':']
224222

223+
224+
completionIndentation :: CabalPrefixInfo -> Int
225+
completionIndentation prefInfo = fromIntegral (pos ^. JL.character) - (T.length $ completionPrefix prefInfo)
226+
where
227+
pos = completionCursorPosition prefInfo
228+
225229
mkCompletionItem :: CabalCompletionItem -> LSP.CompletionItem
226230
mkCompletionItem completionItem =
227231
LSP.CompletionItem

plugins/hls-cabal-plugin/test/Main.hs

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -357,105 +357,108 @@ contextTests =
357357
[ testCase "Empty File - Start" $ do
358358
-- for a completely empty file, the context needs to
359359
-- be top level without a specified keyword
360-
ctx <- callGetContext (Position 0 0) [""]
360+
ctx <- callGetContext (Position 0 0) "" [""]
361361
ctx @?= (TopLevel, None)
362362
, testCase "Cabal version keyword - no value, no space after :" $ do
363363
-- on a file, where the keyword is already written
364364
-- the context should still be toplevel but the keyword should be recognized
365-
ctx <- callGetContext (Position 0 14) ["cabal-version:"]
365+
ctx <- callGetContext (Position 0 14) ""["cabal-version:"]
366366
ctx @?= (TopLevel, KeyWord "cabal-version:")
367367
, testCase "Cabal version keyword - cursor in keyword" $ do
368368
-- on a file, where the keyword is already written
369369
-- but the cursor is in the middle of the keyword,
370370
-- we are not in a keyword context
371-
ctx <- callGetContext (Position 0 5) ["cabal-version:"]
371+
ctx <- callGetContext (Position 0 5) "cabal"["cabal-version:"]
372372
ctx @?= (TopLevel, None)
373373
, testCase "Cabal version keyword - no value, many spaces" $ do
374374
-- on a file, where the "cabal-version:" keyword is already written
375375
-- the context should still be top level but the keyword should be recognized
376-
ctx <- callGetContext (Position 0 45) ["cabal-version:" <> T.replicate 50 " "]
376+
ctx <- callGetContext (Position 0 45) ("")["cabal-version:" <> T.replicate 50 " "]
377377
ctx @?= (TopLevel, KeyWord "cabal-version:")
378378
, testCase "Cabal version keyword - keyword partly written" $ do
379379
-- in the first line of the file, if the keyword
380380
-- has not been written completely, the keyword context
381381
-- should still be None
382-
ctx <- callGetContext (Position 0 5) ["cabal"]
382+
ctx <- callGetContext (Position 0 5) "cabal" ["cabal"]
383383
ctx @?= (TopLevel, None)
384384
, testCase "Cabal version keyword - value partly written" $ do
385385
-- in the first line of the file, if the keyword
386386
-- has not been written completely, the keyword context
387387
-- should still be None
388-
ctx <- callGetContext (Position 0 17) ["cabal-version: 1."]
388+
ctx <- callGetContext (Position 0 17) "1."["cabal-version: 1."]
389389
ctx @?= (TopLevel, KeyWord "cabal-version:")
390390
, testCase "Inside Stanza - no keyword" $ do
391391
-- on a file, where the library stanza has been defined
392392
-- but no keyword is defined afterwards, the stanza context should be recognized
393-
ctx <- callGetContext (Position 3 2) libraryStanzaData
393+
ctx <- callGetContext (Position 3 2) "" libraryStanzaData
394394
ctx @?= (Stanza "library", None)
395395
, testCase "Inside Stanza - keyword, no value" $ do
396396
-- on a file, where the library stanza and a keyword
397397
-- has been defined, the keyword and stanza should be recognized
398-
ctx <- callGetContext (Position 4 21) libraryStanzaData
398+
ctx <- callGetContext (Position 4 21) "" libraryStanzaData
399399
ctx @?= (Stanza "library", KeyWord "build-depends:")
400400
, expectFailBecause "While not valid, it is not that important to make the code more complicated for this" $
401401
testCase "Cabal version keyword - no value, next line" $ do
402402
-- if the cabal version keyword has been written but without a value,
403403
-- in the next line we still should be in top level context with no keyword
404404
-- since the cabal version keyword and value pair need to be in the same line
405-
ctx <- callGetContext (Position 1 2) ["cabal-version:", ""]
405+
ctx <- callGetContext (Position 1 2) "" ["cabal-version:", ""]
406406
ctx @?= (TopLevel, None)
407407
, testCase "Non-cabal-version keyword - no value, next line indentented position" $ do
408408
-- if a keyword, other than the cabal version keyword has been written
409409
-- with no value, in the next line we still should be in top level keyword context
410410
-- of the keyword with no value, since its value may be written in the next line
411-
ctx <- callGetContext (Position 2 4) topLevelData
411+
ctx <- callGetContext (Position 2 4) "" topLevelData
412412
ctx @?= (TopLevel, KeyWord "name:")
413413
, testCase "Non-cabal-version keyword - no value, next line at start" $ do
414414
-- if a keyword, other than the cabal version keyword has been written
415415
-- with no value, in the next line we still should be in top level context
416416
-- but not the keyword's, since it is not viable to write a value for a
417417
-- keyword a the start of the next line
418-
ctx <- callGetContext (Position 2 0) topLevelData
418+
ctx <- callGetContext (Position 2 0) "" topLevelData
419+
ctx @?= (TopLevel, None)
420+
, testCase "Toplevel after stanza partially written" $ do
421+
ctx <- callGetContext (Position 6 2) "ma" libraryStanzaData
419422
ctx @?= (TopLevel, None)
420423
, testCase "Non-cabal-version keyword - no value, multiple lines between" $ do
421424
-- if a keyword, other than the cabal version keyword has been written
422425
-- with no value, even with multiple lines in between we can still write the
423426
-- value corresponding to the keyword
424-
ctx <- callGetContext (Position 5 4) topLevelData
427+
ctx <- callGetContext (Position 5 4) "" topLevelData
425428
ctx @?= (TopLevel, KeyWord "name:")
426429
, testCase "Keyword inside stanza - cursor indented more than keyword in next line" $ do
427430
-- if a keyword, other than the cabal version keyword has been written
428431
-- in a stanza context with no value, then the value may be written in the next line,
429432
-- when the cursor is indented more than the keyword
430-
ctx <- callGetContext (Position 5 8) libraryStanzaData
433+
ctx <- callGetContext (Position 5 8) "" libraryStanzaData
431434
ctx @?= (Stanza "library", KeyWord "build-depends:")
432435
, testCase "Keyword inside stanza - cursor indented less than keyword in next line" $ do
433436
-- if a keyword, other than the cabal version keyword has been written
434437
-- in a stanza context with no value, then the value may not be written in the next line,
435438
-- when the cursor is indented less than the keyword
436-
ctx <- callGetContext (Position 5 2) libraryStanzaData
439+
ctx <- callGetContext (Position 5 2) "" libraryStanzaData
437440
ctx @?= (Stanza "library", None)
438441
, testCase "Keyword inside stanza - cursor at start of next line" $ do
439442
-- in a stanza context with no value the value may not be written in the next line,
440443
-- when the cursor is not indented and we are in the top level context
441-
ctx <- callGetContext (Position 5 0) libraryStanzaData
444+
ctx <- callGetContext (Position 5 0) "" libraryStanzaData
442445
ctx @?= (TopLevel, None)
443446
, testCase "Top level - cursor in later line with partially written value" $ do
444-
ctx <- callGetContext (Position 5 13) topLevelData
447+
ctx <- callGetContext (Position 5 13) "eee" topLevelData
445448
ctx @?= (TopLevel, KeyWord "name:")
446449
]
447450
where
448-
callGetContext :: Position -> [T.Text] -> IO Context
449-
callGetContext pos ls = do
450-
runMaybeT (getContext mempty (simpleCabalPrefixInfo pos) (Rope.fromText $ T.unlines ls))
451+
callGetContext :: Position -> T.Text -> [T.Text] -> IO Context
452+
callGetContext pos pref ls = do
453+
runMaybeT (getContext mempty (simpleCabalPrefixInfo pos pref) (Rope.fromText $ T.unlines ls))
451454
>>= \case
452455
Nothing -> assertFailure "Context must be found"
453456
Just ctx -> pure ctx
454457

455-
simpleCabalPrefixInfo :: Position -> CabalPrefixInfo
456-
simpleCabalPrefixInfo pos =
458+
simpleCabalPrefixInfo :: Position -> T.Text -> CabalPrefixInfo
459+
simpleCabalPrefixInfo pos prefix =
457460
CabalPrefixInfo
458-
{ completionPrefix = ""
461+
{ completionPrefix = prefix
459462
, completionSuffix = Nothing
460463
, completionCursorPosition = pos
461464
, completionRange = Range pos (Position 0 0)
@@ -606,6 +609,7 @@ libraryStanzaData =
606609
, " default-language: Haskell98"
607610
, " build-depends: "
608611
, " "
612+
, "ma "
609613
]
610614

611615
topLevelData :: [T.Text]

0 commit comments

Comments
 (0)