Initial commit

This commit is contained in:
Max Bucknell 2025-01-11 01:25:03 +00:00
commit a9ed31593f
No known key found for this signature in database
10 changed files with 553 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/doc/tags

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# MBNotes
This plugin represents my attempt to incorporate all of my digital notes into
Vim. The basis of this foundation is Quarto, which is required for most of this
to work. Requires Vim 9.
## Installation
This plugin can be installed using whatever plugin installer you so desire. It
also depends on quarto-vim, which itself depends on vim-pandoc-syntax. For
`vim-plug`:
```vim
Plug 'vim-pandoc/vim-pandoc-syntax'
Plug 'quarto-dev/quarto-vim'
Plug 'maxbucknell/vim-mbnotes'
```
## Licence
Distributed under the Vim Licence. See [`:help licence`](https://github.com/vim/vim/blob/master/LICENSE)

View file

@ -0,0 +1,69 @@
snippet note "Note Front Matter" b
---
title: "${1:Title}"
date: "${3:`!v strftime(g:mbnotes_date_format_short, localtime() + (g:mbnotes_new_day_time * -3600))`}
---
# $1
$0
endsnippet
snippet :::n "Callout: Note" b
::: {.callout-note}
${0:${VISUAL:text...}}
:::
endsnippet
snippet :::t "Callout: Tip" b
::: {.callout-tip}
${0:${VISUAL:text...}}
:::
endsnippet
snippet :::w "Callout: Warning" b
::: {.callout-warning}
${0:${VISUAL:text...}}
:::
endsnippet
snippet :::i "Callout: Important" b
::: {.callout-important}
${0:${VISUAL:text...}}
:::
endsnippet
snippet `r "R Code Block" b
\`\`\`{r}
${1:#| output: ${2:false}}
${3:#| echo: ${4:false}}
${0:${VISUAL:# code...}}
\`\`\`
endsnippet
snippet `py "Python Code Block" b
\`\`\`{python}
${1:#| output: ${2:false}}
${3:#| echo: ${4:false}}
${0:${VISUAL:# code...}}
\`\`\`
endsnippet
snippet `ju "Julia Code Block" b
\`\`\`{julia}
${1:#| output: ${2:false}}
${3:#| echo: ${4:false}}
${0:${VISUAL:# code...}}
\`\`\`
endsnippet
snippet todo "Make a To-Do List!" b
- [ ] ${0:What would you like to do..?}
endsnippet
snippet --- "Horizontal Rule" b
--------------------------------------------------------------------------------
$0
endsnippet

109
autoload/mbnotes.vim Normal file
View file

@ -0,0 +1,109 @@
vim9s
if !exists("g:mbnotes_loaded")
finish
endif
export def OpenDailyNote(offset: number = 0)
var diff = (g:mbnotes_new_day_time * -3600) + (offset * 86400)
var date = strftime(g:mbnotes_date_format_short, localtime() + diff)
var filename = g:mbnotes_dir .. "/daily/" .. date .. "-daily.qmd"
execute "silent edit " .. fnameescape(filename)
if !filereadable(filename)
python3 import mbnotes
python3 import vim
execute "python3 vim.current.buffer[:] = mbnotes.generate_daily_note(" .. diff .. ").splitlines()"
write
normal G
endif
enddef
export def RenderNote(format: string, buffer = "%")
var input = expand(buffer)
var output = substitute(
fnamemodify(input, ":r"),
g:mbnotes_dir,
g:mbnotes_out_dir,
""
) .. "." .. format
def ExitCb(job: job, exit: number)
if exit != 0
execute "botright sbuf " .. b:mbnotes_renderer_buffer
elseif exists("g:mbnotes_open_command")
execute "botright sbuf " .. b:mbnotes_renderer_buffer
execute "!" .. g:mbnotes_open_command .. " " .. output
execute "bwipeout " .. b:mbnotes_renderer_buffer
endif
unlet b:mbnotes_renderer_buffer
enddef
b:mbnotes_renderer_buffer = term_start([
"quarto",
"render",
input,
"--to",
format,
"--output-dir",
g:mbnotes_out_dir
], {
cwd: g:mbnotes_dir,
hidden: true,
exit_cb: ExitCb
})
enddef
export def BeforeNoteSave()
var full_path = expand('%:p')
var daily_path = g:mbnotes_dir .. "/daily"
# Don't touch daily notes
if daily_path != full_path[0 : len(daily_path) - 1]
var base = expand('%:t')
var date = base[0 : 9]
var original_mark = getpos("'s")
normal! ms
cursor(1, 1)
var title_line = search('^#\s\+')
if title_line == 0
throw "Unable to save note: No title found."
endif
var title = substitute(getline(title_line), '^#\s\+', '', '')
var sanitised = substitute(
tolower(title),
'[^a-z0-9]\+',
"-",
"g"
)
normal! `s
setpos("'s", original_mark)
b:new_name = date .. "_" .. sanitised .. ".qmd"
endif
enddef
export def AfterNoteSave()
if exists("b:new_name")
execute "silent Move " .. fnameescape(g:mbnotes_dir) .. "/" .. b:new_name
endif
enddef
export def NewNote()
var diff = (g:mbnotes_new_day_time * -3600)
var date = strftime(g:mbnotes_date_format_short, localtime() + diff)
var file = date .. "_new-note.qmd"
execute "edit " .. g:mbnotes_dir .. "/" .. file
enddef

206
doc/mbnotes.txt Normal file
View file

@ -0,0 +1,206 @@
*mbnotes.txt* Max Bucknell's notes framework
==============================================================================
1. Contents *mbnotes-contents*
1. Introduction ...................... |mbnotes|
2. Setup ............................. |mbnotes-setup|
3. Configuration ..................... |mbnotes-config|
4. Commands .......................... |mbnotes-commands|
5. Front Matter ...................... |mbnotes-front-matter|
==============================================================================
1. Introduction *mbnotes*
At the start of 2025, I started to use Vim for my digital notes, aiming to
combine all of the features I liked from the various tools I had used over the
years. This included:
* Markdown
* Convenient exporting to PDF and HTML
* Callout blocks of varying colours
* Linking
* Tagging
* Daily notes that I can write in currently, retrospectively, and in advance
* Interpolation of both mathematical text and code
* Embedding of code to be executed.
Most of this came out of the box with Quarto, at least with some configuration.
This plugin consists of support for that, along with other conveniences to
better glue it all together.
==============================================================================
2. Setup *mbnotes-setup*
Vim 9 is required for this plugin to work, since it is written in Vim9Script.
This plugin can be installed using whatever plugin installer you so desire. It
also depends on quarto-vim, which itself depends on vim-pandoc-syntax. For
vim-plug >vim
Plug 'vim-pandoc/vim-pandoc-syntax'
Plug 'quarto-dev/quarto-vim'
Plug 'maxbucknell/vim-mbnotes'
Please note that you must also set |g:mbnotes_dir|
g:mbnotes_dir *g:mbnotes_dir*
This is the root directory of all notes. It must be set before any
functionality can be used. If it is not set, the plugin will not start, and
will echo an error message.
Set in vimrc >vim
let g:mbnotes_dir = $HOME .. "/notes"
This directory and a subdirectory `daily` will be created. The latter is used
for storing daily notes. See |mbnotes-front-matter| for configuring rendering
options.
==============================================================================
3. Configuration *mbnotes-config*
g:mbnotes_out_dir *g:mbnotes_out_dir*
This is where rendered outputs will be placed. By default this is a temporary
directory, but it can be set manually. If the project `output-dir` is set in
the Quarto project `_metadata.yml` like so:
>yaml
project
output-dir: _output
<
Then `g:mbnotes_out_dir` should be set accordingly:
>vim
let g:mbnnotes_out_dir = g:mbnotes_dir .. "/_output"
<
g:mbnotes_open_daily_on_startup *g:mbnotes_open_daily_on_startup*
When launching Vim with no additional arguments, open the current daily note
rather than the default Vim startup screen. Defaults to 0. Enable by: >vim
let g:mbnotes_open_daily_on_startup = 1
g:mbnotes_new_day_time *g:mbnotes_new_day_time*
Sets the time of day that a new day begins, as far as daily note designation is
concerned. By default, a new day starts at 4am, but to start a new day at
midnight, use >vim
let g:mbnotes_new_day_time = 0
g:mbnotes_date_format_short *g:mbnotes_date_format_short*
This date format is used in file names for new notes, and for daily notes. It
is recommended to keep this as something that sorts alphabetically. This string
should be something that can be passed to `strftime`. See `man 3 strftime` for
more details.
The default format outputs dates like "2023-09-21"
>vim
let g:mbnotes_date_format_short = "%Y-%m-%d"
<
g:mbnotes_date_format_long *g:mbnotes_date_format_long*
This date format is used to set the `date` metadata field in document front
matter, as well as the title for daily notes. See |g:mbnotes_date_format_short|
for formatting details.
The default format outputs dates like "Thursday, 18 May 2024"
>vim
let g:mbnotes_date_format_long = "%A, %-e %B %Y"
<
g:mbnotes_open_command *g:mbnotes_open_command*
External command to open built files. By default, the following commands are
tried (in order):
* `open`
* `xdg-open`
If none of the above are defined, and this variable is not set explicitly, then
the render commands (e.g. |:MBNotesRenderPDF|) will not open the output file
after rendering.
==============================================================================
4. Commands *mbnotes-commands*
`:MBNotesNew` *:MBNotesNew*
Creates a new note in the current window, with today's date in the file name.
The date used rolls over at |g:mbnotes_new_day_time|.
`:MBNotesNewSplit` *:MBNotesNewSplit*
The same as `:MBNotesNew`, but opens in a split. Can be controlled by modifier
commands, such as `:above`.
`:MBNotesOpenDaily` *:MBNotesOpenDaily*
Open the daily note in the current window. This command takes a single optional
integer argument reprenting an offset. To open tomorrow's daily note >vim
:MBNotesOpenDaily 1
Or to open last week's >vim
:MBNotesOpenDaily -7
`:MBNotesOpenDailySplit` *:MBNotesOpenDailySplit*
The same as `:MBNotesOpenDaily`, but opens in a split. Can be controlled by
modifier commands, such as `:vertical`.
`:MBNotesRenderPDF` *:MBNotesRenderPDF*
Render the current buffer as a PDF. It will open the PDF using
`g:mbnotes_open_command` if it successfully builds.
If the document fails to render, a terminal buffer is displayed showing the
results of the quarto render command that was attempted.
`:MBNotesRenderHTML` *:MBNotesRenderHTML*
Render the current buffer as an HTML file and open it. See |:MBNotesRenderPDF|
for details on behaviour.
==============================================================================
5. Front Matter *mbnotes-front-matter*
Quarto supports a wide variety of configuration for document metadata. This can
be placed in the document front matter, but it gets unwieldy. It is recommended
that `g:mbnotes_dir` point to a Quarto project. Then, a file called
`_metadata.yml` can be placed at the root.
A good place to start is:
>yaml
author: "{Your Name}"
format:
html:
embed-resources: true
html-math-method: katex
theme:
light: flatly
dark: darkly
pdf:
documentclass: article
geometry:
- top=30mm
- left=20mm
- heightrounded
<
Anything supported by Quarto can be specified here and will be imported and
merged with any front matter specified at the top of any file. See the Quarto
docs for more information, specifically on projects.
vim:tw=78:ts=8:noet:ft=help:norl:

11
ftdetect/mbnotes.vim Normal file
View file

@ -0,0 +1,11 @@
vim9s
def IsMBNotes()
var path_prefix = expand('%:p')[0 : len(g:mbnotes_dir) - 1]
if path_prefix == g:mbnotes_dir
set filetype=mbnotes
endif
enddef
au BufNewFile,BufRead *.qmd IsMBNotes()

79
plugin/mbnotes.vim Normal file
View file

@ -0,0 +1,79 @@
vim9s
if exists("g:mbnotes_loaded")
finish
endif
if !exists("g:mbnotes_dir")
echoerr "MBNotes: Error: g:mbnotes_dir not set."
finish
else
silent mkdir(fnameescape(g:mbnotes_dir .. "/daily"), "p")
endif
if !exists("g:mbnotes_out_dir")
g:mbnotes_out_dir = $TMPDIR .. "xyz.mpwb.vim.mbnotes"
endif
if !exists("g:mbnotes_open_command")
if executable("open")
g:mbnotes_open_command = "open"
elseif executable("xdg-open")
g:mbnotes_open_command = "xdg-open"
endif
endif
silent mkdir(g:mbnotes_out_dir, "p")
if !exists("g:mbnotes_open_daily_on_startup")
g:mbnotes_open_daily_on_startup = false
endif
if !exists("g:mbnotes_new_day_time")
g:mbnotes_new_day_time = 4
endif
if !exists("g:mbnotes_date_format_short")
g:mbnotes_date_format_short = "%Y-%m-%d"
endif
if !exists("g:mbnotes_date_format_long")
g:mbnotes_date_format_long = "%A, %-e %B %Y"
endif
import autoload 'mbnotes.vim'
augroup MBNotes
autocmd!
if g:mbnotes_open_daily_on_startup
au VimEnter * ++nested {
if @% == ""
mbnotes.OpenDailyNote()
endif
}
endif
execute "autocmd BufWritePre "
.. fnameescape(g:mbnotes_dir) .. "/*.qmd mbnotes.BeforeNoteSave()"
execute "autocmd BufWritePost "
.. fnameescape(g:mbnotes_dir) .. "/*.qmd mbnotes.AfterNoteSave()"
augroup END
command -nargs=? MBNotesOpenDaily mbnotes.OpenDailyNote(<args>)
command -nargs=? MBNotesOpenDailySplit {
execute "<mods> new"
mbnotes.OpenDailyNote(<args>)
}
command -nargs=0 MBNotesRenderPDF mbnotes.RenderNote("pdf")
command -nargs=0 MBNotesRenderHTML mbnotes.RenderNote("html")
command -nargs=0 MBNotesNew mbnotes.NewNote()
command -nargs=0 MBNotesNewSplit {
execute "<mods> new"
mbnotes.NewNote()
}
g:mbnotes_loaded = 1

Binary file not shown.

32
python3/mbnotes.py Normal file
View file

@ -0,0 +1,32 @@
import vim
from string import Template
from datetime import datetime, timedelta
daily_template = Template("""---
title: "Daily Note"
date: "$date"
---
# $date
## Daily Note
""")
def generate_daily_note(date_offset = 0):
"""Generate a new daily note.
By default, this generates a daily note starter for today. The
`date_offset` argument changes this by offsetting the current date by the
given number of seconds.
This function returns a string. To put into a python-buffer, this output
needs to be split by line into a list and assigned to `buffer[:]`.
"""
date = datetime.today() + timedelta(seconds=date_offset)
date_format = vim.eval("g:mbnotes_date_format_long")
date = date.strftime(date_format)
author = vim.eval("g:mbnotes_author")
return daily_template.substitute(date=date, author=author)

25
syntax/mbnotes.vim Normal file
View file

@ -0,0 +1,25 @@
vim9s
if exists("b:current_syntax")
finish
endif
runtime! syntax/quarto.vim
unlet b:current_syntax
syn region mbnCalloutNote start=":::\s\+{\.callout-note\s*" end=":::$" keepend
hi link mbnCalloutNote Question
syn region mbnCalloutWarning start=":::\s\+{\.callout-warning\s*" end=":::$" keepend
hi link mbnCalloutWarning SpellCap
syn region mbnCalloutImportant start=":::\s\+{\.callout-important\s*" end=":::$" keepend
hi link mbnCalloutImportant SpellBad
syn region mbnCalloutTip start=":::\s\+{\.callout-tip\s*" end=":::$" keepend
hi link mbnCalloutNote Question
syn region mbnCalloutCaution start=":::\s\+{\.callout-caution\s*" end=":::$" keepend
hi link mbnCalloutWarning SpellCap
b:current_syntax = "mbnotes"