journaling system cobbled together with nix, vim, coreutils

Compare changes

Choose any two refs to compare.

Changed files
+572 -19
examples
2025
+2
.gitignore
··· 3 3 !flake.* 4 4 !.nvimrc 5 5 !examples 6 + !examples/** 7 + !readme.md
+15 -17
.nvimrc
··· 2 2 iabbrev todo · 3 3 iabbrev done × 4 4 5 - " select the task list and hit `gq` to group by status 6 - " selecting clever status symbols can determine the ordering of tasks 5 + " select the task list and hit `gq` to sort and group by status 7 6 set formatprg=sort\ -V 8 7 9 8 " syntax highlighting ··· 11 10 autocmd! 12 11 autocmd BufReadPost * set filetype=journal 13 12 14 - autocmd BufReadPost * syntax match JournalAll /.*/ " captures the entire buffer 15 - autocmd BufReadPost * syntax match JournalDone /^×.*/ " lines containing 'done' items: × 16 - autocmd BufReadPost * syntax match JournalTodo /^·.*/ " lines containing 'todo' items: · 17 - autocmd BufReadPost * syntax match JournalEvent /^o.*/ " lines containing 'event' items: o 18 - autocmd BufReadPost * syntax match JournalNote /^- .*/ " lines containing 'note' items: - 19 - autocmd BufReadPost * syntax match JournalMoved /^>.*/ " lines containing 'moved' items: > 20 - autocmd BufReadPost * syntax match JournalHeader /\<\u\+\>/ " words containing capitals 13 + autocmd BufReadPost * syntax match JournalAll /.*/ " captures the entire buffer 14 + autocmd BufReadPost * syntax match JournalDone /^×.*/ " lines containing 'done' items: × 15 + autocmd BufReadPost * syntax match JournalTodo /^·.*/ " lines containing 'todo' items: · 16 + autocmd BufReadPost * syntax match JournalEvent /^o.*/ " lines containing 'event' items: o 17 + autocmd BufReadPost * syntax match JournalNote /^- .*/ " lines containing 'note' items: - 18 + autocmd BufReadPost * syntax match JournalMoved /^>.*/ " lines containing 'moved' items: > 19 + autocmd BufReadPost * syntax match JournalHeader /^\<\u\+\>.*/ " lines starting with caps 21 20 22 - autocmd BufReadPost * highlight link JournalAll Noise 23 - autocmd BufReadPost * highlight link JournalHeader Noise 24 - autocmd BufReadPost * highlight link JournalDone Noise 25 - autocmd BufReadPost * highlight link JournalMoved Noise 26 - autocmd BufReadPost * highlight JournalEvent ctermfg=6 " cyan 27 - autocmd BufReadPost * highlight JournalMoved ctermfg=5 " pink 28 - autocmd BufReadPost * highlight JournalNote ctermfg=3 " yellow 29 - autocmd BufReadPost * highlight VertSplit ctermfg=0 ctermbg=0 " hide vert splits 21 + autocmd BufReadPost * highlight JournalAll ctermfg=12 22 + autocmd BufReadPost * highlight JournalHeader ctermfg=12 23 + autocmd BufReadPost * highlight JournalDone ctermfg=12 24 + autocmd BufReadPost * highlight JournalEvent ctermfg=6 " cyan 25 + autocmd BufReadPost * highlight JournalMoved ctermfg=5 " pink 26 + autocmd BufReadPost * highlight JournalNote ctermfg=3 " yellow 27 + autocmd BufReadPost * highlight VertSplit ctermfg=0 ctermbg=0 " hide vert splits 30 28 augroup END 31 29 32 30 augroup JournalHideUIElements
+41
examples/.nvimrc
··· 1 + " insert fancy signifiers with abbrevs 2 + iabbrev todo · 3 + iabbrev done × 4 + 5 + " select the task list and hit `gq` to sort and group by status 6 + set formatprg=sort\ -V 7 + 8 + " syntax highlighting 9 + augroup JournalSyntax 10 + autocmd! 11 + autocmd BufReadPost * set filetype=journal 12 + 13 + autocmd BufReadPost * syntax match JournalAll /.*/ " captures the entire buffer 14 + autocmd BufReadPost * syntax match JournalDone /^×.*/ " lines containing 'done' items: × 15 + autocmd BufReadPost * syntax match JournalTodo /^·.*/ " lines containing 'todo' items: · 16 + autocmd BufReadPost * syntax match JournalEvent /^o.*/ " lines containing 'event' items: o 17 + autocmd BufReadPost * syntax match JournalNote /^- .*/ " lines containing 'note' items: - 18 + autocmd BufReadPost * syntax match JournalMoved /^>.*/ " lines containing 'moved' items: > 19 + autocmd BufReadPost * syntax match JournalHeader /^\<\u\+\>.*/ " lines starting with caps 20 + 21 + autocmd BufReadPost * highlight JournalAll ctermfg=12 22 + autocmd BufReadPost * highlight JournalHeader ctermfg=12 23 + autocmd BufReadPost * highlight JournalDone ctermfg=12 24 + autocmd BufReadPost * highlight JournalEvent ctermfg=6 " cyan 25 + autocmd BufReadPost * highlight JournalMoved ctermfg=5 " pink 26 + autocmd BufReadPost * highlight JournalNote ctermfg=3 " yellow 27 + autocmd BufReadPost * highlight VertSplit ctermfg=0 ctermbg=0 " hide vert splits 28 + augroup END 29 + 30 + augroup JournalHideUIElements 31 + autocmd! 32 + " hide junk 33 + autocmd VimEnter * set laststatus=0 34 + autocmd VimEnter * set noruler nonumber nocursorline nocursorcolumn norelativenumber 35 + 36 + " pin scrolling 37 + autocmd VimEnter * set scrollbind 38 + 39 + augroup END 40 + 41 + syntax on
+44
examples/2025/06
··· 1 + JUNE --------------- 2 + 3 + Mo Tu We Th Fr Sa Su 4 + 1 2 3 4 5 + 5 6 7 8 9 10 11 6 + 12 13 14 15 16 17 18 7 + 19 20 21 22 23 24 25 8 + 26 27 28 29 30 9 + 10 + 11 + WEEK 1 ------------- 12 + 13 + > peel apples 14 + × buy apples 15 + × post letters 16 + × wash them 17 + 18 + 19 + WEEK 2 ------------- 20 + 21 + o shopping trip 22 + > make apple pie 23 + × buy cookbook 24 + × replace brushes 25 + × watch pie recipe 26 + 27 + 28 + WEEK 3 ------------- 29 + 30 + - weight: 70kg 31 + > renew gym sub 32 + > visit thrift store 33 + × make apple pie 34 + × return cookbook 35 + × scan cookbook 36 + 37 + 38 + WEEK 4 ------------- 39 + 40 + o thrift store visit 41 + × make apple pie 42 + × renew gym sub 43 + × return cookbook 44 + × scan cookbook
+48
examples/2025/07
··· 1 + JULY --------------- 2 + 3 + Mo Tu We Th Fr Sa Su 4 + 1 2 5 + 3 4 5 6 7 8 9 6 + 10 11 12 13 14 15 16 7 + 17 18 19 20 21 22 23 8 + 24 25 26 27 28 29 30 9 + 31 10 + 11 + 12 + WEEK 1 ------------- 13 + 14 + o shopping trip 15 + o thrift store visit 16 + > make apple pie 17 + × buy cookbook 18 + × replace brushes 19 + × watch pie recipe 20 + 21 + 22 + WEEK 2 ------------- 23 + 24 + - weight: 70kg 25 + > peel apples 26 + × buy apples 27 + × post letters 28 + × wash them 29 + 30 + 31 + WEEK 3 ------------- 32 + 33 + o thrift store visit 34 + × make apple pie 35 + × renew gym sub 36 + × return cookbook 37 + × scan cookbook 38 + 39 + 40 + WEEK 4 ------------- 41 + 42 + - weight: 70kg 43 + > renew gym sub 44 + > visit thrift store 45 + × make apple pie 46 + × return cookbook 47 + × scan cookbook 48 +
+44
examples/2025/08
··· 1 + AUGUST ------------- 2 + 3 + Mo Tu We Th Fr Sa Su 4 + 1 2 3 4 5 6 5 + 7 8 9 10 11 12 13 6 + 14 15 16 17 18 19 20 7 + 21 22 23 24 25 26 27 8 + 28 29 30 31 9 + 10 + 11 + WEEK 1 ------------- 12 + 13 + o thrift store visit 14 + × make apple pie 15 + × renew gym sub 16 + × return cookbook 17 + × scan cookbook 18 + 19 + 20 + WEEK 2 ------------- 21 + 22 + > peel apples 23 + × buy apples 24 + × post letters 25 + × wash them 26 + 27 + 28 + WEEK 3 ------------- 29 + 30 + - weight: 70kg 31 + > renew gym sub 32 + > visit thrift store 33 + × make apple pie 34 + × return cookbook 35 + × scan cookbook 36 + 37 + 38 + WEEK 4 ------------- 39 + 40 + · shopping trip 41 + · make apple pie 42 + · buy cookbook 43 + · replace brushes 44 + > watch pie recipe
+13
examples/2025/09
··· 1 + SEPTEMBER ---------- 2 + 3 + Mo Tu We Th Fr Sa Su 4 + 1 2 3 5 + 4 5 6 7 8 9 10 6 + 11 12 13 14 15 16 17 7 + 18 19 20 21 22 23 24 8 + 25 26 27 28 29 30 9 + 10 + 11 + WEEK 1 ------------- 12 + 13 + · watch pie recipe
+10
examples/2025/10
··· 1 + OCTOBER ------------ 2 + 3 + Mo Tu We Th Fr Sa Su 4 + 1 5 + 2 3 4 5 6 7 8 6 + 9 10 11 12 13 14 15 7 + 16 17 18 19 20 21 22 8 + 23 24 25 26 27 28 29 9 + 30 31 10 +
+2 -2
flake.nix
··· 9 9 { 10 10 11 11 packages.x86_64-linux.default = 12 - # starts nvim with 2 months of journal entries ahead and behing 13 - # nvim --cmd 'source .nvimrc' -O 2023/10 2023/11 2023/12 2024/01 12 + # starts nvim with 2 months of journal entries ahead and behind 13 + # nvim --cmd 'source .nvimrc' -O 2023/10 2023/11 2023/12 2024/01 2024/02 14 14 pkgs.writeScriptBin "journal" '' 15 15 nvim --cmd 'source .nvimrc' -O $( 16 16 ${pkgs.dateutils}/bin/dateseq \
+353
readme.md
··· 1 + I cobbled together a journaling system with {neo,}vim, 2 + coreutils and [dateutils](http://www.fresse.org/dateutils). 3 + This system is loosely based on [Ryder 4 + Caroll's](https://www.rydercarroll.com/) Bullet Journal 5 + method. 6 + 7 + [![](https://u.peppe.rs/SpF.png)](https://u.peppe.rs/SpF.png) 8 + 9 + ### The format 10 + 11 + The journal for a given year is a directory: 12 + 13 + ```bash 14 + λ ls journal/ 15 + 2022/ 2023/ 16 + ``` 17 + 18 + In each directory are 12 files, one for each month of the 19 + year, numbered like so: 20 + 21 + ```bash 22 + λ ls journal/2023/ 23 + 01 02 03 04 05 06 07 08 09 10 11 12 24 + ``` 25 + 26 + We can now begin writing stuff down: 27 + 28 + ```bash 29 + λ vim journal/2023/1 30 + ``` 31 + 32 + Every month must start with a calendar of course, fill that 33 + in with: 34 + 35 + ```vim 36 + :read !cal -m 37 + ``` 38 + 39 + Your entry for January might look like this: 40 + 41 + ```bash 42 + λ cat journal/2023/01 43 + January 2023 44 + Mo Tu We Th Fr Sa Su 45 + 1 46 + 2 3 4 5 6 7 8 47 + 9 10 11 12 13 14 15 48 + 16 17 18 19 20 21 22 49 + 23 24 25 26 27 28 29 50 + 30 31 51 + ``` 52 + 53 + I prefer planning week by week, as opposed to creating a 54 + task-list every day, here's what I have for the first couple 55 + of weeks: 56 + 57 + ``` 58 + January 2023 59 + Mo Tu We Th Fr Sa Su 60 + 1 61 + 2 3 4 5 6 7 8 62 + 9 10 11 12 13 14 15 63 + 16 17 18 19 20 21 22 64 + 23 24 25 26 27 28 29 65 + 30 31 66 + 67 + 68 + week 1 69 + 70 + done apply leaves 71 + done dload boarding pass 72 + moved reply to dan 73 + 74 + 75 + week 2 76 + 77 + todo reply to dan 78 + todo pack bags 79 + done travel insurance 80 + todo weigh luggage 81 + ``` 82 + 83 + I start the week by writing a header and each item that week 84 + is placed on its own line. The items are prefixed with a 85 + `todo` or a `done` signifier. 86 + 87 + 88 + ### Form over function 89 + 90 + Right off the bat, the signifiers look very noisy, Even more 91 + so once we start introducing variety (I use "event", "note" 92 + and "moved"): 93 + 94 + ``` 95 + week 1 96 + 97 + todo apply leaves 98 + done dload boarding pass 99 + todo reply to dan 100 + event fr trip 101 + note weight 68.6 102 + ``` 103 + 104 + We can clean this up with "abbreviations" (`:h abbreviations`): 105 + 106 + ```vim 107 + :iabbrev todo · 108 + :iabbrev done × 109 + ``` 110 + 111 + Now, typing this: 112 + 113 + ``` 114 + todo apply leaves 115 + ``` 116 + 117 + Automatically inserts: 118 + 119 + ``` 120 + · apply leaves 121 + ``` 122 + 123 + You can use `x` and `o` as well, but `×` (U+00D7, 124 + MULTIPLICATION SIGN) and `·` (U+00B7, MIDDLE DOT) are more 125 + ... *gourmet*. 126 + 127 + The other signifiers I use are: 128 + 129 + - `-` for note 130 + - `o` for event 131 + - `>` for moved. 132 + 133 + Nit #2 is the lack of order. We can employ vim to introduce 134 + grouping and sorting. Select the list of entries for this 135 + week: 136 + 137 + ```vim 138 + vip " line-wise select inner paragraph 139 + :'<,'>sort " the markers '< and '> are automatically inserted, 140 + " they mark the start and end of the selection 141 + ``` 142 + 143 + We end up with: 144 + 145 + ``` 146 + week 1 147 + 148 + · apply leaves 149 + · reply to dan 150 + × dload boarding pass 151 + ``` 152 + 153 + The lines are grouped by their signifiers, segregating todo 154 + items from completed items. Luckily, MIDDLE DOT is lesser 155 + than MULTIPLICATION SIGN, so todo items are placed at the 156 + top. The same goes for `o` and `x` symbols, either set of 157 + signifiers will result in the same sorting order. 158 + 159 + We can shorten this select-paragraph-invoke-sort dance by 160 + setting the `formatprg` variable: 161 + 162 + ```vim 163 + :set formatprg=sort\ -V 164 + ``` 165 + 166 + Now, hitting `gqip` should automatically group and sort the 167 + items for the week under the cursor, moving todo items to 168 + the top. Finding signifier glyphs that suit your sorting 169 + preference is a fun exercise. 170 + 171 + ### Syntax highlighting 172 + 173 + Adding color to items introduces another layer of visual 174 + distinction. In truth, I like to deck it out just because. 175 + 176 + First, create a few syntax groups: 177 + 178 + ```vim 179 + :syntax match JournalAll /.*/ " captures the entire buffer 180 + :syntax match JournalDone /^×.*/ " lines containing 'done' items: × 181 + :syntax match JournalTodo /^·.*/ " lines containing 'todo' items: · 182 + :syntax match JournalEvent /^o.*/ " lines containing 'event' items: o 183 + :syntax match JournalNote /^- .*/ " lines containing 'note' items: - 184 + :syntax match JournalMoved /^>.*/ " lines containing 'moved' items: > 185 + ``` 186 + 187 + Add highlights to each group: 188 + 189 + ```vim 190 + :highlight JournalAll ctermfg=12 " bright black 191 + :highlight JournalDone ctermfg=12 " bright black 192 + :highlight JournalEvent ctermfg=6 " cyan 193 + :highlight JournalMoved ctermfg=5 " magenta 194 + :highlight JournalNote ctermfg=3 " yellow 195 + ``` 196 + 197 + In my terminal, this is rendered like so: 198 + 199 + [![](https://u.peppe.rs/Du6.png)](https://u.peppe.rs/Du6.png) 200 + 201 + ### Habit tracking 202 + 203 + While this is not a part of my journaling system anymore, a 204 + few headers and an awk script is all it takes to track 205 + habits. My weekly entries would include a couple of habit 206 + headers like so: 207 + 208 + ``` 209 + week 1 -------------- 210 + 211 + × wake up on time 212 + × water the plants 213 + 214 + spend 7.5 7 10 215 + --------------------- 216 + 217 + 218 + week 2 -------------- 219 + 220 + · make the bed 221 + · go to bed 222 + 223 + spend 30 2.75 6 224 + --------------------- 225 + ``` 226 + 227 + Here, under the `spend` header in week 1, are a list of 228 + expenditures accumulated over the week. The monthly spend is 229 + calculated with this awk script: 230 + 231 + ```awk 232 + BEGIN {spend=0;} 233 + /spend/ {for(i=1;i<=$NF;i++) spend+=$i;} 234 + END { printf spend "eur"} 235 + ``` 236 + 237 + And invoked like so: 238 + 239 + ``` 240 + λ awk -f spend.awk journal/2023/01 241 + 63.25eur 242 + ``` 243 + 244 + ### Reflection 245 + 246 + Journaling is not just about planning what is to come, but 247 + also reflecting on what has passed. It would make sense to 248 + simultaneously look at the past few weeks' entries while 249 + making your current one. To open multiple months of entries 250 + at the same time: 251 + 252 + ``` 253 + λ vim -O journal/2023/0{1,2,3} 254 + ``` 255 + 256 + Opens 3 months, side-by-side, in vertical splits: 257 + 258 + ``` 259 + JANUARY ------------ │ FEBRUARY ----------- │ MARCH -------------- 260 + │ │ 261 + Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su 262 + 1 │ 1 2 3 4 5 │ 1 2 3 4 5 263 + 2 3 4 5 6 7 8 │ 6 7 8 9 10 11 12 │ 6 7 8 9 10 11 12 264 + 9 10 11 12 13 14 15 │ 13 14 15 16 17 18 19 │ 13 14 15 16 17 18 19 265 + 16 17 18 19 20 21 22 │ 20 21 22 23 24 25 26 │ 20 21 22 23 24 25 26 266 + 23 24 25 26 27 28 29 │ 27 28 │ 27 28 29 30 31 267 + 30 31 │ │ 268 + │ │ 269 + │ │ 270 + WEEK 1 ------------- │ WEEK 1 ------------- │ WEEK 1 ------------- 271 + │ │ 272 + > latex setup │ > forex │ - weight: 64 273 + × make the bed │ × clean shoes │ > close sg-pr 274 + × 03: dentist │ × buy clothes │ × facewash 275 + × integrate tsg │ × draw │ × groceries 276 + │ │ 277 + │ │ 278 + WEEK 2 ------------- │ WEEK 2 ------------- │ WEEK 2 ------------- 279 + │ │ 280 + × latex setup │ - viral fever │ > close sg-pr 281 + × send invoice │ × forex │ × plan meet 282 + × stack-graph pr │ × activate sim │ × sg storage 283 + │ × bitlbee │ 284 + ``` 285 + 286 + ### Reducing friction 287 + 288 + Journaling already requires a solid amount of discipline and 289 + consistency. The added friction of typing `vim 290 + journal/$CURRENT_YEAR/$CURRENT_MONTH` each time is doing no 291 + favors. 292 + 293 + To open the current month based on system time: 294 + 295 + ```bash 296 + λ vim $(date +"%Y/%m") 297 + ``` 298 + 299 + To open all the months within a 2 month window of today, is 300 + a little trickier. The command we wish to generate is (if 301 + today is 2023/12): 302 + 303 + ```bash 304 + λ vim -O 2023/10 2023/11 2023/12 2024/01 2024/02 305 + ``` 306 + 307 + And that is where `dateseq` from 308 + [dateutils](http://www.fresse.org/dateutils) comes in handy, 309 + for example: 310 + 311 + ```bash 312 + λ dateseq 2012-02-01 2012-03-01 313 + 2012-02-01 314 + 2012-02-02 315 + 2012-02-03 316 + ... 317 + 2012-02-28 318 + 2012-02-29 319 + 2012-03-01 320 + ``` 321 + 322 + This script opens all months within a 2 month window of 323 + today: 324 + 325 + ```bash 326 + λ vim -O $( 327 + dateseq \ 328 + "$(date --date "2 months ago" +%Y/%m)" \ 329 + "$(date --date "2 months" +%Y/%m)" \ 330 + -i %Y/%m \ 331 + -f %Y/%m 332 + ) 333 + ``` 334 + 335 + 336 + ### Fin 337 + 338 + You can find a sample vimrc in this repository, 339 + along with a nix flake file to kick things off: 340 + 341 + ``` 342 + λ nix develop 343 + λ cd examples 344 + λ journal 345 + ``` 346 + 347 + Plain text journaling can be just as much fun as a pen and 348 + paper. Throw in some ASCII art for each month, use swankier 349 + signifiers, or louder syntax highlighting. Don't expect 350 + forgiveness from org-mode users though. 351 + 352 + [![](https://u.peppe.rs/ZCK.png)](https://u.peppe.rs/ZCK.png) 353 +