├── .gitignore ├── SpellCheck.manifest ├── tests ├── bad.txt └── good.txt ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── syntax └── qf │ └── SpellCheck.vim ├── autoload ├── SpellCheck.vim └── SpellCheck │ ├── quickfix.vim │ └── mappings.vim ├── plugin └── SpellCheck.vim ├── doc └── SpellCheck.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.msgout 2 | *.msgresult 3 | *.out 4 | *.tap 5 | doc/*.description 6 | doc/*.install 7 | tags 8 | -------------------------------------------------------------------------------- /SpellCheck.manifest: -------------------------------------------------------------------------------- 1 | autoload/SpellCheck.vim 2 | autoload/SpellCheck/mappings.vim 3 | autoload/SpellCheck/quickfix.vim 4 | plugin/SpellCheck.vim 5 | syntax/qf/SpellCheck.vim 6 | doc/SpellCheck.txt 7 | -------------------------------------------------------------------------------- /tests/bad.txt: -------------------------------------------------------------------------------- 1 | Die elektronische Lohnsteuerkarte soll helfen, das Steuerrecht zu vereinfachen: 2 | Bei dem neuen Verfahren muss der Arbeitnehmer nur noch sein Geburtxdatum und 3 | seine steuerliche Identifikationsnummer angeben, wenn er einen neuen Job beginnd 4 | - mit diesen Informationen kann ker Arbeitgeber dann alle wichtigen 5 | Hohnsteuer-Unformationen elektronisch kei ker Finanzverwaltung abrufen. So weit 6 | die Teory 7 | -------------------------------------------------------------------------------- /tests/good.txt: -------------------------------------------------------------------------------- 1 | Die elektronische Lohnsteuerkarte soll helfen, das Steuerrecht zu vereinfachen: 2 | Bei dem neuen Verfahren muss der Arbeitnehmer nur noch sein Geburtsdatum und 3 | seine steuerliche Identifikationsnummer angeben, wenn er einen neuen Job beginnt 4 | - mit diesen Informationen kann der Arbeitgeber dann alle wichtigen 5 | Lohnsteuer-Informationen elektronisch bei der Finanzverwaltung abrufen. So weit 6 | die Theorie 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an enhancement for the plugin 4 | title: '' 5 | labels: enhancement 6 | 7 | --- 8 | 9 | **Motivation** 10 | 11 | _A clear and concise description of what currently is hard to do._ 12 | 13 | **Request and Purpose** 14 | 15 | _A clear and concise description of what you want to happen. Possible fit criteria._ 16 | 17 | **Alternatives** 18 | 19 | _A clear and concise description of any alternative solutions or features you've considered._ 20 | -------------------------------------------------------------------------------- /syntax/qf/SpellCheck.vim: -------------------------------------------------------------------------------- 1 | " qf/SpellCheck.vim: Additional syntax definitions for spelling errors and their context. 2 | " 3 | " DEPENDENCIES: 4 | " - ingo/compat/regexp.vim autoload script 5 | " 6 | " Copyright: (C) 2014-2017 Ingo Karkat 7 | " The VIM LICENSE applies to this script; see ':help copyright'. 8 | " 9 | " Maintainer: Ingo Karkat 10 | 11 | if ! exists('g:SpellCheck_IsQuickfixHighlightActive') || ! g:SpellCheck_IsQuickfixHighlightActive 12 | finish " Only apply the syntax additions when the quickfix window actually contains spelling errors, to avoid messing up errors from unrelated sources. 13 | endif 14 | 15 | let s:countAndMessageExpr = '\%( (\d\+)\)\?\%( \[[^]]\+\]\)\?' 16 | execute 'syntax match qfSpellErrorWord "| \zs' . g:SpellCheck_SpellWordPattern . '\+\%(' . s:countAndMessageExpr . '\%($\|\t\t\)\)\@=" nextgroup=qfSpellContext' 17 | syntax match qfSpellContext "\t\t.*$" contains=qfSpellErrorWordInContext 18 | execute 'syntax match qfSpellErrorWordInContext "' . ingo#compat#regexp#GetOldEnginePrefix() . '\%(| \1' . s:countAndMessageExpr . '\t\t.*\)\@<=\(' . g:SpellCheck_SpellWordPattern . '\+\)" contained' 19 | 20 | 21 | highlight def link qfSpellErrorWord SpellBad 22 | highlight def link qfSpellErrorWordInContext Normal 23 | highlight def link qfSpellContext SpecialKey 24 | 25 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: The plugin isn't working at all or shows wrong or unexpected behavior 4 | title: '' 5 | labels: '' 6 | 7 | --- 8 | **Frequent Issues** 9 | 10 | * **E117: Unknown function: ingo#...**: Have you installed the [ingo-library plugin](http://www.vim.org/scripts/script.php?script_id=4433) (or via [GitHub](https://github.com/inkarkat/vim-ingo-library)) as well, as documented in the _dependencies_ section of the readme and plugin help? 11 | * **Sudden problems after updating**: Did you check out the default _master_ branch? Unless you want to participate in feature development and alpha testing, I would recommend that you switch from _master_ to the _stable_ branch; this way, you'll only update to released versions that are (hopefully) better tested and documented. 12 | * **Neovim**: I don't explicitly consider nor test Neovim compatibility; my plugins are written for Vim. If a plugin can be made to work on Neovim with trivial changes, I wouldn't mind including them, but anything more involved should in my opinion be filed as a compatibility bug against Neovim (as its homepage proclaims: _Fully compatible with Vim's editing model and the Vimscript language._) 13 | 14 | **Describe the bug** 15 | 16 | _A clear and concise description of what the bug is._ 17 | 18 | **How to Reproduce** 19 | 20 | _Detailed steps to reproduce the behavior._ 21 | 22 | **Expected Behavior** 23 | 24 | _A clear and concise description of what you expected to happen._ 25 | 26 | **Environment** 27 | - Plugin version (e.g. stable version _1.10_) / revision _a1b2c3d4_ from the master branch 28 | - Dependency versions (e.g. [ingo-library plugin](https://github.com/inkarkat/vim-ingo-library), or external tool versions) 29 | - Vim version (e.g. _8.1.1234_) Or paste the result of `vim --version`. 30 | - OS: (e.g. _Ubuntu 18.04_, _Windows 10 1809_, _macOS 10.14_) 31 | - Install method: (e.g. manually via Vimball or ZIP file, GitHub clone as pack plugin, Plugin manager _NAME_) 32 | - Other plugins / additional context if you think this could be important 33 | -------------------------------------------------------------------------------- /autoload/SpellCheck.vim: -------------------------------------------------------------------------------- 1 | " SpellCheck.vim: Check for spelling errors. 2 | " 3 | " DEPENDENCIES: 4 | " - ingo-library.vim plugin 5 | " 6 | " Copyright: (C) 2011-2020 Ingo Karkat 7 | " The VIM LICENSE applies to this script; see ':help copyright'. 8 | " 9 | " Maintainer: Ingo Karkat 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | function! SpellCheck#ParseArguments( arguments ) 14 | let l:types = [] 15 | let l:arguments = a:arguments 16 | while l:arguments =~# '^\a\+\%(\s\|$\)' 17 | let [l:first, l:rest] = matchlist(l:arguments, '^\(\a\+\)\s*\(.*\)$')[1:2] 18 | if index(g:SpellCheck_ErrorTypes, l:first) != -1 19 | call add(l:types, l:first) 20 | let l:arguments = l:rest 21 | else 22 | break 23 | endif 24 | endwhile 25 | 26 | return [ 27 | \ ingo#collections#ToDict(empty(l:types) ? 28 | \ ingo#plugin#setting#GetBufferLocal('SpellCheck_ConsideredErrorTypes') : 29 | \ l:types 30 | \ ), 31 | \ (empty(l:arguments) ? 32 | \ ingo#plugin#setting#GetBufferLocal('SpellCheck_Predicates') : 33 | \ l:arguments 34 | \ ) 35 | \] 36 | endfunction 37 | function! SpellCheck#ApplyPredicates( predicates ) 38 | return empty(a:predicates) || eval(a:predicates) 39 | endfunction 40 | 41 | function! SpellCheck#AutoEnableSpell() 42 | setlocal spell 43 | if empty(&l:spelllang) 44 | throw 'No spell language defined; use :setl spl=... to enable spell checking' 45 | endif 46 | endfunction 47 | 48 | function! SpellCheck#SpellAddWrapper( count, command ) 49 | call SpellCheck#AutoEnableSpell() 50 | try 51 | execute 'normal!' a:count . a:command 52 | return 1 53 | catch /^Vim\%((\a\+)\)\=:/ 54 | call ingo#msg#VimExceptionMsg() 55 | return 0 56 | endtry 57 | endfunction 58 | 59 | function! SpellCheck#CheckEnabledSpelling() 60 | if ! &l:spell 61 | if ! empty(g:SpellCheck_OnNospell) 62 | " Allow hook to enable spelling using some sort of logic. 63 | try 64 | call call(g:SpellCheck_OnNospell, []) 65 | catch 66 | call ingo#msg#VimExceptionMsg() 67 | return 0 68 | endtry 69 | endif 70 | endif 71 | if ! &l:spell || empty(&l:spelllang) 72 | call ingo#msg#ErrorMsg('E756: Spell checking is not enabled') 73 | return 0 74 | endif 75 | 76 | return 1 77 | endfunction 78 | 79 | function! s:GotoNextSpellError() 80 | let l:save_wrapscan = &wrapscan 81 | set nowrapscan 82 | silent! keepjumps normal! ]s 83 | let &wrapscan = l:save_wrapscan 84 | endfunction 85 | function! s:GotoFirstMisspelling() 86 | let l:save_wrapscan = &wrapscan 87 | set nowrapscan 88 | silent! normal! gg0]s[s 89 | let &wrapscan = l:save_wrapscan 90 | normal! zv 91 | endfunction 92 | function! SpellCheck#CheckErrors( firstLine, lastLine, isNoJump, arguments ) 93 | if ! SpellCheck#CheckEnabledSpelling() 94 | return 2 95 | endif 96 | 97 | let [l:types, l:predicates] = SpellCheck#ParseArguments(a:arguments) 98 | let l:save_view = winsaveview() 99 | try 100 | let l:isError = 0 101 | let l:isFirst = 1 102 | call cursor(a:firstLine, 1) 103 | while 1 104 | let l:currentPos = getpos('.') 105 | call s:GotoNextSpellError() 106 | 107 | if line('.') < a:firstLine || line('.') > a:lastLine 108 | " The next spell error lies outside the passed range. 109 | elseif getpos('.') != l:currentPos 110 | let l:isError = 1 111 | elseif l:isFirst 112 | " Either there are no spelling errors at all, or we're on the sole 113 | " spelling error in the buffer. 114 | let l:isError = ! empty(spellbadword()[0]) 115 | endif 116 | 117 | if l:isError 118 | if empty(l:types) 119 | if ! SpellCheck#ApplyPredicates(l:predicates) 120 | " The predicates signal to ignore this error, keep searching. 121 | let l:isError = 0 122 | let l:isFirst = 0 123 | continue 124 | endif 125 | else 126 | let [l:spellBadWord, l:errorType] = spellbadword() 127 | if ! has_key(l:types, l:errorType) || ! SpellCheck#ApplyPredicates(l:predicates) 128 | " This is an ignored type of error, or it is ignored by 129 | " predicates; keep searching. 130 | let l:isError = 0 131 | let l:isFirst = 0 132 | continue 133 | endif 134 | endif 135 | endif 136 | 137 | break 138 | endwhile 139 | let l:errorPos = getpos('.') 140 | catch /^Vim\%((\a\+)\)\=:/ 141 | call ingo#msg#VimExceptionMsg() 142 | return 1 143 | finally 144 | call winrestview(l:save_view) 145 | endtry 146 | 147 | if l:isError 148 | if ! a:isNoJump 149 | normal! m' 150 | call cursor(l:errorPos[1:2]) 151 | endif 152 | 153 | call ingo#msg#ErrorMsg('There are spelling errors') 154 | else 155 | call SpellCheck#NoErrorsFoundMessage(l:types, l:predicates) 156 | endif 157 | 158 | return l:isError 159 | endfunction 160 | 161 | function! SpellCheck#NoErrorsFoundMessage( types, predicates ) 162 | call ingo#msg#StatusMsg(printf('No %sspell errors found%s', 163 | \ (empty(a:types) ? '' : join(sort(keys(a:types)), ' or ') . ' '), 164 | \ (empty(a:predicates) ? '' : ' where ' . a:predicates) 165 | \)) 166 | endfunction 167 | 168 | let &cpo = s:save_cpo 169 | unlet s:save_cpo 170 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 171 | -------------------------------------------------------------------------------- /autoload/SpellCheck/quickfix.vim: -------------------------------------------------------------------------------- 1 | " SpellCheck/quickfix.vim: Show all spelling errors as a quickfix list. 2 | " 3 | " DEPENDENCIES: 4 | " - ingo-library.vim plugin 5 | " 6 | " Copyright: (C) 2011-2020 Ingo Karkat 7 | " The VIM LICENSE applies to this script; see ':help copyright'. 8 | " 9 | " Maintainer: Ingo Karkat 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | function! s:GotoNextLine( lastLine ) 14 | if line('.') < a:lastLine 15 | call cursor(line('.') + 1, 1) 16 | return 1 17 | else 18 | return 0 19 | endif 20 | endfunction 21 | function! s:GetErrorContext( lnum, col ) 22 | return matchstr(getline(a:lnum), printf(g:SpellCheck_ErrorContextPattern, '\%' . a:col . 'c')) 23 | endfunction 24 | function! s:RetrieveSpellErrors( firstLine, lastLine, types, predicates ) 25 | let l:spellErrorInfo = {} 26 | let l:spellErrorList = [] 27 | call cursor(a:firstLine, 1) 28 | 29 | while 1 30 | let [l:spellBadWord, l:errorType] = spellbadword() 31 | if empty(l:spellBadWord) 32 | if s:GotoNextLine(a:lastLine) 33 | continue 34 | else 35 | break 36 | endif 37 | endif 38 | 39 | if (empty(a:types) || has_key(a:types, l:errorType)) && SpellCheck#ApplyPredicates(a:predicates) 40 | let [l:lnum, l:col] = getpos('.')[1:2] 41 | if has_key(l:spellErrorInfo, l:spellBadWord) 42 | let l:entry = l:spellErrorInfo[l:spellBadWord] 43 | let l:entry.count += 1 44 | if len(l:entry.context) < g:SpellCheck_ErrorContextNum 45 | call ingo#collections#unique#AddNew(l:entry.context, s:GetErrorContext(l:lnum, l:col)) 46 | endif 47 | else 48 | let l:spellErrorInfo[l:spellBadWord] = { 49 | \ 'type': l:errorType, 50 | \ 'lnum': l:lnum, 51 | \ 'col': l:col, 52 | \ 'count': 1, 53 | \ 'context': (g:SpellCheck_ErrorContextNum > 0 ? [s:GetErrorContext(l:lnum, l:col)] : []) 54 | \} 55 | call add(l:spellErrorList, l:spellBadWord) 56 | endif 57 | endif 58 | 59 | let l:colAfterBadWord = col('.') + len(l:spellBadWord) 60 | if l:colAfterBadWord < col('$') 61 | call cursor(line('.'), l:colAfterBadWord) 62 | elseif ! s:GotoNextLine(a:lastLine) 63 | break 64 | endif 65 | endwhile 66 | 67 | return [l:spellErrorList, l:spellErrorInfo] 68 | endfunction 69 | function! s:ToQfEntry( error, bufnr, spellErrorInfo ) 70 | let l:entry = a:spellErrorInfo 71 | let l:entry.bufnr = a:bufnr 72 | let l:entry.text = a:error . 73 | \ (l:entry.count > 1 ? ' (' . l:entry.count . ')' : '') . 74 | \ (empty(l:entry.context) ? '' : "\t\t" . join(l:entry.context, ', ')) 75 | let l:entry.type = (l:entry.type ==# 'bad' ? '' : toupper(l:entry.type[0])) 76 | return l:entry 77 | endfunction 78 | function! s:FillQuickfixList( bufnr, spellErrorList, spellErrorInfo, isNoJump, isUseLocationList ) 79 | let l:qflist = map(a:spellErrorList, 's:ToQfEntry(v:val, a:bufnr, a:spellErrorInfo[v:val])') 80 | 81 | "let l:quickfixType = (a:isUseLocationList ? 2 : 1) 82 | "call ingo#window#quickfix#CmdPre(l:quickfixType, '[l]spell') 83 | silent call ingo#event#Trigger('QuickFixCmdPre ' . (a:isUseLocationList ? 'lspell' : 'spell')) " Allow hooking into the quickfix update. 84 | 85 | if a:isUseLocationList 86 | let l:list = 'l' 87 | call setloclist(0, l:qflist, ' ') 88 | else 89 | let l:list = 'c' 90 | 91 | let l:errorsFromOtherBuffers = filter(getqflist(), 'v:val.bufnr != a:bufnr') 92 | if empty(l:errorsFromOtherBuffers) 93 | " We haven't accumulated spelling errors from multiple buffers, just 94 | " replace the entire quickfix list. 95 | call setqflist(l:qflist, ' ') 96 | else 97 | " To allow accumulating spelling errors from multiple buffers (e.g. 98 | " via :argdo SpellCheck), just remove the previous errors for the 99 | " current buffer, and append the new list. 100 | call setqflist(l:errorsFromOtherBuffers + l:qflist, 'r') 101 | 102 | " Jump to the first updated spelling error of the current buffer. 103 | let l:list = (len(l:errorsFromOtherBuffers) + 1) . 'c' 104 | endif 105 | endif 106 | 107 | if len(a:spellErrorList) > 0 108 | if ! a:isNoJump 109 | execute l:list . 'first' 110 | normal! zv 111 | endif 112 | endif 113 | 114 | silent call ingo#event#Trigger('QuickFixCmdPost ' . (a:isUseLocationList ? 'lspell' : 'spell')) " Allow hooking into the quickfix update. 115 | "call ingo#window#quickfix#CmdPost(l:quickfixType, '[l]spell') 116 | endfunction 117 | 118 | function! SpellCheck#quickfix#List( firstLine, lastLine, isNoJump, isUseLocationList, arguments ) 119 | if ingo#window#quickfix#IsQuickfixList(1) == (a:isUseLocationList ? 2 : 1) || ! SpellCheck#CheckEnabledSpelling() 120 | return 2 121 | endif 122 | 123 | let [l:types, l:predicates] = SpellCheck#ParseArguments(a:arguments) 124 | let l:save_view = winsaveview() 125 | try 126 | let [l:spellErrorList, l:spellErrorInfo] = s:RetrieveSpellErrors(a:firstLine, a:lastLine, l:types, l:predicates) 127 | catch /^Vim\%((\a\+)\)\=:/ 128 | call ingo#msg#VimExceptionMsg() 129 | return 1 130 | finally 131 | call winrestview(l:save_view) 132 | endtry 133 | 134 | call s:FillQuickfixList(bufnr(''), l:spellErrorList, l:spellErrorInfo, a:isNoJump, a:isUseLocationList) 135 | if len(l:spellErrorList) == 0 136 | call SpellCheck#NoErrorsFoundMessage(l:types, l:predicates) 137 | endif 138 | 139 | return (len(l:spellErrorList) > 0) 140 | endfunction 141 | 142 | let &cpo = s:save_cpo 143 | unlet s:save_cpo 144 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 145 | -------------------------------------------------------------------------------- /plugin/SpellCheck.vim: -------------------------------------------------------------------------------- 1 | " SpellCheck.vim: Work with spelling errors. 2 | " 3 | " DEPENDENCIES: 4 | " - Requires Vim 7.0 or higher. 5 | " - ingo-library.vim plugin 6 | " 7 | " Copyright: (C) 2011-2020 Ingo Karkat 8 | " The VIM LICENSE applies to this script; see ':help copyright'. 9 | " 10 | " Maintainer: Ingo Karkat 11 | 12 | " Avoid installing twice or when in unsupported Vim version. 13 | if exists('g:loaded_SpellCheck') || (v:version < 700) 14 | finish 15 | endif 16 | let g:loaded_SpellCheck = 1 17 | let s:save_cpo = &cpo 18 | set cpo&vim 19 | 20 | "- constants ------------------------------------------------------------------- 21 | 22 | let g:SpellCheck_ErrorTypes = ['bad', 'rare', 'local', 'caps'] 23 | 24 | 25 | "- configuration --------------------------------------------------------------- 26 | 27 | if ! exists('g:SpellCheck_OnNospell') 28 | let g:SpellCheck_OnNospell = function('SpellCheck#AutoEnableSpell') 29 | endif 30 | if ! exists('g:SpellCheck_OnSpellAdd') 31 | let g:SpellCheck_OnSpellAdd = function('SpellCheck#SpellAddWrapper') 32 | endif 33 | 34 | 35 | if ! exists('g:SpellCheck_DefineAuxiliaryCommands') 36 | let g:SpellCheck_DefineAuxiliaryCommands = 1 37 | endif 38 | if ! exists('g:SpellCheck_DefineQuickfixMappings') 39 | let g:SpellCheck_DefineQuickfixMappings = 1 40 | endif 41 | 42 | if ! exists('g:SpellCheck_ErrorContextNum') 43 | let g:SpellCheck_ErrorContextNum = 99 44 | endif 45 | 46 | " From :help spellfile-cleanup: 47 | " Vim uses a fixed method to recognize a word. This is independent of 48 | " 'iskeyword', so that it also works in help files and for languages that 49 | " include characters like '-' in 'iskeyword'. [...] 50 | " The table with word characters is stored in the main .spl file. 51 | " Since we cannot easily query that table, approximate the set of characters. 52 | if ! exists('g:SpellCheck_SpellWordPattern') 53 | let g:SpellCheck_SpellWordPattern = '[[:alnum:]'']' 54 | endif 55 | 56 | if ! exists('g:SpellCheck_ErrorContextPattern') 57 | let g:SpellCheck_ErrorContextPattern = '\%(' . g:SpellCheck_SpellWordPattern . '*\%(' . g:SpellCheck_SpellWordPattern . '\@!\S\)\+\|' . g:SpellCheck_SpellWordPattern . '\+\s\+\)\?%s.' . g:SpellCheck_SpellWordPattern . '*\%(\%(' . g:SpellCheck_SpellWordPattern . '\@!\S\)\+' . g:SpellCheck_SpellWordPattern . '*\|\s\+' . g:SpellCheck_SpellWordPattern . '\+\)\?' 58 | endif 59 | 60 | if ! exists('g:SpellCheck_QuickfixHighlight') 61 | let g:SpellCheck_QuickfixHighlight = 1 62 | endif 63 | 64 | if ! exists('g:SpellCheck_ConsideredErrorTypes') 65 | let g:SpellCheck_ConsideredErrorTypes = [] 66 | endif 67 | if ! exists('g:SpellCheck_Predicates') 68 | let g:SpellCheck_Predicates = '' 69 | endif 70 | 71 | 72 | "- mappings -------------------------------------------------------------------- 73 | 74 | if g:SpellCheck_DefineQuickfixMappings || g:SpellCheck_QuickfixHighlight 75 | augroup SpellCheckQuickfixMappings 76 | autocmd! 77 | " Note: Cannot use the QuickFixCmdPost event directly, as it does not 78 | " necessarily fire in the quickfix window! Fortunately, a BufRead event 79 | " for file "quickfix" is posted whenever a quickfix or location list 80 | " window is opened. (And the filetype is (re-)set afterwards, too.) 81 | " So, we use the QuickFixCmdPost event as a trigger to create the 82 | " mappings for the quickfix window when it is opened. 83 | autocmd QuickFixCmdPost spell,lspell 84 | \ let g:SpellCheck_IsQuickfixHighlightActive = g:SpellCheck_QuickfixHighlight | 85 | \ autocmd! SpellCheckQuickfixMappings BufRead quickfix 86 | \ if g:SpellCheck_DefineQuickfixMappings | 87 | \ call SpellCheck#mappings#MakeMappings() | 88 | \ endif 89 | " As a new scratch buffer is created whenever the quickfix window is 90 | " opened, the autocmd has to persist to embellish future ones (i.e. 91 | " after :cclose | :copen). But we stop embellishing when a different 92 | " quickfix source is used: 93 | autocmd QuickFixCmdPost * 94 | \ if expand('') !~# '^l\?spell' | 95 | \ let g:SpellCheck_IsQuickfixHighlightActive = 0 | 96 | \ execute 'autocmd! SpellCheckQuickfixMappings BufRead quickfix' | 97 | \ endif 98 | augroup END 99 | endif 100 | 101 | 102 | "- commands -------------------------------------------------------------------- 103 | 104 | call ingo#plugin#cmdcomplete#MakeFixedListCompleteFunc(g:SpellCheck_ErrorTypes, 'SpellCheckCompleteFunc') 105 | 106 | if g:SpellCheck_DefineAuxiliaryCommands 107 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc BDeleteUnlessSpellError if ! SpellCheck#CheckErrors(, , 0, ) | bdelete | endif 108 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc WriteUnlessSpellError if ! SpellCheck#CheckErrors(, , 0, ) | write | endif 109 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc WriteDeleteUnlessSpellError if ! SpellCheck#CheckErrors(, , 0, ) | write | bdelete | endif 110 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc XitUnlessSpellError if ! SpellCheck#CheckErrors(, , 0, ) | write | quit | endif 111 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc NextUnlessSpellError if ! SpellCheck#CheckErrors(, , 0, ) | next | endif 112 | 113 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc BDeleteOrSpellCheck if ! SpellCheck#quickfix#List(, , 0, 0, ) | bdelete | endif 114 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc WriteOrSpellCheck if ! SpellCheck#quickfix#List(, , 0, 0, ) | write | endif 115 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc WriteDeleteOrSpellCheck if ! SpellCheck#quickfix#List(, , 0, 0, ) | write | bdelete | endif 116 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc XitOrSpellCheck if ! SpellCheck#quickfix#List(, , 0, 0, ) | write | quit | endif 117 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc NextOrSpellCheck if ! SpellCheck#quickfix#List(, , 0, 0, ) | next | endif 118 | 119 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc UpdateAndSpellCheck update | call SpellCheck#quickfix#List(, , 0, 0, ) 120 | endif 121 | 122 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc SpellCheck call SpellCheck#quickfix#List(, , 0, 0, ) 123 | command! -bar -bang -range=% -nargs=* -complete=customlist,SpellCheckCompleteFunc SpellLCheck call SpellCheck#quickfix#List(, , 0, 1, ) 124 | 125 | let &cpo = s:save_cpo 126 | unlet s:save_cpo 127 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 128 | -------------------------------------------------------------------------------- /autoload/SpellCheck/mappings.vim: -------------------------------------------------------------------------------- 1 | " SpellCheck/mappings.vim: Special quickfix window mappings. 2 | " 3 | " DEPENDENCIES: 4 | " - ingo-library.vim plugin 5 | " - repeat.vim (vimscript #2136) plugin (optional) 6 | " - visualrepeat.vim (vimscript #3848) plugin (optional) 7 | " 8 | " Copyright: (C) 2012-2020 Ingo Karkat 9 | " The VIM LICENSE applies to this script; see ':help copyright'. 10 | " 11 | " Maintainer: Ingo Karkat 12 | 13 | function! SpellCheck#mappings#SpellSuggestWrapper( ... ) 14 | let l:addendum = '' 15 | let l:followUpCommands = copy(a:000) 16 | 17 | if ! &l:spell 18 | if SpellCheck#CheckEnabledSpelling() 19 | call insert(l:followUpCommands, 'setlocal nospell', 0) 20 | else 21 | setlocal nospell " This might have been set by SpellCheck#CheckEnabledSpelling(). 22 | return '' 23 | endif 24 | endif 25 | 26 | if ! empty(l:followUpCommands) 27 | " If no [count] is given, the z= command queries the spell suggestion. 28 | " Unfortunately, the querying is disturbed by any following typeahead, 29 | " even when submitted via feedkeys(). To work around this, we set up a 30 | " temporary autocmd that fires once at the next possible point in time, 31 | " then deletes itself. 32 | if v:count 33 | " No querying, so we can simply append the command to undo the 34 | " temporary enabling of spelling. 35 | let l:addendum = ':' . join(l:followUpCommands, '|') . "\" 36 | else 37 | " Turn off 'spell' at the next possible event: 38 | " BufLeave: Before another buffer is loaded in the current window. 39 | " WinLeave: Before the window is left. 40 | " InsertEnter: Editing is started (or resumed from an insert mode 41 | " {cmd}). 42 | " CursorHold: Nothing happened after the {cmd}. 43 | " ... and reduce the time period by temporarily reducing 44 | " 'updatetime'. Otherwise, the cursor may stay in the 45 | " target buffer, and suddenly move back to the quickfix 46 | " window after many seconds, or immediately after the 47 | " user moves the cursor. 48 | let s:save_updatetime = &updatetime 49 | set updatetime=100 50 | " CursorMoved: The user jumped around in the current buffer. 51 | augroup SpellSuggestOff 52 | autocmd! 53 | execute 'autocmd BufLeave,WinLeave,InsertEnter,CursorHold,CursorMoved let &updatetime = s:save_updatetime | execute "autocmd! SpellSuggestOff" | ' . join(l:followUpCommands, '|') 54 | augroup END 55 | endif 56 | endif 57 | 58 | return 'z=' . l:addendum 59 | endfunction 60 | function! SpellCheck#mappings#SpellRepeat() 61 | try 62 | " Always print the number of repeated spell corrections, even if there 63 | " is only one. 64 | let l:save_report = &report 65 | set report=0 66 | 67 | spellrepall 68 | catch /^Vim\%((\a\+)\)\=:E75[23]:/ " E752: No previous spell replacement; E753: Not found: ... 69 | " Silently ignore the fact that the misspelled word didn't occur 70 | " elsewhere. 71 | finally 72 | let &report = l:save_report 73 | endtry 74 | endfunction 75 | 76 | 77 | function! s:SetCount() 78 | let s:count = (v:count ? v:count : '') 79 | endfunction 80 | function! s:GetCountAndRecordBefore() 81 | let s:beforeTick = b:changedtick 82 | let s:beforeLineContent = getline('.') 83 | return s:count 84 | endfunction 85 | function! s:RecordAfter() 86 | let s:afterTick = b:changedtick 87 | let s:afterLineContent = getline('.') 88 | endfunction 89 | function! s:IsChangeRecorded() 90 | return (s:afterTick > s:beforeTick) 91 | endfunction 92 | function! s:InsertMessage( entry, statusMessage ) 93 | let l:entry = a:entry 94 | if a:statusMessage =~# '\" 124 | let l:isSuccess = call(g:SpellCheck_OnSpellAdd, [l:count, a:command]) 125 | wincmd p 126 | 127 | if ! l:isSuccess || empty(a:statusMessage) | return | endif 128 | call s:QuickfixInsertMessage(line('.'), a:statusMessage) 129 | 130 | silent! call repeat#set(a:command, l:count) 131 | silent! call visualrepeat#set(a:command, l:count) 132 | endfunction 133 | function! s:GetCorrectedText() 134 | " XXX: Unfortunately, Vim doesn't set the change marks `[ `] on z=, so we 135 | " have to split the line where the correction occurred manually into words 136 | " and cut away identical words from the front and end until we've narrowed 137 | " it down to the change. 138 | let l:beforeWords = ingo#collections#SplitKeepSeparators(s:beforeLineContent, '\k\@!.') 139 | let l:afterWords = ingo#collections#SplitKeepSeparators(s:afterLineContent, '\k\@!.') 140 | 141 | let l:startIdx = 0 142 | while get(l:beforeWords, l:startIdx, '') ==# get(l:afterWords, l:startIdx, '') 143 | let l:startIdx += 1 144 | endwhile 145 | let l:endIdx = 0 146 | while get(l:beforeWords, -1 * l:endIdx, '') ==# get(l:afterWords, -1 * l:endIdx, '') 147 | let l:endIdx += 1 148 | endwhile 149 | 150 | return join(l:afterWords[l:startIdx : (-1 * l:endIdx)], '') 151 | endfunction 152 | function! s:QuickfixInsertCorrectionMessage() 153 | let l:changedText = s:GetCorrectedText() 154 | 155 | call s:QuickfixInsertMessage(line('.'), 'corrected' . (empty(l:changedText) ? '' : ': ' . l:changedText)) 156 | endfunction 157 | function! s:UndoCorrectedQuickfixEntry( lnum ) 158 | if getline(a:lnum) =~# ' \[corrected: .*]\%($\|\ze\t\t\)' 159 | call s:QuickfixInsertMessage(a:lnum, '') 160 | return 1 161 | endif 162 | return 0 163 | endfunction 164 | function! s:UndoWrapper() 165 | execute "normal! \" 166 | let l:isAfterSpellCorrection = (exists('s:afterLineContent') && getline('.') ==# s:afterLineContent) 167 | normal u 168 | let l:isSpellCorrectionUndone = (l:isAfterSpellCorrection && exists('s:beforeLineContent') && getline('.') ==# s:beforeLineContent) 169 | unlet! s:beforeLineContent s:afterLineContent 170 | wincmd p 171 | if l:isSpellCorrectionUndone 172 | if ! s:UndoCorrectedQuickfixEntry(line('.')) 173 | " The user might have already progressed to the next quickfix 174 | " entry when he realized he wanted to undo the previous 175 | " correction. 176 | call s:UndoCorrectedQuickfixEntry(line('.') - 1) 177 | endif 178 | endif 179 | endfunction 180 | function! SpellCheck#mappings#MakeMappings() 181 | " Intercept word list management commands. 182 | " The command wrapper itself checks for the success of the wrapped command, 183 | " and updates the list entry accordingly. 184 | for [l:command, l:statusMessage] in [['zg', 'added'], ['zG', 'good'], ['zw', 'added as wrong'], ['zW', 'wrong'], ['zug', 'removed'], ['zuG', 'undo good'], ['zuw', 'removed as wrong'], ['zuW', 'undo wrong']] 185 | execute printf('nnoremap %s :call SpellCheck#mappings#OnSpellAdd(%s, %s)', l:command, string(l:command), string(l:statusMessage)) 186 | endfor 187 | 188 | " Intercept spell suggestion command. 189 | " To find out whether a suggestion was accepted (or the query canceled via 190 | " ), we record b:changetick after moving into the target buffer, then 191 | " record again before moving back to the quickfix list, and evaluate and 192 | " update the list entry after we're back in the quickfix list. 193 | nnoremap (SpellSuggestWrapper) GetCountAndRecordBefore() . SpellCheck#mappings#SpellSuggestWrapper('call RecordAfter()', 'call SpellCheck#mappings#SpellRepeat()', 'wincmd p', 'if IsChangeRecorded()call QuickfixInsertCorrectionMessage()endif') 194 | nnoremap