Compare commits

..

6 commits

8 changed files with 316 additions and 150 deletions

View file

@ -45,7 +45,7 @@ endsnippet
# Code
snippet `r "R Code Block" b
snippet rcode "R Code Block" b
\`\`\`{r}
${1:#| output: ${2:false}}
${3:#| echo: ${4:false}}
@ -53,7 +53,7 @@ ${0:${VISUAL:# code...}}
\`\`\`
endsnippet
snippet `py "Python Code Block" b
snippet pycode "Python Code Block" b
\`\`\`{python}
${1:#| output: ${2:false}}
${3:#| echo: ${4:false}}

View file

@ -0,0 +1,63 @@
function! asyncomplete#sources#mbnotes#get_source_options(opts)
let l:defaults = {
\ 'name': 'mbnotes',
\ 'completor': function('asyncomplete#sources#mbnotes#completor'),
\ 'allowlist': ['mbnotes']
\ }
return extend(l:defaults, a:opts)
endfunction
let s:notes = []
let s:notes_ttl = 0
let s:command = "rg -N --no-heading --color=never --smart-case "
\ .. "--glob='**/*.qmd' -m 1 -- '^#\\s' "
\ .. shellescape(g:mbnotes_dir)
function! asyncomplete#sources#mbnotes#format_cache_entry(key, line)
let l:line = substitute(a:line, g:mbnotes_dir .. "/", "", "")
let l:parts = split(l:line, '\.qmd:#\s*')
return ['[' .. l:parts[1] .. '](' .. l:parts[0] .. '.qmd)'] + l:parts
endfunction!
function! asyncomplete#sources#mbnotes#refresh_cache()
let l:output = systemlist(s:command)
let s:notes = mapnew(l:output, function('asyncomplete#sources#mbnotes#format_cache_entry'))
let s:notes_ttl = localtime() + 20
endfunction!
function! asyncomplete#sources#mbnotes#completor(opt, ctx)
let l:title_pattern = '\[[^\]]*$'
let l:title = matchstr(a:ctx['typed'], l:title_pattern)
if l:title !=# ''
if localtime() > s:notes_ttl
call asyncomplete#sources#mbnotes#refresh_cache()
endif
let l:col = a:ctx['col'] - len(l:title)
let l:matches = mapnew(s:notes, '{"word": v:val[0], "dup": 1, "menu": v:val[1], "abbr": v:val[2]}')
call asyncomplete#complete(a:opt['name'], a:ctx, l:col, l:matches)
return
endif
let l:link_pattern = '\]([^)]*$'
let l:link = matchstr(a:ctx['typed'], l:link_pattern)
if l:link !=# ''
if localtime() > s:notes_ttl
call asyncomplete#sources#mbnotes#refresh_cache()
endif
let l:col = a:ctx['col'] - len(l:link)
let l:matches = mapnew(s:notes, '{"word": "](" .. v:val[1] .. ".qmd)", "abbr": v:val[1]}')
call asyncomplete#complete(a:opt['name'], a:ctx, l:col, l:matches)
return
endif
endfunction

View file

@ -40,7 +40,7 @@ export def RenderNote(format: string, buffer = "%")
endif
else
if exists("g:mbnotes_open_command") && g:mbnotes_open_command != ""
execute "!" .. g:mbnotes_open_command .. " " .. output
execute "!" .. g:mbnotes_open_command .. " " .. shellescape(output)
endif
if g:mbnotes_renderer_close_on_end
@ -68,10 +68,11 @@ enddef
export def BeforeNoteSave()
var full_path = expand('%:p')
var directory = expand('%:p:h')
var daily_path = g:mbnotes_dir .. "/daily"
# Don't touch daily notes
if g:mbnotes_rename_on_save && daily_path != full_path[0 : len(daily_path) - 1]
# Only rename bona fide notes
if directory == expand(g:mbnotes_dir)
var base = expand('%:t')
var date = base[0 : 9]
@ -83,7 +84,7 @@ export def BeforeNoteSave()
var title_line = search('^#\s\+')
if title_line == 0
throw "Unable to save note: No title found."
return
endif
var title = substitute(getline(title_line), '^#\s\+', '', '')
@ -97,13 +98,14 @@ export def BeforeNoteSave()
normal! `s
setpos("'s", original_mark)
b:new_name = date .. "_" .. sanitised .. ".qmd"
b:new_name = fnameescape(expand('%:p:h')) .. "/" .. date .. "_" .. sanitised .. ".qmd"
endif
enddef
export def AfterNoteSave()
if exists("b:new_name")
execute "silent Move " .. fnameescape(g:mbnotes_dir) .. "/" .. b:new_name
execute "silent Move " .. b:new_name
unlet b:new_name
endif
enddef
@ -186,3 +188,19 @@ export def Operator(context = {}, type: string = ''): string
return ""
enddef
export def SearchForTags(tag = '', bang = false)
var query = '#' .. tag
if query ==# '#'
query ..= '[a-zA-Z_]'
endif
fzf#vim#grep(
g:mbnotes_search_command .. shellescape(query),
fzf#vim#with_preview({
"dir": g:mbnotes_dir
}),
bang
)
enddef

View file

@ -5,12 +5,18 @@
1. Introduction ...................... |mbnotes|
2. Setup ............................. |mbnotes-setup|
3. Configuration ..................... |mbnotes-config|
4. Commands .......................... |mbnotes-commands|
4.1. Operator .................... |mbnotes-operator|
5. Front Matter ...................... |mbnotes-front-matter|
2.1. Installation ................ |mbnotes-install|
2.2. Quarto Project .............. |mbnotes-quarto-project|
3. Creating Notes .................... |mbnotes-creating|
3.1. New Notes ................... |mbnotes-new|
3.2. Daily Notes ................. |mbnotes-daily|
3.3. Extracting Notes ............ |mbnotes-extracting|
4. Rendering Notes ................... |mbnotes-rendering|
5. Configuration ..................... |mbnotes-config|
6. Integrations ...................... |mbnotes-integrations|
6.1. UltiSnips ................... |mbnotes-ultisnips|
6.2. FZF ......................... |mbnotes-fzf|
6.3. Asyncomplete ................ mbnotes-asyncomplete
==============================================================================
1. Introduction *mbnotes*
@ -37,6 +43,9 @@ better glue it all together.
Vim 9 is required for this plugin to work, since it is written in Vim9Script.
2.1. Installation *mbnotes-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
@ -61,21 +70,139 @@ 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.
2.2. Quarto Project *mbnotes_quarto_project*
While MBNotes supports `g:mbnotes_dir` pointing at any directory, it is
recommended that this be configured to be a Quarto project, of type "default".
There are a couple of benefits here:
Firstly, having a project allows metadata to be specified outside of a single
note's front matter, meaning that consistent rendering can be achieved without
cluttering up your documents. To do this, create a file "_quarto.yml" in
`g:mbnotes_dir`. I find this is a good place to start
>yaml
author: "{Your Name}"
format:
html:
embed-resources: true
html-math-method: katex
theme:
light: flatly
dark: darkly
pdf:
documentclass: article
geometry:
- top=25mm
- left=25mm
- heightrounded
<
Another benefit is that the output directory can be set inside "_quarto.yml",
and all build artifacts will be kept in one location. If this change is made,
please note that `g:mbnotes_out_dir` will also need to be updated to match.
This gives a convenient location to find built resources, as well as utilise
the caching features of Quarto.
==============================================================================
3. Configuration *mbnotes-config*
3. Creating Notes *mbnotes-creating*
3.1. New Notes *mbnotes-new*
`: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|.
When this note is saved, it will expect to find at least one h1 element. If it
does, the file will be renamed with a sanitised version of that title. If it
does not, an error is thrown. Control this behaviour with
`g:mbnotes_rename_on_save`.
`:MBNotesNewSplit` *:MBNotesNewSplit*
The same as `:MBNotesNew`, but opens in a split. Can be controlled by modifier
commands, such as `:above`.
3.2. Daily Notes *mbnotes-daily*
MBNotes includes a daily note that can be used as a scratch pad, journal, or
really anything. If the flag `g:mbnotes_open_daily_on_startup` is enabled, this
daily note will open when Vim is opened. If it does not:
`:MBNotesDaily` *:MBNotesDaily*
Open a daily note in the current window. By default, this will open today's
note. However, this command takes a single optional integer argument
reprenting an offset. To open tomorrow's daily note >vim
:MBNotesDaily 1
Or to open last week's >vim
:MBNotesDaily -7
`:MBNotesDailySplit` *:MBNotesDailySplit*
The same as `:MBNotesDaily`, but opens in a split. Can be controlled by
modifier commands, such as `:vertical`.
3.3. Extracting Notes *mbnotes-extracting*
New notes can be created from any text within Vim. These will be put into a new
note as created by `:MBNotesNew`, which will be opened.
*gb*
<Plug>MBNotesNew{motion} Yank the text that {motion} moves over and put
it into a new note buffer.
*gbb*
<Plug>MBNotesNewLine{motion} Yank the current line into a new note buffer.
*v_gb*
{Visual}<Plug>MBNotesNew Yank the highlighted text into a new note buffer
(for {Visual} see |Visual-mode|).
These are not mapped to anything usable by default. To use these operators,
choose a suitable key sequence, and set it:
>vim
nmap gb <Plug>MBNotesNew
xmap gb <Plug>MBNotesNew
nmap gbb <Plug>MBNotesNewLine
<
==============================================================================
4. Rendering Notes *mbnotes-rendering*
Notes are normal Quarto documents, and as such can be rendered by Quarto into
anything. We include commands to render and open PDFs and HTML files. Rendering
can be customised by setting `g:mbnotes_quarto_binary` and
`g:mbnotes_quarto_render_args`.
`:MBNotesPDF` *:MBNotesPDF*
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. To control when and
how a the terminal buffer showing rendering information is displayed, see
`g:mbnotes_renderer_show`, `g:mbnotes_renderer_close_on_end`, and
`g:mbnotes_renderer_buffer_command`.
`:MBNotesRenderHTML` *:MBNotesRenderHTML*
Render the current buffer as an HTML file and open it. See |:MBNotesRenderPDF|
for details on behaviour.
==============================================================================
5. 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:
directory, but it can be set manually.
>vim
let g:mbnnotes_out_dir = g:mbnotes_dir .. "/_output"
@ -121,6 +248,10 @@ The default format outputs dates like "Thursday, 18 May 2024"
let g:mbnotes_date_format_long = "%A, %-e %B %Y"
<
g:mbnotes_quarto_binary *g:mbnotes_quarto_binary*
Location of the quarto binary. By default, this uses whichever one is in $PATH.
g:mbnotes_open_command *g:mbnotes_open_command*
External command to open built files. By default, the following commands are
@ -130,19 +261,11 @@ tried (in order):
* `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
the render commands (e.g. `:MBNotesPDF`) will not open the output file
after rendering.
Explicitly set this to the empty string to disable opening behaviour.
g:mbnotes_rename_on_save *g:mbnotes_rename_on_save*
This plugin will attempt to intelligently rename a note based on its title,
which is the first H1 that it finds in the document. Disable this behaviour by
setting this variable to false >vim
let g:mbnotes_rename_on_save = 0
g:mbnotes_renderer_show *g:mbnotes_renderer_show*
When a document is rendered, the quarto command to do so runs in a terminal
@ -171,104 +294,16 @@ example, to always use the jupyter engine: >vim
let g:mbnotes_quarto_render_args = ["-M", "engine:jupyter"]
g:mbnotes_quarto_binary *g:mbnotes_quarto_binary*
g:mbnotes_rename_on_save *g:mbnotes_rename_on_save*
Location of the quarto binary. By default, this uses whichever one is in $PATH.
This plugin will attempt to intelligently rename a note based on its title,
which is the first H1 that it finds in the document. Disable this behaviour by
setting this variable to false >vim
==============================================================================
4. Commands *mbnotes-commands*
let g:mbnotes_rename_on_save = 0
`: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.
==============================================================================
4.1 Operator *mbnotes-operator*
New notes can be created from any text within Vim. These will be put into a new
note as created by `:MBNotesNew`, which will be opened.
*gb*
gb{motion} Yank the text that {motion} moves over and put it into a
new note buffer.
*gbb*
gbb Yank the current line into a new note buffer.
*v_gb*
{Visual}gb Yank the highlighted text into a new note buffer (for
{Visual} see |Visual-mode|).
These mappings are only set if `gb` is not already mapped to something. If they
are, or if you wish to customise these mappings, create new normal and visual
mode mappings (`nmap` and `xmap`) to `<Plug>MBNotesNew`, and a normal mode
mapping to `<Plug>MBNotesNewLine`.
==============================================================================
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.
It is not recommended to disable this without some other way of generating file
names.
==============================================================================
6. Integrations *mbnotes-integrations*
@ -278,7 +313,7 @@ have packaged some of the integrations in this plugin, such that they will load
if those plugin are installed.
==============================================================================
6.1. Snippets *mbnotes-ultisnips*
6.1. UltiSnips *mbnotes-ultisnips*
This plugin packages a few UltiSnips snippets out of the box. These facilitate
creating Quarto front matter with a date and title, callout blocks (as per the
@ -294,4 +329,44 @@ a default priority instruction. Furthermore, any of these snippets can be very
easily overwritten. See the `priority` keyword in |ultisnips-basic-syntax| for
more information.
==============================================================================
6.2. FZF *mbnotes-fzf*
A few commands are packaged that use FZF to open and search your notes.
`:MBNotes` *:MBNotes*
Lists all notes.
More specifically, all files within `g:mbnotes_dir`. The query is prefilled
with ".qmd$" to only find Quarto files.
`:MBNotesSearch [PATTERN]` *:MBNotesSearch*
Search all notes for the given pattern.
By default, this command depends on ripgrep being installed. The underlying
search query can be changed by setting `g:mbnotes_search_command`. [PATTERN] is
appended to that command.
`:MBNotesTags [TAG]` *:MBNotesTags*
Search all notes for a given tag using `g:mbnotes_search_command`.
==============================================================================
6.3. Asyncomplete *mbnotes-asyncomplete*
Completion is supported via asyncomplete, for links to other notes. To enable
this, include:
>vim
autocmd User asyncomplete_setup {
asyncomplete#register_source(
asyncomplete#sources#mbnotes#get_source_options({ 'priority': 9 })
)
}
<
This completion support depends on ripgrep being installed.
vim:tw=78:ts=8:noet:ft=help:norl:

View file

@ -4,7 +4,7 @@ def IsMBNotes()
var path_prefix = expand('%:p')[0 : len(g:mbnotes_dir) - 1]
if path_prefix == g:mbnotes_dir
set filetype=mbnotes.qmd
set filetype=mbnotes
endif
enddef

View file

@ -16,7 +16,7 @@ if !exists("g:mbnotes_dir")
echoerr "MBNotes: Error: g:mbnotes_dir not set."
finish
else
silent mkdir(fnameescape(g:mbnotes_dir .. "/daily"), "p")
silent mkdir(g:mbnotes_dir .. "/daily", "p")
endif
if !exists("g:mbnotes_out_dir")
@ -65,6 +65,17 @@ if !exists("g:mbnotes_renderer_buffer_command")
g:mbnotes_renderer_buffer_command = "botright sbuf"
endif
if !exists("g:mbnotes_search_command")
g:mbnotes_search_command = "rg "
.. "--column "
.. "--line-number "
.. "--no-heading "
.. "--color=always "
.. "--smart-case "
.. "--glob='**/*.qmd' "
.. "-- "
endif
import autoload 'mbnotes.vim'
augroup MBNotes
@ -85,14 +96,14 @@ augroup MBNotes
.. fnameescape(g:mbnotes_dir) .. "/*.qmd mbnotes.AfterNoteSave()"
augroup END
command -nargs=? MBNotesOpenDaily mbnotes.OpenDailyNote(<args>)
command -nargs=? MBNotesOpenDailySplit {
command -nargs=? MBNotesDaily mbnotes.OpenDailyNote(<args>)
command -nargs=? MBNotesDailySplit {
execute "<mods> new"
mbnotes.OpenDailyNote(<args>)
}
command -nargs=0 MBNotesRenderPDF mbnotes.RenderNote("pdf")
command -nargs=0 MBNotesRenderHTML mbnotes.RenderNote("html")
command -nargs=0 MBNotesPDF mbnotes.RenderNote("pdf")
command -nargs=0 MBNotesHTML mbnotes.RenderNote("html")
command -nargs=0 MBNotesNew mbnotes.NewNote()
command -nargs=0 MBNotesNewSplit {
@ -104,10 +115,17 @@ nnoremap <expr> <Plug>MBNotesNew <SID>mbnotes.Operator()
xnoremap <expr> <Plug>MBNotesNew <SID>mbnotes.Operator()
nnoremap <expr> <Plug>MBNotesNewLine <SID>mbnotes.Operator() .. '_'
if !hasmapto('<Plug>MBNotesNew') || maparg('gb', 'n') ==# ''
nmap gb <Plug>MBNotesNew
xmap gb <Plug>MBNotesNew
nmap gbb <Plug>MBNotesNewLine
endif
command -nargs=? -bang MBNotes fzf#run(fzf#wrap('MBNotes', {
\ "options": ["--query", ".qmd$ " .. <q-args>],
\ "dir": g:mbnotes_dir
\ }, <bang>0))
command -nargs=? -bang MBNotesSearch fzf#vim#grep(
\ g:mbnotes_search_command .. shellescape(<q-args>),
\ fzf#vim#with_preview({
\ "dir": g:mbnotes_dir
\ }), <bang>0)
command -nargs=? -bang MBNotesTags mbnotes.SearchForTags(<q-args>, <bang>0)
g:mbnotes_loaded = 1

View file

@ -1,12 +1,7 @@
import vim
from string import Template
from datetime import datetime, timedelta
daily_template = Template("""---
title: "Daily Note"
date: "$date"
---
# $date
daily_template = Template("""# $date
## Daily Note

View file

@ -1,6 +1,5 @@
vim9s
if exists("b:current_syntax")
echom b:current_syntax
finish
endif
@ -17,9 +16,7 @@ syn region mbnCalloutImportant start=":::\s\+{\.callout-important\s*" end=":::$"
hi link mbnCalloutImportant SpellBad
syn region mbnCalloutTip start=":::\s\+{\.callout-tip\s*" end=":::$" keepend
hi link mbnCalloutNote Question
hi link mbnCalloutTip Question
syn region mbnCalloutCaution start=":::\s\+{\.callout-caution\s*" end=":::$" keepend
hi link mbnCalloutWarning SpellCap
b:current_syntax = "mbnotes"
hi link mbnCalloutCaution SpellCap