packages feed

phoityne-0.0.1.0: app/Phoityne/IO/GUI/GTK/TextEditor.hs

{-# LANGUAGE GADTs               #-}
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE MultiWayIf          #-}
{-# LANGUAGE BinaryLiterals      #-}
{-# LANGUAGE TemplateHaskell     #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveDataTypeable  #-}

module Phoityne.IO.GUI.GTK.TextEditor (
  TextEditorData(..)
, LineTextDoubleClickedHandler
, CodeNoteCloseEventHandler
, CodeTextKeyPressEventHandler
, CodeBufferChangedEventHandler
, CodeBufferDeleteRangeEventHandler
, CodeBufferInsertTextEventHandler
, setupCodeNote
, activateCodeNote
, highLightBreakPoint
, offLightBreakPoint
, addBreakPointTagAtLine
, isCurrentTextEditor
, getCodeViewContent
, isTextEditorModified
, setTextEditorModified
, getCodeTextLineNumber
, blockIndentTextEditor
, blockUnIndentTextEditor
, blockCommentTextEditor
, blockUnCommentTextEditor
, setContent2TextEditor
, setCursorOnTextEditor
, deleteRangeOnTextEditor
, insertText2TextEditor
, getSelectedText
, deleteBreakPointTag
, deleteBreakPointTagAtLine
, updateBreakPointTag
, searchEndIter
) where

-- モジュール
import Phoityne.Constant
import Phoityne.IO.GUI.GTK.Constant
import Phoityne.IO.GUI.GTK.Utility

-- システム
import System.Log.Logger
import GHC.Float
import Graphics.UI.Gtk
import Control.Monad
import Control.Monad.IO.Class
import Data.String.Utils
import qualified Control.Exception as E
import qualified Data.Text as T
import qualified Data.ByteString as BS

-- |
--  
-- 
type LineTextDoubleClickedHandler
  =  Bool    -- isTagExists
  -> Int     -- lineNo
  -> IO ()

-- |
--  
-- 
type HSCode = BS.ByteString

-- |
--  
-- 
type CodeNoteCloseEventHandler = TextEditorData -> IO ()

-- |
--  
-- 
data TextEditorData =
  TextEditorData {
    lineTextEditorData :: TextView
  , codeTextEditorData :: TextView
  , boxTextEditorData  :: HBox
  , tabLabelTextEditorData :: Label
  } deriving (Eq)

-- |
--
--
type CodeTextKeyPressEventHandler = String -> Bool -> Bool -> Bool -> Int -> IO Bool

-- |
--
--
type CodeBufferChangedEventHandler = IO ()

-- |
--
--
type CodeBufferDeleteRangeEventHandler = (Int, Int) -> (Int, Int) -> String -> IO ()

-- |
--
--
type CodeBufferInsertTextEventHandler = (Int, Int) -> String -> IO ()


-- |
--
--
searchEndIter :: TextEditorData -> Int -> Int -> String -> IO (Int, Int)
searchEndIter (TextEditorData _ codeView _ _) startLineNo startColNo str = do
  buf <- textViewGetBuffer codeView
  iter <- textBufferGetIterAtLineOffset buf startLineNo startColNo
  forwardTextIter iter $ length str
  lineNo <- textIterGetLine iter
  colNo  <- textIterGetLineOffset iter
  return (lineNo, colNo)


-- |
--
--
setupCodeNote :: Builder
              -> String       -- ファイル名
              -> FilePath     -- ファイルパス
              -> HSCode       -- コンテンツ
              -> CodeNoteCloseEventHandler
              -> LineTextDoubleClickedHandler
              -> Maybe Int    -- スクロールライン
              -> CodeTextKeyPressEventHandler
              -> CodeBufferChangedEventHandler
              -> CodeBufferDeleteRangeEventHandler
              -> CodeBufferInsertTextEventHandler
              -> IO TextEditorData
setupCodeNote builder name path code evh lineViewEvh _ codeKeyEVH bufEVH delRangeEVH insertTextEVH = do
  img <- imageNewFromIcon (T.pack "window-close") 8
  withIcon img
  where
    withIcon Nothing = do
      criticalM _LOG_NAME $ "[setupCodeNote] close icon not found."
      E.throwIO . userError $ "[setupCodeNote] close icon not found."
    withIcon (Just image) = do
      -- ページタブのラベルとクローズボタンの生成
      box <- hBoxNew False 0
      label <- labelNew $ Just name
    
      fontDesc <- fontDescriptionNew
      fontDescriptionSetFamily fontDesc (_FONT_DESC)
      fontDescriptionSetSize fontDesc 9
      widgetOverrideFont label $ Just fontDesc
    
      closeButton <- toolButtonNew (Just image) (Nothing::Maybe T.Text)
    
      boxPackStart box label PackNatural 0
      boxPackStart box closeButton PackNatural 0
      widgetShowAll box
    
      menuWidget <- labelNew $ Just path 
      widgetOverrideFont menuWidget $ Just fontDesc
    
      -- ノートブックに新規ページの生成
      note <- builderGetObject builder castToNotebook _NAME_CODE_NOTE
    
      textEditor <- createTextEditor builder code lineViewEvh label codeKeyEVH bufEVH delRangeEVH insertTextEVH
    
      _ <- notebookAppendPageMenu note (boxTextEditorData textEditor) box menuWidget
    
      notebookSetTabReorderable note (boxTextEditorData textEditor) True
    
      _ <- onToolButtonClicked closeButton (codeNoteCloseEventHandler note textEditor evh)
    
      setFont note
    
      widgetShowAll note
    
      -- activateCodeNote builder textEditor lineNo
    
      return textEditor

-- |
--
--
createTextEditor :: Builder
                 -> HSCode
                 -> LineTextDoubleClickedHandler
                 -> Label
                 -> CodeTextKeyPressEventHandler
                 -> CodeBufferChangedEventHandler
                 -> CodeBufferDeleteRangeEventHandler
                 -> CodeBufferInsertTextEventHandler
                 -> IO TextEditorData
createTextEditor builder code evh tabLabel codeKeyEVH bufEVH delRangeEVH insertTextEVH = do

  tagTable <- builderGetObject builder castToTextTagTable _NAME_TAG_TABLE
  lineBuf  <- textBufferNew $ Just tagTable
  codeBuf  <- textBufferNew $ Just tagTable
  lineTextView <- textViewNewWithBuffer lineBuf
  codeTextView <- textViewNewWithBuffer codeBuf

  textTagTableLookup tagTable (T.unpack _BREAK_POINT_HIGHLIGHT_TAG) >>= \case
    Just _  -> return ()
    Nothing -> do
      tag <- textTagNew (Just _BREAK_POINT_HIGHLIGHT_TAG)
      set tag [textTagBackground := "blue", textTagForeground := "white", textTagBackgroundSet := True]
      textTagTableAdd tagTable tag

  box <- hBoxNew False 0

  lineScroll <- scrolledWindowNew Nothing Nothing
  scrolledWindowSetPlacement lineScroll CornerTopRight

  codeScroll <- scrolledWindowNew Nothing Nothing

  ajust <- scrolledWindowGetVAdjustment codeScroll
  scrolledWindowSetVAdjustment lineScroll ajust
  set lineScroll [ scrolledWindowHscrollbarPolicy := PolicyNever
                 , scrolledWindowVscrollbarPolicy := PolicyAutomatic]

  textViewSetBorderWindowSize codeTextView TextWindowTop 5
  textViewSetBorderWindowSize codeTextView TextWindowBottom 5
  textViewSetBorderWindowSize codeTextView TextWindowLeft 5
  textViewSetBorderWindowSize codeTextView TextWindowRight 5

  textViewSetBorderWindowSize lineTextView TextWindowTop 5
  textViewSetBorderWindowSize lineTextView TextWindowBottom 5
  --textViewSetBorderWindowSize lineTextView TextWindowLeft 10
  --textViewSetBorderWindowSize lineTextView TextWindowRight 5
  textViewSetEditable lineTextView True
  textViewSetCursorVisible lineTextView False
  textViewSetJustification lineTextView JustifyRight

  widgetModifyBg lineTextView StateNormal $ Color 0xd8d8 0xd8d8 0xd8d8

  containerAdd lineScroll lineTextView
  containerAdd codeScroll codeTextView

  boxPackStart box lineScroll PackNatural 0
  boxPackStart box codeScroll PackGrow 0

  setFont lineTextView
  setFont codeTextView

  _ <- on lineTextView buttonPressEvent $ lineTextDoubleClickedHandler lineTextView evh

  codeBuf <- textViewGetBuffer codeTextView
  lineBuf <- textViewGetBuffer lineTextView

  textBufferSetByteString codeBuf code
  lineCount <- textBufferGetLineCount codeBuf
  
  let lineStr = createLineNoText [1..lineCount] ""
  textBufferSetText lineBuf lineStr

  fontDesc <- fontDescriptionNew
  fontDescriptionSetFamily fontDesc (_FONT_DESC)
  fontDescriptionSetSize fontDesc 9
  widgetOverrideFont (castToWidget lineTextView) $ Just fontDesc
  widgetOverrideFont (castToWidget codeTextView) $ Just fontDesc

  {- 
  -- 行番号の垂直スクロールバーを消す。お試し。
  widgetSetSizeRequest (castToWidget lineScroll) 0 (-1)
  scrolledWindowGetVScrollbar lineScroll >>= \case
    Nothing -> return ()
    Just s  -> widgetHide (castToWidget s)
  -}

  saveBt <- builderGetObject builder castToToolButton _NAME_TOOL_BT_SAVE
  textBufferSetModified codeBuf False
  _ <- on codeBuf bufferChanged $ codeTextBufferChangedHandler lineBuf codeBuf bufEVH
  _ <- on codeBuf modifiedChanged $ codeTextModifiedHandler codeBuf tabLabel saveBt
  _ <- on codeTextView keyPressEvent $ codeTextKeyPressEventHandler lineTextView codeTextView codeKeyEVH
  _ <- on codeTextView buttonPressEvent $ codeTextButtonPressEventHandler codeTextView
  _ <- on codeBuf deleteRange $ codeTextDeleteRangeEventHandler codeTextView delRangeEVH
  _ <- on codeBuf bufferInsertText $ codeTextInsertTextEventHandler codeTextView insertTextEVH

  return $ TextEditorData lineTextView codeTextView box tabLabel

  where
    createLineNoText [] acc = acc
    createLineNoText (x:[]) acc = acc ++ " " ++ show x ++ " "
    createLineNoText (x:xs) acc = acc ++ " " ++ show x ++ " \n" ++ createLineNoText xs acc

    codeTextModifiedHandler codeBuf label saveBt = do
      textBufferGetModified codeBuf >>= \case
        False -> do
          str <- labelGetText label
          labelSetText label $ init . init $ (str :: String)
          widgetSetSensitive saveBt False
        True  -> do
          str <- labelGetText label
          labelSetText label $ str ++ (" *"::String)
          widgetSetSensitive saveBt True

    codeTextBufferChangedHandler lineBuf codeBuf _ = do
      ll <- textBufferGetLineCount lineBuf
      cl <- textBufferGetLineCount codeBuf
      updateLineNo lineBuf codeBuf ll cl

    updateLineNo lineBuf _ ll cl
      | ll == cl = return ()
      | ll < cl = do
        let lineTxt = "\n" ++ createLineNoText [ll+1 .. cl] ""
        endIter <- textBufferGetEndIter lineBuf
        textBufferInsert lineBuf endIter lineTxt
      | otherwise = do
        startIter <- textBufferGetIterAtLine lineBuf (cl-1)
        textIterForwardToLineEnd startIter
        endIter <- textBufferGetEndIter lineBuf
        textBufferDelete lineBuf startIter endIter

    codeTextDeleteRangeEventHandler view evh si ei = do
      slineNo <- textIterGetLine si
      sColNo  <- textIterGetLineOffset si
      elineNo <- textIterGetLine ei
      eColNo  <- textIterGetLineOffset ei

      buf <- textViewGetBuffer view
      str <- textBufferGetText buf si ei False
      evh (slineNo, sColNo) (elineNo, eColNo) str

    codeTextInsertTextEventHandler _ evh si str = do
      startLineNo <- textIterGetLine si
      startColNo <- textIterGetLineOffset si
      evh (startLineNo, startColNo) str

-- |
--  Event Handler
-- 
codeNoteCloseEventHandler :: Notebook -> TextEditorData -> CodeNoteCloseEventHandler -> IO ()
codeNoteCloseEventHandler note wid evh = do
  notebookPageNum note (boxTextEditorData wid) >>= \case
    Just idx -> notebookRemovePage note idx
    Nothing  -> errorM _LOG_NAME $ "[codeNoteCloseEventHandler]unexpected error."
  evh wid


-- |
--  
-- 
activateCodeNote :: Builder -> TextEditorData -> Int -> IO ()
activateCodeNote builder wid lineNo = do
  note <- builderGetObject builder castToNotebook _NAME_CODE_NOTE

  notebookPageNum note (boxTextEditorData wid) >>= \case
    Just idx -> notebookSetCurrentPage note idx
    Nothing  -> errorM _LOG_NAME $ "[activateCodeNote]invalid node page."

  window <- builderGetObject builder castToWindow _WINDOW_NAME
  widgetShowAll window
  forceEvent

  let codeTextView = codeTextEditorData wid
  codeBuf <- textViewGetBuffer codeTextView
  iter <- textBufferGetIterAtLine codeBuf lineNo
  _ <- textViewScrollToIter codeTextView iter 0.0 (Just (0.0, 0.5))

  widgetGrabFocus codeTextView

-- |
--  Event Handler
-- 
lineTextDoubleClickedHandler :: TextView -> LineTextDoubleClickedHandler -> EventM EButton Bool
lineTextDoubleClickedHandler self evh = eventClick >>= \case
  DoubleClick -> doubleClicked >> return True
  _           -> return False

  where
    doubleClicked = do
      (_, posYd) <- eventCoordinates 
      liftIO $ do
        scrollY <- textViewGetVadjustment self >>= adjustmentGetValue
        let posY = scrollY + posYd

        (iter, _) <- textViewGetLineAtY self $ double2Int posY
        isTagExists <- textIterBeginsTag iter Nothing
        lineNo <- textIterGetLine iter
        evh isTagExists lineNo


-- |
--
--
updateBreakPointTag :: TextEditorData -> Bool -> Int -> IO ()
updateBreakPointTag (TextEditorData lineView _ _ _) True  lineNo = deleteBreakPointTagAtLine lineView lineNo
updateBreakPointTag (TextEditorData lineView _ _ _) False lineNo = addBreakPointTagAtLine lineView lineNo


-- |
--
--
addBreakPointTagAtLine :: TextView -> Int -> IO ()
addBreakPointTagAtLine self line = do
  buf  <- textViewGetBuffer self
  iter <- textBufferGetIterAtLine buf line
  addBreakPointTagAtIter self iter

-- |
--
--
addBreakPointTagAtIter :: TextView -> TextIter -> IO ()
addBreakPointTagAtIter self startIter = do
  textBuf <- textIterGetBuffer startIter
  tagTable <- textBufferGetTagTable textBuf

  tag <- textTagNew Nothing
  set tag [textTagBackground := "blue", textTagForeground := "white", textTagBackgroundSet := True]
  textTagTableAdd tagTable tag
 
  endIter <- textIterCopy startIter
  _ <- textViewForwardDisplayLineEnd self endIter

  textBufferApplyTag textBuf tag startIter endIter


-- |
--  指定行についているタグをすべて削除する
--
deleteBreakPointTagAtLine :: TextView -> Int -> IO ()
deleteBreakPointTagAtLine textView line = do
  buf  <- textViewGetBuffer textView
  iter <- textBufferGetIterAtLine buf line
  deleteBreakPointTagAtIter iter

-- |
--  TextIterの位置についているタグをすべて削除する
--
deleteBreakPointTagAtIter :: TextIter -> IO ()
deleteBreakPointTagAtIter iter = do
  textBuf  <- textIterGetBuffer iter
  tagTable <- textBufferGetTagTable textBuf
  tags     <- textIterGetTags iter
  mapM_ (textTagTableRemove tagTable) tags

-- |
--
--
deleteBreakPointTag :: TextEditorData -> Int -> IO ()
deleteBreakPointTag (TextEditorData lineView _ _ _) lineNo = do
  textBuf  <- textViewGetBuffer lineView
  tagTable <- textBufferGetTagTable textBuf
  iter     <- textBufferGetIterAtLine textBuf (lineNo - 1)
  tags     <- textIterGetTags iter
  mapM_ (textTagTableRemove tagTable) tags

-- |
--  
--
highLightBreakPoint :: TextEditorData -> Int -> Int -> Int -> Int -> IO ()
highLightBreakPoint textEditor sl sc el ec = do

  let codeTextView = codeTextEditorData textEditor
  codeBuf <- textViewGetBuffer codeTextView
  --tagTable <- textBufferGetTagTable codeBuf

  startLine <- normalizeLine codeBuf sl
  startCol <- normalizeColumn codeBuf startLine sc

  endLine <- normalizeLine codeBuf el
  endCol <- normalizeColumn codeBuf endLine ec

  startIter <- textBufferGetIterAtLineOffset codeBuf startLine startCol
  endIter   <- textBufferGetIterAtLineOffset codeBuf endLine (endCol + 1)

  textBufferPlaceCursor codeBuf endIter
  textBufferSelectRange codeBuf startIter endIter

  where
    normalizeLine buf l = do
      nl <- normalizeLine_ buf l
      return $ if 0 > nl then 0 else nl

    normalizeLine_ buf l = do
      allLines <- textBufferGetLineCount buf
      return $ if l > allLines then allLines - 1 else l - 1

    normalizeColumn buf line col = do
      nc <- normalizeColumn_ buf line col 
      return $ if 0 > nc then 0 else nc

    normalizeColumn_ buf line col = do
      iter <- textBufferGetIterAtLine buf line
      allChars <- textIterGetCharsInLine iter -- -1 for delimiters
      let allChars' = allChars - 1
      return $ if col > allChars' then allChars' - 1 else col - 1


-- |
--  
--
offLightBreakPoint :: TextEditorData -> IO ()
offLightBreakPoint textEditor = do

  let codeTextView = codeTextEditorData textEditor
  codeBuf <- textViewGetBuffer codeTextView
 
  startIter <- textBufferGetStartIter codeBuf
  endIter   <- textBufferGetEndIter codeBuf

  textBufferRemoveTagByName codeBuf _BREAK_POINT_HIGHLIGHT_TAG startIter endIter

  return ()

-- |
--  
--
isCurrentTextEditor :: Builder -> TextEditorData -> IO Bool
isCurrentTextEditor bulder (TextEditorData _ _ box _) = do
  getCurrentTextEditorHBox bulder >>= \case
    Nothing -> return False
    Just b  -> return $ box == b

-- |
--  
--
getCurrentTextEditorHBox :: Builder -> IO (Maybe HBox)
getCurrentTextEditorHBox builder = do
  note <- builderGetObject builder castToNotebook _NAME_CODE_NOTE

  notebookGetCurrentPage note >>= \case
    (-1) -> return Nothing
    idx  -> notebookGetNthPage note idx >>= \case
      Nothing -> return Nothing
      Just w  -> return . Just $ castToHBox w

-- |
--  
--
getCodeViewContent :: TextEditorData -> IO BS.ByteString
getCodeViewContent (TextEditorData _ codeView _ _) = do
  buf <- textViewGetBuffer codeView
  textBufferSetModified buf False
  startIter <- textBufferGetStartIter buf
  endIter <- textBufferGetEndIter buf
  textBufferGetByteString buf startIter endIter False

-- |
--  
--
isTextEditorModified :: TextEditorData -> IO Bool
isTextEditorModified (TextEditorData _ codeView _ _) = do
  buf <- textViewGetBuffer codeView
  textBufferGetModified buf

-- |
--  
--
setTextEditorModified :: TextEditorData -> Bool -> IO ()
setTextEditorModified (TextEditorData _ codeView _ _) isModified = do
  buf <- textViewGetBuffer codeView
  textBufferSetModified buf isModified


-- |
--  
--
codeTextButtonPressEventHandler :: TextView -> EventM EButton Bool
codeTextButtonPressEventHandler textView = eventButton >>= \case
  RightButton -> liftIO $ do
    buf  <- textViewGetBuffer textView
    clip <- widgetGetClipboard textView selectionPrimary
    textBufferHasSelection buf >>= \case
      False -> textBufferPasteClipboardAtCursor buf clip True >> return True
      True ->  textBufferCopyClipboard buf clip >> return True
  _ -> return False


-- |
--  Event Handler
--
codeTextKeyPressEventHandler :: TextView -> TextView -> CodeTextKeyPressEventHandler -> EventM EKey Bool
codeTextKeyPressEventHandler lineTextView codeTextView evh = do
  name <- eventKeyName
  mods <- eventModifier

  liftIO $ do
    buf  <- textViewGetBuffer codeTextView
    mark <- textBufferGetInsert buf
    iter <- textBufferGetIterAtMark buf mark
    lineNo <- textIterGetLine iter

    buf <- textViewGetBuffer lineTextView
    iter <- textBufferGetIterAtLine buf lineNo
    isTagExists <- textIterBeginsTag iter Nothing

    evh (T.unpack name) (elem Shift mods) (elem Control mods) isTagExists lineNo

-- |
--
--
getCodeTextLineNumber :: TextEditorData -> IO (Int, Int) 
getCodeTextLineNumber (TextEditorData _ codeView _ _) = do
  buf  <- textViewGetBuffer codeView
  mark <- textBufferGetInsert buf
  iter <- textBufferGetIterAtMark buf mark
  lineNo <- textIterGetLine iter
  colNo  <- textIterGetLineOffset iter
  return (lineNo, colNo)

-- |
--
--
blockIndentTextEditor :: TextEditorData -> IO ()
blockIndentTextEditor te@(TextEditorData _ codeView _ _) = do
  (startLine, endLine) <- getSelectedLineNumbers te
  buf  <- textViewGetBuffer codeView
  insertIndent buf startLine endLine
  where
    insertIndent buf lineNo endNo = do
      iter <- textBufferGetIterAtLine buf lineNo
      textBufferInsert buf iter _INDENT_SPACE
      if lineNo == endNo then return ()
        else insertIndent buf (lineNo+1) endNo

-- |
--
--
blockUnIndentTextEditor :: TextEditorData -> IO ()
blockUnIndentTextEditor te@(TextEditorData _ codeView _ _) = do
  (startLine, endLine) <- getSelectedLineNumbers te
  buf  <- textViewGetBuffer codeView
  deleteIndent buf startLine endLine
  where
    deleteIndent buf lineNo endNo = do
      iter <- textBufferGetIterAtLine buf lineNo
      next <- textIterCopy iter

      forwardTextIter next 2 >>= \case
        Nothing -> return ()
        Just endIter -> do
          indent <- textBufferGetText buf iter endIter False
          when (_INDENT_SPACE == indent) $ textBufferDelete buf iter endIter
      
      if lineNo == endNo then return ()
        else deleteIndent buf (lineNo+1) endNo

-- |
--
--
blockCommentTextEditor :: TextEditorData -> IO ()
blockCommentTextEditor te@(TextEditorData _ codeView _ _) = do
  (startLine, endLine) <- getSelectedLineNumbers te
  buf  <- textViewGetBuffer codeView
  insertComment buf startLine endLine
  where
    insertComment buf lineNo endNo = do
      iter <- textBufferGetIterAtLine buf lineNo
      cnt  <- getSpaceCount buf iter

      when (cnt /= (-1)) $ do
        forwardTextIter iter cnt >>= \case
          Nothing -> return ()
          Just startIter -> textBufferInsert buf startIter _COMMENT_TAG

      if lineNo == endNo then return ()
        else insertComment buf (lineNo+1) endNo

    getSpaceCount buf iter = do
      isEndLine <- textIterEndsLine iter
      isEndBuf <- textIterIsEnd iter

      if isEndLine || isEndBuf then return (-1) else do
        endIter <- textIterCopy iter
        _ <- textIterForwardToLineEnd endIter
        str <- textBufferGetText buf iter endIter False
        return $ length $ takeWhile (== ' ') str
    
-- |
--
--
blockUnCommentTextEditor :: TextEditorData -> IO ()
blockUnCommentTextEditor te@(TextEditorData _ codeView _ _) = do
  (startLine, endLine) <- getSelectedLineNumbers te
  buf  <- textViewGetBuffer codeView
  deleteComment buf startLine endLine

  where
    deleteComment buf lineNo endNo = do
      iter <- textBufferGetIterAtLine buf lineNo

      (isCmnt, idx) <- getCommentInfo buf iter
      iterMay <- forwardTextIter iter idx
      when isCmnt $ deleteCommentTag buf iterMay
      
      if lineNo == endNo then return ()
        else deleteComment buf (lineNo+1) endNo

    deleteCommentTag _ Nothing = return ()
    deleteCommentTag buf (Just iter) = do
      next <- textIterCopy iter
      forwardTextIter next (length _COMMENT_TAG) >>= \case     
        Nothing -> return ()
        Just endIter -> textBufferDelete buf iter endIter

    getCommentInfo :: TextBuffer -> TextIter -> IO (Bool, Int)
    getCommentInfo buf iter = do
      endIter <- textIterCopy iter
      textIterForwardToLineEnd endIter >>= \case
        False -> return (False, 0)  -- 最終行はFalseとなる問題あり。
        True -> do
          str <- textBufferGetText buf iter endIter False
          if startswith _COMMENT_TAG (strip str) then return (True, length (head (split _COMMENT_TAG str)))
            else return (False, length (takeWhile (== ' ') str))
 
-- |
--
--
getSelectedLineNumbers :: TextEditorData -> IO (Int, Int)
getSelectedLineNumbers te@(TextEditorData _ codeView _ _) = do
  buf  <- textViewGetBuffer codeView
  textBufferHasSelection buf >>= \case
    True -> do
      (startIter, endIter) <- textBufferGetSelectionBounds buf 
      s <- textIterGetLine startIter
      e <- textIterGetLine endIter
      return (s, e)
    False  -> do
      (l, _) <- getCodeTextLineNumber te
      return (l, l)

-- |
--
--
setContent2TextEditor :: TextEditorData -> BS.ByteString -> IO ()
setContent2TextEditor (TextEditorData _ codeView _ _) bs = do
  buf  <- textViewGetBuffer codeView
  textBufferSetByteString buf bs
  textBufferSetModified buf False

-- |
--
--
setCursorOnTextEditor :: TextEditorData -> Int -> Int -> IO ()
setCursorOnTextEditor (TextEditorData _ codeView _ _) lineNo colNo = do
  buf  <- textViewGetBuffer codeView
  iter <- textBufferGetIterAtLineOffset buf lineNo colNo
  textBufferPlaceCursor buf iter
  mark <- textBufferGetInsert buf
  textViewScrollToMark codeView mark 0.0 (Just (0.0, 0.5))

-- |
--
--
insertText2TextEditor :: TextEditorData -> Int -> Int -> String -> IO ()
insertText2TextEditor (TextEditorData _ codeView _ _) startLineNo startColNo str = do
  buf <- textViewGetBuffer codeView
  iter <- textBufferGetIterAtLineOffset buf startLineNo startColNo
  textBufferInsert buf iter str

-- |
--
--
deleteRangeOnTextEditor :: TextEditorData -> Int -> Int -> Int -> Int -> IO ()
deleteRangeOnTextEditor (TextEditorData _ codeView _ _) startLineNo startColNo endLineNo endColNo = do
  buf <- textViewGetBuffer codeView
  startIter <- textBufferGetIterAtLineOffset buf startLineNo startColNo
  endIter <- textBufferGetIterAtLineOffset buf endLineNo endColNo
  textBufferDelete buf startIter endIter

-- |
--
--
getSelectedText :: TextEditorData -> IO String
getSelectedText (TextEditorData _ codeView _ _) = do
  buf  <- textViewGetBuffer codeView
  textBufferHasSelection buf >>= \case
    True -> do
      (startIter, endIter) <- textBufferGetSelectionBounds buf 
      textBufferGetText buf startIter endIter False
    False  -> return ""