yi-core-0.19.3: src/Yi/TextCompletion.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK show-extensions #-}
-- |
-- Module : Yi.TextCompletion
-- License : GPL-2
-- Maintainer : yi-devel@googlegroups.com
-- Stability : experimental
-- Portability : portable
--
-- Module providing text completion functions.
module Yi.TextCompletion (
-- * Word completion
wordComplete,
wordComplete',
wordCompleteString,
wordCompleteString',
mkWordComplete,
resetComplete,
completeWordB,
CompletionScope(..)
) where
import Control.Monad (forM)
import Data.Binary (Binary, get, put)
import Data.Char (GeneralCategory (..), generalCategory)
import Data.Default (Default, def)
import Data.Function (on)
import Data.List (findIndex)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe (isJust)
import qualified Data.Text as T (Text, drop, groupBy, head, isPrefixOf, length, null)
import qualified Data.Text.Encoding as E (decodeUtf8, encodeUtf8)
import Data.Typeable (Typeable)
import Yi.Buffer
import Yi.Completion (completeInList, isCasePrefixOf)
import Yi.Editor
import Yi.Keymap (YiM)
import qualified Yi.Rope as R (fromText, toText)
import Yi.Types (YiVariable)
import Yi.Utils (nubSet)
-- ---------------------------------------------------------------------
-- | Word completion
--
-- when doing keyword completion, we need to keep track of the word
-- we're trying to complete.
newtype Completion = Completion
[T.Text] -- the list of all possible things we can complete to.
-- (this seems very inefficient; but we use laziness to
-- our advantage)
deriving (Typeable, Show, Eq)
instance Binary Completion where
put (Completion ts) = put (E.encodeUtf8 <$> ts)
get = Completion . map E.decodeUtf8 <$> get
-- TODO: put this in keymap state instead
instance Default Completion where
def = Completion []
instance YiVariable Completion
-- | Switch out of completion mode.
resetComplete :: EditorM ()
resetComplete = putEditorDyn (Completion [])
-- | Try to complete the current word with occurrences found elsewhere in the
-- editor. Further calls try other options.
mkWordComplete :: YiM T.Text -- ^ Extract function
-> (T.Text -> YiM [T.Text]) -- ^ Source function
-> ([T.Text] -> YiM ()) -- ^ Message function
-> (T.Text -> T.Text -> Bool) -- ^ Predicate matcher
-> YiM T.Text
mkWordComplete extractFn sourceFn msgFn predMatch = do
Completion complList <- withEditor getEditorDyn
case complList of
(x:xs) -> do -- more alternatives, use them.
msgFn (x:xs)
withEditor . putEditorDyn $ Completion xs
return x
[] -> do -- no alternatives, build them.
w <- extractFn
ws <- sourceFn w
let comps = nubSet (filter (matches w) ws) ++ [w]
putEditorDyn $ Completion comps
-- We put 'w' back at the end so we go back to it after seeing
-- all possibilities.
-- to pick the 1st possibility.
mkWordComplete extractFn sourceFn msgFn predMatch
where matches x y = x `predMatch` y && x/=y
wordCompleteString' :: Bool -> YiM T.Text
wordCompleteString' caseSensitive =
mkWordComplete (withCurrentBuffer $
textRegion =<< regionOfPartB unitWord Backward)
(\_ -> withEditor wordsForCompletion)
(\_ -> return ())
(isCasePrefixOf caseSensitive)
where
textRegion = fmap R.toText . readRegionB
wordCompleteString :: YiM T.Text
wordCompleteString = wordCompleteString' True
wordComplete' :: Bool -> YiM ()
wordComplete' caseSensitive = do
x <- R.fromText <$> wordCompleteString' caseSensitive
withEditor $ withCurrentBuffer $
flip replaceRegionB x =<< regionOfPartB unitWord Backward
wordComplete :: YiM ()
wordComplete = wordComplete' True
----------------------------
-- Alternative Word Completion
{-
'completeWordB' is an alternative to 'wordCompleteB'.
'completeWordB' offers a slightly different interface. The user
completes the word using the mini-buffer in the same way a user
completes a buffer or file name when switching buffers or opening a
file. This means that it never guesses and completes only as much as
it can without guessing.
I think there is room for both approaches. The 'wordCompleteB' approach
which just guesses the completion from a list of possible completion
and then re-hitting the key-binding will cause it to guess again.
I think this is very nice for things such as completing a word within
a TeX-buffer. However using the mini-buffer might be nicer when we allow
syntax knowledge to allow completion for example we may complete from
a Hoogle database.
-}
completeWordB :: CompletionScope -> EditorM ()
completeWordB = veryQuickCompleteWord
data CompletionScope = FromCurrentBuffer | FromAllBuffers
deriving (Eq, Show)
{-
This is a very quick and dirty way to complete the current word.
It works in a similar way to the completion of words in the mini-buffer
it uses the message buffer to give simple feedback such as,
"Matches:" and "Complete, but not unique:"
It is by no means perfect but it's also not bad, pretty usable.
-}
veryQuickCompleteWord :: CompletionScope -> EditorM ()
veryQuickCompleteWord scope = do
(curWord, curWords) <- withCurrentBuffer wordsAndCurrentWord
allWords <- fmap concat $ withEveryBuffer $ words' <$> (R.toText <$> elemsB)
let match :: T.Text -> Maybe T.Text
match x = if (curWord `T.isPrefixOf` x) && (x /= curWord)
then Just x
else Nothing
wordsToChooseFrom = if scope == FromCurrentBuffer
then curWords
else allWords
preText <- completeInList curWord match wordsToChooseFrom
if T.null curWord
then printMsg "No word to complete"
else withCurrentBuffer . insertN . R.fromText $ T.drop (T.length curWord) preText
wordsAndCurrentWord :: BufferM (T.Text, [T.Text])
wordsAndCurrentWord =
do curText <- R.toText <$> elemsB
curWord <-
fmap R.toText $ readRegionB =<< regionOfPartB unitWord Backward
return (curWord, words' curText)
wordsForCompletionInBuffer :: BufferM [T.Text]
wordsForCompletionInBuffer = do
let readTextRegion = fmap R.toText . readRegionB
above <- readTextRegion =<< regionOfPartB Document Backward
below <- readTextRegion =<< regionOfPartB Document Forward
return $ reverse (words' above) ++ words' below
wordsForCompletion :: EditorM [T.Text]
wordsForCompletion = do
_ :| bs <- fmap bkey <$> getBufferStack
w0 <- withCurrentBuffer wordsForCompletionInBuffer
contents <- forM bs $ \b -> withGivenBuffer b (R.toText <$> elemsB)
return $ w0 ++ concatMap words' contents
words' :: T.Text -> [T.Text]
words' = filter (isJust . charClass . T.head) . T.groupBy ((==) `on` charClass)
charClass :: Char -> Maybe Int
charClass c = findIndex (generalCategory c `elem`)
[ [ UppercaseLetter, LowercaseLetter, TitlecaseLetter
, ModifierLetter, OtherLetter
, ConnectorPunctuation
, NonSpacingMark, SpacingCombiningMark, EnclosingMark
, DecimalNumber, LetterNumber, OtherNumber
]
, [ MathSymbol, CurrencySymbol, ModifierSymbol, OtherSymbol ]
]
{-
Finally obviously we wish to have a much more sophisticated completeword.
One which spawns a mini-buffer and allows searching in Hoogle databases
or in other files etc.
-}