packages feed

leksah-0.16.2.2: src/IDE/TextEditor/GtkSourceView.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
--
-- Module      :  IDE.TextEditor.GtkSourceView
-- Copyright   :  2007-2013 Juergen Nicklisch-Franken, Hamish Mackenzie
-- License     :  GPL
--
-- Maintainer  :  maintainer@leksah.org
-- Stability   :  provisional
-- Portability :
--
-- |
--
-----------------------------------------------------------------------------

module IDE.TextEditor.GtkSourceView (
    TextEditor(..)
  , EditorBuffer(..)
  , EditorView(..)
  , EditorIter(..)
  , EditorMark(..)
  , EditorTag(..)
  , EditorTagTable(..)

  , GtkSourceView(..)
  , newGtkBuffer

) where

import IDE.TextEditor.Class (TextEditor(..), EditorStyle(..))
import IDE.Core.Types
       (LogRefType(..), LogRef(..), LogRefType, colorHexString, Prefs(..),
        IDE(..), IDEM)
import Control.Monad.IO.Class (MonadIO(..))
import Data.Typeable (Typeable)
import Control.Applicative ((<$>))
import System.FilePath ((</>))
import IDE.Core.State (onIDE, reflectIDE, readIDE, getDataDir)
import Control.Monad (void, when, forM_)
import Control.Monad.Reader.Class (MonadReader(..))
import Graphics.UI.Editor.Basics (Connection(..))
import Data.Maybe
       (isJust, fromMaybe, isNothing, maybeToList, fromJust)
import Data.IORef (writeIORef, readIORef, newIORef)
import Data.Char (isDigit, isSymbol, isAlphaNum)
import Control.Monad.Trans.Class (MonadTrans(..))
import IDE.Utils.GUIUtils (fontDescription)
import Data.Text (Text)
import qualified Data.Text as T
       (drop, dropWhile, all, length, pack)
import Data.Monoid ((<>))
import Control.Arrow (Arrow(..))
import System.Log.Logger (debugM)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Ptr (nullPtr, castPtr)
import qualified GI.GtkSource as Source
import GI.GtkSource
       (viewSetDrawSpaces, setViewTabWidth, setViewShowLineNumbers,
        setViewRightMarginPosition, setViewShowRightMargin,
        setViewIndentWidth, setViewDrawSpaces, bufferUndo,
        bufferSetStyleScheme, styleSchemeManagerGetScheme,
        styleSchemeManagerGetSchemeIds, styleSchemeManagerAppendSearchPath,
        styleSchemeManagerNew, bufferRemoveSourceMarks, bufferRedo,
        viewSetMarkAttributes, onMarkAttributesQueryTooltipText,
        markAttributesSetIconName, markAttributesNew, setViewShowLineMarks,
        setViewSmartHomeEnd, setViewAutoIndent, setViewIndentOnTab,
        setViewInsertSpacesInsteadOfTabs, setViewHighlightCurrentLine,
        viewNewWithBuffer, bufferCreateSourceMark, getBufferCanUndo,
        getBufferCanRedo, bufferEndNotUndoableAction,
        bufferBeginNotUndoableAction, bufferSetMaxUndoLevels, bufferNew,
        bufferNewWithLanguage, languageManagerGuessLanguage,
        languageManagerSetSearchPath, languageManagerGetSearchPath,
        languageManagerNew)
import GI.Gio (contentTypeGuess)
import GI.Gtk.Objects.TextBuffer
       (afterTextBufferChanged, setTextBufferText, onTextBufferMarkSet,
        afterTextBufferInsertText, afterTextBufferEndUserAction,
        afterTextBufferModifiedChanged, textBufferSetModified,
        textBufferSelectRange, textBufferRemoveTagByName,
        textBufferPlaceCursor, textBufferPasteClipboard, textBufferInsert,
        textBufferGetText, textBufferGetStartIter, textBufferGetSlice,
        textBufferGetSelectionBounds, textBufferGetSelectionBound,
        textBufferGetModified, textBufferGetLineCount,
        textBufferGetIterAtOffset, textBufferGetIterAtMark,
        textBufferGetIterAtLine, textBufferGetInsert, textBufferGetEndIter,
        textBufferEndUserAction, textBufferDeleteSelection,
        textBufferDelete, textBufferCutClipboard, textBufferGetMark,
        textBufferCopyClipboard, textBufferBeginUserAction,
        textBufferApplyTagByName, textBufferGetTagTable, textBufferSetText)
import GI.Gtk.Objects.TextTag
import GI.Gtk.Objects.TextTagTable
       (noTextTagTable, textTagTableLookup, TextTagTable(..),
        textTagTableAdd)
import GI.Gtk.Objects.TextMark (textMarkGetName, TextMark(..))
import GI.Gtk.Objects.TextView
       (textViewSetEditable, onTextViewPopulatePopup,
        onTextViewMoveCursor, afterTextViewToggleOverwrite,
        afterTextViewMoveCursor, textViewScrollToIter,
        textViewScrollToMark, textViewGetOverwrite,
        textViewGetIterLocation, textViewGetIterAtLocation,
        textViewBufferToWindowCoords, textViewSetWrapMode,
        getTextViewBuffer)
import Data.GI.Base.ManagedPtr
       (castTo, withManagedPtr, unsafeCastTo)
import GI.GObject.Functions
       (signalHandlersBlockMatched, signalLookup)
import Data.GI.Base.BasicTypes (GObject(..))
import GI.GObject.Flags (SignalMatchType(..))
import GI.GtkSource.Enums (SmartHomeEndType(..))
import GI.Gtk.Enums
       (PolicyType(..), TextWindowType(..), WrapMode(..))
import GI.Gtk.Objects.ScrolledWindow
       (ScrolledWindow(..), scrolledWindowSetPolicy, scrolledWindowNew)
import GI.Gtk.Objects.Adjustment (noAdjustment)
import GI.Gtk.Objects.Container (containerAdd)
import GI.Gtk.Objects.Widget
       (onWidgetKeyReleaseEvent, onWidgetLeaveNotifyEvent,
        onWidgetMotionNotifyEvent, onWidgetKeyPressEvent,
        onWidgetFocusOutEvent, onWidgetButtonPressEvent,
        onWidgetButtonReleaseEvent, widgetAddEvents,
        afterWidgetFocusInEvent, widgetGrabFocus, toWidget,
        widgetGetParent, widgetGetWindow, widgetModifyFont)
import GI.Pango.Enums (Underline(..))
import GI.GtkSource.Flags (DrawSpacesFlags(..))
import Data.GI.Base.BasicConversions (gflagsToWord)
import GI.Gdk.Flags (ModifierType(..), EventMask(..))
import GI.GLib (pattern PRIORITY_DEFAULT, idleAdd, sourceRemove)
import GI.Gdk
       (setRGBAAlpha, setRGBABlue, setRGBAGreen, setRGBARed, newZeroRGBA,
        RGBA, getEventButtonState)
import GI.Gtk.Structs.TextIter
       (textIterSetOffset, textIterSetLineOffset, textIterSetLine,
        textIterGetBuffer, textIterStartsWord, textIterStartsLine,
        textIterIsEnd, textIterIsStart, textIterGetOffset,
        textIterGetCharsInLine, textIterGetChar, textIterForwardWordEnd,
        textIterForwardToLineEnd, textIterForwardSearch,
        textIterForwardFindChar, textIterForwardChars, textIterForwardChar,
        textIterEndsWord, textIterBackwardChars, textIterGetLineOffset,
        textIterBackwardWordStart, textIterBackwardFindChar,
        textIterBackwardChar, textIterEqual, textIterGetLine, textIterCopy,
        TextIter(..))
import Data.Text.Encoding (encodeUtf8)
import qualified GI.Gdk as Gdk (Window(..))
import Control.Monad.Trans.Reader (ReaderT(..))
import GI.Gtk.Objects.Menu (Menu(..))
import Data.GI.Base.Constructible (Constructible(..))
import Data.GI.Base.Attributes (AttrOp(..))
import Text.PrinterParser (Color(..))
import GI.Gtk (widgetOverrideFont, setTextTagUnderlineRgba)
import GI.GtkSource.Objects.StyleScheme
       (styleSchemeGetStyle, StyleScheme(..))
import GI.GtkSource.Objects.Style
import GI.Pango.Enums (Weight (..), Style (..))

transformGtkIter :: EditorIter GtkSourceView -> (TextIter -> IO a) -> IDEM (EditorIter GtkSourceView)
transformGtkIter (GtkIter i) f = do
    new <- textIterCopy i
    liftIO $ f new
    return (GtkIter new)

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

data GtkSourceView = GtkSourceView deriving( Typeable, Show )

newGtkBuffer :: Maybe FilePath -> Text -> IDEM (EditorBuffer GtkSourceView)
newGtkBuffer mbFilename contents = do
    liftIO $ debugM "lekash" "newGtkBuffer"
    lm      <- languageManagerNew
    dataDir <- getDataDir
    oldPath <- languageManagerGetSearchPath lm
    languageManagerSetSearchPath lm (Just $ T.pack (dataDir </> "language-specs") : oldPath)
    mbLang  <- case mbFilename of
        Just filename -> do
            guess <- contentTypeGuess (Just $ T.pack filename) (Just $ encodeUtf8 contents)
            languageManagerGuessLanguage lm (Just $ T.pack filename) $
                case guess of
                    (_, True)  -> Just "text/x-haskell"
                    (t, False) -> Just t
        Nothing -> languageManagerGuessLanguage lm Nothing (Just "text/x-haskell")
    mbLang2 <- case mbLang of
                    Nothing -> languageManagerGuessLanguage lm Nothing (Just "text/x-haskell")
                    _ -> return mbLang
    buffer <- case mbLang2 of
        Just sLang -> bufferNewWithLanguage sLang
        Nothing -> bufferNew noTextTagTable
    bufferSetMaxUndoLevels buffer (-1)
    bufferBeginNotUndoableAction buffer
    liftIO $ debugM "lekash" "newGtkBuffer setTextBufferText"
    setTextBufferText buffer contents
    bufferEndNotUndoableAction buffer
    liftIO $ debugM "lekash" "newGtkBuffer setup tag table"
    tagTable <- textBufferGetTagTable buffer
    textTagNew (Just "search-match") >>= textTagTableAdd tagTable
    textTagNew (Just "selection-match") >>= textTagTableAdd tagTable
    textTagNew (Just "context") >>= textTagTableAdd tagTable
    textTagNew (Just "breakpoint") >>= textTagTableAdd tagTable
    forM_ [minBound .. maxBound :: LogRefType] $ \ refType ->
        textTagNew (Just . T.pack $ show refType) >>= textTagTableAdd tagTable
    return $ GtkBuffer buffer


-- | Sets the style for a type of tag in the buffer by looking in the stylescheme
-- for a style with the given name, otherwise use the provided action
-- that sets some style on the TextTag (for standard sourceview styleschemes we can't
-- edit the stylescheme files)
setTagStyle :: Source.Buffer -> Text -> StyleScheme -> (Text -> IDEM ()) -> IDEM ()
setTagStyle sb tagName scheme applyDefaultStyle = do
    tagTable <- textBufferGetTagTable sb
    mbTag    <- textTagTableLookup tagTable tagName
    forM_ mbTag $ \tag -> do
        mbStyle <- styleSchemeGetStyle scheme tagName
        case mbStyle of
            Just style -> applyStyle tag style
            Nothing    -> applyDefaultStyle tagName
    where
        applyStyle :: TextTag -> Source.Style -> IDEM ()
        applyStyle tag style = do
            mbBg <- getStyleBackground style
            forM_ mbBg (setTextTagBackground tag)
            mbFg <- getStyleForeground style
            forM_ mbFg (setTextTagForeground tag)
            bold <- getStyleBold style
            when bold (setTextTagWeight tag (fromIntegral . fromEnum $ WeightBold))
            italic <- getStyleItalic style
            when italic (setTextTagStyle tag StyleItalic)

instance TextEditor GtkSourceView where
    data EditorBuffer GtkSourceView = GtkBuffer Source.Buffer
    data EditorView GtkSourceView = GtkView Source.View
    data EditorMark GtkSourceView = GtkMark TextMark
    data EditorIter GtkSourceView = GtkIter TextIter
    data EditorTagTable GtkSourceView = GtkTagTable TextTagTable
    data EditorTag GtkSourceView = GtkTag TextTag

    newBuffer = newGtkBuffer
    applyTagByName (GtkBuffer sb) name (GtkIter first) (GtkIter last) =
        textBufferApplyTagByName sb name first last
    beginNotUndoableAction (GtkBuffer sb) = bufferBeginNotUndoableAction sb
    beginUserAction (GtkBuffer sb) = textBufferBeginUserAction sb
    canRedo (GtkBuffer sb) = getBufferCanRedo sb
    canUndo (GtkBuffer sb) = getBufferCanUndo sb
    copyClipboard (GtkBuffer sb) = textBufferCopyClipboard sb
    createMark (GtkView sv) refType (GtkIter i) tooltip = do
        sb <- getTextViewBuffer sv >>= liftIO . unsafeCastTo Source.Buffer
        n <- textIterGetLine i
        let cat  = T.pack $ show refType
            name = T.pack (show n) <> " " <> tooltip
        mark <- textBufferGetMark sb name
        when (isNothing mark) . void $
            bufferCreateSourceMark sb (Just name) cat i
    cutClipboard (GtkBuffer sb) = textBufferCutClipboard sb
    delete (GtkBuffer sb) (GtkIter first) (GtkIter last) =
        textBufferDelete sb first last
    deleteSelection (GtkBuffer sb) = void $ textBufferDeleteSelection sb True True
    endNotUndoableAction (GtkBuffer sb) = bufferEndNotUndoableAction sb
    endUserAction (GtkBuffer sb) = textBufferEndUserAction sb
    getEndIter (GtkBuffer sb) = GtkIter <$> textBufferGetEndIter sb
    getInsertMark (GtkBuffer sb) = GtkMark <$> textBufferGetInsert sb
    getIterAtLine (GtkBuffer sb) line = GtkIter <$> textBufferGetIterAtLine sb (fromIntegral line)
    getIterAtMark (GtkBuffer sb) (GtkMark m) = GtkIter <$> textBufferGetIterAtMark sb m
    getIterAtOffset (GtkBuffer sb) offset = GtkIter <$> textBufferGetIterAtOffset sb (fromIntegral offset)
    getLineCount (GtkBuffer sb) = fromIntegral <$> textBufferGetLineCount sb
    getModified (GtkBuffer sb) = textBufferGetModified sb
    getSelectionBoundMark (GtkBuffer sb) = GtkMark <$> textBufferGetSelectionBound sb
    getSelectionBounds (GtkBuffer sb) = (\(_, a, b) -> (GtkIter a, GtkIter b)) <$>
        textBufferGetSelectionBounds sb
    getInsertIter (GtkBuffer sb) = GtkIter <$> do
        insertMark <- textBufferGetInsert sb
        textBufferGetIterAtMark sb insertMark
    getSlice (GtkBuffer sb) (GtkIter first) (GtkIter last) includeHidenChars =
        textBufferGetSlice sb first last includeHidenChars
    getStartIter (GtkBuffer sb) = GtkIter <$> textBufferGetStartIter sb
    getTagTable (GtkBuffer sb) = GtkTagTable <$> textBufferGetTagTable sb
    getText (GtkBuffer sb) (GtkIter first) (GtkIter last) includeHidenChars =
        textBufferGetText sb first last includeHidenChars
    hasSelection (GtkBuffer sb) = (\(b, _, _) -> b) <$> textBufferGetSelectionBounds sb
    insert (GtkBuffer sb) (GtkIter i) text = textBufferInsert sb i text (-1)
    newView (GtkBuffer sb) mbFontString = do
        liftIO $ debugM "lekash" "newView (GtkSourceView)"
        prefs <- readIDE prefs
        fd <- fontDescription mbFontString
        sv <- viewNewWithBuffer sb

        -- Disable source_mark_updated handler in sv because it schedules a full redraw
        -- that turns out to be unnecessary and very costly in Leksah
        signal <- signalLookup "source_mark_updated" =<< liftIO (gobjectType (undefined :: Source.Buffer))
        liftIO $ withManagedPtr sv $ \svPtr ->
            signalHandlersBlockMatched sb [SignalMatchTypeId, SignalMatchTypeData]
                signal 0 Nothing nullPtr (castPtr svPtr)

        liftIO $ debugM "lekash" "newView set attirbutes"
        setViewHighlightCurrentLine sv True
        setViewInsertSpacesInsteadOfTabs sv True
        setViewIndentOnTab sv True
        setViewAutoIndent sv True
        setViewSmartHomeEnd sv SmartHomeEndTypeBefore
        setViewShowLineMarks sv True

        liftIO $ debugM "lekash" "newView set up mark attributes"
        forM_ [minBound..maxBound] $ \ refType -> do
            let cat = T.pack $ show refType
                icon = case refType of
                        ErrorRef       -> "ide_error"
                        WarningRef     -> "ide_warning"
                        TestFailureRef -> "software-update-urgent"
                        LintRef        -> "ide_suggestion"
                        BreakpointRef  -> "media-playback-pause"
                        ContextRef     -> "media-playback-start"
            attributes <- markAttributesNew
            markAttributesSetIconName attributes icon
            onMarkAttributesQueryTooltipText attributes $ \ mark ->
                maybe "" (T.drop 1 . T.dropWhile isDigit) <$> textMarkGetName mark
            viewSetMarkAttributes sv cat attributes (fromIntegral $ 1 + fromEnum(maxBound :: LogRefType) - fromEnum refType)
        textViewSetWrapMode sv (if wrapLines prefs
                                    then WrapModeWord
                                    else WrapModeNone)
        sw <- scrolledWindowNew noAdjustment noAdjustment
        containerAdd sw sv
        widgetOverrideFont sv (Just fd)
        return (GtkView sv, sw)
    pasteClipboard (GtkBuffer sb) clipboard (GtkIter i) defaultEditable =
        textBufferPasteClipboard sb clipboard (Just i) defaultEditable
    placeCursor (GtkBuffer sb) (GtkIter i) = textBufferPlaceCursor sb i
    redo (GtkBuffer sb) = bufferRedo sb
    removeTagByName (GtkBuffer sb) name = do
        first <- textBufferGetStartIter sb
        last <- textBufferGetEndIter sb
        textBufferRemoveTagByName sb name first last
        bufferRemoveSourceMarks sb first last (Just name)
    selectRange (GtkBuffer sb) (GtkIter first) (GtkIter last) =
        textBufferSelectRange sb first last
    setModified (GtkBuffer sb) modified = void $ textBufferSetModified sb modified
    setStyle (GtkBuffer sb) EditorStyle {..} = do
        liftIO $ debugM "lekash" "setStyle (GtkSourceView)"
        case styleName of
            Nothing  -> return ()
            Just str -> do
                styleManager <- liftIO styleSchemeManagerNew
                dataDir <- liftIO getDataDir
                styleSchemeManagerAppendSearchPath styleManager . T.pack $ dataDir </> "data/styles"
                ids <- fromMaybe [] <$> styleSchemeManagerGetSchemeIds styleManager
                let preferedNames = if preferDark then [str<>"-dark", str] else [str]
                forM_ (take 1 $ filter (`elem` ids) preferedNames) $ \ name -> do
                    scheme <- styleSchemeManagerGetScheme styleManager name
                    bufferSetStyleScheme sb (Just scheme)
                    tagTable <- getTagTable (GtkBuffer sb)
                    let isDark = name `elem` ["leksah-dark", "oblivion", "cobalt", "industrial"]
                        setBG (dark, light) (Just tag) = background tag (if isDark then dark else light)
                        setBG _             Nothing    = return ()
                        setUnderline mbCol = maybe (return ()) (\tag -> underline tag UnderlineError mbCol)
                    -- This is ugly, we just have to make sure we only provide
                    -- styleschemes that include styles for these tags
                    let applyDefaultStyling tagName = do
                            mbTag <- lookupTag tagTable tagName
                            case tagName of
                                "selection-match" -> setBG matchBG mbTag
                                "search-match" -> setBG foundBG mbTag
                                "BreakpointRef" ->  setBG breakpointBG mbTag
                                "ContextRef" -> setBG contextBG mbTag
                                "ErrorRef" -> setUnderline Nothing mbTag
                                "WarningRef" -> setUnderline (Just $ Color 214 176 4) mbTag
                                "TestFailureRef" -> setUnderline (Just $ Color 207 18 241) mbTag
                                "LintRef" -> setUnderline (Just $ Color 21 110 209) mbTag
                                _ -> return ()
                    let tagNames = ["selection-match", "search-match"]
                                       ++ map (T.pack . show)
                                              [ErrorRef, WarningRef, TestFailureRef, LintRef, BreakpointRef, ContextRef]
                    forM_ tagNames $ \tagName -> do
                        setTagStyle sb tagName scheme applyDefaultStyling

    setText (GtkBuffer sb) text = setTextBufferText sb text
    undo (GtkBuffer sb) = bufferUndo sb

    afterChanged (GtkBuffer sb) f = do
        ideR <- ask
        id1 <- ConnectC sb <$> afterTextBufferChanged sb (reflectIDE f ideR)
        return [id1]

    afterModifiedChanged (GtkBuffer sb) f = do
        ideR <- ask
        id1 <- ConnectC sb <$> afterTextBufferModifiedChanged sb (reflectIDE f ideR)
        return [id1]

    -- View
    bufferToWindowCoords (GtkView sv) (x, y) = (fromIntegral *** fromIntegral) <$>
        textViewBufferToWindowCoords sv TextWindowTypeWidget (fromIntegral x) (fromIntegral y)
    drawTabs (GtkView sv) = viewSetDrawSpaces sv [DrawSpacesFlagsTab, DrawSpacesFlagsSpace, DrawSpacesFlagsTrailing]
    getBuffer (GtkView sv) = GtkBuffer <$> (getTextViewBuffer sv >>= (liftIO . unsafeCastTo Source.Buffer))
    getWindow (GtkView sv) = widgetGetWindow sv
    getIterAtLocation (GtkView sv) x y = GtkIter
#ifdef MIN_VERSION_GTK_3_20
        . snd
#endif
        <$> textViewGetIterAtLocation sv (fromIntegral x) (fromIntegral y)
    getIterLocation (GtkView sv) (GtkIter i) = textViewGetIterLocation sv i
    getOverwrite (GtkView sv) = textViewGetOverwrite sv
    getScrolledWindow (GtkView sv) = widgetGetParent sv >>= \case
        Just v -> liftIO $ unsafeCastTo ScrolledWindow v
        Nothing -> error "getScrolledWindow failed"
    getEditorWidget (GtkView sv) = liftIO $ toWidget sv
    grabFocus (GtkView sv) = widgetGrabFocus sv
    scrollToMark (GtkView sv) (GtkMark m) withMargin mbAlign = uncurry (textViewScrollToMark sv m withMargin (isJust mbAlign)) $ fromMaybe (0,0) mbAlign
    scrollToIter (GtkView sv) (GtkIter i) withMargin mbAlign = void $ uncurry (textViewScrollToIter sv i withMargin (isJust mbAlign)) $ fromMaybe (0,0) mbAlign
    setFont (GtkView sv) mbFontString = do
        fd <- fontDescription mbFontString
        widgetModifyFont sv (Just fd)
    setIndentWidth (GtkView sv) width = setViewIndentWidth sv (fromIntegral width)
    setWrapMode v@(GtkView sv) wrapLines = do
        sw <- getScrolledWindow v
        if wrapLines
            then do
                textViewSetWrapMode sv WrapModeWord
                scrolledWindowSetPolicy sw PolicyTypeNever PolicyTypeAutomatic
            else do
                textViewSetWrapMode sv WrapModeNone
                scrolledWindowSetPolicy sw PolicyTypeAutomatic PolicyTypeAutomatic
    setRightMargin (GtkView sv) mbRightMargin =
        case mbRightMargin of
            Just n -> do
                setViewShowRightMargin sv True
                setViewRightMarginPosition sv (fromIntegral n)
            Nothing -> setViewShowRightMargin sv False
    setShowLineNumbers (GtkView sv) show = setViewShowLineNumbers sv show
    setTabWidth (GtkView sv) width = setViewTabWidth sv (fromIntegral width)

    -- Events
    afterFocusIn (GtkView sv) f = do
        ideR <- ask
        id1 <- ConnectC sv <$> afterWidgetFocusInEvent sv (\e -> reflectIDE f ideR >> return False)
        return [id1]
    afterMoveCursor v@(GtkView sv) f = do
        ideR <- ask
        (GtkBuffer sb) <- getBuffer v
        id1 <- ConnectC sv <$> afterTextViewMoveCursor sv (\_ _ _ -> reflectIDE f ideR)
        widgetAddEvents sv (gflagsToWord [EventMaskButtonReleaseMask])
        id2 <- ConnectC sv <$> onWidgetButtonReleaseEvent sv (\e -> reflectIDE f ideR >> return False)
        id3 <- ConnectC sb <$> afterTextBufferEndUserAction sb (reflectIDE f ideR)
        return [id1, id2, id3]
    afterToggleOverwrite (GtkView sv) f = do
        ideR <- ask
        id1 <- ConnectC sv <$> afterTextViewToggleOverwrite sv (reflectIDE f ideR)
        return [id1]
    onButtonPress (GtkView sv) f = do
        id1 <- onIDE onWidgetButtonPressEvent sv f
        return [id1]
    onButtonRelease (GtkView sv) f = do
        id1 <- onIDE onWidgetButtonReleaseEvent sv f
        return [id1]
    onCompletion v@(GtkView sv) start cancel = do
        ideR <- ask
        (GtkBuffer sb) <- getBuffer v
        -- 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 <- liftIO $ newIORef Nothing
        id1 <- ConnectC sb <$> afterTextBufferInsertText sb (\iter text _ -> do
            lh <- readIORef lastHandler
            debugM "leksah" $ "Removing " <> show lh
            mapM_ sourceRemove $ maybeToList lh
            h <- idleAdd PRIORITY_DEFAULT $ do
                writeIORef lastHandler Nothing
                let isIdent a = isAlphaNum a || a == '\'' || a == '_' || a == '.'
                let isOp    a = isSymbol   a || a == ':'  || a == '\\' || a == '*' || a == '/' || a == '-'
                                             || a == '!'  || a == '@' || a == '%' || a == '&' || a == '?'
                if T.all isIdent text || T.all isOp text
                    then do
                        (hasSel, iterC, _) <- textBufferGetSelectionBounds sb
                        if not hasSel
                            then do
                                atC <- textIterEqual iter iterC
                                when atC $ reflectIDE start ideR
                                return False
                            else do
                                reflectIDE cancel ideR
                                return False
                    else do
                        reflectIDE cancel ideR
                        return False
            writeIORef lastHandler (Just h)
            return ())
        id2 <- ConnectC sv <$> onTextViewMoveCursor sv (\_ _ _ -> reflectIDE cancel ideR)
        id3 <- ConnectC sv <$> onWidgetButtonPressEvent sv (\e -> reflectIDE cancel ideR >> return False)
        id4 <- ConnectC sv <$> onWidgetFocusOutEvent sv (\e -> reflectIDE cancel ideR >> return False)
        return [id1, id2, id3, id4]
    onKeyPress (GtkView sv) f = do
        id1 <- onIDE onWidgetKeyPressEvent sv f
        return [id1]
    onMotionNotify (GtkView sv) f = do
        id1 <- onIDE onWidgetMotionNotifyEvent sv f
        return [id1]
    onLeaveNotify (GtkView sv) f = do
        id1 <- onIDE onWidgetLeaveNotifyEvent sv f
        return [id1]
    onKeyRelease (GtkView sv) f = do
        id1 <- onIDE onWidgetKeyReleaseEvent sv f
        return [id1]
    onLookupInfo (GtkView sv) f = do
        widgetAddEvents sv $ gflagsToWord [EventMaskButtonReleaseMask]
        id1 <- onIDE onWidgetButtonReleaseEvent sv $ do
            e <- lift ask
            mod <- getEventButtonState e
            case mod of
                [ModifierTypeControlMask] -> f >> return True
                _             -> return False
        return [id1]
    onMotionNotifyEvent (GtkView sv) handler = do
        widgetAddEvents sv $ gflagsToWord [EventMaskButtonMotionMask, EventMaskButton1MotionMask]  -- TODO: this doesn't work yet event gets fired anyways: restrict event to being fired when left mouse button is pressed down
        id1 <- onIDE onWidgetMotionNotifyEvent sv handler  --TODO this is potentially slowing leksah, a better event (if there was any) could be more efficient here
        widgetAddEvents sv $ gflagsToWord [EventMaskButtonMotionMask, EventMaskButton1MotionMask]  -- TODO: this doesn't work yet event gets fired anyways: restrict event to being fired when left mouse button is pressed down
        return [id1]
    onPopulatePopup (GtkView sv) f = do
        ideR <- ask
        id1 <- ConnectC sv <$> onTextViewPopulatePopup sv (\menu -> reflectIDE (f =<< liftIO (unsafeCastTo Menu menu)) ideR)
        return [id1]
    onSelectionChanged (GtkBuffer sb) handler = do
        ideR <- ask
        id1 <- ConnectC sb <$> onTextBufferMarkSet sb (\ _ mark -> do
            name <- textMarkGetName mark
            when (name == Just "insert") $ reflectIDE handler ideR)
        return [id1]

    -- Iter
    backwardCharC i = transformGtkIter i textIterBackwardChar
    backwardFindCharC i pred mbLimit = transformGtkIterMaybe i $ \x ->
        textIterBackwardFindChar x (return . pred) $
            case mbLimit of
                Just (GtkIter limit) -> Just limit
                Nothing              -> Nothing
    backwardWordStartC i = transformGtkIterMaybe i textIterBackwardWordStart
    backwardToLineStartC i = transformGtkIter i $ \new -> do
        n <- textIterGetLineOffset new
        textIterBackwardChars new n
        return ()
    endsWord (GtkIter i) = textIterEndsWord i
    forwardCharC i = transformGtkIter i textIterForwardChar
    forwardCharsC i n = transformGtkIter i $ flip textIterForwardChars (fromIntegral n)
    forwardFindCharC i pred mbLimit = transformGtkIterMaybe i $ \x ->
        textIterForwardFindChar x (return . pred) $
            case mbLimit of
                Just (GtkIter limit) -> Just limit
                Nothing              -> Nothing
    forwardSearch (GtkIter i) str flags mbLimit =
        textIterForwardSearch i str flags (
            case mbLimit of
                Just (GtkIter limit) -> Just limit
                Nothing              -> Nothing) >>= \case
                    (False, _, _) -> return Nothing
                    (True, a, b)  -> return $ Just (GtkIter a, GtkIter b)
    forwardToLineEndC i = transformGtkIter i textIterForwardToLineEnd
    forwardWordEndC i = transformGtkIterMaybe i textIterForwardWordEnd
    getChar (GtkIter i) = textIterGetChar i >>= \case '\0' -> return Nothing
                                                      c    -> return $ Just c
    getCharsInLine (GtkIter i) = fromIntegral <$> textIterGetCharsInLine i
    getLine (GtkIter i) = fromIntegral <$> textIterGetLine i
    getLineOffset (GtkIter i) = fromIntegral <$> textIterGetLineOffset i
    getOffset (GtkIter i) = fromIntegral <$> textIterGetOffset i
    isStart (GtkIter i) = textIterIsStart i
    isEnd (GtkIter i) = textIterIsEnd i
    iterEqual (GtkIter i1) (GtkIter i2) = textIterEqual i1 i2
    startsLine (GtkIter i) = textIterStartsLine i
    startsWord (GtkIter i) = textIterStartsWord i
    atEnd (GtkIter i) = GtkIter <$> do
        buffer <- textIterGetBuffer i
        textBufferGetEndIter buffer
    atLine i line = transformGtkIter i $ flip textIterSetLine (fromIntegral line)
    atLineOffset i column = transformGtkIter i $ flip textIterSetLineOffset (fromIntegral column)
    atOffset i offset = transformGtkIter i $ flip textIterSetOffset (fromIntegral offset)
    atStart (GtkIter i) = GtkIter <$> do
        buffer <- textIterGetBuffer i
        textBufferGetEndIter buffer

    -- Tag Table
    newTag (GtkTagTable tt) name = GtkTag <$> do
        t <- textTagNew (Just name)
        textTagTableAdd tt t
        return t
    lookupTag (GtkTagTable tt) name = fmap GtkTag <$> textTagTableLookup tt name

    -- Tag
    background (GtkTag t) color = setTextTagBackground t . T.pack $ colorHexString color
    underline (GtkTag t) value Nothing = setTextTagUnderline t value
    underline (GtkTag t) value (Just (Color r g b)) = do
        col <- newZeroRGBA
        setRGBARed col (fromIntegral r / 255)
        setRGBAGreen col (fromIntegral g / 255)
        setRGBABlue col (fromIntegral b / 255)
        setRGBAAlpha col 1
        setTextTagUnderline t value
        setTextTagUnderlineRgba t col
    setEditable (GtkView view) b = textViewSetEditable view b