├── .github └── workflows │ ├── neovim.yml │ └── vim.yml ├── LICENSE ├── README.md ├── autoload └── vimscript_lasterror.vim ├── doc ├── tags └── vimscript_lasterror.txt ├── plugin └── vimscript_lasterror.vim └── vimscript_lasterror.gif /.github/workflows/neovim.yml: -------------------------------------------------------------------------------- 1 | name: neovim 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: rhysd/action-setup-vim@v1 13 | with: 14 | neovim: true 15 | version: nightly 16 | - name: Run unit tests 17 | run: | 18 | nvim --version 19 | nvim -u NONE -N --noplugin -c "set rtp+=." -c "call vimscript_lasterror#run_tests()" -c "qa!" 20 | if test -f test.log; then exit 1; fi 21 | -------------------------------------------------------------------------------- /.github/workflows/vim.yml: -------------------------------------------------------------------------------- 1 | name: vim 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: rhysd/action-setup-vim@v1 13 | with: 14 | version: nightly 15 | - name: Run unit tests 16 | run: | 17 | vim --version 18 | vim -u NONE -N --noplugin -c "set rtp+=." -c "call vimscript_lasterror#run_tests()" -c "qa!" 19 | if test -f test.log; then exit 1; fi 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Naruhiko Nishino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # vim-vimscript\_lasterror 3 | [![vim](https://github.com/rbtnn/vim-vimscript_lasterror/workflows/vim/badge.svg)](https://github.com/rbtnn/vim-vimscript_lasterror/actions?query=workflow%3Avim) 4 | [![neovim](https://github.com/rbtnn/vim-vimscript_lasterror/workflows/neovim/badge.svg)](https://github.com/rbtnn/vim-vimscript_lasterror/actions?query=workflow%3Aneovim) 5 | 6 | This plugin provides to jump to the Vim script's last error. 7 | 8 | ![](https://raw.githubusercontent.com/rbtnn/vim-vimscript_lasterror/master/vimscript_lasterror.gif) 9 | 10 | ## Usage 11 | 12 | ### :VimscriptLastError [-loclist] [-quickfix] [-messages] 13 | If arguments are not specified, try jumping to the Vim script's last error. 14 | If `-loclist` is specified, set Vim script's errors to current loclist. 15 | If `-quickfix` is specified, set Vim script's errors to quickfix. 16 | If `-messages` is specified, dump the output of `:messages` to new window. Typing `` under a Vim script's error line in the window, You can jump to the location. 17 | 18 | 19 | ## Remarks 20 | 21 | * This plugin find Vim script's errors from output of `:messages`. 22 | " This plugin does not treat E384 as a error(=`search hit TOP without match for:`) 23 | " This plugin does not treat E385 as a error(=`search hit BOTTOM without match for:`) 24 | " This plugin does not treat E553 as a error(=`No more items`) 25 | 26 | ## Concepts 27 | 28 | * This plugin supports Vim and Neovim. 29 | * This plugin does not provide to customize user-settings. 30 | * This plugin provides only one command. 31 | 32 | ## Inspired by 33 | 34 | * [Vim scriptのエラーメッセージをパースしてquickfixに表示する - Qiita](https://qiita.com/tmsanrinsha/items/0787352360997c387e84) 35 | 36 | ## License 37 | 38 | Distributed under MIT License. See LICENSE. 39 | -------------------------------------------------------------------------------- /autoload/vimscript_lasterror.vim: -------------------------------------------------------------------------------- 1 | 2 | scriptencoding utf-8 3 | 4 | let s:TEST_LOG = expand(':h:h:gs?\?/?') . '/test.log' 5 | let s:TITLE = "Vim script's errors" 6 | let s:LOCLIST = '-loclist' 7 | let s:QUICKFIX = '-quickfix' 8 | let s:MESSAGES = '-messages' 9 | 10 | " Does not treat E384 as a error(=`search hit TOP without match for:`) 11 | " Does not treat E385 as a error(=`search hit BOTTOM without match for:`) 12 | " Does not treat E553 as a error(=`No more items`) 13 | let s:NOT_TREAT_PATTERN = '^\(' .. join(['E384', 'E385', 'E553'], '\|') .. '\): ' 14 | 15 | function! vimscript_lasterror#comp(ArgLead, CmdLine, CursorPos) abort 16 | return filter([(s:LOCLIST), (s:QUICKFIX), (s:MESSAGES)], { i,x -> -1 != match(x, a:ArgLead) }) 17 | endfunction 18 | 19 | function! vimscript_lasterror#exec(q_args) abort 20 | if -1 == index([(s:LOCLIST), (s:QUICKFIX), (s:MESSAGES), ''], a:q_args) 21 | echohl Error 22 | echo '[vimscript_lasterror] invalid arguments' 23 | echohl None 24 | elseif s:QUICKFIX == a:q_args 25 | call vimscript_lasterror#exec_quickfix() 26 | elseif s:LOCLIST == a:q_args 27 | call vimscript_lasterror#exec_loclist() 28 | elseif s:MESSAGES == a:q_args 29 | call vimscript_lasterror#exec_messages() 30 | else 31 | call vimscript_lasterror#exec_noargs() 32 | endif 33 | endfunction 34 | 35 | function! vimscript_lasterror#exec_quickfix() abort 36 | let xs = vimscript_lasterror#parse_messages() 37 | if !empty(xs) 38 | call setqflist(reverse(xs), 'r') 39 | call setqflist([], 'r', { 'title': s:TITLE, }) 40 | else 41 | echohl Error 42 | echo "[vimscript_lasterror] could not find any Vim script's errors" 43 | echohl None 44 | endif 45 | endfunction 46 | 47 | function! vimscript_lasterror#exec_loclist() abort 48 | let xs = vimscript_lasterror#parse_messages() 49 | if !empty(xs) 50 | call setloclist(0, reverse(xs), 'r') 51 | call setloclist(0, [], 'r', { 'title': s:TITLE, }) 52 | else 53 | echohl Error 54 | echo "[vimscript_lasterror] could not find any Vim script's errors" 55 | echohl None 56 | endif 57 | endfunction 58 | 59 | function! vimscript_lasterror#exec_messages() abort 60 | new 61 | setlocal modifiable noreadonly 62 | silent! call deletebufline(bufnr(), 1, '$') 63 | call setbufline(bufnr(), 1, filter(split(execute('messages'), "\n"), { i, x -> !empty(x) })) 64 | setlocal buftype=nofile nomodifiable readonly 65 | call clearmatches(winnr()) 66 | call matchadd('Error', '^E\d\+:.*$') 67 | nnoremap :call vimscript_lasterror#exec_messages_cr() 68 | endfunction 69 | 70 | function! vimscript_lasterror#exec_noargs() abort 71 | let xs = vimscript_lasterror#parse_messages() 72 | if !empty(xs) 73 | let x = xs[-1] 74 | if filereadable(get(x, 'filename', '')) 75 | if s:find_window_by_path(x['filename']) 76 | execute printf(':%d', x['lnum']) 77 | else 78 | new 79 | call s:open_file(x['filename'], x['lnum']) 80 | endif 81 | normal! zz 82 | echohl Error 83 | echo x['text'] 84 | echohl None 85 | endif 86 | else 87 | echohl Error 88 | echo "[vimscript_lasterror] could not find any Vim script's errors" 89 | echohl None 90 | endif 91 | endfunction 92 | 93 | function! vimscript_lasterror#exec_messages_cr() abort 94 | let err_i = line('.') - 1 95 | let lines = getbufline(bufnr(), 1, '$') 96 | call map(lines, { i, x -> s:parse_messages(x) }) 97 | let x = s:find_lnum_and_pos_from_message(lines, err_i) 98 | if !empty(x) 99 | if filereadable(get(x, 'filename', '')) 100 | if s:find_window_by_path(x['filename']) 101 | execute printf(':%d', x['lnum']) 102 | else 103 | new 104 | call s:open_file(x['filename'], x['lnum']) 105 | endif 106 | normal! zz 107 | echohl Error 108 | echo x['text'] 109 | echohl None 110 | endif 111 | endif 112 | endfunction 113 | 114 | function! vimscript_lasterror#parse_messages() abort 115 | let xs = [] 116 | let lines = split(execute('messages'), "\n") 117 | call map(lines, { i, x -> s:parse_messages(x) }) 118 | call filter(lines, { i, x -> !empty(x) }) 119 | for err_i in range(0, len(lines) - 1) 120 | let x = s:find_lnum_and_pos_from_message(lines, err_i) 121 | if !empty(x) 122 | if -1 == index(xs, x) 123 | let xs += [x] 124 | endif 125 | endif 126 | endfor 127 | return xs 128 | endfunction 129 | 130 | function! vimscript_lasterror#run_tests() abort 131 | if filereadable(s:TEST_LOG) 132 | call delete(s:TEST_LOG) 133 | endif 134 | 135 | let v:errors = [] 136 | 137 | let FixPath = { path -> substitute(path, '[\/]\+', '/', 'g') } 138 | 139 | let temp = tempname() 140 | 141 | messages clear 142 | call writefile([ 143 | \ '" THIS IS A OUTTER COMMENT LINE.', 144 | \ 'function! s:test_scriptfunc() abort', 145 | \ ' " THIS IS A INNER COMMENT LINE.', 146 | \ ' let i = 1 = 2', 147 | \ 'endfunction', 148 | \ 'call s:test_scriptfunc()', 149 | \ ], temp) 150 | execute printf('source %s', escape(temp, ' \')) 151 | let xs = vimscript_lasterror#parse_messages() 152 | call assert_match('^\(E15\|E488\)', xs[0]['text']) 153 | call assert_match('^\d\+_test_scriptfunc$', xs[0]['file_or_func']) 154 | call assert_equal(4, xs[0]['lnum']) 155 | call assert_equal(FixPath(temp), FixPath(xs[0]['filename'])) 156 | 157 | messages clear 158 | call writefile([ 159 | \ '" THIS IS A OUTTER COMMENT LINE.', 160 | \ 'function! Test_globalfunc() abort', 161 | \ ' " THIS IS AN INNER COMMENT LINE.', 162 | \ ' let i = 3 = 4', 163 | \ 'endfunction', 164 | \ 'call Test_globalfunc()', 165 | \ ], temp) 166 | execute printf('source %s', escape(temp, ' \')) 167 | let xs = vimscript_lasterror#parse_messages() 168 | call assert_match('^\(E15\|E488\)', xs[0]['text']) 169 | call assert_equal('Test_globalfunc', xs[0]['file_or_func']) 170 | call assert_equal(4, xs[0]['lnum']) 171 | call assert_equal(FixPath(temp), FixPath(xs[0]['filename'])) 172 | 173 | messages clear 174 | call writefile([ 175 | \ '" THIS IS A OUTTER COMMENT LINE.', 176 | \ 'function! Test_globalfunc() abort', 177 | \ ' " THIS IS AN INNER COMMENT LINE.', 178 | \ ' function! s:test_scriptfunc() abort', 179 | \ ' " THIS IS AN INNER COMMENT LINE.', 180 | \ ' let i = 5 = 6', 181 | \ ' endfunction', 182 | \ ' call s:test_scriptfunc()', 183 | \ 'endfunction', 184 | \ 'call Test_globalfunc()', 185 | \ ], temp) 186 | execute printf('source %s', escape(temp, ' \')) 187 | let xs = vimscript_lasterror#parse_messages() 188 | call assert_match('^\(E15\|E488\)', xs[0]['text']) 189 | call assert_match('^\d\+_test_scriptfunc$', xs[0]['file_or_func']) 190 | call assert_equal(6, xs[0]['lnum']) 191 | call assert_equal(FixPath(temp), FixPath(xs[0]['filename'])) 192 | 193 | messages clear 194 | call writefile([ 195 | \ '" THIS IS A OUTTER COMMENT LINE.', 196 | \ 'let i = 7 = 8', 197 | \ ], temp) 198 | execute printf('source %s', escape(temp, ' \')) 199 | let xs = vimscript_lasterror#parse_messages() 200 | call assert_match('^\(E15\|E488\)', xs[0]['text']) 201 | call assert_equal(FixPath(temp), FixPath(xs[0]['file_or_func'])) 202 | call assert_equal(2, xs[0]['lnum']) 203 | call assert_equal(FixPath(temp), FixPath(xs[0]['filename'])) 204 | 205 | messages clear 206 | call writefile([ 207 | \ '" THIS IS A OUTTER COMMENT LINE.', 208 | \ 'let F = { -> execute("9 = 10") }', 209 | \ 'call F()', 210 | \ ], temp) 211 | execute printf('source %s', escape(temp, ' \')) 212 | let xs = vimscript_lasterror#parse_messages() 213 | call assert_match('^\d\+$', xs[0]['file_or_func']) 214 | call assert_match('^\d\+(1): \(E488:\|E16:\)', xs[0]['text']) 215 | 216 | call delete(temp) 217 | 218 | if !empty(v:errors) 219 | call writefile(v:errors, s:TEST_LOG) 220 | for err in v:errors 221 | echohl Error 222 | echo err 223 | echohl None 224 | endfor 225 | endif 226 | endfunction 227 | 228 | 229 | 230 | function! s:parse_messages(line) abort 231 | if (a:line =~# '^E\d\+: ') && (a:line !~# s:NOT_TREAT_PATTERN) 232 | return { 'kind' : 'message', 'message' : a:line, } 233 | endif 234 | 235 | let lnum_m = matchlist(a:line, '^line\s\+\(\d\+\):$') 236 | if empty(lnum_m) 237 | let lnum_m = matchlist(a:line, '^行\s\+\(\d\+\):$') 238 | endif 239 | if !empty(lnum_m) 240 | return { 'kind' : 'lnum', 'lnum' : lnum_m[1], } 241 | endif 242 | 243 | let pos_m = matchlist(a:line, '^Error detected while \%(processing\|compiling\) \(.*\):$') 244 | if empty(pos_m) 245 | let pos_m = matchlist(a:line, '^\(.\{-}\) の処理中にエラーが検出されました:$') 246 | endif 247 | if !empty(pos_m) 248 | return { 'kind' : 'pos', 'pos' : pos_m[1], } 249 | endif 250 | 251 | return {} 252 | endfunction 253 | 254 | function! s:new_error(text, file_or_func, filename, lnum) abort 255 | return { 'filename' : a:filename, 'lnum' : a:lnum, 'text' : a:text, 'file_or_func' : a:file_or_func, } 256 | endfunction 257 | 258 | function! s:find_lnum_and_pos_from_message(lines, err_i) abort 259 | if get(a:lines[(a:err_i)], 'kind', '') == 'message' 260 | for lnum_i in range(a:err_i - 1, 0, -1) 261 | if get(a:lines[lnum_i], 'kind', '') == 'lnum' 262 | for pos_i in range(lnum_i - 1, 0, -1) 263 | if get(a:lines[pos_i], 'kind') == 'pos' 264 | let file_or_func = a:lines[pos_i]['pos'] 265 | let lnum = str2nr(a:lines[lnum_i]['lnum']) 266 | let errormsg = a:lines[(a:err_i)]['message'] 267 | if -1 != match(file_or_func, '\.\.') 268 | let file_or_func = split(file_or_func, '\.\.')[-1] 269 | endif 270 | if (file_or_func =~# '^function ') 271 | let file_or_func = file_or_func[9:] 272 | elseif (file_or_func =~# '^script ') 273 | let file_or_func = file_or_func[7:] 274 | endif 275 | if filereadable(file_or_func) 276 | return s:new_error(errormsg, file_or_func, expand(file_or_func), lnum) 277 | elseif file_or_func =~# '^' 278 | let text = printf('%s(%d): %s', file_or_func, lnum, errormsg) 279 | return s:new_error(text, file_or_func, '', -1) 280 | else 281 | try 282 | let verbose_text = get(split(execute(printf('verbose function %s', file_or_func)), "\n"), 1, '') 283 | let m = matchlist(verbose_text, '^\s*Last set from \(.*\) line \(\d\+\)$') 284 | if empty(m) 285 | let m = matchlist(verbose_text, '^\s*最後にセットしたスクリプト: \(.*\) \%(行\|line\) \(\d\+\)$') 286 | endif 287 | if !empty(m) 288 | return s:new_error(errormsg, file_or_func, expand(m[1]), lnum + str2nr(m[2])) 289 | else 290 | let text = printf('%s(%d): %s', file_or_func[len('function '):], lnum, errormsg) 291 | return s:new_error(text, file_or_func, '', -1) 292 | endif 293 | catch 294 | return s:new_error(v:exception, file_or_func, '', -1) 295 | endtry 296 | endif 297 | endif 298 | endfor 299 | break 300 | endif 301 | endfor 302 | endif 303 | return {} 304 | endfunction 305 | 306 | function! s:find_window_by_path(path) abort 307 | for x in filter(getwininfo(), { _, x -> x['tabnr'] == tabpagenr() }) 308 | if x['bufnr'] == s:strict_bufnr(a:path) 309 | execute printf(':%dwincmd w', x['winnr']) 310 | return v:true 311 | endif 312 | endfor 313 | return v:false 314 | endfunction 315 | 316 | function! s:strict_bufnr(path) abort 317 | let bnr = bufnr(a:path) 318 | let fname1 = fnamemodify(a:path, ':t') 319 | let fname2 = fnamemodify(bufname(bnr), ':t') 320 | if (-1 == bnr) || (fname1 != fname2) 321 | return -1 322 | else 323 | return bnr 324 | endif 325 | endfunction 326 | 327 | function! s:open_file(path, lnum) abort 328 | let bnr = s:strict_bufnr(a:path) 329 | if -1 == bnr 330 | execute printf('edit %s', fnameescape(a:path)) 331 | else 332 | silent! execute printf('buffer %d', bnr) 333 | endif 334 | if 0 < a:lnum 335 | call cursor([a:lnum, 1]) 336 | endif 337 | endfunction 338 | 339 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | :VimscriptLastError vimscript_lasterror.txt /*:VimscriptLastError* 2 | vimscript_lasterror-commands vimscript_lasterror.txt /*vimscript_lasterror-commands* 3 | vimscript_lasterror-concepts vimscript_lasterror.txt /*vimscript_lasterror-concepts* 4 | vimscript_lasterror-contents vimscript_lasterror.txt /*vimscript_lasterror-contents* 5 | vimscript_lasterror-remarks vimscript_lasterror.txt /*vimscript_lasterror-remarks* 6 | vimscript_lasterror.txt vimscript_lasterror.txt /*vimscript_lasterror.txt* 7 | -------------------------------------------------------------------------------- /doc/vimscript_lasterror.txt: -------------------------------------------------------------------------------- 1 | *vimscript_lasterror.txt* jump to the Vim script's last error. 2 | 3 | Author : rbtnn 4 | LICENSE: MIT license (see LICENSE.txt) 5 | 6 | CONTENTS *vimscript_lasterror-contents* 7 | 8 | Concepts |vimscript_lasterror-concepts| 9 | Commands |vimscript_lasterror-commands| 10 | Remarks |vimscript_lasterror-remarks| 11 | 12 | 13 | 14 | ============================================================================== 15 | Concepts *vimscript_lasterror-concepts* 16 | 17 | * This plugin supports Vim and Neovim. 18 | * This plugin does not provide to customize user-settings. 19 | * This plugin provides only one command. 20 | 21 | 22 | 23 | ============================================================================== 24 | Commands *vimscript_lasterror-commands* 25 | 26 | :VimscriptLastError [-loclist] [-quickfix] [-messages] *:VimscriptLastError* 27 | If arguments are not specified, try jumping to the Vim 28 | script's last error. 29 | If `-loclist` is specified, set Vim script's errors to current 30 | loclist. 31 | If `-quickfix` is specified, set Vim script's errors to 32 | quickfix. 33 | If `-messages` is specified, dump the output of |:messages| to new 34 | window. Typing || under a Vim script's error line in the 35 | window, You can jump to the location. 36 | 37 | 38 | 39 | ============================================================================== 40 | Remarks *vimscript_lasterror-remarks* 41 | 42 | * This plugin find Vim script's errors from output of `:messages`. 43 | * Ignore E384 and E385. 44 | 45 | 46 | 47 | ============================================================================== 48 | vim:tw=78:ts=8:ft=help:norl:noet:fen:fdl=0: 49 | -------------------------------------------------------------------------------- /plugin/vimscript_lasterror.vim: -------------------------------------------------------------------------------- 1 | 2 | let g:loaded_vimscript_lasterror = 1 3 | 4 | command! -nargs=? -complete=customlist,vimscript_lasterror#comp VimscriptLastError :call vimscript_lasterror#exec() 5 | 6 | -------------------------------------------------------------------------------- /vimscript_lasterror.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbtnn/vim-vimscript_lasterror/32c17efbb206bb39aead6bb769adcf635b668b9b/vimscript_lasterror.gif --------------------------------------------------------------------------------