{-# 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.Applicative import Control.Monad import Data.Binary import Data.Char import Data.Default import Data.Function (on) import Data.List (findIndex) import Data.List.NonEmpty (NonEmpty(..)) import Data.Maybe import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Typeable import Yi.Buffer import Yi.Completion import Yi.Types (YiVariable) import Yi.Editor import Yi.Keymap import qualified Yi.Rope as R import Yi.Utils -- --------------------------------------------------------------------- -- | 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 occurences 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 ()) (mkIsPrefixOf 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. -}