journaling system cobbled together with nix, vim, coreutils

post

Changed files
+395
examples
2025
+1
.gitignore
··· 4 4 !.nvimrc 5 5 !examples 6 6 !examples/** 7 + !readme.md
+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
examples/06 examples/2025/06
examples/07 examples/2025/07
examples/08 examples/2025/08
examples/09 examples/2025/09
examples/10 examples/2025/10
+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 +