<commit_before>{-# LANGUAGE MultiWayIf #-}

module State.Editing where

import           Brick (EventM, Next, suspendAndResume, handleEventLensed)
import           Brick.Widgets.Edit (Editor, handleEditorEvent, getEditContents, editContentsL)
import           Brick.Widgets.Edit (applyEdit)
import qualified Codec.Binary.UTF8.Generic as UTF8
import           Control.Arrow
import           Control.Monad (void)
import           Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as BS
import qualified Data.HashMap.Strict as HM
import           Data.Monoid ((<>))
import qualified Data.Text as T
import           Data.Text.Zipper (textZipper, insertMany,
                                   moveRight, moveLeft, cursorPosition, currentLine,
                                   transposeChars, deleteChar, deletePrevChar,
                                   insertChar, clearZipper)
import           Graphics.Vty (Event(..), Key(..), Modifier(..))
import           Lens.Micro.Platform
import           System.Environment (lookupEnv)
import           System.Exit (ExitCode(..))
import           System.IO (hPutStr, hClose)
import           System.IO.Temp (withSystemTempFile)
import           System.Process (system)

import           Network.Mattermost
import           Network.Mattermost.Lenses

import           Config
import           Types

import           State.Common

startMultilineEditing :: ChatState -> ChatState
startMultilineEditing = csEditState.cedMultiline .~ True

toggleMultilineEditing :: ChatState -> ChatState
toggleMultilineEditing = csEditState.cedMultiline %~ not

invokeExternalEditor :: ChatState -> EventM Name (Next ChatState)
invokeExternalEditor st = do
    -- If EDITOR is in the environment, write the current message to a
    -- temp file, invoke EDITOR on it, read the result, remove the temp
    -- file, and update the program state.
    --
    -- If EDITOR is not present, fall back to 'vi'.
    mEnv <- liftIO $ lookupEnv "EDITOR"
    let editorProgram = maybe "vi" id mEnv

    suspendAndResume $
      withSystemTempFile "matterhorn_editor.tmp" $ \tmpFileName tmpFileHandle -> do
        -- Write the current message to the temp file
        hPutStr tmpFileHandle $ T.unpack $ T.intercalate "\n" $ getEditContents $ st^.cmdLine
        hClose tmpFileHandle

        -- Run the editor
        status <- system (editorProgram <> " " <> tmpFileName)

        -- On editor exit, if exited with zero status, read temp file.
        -- If non-zero status, skip temp file read.
        case status of
            ExitSuccess -> do
                tmpLines <- T.lines <$> T.pack <$> readFile tmpFileName
                return $ st & cmdLine.editContentsL .~ (textZipper tmpLines Nothing)
                            & csEditState.cedMultiline .~ (length tmpLines > 1)
            ExitFailure _ -> return st

toggleMessagePreview :: ChatState -> ChatState
toggleMessagePreview = csShowMessagePreview %~ not

addUserToCurrentChannel :: T.Text -> ChatState -> EventM Name ChatState<commit_msg>Gracefully handle external editor decode errors (fixes #168)<commit_after>{-# LANGUAGE MultiWayIf #-}

module State.Editing where

import           Brick (EventM, Next, suspendAndResume, handleEventLensed)
import           Brick.Widgets.Edit (Editor, handleEditorEvent, getEditContents, editContentsL)
import           Brick.Widgets.Edit (applyEdit)
import qualified Codec.Binary.UTF8.Generic as UTF8
import           Control.Arrow
import           Control.Monad (void)
import           Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as BS
import qualified Data.HashMap.Strict as HM
import           Data.Monoid ((<>))
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import           Data.Text.Zipper (textZipper, insertMany,
                                   moveRight, moveLeft, cursorPosition, currentLine,
                                   transposeChars, deleteChar, deletePrevChar,
                                   insertChar, clearZipper)
import           Graphics.Vty (Event(..), Key(..), Modifier(..))
import           Lens.Micro.Platform
import           System.Environment (lookupEnv)
import           System.Exit (ExitCode(..))
import           System.IO (hPutStr, hClose)
import           System.IO.Temp (withSystemTempFile)
import           System.Process (system)

import           Network.Mattermost
import           Network.Mattermost.Lenses

import           Config
import           Types

import           State.Common

startMultilineEditing :: ChatState -> ChatState
startMultilineEditing = csEditState.cedMultiline .~ True

toggleMultilineEditing :: ChatState -> ChatState
toggleMultilineEditing = csEditState.cedMultiline %~ not

invokeExternalEditor :: ChatState -> EventM Name (Next ChatState)
invokeExternalEditor st = do
    -- If EDITOR is in the environment, write the current message to a
    -- temp file, invoke EDITOR on it, read the result, remove the temp
    -- file, and update the program state.
    --
    -- If EDITOR is not present, fall back to 'vi'.
    mEnv <- liftIO $ lookupEnv "EDITOR"
    let editorProgram = maybe "vi" id mEnv

    suspendAndResume $
      withSystemTempFile "matterhorn_editor.tmp" $ \tmpFileName tmpFileHandle -> do
        -- Write the current message to the temp file
        hPutStr tmpFileHandle $ T.unpack $ T.intercalate "\n" $ getEditContents $ st^.cmdLine
        hClose tmpFileHandle

        -- Run the editor
        status <- system (editorProgram <> " " <> tmpFileName)

        -- On editor exit, if exited with zero status, read temp file.
        -- If non-zero status, skip temp file read.
        case status of
            ExitSuccess -> do
                tmpBytes <- BS.readFile tmpFileName
                case T.decodeUtf8' tmpBytes of
                    Left _ -> do
                        msg <- newClientMessage Error "Failed to decode file contents as UTF-8"
                        return $ addClientMessage msg st
                    Right t -> do
                        let tmpLines = T.lines t
                        return $ st & cmdLine.editContentsL .~ (textZipper tmpLines Nothing)
                                    & csEditState.cedMultiline .~ (length tmpLines > 1)
            ExitFailure _ -> return st

toggleMessagePreview :: ChatState -> ChatState
toggleMessagePreview = csShowMessagePreview %~ not

addUserToCurrentChannel :: T.Text -> ChatState -> EventM Name ChatState
{'fraction_non_alphanumeric': 0.07701824930405196, 'fraction_numerical': 0.0017012063099288586, 'mean_word_length': 2.2351175587793897, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5653306', 'n_tokens_mistral': 1728, 'n_tokens_neox': 1655, 'n_words': 536}