packages feed

chiasma-0.12.2.0: lib/Chiasma/Native/TmuxOutputBlock.hs

module Chiasma.Native.TmuxOutputBlock where

import Data.Attoparsec.ByteString (Parser)
import Data.ByteString.Internal (packChars)
import Prelude hiding (noneOf, try)
import Text.Parser.Char (CharParsing, anyChar, char, newline, noneOf, string)
import Text.Parser.Combinators (choice, manyTill, notFollowedBy, skipMany, try)
import Text.Parser.LookAhead (LookAheadParsing, lookAhead)

import qualified Chiasma.Data.TmuxEvent as TmuxEvent
import Chiasma.Data.TmuxEvent (TmuxEvent)
import Chiasma.Data.TmuxNotification (TmuxNotification (..))
import Chiasma.Data.TmuxOutputBlock (End (EndError, EndSuccess), TmuxOutputBlock (Error, Success))

tillEol :: (CharParsing m) => m Text
tillEol = decodeUtf8 . packChars <$> manyTill anyChar newline

beginLine :: (CharParsing m, Monad m) => m ()
beginLine = void $ string "%begin" >> tillEol

endLine :: (CharParsing m) => m End
endLine = do
  end <- choice [EndSuccess <$ string "%end", EndError <$ string "%error"]
  _ <- tillEol
  pure end

notBeginLine :: (CharParsing m, Monad m) => m ()
notBeginLine = void $ notFollowedBy (string "%begin") >> tillEol

word :: CharParsing m => m Text
word = toText <$> some (noneOf " \n")

notificationLine :: (CharParsing m, Monad m) => m TmuxNotification
notificationLine = do
  notFollowedBy (string "%begin")
  _ <- char '%'
  name <- word
  args <- many (char ' ' *> word)
  _ <- newline
  pure TmuxNotification {name, args}

-- | Parse a sequence of lines between a %start and a %end line.
-- Tmux pads output lines with a single space on both sides, so strip those if the leading one is present.
parseBlock :: (CharParsing m, Monad m, LookAheadParsing m) => m TmuxOutputBlock
parseBlock = do
  _ <- skipMany notBeginLine
  _ <- beginLine
  dataLines <- manyTill tillEol $ try (lookAhead endLine)
  endLine <&> \case
    EndSuccess -> Success dataLines
    EndError -> Error dataLines

parseBlocks :: (CharParsing m, Monad m, LookAheadParsing m) => m [TmuxOutputBlock]
parseBlocks = do
  result <- many (try parseBlock)
  skipMany tillEol
  pure result

-- | Parse either a notification line or a command response block.
parseMessage :: (CharParsing m, Monad m, LookAheadParsing m) => m TmuxEvent
parseMessage =
  choice
    [ TmuxEvent.Notification <$> try notificationLine
    , TmuxEvent.Response <$> parseBlock
    ]

parser :: Parser TmuxOutputBlock
parser = parseBlock

messageParser :: Parser TmuxEvent
messageParser = parseMessage