summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/much.hs (renamed from config/tv.hs)4
-rw-r--r--config/kmein.hs186
-rw-r--r--much.cabal42
-rw-r--r--src/Data/Tree/Extra.hs7
-rw-r--r--src/Much/Action.hs8
-rw-r--r--src/Much/Core.hs2
-rw-r--r--src/Much/TreeView.hs22
-rw-r--r--src/Notmuch.hs120
8 files changed, 54 insertions, 337 deletions
diff --git a/config/tv.hs b/app/much.hs
index 2d57678..84eda6b 100644
--- a/config/tv.hs
+++ b/app/much.hs
@@ -4,7 +4,7 @@
module Main (main) where
-import Blessings.String
+import Blessings.String.WCWidth
import Data.Default
import Data.Maybe
import Much.Action
@@ -43,6 +43,8 @@ myKeymap :: String -> State -> IO State
myKeymap "a" = toggleTagAtCursor "inbox"
myKeymap "s" = toggleTagAtCursor "unread"
+myKeymap "g" = toggleTagAtCursor "killed"
+myKeymap "f" = toggleTagAtCursor "star"
myKeymap "&" = toggleTagAtCursor "killed"
myKeymap "*" = toggleTagAtCursor "star"
myKeymap "k" = moveCursorUp 1
diff --git a/config/kmein.hs b/config/kmein.hs
deleted file mode 100644
index 806072e..0000000
--- a/config/kmein.hs
+++ /dev/null
@@ -1,186 +0,0 @@
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedLists #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-
-module Main (main) where
-
-import Much.Action
-import Much.Core (mainWithState, notmuchSearch)
-import Much.State (State(..), ColorConfig(..))
-import Much.TreeView (TreeView(TVMessagePart), treeViewId, getMessage)
-import Notmuch (notmuchShowPart)
-import Notmuch.Message
-
-import Blessings.String (Blessings(..))
-import Control.Monad ((>=>), unless)
-import Data.Default (Default(..))
-import Data.Functor (($>))
-import Data.Maybe (fromMaybe)
-import Scanner (Scan(ScanMouse), mouseButton, mouseY)
-import System.Directory (doesFileExist)
-import System.FilePath ((</>))
-import System.Posix.Signals (raiseSignal, sigINT)
-import System.Process (callProcess, spawnCommand)
-import qualified Data.ByteString.Lazy.Char8 as LBS8 (writeFile)
-import qualified Data.Text as T (unpack)
-import qualified Data.Text.IO as T (writeFile)
-import qualified Data.Tree.Zipper as Z (label)
-
-{- notmuch's special tags are:
-
- synchonised to maildir: draft flagged passed replied unread
- automatic: attachment signed encrypted
- cli default tags: unread inbox deleted spam
-
- ref: https://notmuchmail.org/special-tags/
--}
-
-main :: IO ()
-main =
- mainWithState def
- { keymap = myKeymap
- , mousemap = myMousemap
- , colorConfig = def
- { boring = pure [38,5,8]
- , alt = pure [38,5,182]
- , search = pure [38,5,13]
- , focus = pure [38,5,4]
- , quote = pure [38,5,7]
- , prefix = pure [38,5,235]
- , date = pure [38,5,1]
- , tags = pure [38,5,14]
- , boringMessage = pure [38,5,3]
- , unreadMessage = pure [38,5,11]
- , unreadSearch = pure [38,5,15]
- , tagMap = pure
- [ ("deleted", pure [38,5,088])
- , ("flagged", pure [38,5,226])
- , ("draft", pure [38,5,63])
- , ("spam", pure [38,5,202])
- ]
- }
- , aliases =
- [ ("flagged", "🔖")
- , ("attachment", "📎")
- , ("signed", "🔒")
- ]
- , query = "tag:inbox"
- }
-
-showCurrentMessagePart :: State -> IO (Maybe (Message, MessagePart))
-showCurrentMessagePart q =
- case Z.label (cursor q) of
- TVMessagePart message part -> do
- let m_id = unMessageID (messageId message)
- partResult <- notmuchShowPart m_id (partID part)
- case partResult of
- Right part' -> return $ Just (message, part')
- Left _ -> return Nothing
- _ -> return Nothing
-
-currentAttachmentPath :: State -> Message -> MessagePart -> FilePath
-currentAttachmentPath q message part =
- attachmentDirectory q </> attachmentFileName q message part
-
-saveAttachment :: State -> IO State
-saveAttachment q =
- showCurrentMessagePart q >>= \case
- Nothing -> return q { flashMessage = "cursor not on attachment" }
- Just (message, part) -> do
- let destination = currentAttachmentPath q message part
- alreadyDownloaded <- doesFileExist destination
- if attachmentOverwrite q || not alreadyDownloaded
- then case partContent part of
- ContentText text ->
- T.writeFile destination text $>
- q { flashMessage = Plain destination }
- ContentRaw raw _ ->
- LBS8.writeFile destination raw $>
- q { flashMessage = Plain destination }
- _ -> return q { flashMessage = "this part cannot be saved" }
- else return q { flashMessage = "not overwriting attachment" }
-
-openAttachment :: State -> IO State
-openAttachment q =
- showCurrentMessagePart q >>= \case
- Nothing -> return q { flashMessage = "cursor not on attachment" }
- Just (message, part) -> do
- let destination = currentAttachmentPath q message part
- alreadyDownloaded <- doesFileExist destination
- unless alreadyDownloaded $ saveAttachment q $> ()
- callProcess "xdg-open" [destination] $> q
-
-reply :: State -> IO State
-reply q = spawnCommand "alacritty -e nvim -c 'read! mail-reply' -c 'execute \"normal gg\" | set filetype=mail'" $> q
-
-myKeymap :: String -> State -> IO State
-myKeymap "h" = closeFold
-myKeymap "l" = openFold
-myKeymap " " = toggleFold
-
-myKeymap "g" = moveCursorToThread >=> moveCursorToFirstOnSameLevel
-myKeymap "G" = moveCursorToThread >=> moveCursorToLastOnSameLevel
-myKeymap "k" = moveCursorUp 1
-myKeymap "j" = moveCursorDown 1
-myKeymap "\ESC[A" = moveCursorDown 1
-myKeymap "\ESC[B" = moveCursorUp 1
-myKeymap "\ESC[C" = moveTreeLeft 10 -- left
-myKeymap "\ESC[D" = moveTreeRight 10 -- right
-
-myKeymap "H" = moveCursorToThread
-
-myKeymap "r" = notmuchSearch
-
-myKeymap "R" = reply
-myKeymap "S" = saveAttachment
-myKeymap "o" = openAttachment
-
-myKeymap "q" = \q -> raiseSignal sigINT $> q
-
-myKeymap "*" = toggleTagAtCursor "flagged"
-myKeymap "a" = toggleTagAtCursor "inbox" -- mnemonic: Archive
-myKeymap "s" = toggleTagAtCursor "unread" -- mnemonic: Seen
-myKeymap "d" = toggleTagAtCursor "deleted"
-myKeymap "!" = toggleTagAtCursor "spam"
-
-myKeymap "N" = moveCursorUpToPrevUnread
-myKeymap "n" = moveCursorDownToNextUnread
-
-myKeymap "K" = moveTreeDown 1
-myKeymap "J" = moveTreeUp 1
-myKeymap "\ESC[a" = moveTreeDown 1
-myKeymap "\ESC[b" = moveTreeUp 1
-myKeymap "\ESC[5~" = \q -> moveTreeDown (screenHeight q `div` 2) q -- PgUp
-myKeymap "\ESC[6~" = \q -> moveTreeUp (screenHeight q `div` 2) q -- PgDn
-myKeymap "\ESC[Z" = moveCursorUpToPrevUnread -- S-Tab
-myKeymap "\DEL" = moveToParent -- backspace
-
--- <F1>
-myKeymap "\ESC[11~" = \q@State{..} ->
- return q { flashMessage = Plain $ show $ treeViewId $ Z.label cursor }
-
--- <F2>
-myKeymap "\ESC[12~" = \q@State{..} ->
- return q { flashMessage =
- Plain $
- show $
- fmap messageFilename $
- getMessage $
- Z.label cursor
- }
-
--- TODO Stuff Vim sends after exit (also there is more...)
-myKeymap "\ESC[2;2R" = \q -> return q { flashMessage = flashMessage q <> " " <> Plain "stupid" }
-myKeymap "\ESC[>85;95;0c" = \q -> return q { flashMessage = flashMessage q <> " " <> Plain "stupid" }
-
-myKeymap s = displayKey s
-
-
-myMousemap :: Scan -> State -> IO State
-myMousemap ScanMouse{mouseButton=1,mouseY=y} = defaultMouse1Click y
-myMousemap ScanMouse{mouseButton=3,mouseY=y} = defaultMouse1Click y >=> toggleFold
-myMousemap ScanMouse{mouseButton=4} = moveTreeDown 3
-myMousemap ScanMouse{mouseButton=5} = moveTreeUp 3
-myMousemap ScanMouse{mouseButton=0} = return
-myMousemap info = displayMouse info
diff --git a/much.cabal b/much.cabal
index cb6b989..9615dee 100644
--- a/much.cabal
+++ b/much.cabal
@@ -1,19 +1,23 @@
+cabal-version: 3.8
name: much
-version: 1.3.1
+version: 1.4.0
license: MIT
author: tv <tv@krebsco.de>
maintainer: tv@krebsco.de
build-type: Simple
-cabal-version: >=1.10
-executable much-tv
- hs-source-dirs: config
- main-is: tv.hs
+common common-stuff
+ build-depends:
+ blessings >= 2.5.0
+
+executable much
+ import: common-stuff
+ hs-source-dirs: app
+ main-is: much.hs
default-language: Haskell2010
ghc-options: -O2 -threaded -with-rtsopts=-N
build-depends: much
, base
- , blessings
, data-default
, deepseq
, transformers
@@ -21,7 +25,7 @@ executable much-tv
, case-insensitive
, time
, safe
- , scanner
+ , terminal-scanner
, directory
, hyphenation
, linebreak
@@ -31,25 +35,8 @@ executable much-tv
, containers
, rosezipper
-executable much-kmein
- hs-source-dirs: config
- main-is: kmein.hs
- default-language: Haskell2010
- ghc-options: -O2 -threaded -with-rtsopts=-N
- build-depends: much
- , base
- , blessings
- , bytestring
- , data-default
- , directory
- , filepath
- , process
- , rosezipper
- , scanner
- , text
- , unix
-
library
+ import: common-stuff
hs-source-dirs: src
exposed-modules: Much.Core
, Much.Action
@@ -79,12 +66,12 @@ library
, Codec.MIME.QuotedPrintable
, Codec.MIME.Type
, Data.Aeson.Extends
+ , Data.Tree.Extra
build-depends: base
, aeson
, attoparsec
, base64-bytestring
, blaze-builder
- , blessings
, bytestring
, case-insensitive
, containers
@@ -102,12 +89,13 @@ library
, old-locale
, optparse-applicative
, process
+ , process-extras
, random
, rosezipper
, safe
- , scanner
, servant-server
, split
+ , terminal-scanner
, terminal-size
, text
, time
diff --git a/src/Data/Tree/Extra.hs b/src/Data/Tree/Extra.hs
new file mode 100644
index 0000000..256af08
--- /dev/null
+++ b/src/Data/Tree/Extra.hs
@@ -0,0 +1,7 @@
+module Data.Tree.Extra where
+
+import Data.Tree (Forest, Tree(subForest))
+
+
+setSubForest :: Forest a -> Tree a -> Tree a
+setSubForest sf t = t { subForest = sf }
diff --git a/src/Much/Action.hs b/src/Much/Action.hs
index 6613eb1..d76e503 100644
--- a/src/Much/Action.hs
+++ b/src/Much/Action.hs
@@ -3,15 +3,15 @@
{-# LANGUAGE RecordWildCards #-}
module Much.Action where
-import Blessings.String
+import Blessings.String.WCWidth
import Data.Maybe
+import Data.Tree.Extra (setSubForest)
import Scanner
import Much.State
import Much.TagUtils
import Much.TreeSearch
import Much.TreeView
import Much.TreeZipperUtils
-import qualified Data.Tree as Tree
import qualified Data.Tree.Zipper as Z
import qualified Notmuch
import qualified Notmuch.Message as Notmuch
@@ -216,7 +216,3 @@ topOverrun State{..} =
botOverrun :: State -> Int
botOverrun State{..} =
max 0 (linearPos cursor - yoffset - (screenHeight - length headBuffer - 1))
-
-
-setSubForest :: Tree.Forest a -> Tree.Tree a -> Tree.Tree a
-setSubForest sf t = t { Tree.subForest = sf }
diff --git a/src/Much/Core.hs b/src/Much/Core.hs
index 2ce5ad7..1c8daa7 100644
--- a/src/Much/Core.hs
+++ b/src/Much/Core.hs
@@ -4,7 +4,7 @@
{-# LANGUAGE RecordWildCards #-}
module Much.Core where
-import Blessings.String (Blessings(Plain,SGR),pp)
+import Blessings.String.WCWidth (Blessings(Plain,SGR),pp)
import Control.Concurrent
import Control.Monad
import Data.Aeson
diff --git a/src/Much/TreeView.hs b/src/Much/TreeView.hs
index e963497..9b309bc 100644
--- a/src/Much/TreeView.hs
+++ b/src/Much/TreeView.hs
@@ -25,6 +25,8 @@ import Notmuch
import Notmuch.Message
import Notmuch.SearchResult
import Much.TreeView.Types as Export
+import System.Exit (ExitCode(ExitSuccess))
+import System.Process.ByteString.Lazy (readProcessWithExitCode)
getMessage :: TreeView -> Maybe Message
@@ -144,8 +146,26 @@ loadSubForest = \case
. findFirsts messageMatch
<$> notmuchShow (termFromMessage m)
+ TVMessagePart m mp@MessagePart{partContentType="text/html"} ->
+ notmuchShowPartRaw (termFromMessage m) (partID mp) >>= \case
+ Left e -> return $ Left $ show e
+ Right html ->
+ readProcessWithExitCode
+ "lynx"
+ [ "-dump"
+ , "-nomargins"
+ , "-stdin"
+ ]
+ html
+ >>= \case
+ (ExitSuccess, out, _err) ->
+ return $ Right $
+ zipWith (\i s -> Node (TVMessageLine m mp i s) []) [0..] (lines . LBS8.unpack $ out)
+
+ (exitCode, _out, err) ->
+ return $ Left $ show exitCode <> ": " <> LBS8.unpack err
+
TVMessagePart m mp ->
- -- TODO parse --format=raw
notmuchShowPart (termFromMessage m) (partID mp) >>= return . \case
Left e -> Left $ show e
Right mp' ->
diff --git a/src/Notmuch.hs b/src/Notmuch.hs
index 080df1e..310657a 100644
--- a/src/Notmuch.hs
+++ b/src/Notmuch.hs
@@ -7,9 +7,6 @@ import qualified Data.ByteString.Lazy.Char8 as LBS8
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LT
import qualified Network.Mail.Mime as M
-import Control.Concurrent
-import Control.DeepSeq (rnf)
-import Control.Exception
import Data.Aeson.Extends
import Data.Either.Combinators (mapRight)
import Data.Functor ((<&>))
@@ -19,126 +16,19 @@ import Notmuch.Message
import Notmuch.SearchResult
import Much.ParseMail (readMail)
import System.Exit
-import System.IO
-import System.Process
+import System.Process.ByteString.Lazy (readProcessWithExitCode)
import Much.TagUtils
--- | Fork a thread while doing something else, but kill it if there's an
--- exception.
---
--- This is important in the cases above because we want to kill the thread
--- that is holding the Handle lock, because when we clean up the process we
--- try to close that handle, which could otherwise deadlock.
---
-withForkWait :: IO () -> (IO () -> IO a) -> IO a
-withForkWait async body = do
- waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))
- mask $ \restore -> do
- tid <- forkIO $ try (restore async) >>= putMVar waitVar
- let wait = takeMVar waitVar >>= either throwIO return
- restore (body wait) `onException` killThread tid
-
-
-
-
notmuch :: [String] -> IO LBS.ByteString
notmuch args = do
- (_, Just hout, _, ph) <- createProcess (proc "notmuch" args)
- { std_out = CreatePipe }
- output <- LBS.hGetContents hout
-
-
- withForkWait (evaluate $ rnf output) $ \waitOut -> do
-
- ---- now write any input
- --unless (null input) $
- -- ignoreSigPipe $ hPutStr inh input
- -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE
- --ignoreSigPipe $ hClose inh
-
- -- wait on the output
- waitOut
- hClose hout
-
- -- wait on the process
- _ex <- waitForProcess ph
- --return (ex, output)
-
- --case ex of
- -- ExitSuccess -> return output
- -- ExitFailure r -> processFailedException "readProcess" cmd args r
-
- return output
+ (_exitCode, out, _err) <- notmuch' args
+ return out
notmuch' :: [String] -> IO (ExitCode, LBS.ByteString, LBS.ByteString)
-notmuch' args = do
- (_, Just hout, Just herr, ph) <-
- createProcess (proc "notmuch" args)
- { std_out = CreatePipe
- , std_err = CreatePipe
- }
- out <- LBS.hGetContents hout
- err <- LBS.hGetContents herr
-
- withForkWait (evaluate $ rnf out) $ \waitOut -> do
- withForkWait (evaluate $ rnf err) $ \waitErr -> do
-
- ---- now write any input
- --unless (null input) $
- -- ignoreSigPipe $ hPutStr inh input
- -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE
- --ignoreSigPipe $ hClose inh
-
- -- wait on the output
- waitOut
- waitErr
- hClose hout
- hClose herr
-
- -- wait on the process
- exitCode <- waitForProcess ph
-
- return (exitCode, out, err)
-
-
-notmuchWithInput
- :: [String]
- -> LBS.ByteString
- -> IO (ExitCode, LBS.ByteString, LBS.ByteString)
-notmuchWithInput args input = do
- (Just hin, Just hout, Just herr, ph) <-
- createProcess (proc "notmuch" args)
- { std_in = CreatePipe
- , std_out = CreatePipe
- , std_err = CreatePipe
- }
- LBS.hPut hin input
- hClose hin
-
- out <- LBS.hGetContents hout
- err <- LBS.hGetContents herr
-
- withForkWait (evaluate $ rnf out) $ \waitOut -> do
- withForkWait (evaluate $ rnf err) $ \waitErr -> do
-
- ---- now write any input
- --unless (null input) $
- -- ignoreSigPipe $ hPutStr inh input
- -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE
- --ignoreSigPipe $ hClose inh
-
- -- wait on the output
- waitOut
- waitErr
- hClose hout
- hClose herr
-
- -- wait on the process
- exitCode <- waitForProcess ph
-
- return (exitCode, out, err)
+notmuch' args =
+ readProcessWithExitCode "notmuch" args ""
search :: [String] -> IO (Either String [SearchResult])