Skip to content

Commit

Permalink
Fix context not ignoring prefix for calculating indentation
Browse files Browse the repository at this point in the history
  • Loading branch information
VeryMilkyJoe committed Jun 23, 2023
1 parent 738a833 commit f0eff24
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 32 deletions.
24 changes: 14 additions & 10 deletions plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal/Completions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import qualified Data.List as List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe)
import Data.Ord (Down (..))
import qualified Data.Text as T
import Data.Text.Utf16.Rope (Rope)
import qualified Data.Text.Utf16.Rope as Rope
import Debug.Trace (traceShowM)
import Development.IDE as D
import Distribution.CabalSpecVersion (CabalSpecVersion (CabalSpecV2_2),
showCabalSpecVersion)
Expand All @@ -26,7 +26,6 @@ import qualified Language.LSP.Protocol.Types as Compls (CompletionItem
import qualified Language.LSP.Protocol.Types as LSP
import qualified Language.LSP.VFS as VFS
import qualified Text.Fuzzy.Parallel as Fuzzy
import Data.Ord (Down(..))

-- ----------------------------------------------------------------
-- Public API for Completions
Expand Down Expand Up @@ -76,30 +75,30 @@ contextToCompleter (Stanza s, KeyWord kw) =
TODO: first line can only have cabal-version: keyword
-}
getContext :: (MonadIO m) => Recorder (WithPriority Log) -> CabalPrefixInfo -> Rope -> MaybeT m Context
getContext recorder ctx ls =
getContext recorder prefInfo ls =
case prevLinesM of
Just prevLines -> do
let lvlContext =
if pos ^. JL.character == 0
if completionIndentation prefInfo == 0
then TopLevel
else currentLevel prevLines
case lvlContext of
TopLevel -> do
kwContext <- MaybeT . pure $ getKeyWordContext ctx prevLines (cabalVersionKeyword <> cabalKeywords)
kwContext <- MaybeT . pure $ getKeyWordContext prefInfo prevLines (cabalVersionKeyword <> cabalKeywords)
pure (TopLevel, kwContext)
Stanza s ->
case Map.lookup s stanzaKeywordMap of
Nothing -> do
pure (Stanza s, None)
Just m -> do
kwContext <- MaybeT . pure $ getKeyWordContext ctx prevLines m
kwContext <- MaybeT . pure $ getKeyWordContext prefInfo prevLines m
pure (Stanza s, kwContext)
Nothing -> do
logWith recorder Warning $ LogFileSplitError pos
-- basically returns nothing
fail "Abort computation"
where
pos = completionCursorPosition ctx
pos = completionCursorPosition prefInfo
prevLinesM = splitAtPosition pos ls

-- ----------------------------------------------------------------
Expand All @@ -112,13 +111,13 @@ getContext recorder ctx ls =
previously written keyword matches one in the map.
-}
getKeyWordContext :: CabalPrefixInfo -> [T.Text] -> Map KeyWordName a -> Maybe KeyWordContext
getKeyWordContext ctx ls keywords = do
getKeyWordContext prefInfo ls keywords = do
case lastNonEmptyLineM of
Nothing -> Just None
Just lastLine' -> do
let (whiteSpaces, lastLine) = T.span (== ' ') lastLine'
let keywordIndentation = T.length whiteSpaces
let cursorIndentation = fromIntegral (pos ^. JL.character) - (T.length $ completionPrefix ctx)
let cursorIndentation = completionIndentation prefInfo
-- in order to be in a keyword context the cursor needs
-- to be indented more than the keyword
if cursorIndentation > keywordIndentation
Expand All @@ -128,7 +127,6 @@ getKeyWordContext ctx ls keywords = do
Just kw -> Just $ KeyWord kw
else Just None
where
pos = completionCursorPosition ctx
lastNonEmptyLineM :: Maybe T.Text
lastNonEmptyLineM = do
(curLine, rest) <- List.uncons ls
Expand Down Expand Up @@ -222,6 +220,12 @@ getCabalPrefixInfo dir prefixInfo =
-- otherwise we parse until a space occurs
stopConditionChars = apostropheOrSpaceSeparator : [',', ':']


completionIndentation :: CabalPrefixInfo -> Int
completionIndentation prefInfo = fromIntegral (pos ^. JL.character) - (T.length $ completionPrefix prefInfo)
where
pos = completionCursorPosition prefInfo

mkCompletionItem :: CabalCompletionItem -> LSP.CompletionItem
mkCompletionItem completionItem =
LSP.CompletionItem
Expand Down
48 changes: 26 additions & 22 deletions plugins/hls-cabal-plugin/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -357,105 +357,108 @@ contextTests =
[ testCase "Empty File - Start" $ do
-- for a completely empty file, the context needs to
-- be top level without a specified keyword
ctx <- callGetContext (Position 0 0) [""]
ctx <- callGetContext (Position 0 0) "" [""]
ctx @?= (TopLevel, None)
, testCase "Cabal version keyword - no value, no space after :" $ do
-- on a file, where the keyword is already written
-- the context should still be toplevel but the keyword should be recognized
ctx <- callGetContext (Position 0 14) ["cabal-version:"]
ctx <- callGetContext (Position 0 14) ""["cabal-version:"]
ctx @?= (TopLevel, KeyWord "cabal-version:")
, testCase "Cabal version keyword - cursor in keyword" $ do
-- on a file, where the keyword is already written
-- but the cursor is in the middle of the keyword,
-- we are not in a keyword context
ctx <- callGetContext (Position 0 5) ["cabal-version:"]
ctx <- callGetContext (Position 0 5) "cabal"["cabal-version:"]
ctx @?= (TopLevel, None)
, testCase "Cabal version keyword - no value, many spaces" $ do
-- on a file, where the "cabal-version:" keyword is already written
-- the context should still be top level but the keyword should be recognized
ctx <- callGetContext (Position 0 45) ["cabal-version:" <> T.replicate 50 " "]
ctx <- callGetContext (Position 0 45) ("")["cabal-version:" <> T.replicate 50 " "]
ctx @?= (TopLevel, KeyWord "cabal-version:")
, testCase "Cabal version keyword - keyword partly written" $ do
-- in the first line of the file, if the keyword
-- has not been written completely, the keyword context
-- should still be None
ctx <- callGetContext (Position 0 5) ["cabal"]
ctx <- callGetContext (Position 0 5) "cabal" ["cabal"]
ctx @?= (TopLevel, None)
, testCase "Cabal version keyword - value partly written" $ do
-- in the first line of the file, if the keyword
-- has not been written completely, the keyword context
-- should still be None
ctx <- callGetContext (Position 0 17) ["cabal-version: 1."]
ctx <- callGetContext (Position 0 17) "1."["cabal-version: 1."]
ctx @?= (TopLevel, KeyWord "cabal-version:")
, testCase "Inside Stanza - no keyword" $ do
-- on a file, where the library stanza has been defined
-- but no keyword is defined afterwards, the stanza context should be recognized
ctx <- callGetContext (Position 3 2) libraryStanzaData
ctx <- callGetContext (Position 3 2) "" libraryStanzaData
ctx @?= (Stanza "library", None)
, testCase "Inside Stanza - keyword, no value" $ do
-- on a file, where the library stanza and a keyword
-- has been defined, the keyword and stanza should be recognized
ctx <- callGetContext (Position 4 21) libraryStanzaData
ctx <- callGetContext (Position 4 21) "" libraryStanzaData
ctx @?= (Stanza "library", KeyWord "build-depends:")
, expectFailBecause "While not valid, it is not that important to make the code more complicated for this" $
testCase "Cabal version keyword - no value, next line" $ do
-- if the cabal version keyword has been written but without a value,
-- in the next line we still should be in top level context with no keyword
-- since the cabal version keyword and value pair need to be in the same line
ctx <- callGetContext (Position 1 2) ["cabal-version:", ""]
ctx <- callGetContext (Position 1 2) "" ["cabal-version:", ""]
ctx @?= (TopLevel, None)
, testCase "Non-cabal-version keyword - no value, next line indentented position" $ do
-- if a keyword, other than the cabal version keyword has been written
-- with no value, in the next line we still should be in top level keyword context
-- of the keyword with no value, since its value may be written in the next line
ctx <- callGetContext (Position 2 4) topLevelData
ctx <- callGetContext (Position 2 4) "" topLevelData
ctx @?= (TopLevel, KeyWord "name:")
, testCase "Non-cabal-version keyword - no value, next line at start" $ do
-- if a keyword, other than the cabal version keyword has been written
-- with no value, in the next line we still should be in top level context
-- but not the keyword's, since it is not viable to write a value for a
-- keyword a the start of the next line
ctx <- callGetContext (Position 2 0) topLevelData
ctx <- callGetContext (Position 2 0) "" topLevelData
ctx @?= (TopLevel, None)
, testCase "Toplevel after stanza partially written" $ do
ctx <- callGetContext (Position 6 2) "ma" libraryStanzaData
ctx @?= (TopLevel, None)
, testCase "Non-cabal-version keyword - no value, multiple lines between" $ do
-- if a keyword, other than the cabal version keyword has been written
-- with no value, even with multiple lines in between we can still write the
-- value corresponding to the keyword
ctx <- callGetContext (Position 5 4) topLevelData
ctx <- callGetContext (Position 5 4) "" topLevelData
ctx @?= (TopLevel, KeyWord "name:")
, testCase "Keyword inside stanza - cursor indented more than keyword in next line" $ do
-- if a keyword, other than the cabal version keyword has been written
-- in a stanza context with no value, then the value may be written in the next line,
-- when the cursor is indented more than the keyword
ctx <- callGetContext (Position 5 8) libraryStanzaData
ctx <- callGetContext (Position 5 8) "" libraryStanzaData
ctx @?= (Stanza "library", KeyWord "build-depends:")
, testCase "Keyword inside stanza - cursor indented less than keyword in next line" $ do
-- if a keyword, other than the cabal version keyword has been written
-- in a stanza context with no value, then the value may not be written in the next line,
-- when the cursor is indented less than the keyword
ctx <- callGetContext (Position 5 2) libraryStanzaData
ctx <- callGetContext (Position 5 2) "" libraryStanzaData
ctx @?= (Stanza "library", None)
, testCase "Keyword inside stanza - cursor at start of next line" $ do
-- in a stanza context with no value the value may not be written in the next line,
-- when the cursor is not indented and we are in the top level context
ctx <- callGetContext (Position 5 0) libraryStanzaData
ctx <- callGetContext (Position 5 0) "" libraryStanzaData
ctx @?= (TopLevel, None)
, testCase "Top level - cursor in later line with partially written value" $ do
ctx <- callGetContext (Position 5 13) topLevelData
ctx <- callGetContext (Position 5 13) "eee" topLevelData
ctx @?= (TopLevel, KeyWord "name:")
]
where
callGetContext :: Position -> [T.Text] -> IO Context
callGetContext pos ls = do
runMaybeT (getContext mempty (simpleCabalPrefixInfo pos) (Rope.fromText $ T.unlines ls))
callGetContext :: Position -> T.Text -> [T.Text] -> IO Context
callGetContext pos pref ls = do
runMaybeT (getContext mempty (simpleCabalPrefixInfo pos pref) (Rope.fromText $ T.unlines ls))
>>= \case
Nothing -> assertFailure "Context must be found"
Just ctx -> pure ctx

simpleCabalPrefixInfo :: Position -> CabalPrefixInfo
simpleCabalPrefixInfo pos =
simpleCabalPrefixInfo :: Position -> T.Text -> CabalPrefixInfo
simpleCabalPrefixInfo pos prefix =
CabalPrefixInfo
{ completionPrefix = ""
{ completionPrefix = prefix
, completionSuffix = Nothing
, completionCursorPosition = pos
, completionRange = Range pos (Position 0 0)
Expand Down Expand Up @@ -606,6 +609,7 @@ libraryStanzaData =
, " default-language: Haskell98"
, " build-depends: "
, " "
, "ma "
]

topLevelData :: [T.Text]
Expand Down

0 comments on commit f0eff24

Please sign in to comment.