Skip to content

Commit 09565db

Browse files
authored
Merge pull request #124 from ad-si/claude/implement-feature-78-vEyTT
Enable users to define their own shortcuts via the config file
2 parents de6ffaf + 8324829 commit 09565db

11 files changed

Lines changed: 328 additions & 5 deletions

File tree

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ jobs:
7272
arch: [x86_64, arm64]
7373
include:
7474
- arch: x86_64
75-
runner: macos-14
75+
runner: macos-15-intel
7676
artifact: tasklite_macos_x86_64
7777
- arch: arm64
78-
runner: macos-latest
78+
runner: macos-26
7979
artifact: tasklite_macos_arm64
8080

8181
runs-on: ${{ matrix.runner }}

docs-source/cli/usage.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,60 @@ It is also possible to immediately add tags when creating a task:
4040
tl add Improve the TaskLite manual +tasklite +pc
4141
```
4242

43+
### Built-in Shortcuts
44+
45+
TaskLite provides several built-in shortcuts to quickly add tasks with common tags:
46+
47+
- `tl write "Email to John"` → Adds "Write Email to John +write"
48+
- `tl read "Article about Haskell"` → Adds "Read Article about Haskell +read"
49+
- `tl idea "New feature"` → Adds "New feature +idea"
50+
- `tl watch "Haskell tutorial"` → Adds "Watch Haskell tutorial +watch"
51+
- `tl listen "Podcast episode"` → Adds "Listen Podcast episode +listen"
52+
- `tl buy "Groceries"` → Adds "Buy Groceries +buy"
53+
- `tl sell "Old laptop"` → Adds "Sell Old laptop +sell"
54+
- `tl pay "Electric bill"` → Adds "Pay Electric bill +pay"
55+
- `tl ship "Package to Mom"` → Adds "Ship Package to Mom +ship"
56+
57+
### Custom Shortcuts
58+
59+
You can also define your own shortcuts in the config file.
60+
For example, to add shortcuts for `cook`, `call`, `fix`, and `shopping`:
61+
62+
```yaml
63+
shortcuts:
64+
cook:
65+
prefix: Cook
66+
tag: cook
67+
call:
68+
prefix: Call
69+
tag: call
70+
fix:
71+
tag: fix # No prefix, just adds the tag
72+
shopping:
73+
prefix: Buy
74+
tags: [shopping, errands] # Multiple tags
75+
quick:
76+
prefix: Do
77+
tags: [] # No tags, just the prefix
78+
```
79+
80+
With this configuration:
81+
82+
- `tl cook dinner` → Adds "Cook dinner +cook"
83+
- `tl call Mom` → Adds "Call Mom +call"
84+
- `tl fix login bug` → Adds "login bug +fix"
85+
- `tl shopping milk` → Adds "Buy milk +shopping +errands"
86+
- `tl quick something` → Adds "Do something"
87+
88+
Each shortcut has the following fields:
89+
90+
- `prefix` (optional): Text to prepend to the task body
91+
- `tag` (optional): Single tag to add to the task (without the `+` prefix)
92+
- `tags` (optional): List of tags to add to the task (without the `+` prefix)
93+
94+
You can use either `tag` for a single tag or `tags` for multiple tags.
95+
If both are specified, they are combined.
96+
4397
And even to set certain fields:
4498

4599
```shell

tasklite-core/example-config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,30 @@ progressBarWidth: 24
4444
# pre:
4545
# - interpreter: python3
4646
# body: print('Python test')
47+
48+
# Custom shortcuts to quickly add tasks with tags.
49+
# For example: `tl cook Dinner` will add a task "Cook Dinner +cook"
50+
# You can use either `tag` for a single tag or `tags` for multiple tags.
51+
# shortcuts:
52+
# cook:
53+
# prefix: Cook
54+
# tag: cook
55+
# wash:
56+
# prefix: Wash
57+
# tag: wash
58+
# print:
59+
# prefix: Print
60+
# tag: print
61+
# call:
62+
# prefix: Call
63+
# tag: call
64+
# email:
65+
# prefix: Email
66+
# tag: email
67+
# fix:
68+
# tag: fix
69+
# shopping:
70+
# prefix: Buy
71+
# tags: [shopping, errands] # Multiple tags
72+
# quick:
73+
# tags: [] # No tags, just the prefix

tasklite-core/package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ library:
5959
- ansi-terminal >= 0.11 && < 1.2
6060
- bytestring >= 0.11 && < 0.13
6161
- cassava >= 0.5.2 && < 0.6
62+
- containers >= 0.6 && < 0.8
6263
- colour >= 2.3 && < 2.4
6364
- directory >= 1.3 && < 1.4
6465
- editor-open >= 0.4 && < 0.7

tasklite-core/source/Cli.hs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,10 @@ import Config (
152152
Config (..),
153153
HookSet (..),
154154
HooksConfig (..),
155+
Shortcut (..),
155156
addHookFilesToConfig,
156157
)
158+
import Data.Map.Strict qualified as Map
157159
import Control.Arrow ((>>>))
158160
import Hooks (executeHooks, formatHookResult)
159161
import ImportExport (
@@ -385,6 +387,8 @@ data Command
385387
| Gui
386388
| UlidToUtc Text
387389
| ExternalCommand Text (Maybe [Text])
390+
| AddCustomShortcut Shortcut [Text]
391+
-- ^ Custom shortcut command from config
388392
deriving (Show, Eq)
389393

390394

@@ -448,6 +452,23 @@ getCommand (alias, commandName) =
448452
(progDesc $ T.unpack $ alias <> "-> " <> commandName)
449453

450454

455+
-- | Create a parser for a custom shortcut from config
456+
getShortcutCommand :: (Text, Shortcut) -> Mod CommandFields Command
457+
getShortcutCommand (cmdName, shortcut) =
458+
command (T.unpack cmdName) $
459+
info
460+
(AddCustomShortcut shortcut <$> some (strArgument
461+
(metavar "BODY" <> help "Body of the task")))
462+
(progDesc $ T.unpack $ description)
463+
where
464+
description = case shortcut.prefix of
465+
Just pfx -> pfx <> " something (custom shortcut)"
466+
Nothing -> case shortcut.tags of
467+
[] -> "Add task (custom shortcut)"
468+
[t] -> "Add task with +" <> t <> " tag (custom shortcut)"
469+
ts -> "Add task with " <> T.intercalate ", " (fmap ("+" <>) ts) <> " tags (custom shortcut)"
470+
471+
451472
toParserInfo :: Parser a -> Text -> ParserInfo a
452473
toParserInfo parser description =
453474
info parser (fullDesc <> progDesc (T.unpack description))
@@ -472,6 +493,7 @@ idsVar =
472493
-- | Help Sections
473494
basic_sec
474495
, shortcut_sec
496+
, custom_shortcut_sec
475497
, list_sec
476498
, vis_sec
477499
, i_o_sec
@@ -482,6 +504,7 @@ basic_sec
482504
(Text, Text)
483505
basic_sec = ("{{basic_sec}}", "Basic Commands")
484506
shortcut_sec = ("{{shortcut_sec}}", "Shortcuts to Add a Task")
507+
custom_shortcut_sec = ("{{custom_shortcut_sec}}", "Custom Shortcuts")
485508
list_sec = ("{{list_sec}}", "List Commands")
486509
vis_sec = ("{{vis_sec}}", "Visualizations")
487510
i_o_sec = ("{{i_o_sec}}", "I/O Commands")
@@ -730,6 +753,16 @@ commandParser conf =
730753
"Ship an item to someone")
731754
)
732755

756+
-- Custom shortcuts from config (only show section if there are shortcuts)
757+
<|> (if null (Map.toList conf.shortcuts)
758+
then P.empty
759+
else hsubparser
760+
( metavar (T.unpack $ snd custom_shortcut_sec)
761+
<> commandGroup (T.unpack $ fst custom_shortcut_sec)
762+
<> foldMap getShortcutCommand (Map.toList conf.shortcuts)
763+
)
764+
)
765+
733766
<|> hsubparser
734767
( metavar (T.unpack $ snd list_sec)
735768
<> commandGroup (T.unpack $ fst list_sec)
@@ -1158,6 +1191,7 @@ helpReplacements :: Config -> [(Text, Doc AnsiStyle)]
11581191
helpReplacements conf =
11591192
[ basic_sec
11601193
, shortcut_sec
1194+
, custom_shortcut_sec
11611195
, list_sec
11621196
, vis_sec
11631197
, i_o_sec
@@ -1340,6 +1374,14 @@ executeCLiCommand config now connection progName args availableLinesMb = do
13401374
AddSell bodyWords -> addTaskC $ ["Sell"] <> bodyWords <> ["+sell"]
13411375
AddPay bodyWords -> addTaskC $ ["Pay"] <> bodyWords <> ["+pay"]
13421376
AddShip bodyWords -> addTaskC $ ["Ship"] <> bodyWords <> ["+ship"]
1377+
AddCustomShortcut shortcut bodyWords ->
1378+
let
1379+
prefixWords = case shortcut.prefix of
1380+
Just pfx -> [pfx]
1381+
Nothing -> []
1382+
tagWords = fmap ("+" <>) shortcut.tags
1383+
in
1384+
addTaskC $ prefixWords <> bodyWords <> tagWords
13431385
LogTask bodyWords -> logTask conf connection bodyWords
13441386
EnterTask -> enterTask conf connection
13451387
ReadyOn datetime ids -> setReadyUtc conf connection datetime ids

tasklite-core/source/Config.hs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import Protolude (
2929
)
3030
import Protolude qualified as P
3131

32+
import Data.Map.Strict (Map)
33+
import Data.Map.Strict qualified as Map
34+
3235
import Data.Aeson (
3336
FromJSON (parseJSON),
3437
ToJSON (toJSON),
@@ -107,6 +110,33 @@ data HooksConfig = HooksConfig
107110
deriving (Generic, Show)
108111

109112

113+
-- | Custom shortcut for adding tasks with predefined prefix and tags
114+
data Shortcut = Shortcut
115+
{ prefix :: Maybe Text
116+
-- ^ Optional prefix to prepend to task body (e.g., "Cook")
117+
, tags :: [Text]
118+
-- ^ Tags to add to the task (without the + prefix)
119+
}
120+
deriving (Eq, Generic, Show)
121+
122+
123+
instance ToJSON Shortcut
124+
125+
126+
instance FromJSON Shortcut where
127+
parseJSON = withObject "shortcut" $ \o -> do
128+
prefix <- o .:? "prefix"
129+
-- Support both "tag: xxx" (single) and "tags: [xxx, yyy]" (multiple)
130+
tagSingle <- o .:? "tag" .!= ""
131+
tagsList <- o .:? "tags" .!= []
132+
let tags = case (tagSingle, tagsList) of
133+
("", []) -> []
134+
("", ts) -> ts
135+
(t, []) -> [t]
136+
(t, ts) -> t : ts -- If both specified, combine them
137+
pure $ Shortcut{..}
138+
139+
110140
instance ToJSON HooksConfig
111141

112142

@@ -285,6 +315,8 @@ data Config = Config
285315
, maxWidth :: Maybe Int -- Automatically uses terminal width if not set
286316
, progressBarWidth :: Int
287317
, hooks :: HooksConfig
318+
, shortcuts :: Map Text Shortcut
319+
-- ^ Custom shortcuts for adding tasks (e.g., "cook" -> adds "Cook" prefix and "+cook" tag)
288320
, noColor :: Bool
289321
}
290322
deriving (Generic, Show)
@@ -318,6 +350,7 @@ instance FromJSON Config where
318350
progressBarWidth <- o .:? "progressBarWidth"
319351
.!= defaultConfig.progressBarWidth
320352
hooks <- o .:? "hooks" .!= defaultConfig.hooks
353+
shortcuts <- o .:? "shortcuts" .!= defaultConfig.shortcuts
321354
noColor <- o .:? "noColor" .!= defaultConfig.noColor
322355

323356
let maxWidth = maxWidthMb >>=
@@ -423,5 +456,6 @@ defaultConfig =
423456
, modify = emptyHookSet
424457
, exit = emptyHookSet
425458
}
459+
, shortcuts = Map.empty
426460
, noColor = False
427461
}

tasklite-core/tasklite-core.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ library
8080
, bytestring >=0.11 && <0.13
8181
, cassava >=0.5.2 && <0.6
8282
, colour ==2.3.*
83+
, containers >=0.6 && <0.8
8384
, directory ==1.3.*
8485
, editor-open >=0.4 && <0.7
8586
, exceptions ==0.10.*

0 commit comments

Comments
 (0)