{-# LANGUAGE CPP #-}
-----------------------------------------------------------------------------
--
-- Module      :  IDE.TextEditor
-- Copyright   :  2007-2011 Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GPL
--
-- Maintainer  :  maintainer@leksah.org
-- Stability   :  provisional
-- Portability :
--
-- |
--
-----------------------------------------------------------------------------

module IDE.TextEditor (

-- Data Types
    EditorBuffer(..)
,   EditorView(..)
,   EditorMark(..)
,   EditorIter(..)
,   EditorTagTable(..)
,   EditorTag(..)

-- Buffer
,   newGtkBuffer
,   newYiBuffer
,   simpleGtkBuffer
,   applyTagByName
,   beginNotUndoableAction
,   beginUserAction
,   canRedo
,   canUndo
,   copyClipboard
,   createMark
,   cutClipboard
,   delete
,   deleteSelection
,   endNotUndoableAction
,   endUserAction
,   getEndIter
,   getInsertMark
,   getInsertIter
,   getIterAtLine
,   getIterAtMark
,   getIterAtOffset
,   getLineCount
,   getModified
,   getSelectionBoundMark
,   getSelectionBounds
,   getSlice
,   getStartIter
,   getTagTable
,   getText
,   hasSelection
,   insert
,   moveMark
,   newView
,   pasteClipboard
,   placeCursor
,   redo
,   removeTagByName
,   selectRange
,   setModified
,   setStyle
,   setText
,   undo

-- View
,   bufferToWindowCoords
,   drawTabs
,   getBuffer
,   getDrawWindow
,   getIterLocation
,   getOverwrite
,   getScrolledWindow
,   grabFocus
,   scrollToMark
,   scrollToIter
,   setFont
,   setIndentWidth
,   setWrapMode
,   setRightMargin
,   setShowLineNumbers
,   setTabWidth

-- Iterator
,   backwardCharC
,   backwardFindCharC
,   backwardWordStartC
,   backwardToLineStartC
,   endsWord
,   forwardCharC
,   forwardCharsC
,   forwardFindCharC
,   forwardToLineEndC
,   forwardWordEndC
,   forwardSearch
,   getChar
,   getCharsInLine
,   getLine
,   getLineOffset
,   getOffset
,   isStart
,   isEnd
,   iterEqual
,   startsLine
,   atEnd
,   atLine
,   atLineOffset
,   atOffset
,   atStart

-- Tag Table
,   newTag
,   lookupTag

-- Tag
,   background
,   underline

-- Events
,   afterFocusIn
,   afterModifiedChanged
,   afterMoveCursor
,   afterToggleOverwrite
,   onButtonPress
,   onButtonRelease
,   onCompletion
,   onKeyPress
,   onKeyRelease
,   onLookupInfo
,   onMotionNotify
,   onLeaveNotify
,   onPopulatePopup
) where

import Prelude hiding(getChar, getLine)
import Data.Char (isAlphaNum, isSymbol)
import Data.Maybe (fromJust, maybeToList)
import Data.IORef
import Control.Monad (when)
import Control.Applicative ((<$>))

import qualified Graphics.UI.Gtk as Gtk hiding(afterToggleOverwrite)
import qualified Graphics.UI.Gtk.SourceView as Gtk
import Graphics.UI.Gtk.Multiline.TextTagTable
import qualified Graphics.UI.Gtk.Multiline.TextBuffer as Gtk
import qualified  Graphics.UI.Gtk.Multiline.TextView as Gtk
import qualified Graphics.UI.Gtk.Scrolling.ScrolledWindow
import Graphics.UI.Gtk.Multiline.TextTag
import Graphics.UI.Gtk.Gdk.Cursor
--import Graphics.Rendering.Pango
import System.Glib.Attributes
import System.Glib.MainLoop
import qualified Graphics.UI.Gtk.Multiline.TextView as Gtk
import qualified Graphics.UI.Gtk.Gdk.Events as GtkOld
import System.Glib.Attributes (AttrOp(..))
import System.GIO.File.ContentType (contentTypeGuess)

#ifdef LEKSAH_WITH_YI
import qualified Yi as Yi hiding(withBuffer)
import qualified Yi.UI.Pango.Control as Yi
import qualified Yi.Keymap.Cua as Yi
import Data.Time (getCurrentTime)
import Control.Monad (unless)
#endif

import IDE.Core.State
import IDE.Utils.GUIUtils(controlIsPressed)
import Paths_leksah (getDataDir)
import System.FilePath ((</>))
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Reader (ask)
import qualified Control.Monad.Reader as Gtk (liftIO)

-- Data types
data EditorBuffer = GtkEditorBuffer Gtk.SourceBuffer
#ifdef LEKSAH_WITH_YI
    | YiEditorBuffer Yi.Buffer
#endif

data EditorView = GtkEditorView Gtk.SourceView
#ifdef LEKSAH_WITH_YI
    | YiEditorView Yi.View
#endif

data EditorMark = GtkEditorMark Gtk.TextMark
#ifdef LEKSAH_WITH_YI
    | YiEditorMark Yi.Mark
#endif

data EditorIter = GtkEditorIter Gtk.TextIter
#ifdef LEKSAH_WITH_YI
    | YiEditorIter Yi.Iter
#endif

data EditorTagTable = GtkEditorTagTable Gtk.TextTagTable
#ifdef LEKSAH_WITH_YI
    | YiEditorTagTable
#endif

data EditorTag = GtkEditorTag Gtk.TextTag
#ifdef LEKSAH_WITH_YI
    | YiEditorTag
#endif


#ifdef LEKSAH_WITH_YI
withYiBuffer' :: Yi.BufferRef -> Yi.BufferM a -> IDEM a
withYiBuffer' b f = liftYi $ Yi.liftEditor $ Yi.withGivenBuffer0 b f

withYiBuffer :: Yi.Buffer -> Yi.BufferM a -> IDEM a
withYiBuffer b f = withYiBuffer' (Yi.fBufRef b) f

mkYiIter' :: Yi.BufferRef -> Yi.Point -> EditorIter
mkYiIter' b p = YiEditorIter $ Yi.Iter b p

mkYiIter :: Yi.Buffer -> Yi.Point -> EditorIter
mkYiIter b p = mkYiIter' (Yi.fBufRef b) p

iterFromYiBuffer' :: Yi.BufferRef -> Yi.BufferM Yi.Point -> IDEM EditorIter
iterFromYiBuffer' b f = mkYiIter' b <$> withYiBuffer' b f

iterFromYiBuffer :: Yi.Buffer -> Yi.BufferM Yi.Point -> IDEM EditorIter
iterFromYiBuffer b f = iterFromYiBuffer' (Yi.fBufRef b) f
#endif

-- Buffer
newGtkBuffer :: Maybe FilePath -> String -> IDEM EditorBuffer
newGtkBuffer mbFilename contents = liftIO $ do
    lm      <- Gtk.sourceLanguageManagerNew
    dataDir <- getDataDir
    oldPath <- Gtk.sourceLanguageManagerGetSearchPath lm
    Gtk.sourceLanguageManagerSetSearchPath lm (Just $ (dataDir </> "language-specs") : oldPath)
    mbLang  <- case mbFilename of
        Just filename -> do
            guess <- contentTypeGuess filename contents (length contents)
            Gtk.sourceLanguageManagerGuessLanguage lm (Just filename) $
                case guess of
                    (True, _)  -> Nothing
                    (False, t) -> Just t
        Nothing       -> Gtk.sourceLanguageManagerGuessLanguage lm Nothing (Just "text/x-haskell")
    buffer <- case mbLang of
        Just sLang -> Gtk.sourceBufferNewWithLanguage sLang
        Nothing -> Gtk.sourceBufferNew Nothing
    Gtk.sourceBufferSetMaxUndoLevels buffer (-1)
    Gtk.sourceBufferBeginNotUndoableAction buffer
    Gtk.textBufferSetText buffer contents
    Gtk.sourceBufferEndNotUndoableAction buffer
    return $ GtkEditorBuffer buffer

-- Buffer
simpleGtkBuffer :: String -> IDEM EditorBuffer
simpleGtkBuffer contents = liftIO $ do
    buffer <- Gtk.sourceBufferNew Nothing
    Gtk.textBufferSetText buffer contents
    return $ GtkEditorBuffer buffer


newYiBuffer :: Maybe FilePath -> String -> IDEM EditorBuffer
#ifdef LEKSAH_WITH_YI
newYiBuffer mbFilename contents = liftYiControl $ do
    let (filename, id) = case mbFilename of
                            Just fn -> (fn, Right fn)
                            Nothing -> ("Unknown.hs", Left "*leksah*")
    buffer <- Yi.newBuffer id contents
    Yi.setBufferMode filename buffer
    return $ YiEditorBuffer buffer
#else
newYiBuffer = newGtkBuffer
#endif

applyTagByName :: EditorBuffer
                  -> String
                  -> EditorIter
                  -> EditorIter
                  -> IDEM ()
applyTagByName (GtkEditorBuffer sb) name (GtkEditorIter first) (GtkEditorIter last) = liftIO $
    Gtk.textBufferApplyTagByName sb name first last
#ifdef LEKSAH_WITH_YI
applyTagByName (YiEditorBuffer fb) name (YiEditorIter first) (YiEditorIter last) = return () -- TODO
applyTagByName _ _ _ _ = liftIO $ fail "Mismatching TextEditor types in createMark"
#endif

beginNotUndoableAction :: EditorBuffer -> IDEM ()
beginNotUndoableAction (GtkEditorBuffer sb) = liftIO $ Gtk.sourceBufferBeginNotUndoableAction sb
#ifdef LEKSAH_WITH_YI
beginNotUndoableAction (YiEditorBuffer fb) = return () -- TODO
#endif

beginUserAction :: EditorBuffer -> IDEM ()
beginUserAction (GtkEditorBuffer sb) = liftIO $ Gtk.textBufferBeginUserAction sb
#ifdef LEKSAH_WITH_YI
beginUserAction (YiEditorBuffer fb) = return () -- TODO
#endif

canRedo :: EditorBuffer -> IDEM Bool
canRedo (GtkEditorBuffer sb) = liftIO $ Gtk.sourceBufferGetCanRedo sb
#ifdef LEKSAH_WITH_YI
canRedo (YiEditorBuffer fb) = return True -- TODO
#endif

canUndo :: EditorBuffer -> IDEM Bool
canUndo (GtkEditorBuffer sb) = liftIO $ Gtk.sourceBufferGetCanUndo sb
#ifdef LEKSAH_WITH_YI
canUndo (YiEditorBuffer fb) = return True -- TODO
#endif

copyClipboard :: EditorBuffer -> Gtk.Clipboard -> IDEM ()
copyClipboard (GtkEditorBuffer sb) clipboard = liftIO $ Gtk.textBufferCopyClipboard sb clipboard
#ifdef LEKSAH_WITH_YI
copyClipboard (YiEditorBuffer fb) _ = liftYi $ Yi.liftEditor $ Yi.copy
#endif

createMark :: EditorBuffer
              -> EditorIter
              -> Bool
              -> IDEM EditorMark
createMark (GtkEditorBuffer sb) (GtkEditorIter i) leftGravity = liftIO $
    GtkEditorMark <$> Gtk.textBufferCreateMark sb Nothing i leftGravity
#ifdef LEKSAH_WITH_YI
createMark (YiEditorBuffer b) (YiEditorIter (Yi.Iter _ p)) leftGravity = withYiBuffer b $
    YiEditorMark <$> Yi.newMarkB (Yi.MarkValue p (if leftGravity then Yi.Backward else Yi.Forward))
createMark _ _ _ = liftIO $ fail "Mismatching TextEditor types in createMark"
#endif

cutClipboard :: EditorBuffer -> Gtk.Clipboard -> Bool -> IDEM ()
cutClipboard (GtkEditorBuffer sb) clipboard defaultEditable = liftIO $ Gtk.textBufferCutClipboard sb clipboard defaultEditable
#ifdef LEKSAH_WITH_YI
cutClipboard (YiEditorBuffer fb) clipboard defaultEditable = liftYi $ Yi.liftEditor $ Yi.cut
#endif

delete :: EditorBuffer -> EditorIter -> EditorIter -> IDEM ()
delete (GtkEditorBuffer sb) (GtkEditorIter first) (GtkEditorIter last) = liftIO $
    Gtk.textBufferDelete sb first last
#ifdef LEKSAH_WITH_YI
delete (YiEditorBuffer b) (YiEditorIter (Yi.Iter _ first)) (YiEditorIter (Yi.Iter _ last)) =
    withYiBuffer b $ Yi.deleteRegionB $ Yi.mkRegion first last
delete _ _ _ = liftIO $ fail "Mismatching TextEditor types in delete"
#endif

deleteSelection :: EditorBuffer -> Bool -> Bool -> IDEM ()
deleteSelection (GtkEditorBuffer sb) interactive defaultEditable = liftIO $
    Gtk.textBufferDeleteSelection sb interactive defaultEditable >> return ()
#ifdef LEKSAH_WITH_YI
deleteSelection (YiEditorBuffer b) interactive defaultEditable = withYiBuffer b $ do
    region <- Yi.getRawestSelectRegionB
    Yi.deleteRegionB region -- TODO support flags
#endif

endNotUndoableAction :: EditorBuffer -> IDEM ()
endNotUndoableAction (GtkEditorBuffer sb) = liftIO $ Gtk.sourceBufferEndNotUndoableAction sb
#ifdef LEKSAH_WITH_YI
endNotUndoableAction (YiEditorBuffer fb) = return () -- TODO
#endif

endUserAction :: EditorBuffer -> IDEM ()
endUserAction (GtkEditorBuffer sb) = liftIO $ Gtk.textBufferEndUserAction sb
#ifdef LEKSAH_WITH_YI
endUserAction (YiEditorBuffer fb) = return () -- TODO
#endif

getEndIter :: EditorBuffer -> IDEM EditorIter
getEndIter (GtkEditorBuffer sb) = liftIO $ GtkEditorIter <$> Gtk.textBufferGetEndIter sb
#ifdef LEKSAH_WITH_YI
getEndIter (YiEditorBuffer b) = iterFromYiBuffer b Yi.sizeB
#endif

getInsertMark :: EditorBuffer -> IDEM EditorMark
getInsertMark (GtkEditorBuffer sb) = liftIO $ fmap GtkEditorMark $ Gtk.textBufferGetInsert sb
#ifdef LEKSAH_WITH_YI
getInsertMark (YiEditorBuffer b) = YiEditorMark <$> (withYiBuffer b $ Yi.insMark <$> Yi.askMarks)
#endif

getIterAtLine :: EditorBuffer -> Int -> IDEM EditorIter
getIterAtLine (GtkEditorBuffer sb) line = liftIO $ GtkEditorIter <$> Gtk.textBufferGetIterAtLine sb line
#ifdef LEKSAH_WITH_YI
getIterAtLine (YiEditorBuffer b) line = iterFromYiBuffer b $ Yi.pointOfLineColB line 1
#endif

getIterAtMark :: EditorBuffer -> EditorMark -> IDEM EditorIter
getIterAtMark (GtkEditorBuffer sb) (GtkEditorMark m) = liftIO $ GtkEditorIter <$> Gtk.textBufferGetIterAtMark sb m
#ifdef LEKSAH_WITH_YI
getIterAtMark (YiEditorBuffer b) (YiEditorMark m) = iterFromYiBuffer b $ Yi.getMarkPointB m
getIterAtMark _ _ = liftIO $ fail "Mismatching TextEditor types in getIterAtMark"
#endif

getIterAtOffset :: EditorBuffer -> Int -> IDEM EditorIter
getIterAtOffset (GtkEditorBuffer sb) offset = liftIO $ GtkEditorIter <$> Gtk.textBufferGetIterAtOffset sb offset
#ifdef LEKSAH_WITH_YI
getIterAtOffset (YiEditorBuffer b) offset = return $ mkYiIter b $ Yi.Point offset
#endif

getLineCount :: EditorBuffer -> IDEM Int
getLineCount (GtkEditorBuffer sb) = liftIO $ Gtk.textBufferGetLineCount sb
#ifdef LEKSAH_WITH_YI
getLineCount (YiEditorBuffer b) = withYiBuffer b $ Yi.sizeB >>= Yi.lineOf
#endif

getModified :: EditorBuffer -> IDEM Bool
getModified (GtkEditorBuffer sb) = liftIO $ Gtk.textBufferGetModified sb
#ifdef LEKSAH_WITH_YI
getModified (YiEditorBuffer b) = not <$> (withYiBuffer b $ Yi.gets Yi.isUnchangedBuffer)
#endif

getSelectionBoundMark :: EditorBuffer -> IDEM EditorMark
getSelectionBoundMark (GtkEditorBuffer sb) = liftIO $ fmap GtkEditorMark $ Gtk.textBufferGetSelectionBound sb
#ifdef LEKSAH_WITH_YI
getSelectionBoundMark (YiEditorBuffer b) = YiEditorMark . Yi.selMark <$> (withYiBuffer b $ Yi.askMarks)
#endif

getSelectionBounds :: EditorBuffer -> IDEM (EditorIter, EditorIter)
getSelectionBounds (GtkEditorBuffer sb) = liftIO $ do
    (first, last) <- Gtk.textBufferGetSelectionBounds sb
    return (GtkEditorIter first, GtkEditorIter last)
#ifdef LEKSAH_WITH_YI
getSelectionBounds (YiEditorBuffer b) = withYiBuffer b $ do
    region <- Yi.getRawestSelectRegionB
    return (mkYiIter b (Yi.regionStart region),
            mkYiIter b (Yi.regionEnd region))
#endif

getInsertIter :: EditorBuffer -> IDEM EditorIter
getInsertIter (GtkEditorBuffer sb) = liftIO $ do
    insertMark <- Gtk.textBufferGetInsert sb
    insertIter <- Gtk.textBufferGetIterAtMark sb insertMark
    return (GtkEditorIter insertIter)
#ifdef LEKSAH_WITH_YI
getInsertIter (YiEditorBuffer b) = withYiBuffer b $ do
    insertMark <- Yi.insMark <$> Yi.askMarks
    mkYiIter b <$> Yi.getMarkPointB insertMark
#endif


getSlice :: EditorBuffer
            -> EditorIter
            -> EditorIter
            -> Bool
            -> IDEM String
getSlice (GtkEditorBuffer sb) (GtkEditorIter first) (GtkEditorIter last) includeHidenChars = liftIO $
    Gtk.textBufferGetSlice sb first last includeHidenChars
#ifdef LEKSAH_WITH_YI
getSlice (YiEditorBuffer b) (YiEditorIter first) (YiEditorIter last) includeHidenChars = liftYiControl $
    Yi.getText b first last
getSlice _ _ _ _ = liftIO $ fail "Mismatching TextEditor types in getSlice"
#endif

getStartIter :: EditorBuffer -> IDEM EditorIter
getStartIter (GtkEditorBuffer sb) = liftIO $ GtkEditorIter <$> Gtk.textBufferGetStartIter sb
#ifdef LEKSAH_WITH_YI
getStartIter (YiEditorBuffer b) = return $ mkYiIter b $ Yi.Point 0
#endif

getTagTable :: EditorBuffer -> IDEM EditorTagTable
getTagTable (GtkEditorBuffer sb) = liftIO $ fmap GtkEditorTagTable $ Gtk.textBufferGetTagTable sb
#ifdef LEKSAH_WITH_YI
getTagTable (YiEditorBuffer b) = return YiEditorTagTable -- TODO
#endif

getText :: EditorBuffer
           -> EditorIter
           -> EditorIter
           -> Bool
           -> IDEM String
getText (GtkEditorBuffer sb) (GtkEditorIter first) (GtkEditorIter last) includeHidenChars = liftIO $
    Gtk.textBufferGetText sb first last includeHidenChars
#ifdef LEKSAH_WITH_YI
getText (YiEditorBuffer b) (YiEditorIter first) (YiEditorIter last) includeHidenChars = liftYiControl $
    Yi.getText b first last
getText _ _ _ _ = liftIO $ fail "Mismatching TextEditor types in getText"
#endif

hasSelection :: EditorBuffer -> IDEM Bool
hasSelection (GtkEditorBuffer sb) = liftIO $ Gtk.textBufferHasSelection sb
#ifdef LEKSAH_WITH_YI
hasSelection (YiEditorBuffer b) = withYiBuffer b $ do
    region <- Yi.getRawestSelectRegionB
    return $ not $ Yi.regionIsEmpty region
#endif

insert :: EditorBuffer -> EditorIter -> String -> IDEM ()
insert (GtkEditorBuffer sb) (GtkEditorIter i) text = liftIO $ Gtk.textBufferInsert sb i text
#ifdef LEKSAH_WITH_YI
insert (YiEditorBuffer b) (YiEditorIter (Yi.Iter _ p)) text = withYiBuffer b $ Yi.insertNAt text p
insert _ _ _ = liftIO $ fail "Mismatching TextEditor types in insert"
#endif

moveMark :: EditorBuffer -> EditorMark -> EditorIter -> IDEM ()
moveMark (GtkEditorBuffer sb) (GtkEditorMark m) (GtkEditorIter i) = liftIO $ Gtk.textBufferMoveMark sb m i
#ifdef LEKSAH_WITH_YI
moveMark (YiEditorBuffer b) (YiEditorMark m) (YiEditorIter (Yi.Iter _ p)) = withYiBuffer b $ Yi.setMarkPointB m p
moveMark _ _ _ = liftIO $ fail "Mismatching TextEditor types in moveMark"
#endif

newView :: EditorBuffer -> Maybe String -> IDEM EditorView
newView (GtkEditorBuffer sb) mbFontString = do
    prefs <- readIDE prefs
    fd <- fontDescription mbFontString
    liftIO $ do
        sv <- Gtk.sourceViewNewWithBuffer sb
        Gtk.sourceViewSetHighlightCurrentLine sv True
        Gtk.sourceViewSetInsertSpacesInsteadOfTabs sv True
        Gtk.sourceViewSetIndentOnTab sv True
        Gtk.sourceViewSetAutoIndent sv True
        Gtk.sourceViewSetSmartHomeEnd sv Gtk.SourceSmartHomeEndBefore
        if wrapLines prefs
            then Gtk.textViewSetWrapMode sv Gtk.WrapWord
            else Gtk.textViewSetWrapMode sv Gtk.WrapNone
        sw <- Gtk.scrolledWindowNew Nothing Nothing
        Gtk.containerAdd sw sv
        Gtk.widgetModifyFont sv (Just fd)
        return (GtkEditorView sv)
#ifdef LEKSAH_WITH_YI
newView (YiEditorBuffer b) mbFontString = do
    fd <- fontDescription mbFontString
    liftYiControl $ fmap YiEditorView $ Yi.newView b fd
#endif

pasteClipboard :: EditorBuffer
                  -> Gtk.Clipboard
                  -> EditorIter
                  -> Bool
                  -> IDEM ()
pasteClipboard (GtkEditorBuffer sb) clipboard (GtkEditorIter i) defaultEditable = liftIO $
    Gtk.textBufferPasteClipboard sb clipboard i defaultEditable
#ifdef LEKSAH_WITH_YI
pasteClipboard (YiEditorBuffer b) clipboard (YiEditorIter (Yi.Iter _ p)) defaultEditable = liftYi $ Yi.liftEditor $ Yi.paste
pasteClipboard _ _ _ _ = liftIO $ fail "Mismatching TextEditor types in pasteClipboard"
#endif

placeCursor :: EditorBuffer -> EditorIter -> IDEM ()
placeCursor (GtkEditorBuffer sb) (GtkEditorIter i) = liftIO $ Gtk.textBufferPlaceCursor sb i
#ifdef LEKSAH_WITH_YI
placeCursor (YiEditorBuffer b) (YiEditorIter (Yi.Iter _ p)) = withYiBuffer b $ Yi.moveTo p
placeCursor _ _ = liftIO $ fail "Mismatching TextEditor types in placeCursor"
#endif

redo :: EditorBuffer -> IDEM ()
redo (GtkEditorBuffer sb) = liftIO $ Gtk.sourceBufferRedo sb
#ifdef LEKSAH_WITH_YI
redo (YiEditorBuffer b) = withYiBuffer b Yi.redoB
#endif

removeTagByName :: EditorBuffer
                   -> String
                   -> EditorIter
                   -> EditorIter
                   -> IDEM ()
removeTagByName (GtkEditorBuffer sb) name (GtkEditorIter first) (GtkEditorIter last) = liftIO $
    Gtk.textBufferRemoveTagByName sb name first last
#ifdef LEKSAH_WITH_YI
removeTagByName (YiEditorBuffer b) name (YiEditorIter (Yi.Iter _ first)) (YiEditorIter (Yi.Iter _ last)) = return () -- TODO
removeTagByName _ _ _ _ = liftIO $ fail "Mismatching TextEditor types in removeTagByName"
#endif

selectRange :: EditorBuffer -> EditorIter -> EditorIter -> IDEM ()
selectRange (GtkEditorBuffer sb) (GtkEditorIter first) (GtkEditorIter last) = liftIO $
    Gtk.textBufferSelectRange sb first last
#ifdef LEKSAH_WITH_YI
selectRange (YiEditorBuffer b) (YiEditorIter (Yi.Iter _ first)) (YiEditorIter (Yi.Iter _ last)) = withYiBuffer b $
    Yi.setSelectRegionB $ Yi.mkRegion first last
selectRange _ _ _ = liftIO $ fail "Mismatching TextEditor types in selectRange"
#endif

setModified :: EditorBuffer -> Bool -> IDEM ()
setModified (GtkEditorBuffer sb) modified = liftIO $ Gtk.textBufferSetModified sb modified >> return ()
#ifdef LEKSAH_WITH_YI
setModified (YiEditorBuffer b) modified = unless modified $ do
    now <- liftIO $ getCurrentTime
    withYiBuffer b $ Yi.markSavedB now
#endif

setStyle :: EditorBuffer -> Maybe String -> IDEM ()
setStyle (GtkEditorBuffer sb) mbStyle = liftIO $ do
    case mbStyle of
        Nothing  -> return ()
        Just str -> do
            styleManager <- Gtk.sourceStyleSchemeManagerNew
            ids <- Gtk.sourceStyleSchemeManagerGetSchemeIds styleManager
            when (elem str ids) $ do
                scheme <- Gtk.sourceStyleSchemeManagerGetScheme styleManager str
#if MIN_VERSION_gtksourceview2(0,12,0)
                Gtk.sourceBufferSetStyleScheme sb (Just scheme)
#else
                Gtk.sourceBufferSetStyleScheme sb scheme
#endif

#ifdef LEKSAH_WITH_YI
setStyle (YiEditorBuffer b) mbStyle = return () -- TODO
#endif

setText :: EditorBuffer -> String -> IDEM ()
setText (GtkEditorBuffer sb) text = liftIO $ Gtk.textBufferSetText sb text
#ifdef LEKSAH_WITH_YI
setText (YiEditorBuffer b) text = liftYiControl $ Yi.setText b text
#endif

undo :: EditorBuffer -> IDEM ()
undo (GtkEditorBuffer sb) = liftIO $ Gtk.sourceBufferUndo sb
#ifdef LEKSAH_WITH_YI
undo (YiEditorBuffer b) =  withYiBuffer b Yi.undoB
#endif

-- View
bufferToWindowCoords :: EditorView -> (Int, Int) -> IDEM (Int, Int)
bufferToWindowCoords (GtkEditorView sv) point = liftIO $ Gtk.textViewBufferToWindowCoords sv Gtk.TextWindowWidget point
#ifdef LEKSAH_WITH_YI
bufferToWindowCoords (YiEditorView v) point = return point -- TODO
#endif

drawTabs :: EditorView -> IDEM ()
drawTabs (GtkEditorView sv) = liftIO $ Gtk.sourceViewSetDrawSpaces sv Gtk.SourceDrawSpacesTab
#ifdef LEKSAH_WITH_YI
drawTabs (YiEditorView _) = return () -- TODO
#endif

getBuffer :: EditorView -> IDEM EditorBuffer
getBuffer (GtkEditorView sv) = liftIO $ fmap (GtkEditorBuffer . Gtk.castToSourceBuffer) $ sv `Gtk.get` Gtk.textViewBuffer
#ifdef LEKSAH_WITH_YI
getBuffer (YiEditorView v) = return $ YiEditorBuffer $ Yi.getBuffer v
#endif

getDrawWindow :: EditorView -> IDEM Gtk.DrawWindow
getDrawWindow (GtkEditorView sv) = liftIO $ Gtk.widgetGetDrawWindow sv
#ifdef LEKSAH_WITH_YI
getDrawWindow (YiEditorView v) = liftIO $ Gtk.widgetGetDrawWindow (Yi.drawArea v)
#endif

getIterLocation :: EditorView -> EditorIter -> IDEM Gtk.Rectangle
getIterLocation (GtkEditorView sv) (GtkEditorIter i) = liftIO $ Gtk.textViewGetIterLocation sv i
#ifdef LEKSAH_WITH_YI
getIterLocation (YiEditorView v) (YiEditorIter i) = return $ Gtk.Rectangle 0 0 0 0 -- TODO
getIterLocation _ _ = liftIO $ fail "Mismatching TextEditor types in getIterLocation"
#endif

getOverwrite :: EditorView -> IDEM Bool
getOverwrite (GtkEditorView sv) = liftIO $ Gtk.textViewGetOverwrite sv
#ifdef LEKSAH_WITH_YI
getOverwrite (YiEditorView Yi.View{Yi.viewFBufRef = b}) = withYiBuffer' b $ not <$> Yi.getA Yi.insertingA
#endif

getScrolledWindow :: EditorView -> IDEM Gtk.ScrolledWindow
getScrolledWindow (GtkEditorView sv) = liftIO $ fmap (Gtk.castToScrolledWindow . fromJust) $ Gtk.widgetGetParent sv
#ifdef LEKSAH_WITH_YI
getScrolledWindow (YiEditorView v) = return $ Yi.scrollWin v
#endif

grabFocus :: EditorView -> IDEM ()
grabFocus (GtkEditorView sv) = liftIO $ Gtk.widgetGrabFocus sv
#ifdef LEKSAH_WITH_YI
grabFocus (YiEditorView Yi.View{Yi.drawArea = da}) = liftIO $ Gtk.widgetGrabFocus da
#endif

scrollToMark :: EditorView
                -> EditorMark
                -> Double
                -> Maybe (Double, Double)
                -> IDEM ()
scrollToMark (GtkEditorView sv) (GtkEditorMark m) withMargin mbAlign = liftIO $ Gtk.textViewScrollToMark sv m withMargin mbAlign
#ifdef LEKSAH_WITH_YI
scrollToMark (YiEditorView v) (YiEditorMark m) withMargin mbAlign = return () -- TODO
scrollToMark _ _ _ _ = liftIO $ fail "Mismatching TextEditor types in scrollToMark"
#endif

scrollToIter :: EditorView
                -> EditorIter
                -> Double
                -> Maybe (Double, Double)
                -> IDEM ()
scrollToIter (GtkEditorView sv) (GtkEditorIter i) withMargin mbAlign = liftIO $ Gtk.textViewScrollToIter sv i withMargin mbAlign >> return ()
#ifdef LEKSAH_WITH_YI
scrollToIter (YiEditorView v) (YiEditorIter i) withMargin mbAlign = return () -- TODO
scrollToIter _ _ _ _ = liftIO $ fail "Mismatching TextEditor types in scrollToIter"
#endif

fontDescription :: Maybe String -> IDEM Gtk.FontDescription
fontDescription mbFontString = liftIO $ do
    case mbFontString of
        Just str -> do
            Gtk.fontDescriptionFromString str
        Nothing -> do
            f <- Gtk.fontDescriptionNew
            Gtk.fontDescriptionSetFamily f "Monospace"
            return f

setFont :: EditorView -> Maybe String -> IDEM ()
setFont (GtkEditorView sv) mbFontString = do
    fd <- fontDescription mbFontString
    liftIO $ Gtk.widgetModifyFont sv (Just fd)
#ifdef LEKSAH_WITH_YI
setFont (YiEditorView v) mbFontString = do
    fd <- fontDescription mbFontString
    liftIO $ Gtk.layoutSetFontDescription (Yi.layout v) (Just fd)
#endif

setIndentWidth :: EditorView -> Int -> IDEM ()
setIndentWidth (GtkEditorView sv) width = liftIO $ Gtk.sourceViewSetIndentWidth sv width
#ifdef LEKSAH_WITH_YI
setIndentWidth (YiEditorView Yi.View{Yi.viewFBufRef = b}) width =
    withYiBuffer' b $ Yi.modifyMode $
        \ (mode@Yi.Mode{Yi.modeIndentSettings = mis}) ->
            mode{Yi.modeIndentSettings = mis{Yi.shiftWidth = width}}
#endif

setWrapMode :: EditorView -> Bool-> IDEM ()
setWrapMode ev@(GtkEditorView sv) wrapLines = do
    sw <- getScrolledWindow ev
    if wrapLines
        then liftIO $ do
            Gtk.textViewSetWrapMode sv Gtk.WrapWord
            Gtk.scrolledWindowSetPolicy sw Gtk.PolicyNever Gtk.PolicyAutomatic
        else liftIO $ do
            Gtk.textViewSetWrapMode sv Gtk.WrapNone
            Gtk.scrolledWindowSetPolicy sw Gtk.PolicyAutomatic Gtk.PolicyAutomatic
#ifdef LEKSAH_WITH_YI
setWrapMode (YiEditorView Yi.View{Yi.viewFBufRef = b}) width = return ()
#endif


setRightMargin :: EditorView -> Maybe Int -> IDEM ()
setRightMargin (GtkEditorView sv) mbRightMargin = liftIO $ do
    case mbRightMargin of
        Just n -> do
            Gtk.sourceViewSetShowRightMargin sv True
            Gtk.sourceViewSetRightMarginPosition sv (fromIntegral n)
        Nothing -> Gtk.sourceViewSetShowRightMargin sv False
#ifdef LEKSAH_WITH_YI
setRightMargin (YiEditorView v) mbRightMargin = return () -- TODO
#endif

setShowLineNumbers :: EditorView -> Bool -> IDEM ()
setShowLineNumbers (GtkEditorView sv) show = liftIO $ Gtk.sourceViewSetShowLineNumbers sv show
#ifdef LEKSAH_WITH_YI
setShowLineNumbers (YiEditorView v) show = return () -- TODO
#endif

setTabWidth :: EditorView -> Int -> IDEM ()
setTabWidth (GtkEditorView sv) width = liftIO $ Gtk.sourceViewSetTabWidth sv width
#ifdef LEKSAH_WITH_YI
setTabWidth (YiEditorView Yi.View{Yi.viewFBufRef = b}) width =
    withYiBuffer' b $ Yi.modifyMode $
        \ (mode@Yi.Mode{Yi.modeIndentSettings = mis}) ->
            mode{Yi.modeIndentSettings = mis{Yi.tabSize = width}}
#endif

-- Iterator
transformGtkIter :: Gtk.TextIter -> (Gtk.TextIter -> IO a) -> IDEM EditorIter
transformGtkIter i f = do
    new <- liftIO $ Gtk.textIterCopy i
    liftIO $ f new
    return $ GtkEditorIter new

transformGtkIterMaybe :: Gtk.TextIter -> (Gtk.TextIter -> IO Bool) -> IDEM (Maybe EditorIter)
transformGtkIterMaybe i f = do
    new <- liftIO $ Gtk.textIterCopy i
    found <- liftIO $ f new
    return $ if found
        then Just $ GtkEditorIter new
        else Nothing

#ifdef LEKSAH_WITH_YI
withYiIter :: Yi.Iter -> Yi.BufferM a -> IDEM a
withYiIter (Yi.Iter b p) f = withYiBuffer' b $ do
    Yi.savingPointB $ do
        Yi.moveTo p
        f

transformYiIter' :: Yi.Iter -> Yi.BufferM Yi.Point -> IDEM EditorIter
transformYiIter' i f = mkYiIter' (Yi.iterFBufRef i) <$> withYiIter i f

transformYiIter :: Yi.Iter -> Yi.BufferM a -> IDEM EditorIter
transformYiIter i f = transformYiIter' i (f >> Yi.pointB)

tryTransformYiIter' :: Yi.Iter -> Yi.BufferM Yi.Point -> IDEM (Maybe EditorIter)
tryTransformYiIter' i@(Yi.Iter b p) f = withYiIter i $ do
    newPoint <- f
    if p == newPoint
        then return Nothing
        else return . Just $ mkYiIter' b newPoint

tryTransformYiIter :: Yi.Iter -> Yi.BufferM a -> IDEM (Maybe EditorIter)
tryTransformYiIter i f = tryTransformYiIter' i (f >> Yi.pointB)
#endif

backwardCharC :: EditorIter -> IDEM EditorIter
backwardCharC (GtkEditorIter i) = transformGtkIter i Gtk.textIterBackwardChar
#ifdef LEKSAH_WITH_YI
backwardCharC (YiEditorIter i) = transformYiIter' i Yi.prevPointB
#endif

backwardFindCharC :: EditorIter
                    -> (Char -> Bool)
                    -> Maybe EditorIter
                    -> IDEM (Maybe EditorIter)
backwardFindCharC (GtkEditorIter i) pred mbLimit = transformGtkIterMaybe i $ \x ->
    Gtk.textIterBackwardFindChar x pred $
        case mbLimit of
            Just (GtkEditorIter limit) -> Just limit
            Nothing                    -> Nothing
#ifdef LEKSAH_WITH_YI
            _                          -> fail "Mismatching TextEditor types in backwardFindChar"
#endif

#ifdef LEKSAH_WITH_YI
backwardFindCharC (YiEditorIter i) pred mbLimit = tryTransformYiIter i $
    Yi.doUntilB_ (pred <$> Yi.readB) Yi.leftB
#endif

backwardWordStartC :: EditorIter -> IDEM (Maybe EditorIter)
backwardWordStartC (GtkEditorIter i) = transformGtkIterMaybe i Gtk.textIterBackwardWordStart
#ifdef LEKSAH_WITH_YI
backwardWordStartC (YiEditorIter i@(Yi.Iter b p)) = withYiIter i $ do
    Yi.prevWordB
    newPoint <- Yi.pointB
    if p == newPoint
        then return Nothing
        else return . Just $ mkYiIter' b newPoint
#endif

backwardToLineStartC :: EditorIter -> IDEM EditorIter
backwardToLineStartC (GtkEditorIter i) = transformGtkIter i $ \new -> do
    n <- Gtk.textIterGetLineOffset new
    Gtk.textIterBackwardChars new n
    return ()
#ifdef LEKSAH_WITH_YI
backwardToLineStartC (YiEditorIter i) = transformYiIter i Yi.moveToSol
#endif

endsWord :: EditorIter -> IDEM Bool
endsWord (GtkEditorIter i) = liftIO $ Gtk.textIterEndsWord i
#ifdef LEKSAH_WITH_YI
endsWord (YiEditorIter i) = withYiIter i $ do
    Yi.atBoundaryB Yi.unitWord Yi.Forward
#endif

forwardCharC :: EditorIter -> IDEM EditorIter
forwardCharC (GtkEditorIter i) = transformGtkIter i Gtk.textIterForwardChar
#ifdef LEKSAH_WITH_YI
forwardCharC (YiEditorIter i) = transformYiIter' i Yi.nextPointB
#endif

forwardCharsC :: EditorIter -> Int -> IDEM EditorIter
forwardCharsC (GtkEditorIter i) n = transformGtkIter i $ flip Gtk.textIterForwardChars n
#ifdef LEKSAH_WITH_YI
forwardCharsC (YiEditorIter i) n = transformYiIter i $ Yi.rightN n
#endif

forwardFindCharC :: EditorIter
                   -> (Char -> Bool)
                   -> Maybe EditorIter
                   -> IDEM (Maybe EditorIter)
forwardFindCharC (GtkEditorIter i) pred mbLimit = transformGtkIterMaybe i $ \x ->
    Gtk.textIterForwardFindChar x pred $
        case mbLimit of
            Just (GtkEditorIter limit) -> Just limit
            Nothing                    -> Nothing
#ifdef LEKSAH_WITH_YI
            _                          -> fail "Mismatching TextEditor types in forwardFindChar"
#endif

#ifdef LEKSAH_WITH_YI
forwardFindCharC (YiEditorIter i) pred mbLimit = tryTransformYiIter i $
    Yi.doUntilB_ (pred <$> Yi.readB) Yi.rightB
#endif

forwardSearch :: EditorIter
                 -> String
                 -> [Gtk.TextSearchFlags]
                 -> Maybe EditorIter
                 -> IDEM (Maybe (EditorIter, EditorIter))
forwardSearch (GtkEditorIter i) str flags mbLimit = liftIO $ do
    fmap (fmap (\(start, end) -> (GtkEditorIter start, GtkEditorIter end))) $
        Gtk.textIterForwardSearch i str flags $
            case mbLimit of
                Just (GtkEditorIter limit) -> Just limit
                Nothing                    -> Nothing
#ifdef LEKSAH_WITH_YI
                _                          -> fail "Mismatching TextEditor types in forwardSearch"
#endif

#ifdef LEKSAH_WITH_YI
forwardSearch (YiEditorIter i) str pred mbLimit = return Nothing -- TODO
#endif

forwardToLineEndC :: EditorIter -> IDEM EditorIter
forwardToLineEndC (GtkEditorIter i) = transformGtkIter i Gtk.textIterForwardToLineEnd
#ifdef LEKSAH_WITH_YI
forwardToLineEndC (YiEditorIter i) = transformYiIter i Yi.moveToEol
#endif

forwardWordEndC :: EditorIter -> IDEM (Maybe EditorIter)
forwardWordEndC (GtkEditorIter i) = transformGtkIterMaybe i Gtk.textIterForwardWordEnd
#ifdef LEKSAH_WITH_YI
forwardWordEndC (YiEditorIter i@(Yi.Iter b p)) = withYiIter i $ do
    Yi.nextWordB
    newPoint <- Yi.pointB
    if p == newPoint
        then return Nothing
        else return . Just $ mkYiIter' b newPoint
#endif

getChar :: EditorIter -> IDEM (Maybe Char)
getChar (GtkEditorIter i) = liftIO $ Gtk.textIterGetChar i
#ifdef LEKSAH_WITH_YI
getChar (YiEditorIter i) = withYiIter i Yi.readCharB
#endif

getCharsInLine :: EditorIter -> IDEM Int
getCharsInLine (GtkEditorIter i) = liftIO $ Gtk.textIterGetCharsInLine i
#ifdef LEKSAH_WITH_YI
getCharsInLine (YiEditorIter i) = withYiIter i $ length <$> Yi.readLnB
#endif

getLine :: EditorIter -> IDEM Int
getLine (GtkEditorIter i) = liftIO $ Gtk.textIterGetLine i
#ifdef LEKSAH_WITH_YI
getLine (YiEditorIter i) = withYiIter i Yi.curLn
#endif

getLineOffset :: EditorIter -> IDEM Int
getLineOffset (GtkEditorIter i) = liftIO $ Gtk.textIterGetLineOffset i
#ifdef LEKSAH_WITH_YI
getLineOffset (YiEditorIter i) = withYiIter i Yi.curCol
#endif

getOffset :: EditorIter -> IDEM Int
getOffset (GtkEditorIter i) = liftIO $ Gtk.textIterGetOffset i
#ifdef LEKSAH_WITH_YI
getOffset (YiEditorIter (Yi.Iter _ (Yi.Point o))) = return o
#endif

isStart :: EditorIter -> IDEM Bool
isStart (GtkEditorIter i) = liftIO $ Gtk.textIterIsStart i
#ifdef LEKSAH_WITH_YI
isStart (YiEditorIter i) = withYiIter i Yi.atSof
#endif

isEnd :: EditorIter -> IDEM Bool
isEnd (GtkEditorIter i) = liftIO $ Gtk.textIterIsEnd i
#ifdef LEKSAH_WITH_YI
isEnd (YiEditorIter i) = withYiIter i Yi.atEof
#endif

iterEqual :: EditorIter -> EditorIter -> IDEM Bool
iterEqual (GtkEditorIter i1) (GtkEditorIter i2) = liftIO $ Gtk.textIterEqual i1 i2
#ifdef LEKSAH_WITH_YI
iterEqual (YiEditorIter (Yi.Iter _ p1)) (YiEditorIter (Yi.Iter _ p2)) = return $ p1 == p2
iterEqual _ _ = liftIO $ fail "Mismatching TextEditor types in iterEqual"
#endif

startsLine :: EditorIter -> IDEM Bool
startsLine (GtkEditorIter i) = liftIO $ Gtk.textIterStartsLine i
#ifdef LEKSAH_WITH_YI
startsLine (YiEditorIter i) = withYiIter i Yi.atSol
#endif

atEnd :: EditorIter -> IDEM EditorIter
atEnd (GtkEditorIter i) = liftIO $ GtkEditorIter <$> do
    buffer <- Gtk.textIterGetBuffer i
    Gtk.textBufferGetEndIter buffer
#ifdef LEKSAH_WITH_YI
atEnd (YiEditorIter (Yi.Iter b _)) = iterFromYiBuffer' b Yi.sizeB
#endif

atLine :: EditorIter -> Int -> IDEM EditorIter
atLine (GtkEditorIter i) line = transformGtkIter i $ flip Gtk.textIterSetLine line
#ifdef LEKSAH_WITH_YI
atLine (YiEditorIter i) line = transformYiIter i $ Yi.gotoLn line
#endif

atLineOffset :: EditorIter -> Int -> IDEM EditorIter
atLineOffset (GtkEditorIter i) column = transformGtkIter i $ flip Gtk.textIterSetLineOffset column
#ifdef LEKSAH_WITH_YI
atLineOffset (YiEditorIter i) column = transformYiIter i $ Yi.moveToColB column
#endif

atOffset :: EditorIter -> Int -> IDEM EditorIter
atOffset (GtkEditorIter i) offset = transformGtkIter i $ flip Gtk.textIterSetOffset offset
#ifdef LEKSAH_WITH_YI
atOffset (YiEditorIter (Yi.Iter b _)) offset = return $ YiEditorIter $ Yi.Iter b (Yi.Point offset)
#endif

atStart :: EditorIter -> IDEM EditorIter
atStart (GtkEditorIter i) = liftIO $ GtkEditorIter <$> do
    buffer <- Gtk.textIterGetBuffer i
    Gtk.textBufferGetEndIter buffer
#ifdef LEKSAH_WITH_YI
atStart (YiEditorIter (Yi.Iter b _)) = return $ mkYiIter' b $ Yi.Point 0
#endif

-- Tag Table
newTag :: EditorTagTable -> String -> IDEM EditorTag
newTag (GtkEditorTagTable tt) name = liftIO $ do
    t <- Gtk.textTagNew (Just name)
    Gtk.textTagTableAdd tt t
    return $ GtkEditorTag t
#ifdef LEKSAH_WITH_YI
newTag (YiEditorTagTable) name = return YiEditorTag -- TODO
#endif

lookupTag :: EditorTagTable -> String -> IDEM (Maybe EditorTag)
lookupTag (GtkEditorTagTable tt) name = liftIO $ fmap (fmap GtkEditorTag) $ Gtk.textTagTableLookup tt name
#ifdef LEKSAH_WITH_YI
lookupTag (YiEditorTagTable) name = return Nothing -- TODO
#endif

-- Tag
background :: EditorTag -> Gtk.Color -> IDEM ()
background (GtkEditorTag t) color = liftIO $ Gtk.set t [Gtk.textTagBackground := colorHexString color]
#ifdef LEKSAH_WITH_YI
background (YiEditorTag) color = return () -- TODO
#endif

underline :: EditorTag -> Gtk.Underline -> IDEM ()
underline (GtkEditorTag t) value = liftIO $ Gtk.set t [Gtk.textTagUnderline := value]
#ifdef LEKSAH_WITH_YI
underline (YiEditorTag) value = return () -- TODO
#endif

-- Events
afterFocusIn :: EditorView -> IDEM () -> IDEM [Connection]
afterFocusIn (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sv `Gtk.afterFocusIn` \_ -> reflectIDE f ideR >> return False
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
afterFocusIn (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.afterFocusIn` \_ -> reflectIDE f ideR >> return False
        return [ConnectC id1]
#endif

afterModifiedChanged :: EditorBuffer -> IDEM () -> IDEM [Connection]
afterModifiedChanged (GtkEditorBuffer sb) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sb `Gtk.afterModifiedChanged` reflectIDE f ideR
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
afterModifiedChanged (YiEditorBuffer b) f = return [] -- TODO
#endif

afterMoveCursor :: EditorView -> IDEM () -> IDEM [Connection]
afterMoveCursor (GtkEditorView sv) f = do
    ideR <- ask
    GtkEditorBuffer sb <- getBuffer (GtkEditorView sv)
    liftIO $ do
#if MIN_VERSION_gtk(0,10,5)
        id1 <- sv `Gtk.after` Gtk.moveCursor $ \_ _ _ -> reflectIDE f ideR
#else
        id1 <- sv `Gtk.afterMoveCursor` \_ _ _ -> reflectIDE f ideR
#endif
        sv `Gtk.widgetAddEvents` [Gtk.ButtonReleaseMask]
        id2 <- sv `Gtk.onButtonRelease` \_ -> reflectIDE f ideR >> return False
        id3 <- sb `Gtk.afterEndUserAction` reflectIDE f ideR
        return [ConnectC id1, ConnectC id2, ConnectC id3]
#ifdef LEKSAH_WITH_YI
afterMoveCursor (YiEditorView v) f = return [] -- TODO
#endif

afterToggleOverwrite :: EditorView -> IDEM () -> IDEM [Connection]
afterToggleOverwrite (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
#if MIN_VERSION_gtk(0,10,5)
        id1 <- sv `Gtk.after` Gtk.toggleOverwrite $ reflectIDE f ideR
#else
        id1 <- sv `Gtk.afterToggleOverwrite` reflectIDE f ideR
#endif
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
afterToggleOverwrite (YiEditorView v) f = return [] -- TODO
#endif

onButtonPress :: EditorView
                 -> (GtkOld.Event -> IDEM Bool)
                 -> IDEM [Connection]
onButtonPress (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sv `Gtk.onButtonPress` \event -> reflectIDE (f event) ideR
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onButtonPress (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.onButtonPress` \event -> reflectIDE (f event) ideR
        return [ConnectC id1]
#endif

onButtonRelease :: EditorView
                 -> (GtkOld.Event -> IDEM Bool)
                 -> IDEM [Connection]
onButtonRelease (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sv `Gtk.onButtonRelease` \event -> reflectIDE (f event) ideR
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onButtonRelease (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.onButtonRelease` \event -> reflectIDE (f event) ideR
        return [ConnectC id1]
#endif

onCompletion :: EditorView -> IDEM () -> IDEM () -> IDEM [Connection]
onCompletion (GtkEditorView sv) start cancel = do
    ideR <- ask
    (GtkEditorBuffer sb) <- getBuffer (GtkEditorView sv)
    liftIO $ do
        -- when multiple afterBufferInsertText are called quickly,
        -- we cancel previous idle action which was not processed,
        -- its handler is stored here.
        -- Paste operation is example of such sequential events (each word!).
        lastHandler <- newIORef Nothing
        id1 <- sb `Gtk.afterBufferInsertText` \iter text -> do
            mapM_ idleRemove =<< maybeToList <$> readIORef lastHandler
            writeIORef lastHandler =<< Just <$> do
                (flip idleAdd) priorityDefault $ do
                    let isIdent a = isAlphaNum a || a == '\'' || a == '_' || a == '.'
                    let isOp    a = isSymbol   a || a == ':'  || a == '\\' || a == '*' || a == '/' || a == '-'
                                                 || a == '!'  || a == '@' || a == '%' || a == '&' || a == '?'
                    if (all isIdent text) || (all isOp text)
                        then do
                            hasSel <- Gtk.textBufferHasSelection sb
                            if not hasSel
                                then do
                                    (iterC, _) <- Gtk.textBufferGetSelectionBounds sb
                                    atC <- Gtk.textIterEqual iter iterC
                                    when atC $ reflectIDE start ideR
                                    return False
                                else do
                                    reflectIDE cancel ideR
                                    return False
                        else do
                            reflectIDE cancel ideR
                            return False
            return ()
#if MIN_VERSION_gtk(0,10,5)
        id2 <- sv `Gtk.on` Gtk.moveCursor $ \_ _ _ -> reflectIDE cancel ideR
#else
        id2 <- sv `Gtk.onMoveCursor` \_ _ _ -> reflectIDE cancel ideR
#endif
        id3 <- sv `Gtk.onButtonPress` \_ -> reflectIDE cancel ideR >> return False
        id4 <- sv `Gtk.onFocusOut` \_ -> reflectIDE cancel ideR >> return False
        return [ConnectC id1, ConnectC id2, ConnectC id3, ConnectC id4]
#ifdef LEKSAH_WITH_YI
onCompletion (YiEditorView v) start cancel = return [] -- TODO
#endif

onKeyPress :: EditorView
              -> (String -> [Gtk.Modifier] -> Gtk.KeyVal -> IDEM Bool)
              -> IDEM [Connection]
onKeyPress (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sv `Gtk.on` Gtk.keyPressEvent $ do
            name        <- Gtk.eventKeyName
            modifier    <- Gtk.eventModifier
            keyVal      <- Gtk.eventKeyVal
            Gtk.liftIO $ reflectIDE (f name modifier keyVal) ideR
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onKeyPress (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.on` Gtk.keyPressEvent $ do
            name        <- Gtk.eventKeyName
            modifier    <- Gtk.eventModifier
            keyVal      <- Gtk.eventKeyVal
            liftIO $ reflectIDE (f name modifier keyVal) ideR
        return [ConnectC id1]
#endif

onMotionNotify :: EditorView
              -> (Double -> Double -> [Gtk.Modifier] -> IDEM Bool)
              -> IDEM [Connection]
onMotionNotify (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sv `Gtk.on` Gtk.motionNotifyEvent $ do
            (ex,ey)     <- Gtk.eventCoordinates
            modifier    <- Gtk.eventModifier
            Gtk.liftIO $ reflectIDE (f ex ey modifier) ideR
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onMotionNotify (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.on` Gtk.motionNotifyEvent $ do
            (ex,ey)     <- Gtk.eventCoordinates
            modifier    <- Gtk.eventModifier
            liftIO $ reflectIDE (f ex ey modifier) ideR
        return [ConnectC id1]
#endif

onLeaveNotify :: EditorView
              -> (IDEM Bool)
              -> IDEM [Connection]
onLeaveNotify (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sv `Gtk.on` Gtk.leaveNotifyEvent $ do
            Gtk.liftIO $ reflectIDE (f) ideR
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onLeaveNotify (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.on` Gtk.leaveNotifyEvent $ do
            liftIO $ reflectIDE (f) ideR
        return [ConnectC id1]
#endif


onKeyRelease :: EditorView
                -> (String -> [Gtk.Modifier] -> Gtk.KeyVal -> IDEM Bool)
                -> IDEM [Connection]
onKeyRelease (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        id1 <- sv `Gtk.on` Gtk.keyReleaseEvent $ do
            name        <- Gtk.eventKeyName
            modifier    <- Gtk.eventModifier
            keyVal      <- Gtk.eventKeyVal
            Gtk.liftIO $ reflectIDE (f name modifier keyVal) ideR
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onKeyRelease (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.on` Gtk.keyReleaseEvent $ do
            name        <- Gtk.eventKeyName
            modifier    <- Gtk.eventModifier
            keyVal      <- Gtk.eventKeyVal
            liftIO $ reflectIDE (f name modifier keyVal) ideR
        return [ConnectC id1]
#endif

onLookupInfo :: EditorView -> IDEM () -> IDEM [Connection]
onLookupInfo (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
        sv `Gtk.widgetAddEvents` [Gtk.ButtonReleaseMask]
        id1 <- sv `Gtk.onButtonRelease` \e -> when (controlIsPressed e) (reflectIDE f ideR) >> return False
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onLookupInfo (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        (Yi.drawArea v) `Gtk.widgetAddEvents` [Gtk.ButtonReleaseMask]
        id1 <- (Yi.drawArea v) `Gtk.onButtonRelease` \e -> when (controlIsPressed e) (reflectIDE f ideR) >> return False
        return [ConnectC id1]
#endif

onPopulatePopup :: EditorView -> (Gtk.Menu -> IDEM ()) -> IDEM [Connection]
onPopulatePopup (GtkEditorView sv) f = do
    ideR <- ask
    liftIO $ do
#if MIN_VERSION_gtk(0,10,5)
        id1 <- sv `Gtk.on` Gtk.populatePopup $ \menu -> reflectIDE (f menu) ideR
#else
        id1 <- sv `Gtk.onPopulatePopup` \menu -> reflectIDE (f menu) ideR
#endif
        return [ConnectC id1]
#ifdef LEKSAH_WITH_YI
onPopulatePopup (YiEditorView v) f = do
    ideR <- ask
    liftIO $ do
        id1 <- (Yi.drawArea v) `Gtk.onPopupMenu` do
             menu <- Gtk.menuNew
             reflectIDE (f menu) ideR
             Gtk.menuPopup menu Nothing
        return [ConnectC id1]
#endif