-- Author:     Andy Stewart <lazycat.manatee@gmail.com>
-- Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
-- 
-- Copyright (C) 2010 Andy Stewart, all rights reserved.
-- 
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
-- 
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

module Manatee.Toolkit.Gtk.Multiline where

import Control.Monad
import Data.ByteString (ByteString)
import Graphics.UI.Gtk hiding (Statusbar, statusbarNew)
import Manatee.Toolkit.General.Basic
import Manatee.Toolkit.General.Functor
import Manatee.Toolkit.General.Maybe
import System.IO.Unsafe

-- | Wrap user action.
-- Then user can undo/redo this action.
textBufferWrapAction :: TextBufferClass self => self -> IO () -> IO ()
textBufferWrapAction tb action = do
  textBufferBeginUserAction tb  -- entry in action
  action                        -- execute action
  textBufferEndUserAction tb    -- entry out action


-- | Get text with given text tag.
textBufferGetTagTextWithIter :: TextBufferClass buffer => buffer
                             -> TextIter
                             -> TextTag
                             -> IO (Maybe String)
textBufferGetTagTextWithIter buffer iter tag = do
  startIter <- textIterCopy iter
  endIter   <- textIterCopy iter
  moveBackward <- textIterBackwardToTagToggle startIter (Just tag)
  moveForward  <- textIterForwardToTagToggle endIter (Just tag)
  if moveBackward && moveForward
     then liftM Just $ textBufferGetText buffer startIter endIter True
     else return Nothing

-- | Get insert text with given text tag.
textBufferGetTagText :: TextBufferClass buffer => buffer
                     -> TextTag
                     -> IO (Maybe String)
textBufferGetTagText buffer tag = do
  iter <- textBufferGetInsertIter buffer
  textBufferGetTagTextWithIter buffer iter tag

-- | Get bytestring with give text tag.
textBufferGetTagByteStringWithIter :: TextBufferClass buffer => buffer
                                   -> TextIter
                                   -> TextTag
                                   -> IO (Maybe ByteString)
textBufferGetTagByteStringWithIter buffer iter tag = do
  startIter <- textIterCopy iter
  endIter   <- textIterCopy iter
  moveBackward <- textIterBackwardToTagToggle startIter (Just tag)
  moveForward  <- textIterForwardToTagToggle endIter (Just tag)
  if moveBackward && moveForward
     then liftM Just $ textBufferGetByteString buffer startIter endIter True
     else return Nothing

-- | Get insert text with given text tag.
textBufferGetTagByteString :: TextBufferClass buffer => buffer
                           -> TextTag
                           -> IO (Maybe ByteString)
textBufferGetTagByteString buffer tag = do
  iter <- textBufferGetInsertIter buffer
  textBufferGetTagByteStringWithIter buffer iter tag

-- | Text buffer keep selection to execute action.
textBufferKeepSelection :: TextBufferClass self => self -> Bool -> Bool -> IO () -> IO ()           
textBufferKeepSelection tb sLeft eLeft action = do 
  -- Get selection range.
  (sIter, eIter) <- textBufferGetSelectionBounds tb

  -- Decide selection range.
  fromUpToDown <- textBufferIsEqualInsertIter tb eIter

  -- Record iter mark.
  sMark <- textBufferCreateMark tb Nothing sIter sLeft
  eMark <- textBufferCreateMark tb Nothing eIter eLeft

  -- Execute action.
  action

  -- Restore iter.
  startIter <- textBufferGetIterAtMark tb sMark
  endIter   <- textBufferGetIterAtMark tb eMark

  -- Restore selection range.
  if fromUpToDown
     then textBufferSelectRange tb endIter startIter
     else textBufferSelectRange tb startIter endIter

  -- Update selection iter.
  textBufferUpdateSelectionIter tb

-- | Move text, tags, and pixbufs between start and end (the order of start and end doesn't matter) to iter.
textBufferMoveRange :: TextBufferClass self => self -> TextIter -> TextIter -> TextIter -> IO ()
textBufferMoveRange tb iter start end = 
    -- Don't do anything when range len is 0.
    unlessM (textIterEqual start end) $ do
      -- Order range iter first.
      textIterOrder start end
      -- Move range.
      ifM (textIterInRange iter start end)
              -- In range.
              (do                              
                -- Record iter mark for restore.
                sMark <- textBufferCreateMark tb Nothing start True

                -- Get slice.
                slice <- textBufferGetSlice tb start end True

                -- Delete old range.
                textBufferDelete tb start end

                -- Insert new slice.
                textBufferInsertAtMark tb sMark slice)          
              -- Out range.
              (do                               
                -- Record iter mark for restore.
                sMark <- textBufferCreateMark tb Nothing start True
                eMark <- textBufferCreateMark tb Nothing end True

                -- Insert range.
                textBufferInsertRange tb iter start end

                -- Delete old range.
                sIter <- textBufferGetIterAtMark tb sMark
                eIter <- textBufferGetIterAtMark tb eMark
                textBufferDelete tb sIter eIter)          

-- | Cancel selection.
textBufferCancelSelection :: TextBufferClass self => self -> IO ()
textBufferCancelSelection tb = do
  iter <- textBufferGetInsertIter tb
  textBufferSelectRange tb iter iter

-- | Get text iter at cursor.
textBufferGetInsertIter :: TextBufferClass self => self -> IO TextIter
textBufferGetInsertIter tb =
    textBufferGetIterAtMark tb 
        =<< textBufferGetInsert tb

-- | Create an iterator at start position of special line.
textBufferGetLineStartIter :: TextBufferClass self => self -> Int -> IO TextIter  
textBufferGetLineStartIter = textBufferGetIterAtLine

-- | Create an iterator at start position of current line.
textBufferGetCurrentLineStartIter :: TextBufferClass self => self -> IO TextIter
textBufferGetCurrentLineStartIter tb = 
    textBufferGetLineStartIter tb 
        =<< textBufferGetLine tb

-- | Create an iterator line delimiter characters, 
-- If incLimitincDelimiter is True which will be either a newline, a carriage return, a carriage return/newline in sequence.
-- Otherwise, not include delimiter characters. 
textBufferGetLineEndIter :: TextBufferClass self => self -> Int -> Bool -> IO TextIter
textBufferGetLineEndIter tb line incDelimiter = do
  -- Get iter.
  iter <- textBufferGetInsertIter tb
  -- Set line.
  textIterSetLine iter line
  -- Get chars in line.
  chars <- (if incDelimiter 
           -- Get chars include delimiter.
           then textIterGetCharsInLine
           -- Get chars not include delimiter.
           else textIterGetCharsInLine_ ) iter
  -- Set line offset.
  textIterSetLineOffset iter chars
  return iter

-- | Create an iterator line delimiter characters, 
-- which will be either a newline, a carriage return, a carriage return/newline in sequence.
textBufferGetCurrentLineEndIter :: TextBufferClass self => self -> IO TextIter  
textBufferGetCurrentLineEndIter tb = do
  line <- textBufferGetLine tb
  textBufferGetLineEndIter tb line True

-- | Create an iterator line delimiter characters, 
textBufferGetCurrentLineEndIter_ :: TextBufferClass self => self -> IO TextIter  
textBufferGetCurrentLineEndIter_ tb = do
  line <- textBufferGetLine tb
  textBufferGetLineEndIter tb line False

-- | Get line from text buffer.
textBufferGetLine :: TextBufferClass self => self -> IO Int  
textBufferGetLine tb = 
    textIterGetLine 
        =<< textBufferGetInsertIter tb

-- | Get line offset from text buffer.
textBufferGetLineOffset :: TextBufferClass self => self -> IO Int  
textBufferGetLineOffset tb = 
    textIterGetLineOffset 
        =<< textBufferGetInsertIter tb

{-# NOINLINE textBufferSelectionIter #-}
-- Don't use `TextBufferClass` replace `TextBuffer`,
-- Otherwise, every time you use this attribute, 
-- it will be re-evaluate to the definition by compiler.
-- That's to say, you always `get` Nothing.  
textBufferSelectionIter :: Attr TextBuffer (Maybe TextIter)
textBufferSelectionIter = unsafePerformIO objectCreateAttribute

-- | Set text buffer selection iter.
textBufferSetSelectionIter :: TextBufferClass self => self -> TextIter -> IO ()       
textBufferSetSelectionIter tb ti = 
    set (toTextBuffer tb) [textBufferSelectionIter := Just ti]

-- | Remove text buffer selection iter.    
textBufferRemoveSelectionIter :: TextBufferClass self => self -> IO ()
textBufferRemoveSelectionIter tb =
    set (toTextBuffer tb) [textBufferSelectionIter := Nothing]

-- | Get text buffer selection iter.
textBufferGetSelectionIter :: TextBufferClass self => self -> IO (Maybe TextIter)
textBufferGetSelectionIter tb = 
    get (toTextBuffer tb) textBufferSelectionIter

-- | Update selection iter after buffer changed.
-- If selection active, just update SelectionIter, 
-- otherwise set Nothing.
textBufferUpdateSelectionIter :: TextBufferClass self => self -> IO ()
textBufferUpdateSelectionIter tb = 
    ifM (textBufferHasSelection tb)
            (textBufferSetSelectionIter tb 
                 =<< textBufferGetSelectionBoundIter tb)
            (textBufferRemoveSelectionIter tb)

-- | Get selection bound line.
textBufferGetSelectionBoundLines :: TextBufferClass self => self -> IO (TextIter, TextIter, Bool, Bool) 
textBufferGetSelectionBoundLines tb = do
  -- Whether active selection.
  hasSelect <- textBufferHasSelection tb
  if hasSelect
     -- Get iterator of selection bound.
     then do
       (sIter, eIter) <- textBufferGetSelectionBounds tb
       firstLine <- textIterIsFirstLine sIter
       lastLine  <- textIterIsLastLine eIter
       textIterBackwardToLineStart sIter
       chars <- textIterGetCharsInLine eIter
       textIterSetLineOffset eIter chars

       return (sIter, eIter, firstLine, lastLine)
     -- Get iterator of current line.
     else do
       sIter <- textBufferGetCurrentLineStartIter tb
       eIter <- textBufferGetCurrentLineEndIter tb
       firstLine <- textIterIsFirstLine sIter
       lastLine  <- textIterIsLastLine sIter

       return (sIter, eIter, firstLine, lastLine)

-- | Get bound iter of selection region.
textBufferGetSelectionBoundIter :: TextBufferClass self => self -> IO TextIter
textBufferGetSelectionBoundIter tb = (<=<) (textBufferGetIterAtMark tb) textBufferGetSelectionBound tb

-- | Get insert iter of selection region.
textBufferGetSelectionInsertIter :: TextBufferClass self => self -> IO TextIter  
textBufferGetSelectionInsertIter = textBufferGetInsertIter

-- | Is equal current InsertIter?
textBufferIsEqualInsertIter :: TextBufferClass self => self -> TextIter -> IO Bool
textBufferIsEqualInsertIter tb ti = 
  textIterEqual ti 
      =<< textBufferGetInsertIter tb
  
-- | Delete last char.
textBufferDeleteLastChar :: TextBufferClass self => self -> IO ()
textBufferDeleteLastChar tb = do
  -- Get start and end iter for last char.
  startIter <- textBufferGetEndIter tb
  endIter   <- textIterCopy startIter
  textIterBackwardChar startIter

  -- Delete last char.
  textBufferDelete tb startIter endIter

-- | Text buffer newline at mark.
textBufferInsertNewlineAtMark :: TextBufferClass self => self -> TextMark -> IO ()  
textBufferInsertNewlineAtMark tb mark = 
    textBufferInsertAtMark tb mark "\n"

-- | Text buffer insert at mark.
textBufferInsertAtMark :: TextBufferClass self => self -> TextMark -> String -> IO ()  
textBufferInsertAtMark tb mark str = do
  -- Get iter.
  iter <- textBufferGetIterAtMark tb mark

  -- Insert at mark.
  textBufferInsert tb iter str

-- | Text view place cursor.
-- Move insert to iter, then scroll text view to visible area of widget.
textViewPlaceCursor :: TextViewClass self => self -> TextIter -> IO ()
textViewPlaceCursor tv ti = do
  tb <- textViewGetBuffer tv
  textBufferPlaceCursor tb ti
  textViewScrollCursorOnscreen tv

-- | Place cursor with mark.
textViewPlaceCursorWithMark :: TextViewClass self => self -> TextMark -> IO ()  
textViewPlaceCursorWithMark tv tm = do
  tb <- textViewGetBuffer tv
  iter <- textBufferGetIterAtMark tb tm
  textViewPlaceCursor tv iter

-- | Goto line.
-- If line number not in buffer bound, goto min/max line.
textViewGotoLine :: TextViewClass self => self -> Int -> IO ()
textViewGotoLine tv line = do
  tb <- textViewGetBuffer tv
  lineCount <- textBufferGetLineCount tb
  let lineNumber 
          | line <= 1
              = 1
          | line > lineCount
              = lineCount
          | otherwise 
              = line
  -- Line number count from 0, so need increase '1'
  iter <- textBufferGetIterAtLine tb (lineNumber - 1)
  textViewPlaceCursor tv iter

-- | Goto column.
-- If column number not in buffer bound, goto start/end of line.
textViewGotoColumn :: TextViewClass self => self -> Int -> IO ()
textViewGotoColumn tv column = do
  tb <- textViewGetBuffer tv
  currentIter <- textBufferGetInsertIter tb
  columnCount <- textIterGetCharsInLine_ currentIter
  let columnNumber 
          | column <= 0
              = 0
          | column > columnCount
              = columnCount
          | otherwise
              = column
  currentLine <- textIterGetLine currentIter
  columnIter  <- textBufferGetIterAtLineOffset tb currentLine columnNumber
  textViewPlaceCursor tv columnIter

-- | Text view wrap user action.
textViewWrapAction :: TextViewClass self => self -> IO () -> IO ()
textViewWrapAction tv action = do
  tb <- textViewGetBuffer tv
  textBufferWrapAction tb action

-- | Text View load file.
textViewLoadFile :: TextViewClass self => self -> FilePath -> IO ()  
textViewLoadFile tv path = textViewSetText tv =<< readFile path

-- | Set text.
textViewSetText :: TextViewClass self => self -> String -> IO ()
textViewSetText tv str = do
  tb <- textViewGetBuffer tv
  textBufferSetText tb str

-- | Get text iter from text view.
textViewGetTextIter :: TextViewClass self => self -> IO TextIter
textViewGetTextIter tv = 
    textBufferGetInsertIter 
        =<< textViewGetBuffer tv

-- | Move text iter
textViewMoveTextIter :: (TextViewClass self, ScrolledWindowClass swc) => (TextIter -> IO Bool) -> self -> swc -> IO ()
textViewMoveTextIter move tv sw = do
  -- Action for text iter.
  ti <- textViewGetTextIter tv
  move ti

  -- Move cursor place.
  tb <- textViewGetBuffer tv
  textBufferPlaceCursor tb ti

  -- Scroll view to mark of iter.
  aj <- scrolledWindowGetVAdjustment sw
  vl <- adjustmentGetValue aj
  y  <- (<<<=) (i2d . fst) (textViewGetLineYrange tv) 
                               =<< textViewGetTextIter tv
  if y <= vl
     -- When mark below or equal current top y coordinate.
     -- just scroll page to avoid screen jump from top to end.
     then adjustmentSetValue aj y
     -- Otherwise scroll screen to including mark.
     else textViewScrollCursorOnscreen tv 

  return ()

-- | Forward line.
textViewForwardLine :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewForwardLine = 
    textViewMoveTextIter $ \iter -> do
      -- Whether at last line.
      lastLine <- textIterIsLastLine iter
      -- Do nothing when at last line.
      unless lastLine $ textIterForwardLine iter >> return ()
      return True 

-- | Backward line.
textViewBackwardLine :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewBackwardLine = textViewMoveTextIter textIterBackwardLine

-- | Forward char.
textViewForwardChar :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewForwardChar = textViewMoveTextIter textIterForwardChar

-- | Backward char.
textViewBackwardChar :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewBackwardChar = textViewMoveTextIter textIterBackwardChar

-- | Forward word.
textViewForwardWord :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewForwardWord = textViewMoveTextIter textIterForwardWordEnd

-- | Backward word.
textViewBackwardWord :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewBackwardWord = textViewMoveTextIter textIterBackwardWordStart

-- | Move to buffer beginning.
textViewBegin :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewBegin = 
    textViewMoveTextIter $ \t -> textIterBackwardToStart t >> return True

-- | Move to buffer end.
textViewEnd :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewEnd =
    textViewMoveTextIter $ \t -> textIterForwardToEnd t >> return True

-- | Get text.
textViewGetText :: TextViewClass self => self -> IO String
textViewGetText tv = do
  tb           <- textViewGetBuffer tv
  (start, end) <- textBufferGetBounds tb
  textBufferGetText tb start end True

-- | Get current line.
textViewGetCurrentLineContent :: TextViewClass self => self -> IO String
textViewGetCurrentLineContent tv = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv
  
  -- Get line start and end iter.
  startIter <- textBufferGetCurrentLineStartIter tb
  endIter   <- textBufferGetCurrentLineEndIter_ tb

  -- Get current line.
  textBufferGetText tb startIter endIter True

-- | Get current line number.
textViewGetLine :: TextViewClass self => self -> IO Int
textViewGetLine = 
    (<<<=) succ ((<=<) textIterGetLine textViewGetTextIter)

-- | Get current column number
textViewGetColumn :: TextViewClass self => self -> IO Int
textViewGetColumn =
    (<=<) textIterGetLineOffset textViewGetTextIter

-- | Select all.
textViewSelectAll :: TextViewClass self => self -> IO ()
textViewSelectAll tv = do
  -- Get text buffer.
  tb           <- textViewGetBuffer tv

  -- Get start end iter.
  (start, end) <- textBufferGetBounds tb

  -- Selection all.
  textBufferSelectRange tb start end

  -- Update selection iter.
  textBufferUpdateSelectionIter tb

-- | Delete.
textViewDelete :: TextViewClass self => self -> Bool -> Bool -> IO Bool
textViewDelete tv interactive defaultEditable = do
  tb <- textViewGetBuffer tv
  textBufferDeleteSelection tb interactive defaultEditable

-- | Cut.
textViewCut :: TextViewClass self => self -> IO ()
textViewCut tv = do
  tb   <- textViewGetBuffer tv
  clip <- clipboardGet selectionClipboard
  textBufferCutClipboard tb clip True

-- | Copy.
textViewCopy :: TextViewClass self => self -> IO ()
textViewCopy tv = do
  tb   <- textViewGetBuffer tv
  clip <- clipboardGet selectionClipboard
  textBufferCopyClipboard tb clip

-- | Paste.
textViewPaste :: TextViewClass self => self -> IO ()
textViewPaste tv = do
  tb   <- textViewGetBuffer tv
  clip <- clipboardGet selectionClipboard
  textBufferPasteClipboardAtCursor tb clip True

-- | Delete forward char.
textViewDeleteForwardChar :: TextViewClass self => self -> Bool -> IO Bool
textViewDeleteForwardChar tv = 
    textViewDeleteBound tv textIterForwardChar

-- | Delete backward char.
textViewDeleteBackwardChar :: TextViewClass self => self -> Bool -> IO Bool
textViewDeleteBackwardChar tv = 
    textViewDeleteBound tv textIterBackwardChar

-- | Delete forward word.
textViewDeleteForwardWord :: TextViewClass self => self -> Bool -> IO Bool
textViewDeleteForwardWord tv = 
    textViewDeleteBound tv textIterForwardWordEnd

-- | Delete backward word.
textViewDeleteBackwardWord :: TextViewClass self => self -> Bool -> IO Bool
textViewDeleteBackwardWord tv = 
    textViewDeleteBound tv textIterBackwardWordStart

-- | Delete to line end.
textViewDeleteToLineEnd :: TextViewClass self => self -> Bool -> IO Bool
textViewDeleteToLineEnd tv = 
    textViewDeleteBound tv textIterForwardToLineEnd

-- | Delete to line start.
textViewDeleteToLineStart :: TextViewClass self => self -> Bool -> IO Bool
textViewDeleteToLineStart tv = 
    textViewDeleteBound tv (\t -> textIterBackwardToLineStart t >> return True)

-- | Delete bound.
textViewDeleteBound :: TextViewClass self => self -> (TextIter -> IO Bool) -> Bool -> IO Bool
textViewDeleteBound tv move deleteWhenEditable = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv

  -- Get start iter.
  start <- textViewGetTextIter tv
  offset <- textIterGetOffset start

  -- Move iter.
  move start

  -- Get end iter.
  end <- textViewGetTextIter tv

  -- Build delete/restore action.
  let deleteAction = do
        textBufferDelete tb start end
        return True
      restoreAction = do
        textIterSetOffset end offset
        return False

  isEditable <- textIterBoundIsEditable (start, end)
  if deleteWhenEditable && not isEditable
     -- Restore if can't delete. 
     then restoreAction
     -- Otherwise delete bound.
     else deleteAction

-- | Duplicate lines.
-- Just duplicate current line to below if haven't selection anything.
-- Otherwise duplicate selection lines to below.
textViewDupLines :: TextViewClass self => self -> Bool -> IO ()
textViewDupLines tv dupBelow = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv

  -- Get selection bound line.
  (sIter, eIter, _, lastLine) <- textBufferGetSelectionBoundLines tb

  if dupBelow
     -- Duplicate below.
     then textBufferKeepSelection tb True True $ 
          do
            -- Record end iter mark.
            eMark <- textBufferCreateMark tb Nothing eIter True -- make mark before insert
  
            -- Insert duplicate content.
            textBufferInsertRange tb eIter sIter eIter

            -- Add newline after end iterator when end iter at last line of buffer.
            when lastLine $ textBufferInsertNewlineAtMark tb eMark
     -- Duplicate above
     else textBufferKeepSelection tb False False $
          do 
            -- Record start iter mark.
            sMark <- textBufferCreateMark tb Nothing sIter False -- make mark after insert

            -- Insert duplicate content.
            textBufferInsertRange tb sIter sIter eIter

            -- Add newline before start iter when start iter at last line of buffer.
            when lastLine $ textBufferInsertNewlineAtMark tb sMark

textViewDupLinesBelow, textViewDupLinesAbove :: TextViewClass self => self -> IO ()
textViewDupLinesBelow tv = textViewDupLines tv True
textViewDupLinesAbove tv = textViewDupLines tv False

-- | Transposes lines.
-- Just transposes current line to below if haven't selection anything.
-- Otherwise transposes selection lines to below.
textViewTraLines :: TextViewClass self => self -> Bool -> IO ()
textViewTraLines tv traBelow = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv
  
  -- Get selection bound line.
  (sIter, eIter, firstLine, lastLine) <- textBufferGetSelectionBoundLines tb
  
  -- Transposes lines.
  if traBelow
     then 
         unless lastLine $ textBufferKeepSelection tb False False $
                do
                  -- Record iter mark.
                  sMark <- textBufferCreateMark tb Nothing sIter False
                  eMark <- textBufferCreateMark tb Nothing eIter False

                  -- Get start and end iter to move.
                  trasStartIter <- textIterCopy eIter
                  trasEndIter   <- textIterCopy eIter
                  atLastLine    <- textIterIsLastLine eIter
                  chars         <- textIterGetCharsInLine eIter
                  textIterSetLineOffset trasEndIter chars

                  -- Move range.
                  textBufferMoveRange tb sIter trasStartIter trasEndIter

                  -- Adjust when reach last line.
                  when atLastLine $ do
                    -- Insert new line.
                    textBufferInsertNewlineAtMark tb sMark

                    -- Delete last char.
                    textBufferDeleteLastChar tb

                  -- Scroll screen to display iter.
                  textViewScrollMarkOnscreen tv eMark
     else 
         unless firstLine $ textBufferKeepSelection tb True True $
                do
                  -- Record iter mark.
                  sMark <- textBufferCreateMark tb Nothing sIter True
                  eMark <- textBufferCreateMark tb Nothing eIter True

                  -- Get start and end iter to move.
                  trasStartIter <- textIterCopy sIter
                  trasEndIter <- textIterCopy sIter
                  textIterBackwardLine trasStartIter

                  -- Move range.
                  textBufferMoveRange tb eIter trasStartIter trasEndIter

                  -- Adjust when reach last line.
                  when lastLine $ do
                    -- Insert newline.
                    textBufferInsertNewlineAtMark tb eMark

                    -- Delete last char.
                    textBufferDeleteLastChar tb

                  -- Scroll screen to display iter.
                  textViewScrollMarkOnscreen tv sMark

textViewTraLinesBelow, textViewTraLinesAbove :: TextViewClass self => self -> IO ()
textViewTraLinesBelow tv = textViewTraLines tv True
textViewTraLinesAbove tv = textViewTraLines tv False

-- | Delete lines.
-- Just delete current line to below if haven't selection anything.
-- Otherwise delete selection lines to below.
-- Return 'False' if in uneditable area.
textViewDelLines :: TextViewClass self => self -> IO Bool
textViewDelLines tv = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv

  -- Get selection bound line.
  (sIter, eIter, _, lastLine) <- textBufferGetSelectionBoundLines tb

  isEditable <- textIterBoundIsEditable (sIter, eIter)
  if isEditable  
     -- Just delete lines when in editable area.
     then do
       -- Delete lines.
       textBufferDelete tb sIter eIter
       
       -- Adjust when reach last line.
       when lastLine $ do
         -- Delete last char.
         textViewDeleteBackwardChar tv False
       
         -- Move to line start.
         iter <- textViewGetTextIter tv
         textIterBackwardToLineStart iter
         textBufferPlaceCursor tb iter

       return True
     else return False

-- | Smart home.
textViewSmartHome :: TextViewClass self => self -> IO ()
textViewSmartHome tv = do
  -- Get iter information.
  ti <- textViewGetTextIter tv
  lo <- textIterGetLineOffset ti
  tb <- textViewGetBuffer tv

  -- Move to line end and got search limit.
  textIterForwardToLineEnd_ ti 
  limit <- textIterCopy ti

  -- Init to line start and search forward visible char.
  textIterBackwardToLineStart ti

  -- Move iter.
  ifM (textIterForwardFindChar ti (/= ' ') (Just limit))
           (do
             nlo <- textIterGetLineOffset ti
             if lo == nlo || nlo == 1
                -- Move to line start when iter at indent position
                -- or current line is empty line.
                then textIterBackwardToLineStart ti
                -- Otherwise move to line indent position.
                else textIterSetLineOffset ti nlo)
           -- Move to line start when search failed.
           (textIterBackwardToLineStart ti)

  -- Move cursor to iter place.
  textBufferPlaceCursor tb ti

-- | Smart end.
textViewSmartEnd :: TextViewClass self => self -> IO ()
textViewSmartEnd tv = do
  -- Get iter information.
  ti <- textViewGetTextIter tv
  lo <- textIterGetLineOffset ti
  tb <- textViewGetBuffer tv

  -- Move to line start and got search limit
  textIterBackwardToLineStart ti
  limit <- textIterCopy ti

  -- Init to line end and search backward visible char.
  textIterForwardToLineEnd_ ti

  -- Move iter.
  ifM (textIterBackwardFindChar ti (/= ' ') (Just limit))
          (do
            nlo <- textIterGetLineOffset ti
            let nl = nlo + 1 
            if lo == nl || nlo == 0
               -- Move to line end when iter at last visible char
               -- or current line is empty line.
               then textIterForwardToLineEnd_ ti
               -- Otherwise move to last visible char.
               else textIterSetLineOffset ti nl)
           -- Move to line end when search failed.
           (textIterForwardToLineEnd_ ti)

  -- Move cursor to iter place.
  textBufferPlaceCursor tb ti

-- | Scroll vertical.
textViewScrollVertical :: (TextViewClass tvc, ScrolledWindowClass swc) => tvc -> swc -> Double -> IO ()
textViewScrollVertical tv sw inc = do
  -- Get adjustment arguments.
  aj <- scrolledWindowGetVAdjustment sw
  ps <- adjustmentGetPageSize aj
  vl <- adjustmentGetValue aj
  ur <- adjustmentGetUpper aj
  lr <- adjustmentGetLower aj

  -- Get text iter arguments.
  ti <- textViewGetTextIter tv
  tb <- textViewGetBuffer tv
  (y, lnH) <- textViewGetLineYrange tv ti

  -- Adjust scroll value.
  let iv = vl + inc
      av = if inc >= 0
              -- Don't bigger than upper value when scroll up.
              then min iv (ur - i2d lnH)
              -- Don't less than lower value when scroll down.
              else max iv lr
  adjustmentSetValue aj av

  -- Adjust iter position.
  top <- adjustmentGetValue aj
  let bottom = top + ps
  if top >= i2d y
     -- Adjust iter to first line in view visual port 
     -- when iter is up out view visual port.
     then do
       (nti, _) <- textViewGetLineAtY tv (truncate top)
       textBufferPlaceCursor tb nti
     -- Adjust iter to last line in view visual port
     -- when iter is down out view virtual port.
     else when (bottom <= i2d (y + lnH)) $
          do
            (nti, _) <- textViewGetLineAtY tv (truncate (bottom - i2d lnH))
            textBufferPlaceCursor tb nti

-- | Toggle selection.
textViewToggleSelectionMark :: TextViewClass self => self -> IO ()  
textViewToggleSelectionMark tv = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv

  -- Whether selection active.
  selectionActive <- textBufferHasSelection tb

  -- Toggle selection iter.
  if selectionActive 
     then textViewCancelSelectionMark tv
     else textViewSetSelectionMark tv

-- | Exchange selection mark.
textViewExchangeSelectionMark :: TextViewClass self => self -> IO ()  
textViewExchangeSelectionMark tv = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv

  -- Whether selection active.
  selectionActive <- textBufferHasSelection tb

  -- Exchange selection mark when selection active.
  when selectionActive $ do
    -- Get insert and bound iter.
    (sIter, eIter) <- textBufferGetSelectionBounds tb
    -- Get new iter along with selection direction.
    selectionAbove <- textBufferIsEqualInsertIter tb sIter
    let (startIter, endIter) = 
            if selectionAbove
               then (eIter, sIter)
               else (sIter, eIter)
    -- Set selection mark.
    textBufferSetSelectionIter tb endIter
    -- Set selection range.
    textBufferSelectRange tb startIter endIter
    -- Move current cursor in screen.
    textViewScrollCursorOnscreen tv

-- | Scroll text view the minimum distance such that cursor is contained within the visible area of widget.
textViewScrollCursorOnscreen :: TextViewClass self => self -> IO ()
textViewScrollCursorOnscreen tv =
    textViewScrollMarkOnscreen tv 
        =<< (<=<) textBufferGetInsert textViewGetBuffer tv

-- | Set selection.
textViewSetSelectionMark :: TextViewClass self => self -> IO ()
textViewSetSelectionMark tv = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv
  
  -- Cancel selection first.
  textBufferCancelSelection tb
  
  -- Set new selection iter.
  textBufferSetSelectionIter tb 
      =<< textViewGetTextIter tv
  
-- | Cancel selection.
textViewCancelSelectionMark :: TextViewClass self => self -> IO ()  
textViewCancelSelectionMark tv = do
  -- Get text buffer.
  tb <- textViewGetBuffer tv
  
  -- Cancel selection first.
  textBufferCancelSelection tb
  
  -- Cancel new selection iter.
  textBufferRemoveSelectionIter tb

-- | Apply selection.
textViewApplySelectionMark :: TextViewClass self => self -> IO ()  
textViewApplySelectionMark tv = do
  tb <- textViewGetBuffer tv
  textBufferGetSelectionIter tb >>= 
    (?>= (\markIter -> do
            -- Get insert iter.
            insertIter <- textBufferGetInsertIter tb
            -- Selection range.
            textBufferSelectRange tb insertIter markIter))

-- | Text view newline.
textViewNewLine :: TextViewClass self => self -> IO ()  
textViewNewLine tv = 
  textViewInsertAtCursor tv "\n"

-- | Insert string at cursor.
textViewInsertAtCursor :: TextViewClass self => self -> String -> IO ()  
textViewInsertAtCursor tv str = do
  -- Cancel selection first.
  tb <- textViewGetBuffer tv
  textBufferCancelSelection tb
  -- Insert string.
  textBufferInsertAtCursor tb str 
  -- Scroll cursor to screen.
  textViewScrollCursorOnscreen tv

-- | Move to next line and then opens a line.
textViewOpenNewlineBelow :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewOpenNewlineBelow tv swc = do
  -- Get text buffer and iter.
  tb   <- textViewGetBuffer tv
  iter <- textViewGetTextIter tv
  -- Whether in blank line.
  inBlankLine <- textIterInBlankLine iter
  -- Move iter to line end and insert newline.
  textIterForwardToLineEnd_ iter
  textBufferInsert tb iter "\n"
  -- Don't forward line at blank line.
  unless inBlankLine $ textViewForwardLine tv swc

-- | Move to previous line and then opens a line.
textViewOpenNewlineAbove :: (TextViewClass self, ScrolledWindowClass swc) => self -> swc -> IO ()
textViewOpenNewlineAbove tv swc = do
  -- Get text buffer and iter.
  tb   <- textViewGetBuffer tv
  iter <- textViewGetTextIter tv
  -- Move iter to line start and insert newline.
  textIterBackwardToLineStart iter
  textBufferInsert tb iter "\n"
  -- Backward line.
  textViewBackwardLine tv swc
  
-- | Move to `first iter` of buffer.
textIterBackwardToStart :: TextIter -> IO ()  
textIterBackwardToStart ti = do
  textIterSetLine ti 0
  textIterBackwardToLineStart ti

-- | Forward to line end, like textIterForwardToLineEnd, 
-- but don't move to next line when current line is empty.
textIterForwardToLineEnd_ :: TextIter -> IO ()
textIterForwardToLineEnd_ ti = 
    textIterSetLineOffset ti 
        =<< textIterGetCharsInLine_ ti

-- | In blank line.
textIterInBlankLine :: TextIter -> IO Bool
textIterInBlankLine =
    (<<<=) (1 ==) textIterGetCharsInLine 

-- | Is first line.
textIterIsFirstLine :: TextIter -> IO Bool  
textIterIsFirstLine =
    (<<<=) (0 ==) textIterGetLine

-- | Is last line.
textIterIsLastLine :: TextIter -> IO Bool
textIterIsLastLine ti = do
  line  <- textIterGetLine ti
  lines <- (<=<) textBufferGetLineCount textIterGetBuffer ti
  return $ line == (lines - 1)

-- | Backward to line start.
textIterBackwardToLineStart :: TextIter -> IO ()  
textIterBackwardToLineStart ti =
    textIterSetLineOffset ti 0

-- | Return number of characters in this line.
-- The return value not includes delimiters.
textIterGetCharsInLine_ :: TextIter -> IO Int
textIterGetCharsInLine_ ti = do
  -- Get number of characters in this line.
  chars <- textIterGetCharsInLine ti
  ifM (textIterIsLastLine ti)
      -- Return chars if at last line.
      (return chars)
      -- Otherwise not includes delimiter.
      (return $ chars - 1)

-- | Whether selection iter is editable.
textIterBoundIsEditable :: (TextIter, TextIter) -> IO Bool
textIterBoundIsEditable (startIter, endIter) = 
    liftM2 (&&) 
           (textIterEditable startIter True)
           (textIterEditable endIter True)