packages feed

yi-0.3: Yi/TextCompletion.hs

{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}

-- Copyright (C) 2008 JP Bernardy

module Yi.TextCompletion (
        -- * Word completion
        wordCompleteB,
        resetCompleteB,
        completeWordB,
) where

import Yi.Completion
import Yi.Buffer
import Text.Regex
import Data.Char
import Data.Typeable
import Data.List
import qualified Data.Map as M

import Control.Applicative
import Control.Exception    ( assert )
import Yi.Buffer.Normal
import Yi.Buffer.Region
import Yi.Editor
import Yi.Core

-- ---------------------------------------------------------------------
-- | Word completion
--
-- when doing keyword completion, we need to keep track of the word
-- we're trying to complete.

-- remember the word, if any, we're trying to complete, previous matches
-- we've seen, and the point in the search we are up to.
newtype Completion = Completion (Maybe (String,M.Map String (),Int)) deriving Typeable

instance Initializable Completion where
    initial = Completion Nothing
--
-- | Switch out of completion mode.
--
resetCompleteB :: BufferM ()
resetCompleteB = setDynamicB (Completion Nothing)

--
-- The word-completion BufferM (), down the buffer
--
wordCompleteB :: BufferM ()
wordCompleteB = getDynamicB >>= loop >>= setDynamicB

  where
    --
    -- work out where to start our next search
    --
    loop :: Completion -> BufferM Completion
    loop (Completion (Just (w,fm,n))) = do
            p  <- pointB
            moveTo (n+1)        -- start where we left off
            doloop p (w,fm)
    loop (Completion Nothing) = do
            p  <- pointB
            w <- readRegionB =<< regionOfPartB Word Backward
            rightB  -- start past point
            doloop p (w,M.singleton w ())

    --
    -- actually do the search, and analyse the result
    --
    doloop :: Int -> (String,M.Map String ())
           -> BufferM Completion

    doloop p (w,fm) = do
            m' <- nextWordMatch w
            moveTo p
            j <-regionStart <$> regionOfB Word
            case m' of
                Just (s,i)
                    | j == i                -- seen entire file
                    -> do replaceLeftWith w
                          return (Completion Nothing)

                    | s `M.member` fm         -- already seen
                    -> loop (Completion (Just (w,fm,i)))

                    | otherwise             -- new
                    -> do replaceLeftWith s
                          return (Completion (Just (w,M.insert s () fm,i)))

                Nothing -> loop (Completion (Just (w,fm,(-1)))) -- goto start of file

    --
    -- replace word under cursor with @s@
    --
    replaceLeftWith :: String -> BufferM ()
    replaceLeftWith s = do
        r <- regionOfPartB Word Backward     -- back at start
        replaceRegionB r s
        moveTo (regionStart r + length s)

    --
    -- Return next match, and index of that match (to be used for later searches)
    -- Leaves the cursor at the next word.
    --
    nextWordMatch :: String -> BufferM (Maybe (String,Int))
    nextWordMatch w = do
        let re = ("( |\t|\n|\r|^)"++w)
        let re_c = mkRegex re
        mi   <- regexB re_c
        case mi of
            Nothing -> return Nothing
            Just (i,j) -> do
                c <- readAtB i
                let i' = if i == 0 && isAlphaNum c then 0 else i+1 -- for the space
                moveTo i'
                s <- readUnitB Word
                assert (s /= [] && i /= j) $ return $ Just (s,i')


----------------------------
-- Alternative Word Completion

{-
  'completeWordB' is an alternative to 'wordCompleteB'.
  Currently the main reason for this extra function is that the
  aforementioned is rather buggy, two problems I currently have with
  it is that it occasionally remembers the previous word it was completing
  before and completes that rather than the current one. More seriously
  it occasionally crashes yi by going into an infinite loop.

  In the longer term assuming that 'Yi.CharMove.wordCompleteB' is fixed
  (which I would love) then '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 :: EditorM ()
completeWordB = veryQuickCompleteWord


{-
  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 :: EditorM ()
veryQuickCompleteWord =
  do (curWord, curWords) <- withBuffer0 wordsAndCurrentWord
     let condition :: String -> Bool
         condition x   = (isPrefixOf curWord x) && (x /= curWord)

     preText             <- completeInList curWord condition curWords
     if curWord == ""
        then printMsg "No word to complete"
        else withBuffer0 $ insertN $ drop (length curWord) preText

wordsAndCurrentWord :: BufferM (String, [ String ])
wordsAndCurrentWord =
  do curSize          <- sizeB
     curText          <- readRegionB $ mkRegion 0 curSize
     curWord          <- readRegionB =<< regionOfPartB Word Backward
     let curWords     = words curText
     return (curWord, curWords)

{-
  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.
-}