summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsmallhadroncollider <>2020-08-10 15:12:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2020-08-10 15:12:00 (GMT)
commit5ade166738f4a1b97a9c703283fbd94da103cc10 (patch)
tree6e496975e58e8af15ed4431a7375c09807a04346
parent3ebf4b02430a09122a5b294f2af87a8bf92e47d7 (diff)
version 1.10.01.10.0
-rw-r--r--README.md4
-rw-r--r--src/Taskell/Config.hs13
-rw-r--r--src/Taskell/Data/List.hs156
-rw-r--r--src/Taskell/Data/List/Internal.hs128
-rw-r--r--src/Taskell/Data/Lists.hs109
-rw-r--r--src/Taskell/Data/Lists/Internal.hs89
-rw-r--r--src/Taskell/Data/Subtask.hs41
-rw-r--r--src/Taskell/Data/Subtask/Internal.hs29
-rw-r--r--src/Taskell/Data/Task.hs124
-rw-r--r--src/Taskell/Data/Task/Internal.hs97
-rw-r--r--src/Taskell/IO.hs24
-rw-r--r--src/Taskell/IO/Config.hs4
-rw-r--r--src/Taskell/IO/Markdown/Parser.hs2
-rw-r--r--taskell.cabal101
-rw-r--r--templates/template.md8
-rw-r--r--test/Taskell/Data/ListNavigationTest.hs4
-rw-r--r--test/Taskell/Data/ListTest.hs4
-rw-r--r--test/Taskell/Data/ListsTest.hs17
-rw-r--r--test/Taskell/Data/SubtaskTest.hs2
-rw-r--r--test/Taskell/Data/TaskTest.hs6
-rw-r--r--test/Taskell/IO/KeyboardTest.hs2
-rw-r--r--test/Taskell/IO/Markdown/ParserTest.hs6
-rw-r--r--test/Taskell/IO/data/roadmap.md361
23 files changed, 815 insertions, 516 deletions
diff --git a/README.md b/README.md
index 4320da0..cbac827 100644
--- a/README.md
+++ b/README.md
@@ -257,6 +257,10 @@ subtask = "-"
**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.
diff --git a/src/Taskell/Config.hs b/src/Taskell/Config.hs
index 0374cd1..d4f811f 100644
--- a/src/Taskell/Config.hs
+++ b/src/Taskell/Config.hs
@@ -4,10 +4,19 @@ module Taskell.Config where
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")
diff --git a/src/Taskell/Data/List.hs b/src/Taskell/Data/List.hs
index 832ccd5..1d8a0ee 100644
--- a/src/Taskell/Data/List.hs
+++ b/src/Taskell/Data/List.hs
@@ -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
-
-import Taskell.Data.List.Internal
+{-# LANGUAGE TemplateHaskell #-}
+
+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
diff --git a/src/Taskell/Data/List/Internal.hs b/src/Taskell/Data/List/Internal.hs
deleted file mode 100644
index d42d48e..0000000
--- a/src/Taskell/Data/List/Internal.hs
+++ /dev/null
@@ -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
diff --git a/src/Taskell/Data/Lists.hs b/src/Taskell/Data/Lists.hs
index c7a7b8c..4d5f4ce 100644
--- a/src/Taskell/Data/Lists.hs
+++ b/src/Taskell/Data/Lists.hs
@@ -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
-
-import Taskell.Data.Lists.Internal
+module Taskell.Data.Lists 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 []
+
+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)
+ ]
diff --git a/src/Taskell/Data/Lists/Internal.hs b/src/Taskell/Data/Lists/Internal.hs
deleted file mode 100644
index d981f66..0000000
--- a/src/Taskell/Data/Lists/Internal.hs
+++ /dev/null
@@ -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)
- ]
diff --git a/src/Taskell/Data/Subtask.hs b/src/Taskell/Data/Subtask.hs
index baf6baa..c51a650 100644
--- a/src/Taskell/Data/Subtask.hs
+++ b/src/Taskell/Data/Subtask.hs
@@ -1,12 +1,29 @@
-module Taskell.Data.Subtask
- ( Subtask
- , Update
- , new
- , blank
- , name
- , complete
- , toggle
- , duplicate
- ) where
-
-import Taskell.Data.Subtask.Internal
+{-# LANGUAGE TemplateHaskell #-}
+
+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
diff --git a/src/Taskell/Data/Subtask/Internal.hs b/src/Taskell/Data/Subtask/Internal.hs
deleted file mode 100644
index 3c6b3d8..0000000
--- a/src/Taskell/Data/Subtask/Internal.hs
+++ /dev/null
@@ -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
diff --git a/src/Taskell/Data/Task.hs b/src/Taskell/Data/Task.hs
index 249cd8c..41c1dc6 100644
--- a/src/Taskell/Data/Task.hs
+++ b/src/Taskell/Data/Task.hs
@@ -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
-
-import Taskell.Data.Task.Internal
+{-# LANGUAGE TemplateHaskell #-}
+
+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
diff --git a/src/Taskell/Data/Task/Internal.hs b/src/Taskell/Data/Task/Internal.hs
deleted file mode 100644
index ba162bb..0000000
--- a/src/Taskell/Data/Task/Internal.hs
+++ /dev/null
@@ -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
diff --git a/src/Taskell/IO.hs b/src/Taskell/IO.hs
index 813a31a..f463b7e 100644
--- a/src/Taskell/IO.hs
+++ b/src/Taskell/IO.hs
@@ -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 @@ createRemote tokenFn missingToken getFn identifier path = do
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 :: FilePath -> ReaderConfig ()
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 ()
diff --git a/src/Taskell/IO/Config.hs b/src/Taskell/IO/Config.hs
index 3772a00..3951c25 100644
--- a/src/Taskell/IO/Config.hs
+++ b/src/Taskell/IO/Config.hs
@@ -68,6 +68,9 @@ themePath = (</> "theme.ini")
configPath :: FilePath -> FilePath
configPath = (</> "config.ini")
+templatePath :: FilePath -> FilePath
+templatePath = (</> "template.md")
+
bindingsPath :: FilePath -> FilePath
bindingsPath = (</> "bindings.ini")
@@ -80,6 +83,7 @@ setup
-- 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
diff --git a/src/Taskell/IO/Markdown/Parser.hs b/src/Taskell/IO/Markdown/Parser.hs
index 420fc94..b0bb915 100644
--- a/src/Taskell/IO/Markdown/Parser.hs
+++ b/src/Taskell/IO/Markdown/Parser.hs
@@ -57,7 +57,7 @@ listP :: Symbol -> Parser L.List
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
diff --git a/taskell.cabal b/taskell.cabal
index 78707ca..911a4d7 100644
--- a/taskell.cabal
+++ b/taskell.cabal
@@ -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 @@ library
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 @@ library
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 @@ library
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 @@ library
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 @@ executable taskell
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 @@ test-suite taskell-test
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 @@ test-suite taskell-test
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
diff --git a/templates/template.md b/templates/template.md
new file mode 100644
index 0000000..4da1daf
--- /dev/null
+++ b/templates/template.md
@@ -0,0 +1,8 @@
+## To Do
+
+
+## Doing
+
+
+## Done
+
diff --git a/test/Taskell/Data/ListNavigationTest.hs b/test/Taskell/Data/ListNavigationTest.hs
index 00fcbb3..e0a98db 100644
--- a/test/Taskell/Data/ListNavigationTest.hs
+++ b/test/Taskell/Data/ListNavigationTest.hs
@@ -7,8 +7,8 @@ import ClassyPrelude as CP
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 =
diff --git a/test/Taskell/Data/ListTest.hs b/test/Taskell/Data/ListTest.hs
index 459c22e..d2790ed 100644
--- a/test/Taskell/Data/ListTest.hs
+++ b/test/Taskell/Data/ListTest.hs
@@ -9,8 +9,8 @@ import Control.Lens ((.~))
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"
diff --git a/test/Taskell/Data/ListsTest.hs b/test/Taskell/Data/ListsTest.hs
index 47f29d9..d5753e3 100644
--- a/test/Taskell/Data/ListsTest.hs
+++ b/test/Taskell/Data/ListsTest.hs
@@ -9,11 +9,11 @@ import Test.Tasty.HUnit
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 :: TestTree
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
diff --git a/test/Taskell/Data/SubtaskTest.hs b/test/Taskell/Data/SubtaskTest.hs
index 0cb9aa6..505608c 100644
--- a/test/Taskell/Data/SubtaskTest.hs
+++ b/test/Taskell/Data/SubtaskTest.hs
@@ -7,7 +7,7 @@ import ClassyPrelude
import Test.Tasty
import Test.Tasty.HUnit
-import Taskell.Data.Subtask.Internal
+import Taskell.Data.Subtask
-- tests
test_subtask :: TestTree
diff --git a/test/Taskell/Data/TaskTest.hs b/test/Taskell/Data/TaskTest.hs
index cfd197e..76d0e2c 100644
--- a/test/Taskell/Data/TaskTest.hs
+++ b/test/Taskell/Data/TaskTest.hs
@@ -11,9 +11,9 @@ import Test.Tasty.HUnit
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"
diff --git a/test/Taskell/IO/KeyboardTest.hs b/test/Taskell/IO/KeyboardTest.hs
index 3e0eb8f..5f0ed1b 100644
--- a/test/Taskell/IO/KeyboardTest.hs
+++ b/test/Taskell/IO/KeyboardTest.hs
@@ -15,7 +15,7 @@ import Data.Time.Clock (secondsToDiffTime)
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)
diff --git a/test/Taskell/IO/Markdown/ParserTest.hs b/test/Taskell/IO/Markdown/ParserTest.hs
index c46b2a8..909a8ff 100644
--- a/test/Taskell/IO/Markdown/ParserTest.hs
+++ b/test/Taskell/IO/Markdown/ParserTest.hs
@@ -134,6 +134,12 @@ test_parser =
"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"
diff --git a/test/Taskell/IO/data/roadmap.md b/test/Taskell/IO/data/roadmap.md
new file mode 100644
index 0000000..3cc6a1a
--- /dev/null
+++ b/test/Taskell/IO/data/roadmap.md
@@ -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