{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Ide.Plugin.Cabal.Completion.Completions (contextToCompleter, getContext, getCabalPrefixInfo) where
import Control.Lens ((^.))
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Trans.Maybe
import Data.Foldable (asum)
import qualified Data.List as List
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Text as T
import Data.Text.Utf16.Rope (Rope)
import qualified Data.Text.Utf16.Rope as Rope
import Development.IDE as D
import Ide.Plugin.Cabal.Completion.Completer.Simple
import Ide.Plugin.Cabal.Completion.Completer.Snippet
import Ide.Plugin.Cabal.Completion.Completer.Types (Completer)
import Ide.Plugin.Cabal.Completion.Data
import Ide.Plugin.Cabal.Completion.Types
import qualified Language.LSP.Protocol.Lens as JL
import qualified Language.LSP.VFS as VFS
import qualified System.FilePath as FP
import System.FilePath (takeBaseName)
contextToCompleter :: Context -> Completer
contextToCompleter :: Context -> Completer
contextToCompleter (StanzaContext
TopLevel, FieldContext
None) =
Completer
snippetCompleter
forall a. Semigroup a => a -> a -> a
<> ( [Text] -> Completer
constantCompleter forall a b. (a -> b) -> a -> b
$
forall k a. Map k a -> [k]
Map.keys (Map Text Completer
cabalVersionKeyword forall a. Semigroup a => a -> a -> a
<> Map Text Completer
cabalKeywords) forall a. [a] -> [a] -> [a]
++ forall k a. Map k a -> [k]
Map.keys Map Text (Map Text Completer)
stanzaKeywordMap
)
contextToCompleter (StanzaContext
TopLevel, KeyWord Text
kw) =
case forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
kw (Map Text Completer
cabalVersionKeyword forall a. Semigroup a => a -> a -> a
<> Map Text Completer
cabalKeywords) of
Maybe Completer
Nothing -> Log -> Completer
errorNoopCompleter (Text -> Log
LogUnknownKeyWordInContextError Text
kw)
Just Completer
l -> Completer
l
contextToCompleter (Stanza Text
s Maybe Text
_, FieldContext
None) =
case forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
s Map Text (Map Text Completer)
stanzaKeywordMap of
Maybe (Map Text Completer)
Nothing -> Log -> Completer
errorNoopCompleter (Text -> Log
LogUnknownStanzaNameInContextError Text
s)
Just Map Text Completer
l -> [Text] -> Completer
constantCompleter forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [k]
Map.keys Map Text Completer
l
contextToCompleter (Stanza Text
s Maybe Text
_, KeyWord Text
kw) =
case forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
s Map Text (Map Text Completer)
stanzaKeywordMap of
Maybe (Map Text Completer)
Nothing -> Log -> Completer
errorNoopCompleter (Text -> Log
LogUnknownStanzaNameInContextError Text
s)
Just Map Text Completer
m -> case forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
kw Map Text Completer
m of
Maybe Completer
Nothing -> Log -> Completer
errorNoopCompleter (Text -> Log
LogUnknownKeyWordInContextError Text
kw)
Just Completer
l -> Completer
l
getContext :: (MonadIO m) => Recorder (WithPriority Log) -> CabalPrefixInfo -> Rope -> MaybeT m Context
getContext :: forall (m :: * -> *).
MonadIO m =>
Recorder (WithPriority Log)
-> CabalPrefixInfo -> Rope -> MaybeT m Context
getContext Recorder (WithPriority Log)
recorder CabalPrefixInfo
prefInfo Rope
ls =
case Maybe [Text]
prevLinesM of
Just [Text]
prevLines -> do
let lvlContext :: StanzaContext
lvlContext =
if CabalPrefixInfo -> Int
completionIndentation CabalPrefixInfo
prefInfo forall a. Eq a => a -> a -> Bool
== Int
0
then StanzaContext
TopLevel
else [Text] -> StanzaContext
currentLevel [Text]
prevLines
case StanzaContext
lvlContext of
StanzaContext
TopLevel -> do
FieldContext
kwContext <- forall (m :: * -> *) a. m (Maybe a) -> MaybeT m a
MaybeT forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a.
CabalPrefixInfo -> [Text] -> Map Text a -> Maybe FieldContext
getKeyWordContext CabalPrefixInfo
prefInfo [Text]
prevLines (Map Text Completer
cabalVersionKeyword forall a. Semigroup a => a -> a -> a
<> Map Text Completer
cabalKeywords)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StanzaContext
TopLevel, FieldContext
kwContext)
Stanza Text
s Maybe Text
n ->
case forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
s Map Text (Map Text Completer)
stanzaKeywordMap of
Maybe (Map Text Completer)
Nothing -> do
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> Maybe Text -> StanzaContext
Stanza Text
s Maybe Text
n, FieldContext
None)
Just Map Text Completer
m -> do
FieldContext
kwContext <- forall (m :: * -> *) a. m (Maybe a) -> MaybeT m a
MaybeT forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a.
CabalPrefixInfo -> [Text] -> Map Text a -> Maybe FieldContext
getKeyWordContext CabalPrefixInfo
prefInfo [Text]
prevLines Map Text Completer
m
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> Maybe Text -> StanzaContext
Stanza Text
s Maybe Text
n, FieldContext
kwContext)
Maybe [Text]
Nothing -> do
forall (m :: * -> *) msg.
(HasCallStack, MonadIO m) =>
Recorder (WithPriority msg) -> Priority -> msg -> m ()
logWith Recorder (WithPriority Log)
recorder Priority
Warning forall a b. (a -> b) -> a -> b
$ Position -> Log
LogFileSplitError Position
pos
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Abort computation"
where
pos :: Position
pos = CabalPrefixInfo -> Position
completionCursorPosition CabalPrefixInfo
prefInfo
prevLinesM :: Maybe [Text]
prevLinesM = Position -> Rope -> Maybe [Text]
splitAtPosition Position
pos Rope
ls
getCabalPrefixInfo :: FilePath -> VFS.PosPrefixInfo -> CabalPrefixInfo
getCabalPrefixInfo :: String -> PosPrefixInfo -> CabalPrefixInfo
getCabalPrefixInfo String
fp PosPrefixInfo
prefixInfo =
CabalPrefixInfo
{ completionPrefix :: Text
completionPrefix = Text
completionPrefix',
isStringNotation :: Maybe Apostrophe
isStringNotation = Char -> Text -> Maybe Apostrophe
mkIsStringNotation Char
separator Text
afterCursorText,
completionCursorPosition :: Position
completionCursorPosition = PosPrefixInfo -> Position
VFS.cursorPos PosPrefixInfo
prefixInfo,
completionRange :: Range
completionRange = Position -> Position -> Range
Range Position
completionStart Position
completionEnd,
completionWorkingDir :: String
completionWorkingDir = String -> String
FP.takeDirectory String
fp,
completionFileName :: Text
completionFileName = String -> Text
T.pack forall a b. (a -> b) -> a -> b
$ String -> String
takeBaseName String
fp
}
where
completionEnd :: Position
completionEnd = PosPrefixInfo -> Position
VFS.cursorPos PosPrefixInfo
prefixInfo
completionStart :: Position
completionStart =
UInt -> UInt -> Position
Position
(Position -> UInt
_line Position
completionEnd)
(Position -> UInt
_character Position
completionEnd forall a. Num a => a -> a -> a
- (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Text -> Int
T.length Text
completionPrefix'))
(Text
beforeCursorText, Text
afterCursorText) = Int -> Text -> (Text, Text)
T.splitAt Int
cursorColumn forall a b. (a -> b) -> a -> b
$ PosPrefixInfo -> Text
VFS.fullLine PosPrefixInfo
prefixInfo
completionPrefix' :: Text
completionPrefix' = (Char -> Bool) -> Text -> Text
T.takeWhileEnd (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
stopConditionChars)) Text
beforeCursorText
separator :: Char
separator =
if forall a. Integral a => a -> Bool
odd forall a b. (a -> b) -> a -> b
$ Text -> Text -> Int
T.count Text
"\"" Text
beforeCursorText
then Char
'\"'
else Char
' '
cursorColumn :: Int
cursorColumn = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ PosPrefixInfo -> Position
VFS.cursorPos PosPrefixInfo
prefixInfo forall s a. s -> Getting a s a -> a
^. forall s a. HasCharacter s a => Lens' s a
JL.character
stopConditionChars :: String
stopConditionChars = Char
separator forall a. a -> [a] -> [a]
: [Char
',', Char
':']
mkIsStringNotation :: Char -> T.Text -> Maybe Apostrophe
mkIsStringNotation :: Char -> Text -> Maybe Apostrophe
mkIsStringNotation Char
'\"' Text
restLine
| Just (Char
'\"', Text
_) <- Text -> Maybe (Char, Text)
T.uncons Text
restLine = forall a. a -> Maybe a
Just Apostrophe
Surrounded
| Bool
otherwise = forall a. a -> Maybe a
Just Apostrophe
LeftSide
mkIsStringNotation Char
_ Text
_ = forall a. Maybe a
Nothing
getKeyWordContext :: CabalPrefixInfo -> [T.Text] -> Map KeyWordName a -> Maybe FieldContext
getKeyWordContext :: forall a.
CabalPrefixInfo -> [Text] -> Map Text a -> Maybe FieldContext
getKeyWordContext CabalPrefixInfo
prefInfo [Text]
ls Map Text a
keywords = do
case Maybe Text
lastNonEmptyLineM of
Maybe Text
Nothing -> forall a. a -> Maybe a
Just FieldContext
None
Just Text
lastLine' -> do
let (Text
whiteSpaces, Text
lastLine) = (Char -> Bool) -> Text -> (Text, Text)
T.span (forall a. Eq a => a -> a -> Bool
== Char
' ') Text
lastLine'
let keywordIndentation :: Int
keywordIndentation = Text -> Int
T.length Text
whiteSpaces
let cursorIndentation :: Int
cursorIndentation = CabalPrefixInfo -> Int
completionIndentation CabalPrefixInfo
prefInfo
if Int
cursorIndentation forall a. Ord a => a -> a -> Bool
> Int
keywordIndentation
then
case forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
List.find (Text -> Text -> Bool
`T.isPrefixOf` Text
lastLine) (forall k a. Map k a -> [k]
Map.keys Map Text a
keywords) of
Maybe Text
Nothing -> forall a. a -> Maybe a
Just FieldContext
None
Just Text
kw -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Text -> FieldContext
KeyWord Text
kw
else forall a. a -> Maybe a
Just FieldContext
None
where
lastNonEmptyLineM :: Maybe T.Text
lastNonEmptyLineM :: Maybe Text
lastNonEmptyLineM = do
(Text
curLine, [Text]
rest) <- forall a. [a] -> Maybe (a, [a])
List.uncons [Text]
ls
let cur :: Text
cur = Text -> Text
stripPartiallyWritten Text
curLine
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
List.find (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
T.stripEnd) forall a b. (a -> b) -> a -> b
$
Text
cur forall a. a -> [a] -> [a]
: [Text]
rest
currentLevel :: [T.Text] -> StanzaContext
currentLevel :: [Text] -> StanzaContext
currentLevel [] = StanzaContext
TopLevel
currentLevel (Text
cur : [Text]
xs)
| Just (Text
s, Maybe Text
n) <- Maybe (Text, Maybe Text)
stanza = Text -> Maybe Text -> StanzaContext
Stanza Text
s Maybe Text
n
| Bool
otherwise = [Text] -> StanzaContext
currentLevel [Text]
xs
where
stanza :: Maybe (Text, Maybe Text)
stanza = forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Text -> Maybe (Text, Maybe Text)
checkStanza (forall k a. Map k a -> [k]
Map.keys Map Text (Map Text Completer)
stanzaKeywordMap)
checkStanza :: StanzaType -> Maybe (StanzaType, Maybe StanzaName)
checkStanza :: Text -> Maybe (Text, Maybe Text)
checkStanza Text
t =
case Text -> Text -> Maybe Text
T.stripPrefix Text
t (Text -> Text
T.strip Text
cur) of
Just Text
n
| Text -> Bool
T.null Text
n -> forall a. a -> Maybe a
Just (Text
t, forall a. Maybe a
Nothing)
| Bool
otherwise -> forall a. a -> Maybe a
Just (Text
t, forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Text -> Text
T.strip Text
n)
Maybe Text
Nothing -> forall a. Maybe a
Nothing
splitAtPosition :: Position -> Rope -> Maybe [T.Text]
splitAtPosition :: Position -> Rope -> Maybe [Text]
splitAtPosition Position
pos Rope
ls = do
(Rope, Rope)
split <- Maybe (Rope, Rope)
splitFile
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ Rope -> [Text]
Rope.lines forall a b. (a -> b) -> a -> b
$ forall a b. (a, b) -> a
fst (Rope, Rope)
split
where
splitFile :: Maybe (Rope, Rope)
splitFile = Position -> Rope -> Maybe (Rope, Rope)
Rope.splitAtPosition Position
ropePos Rope
ls
ropePos :: Position
ropePos =
Rope.Position
{ posLine :: Word
Rope.posLine = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Position
pos forall s a. s -> Getting a s a -> a
^. forall s a. HasLine s a => Lens' s a
JL.line,
posColumn :: Word
Rope.posColumn = forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Position
pos forall s a. s -> Getting a s a -> a
^. forall s a. HasCharacter s a => Lens' s a
JL.character
}
stripPartiallyWritten :: T.Text -> T.Text
stripPartiallyWritten :: Text -> Text
stripPartiallyWritten = (Char -> Bool) -> Text -> Text
T.dropWhileEnd (\Char
y -> (Char
y forall a. Eq a => a -> a -> Bool
/= Char
' ') Bool -> Bool -> Bool
&& (Char
y forall a. Eq a => a -> a -> Bool
/= Char
':'))
completionIndentation :: CabalPrefixInfo -> Int
completionIndentation :: CabalPrefixInfo -> Int
completionIndentation CabalPrefixInfo
prefInfo = forall a b. (Integral a, Num b) => a -> b
fromIntegral (Position
pos forall s a. s -> Getting a s a -> a
^. forall s a. HasCharacter s a => Lens' s a
JL.character) forall a. Num a => a -> a -> a
- (Text -> Int
T.length forall a b. (a -> b) -> a -> b
$ CabalPrefixInfo -> Text
completionPrefix CabalPrefixInfo
prefInfo)
where
pos :: Position
pos = CabalPrefixInfo -> Position
completionCursorPosition CabalPrefixInfo
prefInfo