Skip to content

Commit 576333a

Browse files
committed
Support for nested list items with user defined bullets
1 parent 48208ca commit 576333a

File tree

6 files changed

+171
-42
lines changed

6 files changed

+171
-42
lines changed

README.md

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,21 @@ If you set this option to the string `'no'` this feature will be completely disa
5353

5454
### The `g:notes_smart_quotes` option
5555

56-
By default the notes plug-in automatically performs several substitutions on the text you type in insert mode. Here are those substitutions:
56+
By default the notes plug-in automatically performs several substitutions on the text you type in insert mode, for example regular quote marks are replaced with curly quotes. The full list of substitutions can be found below in the documentation on mappings. If you don't want the plug-in to perform these substitutions, you can set this option to zero like this:
5757

58-
* `'` becomes `` or `` depending on where you type it
59-
* `"` becomes `` or `` (same goes for these)
60-
* `--` becomes ``
61-
* `->` becomes ``
62-
* `<-` becomes ``
63-
* the bullets `*`, `+` and `-` become ``
58+
:let g:notes_smart_quotes = 0
6459

65-
If you don't want the plug-in to perform these substitutions, you can set this option to zero like this:
60+
### The `g:notes_ruler_text` option
6661

67-
:let g:notes_smart_quotes = 0
62+
The text of the ruler line inserted when you type `***` in quick succession. It defaults to three asterisks separated by spaces, center aligned to the text width.
63+
64+
### The `g:notes_list_bullets` option
65+
66+
A list of characters used as list bullets. When you're using a Unicode encoding this defaults to `['•', '◦', '▸', '▹', '▪', '▫']`, otherwise it defaults to `['*', '-', '+']`.
67+
68+
When you change the nesting level (indentation) of a line containing a bullet point using one of the mappings `Tab`, `Shift-Tab`, `Alt-Left` and `Alt-Right` the bullet point will be automatically changed to correspond to the new nesting level.
6869

69-
If you type the three characters `***` in insert mode in quick succession, a horizontal ruler delimited by empty lines will be inserted. This mapping cannot be disabled simply because it seems unlikely to me that someone would want to type this actual text.
70+
The first level of list items gets the first bullet point in `g:notes_list_bullets`, the second level gets the second, etc. When you're indenting a list item to a level where the `g:notes_list_bullets` doesn't have enough bullets, the plug-in starts again at the first bullet in the list (in other words the selection of bullets wraps around).
7071

7172
### The `g:notes_shadowdir` option
7273

@@ -174,6 +175,24 @@ The completion menu is populated from a text file listing all your tags, one on
174175

175176
If for any reason you want to recreate the list of tags you can execute the `:IndexTaggedNotes` command.
176177

178+
## Mappings
179+
180+
The following key mappings are defined inside notes.
181+
182+
### Insert mode mappings
183+
184+
* `@` automatically triggers tag completion
185+
* `'` becomes `` or `` depending on where you type it
186+
* `"` becomes `` or `` (same goes for these)
187+
* `--` becomes ``
188+
* `->` becomes ``
189+
* `<-` becomes ``
190+
* the bullets `*`, `-` and `+` become ``
191+
* the three characters `***` in insert mode in quick succession insert a horizontal ruler delimited by empty lines
192+
* `Tab` and `Alt-Right` increase indentation of list items (works on the current line and selected lines)
193+
* `Shift-Tab` and `Alt-Left` decrease indentation of list items
194+
* `Enter` on a line with only a list bullet removes the bullet and starts a new line below the current line
195+
177196
## Customizing the syntax highlighting of notes
178197

179198
The syntax mode for notes is written so you can override styles you don't like. To do so you can add lines such as the following to your [vimrc script] [vimrc]:

autoload/xolox/notes.vim

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
" Note: This file is encoded in UTF-8 including a byte order mark so
77
" that Vim loads the script using the right encoding transparently.
88

9-
let g:xolox#notes#version = '0.15.5'
9+
let g:xolox#notes#version = '0.16'
1010

1111
function! xolox#notes#shortcut() " {{{1
1212
" The "note:" pseudo protocol is just a shortcut for the :Note command.
@@ -770,23 +770,71 @@ function! xolox#notes#get_bullet(chr)
770770
return xolox#notes#unicode_enabled() ? '' : a:chr
771771
endfunction
772772

773-
function! xolox#notes#indent_list(command, line1, line2) " {{{3
773+
function! xolox#notes#indent_list(direction, line1, line2) " {{{3
774774
" Change indent of list items from {line1} to {line2} using {command}.
775+
let indentstr = repeat(' ', &tabstop)
775776
if a:line1 == a:line2 && getline(a:line1) == ''
776-
call setline(a:line1, repeat(' ', &tabstop))
777+
call setline(a:line1, indentstr)
777778
else
778-
execute a:line1 . ',' . a:line2 . 'normal' a:command
779-
if getline('.') =~ '\(•\|\*\)$'
779+
" Regex to match a leading bullet.
780+
let leading_bullet = xolox#notes#leading_bullet_pattern()
781+
for lnum in range(a:line1, a:line2)
782+
let line = getline(lnum)
783+
" Calculate new nesting level, should not result in < 0.
784+
let level = max([0, xolox#notes#get_list_level(line) + a:direction])
785+
if a:direction == 1
786+
" Indent the line.
787+
let line = indentstr . line
788+
else
789+
" Unindent the line.
790+
let line = substitute(line, '^' . indentstr, '', '')
791+
endif
792+
" Replace the bullet.
793+
let bullet = g:notes_list_bullets[level % len(g:notes_list_bullets)]
794+
call setline(lnum, substitute(line, leading_bullet, xolox#misc#escape#substitute(bullet), ''))
795+
endfor
796+
" Regex to match a trailing bullet.
797+
if getline('.') =~ xolox#notes#trailing_bullet_pattern()
780798
" Restore trailing space after list bullet.
781799
call setline('.', getline('.') . ' ')
782800
endif
783801
endif
784802
normal $
785803
endfunction
786804

805+
function! xolox#notes#leading_bullet_pattern()
806+
" Return a regular expression pattern that matches any leading list bullet.
807+
let escaped_bullets = copy(g:notes_list_bullets)
808+
call map(escaped_bullets, 'xolox#misc#escape#pattern(v:val)')
809+
return '\(\_^\s*\)\@<=\(' . join(escaped_bullets, '\|') . '\)'
810+
endfunction
811+
812+
function! xolox#notes#trailing_bullet_pattern()
813+
" Return a regular expression pattern that matches any trailing list bullet.
814+
let escaped_bullets = copy(g:notes_list_bullets)
815+
call map(escaped_bullets, 'xolox#misc#escape#pattern(v:val)')
816+
return '\(' . join(escaped_bullets, '\|') . '\|\*\)$'
817+
endfunction
818+
819+
function! xolox#notes#get_comments_option()
820+
" Get the value for the &comments option including user defined list bullets.
821+
let items = copy(g:notes_list_bullets)
822+
call map(items, '": " . v:val . " "')
823+
call add(items, ':> ') " <- e-mail style block quotes.
824+
return join(items, ',')
825+
endfunction
826+
827+
function! xolox#notes#get_list_level(line)
828+
" Get the nesting level of the list item on the given line. This will only
829+
" work with the list item indentation style expected by the notes plug-in
830+
" (that is, top level list items are indented with one space, each nested
831+
" level below that is indented by pairs of three spaces).
832+
return (len(matchstr(a:line, '^\s*')) - 1) / 3
833+
endfunction
834+
787835
function! xolox#notes#cleanup_list() " {{{3
788836
" Automatically remove empty list items on Enter.
789-
if getline('.') =~ '^\s*\' . xolox#notes#get_bullet('*') . '\s*$'
837+
if getline('.') =~ (xolox#notes#leading_bullet_pattern() . '\s*$')
790838
let s:sol_save = &startofline
791839
setlocal nostartofline " <- so that <C-u> clears the complete line
792840
return "\<C-o>0\<C-o>d$\<C-o>o"

doc/notes.txt

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -128,29 +128,37 @@ automatically rename the file on disk to match the title.
128128
The *g:notes_smart_quotes* option
129129

130130
By default the notes plug-in automatically performs several substitutions on
131-
the text you type in insert mode. Here are those substitutions:
132-
133-
- ' becomes '‘' or '’' depending on where you type it
134-
135-
- '"' becomes '“' or '”' (same goes for these)
131+
the text you type in insert mode, for example regular quote marks are replaced
132+
with curly quotes. The full list of substitutions can be found below in the
133+
documentation on mappings. If you don't want the plug-in to perform these
134+
substitutions, you can set this option to zero like this:
135+
>
136+
:let g:notes_smart_quotes = 0
136137
137-
- '--' becomes '—'
138+
-------------------------------------------------------------------------------
139+
The *g:notes_ruler_text* option
138140

139-
- '->' becomes '->'
141+
The text of the ruler line inserted when you type '***' in quick succession.
142+
It defaults to three asterisks separated by spaces, center aligned to the text
143+
width.
140144

141-
- '<-' becomes '←'
145+
-------------------------------------------------------------------------------
146+
The *g:notes_list_bullets* option
142147

143-
- the bullets '*', '+' and '-' become '•'
148+
A list of characters used as list bullets. When you're using a Unicode
149+
encoding this defaults to '['•', '◦', '▸', '▹', '▪', '▫']', otherwise it
150+
defaults to '['*', '-', '+']'.
144151

145-
If you don't want the plug-in to perform these substitutions, you can set this
146-
option to zero like this:
147-
>
148-
:let g:notes_smart_quotes = 0
152+
When you change the nesting level (indentation) of a line containing a bullet
153+
point using one of the mappings 'Tab', 'Shift-Tab', 'Alt-Left' and 'Alt-Right'
154+
the bullet point will be automatically changed to correspond to the new
155+
nesting level.
149156

150-
If you type the three characters '***' in insert mode in quick succession, a
151-
horizontal ruler delimited by empty lines will be inserted. This mapping
152-
cannot be disabled simply because it seems unlikely to me that someone would
153-
want to type this actual text.
157+
The first level of list items gets the first bullet point in
158+
|g:notes_list_bullets|, the second level gets the second, etc. When you're
159+
indenting a list item to a level where the |g:notes_list_bullets| doesn't have
160+
enough bullets, the plug-in starts again at the first bullet in the list (in
161+
other words the selection of bullets wraps around).
154162

155163
-------------------------------------------------------------------------------
156164
The *g:notes_shadowdir* option
@@ -336,6 +344,41 @@ add/remove tags.
336344
If for any reason you want to recreate the list of tags you can execute the
337345
|:IndexTaggedNotes| command.
338346

347+
===============================================================================
348+
*notes-mappings*
349+
Mappings ~
350+
351+
The following key mappings are defined inside notes.
352+
353+
-------------------------------------------------------------------------------
354+
*notes-insert-mode-mappings*
355+
Insert mode mappings ~
356+
357+
- '@' automatically triggers tag completion
358+
359+
- ' becomes '‘' or '’' depending on where you type it
360+
361+
- '"' becomes '“' or '”' (same goes for these)
362+
363+
- '--' becomes '—'
364+
365+
- '->' becomes '->'
366+
367+
- '<-' becomes '←'
368+
369+
- the bullets '*', '-' and '+' become '•'
370+
371+
- the three characters '***' in insert mode in quick succession insert a
372+
horizontal ruler delimited by empty lines
373+
374+
- 'Tab' and 'Alt-Right' increase indentation of list items (works on the
375+
current line and selected lines)
376+
377+
- 'Shift-Tab' and 'Alt-Left' decrease indentation of list items
378+
379+
- 'Enter' on a line with only a list bullet removes the bullet and starts a
380+
new line below the current line
381+
339382
===============================================================================
340383
Customizing the syntax highlighting of notes ~
341384

ftplugin/notes.vim

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ setlocal tabstop=3 shiftwidth=3 expandtab
1818
let b:undo_ftplugin .= ' | set tabstop< shiftwidth< expandtab<'
1919

2020
" Automatic formatting for bulleted lists. {{{1
21-
let &l:comments = xolox#notes#unicode_enabled() ? ': • ,: * ,:> ' : ': * ,:> '
21+
let &l:comments = xolox#notes#get_comments_option()
2222
setlocal formatoptions=tcron
2323
let b:undo_ftplugin .= ' | set comments< formatoptions<'
2424

@@ -79,28 +79,38 @@ endif
7979

8080
" Convert ASCII list bullets to Unicode bullets. {{{1
8181
if g:notes_smart_quotes
82+
imap <buffer> <expr> * xolox#notes#insert_bullet('*')
8283
imap <buffer> <expr> - xolox#notes#insert_bullet('-')
8384
imap <buffer> <expr> + xolox#notes#insert_bullet('+')
84-
imap <buffer> <expr> * xolox#notes#insert_bullet('*')
85+
let b:undo_ftplugin .= ' | execute "iunmap <buffer> *"'
8586
let b:undo_ftplugin .= ' | execute "iunmap <buffer> -"'
8687
let b:undo_ftplugin .= ' | execute "iunmap <buffer> +"'
87-
let b:undo_ftplugin .= ' | execute "iunmap <buffer> *"'
8888
endif
8989

9090
" Format three asterisks as a horizontal ruler. {{{1
9191
inoremap <buffer> *** <C-o>:call xolox#notes#insert_ruler()<CR>
9292
let b:undo_ftplugin .= ' | execute "iunmap <buffer> ***"'
9393

94-
" Indent list items using <Tab>. {{{1
95-
imap <buffer> <silent> <Tab> <C-o>:call xolox#notes#indent_list('>>', line('.'), line('.'))<CR>
96-
smap <buffer> <silent> <Tab> <C-o>:<C-u>call xolox#notes#indent_list('>>', line("'<"), line("'>"))<CR><C-o>gv
94+
" Indent list items using <Tab> and <Shift-Tab>. {{{1
95+
imap <buffer> <silent> <Tab> <C-o>:call xolox#notes#indent_list(1, line('.'), line('.'))<CR>
96+
smap <buffer> <silent> <Tab> <C-o>:<C-u>call xolox#notes#indent_list(1, line("'<"), line("'>"))<CR><C-o>gv
9797
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <Tab>"'
9898
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <Tab>"'
99-
imap <buffer> <silent> <S-Tab> <C-o>:call xolox#notes#indent_list('<<', line('.'), line('.'))<CR>
100-
smap <buffer> <silent> <S-Tab> <C-o>:<C-u>call xolox#notes#indent_list('<<', line("'<"), line("'>"))<CR><C-o>gv
99+
imap <buffer> <silent> <S-Tab> <C-o>:call xolox#notes#indent_list(-1, line('.'), line('.'))<CR>
100+
smap <buffer> <silent> <S-Tab> <C-o>:<C-u>call xolox#notes#indent_list(-1, line("'<"), line("'>"))<CR><C-o>gv
101101
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <S-Tab>"'
102102
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <S-Tab>"'
103103

104+
" Indent list items using <Alt-Left> and <Alt-Right>. {{{1
105+
imap <buffer> <silent> <A-Right> <C-o>:call xolox#notes#indent_list(1, line('.'), line('.'))<CR>
106+
smap <buffer> <silent> <A-Right> <C-o>:<C-u>call xolox#notes#indent_list(1, line("'<"), line("'>"))<CR><C-o>gv
107+
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <A-Right>"'
108+
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <A-Right>"'
109+
imap <buffer> <silent> <A-Left> <C-o>:call xolox#notes#indent_list(-1, line('.'), line('.'))<CR>
110+
smap <buffer> <silent> <A-Left> <C-o>:<C-u>call xolox#notes#indent_list(-1, line("'<"), line("'>"))<CR><C-o>gv
111+
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <A-Left>"'
112+
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <A-Left>"'
113+
104114
" Automatically remove empty list items on Enter. {{{1
105115
inoremap <buffer> <silent> <expr> <CR> xolox#notes#cleanup_list()
106116

plugin/notes.vim

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ if !exists('g:notes_ruler_text')
6060
let g:notes_ruler_text = repeat(' ', ((&tw > 0 ? &tw : 79) - 5) / 2) . '* * *'
6161
endif
6262

63+
" Symbols used to denote list items with increasing nesting levels.
64+
if !exists('g:notes_list_bullets')
65+
if xolox#notes#unicode_enabled()
66+
let g:notes_list_bullets = ['', '', '', '', '', '']
67+
else
68+
let g:notes_list_bullets = ['*', '-', '+']
69+
endif
70+
endif
71+
6372
" User commands to create, delete and search notes.
6473
command! -bar -bang -nargs=? -complete=customlist,xolox#notes#cmd_complete Note call xolox#notes#edit(<q-bang>, <q-args>)
6574
command! -bar -bang -range NoteFromSelectedText call xolox#notes#from_selection(<q-bang>, 'edit')

syntax/notes.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ syntax match notesTagName /\(^\|\s\)\@<=@\k\+/
3232
highlight def link notesTagName Underlined
3333

3434
" Highlight list bullets and numbers. {{{2
35-
syntax match notesListBullet /^\s*\zs\(\|\*\)/
35+
execute 'syntax match notesListBullet /' . escape(xolox#notes#leading_bullet_pattern(), '/') . '/'
3636
highlight def link notesListBullet Comment
3737
syntax match notesListNumber /^\s*\zs\d\+[[:punct:]]\?\ze\s/
3838
highlight def link notesListNumber Comment

0 commit comments

Comments
 (0)