packages feed

taskell 1.9.3.0 → 1.10.0

raw patch · 23 files changed

+811/−512 lines, 23 filesdep ~aesondep ~attoparsecdep ~brick

Dependency ranges changed: aeson, attoparsec, brick, directory, file-embed, http-client, vty

Files

README.md view
@@ -257,6 +257,10 @@  **Warning**: currently if you change your `[markdown]` settings any older files stored with different settings will not be readable. +### Template++You can edit the `template.md` config file to change what a new taskell file looks like.+ ### Controls  You can edit keyboard bindings in the `bindings.ini` config file.
src/Taskell/Config.hs view
@@ -4,10 +4,19 @@  import ClassyPrelude -import Data.FileEmbed (embedFile)+import           Data.FileEmbed             (embedFile)+import           Data.Version               (showVersion)+import           Language.Haskell.TH.Syntax (liftString)+import qualified Paths_taskell              (version)  version :: Text-version = "1.9.3"+version = $(liftString $ showVersion Paths_taskell.version)++trelloUsage :: Text+trelloUsage = decodeUtf8 $(embedFile "templates/trello-token.txt")++githubUsage :: Text+githubUsage = decodeUtf8 $(embedFile "templates/github-token.txt")  usage :: Text usage = decodeUtf8 $(embedFile "templates/usage.txt")
src/Taskell/Data/List.hs view
@@ -1,28 +1,128 @@-module Taskell.Data.List-    ( List-    , Update-    , title-    , tasks-    , create-    , empty-    , due-    , clearDue-    , new-    , count-    , newAt-    , duplicate-    , append-    , prepend-    , extract-    , updateFn-    , update-    , move-    , deleteTask-    , getTask-    , searchFor-    , nextTask-    , prevTask-    , nearest-    ) where+{-# LANGUAGE TemplateHaskell #-} -import Taskell.Data.List.Internal+module Taskell.Data.List where++import ClassyPrelude++import Control.Lens (element, makeLenses, (%%~), (%~), (&), (.~), (^.), (^?))++import Data.Sequence as S (adjust', deleteAt, insertAt, update, (<|), (|>))++import qualified Taskell.Data.Seq  as S+import qualified Taskell.Data.Task as T (Task, Update, blank, clearDue, contains, due, duplicate)+import           Taskell.Types     (TaskIndex (TaskIndex))++data List = List+    { _title :: Text+    , _tasks :: Seq T.Task+    } deriving (Show, Eq)++type Update = List -> List++-- create lenses+$(makeLenses ''List)++-- operations+create :: Text -> Seq T.Task -> List+create = List++empty :: Text -> List+empty text = List text ClassyPrelude.empty++new :: Update+new = append T.blank++count :: List -> Int+count = length . (^. tasks)++due :: List -> Seq (TaskIndex, T.Task)+due list = catMaybes (filt S.<#> (list ^. tasks))+  where+    filt int task = const (TaskIndex int, task) <$> task ^. T.due++clearDue :: TaskIndex -> Update+clearDue (TaskIndex int) = updateFn int T.clearDue++newAt :: Int -> Update+newAt idx = tasks %~ S.insertAt idx T.blank++duplicate :: Int -> List -> Maybe List+duplicate idx list = do+    task <- T.duplicate <$> getTask idx list+    pure $ list & tasks %~ S.insertAt idx task++append :: T.Task -> Update+append task = tasks %~ (S.|> task)++prepend :: T.Task -> Update+prepend task = tasks %~ (task S.<|)++extract :: Int -> List -> Maybe (List, T.Task)+extract idx list = do+    (xs, x) <- S.extract idx (list ^. tasks)+    pure (list & tasks .~ xs, x)++updateFn :: Int -> T.Update -> Update+updateFn idx fn = tasks %~ adjust' fn idx++update :: Int -> T.Task -> Update+update idx task = tasks %~ S.update idx task++move :: Int -> Int -> Maybe Text -> List -> Maybe (List, Int)+move current dir term list =+    case term of+        Nothing -> (, bound list (current + dir)) <$> (list & tasks %%~ S.shiftBy current dir)+        Just _ -> do+            idx <- changeTask dir current term list+            (, idx) <$> (list & tasks %%~ S.shiftBy current (idx - current))++deleteTask :: Int -> Update+deleteTask idx = tasks %~ deleteAt idx++getTask :: Int -> List -> Maybe T.Task+getTask idx = (^? tasks . element idx)++searchFor :: Text -> Update+searchFor text = tasks %~ filter (T.contains text)++changeTask :: Int -> Int -> Maybe Text -> List -> Maybe Int+changeTask dir current term list = do+    let next = current + dir+    tsk <- getTask next list+    case term of+        Nothing -> Just next+        Just trm ->+            if T.contains trm tsk+                then Just next+                else changeTask dir next term list++nextTask :: Int -> Maybe Text -> List -> Int+nextTask idx text lst = fromMaybe idx $ changeTask 1 idx text lst++prevTask :: Int -> Maybe Text -> List -> Int+prevTask idx text lst = fromMaybe idx $ changeTask (-1) idx text lst++closest :: Int -> Int -> Int -> Int+closest current previous next =+    if (next - current) < (current - previous)+        then next+        else previous++bound :: List -> Int -> Int+bound lst = S.bound (lst ^. tasks)++nearest' :: Int -> Maybe Text -> List -> Maybe Int+nearest' current term lst = do+    let prev = changeTask (-1) current term lst+    let nxt = changeTask 1 current term lst+    let comp idx = Just $ maybe idx (closest current idx) nxt+    maybe nxt comp prev++nearest :: Int -> Maybe Text -> List -> Int+nearest current term lst = idx+  where+    near = fromMaybe (-1) $ nearest' current term lst+    idx =+        case term of+            Nothing  -> bound lst current+            Just txt -> maybe near (bool near current . T.contains txt) $ getTask current lst
− src/Taskell/Data/List/Internal.hs
@@ -1,128 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}--module Taskell.Data.List.Internal where--import ClassyPrelude--import Control.Lens (element, makeLenses, (%%~), (%~), (&), (.~), (^.), (^?))--import Data.Sequence as S (adjust', deleteAt, insertAt, update, (<|), (|>))--import qualified Taskell.Data.Seq  as S-import qualified Taskell.Data.Task as T (Task, Update, blank, clearDue, contains, due, duplicate)-import           Taskell.Types     (TaskIndex (TaskIndex))--data List = List-    { _title :: Text-    , _tasks :: Seq T.Task-    } deriving (Show, Eq)--type Update = List -> List---- create lenses-$(makeLenses ''List)---- operations-create :: Text -> Seq T.Task -> List-create = List--empty :: Text -> List-empty text = List text ClassyPrelude.empty--new :: Update-new = append T.blank--count :: List -> Int-count = length . (^. tasks)--due :: List -> Seq (TaskIndex, T.Task)-due list = catMaybes (filt S.<#> (list ^. tasks))-  where-    filt int task = const (TaskIndex int, task) <$> task ^. T.due--clearDue :: TaskIndex -> Update-clearDue (TaskIndex int) = updateFn int T.clearDue--newAt :: Int -> Update-newAt idx = tasks %~ S.insertAt idx T.blank--duplicate :: Int -> List -> Maybe List-duplicate idx list = do-    task <- T.duplicate <$> getTask idx list-    pure $ list & tasks %~ S.insertAt idx task--append :: T.Task -> Update-append task = tasks %~ (S.|> task)--prepend :: T.Task -> Update-prepend task = tasks %~ (task S.<|)--extract :: Int -> List -> Maybe (List, T.Task)-extract idx list = do-    (xs, x) <- S.extract idx (list ^. tasks)-    pure (list & tasks .~ xs, x)--updateFn :: Int -> T.Update -> Update-updateFn idx fn = tasks %~ adjust' fn idx--update :: Int -> T.Task -> Update-update idx task = tasks %~ S.update idx task--move :: Int -> Int -> Maybe Text -> List -> Maybe (List, Int)-move current dir term list =-    case term of-        Nothing -> (, bound list (current + dir)) <$> (list & tasks %%~ S.shiftBy current dir)-        Just _ -> do-            idx <- changeTask dir current term list-            (, idx) <$> (list & tasks %%~ S.shiftBy current (idx - current))--deleteTask :: Int -> Update-deleteTask idx = tasks %~ deleteAt idx--getTask :: Int -> List -> Maybe T.Task-getTask idx = (^? tasks . element idx)--searchFor :: Text -> Update-searchFor text = tasks %~ filter (T.contains text)--changeTask :: Int -> Int -> Maybe Text -> List -> Maybe Int-changeTask dir current term list = do-    let next = current + dir-    tsk <- getTask next list-    case term of-        Nothing -> Just next-        Just trm ->-            if T.contains trm tsk-                then Just next-                else changeTask dir next term list--nextTask :: Int -> Maybe Text -> List -> Int-nextTask idx text lst = fromMaybe idx $ changeTask 1 idx text lst--prevTask :: Int -> Maybe Text -> List -> Int-prevTask idx text lst = fromMaybe idx $ changeTask (-1) idx text lst--closest :: Int -> Int -> Int -> Int-closest current previous next =-    if (next - current) < (current - previous)-        then next-        else previous--bound :: List -> Int -> Int-bound lst = S.bound (lst ^. tasks)--nearest' :: Int -> Maybe Text -> List -> Maybe Int-nearest' current term lst = do-    let prev = changeTask (-1) current term lst-    let nxt = changeTask 1 current term lst-    let comp idx = Just $ maybe idx (closest current idx) nxt-    maybe nxt comp prev--nearest :: Int -> Maybe Text -> List -> Int-nearest current term lst = idx-  where-    near = fromMaybe (-1) $ nearest' current term lst-    idx =-        case term of-            Nothing  -> bound lst current-            Just txt -> maybe near (bool near current . T.contains txt) $ getTask current lst
src/Taskell/Data/Lists.hs view
@@ -1,20 +1,89 @@-module Taskell.Data.Lists-    ( Lists-    , ListPosition(..)-    , initial-    , updateLists-    , count-    , due-    , clearDue-    , get-    , changeList-    , newList-    , delete-    , exists-    , shiftBy-    , search-    , appendToLast-    , analyse-    ) where+module Taskell.Data.Lists where -import Taskell.Data.Lists.Internal+import ClassyPrelude++import Control.Lens  ((^.))+import Data.Sequence as S (adjust', deleteAt, update, (!?), (|>))++import qualified Taskell.Data.List as L (List, Update, append, clearDue, count, due, empty, extract,+                                         prepend, searchFor)+import qualified Taskell.Data.Seq  as S+import qualified Taskell.Data.Task as T (Task, due)+import           Taskell.Types     (ListIndex (ListIndex), Pointer, TaskIndex (TaskIndex))++type Lists = Seq L.List++type Update = Lists -> Lists++data ListPosition+    = Top+    | Bottom++initial :: Lists+initial = fromList []++updateLists :: Int -> L.List -> Update+updateLists = S.update++count :: Int -> Lists -> Int+count idx tasks = maybe 0 L.count (tasks !? idx)++due :: Lists -> Seq (Pointer, T.Task)+due lists = sortOn ((^. T.due) . snd) dues+  where+    format x lst = (\(y, t) -> ((ListIndex x, y), t)) <$> L.due lst+    dues = concat $ format S.<#> lists++clearDue :: Pointer -> Update+clearDue (idx, tsk) = updateFn idx (L.clearDue tsk)++updateFn :: ListIndex -> L.Update -> Update+updateFn (ListIndex idx) fn = adjust' fn idx++get :: Lists -> Int -> Maybe L.List+get = (!?)++changeList :: ListPosition -> Pointer -> Lists -> Int -> Maybe Lists+changeList pos (ListIndex list, TaskIndex idx) tasks dir = do+    let next = list + dir+    let fn =+            case pos of+                Top    -> L.prepend+                Bottom -> L.append+    (from, task) <- L.extract idx =<< tasks !? list -- extract current task+    to <- fn task <$> tasks !? next -- get next list and append task+    pure . updateLists next to $ updateLists list from tasks -- update lists++newList :: Text -> Update+newList title = (|> L.empty title)++delete :: Int -> Update+delete = deleteAt++exists :: Int -> Lists -> Bool+exists idx tasks = isJust $ tasks !? idx++shiftBy :: Int -> Int -> Lists -> Maybe Lists+shiftBy = S.shiftBy++search :: Text -> Update+search text = (L.searchFor text <$>)++appendToLast :: T.Task -> Update+appendToLast task lists =+    fromMaybe lists $ do+        let idx = length lists - 1+        list <- L.append task <$> lists !? idx+        pure $ updateLists idx list lists++analyse :: Text -> Lists -> Text+analyse filepath lists =+    concat+        [ filepath+        , "\n"+        , "Lists: "+        , tshow $ length lists+        , "\n"+        , "Tasks: "+        , tshow $ foldl' (+) 0 (L.count <$> lists)+        ]
− src/Taskell/Data/Lists/Internal.hs
@@ -1,89 +0,0 @@-module Taskell.Data.Lists.Internal where--import ClassyPrelude--import Control.Lens  ((^.))-import Data.Sequence as S (adjust', deleteAt, update, (!?), (|>))--import qualified Taskell.Data.List as L (List, Update, append, clearDue, count, due, empty, extract,-                                         prepend, searchFor)-import qualified Taskell.Data.Seq  as S-import qualified Taskell.Data.Task as T (Task, due)-import           Taskell.Types     (ListIndex (ListIndex), Pointer, TaskIndex (TaskIndex))--type Lists = Seq L.List--type Update = Lists -> Lists--data ListPosition-    = Top-    | Bottom--initial :: Lists-initial = fromList [L.empty "To Do", L.empty "Done"]--updateLists :: Int -> L.List -> Update-updateLists = S.update--count :: Int -> Lists -> Int-count idx tasks = maybe 0 L.count (tasks !? idx)--due :: Lists -> Seq (Pointer, T.Task)-due lists = sortOn ((^. T.due) . snd) dues-  where-    format x lst = (\(y, t) -> ((ListIndex x, y), t)) <$> L.due lst-    dues = concat $ format S.<#> lists--clearDue :: Pointer -> Update-clearDue (idx, tsk) = updateFn idx (L.clearDue tsk)--updateFn :: ListIndex -> L.Update -> Update-updateFn (ListIndex idx) fn = adjust' fn idx--get :: Lists -> Int -> Maybe L.List-get = (!?)--changeList :: ListPosition -> Pointer -> Lists -> Int -> Maybe Lists-changeList pos (ListIndex list, TaskIndex idx) tasks dir = do-    let next = list + dir-    let fn =-            case pos of-                Top    -> L.prepend-                Bottom -> L.append-    (from, task) <- L.extract idx =<< tasks !? list -- extract current task-    to <- fn task <$> tasks !? next -- get next list and append task-    pure . updateLists next to $ updateLists list from tasks -- update lists--newList :: Text -> Update-newList title = (|> L.empty title)--delete :: Int -> Update-delete = deleteAt--exists :: Int -> Lists -> Bool-exists idx tasks = isJust $ tasks !? idx--shiftBy :: Int -> Int -> Lists -> Maybe Lists-shiftBy = S.shiftBy--search :: Text -> Update-search text = (L.searchFor text <$>)--appendToLast :: T.Task -> Update-appendToLast task lists =-    fromMaybe lists $ do-        let idx = length lists - 1-        list <- L.append task <$> lists !? idx-        pure $ updateLists idx list lists--analyse :: Text -> Lists -> Text-analyse filepath lists =-    concat-        [ filepath-        , "\n"-        , "Lists: "-        , tshow $ length lists-        , "\n"-        , "Tasks: "-        , tshow $ foldl' (+) 0 (L.count <$> lists)-        ]
src/Taskell/Data/Subtask.hs view
@@ -1,12 +1,29 @@-module Taskell.Data.Subtask-    ( Subtask-    , Update-    , new-    , blank-    , name-    , complete-    , toggle-    , duplicate-    ) where+{-# LANGUAGE TemplateHaskell #-} -import Taskell.Data.Subtask.Internal+module Taskell.Data.Subtask where++import ClassyPrelude+import Control.Lens  (makeLenses, (%~))++data Subtask = Subtask+    { _name     :: Text+    , _complete :: Bool+    } deriving (Show, Eq)++type Update = Subtask -> Subtask++-- create lenses+$(makeLenses ''Subtask)++-- operations+blank :: Subtask+blank = Subtask "" False++new :: Text -> Bool -> Subtask+new = Subtask++toggle :: Update+toggle = complete %~ not++duplicate :: Subtask -> Subtask+duplicate (Subtask n c) = Subtask n c
− src/Taskell/Data/Subtask/Internal.hs
@@ -1,29 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}--module Taskell.Data.Subtask.Internal where--import ClassyPrelude-import Control.Lens  (makeLenses, (%~))--data Subtask = Subtask-    { _name     :: Text-    , _complete :: Bool-    } deriving (Show, Eq)--type Update = Subtask -> Subtask---- create lenses-$(makeLenses ''Subtask)---- operations-blank :: Subtask-blank = Subtask "" False--new :: Text -> Bool -> Subtask-new = Subtask--toggle :: Update-toggle = complete %~ not--duplicate :: Subtask -> Subtask-duplicate (Subtask n c) = Subtask n c
src/Taskell/Data/Task.hs view
@@ -1,27 +1,97 @@-module Taskell.Data.Task-    ( Task-    , Update-    , name-    , description-    , due-    , subtasks-    , blank-    , new-    , create-    , duplicate-    , setDescription-    , appendDescription-    , setDue-    , clearDue-    , getSubtask-    , addSubtask-    , hasSubtasks-    , updateSubtask-    , removeSubtask-    , countSubtasks-    , countCompleteSubtasks-    , contains-    , isBlank-    ) where+{-# LANGUAGE TemplateHaskell #-} -import Taskell.Data.Task.Internal+module Taskell.Data.Task where++import ClassyPrelude++import Control.Lens (ix, makeLenses, (%~), (&), (.~), (?~), (^.), (^?))++import           Data.Sequence        as S (adjust', deleteAt, (|>))+import           Data.Text            (strip)+import           Data.Time.Zones      (TZ)+import           Taskell.Data.Date    (Due (..), inputToTime)+import qualified Taskell.Data.Subtask as ST (Subtask, Update, complete, duplicate, name)++data Task = Task+    { _name        :: Text+    , _description :: Maybe Text+    , _subtasks    :: Seq ST.Subtask+    , _due         :: Maybe Due+    } deriving (Show, Eq)++type Update = Task -> Task++-- create lenses+$(makeLenses ''Task)++-- operations+create :: Text -> Maybe Due -> Maybe Text -> Seq ST.Subtask -> Task+create name' due' description' subtasks' = Task name' description' subtasks' due'++blank :: Task+blank = Task "" Nothing empty Nothing++new :: Text -> Task+new text = blank & (name .~ text)++setDescription :: Text -> Update+setDescription text =+    description .~+    if null (strip text)+        then Nothing+        else Just text++maybeAppend :: Text -> Maybe Text -> Maybe Text+maybeAppend text (Just current) = Just (concat [current, "\n", text])+maybeAppend text Nothing        = Just text++appendDescription :: Text -> Update+appendDescription text =+    if null (strip text)+        then id+        else description %~ maybeAppend text++setDue :: TZ -> UTCTime -> Text -> Task -> Maybe Task+setDue tz now date task =+    if null date+        then Just (task & due .~ Nothing)+        else (\d -> task & due ?~ d) <$> inputToTime tz now (strip date)++clearDue :: Update+clearDue task = task & due .~ Nothing++getSubtask :: Int -> Task -> Maybe ST.Subtask+getSubtask idx = (^? subtasks . ix idx)++addSubtask :: ST.Subtask -> Update+addSubtask subtask = subtasks %~ (|> subtask)++hasSubtasks :: Task -> Bool+hasSubtasks = not . null . (^. subtasks)++updateSubtask :: Int -> ST.Update -> Update+updateSubtask idx fn = subtasks %~ adjust' fn idx++removeSubtask :: Int -> Update+removeSubtask idx = subtasks %~ S.deleteAt idx++countSubtasks :: Task -> Int+countSubtasks = length . (^. subtasks)++countCompleteSubtasks :: Task -> Int+countCompleteSubtasks = length . filter (^. ST.complete) . (^. subtasks)++contains :: Text -> Task -> Bool+contains text task =+    check (task ^. name) || maybe False check (task ^. description) || not (null sts)+  where+    check = isInfixOf (toLower text) . toLower+    sts = filter check $ (^. ST.name) <$> (task ^. subtasks)++isBlank :: Task -> Bool+isBlank task =+    null (task ^. name) &&+    isNothing (task ^. description) && null (task ^. subtasks) && isNothing (task ^. due)++duplicate :: Task -> Task+duplicate (Task n d st du) = Task n d (ST.duplicate <$> st) du
− src/Taskell/Data/Task/Internal.hs
@@ -1,97 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}--module Taskell.Data.Task.Internal where--import ClassyPrelude--import Control.Lens (ix, makeLenses, (%~), (&), (.~), (?~), (^.), (^?))--import           Data.Sequence        as S (adjust', deleteAt, (|>))-import           Data.Text            (strip)-import           Data.Time.Zones      (TZ)-import           Taskell.Data.Date    (Due (..), inputToTime)-import qualified Taskell.Data.Subtask as ST (Subtask, Update, complete, duplicate, name)--data Task = Task-    { _name        :: Text-    , _description :: Maybe Text-    , _subtasks    :: Seq ST.Subtask-    , _due         :: Maybe Due-    } deriving (Show, Eq)--type Update = Task -> Task---- create lenses-$(makeLenses ''Task)---- operations-create :: Text -> Maybe Due -> Maybe Text -> Seq ST.Subtask -> Task-create name' due' description' subtasks' = Task name' description' subtasks' due'--blank :: Task-blank = Task "" Nothing empty Nothing--new :: Text -> Task-new text = blank & (name .~ text)--setDescription :: Text -> Update-setDescription text =-    description .~-    if null (strip text)-        then Nothing-        else Just text--maybeAppend :: Text -> Maybe Text -> Maybe Text-maybeAppend text (Just current) = Just (concat [current, "\n", text])-maybeAppend text Nothing        = Just text--appendDescription :: Text -> Update-appendDescription text =-    if null (strip text)-        then id-        else description %~ maybeAppend text--setDue :: TZ -> UTCTime -> Text -> Task -> Maybe Task-setDue tz now date task =-    if null date-        then Just (task & due .~ Nothing)-        else (\d -> task & due ?~ d) <$> inputToTime tz now (strip date)--clearDue :: Update-clearDue task = task & due .~ Nothing--getSubtask :: Int -> Task -> Maybe ST.Subtask-getSubtask idx = (^? subtasks . ix idx)--addSubtask :: ST.Subtask -> Update-addSubtask subtask = subtasks %~ (|> subtask)--hasSubtasks :: Task -> Bool-hasSubtasks = not . null . (^. subtasks)--updateSubtask :: Int -> ST.Update -> Update-updateSubtask idx fn = subtasks %~ adjust' fn idx--removeSubtask :: Int -> Update-removeSubtask idx = subtasks %~ S.deleteAt idx--countSubtasks :: Task -> Int-countSubtasks = length . (^. subtasks)--countCompleteSubtasks :: Task -> Int-countCompleteSubtasks = length . filter (^. ST.complete) . (^. subtasks)--contains :: Text -> Task -> Bool-contains text task =-    check (task ^. name) || maybe False check (task ^. description) || not (null sts)-  where-    check = isInfixOf (toLower text) . toLower-    sts = filter check $ (^. ST.name) <$> (task ^. subtasks)--isBlank :: Task -> Bool-isBlank task =-    null (task ^. name) &&-    isNothing (task ^. description) && null (task ^. subtasks) && isNothing (task ^. due)--duplicate :: Task -> Task-duplicate (Task n d st du) = Task n d (ST.duplicate <$> st) du
src/Taskell/IO.hs view
@@ -1,20 +1,18 @@-{-# LANGUAGE TemplateHaskell #-}- module Taskell.IO where  import ClassyPrelude  import Control.Monad.Reader (runReader)-import Data.FileEmbed       (embedFile) import Data.Text.Encoding   (decodeUtf8With) import System.Directory     (doesFileExist, getCurrentDirectory)  import Data.Time.Zones (TZ) -import Taskell.Config     (usage, version)+import Taskell.Config     (githubUsage, trelloUsage, usage, version) import Taskell.Data.Lists (Lists, analyse, initial) -import           Taskell.IO.Config         (Config, general, github, markdown, trello)+import           Taskell.IO.Config         (Config, general, getDir, github, markdown, templatePath,+                                            trello) import           Taskell.IO.Config.General (filename) import qualified Taskell.IO.Config.GitHub  as GitHub (token) import qualified Taskell.IO.Config.Trello  as Trello (token)@@ -106,18 +104,10 @@                     bool (pure Exit) (Load path ls <$ lift (writeData tz config ls path))  createTrello :: Trello.TrelloBoardID -> FilePath -> ReaderConfig Next-createTrello =-    createRemote-        (Trello.token . trello)-        (decodeUtf8 $(embedFile "templates/trello-token.txt"))-        Trello.getLists+createTrello = createRemote (Trello.token . trello) trelloUsage Trello.getLists  createGitHub :: GitHub.GitHubIdentifier -> FilePath -> ReaderConfig Next-createGitHub =-    createRemote-        (GitHub.token . github)-        (decodeUtf8 $(embedFile "templates/github-token.txt"))-        GitHub.getLists+createGitHub = createRemote (GitHub.token . github) githubUsage GitHub.getLists  exists :: Text -> ReaderConfig (Maybe FilePath) exists filepath = do@@ -140,7 +130,9 @@ createPath path = do     config <- asks ioConfig     tz <- asks ioTZ-    lift (writeData tz config initial path)+    template <- readData =<< templatePath <$> lift getDir+    let ls = either (const initial) id template+    lift (writeData tz config ls path)  -- writes Tasks to json file writeData :: TZ -> Config -> Lists -> FilePath -> IO ()
src/Taskell/IO/Config.hs view
@@ -68,6 +68,9 @@ configPath :: FilePath -> FilePath configPath = (</> "config.ini") +templatePath :: FilePath -> FilePath+templatePath = (</> "template.md")+ bindingsPath :: FilePath -> FilePath bindingsPath = (</> "bindings.ini") @@ -80,6 +83,7 @@     -- create config files     create (configPath dir) $(embedFile "templates/config.ini")     create (themePath dir) $(embedFile "templates/theme.ini")+    create (templatePath dir) $(embedFile "templates/template.md")     create (bindingsPath dir) $(embedFile "templates/bindings.ini")     -- get config     getConfig
src/Taskell/IO/Markdown/Parser.hs view
@@ -57,7 +57,7 @@ listP sym = L.create <$> listTitleP sym <*> (fromList <$> many' (taskP sym))  markdownP :: Symbol -> Parser LS.Lists-markdownP sym = fromList <$> many1 (listP sym) <* endOfInput+markdownP sym = fromList <$> many1 (listP sym) <* skipSpace <* endOfInput  -- parse parse :: Config -> Text -> Either Text LS.Lists
taskell.cabal view
@@ -1,33 +1,36 @@-cabal-version: 1.12-name: taskell-version: 1.9.3.0-license: BSD3-license-file: LICENSE-copyright: 2019 Mark Wales-maintainer: mark@smallhadroncollider.com-author: Mark Wales-homepage: https://github.com/smallhadroncollider/taskell#readme-bug-reports: https://github.com/smallhadroncollider/taskell/issues-synopsis: A command-line kanban board/task manager+cabal-version:      1.12+name:               taskell+version:            1.10.0+license:            BSD3+license-file:       LICENSE+copyright:          2019 Mark Wales+maintainer:         mark@smallhadroncollider.com+author:             Mark Wales+homepage:           https://github.com/smallhadroncollider/taskell#readme+bug-reports:        https://github.com/smallhadroncollider/taskell/issues+synopsis:           A command-line kanban board/task manager description:     Please see the README on GitHub at <https://github.com/smallhadroncollider/taskell#readme>-category: Command Line Tools-build-type: Simple++category:           Command Line Tools+build-type:         Simple extra-source-files:     README.md     templates/api-error.txt     templates/bindings.ini     templates/config.ini     templates/github-token.txt+    templates/template.md     templates/theme.ini     templates/trello-token.txt     templates/usage.txt     test/Taskell/IO/data/trello-checklists.json     test/Taskell/IO/data/trello.json+    test/Taskell/IO/data/roadmap.md     test/Taskell/IO/Keyboard/data/bindings.ini  source-repository head-    type: git+    type:     git     location: https://github.com/smallhadroncollider/taskell  library@@ -39,14 +42,10 @@         Taskell.Data.Date         Taskell.Data.Date.RelativeParser         Taskell.Data.List-        Taskell.Data.List.Internal         Taskell.Data.Lists-        Taskell.Data.Lists.Internal         Taskell.Data.Seq         Taskell.Data.Subtask-        Taskell.Data.Subtask.Internal         Taskell.Data.Task-        Taskell.Data.Task.Internal         Taskell.Events.Actions.Types         Taskell.Events.State.History         Taskell.Events.State.Types@@ -63,7 +62,8 @@         Taskell.IO.Keyboard.Types         Taskell.UI.Draw.Field         Taskell.Types-    hs-source-dirs: src++    hs-source-dirs:     src     other-modules:         Taskell.Config         Taskell.Data.Date.Types@@ -109,22 +109,25 @@         Taskell.UI.Types         Taskell.Utility.Parser         Paths_taskell-    default-language: Haskell2010-    default-extensions: OverloadedStrings NoImplicitPrelude-                        TupleSections LambdaCase RankNTypes++    default-language:   Haskell2010+    default-extensions:+        OverloadedStrings NoImplicitPrelude TupleSections LambdaCase+        RankNTypes+     build-depends:-        aeson >=1.4.6.0 && <1.5,-        attoparsec >=0.13.2.3 && <0.14,+        aeson >=1.4.7.1 && <1.5,+        attoparsec >=0.13.2.4 && <0.14,         base >=4.13.0.0 && <=5,-        brick ==0.52.*,+        brick >=0.52.1 && <0.53,         bytestring >=0.10.10.0 && <0.11,         classy-prelude >=1.5.0 && <1.6,         config-ini >=0.2.4.0 && <0.3,         containers >=0.6.2.1 && <0.7,-        directory >=1.3.4.0 && <1.4,-        file-embed >=0.0.11.1 && <0.1,+        directory >=1.3.6.0 && <1.4,+        file-embed >=0.0.11.2 && <0.1,         fold-debounce >=0.2.0.9 && <0.3,-        http-client >=0.6.4 && <0.7,+        http-client >=0.6.4.1 && <0.7,         http-conduit >=2.3.7.3 && <2.4,         http-types >=0.12.3 && <0.13,         lens >=4.18.1 && <4.19,@@ -133,17 +136,18 @@         text >=1.2.4.0 && <1.3,         time >=1.9.3 && <1.10,         tz >=0.1.3.3 && <0.2,-        vty ==5.26.*+        vty >=5.28.2 && <5.29  executable taskell-    main-is: Main.hs-    hs-source-dirs: app-    other-modules:-        Paths_taskell-    default-language: Haskell2010-    default-extensions: OverloadedStrings NoImplicitPrelude-                        TupleSections LambdaCase RankNTypes-    ghc-options: -threaded -rtsopts -with-rtsopts=-N+    main-is:            Main.hs+    hs-source-dirs:     app+    other-modules:      Paths_taskell+    default-language:   Haskell2010+    default-extensions:+        OverloadedStrings NoImplicitPrelude TupleSections LambdaCase+        RankNTypes++    ghc-options:        -threaded -rtsopts -with-rtsopts=-N     build-depends:         base >=4.13.0.0 && <4.14,         classy-prelude >=1.5.0 && <1.6,@@ -151,9 +155,9 @@         tz >=0.1.3.3 && <0.2  test-suite taskell-test-    type: exitcode-stdio-1.0-    main-is: Spec.hs-    hs-source-dirs: test+    type:               exitcode-stdio-1.0+    main-is:            Spec.hs+    hs-source-dirs:     test     other-modules:         Taskell.Data.Date.RelativeParserTest         Taskell.Data.DateTest@@ -175,16 +179,19 @@         Taskell.IO.TrelloTest         Taskell.UI.FieldTest         Paths_taskell-    default-language: Haskell2010-    default-extensions: OverloadedStrings NoImplicitPrelude-                        TupleSections LambdaCase RankNTypes-    ghc-options: -threaded -rtsopts -with-rtsopts=-N++    default-language:   Haskell2010+    default-extensions:+        OverloadedStrings NoImplicitPrelude TupleSections LambdaCase+        RankNTypes++    ghc-options:        -threaded -rtsopts -with-rtsopts=-N     build-depends:-        aeson >=1.4.6.0 && <1.5,+        aeson >=1.4.7.1 && <1.5,         base >=4.13.0.0 && <4.14,         classy-prelude >=1.5.0 && <1.6,         containers >=0.6.2.1 && <0.7,-        file-embed >=0.0.11.1 && <0.1,+        file-embed >=0.0.11.2 && <0.1,         lens >=4.18.1 && <4.19,         mtl >=2.2.2 && <2.3,         raw-strings-qq ==1.1.*,@@ -196,4 +203,4 @@         text >=1.2.4.0 && <1.3,         time >=1.9.3 && <1.10,         tz >=0.1.3.3 && <0.2,-        vty ==5.26.*+        vty >=5.28.2 && <5.29
+ templates/template.md view
@@ -0,0 +1,8 @@+## To Do+++## Doing+++## Done+
test/Taskell/Data/ListNavigationTest.hs view
@@ -7,8 +7,8 @@ import Test.Tasty import Test.Tasty.HUnit -import           Taskell.Data.List.Internal as L-import qualified Taskell.Data.Task          as T (Task, new)+import           Taskell.Data.List as L+import qualified Taskell.Data.Task as T (Task, new)  taskSeq :: Seq T.Task taskSeq =
test/Taskell/Data/ListTest.hs view
@@ -9,8 +9,8 @@ import Test.Tasty import Test.Tasty.HUnit -import           Taskell.Data.List.Internal as L-import qualified Taskell.Data.Task          as T (Task, blank, name, new)+import           Taskell.Data.List as L+import qualified Taskell.Data.Task as T (Task, blank, name, new)  emptyList :: List emptyList = L.empty "Test"
test/Taskell/Data/ListsTest.hs view
@@ -9,11 +9,11 @@  import Control.Lens ((.~)) -import           Taskell.Data.Date           (textToTime)-import qualified Taskell.Data.List           as L-import           Taskell.Data.Lists.Internal-import qualified Taskell.Data.Task           as T-import           Taskell.Types               (ListIndex (ListIndex), TaskIndex (TaskIndex))+import           Taskell.Data.Date  (textToTime)+import qualified Taskell.Data.List  as L+import           Taskell.Data.Lists+import qualified Taskell.Data.Task  as T+import           Taskell.Types      (ListIndex (ListIndex), TaskIndex (TaskIndex))  -- test data list1, list2, list3 :: L.List@@ -43,12 +43,7 @@ test_lists =     testGroup         "Data.Taskell.Lists"-        [ testCase-              "initial"-              (assertEqual-                   "Returns To Do and Done lists"-                   (fromList [L.empty "To Do", L.empty "Done"])-                   initial)+        [ testCase "initial" (assertEqual "Returns empty" (fromList []) initial)         , testCase               "updateLists"               (assertEqual
test/Taskell/Data/SubtaskTest.hs view
@@ -7,7 +7,7 @@ import Test.Tasty import Test.Tasty.HUnit -import Taskell.Data.Subtask.Internal+import Taskell.Data.Subtask  -- tests test_subtask :: TestTree
test/Taskell/Data/TaskTest.hs view
@@ -11,9 +11,9 @@  import Data.Time.Calendar (fromGregorianValid) -import           Taskell.Data.Date          (Due (DueDate))-import qualified Taskell.Data.Subtask       as ST (name, new)-import           Taskell.Data.Task.Internal+import           Taskell.Data.Date    (Due (DueDate))+import qualified Taskell.Data.Subtask as ST (name, new)+import           Taskell.Data.Task  desc :: Maybe Text desc = Just "A very boring description"
test/Taskell/IO/KeyboardTest.hs view
@@ -15,7 +15,7 @@ import Data.Time.Zones (utcTZ)  import Graphics.Vty.Input.Events       (Event (..), Key (..))-import Taskell.Data.Lists.Internal     (initial)+import Taskell.Data.Lists              (initial) import Taskell.Events.Actions.Types    as A import Taskell.Events.State            (create, quit) import Taskell.Events.State.Types      (State, Stateful, mode)
test/Taskell/IO/Markdown/ParserTest.hs view
@@ -134,6 +134,12 @@                          "List item with Sub-Task"                          (Right (makeSubTask "Blah" True))                          (parse defaultConfig "## Test\n\n- Test Item\n    * [x] Blah"))+              , testCase+                    "Line break at end"+                    (assertEqual+                         "List item"+                         (Right listWithItem)+                         (parse defaultConfig "## Test\n\n- Test Item\n\n"))               ]         , testGroup               "Alternative Format"
+ test/Taskell/IO/data/roadmap.md view
@@ -0,0 +1,361 @@+## Misc.++- Add more information to taskell.app+    > Update taskell.app to have more than just README.md contents. Use cases, more images, examples, etc.+    * [ ] Use case example: checklist+    * [ ] Use case example: Git controlled tasks+    * [ ] Blog posts for updates+- Add to Flatpack+    > https://opensource.com/article/19/10/how-build-flatpak-packaging++## Refactoring++- 乤乭 乤亍 乤乭乫 乤乭亍乫 乤乭 乤乭亍+- Refactor Task `Update` to be `Task -> Maybe Task`?+- Use Attoparsec for parsing+- Add tests for Taskell.IO.GitHub+- Break up State module+    * [ ] More of logic should go into Task, List, and Lists+- Parse checkItems Trello JSON using Aeson FromJSON rather than needing extra record type+- Remove duplication of config - currently using ini and hard-coded defaults+- Use Shake instead of bash script++## Bugs++- Empty subtasks create "---" task+    > Feel like I added this for some reason, can't think why...+- Pressing Esc while editing a task should go back to previous version?+- Selected item can still get a bit lost in SEARCH mode when going between lists+    > Sometimes defaults to 0, when there is something to be found+- Very long words should get hyphenated+    > The cursor gets lost if a word is longer than the line - URLs in particular can cause issues+- Help modal needs to wrap and scroll+- Limit modal height based on content+- Multiple spaces in a line don't show up as more than one, but are saved as more than one+- Task description should be visible by default in task detail+    > Visibility should be on the description by default?+- No obvious way to know if there are more items in a list off-screen+    > Lowest item should be "..." if more items+- Blank trello token should show info about setting it up rather than auth error+    > Auth error should show setup info too?+- A single issue with config.ini reverts to defaultConfig?++## Features++- Add Cabal test to build process+    > Use `stack sdist .` to generate cabal file+- Define key binding info in one place+    > Currently all over the place+    * [ ] Bindings+    * [ ] Default key+    * [ ] Description+    * [ ] Generate bindings.ini from it+- Somehow merge `event` and `events` in Actions that use bindings+    > Can one event trigger multiple separate state changes?+- Some way to just see tasks with due dates+    * [x] Sort by date+    * [x] Scrollable+    * [x] Pressing Enter on one takes you to it+    * [x] Use renderTask from Draw+    * [ ] Filter by: overdue, this week, later+    * [ ] Number of due items in status bar?+    * [x] Descriptions need to wrap+    * [ ] Search should filter+    * [ ] Show list each task belongs to?+    * [x] Backspace (customisable) removes date+- Configuration options+    > See #54+    * [ ] Padding+    * [ ] Move to top of list - different key binding?+    * [ ] Show toggle bar+- Edit task text in DETAIL mode+    > Need to be able to select different parts of the DETAIL modal. Probably need to rethink Mode constructors.+- Pressing Undo in DUE mode should undo without leaving view+    > Fallthrough to NORMAL events more generally? Might require rethinking modes to separate behaviour and things being tracked?+- Add a List widget for common actions between tasks and sub-tasks+    > Or use a typeclass... somehow?+    * [ ] Moving around+    * [ ] Re-ordering subtasks+    * [ ] New items above/below+    * [ ] Duplicating+    * [ ] Marking complete+    * [ ] Deleting+- Add custom key support+    * [x] Create bindings.ini+    * [x] Update events to use Map from bindings.ini+    * [ ] Check for key conflicts: include keys not explicitly mapped (e.g. 1-9, Esc, Enter)+    * [x] Check for bits of functionality missing a mapping+    * [x] Update Help dialogue with key mappings+    * [x] Needs to support merging with default options so that it's easy to add new default keys in the future+    * [ ] Add keys to Help which aren't in bindings+    * [ ] More detailed error messages for missing/invalid mappings+- Add tags/labels with `t`+    * [ ] Way to filter by tag+    * [ ] Hide/show tags with key press+- Performance with large files+    > Becomes unusable with large files+    * [x] Initially use debouncing to avoid writing too often+    * [ ] Cache formatting results+    * [ ] Invalidate layout cache less frequently+    * [ ] Benchmarking tests+    * [ ] Allow cancelling write to avoid trying to write the same file at the same time+- Should be able to have new-lines in task descriptions+    * [x] Trello import+    * [ ] Regular input (Shift + Enter for new line?)+    * [x] Markdown parsing+    * [ ] Text line breaks go a bit funny with multi-line descriptions+- Always show list title+    > Floating list titles - so you can always see what list you're in+- Make token UX better+    * [ ] Open link automatically?+    * [ ] Ask for token and save to ini file automatically+- URL field - plus config to run specific command when selected (e.g. `open -a Chrome.app #{url}`)+- Import Issues from GitHub using  labels+- Readline support?+    > Using Haskline: https://rootmos.github.io/main/2017/08/31/combining-brick-and-haskeline.html+- Editable title?+    > Use a `# Title` at top of file and display title somewhere in taskell+- Keep undo between sessions?+- Ability to load a taskell file with custom config.ini settings+    > Either command line arguments for settings or just a `-c other.ini` command+- Inifinite task depth?+    > No reason, other than UX, that sub-tasks can't have sub-tasks.+- Add Trello syncing++## In Progress++- Relative dates days and weeks shouldn't include time?+- Show remaining time on near dates++## Done++- `a` to add+- `e` to edit+- `Space` to mark complete+- `j`/`k`/`up`/`down` to move up/down list+- `h`/`l`/`Left`/`Right` to move between lists+- `q` to quit+- Create taskell.json if doesn't exist+- Move tasks up/down+- Delete with `D`+- No padding on lists+- Order of lists is wrong+- Can't switch lists+- Add doesn't go to bottom of list+- foldr1 in UI/Main will break if no lists+- Cursor support+- Move tasks between lists with `H`/`K`+- Move to next list with `Space`+- Create new list with `N`+- Delete lists with `X`+- Enter while in add mode creates new/Esc leaves add mode+- Rename Tasks to List+- Rename AllTasks to Lists+- Horizontal scrolling+- Scrolling long lists+- If no lists crashes on up/down - using index in AllTasks+- State should return Maybe?+- Select lists with `1-9`+- Run with any correctly formatted json file+- Wrap lines+- UI modules need refactoring+- Horizontal scrolling stops working if a word longer than the column width is entered+- Cursor no longer in the right place+- Space in edit/add mode doesn't move cursor along - bit disconcerting+- If no items in current list, returns a Nothing, so everything dissappears+- CreateList mode doesn't display anything+- Cursor doesn't show on CreateList mode+- Reordering lists with `>` and `<`+- `o` to add on line below+- `O` to add on line above+- `G` take to bottom of file+- Undo with `u`+- Should only save after adding a new item or finished editing an item - should save when pressing Enter while adding new items+- `C` to change task - i.e. deletes text and goes into edit mode+- Space moves to next and stays in list / H & L move but keep current+- Cursor position assumes single line list title+- `E` edits list title+- Pressing enter on Edit creates new task+- Reordering leftmost list left takes you to last list+- Linux binary+- Homebrew support+- Tabs in tasks throw off cursor and wrapping+- Cursor vertical offset is wrong when adding a new list+- Search using `/`+- Change foldl to foldl`+- Debian package+- Empty tasks aren't obviously selectable+- Pressing `e` on a blank list breaks things+- Add support for Markdown+- Fixed Unicode support+- Scrolling+- New list outside view doesn't scroll+- New item outside view doesn't scroll+- Vertical scrolling falls behind+- Use concurrency for IO+- Search UI+- Vertical scrolling hides list titles+- Use Brick for UI+- `C` doesn't work properly+- Custom colours+- `.taskell` config file in home directory+- Rename Persistence to Taskell.IO+- List titles sometimes go missing+- Use Template Haskell to import in config file templates+- On `?` show keyboard commands+- Remove size from state+- Sub-lists+    * [x] Scrolling in sub-tasks+    * [x] Press Enter to create next+    * [x] Word wrapping+    * [x] Searching+    * [x] Delete items+- No cursor in sub-task view+    * [x] Single line+    * [x] Multi-line+- Customisable Markdown format+    * [x] Change top level headers+    * [x] Change top level list item: e.g. to H3 instead of li+    * [x] Change sub-list: e.g. from "    *" to "-"+- Feels sluggish in sub-task view - cache main view?+- Leaving search only refreshes current list+- Display a warning if any line of the file could not be parsed - otherwise could lead to data loss+- One bad config line stops all config from working - needs to merge with defaultConfig+- Split up Draw/Modal code into more logical chunks+- Move between lists with `m` - shows possible lists+- Caching issue when using `m` to move lists - doesn't update previous list+- Copy and paste?+- Pressing Enter on empty list shows an subtasks box with an error+- Cursor goes missing on the left hand side at the end of a line - needs to wrap+- Sub-task count not visible on last item in a list longer than the vertical height+- Vertical spacing doesn't work if the current item is blank+- Empty tasks - i.e. just a space - don't show up+- Editing list title doesn't always have visibility+- Left/Right arrow keys in insert mode+- Share code between tasks and sub-tasks lists?+    * [x] Move wrap into Field widget+    * [x] Use Field for search+    * [x] Use Field for sub-tasks+    * [x] Use Field for titles+    * [x] Use Field for tasks+    * [x] Make sure `C` works+- Copy and paste+    * [x] List titles+    * [x] Search+- Multiple spaces at the beginning of a line can break cursor positioning+- Task body - e.g. as well as sub lists, have a longer description+- Indicator for when a task has a description+    > Use ≡?+- Pressing `Esc` when entering task description shouldn't reset it+- Better Trello import errors - e.g. auth vs. parsing issues+    * [x] Error on parse issues+    * [x] Error on Auth issues+- Add due dates to tasks with `@`+    * [x] Render due dates+    * [x] Editable due dates+- Trello dates need to take current timezone into account+    > Trello gives dates in UTC, but need to display them in the current timezone. Deadlines should also take timezones into account if necessary.+- Move to column only works for columns before the one you're in+- Add Trello import+    * [x] Basic trello import+    * [x] Add due date support+    * [x] Add sub-tasks support+    * [x] Add card summary support+- Improve Trello checklist import+    * [x] Take checklist fetch errors into account+    * [x] Refactor code+    * [x] Use Reader to pass around trello token?+- Should change list numbering to letters when in move list mode+- GitHub checklist support - []/[x]+- Caching doesn't clear properly when using `o` and `O`+- Add description status indicator option to config.ini+    > Part of the themeing should allow changing to different icon - might not work in all fonts+- Add Twitter links+    * [x] Website+    * [x] Github+- Move image to taskell.app+    > The demo image should live on the taskell.app server, rather than being on an orphan branch on GitHub+    * [x] Move image+    * [x] Update site+    * [x] Update GitHub README.md+    * [x] Remove img branch+- Sort out Homebrew forumula+    > Make the necessary changes so that taskell can be put on the homebrew-core repository+    * [x] Find someone to submit it+    * [x] Use `install_cabal_package`+    * [x] Use `depends_on "cabal-install" => :build`+- Update Task field naming+    * [x] Task: description -> name/title+    * [x] Task: summary -> description+    * [x] Taskell.UI.Modal.SubTasks -> Taskell.UI.Modal.Detail+- The isBlank check on tasks could potentially delete a task with no description but which does have sub-tasks+- Blank task names should appear as something+- Use lenses for nested data+- Add more tests+    * [x] Trello response parsing+- Add GitHub Project support+- Refactor Taskell.IO+    > Avoid repeating basically the same code for Trello and GitHub fetching+- Add ability to list GitHub projects+    > Give an organisation or username and repo, list the possible projects to fetch - avoid having to look up the project ID manually first+- GitHub import should take pagination into account+- Use XDG spec for storing config files+- Automate website publishing when doing a new build+    > Should automatically update the `_config.yml` file, build the website, then deploy it+- Can't remove a description+- Title bar for extra info+    * [x] File path+    * [x] Current position+- Search should be case insensitive+- Add Mode to status bar+- Modals interfere with status bar+- Showing a specific task in search mode shows wrong task+    > Based on the index in the full list, rather than the filtered one. So will show the task from the full list if the indexes don't happen to match.+- Can't remove dates+- Tidy up load functions in Taskell.IO+- Getting stuck in INSERT mode when blank item+- If an item isn't created, then selection gets lost+- Search navigation issues+    > Issues with navigation when in NORMAL + SEARCH mode+    * [x] Navigating up and down+    * [x] Navigating between lists+    * [x] Moving task up and down+    * [x] Often nothing is selected when first entering search mode+- Duplicate task with `+`+- Use Reader throughout Draw/Modal modules+- Simplify DrawState+    * [x] Bindings+    * [x] Today+    * [x] Normalised State+    * [x] Layout+- Add a debug option+    * [x] Shows full Mode print out+- Add a "complete" action+    > Moves to last column and removes date. Space bar by default.+- Remove `~` style sub-task complete parsing+- MoveTo shouldn't show if nothing selected+- Deleting a list can screw up current position+- Remove date+    * [x] NORMAL mode+    * [x] DETAIL mode+    * [x] DUE mode+- Update screenshot+- Date should update if taskell is left open+- Use proper error codes+- Redo functionality+- Modifier keys?+- Use relative times for due dates+    > e.g. 1w, 2d, 1w 2d (see `man sleep` options for ideas)+    * [x] Markdown input+    * [x] Markdown output+    * [x] Markdown needs to use actual timezone+    * [x] Need to use parser+    * [x] Trello time parsing+- Use ReaderConfig in Taskell.IO.Markdown.Internal stringify functions+- Add test for subtask linebreaks to MarkdownTest+- Config option to always use UTC for markdown output+- Check times work no matter what timezone+- Show time on short dates+- Date validation+- Check order of due items+- Trim spaces for date input