Permalink
Browse files

Version 2.00

Modularize and generalize for completing other types of snippets (e.g. from snipMate).
  • Loading branch information...
1 parent 7d39682 commit 3d589356fac41644c9b6b5f77f40d82973764305 Ingo Karkat committed with May 8, 2012
Showing with 328 additions and 156 deletions.
  1. +18 −18 README
  2. +108 −99 autoload/SnippetComplete.vim
  3. +74 −0 autoload/SnippetComplete/Abbreviations.vim
  4. +61 −23 doc/SnippetComplete.txt
  5. +67 −16 plugin/SnippetComplete.vim
View
36 README
@@ -4,27 +4,27 @@ DESCRIPTION
Insert mode abbreviations and snippets can dramatically speed up editing, but
how does one remember all those shortcuts that are rarely used? You can list
all insert mode abbreviations via :ia to break out of this vicious circle,
-but switching to command mode for that is cumbersome.
+but switching to command mode for that is cumbersome.
This plugin offers a context-sensitive insert mode completion to quickly list
-and complete defined abbreviations directly while typing.
+and complete defined abbreviations directly while typing.
USAGE
-i_CTRL-X_] Find matches for abbreviations that start with the
- text in front of the cursor.
+i_CTRL-X_] Find matches for abbreviations that start with the
+ text in front of the cursor.
- There are three types of abbreviations (full-id,
- end-id and non-id), which can consist of different
- characters. Thus, there can be more than one candidate
- for the existing completion base, e.g. "pre@c" can
- expand into a full-id abbreviation starting with "c"
- or into a non-id one starting with "pre@c". The
- completion indicates such a ambiguity through the
- message "base n of m; next: blah", and you can cycle
- through the different completion bases by repeating
- the i_CTRL-X_] shortcut.
+ There are three types of abbreviations (full-id,
+ end-id and non-id), which can consist of different
+ characters. Thus, there can be more than one candidate
+ for the existing completion base, e.g. "pre@c" can
+ expand into a full-id abbreviation starting with "c"
+ or into a non-id one starting with "pre@c". The
+ completion indicates such a ambiguity through the
+ message "base n of m; next: blah", and you can cycle
+ through the different completion bases by repeating
+ the i_CTRL-X_] shortcut.
- Matches are selected and inserted as with any other
- ins-completion, see popupmenu-keys. If you use
- <Space> or i_CTRL-] to select an abbreviation, it'll
- be expanded automatically.
+ Matches are selected and inserted as with any other
+ ins-completion, see popupmenu-keys. If you use
+ <Space> or i_CTRL-] to select an abbreviation, it'll
+ be expanded automatically.
@@ -1,53 +1,103 @@
" SnippetComplete.vim: Insert mode completion that completes defined
-" abbreviations.
+" abbreviations and snippets.
"
" DEPENDENCIES:
"
-" Copyright: (C) 2010 Ingo Karkat
-" The VIM LICENSE applies to this script; see ':help copyright'.
+" Copyright: (C) 2010-2012 Ingo Karkat
+" The VIM LICENSE applies to this script; see ':help copyright'.
"
" Maintainer: Ingo Karkat <ingo@karkat.de>
"
-" REVISION DATE REMARKS
+" REVISION DATE REMARKS
+" 2.00.003 05-May-2012 Rewrite s:RegistryTypeCompare() so that it
+" doesn't need to access
+" g:SnippetComplete_Registry.
+" Allow to pass in types instead of accessing
+" g:SnippetComplete_Registry.
+" 2.00.002 03-May-2012 Generalize for completing other types of
+" snippets (e.g. from snipMate):
+" Introduce g:SnippetComplete_Registry for snippet
+" types.
+" Move resolution of Vim abbreviations to
+" SnippetComplete/Abbreviations.vim autoload
+" script.
" 1.01.001 25-Sep-2010 Moved functions from plugin to separate autoload
-" script.
+" script.
" The autocmd uses a global variable so that it
" can pass the
" g:SnippetComplete_LastInsertStartPosition to the
" autoload script's functions without loading the
-" autoload script on the first InsertEnter.
+" autoload script on the first InsertEnter.
" file creation
+let s:save_cpo = &cpo
+set cpo&vim
-let s:abbreviationExpressions = [['fullid', '\k\+'], ['endid', '\%(\k\@!\S\)\+\k\?'], ['nonid', '\S\+\%(\k\@!\S\)\?']]
-function! s:DetermineBaseCol()
+function! s:TypeCompare( c1, c2 )
+ let l:p1 = a:c1[1].priority
+ let l:p2 = a:c2[1].priority
+ return (l:p1 ==# l:p2 ? 0 : l:p1 ># l:p2 ? 1 : -1)
+endfunction
+function! s:GetSortedTypes( types )
+ return map(sort(items(a:types), 's:TypeCompare'), 'v:val[0]')
+endfunction
+function! s:GetSnippetPatternsPerType( types )
+ return map(
+ \ s:GetSortedTypes(a:types),
+ \ '[v:val, a:types[v:val].pattern]'
+ \)
+endfunction
+function! s:GetSnippets( types, type )
+ if a:type ==# 'none'
+ " There is no base, so the snippet type can not be determined. Ask all
+ " registered types for their snippets.
+ let l:snippets = []
+ for l:type in s:GetSortedTypes(a:types)
+ let l:snippets += call(a:types[l:type].generator, [])
+ endfor
+ return l:snippets
+ else
+ return call(a:types[a:type].generator, [])
+ endif
+endfunction
+
+function! s:DetermineBaseCol( types )
"*******************************************************************************
"* PURPOSE:
" Check the text before the cursor to determine possible base columns where
-" abbreviations of the various types may start.
+" snippets of the various types may start.
"* ASSUMPTIONS / PRECONDITIONS:
-" None.
+" None.
"* EFFECTS / POSTCONDITIONS:
-" None.
+" None.
"* INPUTS:
-" None.
-"* RETURN VALUES:
-" List of possible base columns, more stringently defined and shorter
-" abbreviation types come first. Each element consists of a [abbreviationType,
-" baseCol] tuple.
+" None.
+"* RETURN VALUES:
+" List of possible base columns, more stringently defined and shorter snippet
+" types come first. Each element consists of a [type, baseCol] tuple.
"*******************************************************************************
- " Locate possible start positions of the abbreviation, searching for
- " full-id, end-id and non-id abbreviations.
- " If the insertion started in the current line, only consider characters
- " that were inserted by the last insertion. For this, we match with the
- " stored start position of the current insert mode, if insertion started in
- " the current line. The matched text must definitely be somewhere after it,
- " but need not start with the start-of-insert position.
- let l:insertedTextExpr = (line('.') == g:SnippetComplete_LastInsertStartPosition[1] ? '\%(\%' . g:SnippetComplete_LastInsertStartPosition[2] . 'c.\)\?\%>' . g:SnippetComplete_LastInsertStartPosition[2] . 'c.*\%#\&' : '')
+ " Locate possible start positions of the snippet, searching for full-id,
+ " end-id and non-id abbreviations, and whatever else was registered.
+
+ let l:insertedTextExpr = ''
+ if line('.') == g:SnippetComplete_LastInsertStartPosition[1]
+ " If the insertion started in the current line, only consider characters
+ " that were inserted by the last insertion for Vim abbreviations,
+ " because they only apply then. For this, we match with the stored start
+ " position of the current insert mode, if insertion started in the
+ " current line. The matched text must definitely be somewhere after it,
+ " but need not start with the start-of-insert position.
+ let l:insertedTextExpr = '\%(\%' . g:SnippetComplete_LastInsertStartPosition[2] . 'c.\)\?\%>' . g:SnippetComplete_LastInsertStartPosition[2] . 'c.*\%#\&'
+ endif
+
let l:baseColumns = []
- for l:abbreviationExpression in s:abbreviationExpressions
- let l:startCol = searchpos(l:insertedTextExpr . '\%(' . l:abbreviationExpression[1] . '\)\%#', 'bn', line('.'))[1]
+ for [l:type, l:pattern] in s:GetSnippetPatternsPerType(a:types)
+ let l:needsInsertionAtOnce = get(a:types[l:type], 'needsInsertionAtOnce', 0)
+ let l:startCol = searchpos(
+ \ (l:needsInsertionAtOnce ? l:insertedTextExpr : '') .
+ \ '\%(' . l:pattern . '\)\%#',
+ \ 'bn', line('.'))[1]
if l:startCol != 0
- call add(l:baseColumns, [l:abbreviationExpression[0], l:startCol])
+ call add(l:baseColumns, [l:type, l:startCol])
endif
endfor
if empty(l:baseColumns)
@@ -56,66 +106,25 @@ function! s:DetermineBaseCol()
return l:baseColumns
endfunction
-function! s:GetAbbreviations()
- let l:abbreviations = ''
- let l:save_verbose = &verbose
- try
- set verbose=0 " Do not include any "Last set from" info.
- redir => l:abbreviations
- silent iabbrev
- finally
- redir END
- let &verbose = l:save_verbose
- endtry
-
- let l:globalMatches = []
- let l:localMatches = []
- try
- for l:abb in split(l:abbreviations, "\n")
- let [l:lhs, l:flags, l:rhs] = matchlist(l:abb, '^\S\s\+\(\S\+\)\s\+\([* ][@ ]\)\(.*\)$')[1:3]
- let l:match = { 'word': l:lhs, 'menu': l:rhs }
- call add((l:flags =~# '@' ? l:localMatches : l:globalMatches), l:match)
- endfor
- catch /^Vim\%((\a\+)\)\=:E688/ " catch error E688: More targets than List items
- " When there are no abbreviations, Vim returns "No abbreviation found".
- endtry
-
- " A buffer-local abbreviation overrides an existing global abbreviation with
- " the same {lhs}.
- for l:localWord in map(copy(l:localMatches), 'v:val.word')
- call filter(l:globalMatches, 'v:val.word !=# ' . string(l:localWord))
- endfor
- return l:globalMatches + l:localMatches
-endfunction
-
function! s:GetBase( baseCol, cursorCol )
return strpart(getline('.'), a:baseCol - 1, (a:cursorCol - a:baseCol))
endfunction
-function! s:MatchAbbreviations( abbreviations, abbreviationFilterExpr, baseCol )
+function! s:MatchSnippets( snippets, baseCol )
let l:base = s:GetBase(a:baseCol, col('.'))
"****D echomsg '****' a:baseCol l:base
-
- let l:filter = 'v:val.word =~#' . string(a:abbreviationFilterExpr)
- if ! empty(l:base)
- let l:filter .= ' && v:val.word =~# ''^\V'' . ' . string(escape(l:base, '\'))
- endif
- return filter(copy(a:abbreviations), l:filter)
+ return (empty(l:base) ?
+ \ a:snippets :
+ \ filter(copy(a:snippets), 'strpart(v:val.word, 0, ' . len(l:base) . ') ==# ' . string(l:base))
+ \)
endfunction
-let s:filterExpr = {
-\ 'fullid': '^\k\+$',
-\ 'endid': '^\%(\k\@\!\S\)\+\k$',
-\ 'nonid': '^\S*\%(\k\@!\S\)$',
-\ 'none': '^\S\+$'
-\}
-function! s:GetAbbreviationCompletions()
- let l:baseColumns = s:DetermineBaseCol()
- let l:abbreviations = s:GetAbbreviations()
+function! s:GetSnippetCompletions( types )
+ let l:baseColumns = s:DetermineBaseCol(a:types)
"****D echomsg '####' string(l:baseColumns)
-
let l:completionsByBaseCol = {}
- for [l:abbreviationType, l:baseCol] in l:baseColumns
- let l:matches = s:MatchAbbreviations(l:abbreviations, s:filterExpr[l:abbreviationType], l:baseCol)
-"****D echomsg '****' l:abbreviationType string(l:matches)
+ for [l:type, l:baseCol] in l:baseColumns
+ let l:snippets = s:GetSnippets(a:types, l:type)
+ let l:matches = s:MatchSnippets(l:snippets, l:baseCol)
+"****D echomsg '****' l:type string(l:matches)
if ! empty(l:matches)
let l:completions = get(l:completionsByBaseCol, l:baseCol, [])
let l:completions += l:matches
@@ -134,14 +143,14 @@ function! s:SetupCmdlineForBaseMessage()
" built-in "match m of n" completion mode messages. Unfortunately, an active
" 'showmode' setting may prevent the user from seeing the message in a
" one-line command line. Thus, we temporarily disable the 'showmode'
- " setting.
+ " setting.
if &showmode && &cmdheight == 1
set noshowmode
" Use a single-use autocmd to restore the 'showmode' setting when the
" cursor is moved (this already happens when a next match is selected,
" but then the "match m of n" message takes over) or insert mode is
- " left.
+ " left.
augroup SnippetCompleteTemporaryNoShowMode
autocmd!
autocmd CursorMovedI,InsertLeave * set showmode | autocmd! SnippetCompleteTemporaryNoShowMode
@@ -160,59 +169,59 @@ function! s:ShowMultipleBasesMessage( nextIdx, baseNum, nextBase )
endfunction
function! s:RecordPosition()
" The position record consists of the current cursor position, the buffer,
- " window and tab page number and the buffer's current change state.
+ " window and tab page number and the buffer's current change state.
" As soon as you make an edit, move to another buffer or even the same
" buffer in another tab page or window (or as a minor side effect just close
- " a window above the current), the position changes.
+ " a window above the current), the position changes.
return getpos('.') + [bufnr(''), winnr(), tabpagenr()]
endfunction
let s:lastCompletionsByBaseCol = {}
let s:nextBaseIdx = 0
let s:initialCompletePosition = []
let s:lastCompleteEndPosition = []
-function! SnippetComplete#SnippetComplete()
+function! SnippetComplete#SnippetComplete( types )
"****D echomsg '****' string(s:RecordPosition())
let l:baseNum = len(keys(s:lastCompletionsByBaseCol))
if s:initialCompletePosition == s:RecordPosition() && l:baseNum > 1
" The Snippet complete mapping is being repeatedly executed on the same
" position, and we have multiple completion bases. Use the next/first
- " base from the cached completions.
+ " base from the cached completions.
let l:baseIdx = s:nextBaseIdx
else
- " This is a new completion.
- let s:lastCompletionsByBaseCol = s:GetAbbreviationCompletions()
+ " This is a new completion.
+ let s:lastCompletionsByBaseCol = s:GetSnippetCompletions(a:types)
let l:baseIdx = 0
let l:baseNum = len(keys(s:lastCompletionsByBaseCol))
let s:initialCompletePosition = s:RecordPosition()
- let s:initialCompletionCol = col('.') " Note: The column is also contained in s:initialCompletePosition, but a separate variable is more expressive.
+ let s:initialCompletionCol = col('.') " Note: The column is also contained in s:initialCompletePosition, but a separate variable is more expressive.
endif
" Multiple bases are presented from shortest base (i.e. largest base column)
" to longest base. Full-id abbreviations have the most restrictive pattern
" and thus always generate the shortest bases; end-id and non-id
" abbreviations accept more character classes and can result in longer
- " bases.
+ " bases.
let l:baseColumns = reverse(sort(keys(s:lastCompletionsByBaseCol)))
if l:baseNum > 0
- " Show the completions for the current base.
+ " Show the completions for the current base.
call complete(l:baseColumns[l:baseIdx], sort(s:lastCompletionsByBaseCol[l:baseColumns[l:baseIdx]], 's:CompletionCompare'))
let s:lastCompleteEndPosition = s:RecordPosition()
if l:baseNum > 1
" There are multiple bases; make subsequent invocations cycle
- " through them.
+ " through them.
let s:nextBaseIdx = (l:baseIdx < l:baseNum - 1 ? l:baseIdx + 1 : 0)
" Note: Setting the completions typically inserts the first match
" and thus advances the cursor. We need the initial cursor position
" to resolve the next base(s) only up to what has actually been
- " entered.
+ " entered.
let l:nextBase = s:GetBase(l:baseColumns[s:nextBaseIdx], s:initialCompletionCol)
" Indicate to the user that additional bases exist, and offer a
- " preview of the next one.
+ " preview of the next one.
call s:ShowMultipleBasesMessage(l:baseIdx + 1, l:baseNum, l:nextBase)
endif
endif
@@ -226,17 +235,17 @@ function! SnippetComplete#PreSnippetCompleteExpr()
" completion end position (after the completions are shown) is recorded in
" s:lastCompleteEndPosition. This position can change if the user selects
" another completion match (via CTRL-N) that has a different length, and
- " only then re-triggers the completion for the next abbreviation base.
+ " only then re-triggers the completion for the next abbreviation base.
" We can still handle this situation by checking for an active popup menu;
" that means that (presumably, could be from another completion type)
- " another abbreviation completion had been triggered.
+ " another abbreviation completion had been triggered.
" To return the cursor to the inital completion position, CTRL-E is used to
" end the completion; this may only not work when 'completeopt' contains
- " "longest" (Vim returns to what was typed or longest common string).
+ " "longest" (Vim returns to what was typed or longest common string).
let l:baseNum = len(keys(s:lastCompletionsByBaseCol))
return (pumvisible() || s:lastCompleteEndPosition == s:RecordPosition() && l:baseNum > 1 ? "\<C-e>" : '')
endfunction
-function! SnippetComplete#RecordInsertStartPosition()
-endfunction
-" vim: set sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
+let &cpo = s:save_cpo
+unlet s:save_cpo
+" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
Oops, something went wrong.

0 comments on commit 3d58935

Please sign in to comment.