Skip to content

Commit

Permalink
Support for nested list items with user defined bullets
Browse files Browse the repository at this point in the history
  • Loading branch information
xolox committed Nov 25, 2011
1 parent 48208ca commit 576333a
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 42 deletions.
39 changes: 29 additions & 10 deletions README.md
Expand Up @@ -53,20 +53,21 @@ If you set this option to the string `'no'` this feature will be completely disa

### The `g:notes_smart_quotes` option

By default the notes plug-in automatically performs several substitutions on the text you type in insert mode. Here are those substitutions:
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:

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

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

:let g:notes_smart_quotes = 0
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.

### The `g:notes_list_bullets` option

A list of characters used as list bullets. When you're using a Unicode encoding this defaults to `['•', '◦', '▸', '▹', '▪', '▫']`, otherwise it defaults to `['*', '-', '+']`.

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.

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.
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).

### The `g:notes_shadowdir` option

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

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

## Mappings

The following key mappings are defined inside notes.

### Insert mode mappings

* `@` automatically triggers tag completion
* `'` becomes `` or `` depending on where you type it
* `"` becomes `` or `` (same goes for these)
* `--` becomes ``
* `->` becomes ``
* `<-` becomes ``
* the bullets `*`, `-` and `+` become ``
* the three characters `***` in insert mode in quick succession insert a horizontal ruler delimited by empty lines
* `Tab` and `Alt-Right` increase indentation of list items (works on the current line and selected lines)
* `Shift-Tab` and `Alt-Left` decrease indentation of list items
* `Enter` on a line with only a list bullet removes the bullet and starts a new line below the current line

## Customizing the syntax highlighting of notes

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]:
Expand Down
60 changes: 54 additions & 6 deletions autoload/xolox/notes.vim
Expand Up @@ -6,7 +6,7 @@
" Note: This file is encoded in UTF-8 including a byte order mark so
" that Vim loads the script using the right encoding transparently.

let g:xolox#notes#version = '0.15.5'
let g:xolox#notes#version = '0.16'

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

function! xolox#notes#indent_list(command, line1, line2) " {{{3
function! xolox#notes#indent_list(direction, line1, line2) " {{{3
" Change indent of list items from {line1} to {line2} using {command}.
let indentstr = repeat(' ', &tabstop)
if a:line1 == a:line2 && getline(a:line1) == ''
call setline(a:line1, repeat(' ', &tabstop))
call setline(a:line1, indentstr)
else
execute a:line1 . ',' . a:line2 . 'normal' a:command
if getline('.') =~ '\(•\|\*\)$'
" Regex to match a leading bullet.
let leading_bullet = xolox#notes#leading_bullet_pattern()
for lnum in range(a:line1, a:line2)
let line = getline(lnum)
" Calculate new nesting level, should not result in < 0.
let level = max([0, xolox#notes#get_list_level(line) + a:direction])
if a:direction == 1
" Indent the line.
let line = indentstr . line
else
" Unindent the line.
let line = substitute(line, '^' . indentstr, '', '')
endif
" Replace the bullet.
let bullet = g:notes_list_bullets[level % len(g:notes_list_bullets)]
call setline(lnum, substitute(line, leading_bullet, xolox#misc#escape#substitute(bullet), ''))
endfor
" Regex to match a trailing bullet.
if getline('.') =~ xolox#notes#trailing_bullet_pattern()
" Restore trailing space after list bullet.
call setline('.', getline('.') . ' ')
endif
endif
normal $
endfunction

function! xolox#notes#leading_bullet_pattern()
" Return a regular expression pattern that matches any leading list bullet.
let escaped_bullets = copy(g:notes_list_bullets)
call map(escaped_bullets, 'xolox#misc#escape#pattern(v:val)')
return '\(\_^\s*\)\@<=\(' . join(escaped_bullets, '\|') . '\)'
endfunction

function! xolox#notes#trailing_bullet_pattern()
" Return a regular expression pattern that matches any trailing list bullet.
let escaped_bullets = copy(g:notes_list_bullets)
call map(escaped_bullets, 'xolox#misc#escape#pattern(v:val)')
return '\(' . join(escaped_bullets, '\|') . '\|\*\)$'
endfunction

function! xolox#notes#get_comments_option()
" Get the value for the &comments option including user defined list bullets.
let items = copy(g:notes_list_bullets)
call map(items, '": " . v:val . " "')
call add(items, ':> ') " <- e-mail style block quotes.
return join(items, ',')
endfunction

function! xolox#notes#get_list_level(line)
" Get the nesting level of the list item on the given line. This will only
" work with the list item indentation style expected by the notes plug-in
" (that is, top level list items are indented with one space, each nested
" level below that is indented by pairs of three spaces).
return (len(matchstr(a:line, '^\s*')) - 1) / 3
endfunction

function! xolox#notes#cleanup_list() " {{{3
" Automatically remove empty list items on Enter.
if getline('.') =~ '^\s*\' . xolox#notes#get_bullet('*') . '\s*$'
if getline('.') =~ (xolox#notes#leading_bullet_pattern() . '\s*$')
let s:sol_save = &startofline
setlocal nostartofline " <- so that <C-u> clears the complete line
return "\<C-o>0\<C-o>d$\<C-o>o"
Expand Down
77 changes: 60 additions & 17 deletions doc/notes.txt
Expand Up @@ -128,29 +128,37 @@ automatically rename the file on disk to match the title.
The *g:notes_smart_quotes* option

By default the notes plug-in automatically performs several substitutions on
the text you type in insert mode. Here are those substitutions:

- ' becomes '‘' or '’' depending on where you type it

- '"' becomes '“' or '”' (same goes for these)
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:
>
:let g:notes_smart_quotes = 0
- '--' becomes '—'
-------------------------------------------------------------------------------
The *g:notes_ruler_text* option

- '->' becomes '->'
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.

- '<-' becomes '←'
-------------------------------------------------------------------------------
The *g:notes_list_bullets* option

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

If you don't want the plug-in to perform these substitutions, you can set this
option to zero like this:
>
:let g:notes_smart_quotes = 0
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.

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.
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).

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

===============================================================================
*notes-mappings*
Mappings ~

The following key mappings are defined inside notes.

-------------------------------------------------------------------------------
*notes-insert-mode-mappings*
Insert mode mappings ~

- '@' automatically triggers tag completion

- ' becomes '‘' or '’' depending on where you type it

- '"' becomes '“' or '”' (same goes for these)

- '--' becomes '—'

- '->' becomes '->'

- '<-' becomes '←'

- the bullets '*', '-' and '+' become '•'

- the three characters '***' in insert mode in quick succession insert a
horizontal ruler delimited by empty lines

- 'Tab' and 'Alt-Right' increase indentation of list items (works on the
current line and selected lines)

- 'Shift-Tab' and 'Alt-Left' decrease indentation of list items

- 'Enter' on a line with only a list bullet removes the bullet and starts a
new line below the current line

===============================================================================
Customizing the syntax highlighting of notes ~

Expand Down
26 changes: 18 additions & 8 deletions ftplugin/notes.vim
Expand Up @@ -18,7 +18,7 @@ setlocal tabstop=3 shiftwidth=3 expandtab
let b:undo_ftplugin .= ' | set tabstop< shiftwidth< expandtab<'

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

Expand Down Expand Up @@ -79,28 +79,38 @@ endif

" Convert ASCII list bullets to Unicode bullets. {{{1
if g:notes_smart_quotes
imap <buffer> <expr> * xolox#notes#insert_bullet('*')
imap <buffer> <expr> - xolox#notes#insert_bullet('-')
imap <buffer> <expr> + xolox#notes#insert_bullet('+')
imap <buffer> <expr> * xolox#notes#insert_bullet('*')
let b:undo_ftplugin .= ' | execute "iunmap <buffer> *"'
let b:undo_ftplugin .= ' | execute "iunmap <buffer> -"'
let b:undo_ftplugin .= ' | execute "iunmap <buffer> +"'
let b:undo_ftplugin .= ' | execute "iunmap <buffer> *"'
endif

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

" Indent list items using <Tab>. {{{1
imap <buffer> <silent> <Tab> <C-o>:call xolox#notes#indent_list('>>', line('.'), line('.'))<CR>
smap <buffer> <silent> <Tab> <C-o>:<C-u>call xolox#notes#indent_list('>>', line("'<"), line("'>"))<CR><C-o>gv
" Indent list items using <Tab> and <Shift-Tab>. {{{1
imap <buffer> <silent> <Tab> <C-o>:call xolox#notes#indent_list(1, line('.'), line('.'))<CR>
smap <buffer> <silent> <Tab> <C-o>:<C-u>call xolox#notes#indent_list(1, line("'<"), line("'>"))<CR><C-o>gv
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <Tab>"'
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <Tab>"'
imap <buffer> <silent> <S-Tab> <C-o>:call xolox#notes#indent_list('<<', line('.'), line('.'))<CR>
smap <buffer> <silent> <S-Tab> <C-o>:<C-u>call xolox#notes#indent_list('<<', line("'<"), line("'>"))<CR><C-o>gv
imap <buffer> <silent> <S-Tab> <C-o>:call xolox#notes#indent_list(-1, line('.'), line('.'))<CR>
smap <buffer> <silent> <S-Tab> <C-o>:<C-u>call xolox#notes#indent_list(-1, line("'<"), line("'>"))<CR><C-o>gv
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <S-Tab>"'
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <S-Tab>"'

" Indent list items using <Alt-Left> and <Alt-Right>. {{{1
imap <buffer> <silent> <A-Right> <C-o>:call xolox#notes#indent_list(1, line('.'), line('.'))<CR>
smap <buffer> <silent> <A-Right> <C-o>:<C-u>call xolox#notes#indent_list(1, line("'<"), line("'>"))<CR><C-o>gv
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <A-Right>"'
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <A-Right>"'
imap <buffer> <silent> <A-Left> <C-o>:call xolox#notes#indent_list(-1, line('.'), line('.'))<CR>
smap <buffer> <silent> <A-Left> <C-o>:<C-u>call xolox#notes#indent_list(-1, line("'<"), line("'>"))<CR><C-o>gv
let b:undo_ftplugin .= ' | execute "iunmap <buffer> <A-Left>"'
let b:undo_ftplugin .= ' | execute "sunmap <buffer> <A-Left>"'

" Automatically remove empty list items on Enter. {{{1
inoremap <buffer> <silent> <expr> <CR> xolox#notes#cleanup_list()
Expand Down
9 changes: 9 additions & 0 deletions plugin/notes.vim
Expand Up @@ -60,6 +60,15 @@ if !exists('g:notes_ruler_text')
let g:notes_ruler_text = repeat(' ', ((&tw > 0 ? &tw : 79) - 5) / 2) . '* * *'
endif

" Symbols used to denote list items with increasing nesting levels.
if !exists('g:notes_list_bullets')
if xolox#notes#unicode_enabled()
let g:notes_list_bullets = ['', '', '', '', '', '']
else
let g:notes_list_bullets = ['*', '-', '+']
endif
endif

" User commands to create, delete and search notes.
command! -bar -bang -nargs=? -complete=customlist,xolox#notes#cmd_complete Note call xolox#notes#edit(<q-bang>, <q-args>)
command! -bar -bang -range NoteFromSelectedText call xolox#notes#from_selection(<q-bang>, 'edit')
Expand Down
2 changes: 1 addition & 1 deletion syntax/notes.vim
Expand Up @@ -32,7 +32,7 @@ syntax match notesTagName /\(^\|\s\)\@<=@\k\+/
highlight def link notesTagName Underlined

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

0 comments on commit 576333a

Please sign in to comment.