mirror of
https://github.com/Horhik/dotfiles.git
synced 2024-11-29 11:31:29 +00:00
399 lines
12 KiB
Haskell
399 lines
12 KiB
Haskell
|
{-# LANGUAGE ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances #-}
|
||
|
-----------------------------------------------------------------------------
|
||
|
-- |
|
||
|
-- Module : TaskMonad
|
||
|
-- Copyright : Max magorsch <max@magorsch.de>
|
||
|
-- License : BSD-style (see LICENSE)
|
||
|
--
|
||
|
-- Maintainer : Max Magorsch <max@magorsch.de>
|
||
|
-- Stability : unstable
|
||
|
-- Portability : unportable
|
||
|
--
|
||
|
-- TaskMonad bundles a number of tools that can be used to directly interact
|
||
|
-- with taskwarrior from within xmonad. Furthermore, workflows following the
|
||
|
-- Getting Things Done principles are implemented.
|
||
|
--
|
||
|
--
|
||
|
-----------------------------------------------------------------------------
|
||
|
|
||
|
module TaskMonad
|
||
|
(
|
||
|
-- * Installation
|
||
|
|
||
|
-- ** Install with Cabal
|
||
|
-- $installWithCabal
|
||
|
|
||
|
-- ** Install without Cabal
|
||
|
-- $installWithoutCabal
|
||
|
|
||
|
-- * Usage
|
||
|
-- $usage
|
||
|
|
||
|
-- * Step 1: Capture
|
||
|
-- $capture
|
||
|
taskwarriorPrompt
|
||
|
,
|
||
|
|
||
|
-- * Step 2 & 3: Clarify & Organize
|
||
|
-- $organize
|
||
|
processInbox
|
||
|
,
|
||
|
|
||
|
-- * Step 4: Reflect
|
||
|
-- $reflect
|
||
|
togglePriority
|
||
|
,
|
||
|
|
||
|
-- * Step 5: Engage
|
||
|
-- $engage
|
||
|
taskSelect
|
||
|
, dueSelect
|
||
|
, tagSelect
|
||
|
, projectSelect
|
||
|
,
|
||
|
|
||
|
-- * Scratchpad
|
||
|
-- $scratchpad
|
||
|
taskwarriorScratchpads
|
||
|
, taskwarriorScratchpad
|
||
|
|
||
|
-- * All Components
|
||
|
-- $components
|
||
|
)
|
||
|
where
|
||
|
|
||
|
import Data.List
|
||
|
import Data.Maybe
|
||
|
import System.Process
|
||
|
import System.IO
|
||
|
import Control.Monad ( filterM )
|
||
|
|
||
|
import XMonad hiding ( liftX )
|
||
|
import XMonad.Util.Font
|
||
|
import qualified XMonad.StackSet as W
|
||
|
import XMonad.Layout.Decoration
|
||
|
import XMonad.Prompt
|
||
|
import XMonad.Prompt.Input
|
||
|
import XMonad.Util.Image
|
||
|
import XMonad.Util.NamedWindows
|
||
|
import XMonad.Util.XUtils
|
||
|
import XMonad.Util.NamedScratchpad
|
||
|
import XMonad.Util.Run
|
||
|
import XMonad.Actions.GridSelect
|
||
|
|
||
|
import qualified GridSelect.Extras
|
||
|
|
||
|
import TaskMonad.Prompt
|
||
|
import TaskMonad.ScratchPad
|
||
|
import TaskMonad.Utils
|
||
|
import TaskMonad.GridSelect
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
-- $installWithCabal
|
||
|
--
|
||
|
-- To install TaskMonad from hackage just execute:
|
||
|
--
|
||
|
-- > cabal update
|
||
|
-- > cabal install TaskMonad
|
||
|
--
|
||
|
-- Afterwards import TaskMonad in your `xmonad.hs`
|
||
|
--
|
||
|
-- > import TaskMonad
|
||
|
--
|
||
|
|
||
|
-- $installWithoutCabal
|
||
|
--
|
||
|
-- To install Taskmonad without using cabal just download and copy the source code into your `~/.xmonad/-- lib/` folder. The folder structure should afterwards look like this:
|
||
|
--
|
||
|
--
|
||
|
-- > .xmonad
|
||
|
-- > |-- lib
|
||
|
-- > | |-- Taskmonad.hs
|
||
|
-- > | |-- Taskmonad
|
||
|
-- > | | |-- GridSelect.hs
|
||
|
-- > | | |-- Prompt.hs
|
||
|
-- > | | |-- ScratchPad.hs
|
||
|
-- > | | `-- Utils.hs
|
||
|
-- > | |-- GridSelect
|
||
|
-- > | | `-- Extras.hs
|
||
|
-- > | `-- ...
|
||
|
-- > |-- xmonad.hs
|
||
|
--
|
||
|
--
|
||
|
-- Afterwards import TaskMonad in your `xmonad.hs`
|
||
|
--
|
||
|
-- > import TaskMonad
|
||
|
--
|
||
|
|
||
|
|
||
|
-- $usage
|
||
|
-- To get started, add a manage hook for the taskwarrior scratchpad:
|
||
|
--
|
||
|
-- > import TaskMonad
|
||
|
-- >
|
||
|
-- > -- ...
|
||
|
-- >
|
||
|
-- > ... , manageHook = namedScratchpadManageHook taskwarriorScratchpads
|
||
|
--
|
||
|
-- After that you can bind the taskwarrior prompt to a key to get started:
|
||
|
--
|
||
|
-- > ... , ("M-p", taskwarriorPrompt [(\x -> x == "processInbox", processInbox)])
|
||
|
--
|
||
|
-- You can also bind any other TaskMonad action to a key. For example:
|
||
|
--
|
||
|
-- > ... , ("M-S-p", taskwarriorScratchpad) -- Opens the taskwarrior scratchpad
|
||
|
-- >
|
||
|
-- > ... , ("M-C-p", taskSelect "status:pending") -- Displays all pending tasks
|
||
|
-- >
|
||
|
-- > ... , ("M-C-S-p", tagSelect) -- Displays all tags
|
||
|
--
|
||
|
-- In general you can customize the tools ad libitum. A good way to get started is to implement custom actions for the taskwarrior prompt. Please refer to 'taskwarriorPrompt' for further information.
|
||
|
|
||
|
|
||
|
-- $capture
|
||
|
--
|
||
|
-- You can easily capture tasks, ideas or notes using the 'taskwarriorPrompt' like this:
|
||
|
--
|
||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/capture.png >>
|
||
|
--
|
||
|
|
||
|
-- $organize
|
||
|
-- You can clarify and organize your tasks using 'processInbox'.
|
||
|
-- It implements the typical Getting Things Done workflow using GridSelects:
|
||
|
--
|
||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/workflow.png >>
|
||
|
--
|
||
|
|
||
|
-- $reflect
|
||
|
-- You can implement your own custom daily- and weeklyreview routines.
|
||
|
-- For example you can use 'togglePriority' to adjust the priority of tasks
|
||
|
-- during the daily- / weeklyreview like this:
|
||
|
--
|
||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/taskmonad-gridselect.png >>
|
||
|
|
||
|
-- $engage
|
||
|
-- To decide which task to do next, you can use a collection of gridselects.
|
||
|
-- You can use 'tagSelect', 'projectSelect', 'dueSelect' to display a gridselect
|
||
|
-- to filter the tasks by tag, project or due date. However you can also display
|
||
|
-- all pending tasks using 'taskSelect' like this:
|
||
|
--
|
||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/engage.png >>
|
||
|
|
||
|
-- $scratchpad
|
||
|
-- The taskwarrior scratchpad is used to display taskwarrior reports that
|
||
|
-- have been invoked using the taskwarrior prompt. However, you can use the
|
||
|
-- scratchpad at your convenience. Just add a manage hook:
|
||
|
--
|
||
|
-- > ... , manageHook = namedScratchpadManageHook taskwarriorScratchpads
|
||
|
--
|
||
|
-- Afterwards you can bind a key to 'taskwarriorScratchpad'. The Scratchpad will look like this
|
||
|
--
|
||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/taskmonad-scratchpad.png >>
|
||
|
|
||
|
-- $components
|
||
|
-- * 'TaskMonad'
|
||
|
--
|
||
|
-- * 'TaskMonad.Prompt'
|
||
|
-- * 'TaskMonad.Scratchpad'
|
||
|
-- * 'TaskMonad.GridSelect'
|
||
|
-- * 'TaskMonad.Utils'
|
||
|
--
|
||
|
-- * 'GridSelect.Extras'
|
||
|
|
||
|
|
||
|
-- | Opens a set of gridselects used to process the inbox using the Getting Things Done workflow
|
||
|
processInbox :: X ()
|
||
|
processInbox = io (getTaskwarriorTaskList "+INBOX" ["id", "description"])
|
||
|
>>= \ts -> startInboxProcessing ts
|
||
|
|
||
|
|
||
|
-- | Recursively processes a given list of tasks using the typical GTD workflow.
|
||
|
startInboxProcessing :: [[String]] -> X ()
|
||
|
startInboxProcessing [] = dummyAction
|
||
|
startInboxProcessing (t : ts) = twgs
|
||
|
("Actionable? " ++ t !! 1, [("YES", actionable), ("NO", not_actionable)])
|
||
|
where
|
||
|
not_actionable = twgs
|
||
|
( "Next Steps: " ++ t !! 1
|
||
|
, [ ("[Back]" , startInboxProcessing (t : ts))
|
||
|
, ("Reference" , moveToFile t ts)
|
||
|
, ("Project Support", moveToFile t ts)
|
||
|
, ("Someday/Later" , somedayLater t ts)
|
||
|
, ("Trash" , trash t ts)
|
||
|
]
|
||
|
)
|
||
|
|
||
|
actionable = twgs
|
||
|
( "Does it take multiple steps?"
|
||
|
, [ ("[Back]", startInboxProcessing (t : ts))
|
||
|
, ("YES" , makeProject t ts)
|
||
|
, ("NO" , single_step)
|
||
|
]
|
||
|
)
|
||
|
|
||
|
single_step = twgs
|
||
|
( "Does it take less than 2 minutes?"
|
||
|
, [("[Back]", actionable), ("YES", do_it), ("NO", more_than_2_min)]
|
||
|
)
|
||
|
|
||
|
do_it =
|
||
|
twgs ("Do it now!", [("[Back]", single_step), ("Finished", done t ts)])
|
||
|
|
||
|
more_than_2_min = twgs
|
||
|
( "Next Steps: " ++ t !! 1
|
||
|
, [ ("[Back]" , single_step)
|
||
|
, ("Move to calendar", calendar t ts)
|
||
|
, ("Waiting For" , waitingFor t ts)
|
||
|
, ("Edit Task" , edit_task)
|
||
|
]
|
||
|
)
|
||
|
|
||
|
edit_task = twgs
|
||
|
( "How to process task: " ++ head t
|
||
|
, [ ("[Back]" , more_than_2_min)
|
||
|
, ("[Finish]" , startInboxProcessing ts)
|
||
|
, ("Free Editing" , editTaskAction t ts)
|
||
|
, ("Set Tags" , editTaskAction t ts)
|
||
|
, ("Set Description", editTaskAction t ts)
|
||
|
, ("Set Due Date" , editTaskAction t ts)
|
||
|
, ("Set Project" , editTaskAction t ts)
|
||
|
, ("Set Context" , editTaskAction t ts)
|
||
|
]
|
||
|
)
|
||
|
|
||
|
|
||
|
-- | Sends a message to the notification daemon
|
||
|
notify :: MonadIO m => String -> m ()
|
||
|
notify message = unsafeSpawn $ "notify-send '" ++ message ++ "'"
|
||
|
|
||
|
|
||
|
-- | Opens a GridSelect with a custom message and the taskwarrior icon
|
||
|
twgs :: (String, [(String, X ())]) -> X ()
|
||
|
twgs (message, actions) = GridSelect.Extras.runSelectedActionWithMessageAndIcon
|
||
|
defaultTWGSExtraConfig
|
||
|
message
|
||
|
twicon
|
||
|
actions
|
||
|
|
||
|
|
||
|
-- | Deletes the current task. Afterwards the remaining tasks will be processed.
|
||
|
trash
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
trash t ts = do
|
||
|
runTmuxCommand $ "echo 'yes' | task " ++ head t ++ " delete"
|
||
|
notify $ "Deleted Task:\n\n " ++ (t !! 1)
|
||
|
startInboxProcessing ts
|
||
|
|
||
|
|
||
|
-- | Sets a task to done. Afterwards the remaining tasks will be processed.
|
||
|
done
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
done t ts = do
|
||
|
runTmuxCommand $ "echo 'yes' | task " ++ head t ++ " done"
|
||
|
notify $ "Done Task:\n\n " ++ (t !! 1)
|
||
|
startInboxProcessing ts
|
||
|
|
||
|
|
||
|
-- | Changes the tag of the current task from INBOX to SOMEDAY
|
||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||
|
somedayLater
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
somedayLater t ts = do
|
||
|
runTmuxCommand $ "task " ++ head t ++ " modify -INBOX +SOMEDAY"
|
||
|
notify $ "Changed Task:\n\n " ++ (t !! 1) ++ "\n\n to Somday/Maybe"
|
||
|
startInboxProcessing ts
|
||
|
|
||
|
|
||
|
-- | Changes the tag of the current task from INBOX to WAITINGFOR
|
||
|
-- Afterwards the remaining tasks will be processed.
|
||
|
waitingFor
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
waitingFor t ts = do
|
||
|
runTmuxCommand $ "task " ++ head t ++ " modify -INBOX +WAITINGFOR"
|
||
|
notify $ "Changed Task:\n\n " ++ (t !! 1) ++ "\n\n to Waiting For"
|
||
|
startInboxProcessing ts
|
||
|
|
||
|
|
||
|
-- | Moves the information of the current task to a markdown file.
|
||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||
|
moveToFile
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
moveToFile t ts = do
|
||
|
runTmuxCommand
|
||
|
$ "task information "
|
||
|
++ head t
|
||
|
++ " >> ~/reference/inbox.md && echo 'yes' | task "
|
||
|
++ head t
|
||
|
++ " done"
|
||
|
notify
|
||
|
$ "Moved the task:\n\n "
|
||
|
++ (t !! 1)
|
||
|
++ "\n\n to the file: \n\n ~/reference/inbox.md.\n \n Please consider editing the file."
|
||
|
startInboxProcessing ts
|
||
|
|
||
|
|
||
|
-- | Opens a customPrompt to create an appointment using
|
||
|
-- [gcalcli](https://github.com/insanum/gcalcli)
|
||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||
|
calendar
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
calendar t ts = customPrompt "gcalcli add" [] (addAndContinue t ts)
|
||
|
where
|
||
|
addAndContinue t ts x = do
|
||
|
runTmuxCommand ("gcalcli add --noprompt " ++ x)
|
||
|
notify "Created Appointment."
|
||
|
done t ts
|
||
|
|
||
|
|
||
|
-- | Opens a customPrompt to create a project for a given task
|
||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||
|
makeProject
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
makeProject t ts = do
|
||
|
notify ("Please create your first task for the project: \n\n " ++ t !! 1)
|
||
|
customPrompt "task add" [] (addAndContinue t ts)
|
||
|
where
|
||
|
addAndContinue t ts x = do
|
||
|
runTmuxCommand ("task add " ++ x)
|
||
|
done t ts
|
||
|
|
||
|
|
||
|
-- | Opens a customPrompt in order to modify the current taskwarrior task.
|
||
|
-- Afterwards the remaining tasks will be processed.
|
||
|
editTaskAction
|
||
|
:: [String] -- ^ the current task
|
||
|
-> [[String]] -- ^ the remaining tasks
|
||
|
-> X ()
|
||
|
editTaskAction t ts = do
|
||
|
notify "Please edit your task."
|
||
|
customPrompt ("task " ++ head t ++ " modify") [] (addAndContinue t ts)
|
||
|
where
|
||
|
addAndContinue t ts x = do
|
||
|
runTmuxCommand ("task " ++ head t ++ " modify " ++ x)
|
||
|
notify "Edited the task"
|
||
|
startInboxProcessing ts
|
||
|
|
||
|
|
||
|
-- | A dummy action for testing purposes
|
||
|
dummyAction :: X ()
|
||
|
dummyAction = createTWwindow >>= deleteWindow
|
||
|
where
|
||
|
createTWwindow =
|
||
|
createNewWindow (Rectangle 450 150 1000 60) Nothing "Test" True
|