diff options
Diffstat (limited to 'runtime/pack')
-rw-r--r-- | runtime/pack/dist/opt/cfilter/plugin/cfilter.vim | 72 | ||||
-rw-r--r-- | runtime/pack/dist/opt/dvorak/dvorak/disable.vim | 72 | ||||
-rw-r--r-- | runtime/pack/dist/opt/dvorak/dvorak/enable.vim | 77 | ||||
-rw-r--r-- | runtime/pack/dist/opt/dvorak/plugin/dvorak.vim | 16 | ||||
-rw-r--r-- | runtime/pack/dist/opt/editexisting/plugin/editexisting.vim | 118 | ||||
-rw-r--r-- | runtime/pack/dist/opt/justify/plugin/justify.vim | 316 | ||||
-rw-r--r-- | runtime/pack/dist/opt/matchit/autoload/matchit.vim | 777 | ||||
-rw-r--r-- | runtime/pack/dist/opt/matchit/doc/matchit.txt | 397 | ||||
-rw-r--r-- | runtime/pack/dist/opt/matchit/doc/tags | 50 | ||||
-rw-r--r-- | runtime/pack/dist/opt/matchit/plugin/matchit.vim | 98 | ||||
-rw-r--r-- | runtime/pack/dist/opt/shellmenu/plugin/shellmenu.vim | 104 | ||||
-rw-r--r-- | runtime/pack/dist/opt/swapmouse/plugin/swapmouse.vim | 22 | ||||
-rw-r--r-- | runtime/pack/dist/opt/termdebug/plugin/termdebug.vim | 1527 |
13 files changed, 3646 insertions, 0 deletions
diff --git a/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim b/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim new file mode 100644 index 0000000..7a65630 --- /dev/null +++ b/runtime/pack/dist/opt/cfilter/plugin/cfilter.vim @@ -0,0 +1,72 @@ +vim9script + +# cfilter.vim: Plugin to filter entries from a quickfix/location list +# Last Change: Jun 30, 2022 +# Maintainer: Yegappan Lakshmanan (yegappan AT yahoo DOT com) +# Version: 2.0 +# +# Commands to filter the quickfix list: +# :Cfilter[!] /{pat}/ +# Create a new quickfix list from entries matching {pat} in the current +# quickfix list. Both the file name and the text of the entries are +# matched against {pat}. If ! is supplied, then entries not matching +# {pat} are used. The pattern can be optionally enclosed using one of +# the following characters: ', ", /. If the pattern is empty, then the +# last used search pattern is used. +# :Lfilter[!] /{pat}/ +# Same as :Cfilter but operates on the current location list. +# + +def Qf_filter(qf: bool, searchpat: string, bang: string) + var Xgetlist: func + var Xsetlist: func + var cmd: string + var firstchar: string + var lastchar: string + var pat: string + var title: string + var Cond: func + var items: list<any> + + if qf + Xgetlist = function('getqflist') + Xsetlist = function('setqflist') + cmd = ':Cfilter' .. bang + else + Xgetlist = function('getloclist', [0]) + Xsetlist = function('setloclist', [0]) + cmd = ':Lfilter' .. bang + endif + + firstchar = searchpat[0] + lastchar = searchpat[-1 :] + if firstchar == lastchar && + (firstchar == '/' || firstchar == '"' || firstchar == "'") + pat = searchpat[1 : -2] + if pat == '' + # Use the last search pattern + pat = @/ + endif + else + pat = searchpat + endif + + if pat == '' + return + endif + + if bang == '!' + Cond = (_, val) => val.text !~# pat && bufname(val.bufnr) !~# pat + else + Cond = (_, val) => val.text =~# pat || bufname(val.bufnr) =~# pat + endif + + items = filter(Xgetlist(), Cond) + title = cmd .. ' /' .. pat .. '/' + Xsetlist([], ' ', {title: title, items: items}) +enddef + +command! -nargs=+ -bang Cfilter Qf_filter(true, <q-args>, <q-bang>) +command! -nargs=+ -bang Lfilter Qf_filter(false, <q-args>, <q-bang>) + +# vim: shiftwidth=2 sts=2 expandtab diff --git a/runtime/pack/dist/opt/dvorak/dvorak/disable.vim b/runtime/pack/dist/opt/dvorak/dvorak/disable.vim new file mode 100644 index 0000000..1e9b070 --- /dev/null +++ b/runtime/pack/dist/opt/dvorak/dvorak/disable.vim @@ -0,0 +1,72 @@ +" Back to Qwerty keyboard after using Dvorak. + +iunmap a +iunmap b +iunmap c +iunmap d +iunmap e +iunmap f +iunmap g +iunmap h +iunmap i +iunmap j +iunmap k +iunmap l +iunmap m +iunmap n +iunmap o +iunmap p +iunmap q +iunmap r +iunmap s +iunmap t +iunmap u +iunmap v +iunmap w +iunmap x +iunmap y +iunmap z +iunmap ; +iunmap ' +iunmap " +iunmap , +iunmap . +iunmap / +iunmap A +iunmap B +iunmap C +iunmap D +iunmap E +iunmap F +iunmap G +iunmap H +iunmap I +iunmap J +iunmap K +iunmap L +iunmap M +iunmap N +iunmap O +iunmap P +iunmap Q +iunmap R +iunmap S +iunmap T +iunmap U +iunmap V +iunmap W +iunmap X +iunmap Y +iunmap Z +iunmap < +iunmap > +iunmap ? +iunmap : +iunmap [ +iunmap ] +iunmap { +iunmap } +iunmap - +iunmap _ +iunmap = +iunmap + diff --git a/runtime/pack/dist/opt/dvorak/dvorak/enable.vim b/runtime/pack/dist/opt/dvorak/dvorak/enable.vim new file mode 100644 index 0000000..8ff363f --- /dev/null +++ b/runtime/pack/dist/opt/dvorak/dvorak/enable.vim @@ -0,0 +1,77 @@ +" Dvorak keyboard, only in Insert mode. +" +" Change "inoremap" to "map!" to also use in Ex mode. +" Also change disable.vim then: "iunmap" to "unmap!". +" +" You may want to add a list of map's too. + +inoremap a a +inoremap b x +inoremap c j +inoremap d e +inoremap e . +inoremap f u +inoremap g i +inoremap h d +inoremap i c +inoremap j h +inoremap k t +inoremap l n +inoremap m m +inoremap n b +inoremap o r +inoremap p l +inoremap q ' +inoremap r p +inoremap s o +inoremap t y +inoremap u g +inoremap v k +inoremap w , +inoremap x q +inoremap y f +inoremap z ; +inoremap ; s +inoremap ' - +inoremap " _ +inoremap , w +inoremap . v +inoremap / z +inoremap A A +inoremap B X +inoremap C J +inoremap D E +inoremap E > +inoremap F U +inoremap G I +inoremap H D +inoremap I C +inoremap J H +inoremap K T +inoremap L N +inoremap M M +inoremap N B +inoremap O R +inoremap P L +inoremap Q " +inoremap R P +inoremap S O +inoremap T Y +inoremap U G +inoremap V K +inoremap W < +inoremap X Q +inoremap Y F +inoremap Z : +inoremap < W +inoremap > V +inoremap ? Z +inoremap : S +inoremap [ / +inoremap ] = +inoremap { ? +inoremap } + +inoremap - [ +inoremap _ { +inoremap = ] +inoremap + } diff --git a/runtime/pack/dist/opt/dvorak/plugin/dvorak.vim b/runtime/pack/dist/opt/dvorak/plugin/dvorak.vim new file mode 100644 index 0000000..c8d5d5c --- /dev/null +++ b/runtime/pack/dist/opt/dvorak/plugin/dvorak.vim @@ -0,0 +1,16 @@ +" When using a dvorak keyboard this file may be of help to you. +" These mappings have been made by Lawrence Kesteloot <kesteloo@cs.unc.edu>. +" What they do is that the most often used keys, like hjkl, are put in a more +" easy to use position. +" It may take some time to learn using this. + +if exists("g:loaded_dvorak_plugin") + finish +endif +let g:loaded_dvorak_plugin = 1 + +" Key to go into dvorak mode: +map ,d :runtime dvorak/enable.vim<CR> + +" Key to get out of dvorak mode: +map ,q :runtime dvorak/disable.vim<CR> diff --git a/runtime/pack/dist/opt/editexisting/plugin/editexisting.vim b/runtime/pack/dist/opt/editexisting/plugin/editexisting.vim new file mode 100644 index 0000000..5eda290 --- /dev/null +++ b/runtime/pack/dist/opt/editexisting/plugin/editexisting.vim @@ -0,0 +1,118 @@ +" Vim Plugin: Edit the file with an existing Vim if possible +" Maintainer: Bram Moolenaar +" Last Change: 2022 Jun 17 + +" To use add ":packadd! editexisting" in your vimrc file. + +" This plugin serves two purposes: +" 1. On startup, if we were invoked with one file name argument and the file +" is not modified then try to find another Vim instance that is editing +" this file. If there is one then bring it to the foreground and exit. +" 2. When a file is edited and a swap file exists for it, try finding that +" other Vim and bring it to the foreground. Requires Vim 7, because it +" uses the SwapExists autocommand event. + +" Function that finds the Vim instance that is editing "filename" and brings +" it to the foreground. +func s:EditElsewhere(filename) + let fname_esc = substitute(a:filename, "'", "''", "g") + + let servers = serverlist() + while servers != '' + " Get next server name in "servername"; remove it from "servers". + let i = match(servers, "\n") + if i == -1 + let servername = servers + let servers = '' + else + let servername = strpart(servers, 0, i) + let servers = strpart(servers, i + 1) + endif + + " Skip ourselves. + if servername ==? v:servername + continue + endif + + " Check if this server is editing our file. + try + if remote_expr(servername, "bufloaded('" . fname_esc . "')") + " Yes, bring it to the foreground. + if has("win32") + call remote_foreground(servername) + endif + call remote_expr(servername, "foreground()") + + if remote_expr(servername, "exists('*EditExisting')") + " Make sure the file is visible in a window (not hidden). + " If v:swapcommand exists and is set, send it to the server. + if exists("v:swapcommand") + let c = substitute(v:swapcommand, "'", "''", "g") + call remote_expr(servername, "EditExisting('" . fname_esc . "', '" . c . "')") + else + call remote_expr(servername, "EditExisting('" . fname_esc . "', '')") + endif + endif + + if !(has('vim_starting') && has('gui_running') && has('gui_win32')) + " Tell the user what is happening. Not when the GUI is starting + " though, it would result in a message box. + echomsg "File is being edited by " . servername + sleep 2 + endif + return 'q' + endif + catch /^Vim\%((\a\+)\)\=:E241:/ + " Unable to send to this server, ignore it. + endtry + endwhile + return '' +endfunc + +" When the plugin is loaded and there is one file name argument: Find another +" Vim server that is editing this file right now. +if argc() == 1 && !&modified + if s:EditElsewhere(expand("%:p")) == 'q' + quit + endif +endif + +" Setup for handling the situation that an existing swap file is found. +try + au! SwapExists * let v:swapchoice = s:EditElsewhere(expand("<afile>:p")) +catch + " Without SwapExists we don't do anything for ":edit" commands +endtry + +" Function used on the server to make the file visible and possibly execute a +" command. +func! EditExisting(fname, command) + " Get the window number of the file in the current tab page. + let winnr = bufwinnr(a:fname) + if winnr <= 0 + " Not found, look in other tab pages. + let bufnr = bufnr(a:fname) + for i in range(tabpagenr('$')) + if index(tabpagebuflist(i + 1), bufnr) >= 0 + " Make this tab page the current one and find the window number. + exe 'tabnext ' . (i + 1) + let winnr = bufwinnr(a:fname) + break + endif + endfor + endif + + if winnr > 0 + exe winnr . "wincmd w" + elseif exists('*fnameescape') + exe "split " . fnameescape(a:fname) + else + exe "split " . escape(a:fname, " \t\n*?[{`$\\%#'\"|!<") + endif + + if a:command != '' + exe "normal! " . a:command + endif + + redraw +endfunc diff --git a/runtime/pack/dist/opt/justify/plugin/justify.vim b/runtime/pack/dist/opt/justify/plugin/justify.vim new file mode 100644 index 0000000..4ef3bf9 --- /dev/null +++ b/runtime/pack/dist/opt/justify/plugin/justify.vim @@ -0,0 +1,316 @@ +" Function to left and right align text. +" +" Written by: Preben "Peppe" Guldberg <c928400@student.dtu.dk> +" Created: 980806 14:13 (or around that time anyway) +" Revised: 001103 00:36 (See "Revisions" below) + + +" function Justify( [ textwidth [, maxspaces [, indent] ] ] ) +" +" Justify() will left and right align a line by filling in an +" appropriate amount of spaces. Extra spaces are added to existing +" spaces starting from the right side of the line. As an example, the +" following documentation has been justified. +" +" The function takes the following arguments: + +" textwidth argument +" ------------------ +" If not specified, the value of the 'textwidth' option is used. If +" 'textwidth' is zero a value of 80 is used. +" +" Additionally the arguments 'tw' and '' are accepted. The value of +" 'textwidth' will be used. These are handy, if you just want to specify +" the maxspaces argument. + +" maxspaces argument +" ------------------ +" If specified, alignment will only be done, if the longest space run +" after alignment is no longer than maxspaces. +" +" An argument of '' is accepted, should the user like to specify all +" arguments. +" +" To aid user defined commands, negative values are accepted aswell. +" Using a negative value specifies the default behaviour: any length of +" space runs will be used to justify the text. + +" indent argument +" --------------- +" This argument specifies how a line should be indented. The default is +" to keep the current indentation. +" +" Negative values: Keep current amount of leading whitespace. +" Positive values: Indent all lines with leading whitespace using this +" amount of whitespace. +" +" Note that the value 0, needs to be quoted as a string. This value +" leads to a left flushed text. +" +" Additionally units of 'shiftwidth'/'sw' and 'tabstop'/'ts' may be +" added. In this case, if the value of indent is positive, the amount of +" whitespace to be added will be multiplied by the value of the +" 'shiftwidth' and 'tabstop' settings. If these units are used, the +" argument must be given as a string, eg. Justify('','','2sw'). +" +" If the values of 'sw' or 'tw' are negative, they are treated as if +" they were 0, which means that the text is flushed left. There is no +" check if a negative number prefix is used to change the sign of a +" negative 'sw' or 'ts' value. +" +" As with the other arguments, '' may be used to get the default +" behaviour. + + +" Notes: +" +" If the line, adjusted for space runs and leading/trailing whitespace, +" is wider than the used textwidth, the line will be left untouched (no +" whitespace removed). This should be equivalent to the behaviour of +" :left, :right and :center. +" +" If the resulting line is shorter than the used textwidth it is left +" untouched. +" +" All space runs in the line are truncated before the alignment is +" carried out. +" +" If you have set 'noexpandtab', :retab! is used to replace space runs +" with whitespace using the value of 'tabstop'. This should be +" conformant with :left, :right and :center. +" +" If joinspaces is set, an extra space is added after '.', '?' and '!'. +" If 'cpooptions' include 'j', extra space is only added after '.'. +" (This may on occasion conflict with maxspaces.) + + +" Related mappings: +" +" Mappings that will align text using the current text width, using at +" most four spaces in a space run and keeping current indentation. +nmap _j :%call Justify('tw',4)<CR> +vmap _j :call Justify('tw',4)<CR> +" +" Mappings that will remove space runs and format lines (might be useful +" prior to aligning the text). +nmap ,gq :%s/\s\+/ /g<CR>gq1G +vmap ,gq :s/\s\+/ /g<CR>gvgq + + +" User defined command: +" +" The following is an ex command that works as a shortcut to the Justify +" function. Arguments to Justify() can be added after the command. +com! -range -nargs=* Justify <line1>,<line2>call Justify(<f-args>) +" +" The following commands are all equivalent: +" +" 1. Simplest use of Justify(): +" :call Justify() +" :Justify +" +" 2. The _j mapping above via the ex command: +" :%Justify tw 4 +" +" 3. Justify visualised text at 72nd column while indenting all +" previously indented text two shiftwidths +" :'<,'>call Justify(72,'','2sw') +" :'<,'>Justify 72 -1 2sw +" +" This documentation has been justified using the following command: +":se et|kz|1;/^" function Justify(/+,'z-g/^" /s/^" //|call Justify(70,3)|s/^/" / + +" Revisions: +" 001103: If 'joinspaces' was set, calculations could be wrong. +" Tabs at start of line could also lead to errors. +" Use setline() instead of "exec 's/foo/bar/' - safer. +" Cleaned up the code a bit. +" +" Todo: Convert maps to the new script specific form + +" Error function +function! Justify_error(message) + echohl Error + echo "Justify([tw, [maxspaces [, indent]]]): " . a:message + echohl None +endfunction + + +" Now for the real thing +function! Justify(...) range + + if a:0 > 3 + call Justify_error("Too many arguments (max 3)") + return 1 + endif + + " Set textwidth (accept 'tw' and '' as arguments) + if a:0 >= 1 + if a:1 =~ '^\(tw\)\=$' + let tw = &tw + elseif a:1 =~ '^\d\+$' + let tw = a:1 + else + call Justify_error("tw must be a number (>0), '' or 'tw'") + return 2 + endif + else + let tw = &tw + endif + if tw == 0 + let tw = 80 + endif + + " Set maximum number of spaces between WORDs + if a:0 >= 2 + if a:2 == '' + let maxspaces = tw + elseif a:2 =~ '^-\d\+$' + let maxspaces = tw + elseif a:2 =~ '^\d\+$' + let maxspaces = a:2 + else + call Justify_error("maxspaces must be a number or ''") + return 3 + endif + else + let maxspaces = tw + endif + if maxspaces <= 1 + call Justify_error("maxspaces should be larger than 1") + return 4 + endif + + " Set the indentation style (accept sw and ts units) + let indent_fix = '' + if a:0 >= 3 + if (a:3 == '') || a:3 =~ '^-[1-9]\d*\(shiftwidth\|sw\|tabstop\|ts\)\=$' + let indent = -1 + elseif a:3 =~ '^-\=0\(shiftwidth\|sw\|tabstop\|ts\)\=$' + let indent = 0 + elseif a:3 =~ '^\d\+\(shiftwidth\|sw\|tabstop\|ts\)\=$' + let indent = substitute(a:3, '\D', '', 'g') + elseif a:3 =~ '^\(shiftwidth\|sw\|tabstop\|ts\)$' + let indent = 1 + else + call Justify_error("indent: a number with 'sw'/'ts' unit") + return 5 + endif + if indent >= 0 + while indent > 0 + let indent_fix = indent_fix . ' ' + let indent = indent - 1 + endwhile + let indent_sw = 0 + if a:3 =~ '\(shiftwidth\|sw\)' + let indent_sw = &sw + elseif a:3 =~ '\(tabstop\|ts\)' + let indent_sw = &ts + endif + let indent_fix2 = '' + while indent_sw > 0 + let indent_fix2 = indent_fix2 . indent_fix + let indent_sw = indent_sw - 1 + endwhile + let indent_fix = indent_fix2 + endif + else + let indent = -1 + endif + + " Avoid substitution reports + let save_report = &report + set report=1000000 + + " Check 'joinspaces' and 'cpo' + if &js == 1 + if &cpo =~ 'j' + let join_str = '\(\. \)' + else + let join_str = '\([.!?!] \)' + endif + endif + + let cur = a:firstline + while cur <= a:lastline + + let str_orig = getline(cur) + let save_et = &et + set et + exec cur . "retab" + let &et = save_et + let str = getline(cur) + + let indent_str = indent_fix + let indent_n = strlen(indent_str) + " Shall we remember the current indentation + if indent < 0 + let indent_orig = matchstr(str_orig, '^\s*') + if strlen(indent_orig) > 0 + let indent_str = indent_orig + let indent_n = strlen(matchstr(str, '^\s*')) + endif + endif + + " Trim trailing, leading and running whitespace + let str = substitute(str, '\s\+$', '', '') + let str = substitute(str, '^\s\+', '', '') + let str = substitute(str, '\s\+', ' ', 'g') + let str_n = strdisplaywidth(str) + + " Possible addition of space after punctuation + if exists("join_str") + let str = substitute(str, join_str, '\1 ', 'g') + endif + let join_n = strdisplaywidth(str) - str_n + + " Can extraspaces be added? + " Note that str_n may be less than strlen(str) [joinspaces above] + if strdisplaywidth(str) <= tw - indent_n && str_n > 0 + " How many spaces should be added + let s_add = tw - str_n - indent_n - join_n + let s_nr = strlen(substitute(str, '\S', '', 'g') ) - join_n + let s_dup = s_add / s_nr + let s_mod = s_add % s_nr + + " Test if the changed line fits with tw + if 0 <= (str_n + (maxspaces - 1)*s_nr + indent_n) - tw + + " Duplicate spaces + while s_dup > 0 + let str = substitute(str, '\( \+\)', ' \1', 'g') + let s_dup = s_dup - 1 + endwhile + + " Add extra spaces from the end + while s_mod > 0 + let str = substitute(str, '\(\(\s\+\S\+\)\{' . s_mod . '}\)$', ' \1', '') + let s_mod = s_mod - 1 + endwhile + + " Indent the line + if indent_n > 0 + let str = substitute(str, '^', indent_str, '' ) + endif + + " Replace the line + call setline(cur, str) + + " Convert to whitespace + if &et == 0 + exec cur . 'retab!' + endif + + endif " Change of line + endif " Possible change + + let cur = cur + 1 + endwhile + + norm ^ + + let &report = save_report + +endfunction + +" EOF vim: tw=78 ts=8 sw=4 sts=4 noet ai diff --git a/runtime/pack/dist/opt/matchit/autoload/matchit.vim b/runtime/pack/dist/opt/matchit/autoload/matchit.vim new file mode 100644 index 0000000..eafb7c0 --- /dev/null +++ b/runtime/pack/dist/opt/matchit/autoload/matchit.vim @@ -0,0 +1,777 @@ +" matchit.vim: (global plugin) Extended "%" matching +" autload script of matchit plugin, see ../plugin/matchit.vim +" Last Change: Jun 10, 2021 + +" Neovim does not support scriptversion +if has("vimscript-4") + scriptversion 4 +endif + +let s:last_mps = "" +let s:last_words = ":" +let s:patBR = "" + +let s:save_cpo = &cpo +set cpo&vim + +" Auto-complete mappings: (not yet "ready for prime time") +" TODO Read :help write-plugin for the "right" way to let the user +" specify a key binding. +" let g:match_auto = '<C-]>' +" let g:match_autoCR = '<C-CR>' +" if exists("g:match_auto") +" execute "inoremap " . g:match_auto . ' x<Esc>"=<SID>Autocomplete()<CR>Pls' +" endif +" if exists("g:match_autoCR") +" execute "inoremap " . g:match_autoCR . ' <CR><C-R>=<SID>Autocomplete()<CR>' +" endif +" if exists("g:match_gthhoh") +" execute "inoremap " . g:match_gthhoh . ' <C-O>:call <SID>Gthhoh()<CR>' +" endif " gthhoh = "Get the heck out of here!" + +let s:notslash = '\\\@1<!\%(\\\\\)*' + +function s:RestoreOptions() + " In s:CleanUp(), :execute "set" restore_options . + let restore_options = "" + if get(b:, 'match_ignorecase', &ic) != &ic + let restore_options ..= (&ic ? " " : " no") .. "ignorecase" + let &ignorecase = b:match_ignorecase + endif + if &ve != '' + let restore_options = " ve=" .. &ve .. restore_options + set ve= + endif + return restore_options +endfunction + +function matchit#Match_wrapper(word, forward, mode) range + let restore_options = s:RestoreOptions() + " In s:CleanUp(), we may need to check whether the cursor moved forward. + let startpos = [line("."), col(".")] + " if a count has been applied, use the default [count]% mode (see :h N%) + if v:count + exe "normal! " .. v:count .. "%" + return s:CleanUp(restore_options, a:mode, startpos) + end + if a:mode =~# "v" && mode(1) =~# 'ni' + exe "norm! gv" + elseif a:mode == "o" && mode(1) !~# '[vV]' + exe "norm! v" + " If this function was called from Visual mode, make sure that the cursor + " is at the correct end of the Visual range: + elseif a:mode == "v" + execute "normal! gv\<Esc>" + let startpos = [line("."), col(".")] + endif + + " First step: if not already done, set the script variables + " s:do_BR flag for whether there are backrefs + " s:pat parsed version of b:match_words + " s:all regexp based on s:pat and the default groups + if !exists("b:match_words") || b:match_words == "" + let match_words = "" + elseif b:match_words =~ ":" + let match_words = b:match_words + else + " Allow b:match_words = "GetVimMatchWords()" . + execute "let match_words =" b:match_words + endif +" Thanks to Preben "Peppe" Guldberg and Bram Moolenaar for this suggestion! + if (match_words != s:last_words) || (&mps != s:last_mps) + \ || exists("b:match_debug") + let s:last_mps = &mps + " quote the special chars in 'matchpairs', replace [,:] with \| and then + " append the builtin pairs (/*, */, #if, #ifdef, #ifndef, #else, #elif, + " #endif) + let default = escape(&mps, '[$^.*~\\/?]') .. (strlen(&mps) ? "," : "") .. + \ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' + " s:all = pattern with all the keywords + let match_words = match_words .. (strlen(match_words) ? "," : "") .. default + let s:last_words = match_words + if match_words !~ s:notslash .. '\\\d' + let s:do_BR = 0 + let s:pat = match_words + else + let s:do_BR = 1 + let s:pat = s:ParseWords(match_words) + endif + let s:all = substitute(s:pat, s:notslash .. '\zs[,:]\+', '\\|', 'g') + " Just in case there are too many '\(...)' groups inside the pattern, make + " sure to use \%(...) groups, so that error E872 can be avoided + let s:all = substitute(s:all, '\\(', '\\%(', 'g') + let s:all = '\%(' .. s:all .. '\)' + if exists("b:match_debug") + let b:match_pat = s:pat + endif + " Reconstruct the version with unresolved backrefs. + let s:patBR = substitute(match_words .. ',', + \ s:notslash .. '\zs[,:]*,[,:]*', ',', 'g') + let s:patBR = substitute(s:patBR, s:notslash .. '\zs:\{2,}', ':', 'g') + endif + + " Second step: set the following local variables: + " matchline = line on which the cursor started + " curcol = number of characters before match + " prefix = regexp for start of line to start of match + " suffix = regexp for end of match to end of line + " Require match to end on or after the cursor and prefer it to + " start on or before the cursor. + let matchline = getline(startpos[0]) + if a:word != '' + " word given + if a:word !~ s:all + echohl WarningMsg|echo 'Missing rule for word:"'.a:word.'"'|echohl NONE + return s:CleanUp(restore_options, a:mode, startpos) + endif + let matchline = a:word + let curcol = 0 + let prefix = '^\%(' + let suffix = '\)$' + " Now the case when "word" is not given + else " Find the match that ends on or after the cursor and set curcol. + let regexp = s:Wholematch(matchline, s:all, startpos[1]-1) + let curcol = match(matchline, regexp) + " If there is no match, give up. + if curcol == -1 + " Make sure macros abort properly + "exe "norm! \<esc>" + call feedkeys("\e", 'tni') + return s:CleanUp(restore_options, a:mode, startpos) + endif + let endcol = matchend(matchline, regexp) + let suf = strlen(matchline) - endcol + let prefix = (curcol ? '^.*\%' .. (curcol + 1) .. 'c\%(' : '^\%(') + let suffix = (suf ? '\)\%' .. (endcol + 1) .. 'c.*$' : '\)$') + endif + if exists("b:match_debug") + let b:match_match = matchstr(matchline, regexp) + let b:match_col = curcol+1 + endif + + " Third step: Find the group and single word that match, and the original + " (backref) versions of these. Then, resolve the backrefs. + " Set the following local variable: + " group = colon-separated list of patterns, one of which matches + " = ini:mid:fin or ini:fin + " + " Now, set group and groupBR to the matching group: 'if:endif' or + " 'while:endwhile' or whatever. A bit of a kluge: s:Choose() returns + " group . "," . groupBR, and we pick it apart. + let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, s:patBR) + let i = matchend(group, s:notslash .. ",") + let groupBR = strpart(group, i) + let group = strpart(group, 0, i-1) + " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix + if s:do_BR " Do the hard part: resolve those backrefs! + let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline) + endif + if exists("b:match_debug") + let b:match_wholeBR = groupBR + let i = matchend(groupBR, s:notslash .. ":") + let b:match_iniBR = strpart(groupBR, 0, i-1) + endif + + " Fourth step: Set the arguments for searchpair(). + let i = matchend(group, s:notslash .. ":") + let j = matchend(group, '.*' .. s:notslash .. ":") + let ini = strpart(group, 0, i-1) + let mid = substitute(strpart(group, i,j-i-1), s:notslash .. '\zs:', '\\|', 'g') + let fin = strpart(group, j) + "Un-escape the remaining , and : characters. + let ini = substitute(ini, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g') + let mid = substitute(mid, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g') + let fin = substitute(fin, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g') + " searchpair() requires that these patterns avoid \(\) groups. + let ini = substitute(ini, s:notslash .. '\zs\\(', '\\%(', 'g') + let mid = substitute(mid, s:notslash .. '\zs\\(', '\\%(', 'g') + let fin = substitute(fin, s:notslash .. '\zs\\(', '\\%(', 'g') + " Set mid. This is optimized for readability, not micro-efficiency! + if a:forward && matchline =~ prefix .. fin .. suffix + \ || !a:forward && matchline =~ prefix .. ini .. suffix + let mid = "" + endif + " Set flag. This is optimized for readability, not micro-efficiency! + if a:forward && matchline =~ prefix .. fin .. suffix + \ || !a:forward && matchline !~ prefix .. ini .. suffix + let flag = "bW" + else + let flag = "W" + endif + " Set skip. + if exists("b:match_skip") + let skip = b:match_skip + elseif exists("b:match_comment") " backwards compatibility and testing! + let skip = "r:" .. b:match_comment + else + let skip = 's:comment\|string' + endif + let skip = s:ParseSkip(skip) + if exists("b:match_debug") + let b:match_ini = ini + let b:match_tail = (strlen(mid) ? mid .. '\|' : '') .. fin + endif + + " Fifth step: actually start moving the cursor and call searchpair(). + " Later, :execute restore_cursor to get to the original screen. + let view = winsaveview() + call cursor(0, curcol + 1) + if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) + let skip = "0" + else + execute "if " .. skip .. "| let skip = '0' | endif" + endif + let sp_return = searchpair(ini, mid, fin, flag, skip) + if &selection isnot# 'inclusive' && a:mode == 'v' + " move cursor one pos to the right, because selection is not inclusive + " add virtualedit=onemore, to make it work even when the match ends the + " line + if !(col('.') < col('$')-1) + let eolmark=1 " flag to set a mark on eol (since we cannot move there) + endif + norm! l + endif + let final_position = "call cursor(" .. line(".") .. "," .. col(".") .. ")" + " Restore cursor position and original screen. + call winrestview(view) + normal! m' + if sp_return > 0 + execute final_position + endif + if exists('eolmark') && eolmark + call setpos("''", [0, line('.'), col('$'), 0]) " set mark on the eol + endif + return s:CleanUp(restore_options, a:mode, startpos, mid .. '\|' .. fin) +endfun + +" Restore options and do some special handling for Operator-pending mode. +" The optional argument is the tail of the matching group. +fun! s:CleanUp(options, mode, startpos, ...) + if strlen(a:options) + execute "set" a:options + endif + " Open folds, if appropriate. + if a:mode != "o" + if &foldopen =~ "percent" + normal! zv + endif + " In Operator-pending mode, we want to include the whole match + " (for example, d%). + " This is only a problem if we end up moving in the forward direction. + elseif (a:startpos[0] < line(".")) || + \ (a:startpos[0] == line(".") && a:startpos[1] < col(".")) + if a:0 + " Check whether the match is a single character. If not, move to the + " end of the match. + let matchline = getline(".") + let currcol = col(".") + let regexp = s:Wholematch(matchline, a:1, currcol-1) + let endcol = matchend(matchline, regexp) + if endcol > currcol " This is NOT off by one! + call cursor(0, endcol) + endif + endif " a:0 + endif " a:mode != "o" && etc. + return 0 +endfun + +" Example (simplified HTML patterns): if +" a:groupBR = '<\(\k\+\)>:</\1>' +" a:prefix = '^.\{3}\(' +" a:group = '<\(\k\+\)>:</\(\k\+\)>' +" a:suffix = '\).\{2}$' +" a:matchline = "123<tag>12" or "123</tag>12" +" then extract "tag" from a:matchline and return "<tag>:</tag>" . +fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline) + if a:matchline !~ a:prefix .. + \ substitute(a:group, s:notslash .. '\zs:', '\\|', 'g') .. a:suffix + return a:group + endif + let i = matchend(a:groupBR, s:notslash .. ':') + let ini = strpart(a:groupBR, 0, i-1) + let tailBR = strpart(a:groupBR, i) + let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix, + \ a:groupBR) + let i = matchend(word, s:notslash .. ":") + let wordBR = strpart(word, i) + let word = strpart(word, 0, i-1) + " Now, a:matchline =~ a:prefix . word . a:suffix + if wordBR != ini + let table = s:Resolve(ini, wordBR, "table") + else + let table = "" + let d = 0 + while d < 10 + if tailBR =~ s:notslash .. '\\' .. d + let table = table .. d + else + let table = table .. "-" + endif + let d = d + 1 + endwhile + endif + let d = 9 + while d + if table[d] != "-" + let backref = substitute(a:matchline, a:prefix .. word .. a:suffix, + \ '\' .. table[d], "") + " Are there any other characters that should be escaped? + let backref = escape(backref, '*,:') + execute s:Ref(ini, d, "start", "len") + let ini = strpart(ini, 0, start) .. backref .. strpart(ini, start+len) + let tailBR = substitute(tailBR, s:notslash .. '\zs\\' .. d, + \ escape(backref, '\\&'), 'g') + endif + let d = d-1 + endwhile + if exists("b:match_debug") + if s:do_BR + let b:match_table = table + let b:match_word = word + else + let b:match_table = "" + let b:match_word = "" + endif + endif + return ini .. ":" .. tailBR +endfun + +" Input a comma-separated list of groups with backrefs, such as +" a:groups = '\(foo\):end\1,\(bar\):end\1' +" and return a comma-separated list of groups with backrefs replaced: +" return '\(foo\):end\(foo\),\(bar\):end\(bar\)' +fun! s:ParseWords(groups) + let groups = substitute(a:groups .. ",", s:notslash .. '\zs[,:]*,[,:]*', ',', 'g') + let groups = substitute(groups, s:notslash .. '\zs:\{2,}', ':', 'g') + let parsed = "" + while groups =~ '[^,:]' + let i = matchend(groups, s:notslash .. ':') + let j = matchend(groups, s:notslash .. ',') + let ini = strpart(groups, 0, i-1) + let tail = strpart(groups, i, j-i-1) .. ":" + let groups = strpart(groups, j) + let parsed = parsed .. ini + let i = matchend(tail, s:notslash .. ':') + while i != -1 + " In 'if:else:endif', ini='if' and word='else' and then word='endif'. + let word = strpart(tail, 0, i-1) + let tail = strpart(tail, i) + let i = matchend(tail, s:notslash .. ':') + let parsed = parsed .. ":" .. s:Resolve(ini, word, "word") + endwhile " Now, tail has been used up. + let parsed = parsed .. "," + endwhile " groups =~ '[^,:]' + let parsed = substitute(parsed, ',$', '', '') + return parsed +endfun + +" TODO I think this can be simplified and/or made more efficient. +" TODO What should I do if a:start is out of range? +" Return a regexp that matches all of a:string, such that +" matchstr(a:string, regexp) represents the match for a:pat that starts +" as close to a:start as possible, before being preferred to after, and +" ends after a:start . +" Usage: +" let regexp = s:Wholematch(getline("."), 'foo\|bar', col(".")-1) +" let i = match(getline("."), regexp) +" let j = matchend(getline("."), regexp) +" let match = matchstr(getline("."), regexp) +fun! s:Wholematch(string, pat, start) + let group = '\%(' .. a:pat .. '\)' + let prefix = (a:start ? '\(^.*\%<' .. (a:start + 2) .. 'c\)\zs' : '^') + let len = strlen(a:string) + let suffix = (a:start+1 < len ? '\(\%>' .. (a:start+1) .. 'c.*$\)\@=' : '$') + if a:string !~ prefix .. group .. suffix + let prefix = '' + endif + return prefix .. group .. suffix +endfun + +" No extra arguments: s:Ref(string, d) will +" find the d'th occurrence of '\(' and return it, along with everything up +" to and including the matching '\)'. +" One argument: s:Ref(string, d, "start") returns the index of the start +" of the d'th '\(' and any other argument returns the length of the group. +" Two arguments: s:Ref(string, d, "foo", "bar") returns a string to be +" executed, having the effect of +" :let foo = s:Ref(string, d, "start") +" :let bar = s:Ref(string, d, "len") +fun! s:Ref(string, d, ...) + let len = strlen(a:string) + if a:d == 0 + let start = 0 + else + let cnt = a:d + let match = a:string + while cnt + let cnt = cnt - 1 + let index = matchend(match, s:notslash .. '\\(') + if index == -1 + return "" + endif + let match = strpart(match, index) + endwhile + let start = len - strlen(match) + if a:0 == 1 && a:1 == "start" + return start - 2 + endif + let cnt = 1 + while cnt + let index = matchend(match, s:notslash .. '\\(\|\\)') - 1 + if index == -2 + return "" + endif + " Increment if an open, decrement if a ')': + let cnt = cnt + (match[index]=="(" ? 1 : -1) " ')' + let match = strpart(match, index+1) + endwhile + let start = start - 2 + let len = len - start - strlen(match) + endif + if a:0 == 1 + return len + elseif a:0 == 2 + return "let " .. a:1 .. "=" .. start .. "| let " .. a:2 .. "=" .. len + else + return strpart(a:string, start, len) + endif +endfun + +" Count the number of disjoint copies of pattern in string. +" If the pattern is a literal string and contains no '0' or '1' characters +" then s:Count(string, pattern, '0', '1') should be faster than +" s:Count(string, pattern). +fun! s:Count(string, pattern, ...) + let pat = escape(a:pattern, '\\') + if a:0 > 1 + let foo = substitute(a:string, '[^' .. a:pattern .. ']', "a:1", "g") + let foo = substitute(a:string, pat, a:2, "g") + let foo = substitute(foo, '[^' .. a:2 .. ']', "", "g") + return strlen(foo) + endif + let result = 0 + let foo = a:string + let index = matchend(foo, pat) + while index != -1 + let result = result + 1 + let foo = strpart(foo, index) + let index = matchend(foo, pat) + endwhile + return result +endfun + +" s:Resolve('\(a\)\(b\)', '\(c\)\2\1\1\2') should return table.word, where +" word = '\(c\)\(b\)\(a\)\3\2' and table = '-32-------'. That is, the first +" '\1' in target is replaced by '\(a\)' in word, table[1] = 3, and this +" indicates that all other instances of '\1' in target are to be replaced +" by '\3'. The hard part is dealing with nesting... +" Note that ":" is an illegal character for source and target, +" unless it is preceded by "\". +fun! s:Resolve(source, target, output) + let word = a:target + let i = matchend(word, s:notslash .. '\\\d') - 1 + let table = "----------" + while i != -2 " There are back references to be replaced. + let d = word[i] + let backref = s:Ref(a:source, d) + " The idea is to replace '\d' with backref. Before we do this, + " replace any \(\) groups in backref with :1, :2, ... if they + " correspond to the first, second, ... group already inserted + " into backref. Later, replace :1 with \1 and so on. The group + " number w+b within backref corresponds to the group number + " s within a:source. + " w = number of '\(' in word before the current one + let w = s:Count( + \ substitute(strpart(word, 0, i-1), '\\\\', '', 'g'), '\(', '1') + let b = 1 " number of the current '\(' in backref + let s = d " number of the current '\(' in a:source + while b <= s:Count(substitute(backref, '\\\\', '', 'g'), '\(', '1') + \ && s < 10 + if table[s] == "-" + if w + b < 10 + " let table[s] = w + b + let table = strpart(table, 0, s) .. (w+b) .. strpart(table, s+1) + endif + let b = b + 1 + let s = s + 1 + else + execute s:Ref(backref, b, "start", "len") + let ref = strpart(backref, start, len) + let backref = strpart(backref, 0, start) .. ":" .. table[s] + \ .. strpart(backref, start+len) + let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1') + endif + endwhile + let word = strpart(word, 0, i-1) .. backref .. strpart(word, i+1) + let i = matchend(word, s:notslash .. '\\\d') - 1 + endwhile + let word = substitute(word, s:notslash .. '\zs:', '\\', 'g') + if a:output == "table" + return table + elseif a:output == "word" + return word + else + return table .. word + endif +endfun + +" Assume a:comma = ",". Then the format for a:patterns and a:1 is +" a:patterns = "<pat1>,<pat2>,..." +" a:1 = "<alt1>,<alt2>,..." +" If <patn> is the first pattern that matches a:string then return <patn> +" if no optional arguments are given; return <patn>,<altn> if a:1 is given. +fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...) + let tail = (a:patterns =~ a:comma .. "$" ? a:patterns : a:patterns .. a:comma) + let i = matchend(tail, s:notslash .. a:comma) + if a:0 + let alttail = (a:1 =~ a:comma .. "$" ? a:1 : a:1 .. a:comma) + let j = matchend(alttail, s:notslash .. a:comma) + endif + let current = strpart(tail, 0, i-1) + if a:branch == "" + let currpat = current + else + let currpat = substitute(current, s:notslash .. a:branch, '\\|', 'g') + endif + while a:string !~ a:prefix .. currpat .. a:suffix + let tail = strpart(tail, i) + let i = matchend(tail, s:notslash .. a:comma) + if i == -1 + return -1 + endif + let current = strpart(tail, 0, i-1) + if a:branch == "" + let currpat = current + else + let currpat = substitute(current, s:notslash .. a:branch, '\\|', 'g') + endif + if a:0 + let alttail = strpart(alttail, j) + let j = matchend(alttail, s:notslash .. a:comma) + endif + endwhile + if a:0 + let current = current .. a:comma .. strpart(alttail, 0, j-1) + endif + return current +endfun + +fun! matchit#Match_debug() + let b:match_debug = 1 " Save debugging information. + " pat = all of b:match_words with backrefs parsed + amenu &Matchit.&pat :echo b:match_pat<CR> + " match = bit of text that is recognized as a match + amenu &Matchit.&match :echo b:match_match<CR> + " curcol = cursor column of the start of the matching text + amenu &Matchit.&curcol :echo b:match_col<CR> + " wholeBR = matching group, original version + amenu &Matchit.wh&oleBR :echo b:match_wholeBR<CR> + " iniBR = 'if' piece, original version + amenu &Matchit.ini&BR :echo b:match_iniBR<CR> + " ini = 'if' piece, with all backrefs resolved from match + amenu &Matchit.&ini :echo b:match_ini<CR> + " tail = 'else\|endif' piece, with all backrefs resolved from match + amenu &Matchit.&tail :echo b:match_tail<CR> + " fin = 'endif' piece, with all backrefs resolved from match + amenu &Matchit.&word :echo b:match_word<CR> + " '\'.d in ini refers to the same thing as '\'.table[d] in word. + amenu &Matchit.t&able :echo '0:' .. b:match_table .. ':9'<CR> +endfun + +" Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW" +" or the nearest unmatched "</tag>" or "endif" or ")" if a:spflag == "W". +" Return a "mark" for the original position, so that +" let m = MultiMatch("bW", "n") ... call winrestview(m) +" will return to the original position. If there is a problem, do not +" move the cursor and return {}, unless a count is given, in which case +" go up or down as many levels as possible and again return {}. +" TODO This relies on the same patterns as % matching. It might be a good +" idea to give it its own matching patterns. +fun! matchit#MultiMatch(spflag, mode) + let restore_options = s:RestoreOptions() + let startpos = [line("."), col(".")] + " save v:count1 variable, might be reset from the restore_cursor command + let level = v:count1 + if a:mode == "o" && mode(1) !~# '[vV]' + exe "norm! v" + endif + + " First step: if not already done, set the script variables + " s:do_BR flag for whether there are backrefs + " s:pat parsed version of b:match_words + " s:all regexp based on s:pat and the default groups + " This part is copied and slightly modified from matchit#Match_wrapper(). + if !exists("b:match_words") || b:match_words == "" + let match_words = "" + " Allow b:match_words = "GetVimMatchWords()" . + elseif b:match_words =~ ":" + let match_words = b:match_words + else + execute "let match_words =" b:match_words + endif + if (match_words != s:last_words) || (&mps != s:last_mps) || + \ exists("b:match_debug") + let default = escape(&mps, '[$^.*~\\/?]') .. (strlen(&mps) ? "," : "") .. + \ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' + let s:last_mps = &mps + let match_words = match_words .. (strlen(match_words) ? "," : "") .. default + let s:last_words = match_words + if match_words !~ s:notslash .. '\\\d' + let s:do_BR = 0 + let s:pat = match_words + else + let s:do_BR = 1 + let s:pat = s:ParseWords(match_words) + endif + let s:all = '\%(' .. substitute(s:pat, '[,:]\+', '\\|', 'g') .. '\)' + if exists("b:match_debug") + let b:match_pat = s:pat + endif + " Reconstruct the version with unresolved backrefs. + let s:patBR = substitute(match_words .. ',', + \ s:notslash .. '\zs[,:]*,[,:]*', ',', 'g') + let s:patBR = substitute(s:patBR, s:notslash .. '\zs:\{2,}', ':', 'g') + endif + + " Second step: figure out the patterns for searchpair() + " and save the screen, cursor position, and 'ignorecase'. + " - TODO: A lot of this is copied from matchit#Match_wrapper(). + " - maybe even more functionality should be split off + " - into separate functions! + let openlist = split(s:pat .. ',', s:notslash .. '\zs:.\{-}' .. s:notslash .. ',') + let midclolist = split(',' .. s:pat, s:notslash .. '\zs,.\{-}' .. s:notslash .. ':') + call map(midclolist, {-> split(v:val, s:notslash .. ':')}) + let closelist = [] + let middlelist = [] + call map(midclolist, {i,v -> [extend(closelist, v[-1 : -1]), + \ extend(middlelist, v[0 : -2])]}) + call map(openlist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v}) + call map(middlelist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v}) + call map(closelist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v}) + let open = join(openlist, ',') + let middle = join(middlelist, ',') + let close = join(closelist, ',') + if exists("b:match_skip") + let skip = b:match_skip + elseif exists("b:match_comment") " backwards compatibility and testing! + let skip = "r:" .. b:match_comment + else + let skip = 's:comment\|string' + endif + let skip = s:ParseSkip(skip) + let view = winsaveview() + + " Third step: call searchpair(). + " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'. + let openpat = substitute(open, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g') + let openpat = substitute(openpat, ',', '\\|', 'g') + let closepat = substitute(close, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g') + let closepat = substitute(closepat, ',', '\\|', 'g') + let middlepat = substitute(middle, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g') + let middlepat = substitute(middlepat, ',', '\\|', 'g') + + if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) + let skip = '0' + else + try + execute "if " .. skip .. "| let skip = '0' | endif" + catch /^Vim\%((\a\+)\)\=:E363/ + " We won't find anything, so skip searching, should keep Vim responsive. + return {} + endtry + endif + mark ' + while level + if searchpair(openpat, middlepat, closepat, a:spflag, skip) < 1 + call s:CleanUp(restore_options, a:mode, startpos) + return {} + endif + let level = level - 1 + endwhile + + " Restore options and return a string to restore the original position. + call s:CleanUp(restore_options, a:mode, startpos) + return view +endfun + +" Search backwards for "if" or "while" or "<tag>" or ... +" and return "endif" or "endwhile" or "</tag>" or ... . +" For now, this uses b:match_words and the same script variables +" as matchit#Match_wrapper() . Later, it may get its own patterns, +" either from a buffer variable or passed as arguments. +" fun! s:Autocomplete() +" echo "autocomplete not yet implemented :-(" +" if !exists("b:match_words") || b:match_words == "" +" return "" +" end +" let startpos = matchit#MultiMatch("bW") +" +" if startpos == "" +" return "" +" endif +" " - TODO: figure out whether 'if' or '<tag>' matched, and construct +" " - the appropriate closing. +" let matchline = getline(".") +" let curcol = col(".") - 1 +" " - TODO: Change the s:all argument if there is a new set of match pats. +" let regexp = s:Wholematch(matchline, s:all, curcol) +" let suf = strlen(matchline) - matchend(matchline, regexp) +" let prefix = (curcol ? '^.\{' . curcol . '}\%(' : '^\%(') +" let suffix = (suf ? '\).\{' . suf . '}$' : '\)$') +" " Reconstruct the version with unresolved backrefs. +" let patBR = substitute(b:match_words.',', '[,:]*,[,:]*', ',', 'g') +" let patBR = substitute(patBR, ':\{2,}', ':', "g") +" " Now, set group and groupBR to the matching group: 'if:endif' or +" " 'while:endwhile' or whatever. +" let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, patBR) +" let i = matchend(group, s:notslash . ",") +" let groupBR = strpart(group, i) +" let group = strpart(group, 0, i-1) +" " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix +" if s:do_BR +" let group = s:InsertRefs(groupBR, prefix, group, suffix, matchline) +" endif +" " let g:group = group +" +" " - TODO: Construct the closing from group. +" let fake = "end" . expand("<cword>") +" execute startpos +" return fake +" endfun + +" Close all open structures. "Get the heck out of here!" +" fun! s:Gthhoh() +" let close = s:Autocomplete() +" while strlen(close) +" put=close +" let close = s:Autocomplete() +" endwhile +" endfun + +" Parse special strings as typical skip arguments for searchpair(): +" s:foo becomes (current syntax item) =~ foo +" S:foo becomes (current syntax item) !~ foo +" r:foo becomes (line before cursor) =~ foo +" R:foo becomes (line before cursor) !~ foo +fun! s:ParseSkip(str) + let skip = a:str + if skip[1] == ":" + if skip[0] == "s" + let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .. + \ strpart(skip,2) .. "'" + elseif skip[0] == "S" + let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .. + \ strpart(skip,2) .. "'" + elseif skip[0] == "r" + let skip = "strpart(getline('.'),0,col('.'))=~'" .. strpart(skip,2) .. "'" + elseif skip[0] == "R" + let skip = "strpart(getline('.'),0,col('.'))!~'" .. strpart(skip,2) .. "'" + endif + endif + return skip +endfun + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim:sts=2:sw=2:et: diff --git a/runtime/pack/dist/opt/matchit/doc/matchit.txt b/runtime/pack/dist/opt/matchit/doc/matchit.txt new file mode 100644 index 0000000..52f448f --- /dev/null +++ b/runtime/pack/dist/opt/matchit/doc/matchit.txt @@ -0,0 +1,397 @@ +*matchit.txt* Extended "%" matching + +For instructions on installing this file, type + `:help matchit-install` +inside Vim. + +For Vim version 8.2. Last change: 2021 Dec 24 + + + VIM REFERENCE MANUAL by Benji Fisher et al + +*matchit* *matchit.vim* + +1. Extended matching with "%" |matchit-intro| +2. Activation |matchit-activate| +3. Configuration |matchit-configure| +4. Supporting a New Language |matchit-newlang| +5. Known Bugs and Limitations |matchit-bugs| + +The functionality mentioned here is a plugin, see |add-plugin|. +This plugin is only available if 'compatible' is not set. + +============================================================================== +1. Extended matching with "%" *matchit-intro* + + *matchit-%* +% Cycle forward through matching groups, such as "if", "else", "endif", + as specified by |b:match_words|. + + *g%* *v_g%* *o_g%* +g% Cycle backwards through matching groups, as specified by + |b:match_words|. For example, go from "if" to "endif" to "else". + + *[%* *v_[%* *o_[%* +[% Go to [count] previous unmatched group, as specified by + |b:match_words|. Similar to |[{|. + + *]%* *v_]%* *o_]%* +]% Go to [count] next unmatched group, as specified by + |b:match_words|. Similar to |]}|. + + *v_a%* +a% In Visual mode, select the matching group, as specified by + |b:match_words|, containing the cursor. Similar to |v_a[|. + A [count] is ignored, and only the first character of the closing + pattern is selected. + +In Vim, as in plain vi, the percent key, |%|, jumps the cursor from a brace, +bracket, or paren to its match. This can be configured with the 'matchpairs' +option. The matchit plugin extends this in several ways: + + You can match whole words, such as "if" and "endif", not just + single characters. You can also specify a |regular-expression|. + You can define groups with more than two words, such as "if", + "else", "endif". Banging on the "%" key will cycle from the "if" to + the first "else", the next "else", ..., the closing "endif", and back + to the opening "if". Nested structures are skipped. Using |g%| goes + in the reverse direction. + By default, words inside comments and strings are ignored, unless + the cursor is inside a comment or string when you type "%". If the + only thing you want to do is modify the behavior of "%" so that it + behaves this way, you do not have to define |b:match_words|, since the + script uses the 'matchpairs' option as well as this variable. + +See |matchit-details| for details on what the script does, and |b:match_words| +for how to specify matching patterns. + +MODES: *matchit-modes* *matchit-v_%* *matchit-o_%* + +Mostly, % and related motions (|g%| and |[%| and |]%|) should just work like built-in +|motion| commands in |Operator-pending| and |Visual| modes (as of 8.1.648) + +LANGUAGES: *matchit-languages* + +Currently, the following languages are supported: Ada, ASP with VBS, Csh, +DTD, Entity, Essbase, Fortran, HTML, JSP (same as HTML), LaTeX, Lua, Pascal, +SGML, Shell, Tcsh, Vim, XML. Other languages may already have support via +the default |filetype-plugin|s in the standard vim distribution. + +To support a new language, see |matchit-newlang| below. + +DETAILS: *matchit-details* *matchit-parse* + +Here is an outline of what matchit.vim does each time you hit the "%" key. If +there are |backref|s in |b:match_words| then the first step is to produce a +version in which these back references have been eliminated; if there are no +|backref|s then this step is skipped. This step is called parsing. For +example, "\(foo\|bar\):end\1" is parsed to yield +"\(foo\|bar\):end\(foo\|bar\)". This can get tricky, especially if there are +nested groups. If debugging is turned on, the parsed version is saved as +|b:match_pat|. + + *matchit-choose* +Next, the script looks for a word on the current line that matches the pattern +just constructed. It includes the patterns from the 'matchpairs' option. +The goal is to do what you expect, which turns out to be a little complicated. +The script follows these rules: + + Insist on a match that ends on or after the cursor. + Prefer a match that includes the cursor position (that is, one that + starts on or before the cursor). + Prefer a match that starts as close to the cursor as possible. + If more than one pattern in |b:match_words| matches, choose the one + that is listed first. + +Examples: + + Suppose you > + :let b:match_words = '<:>,<tag>:</tag>' +< and hit "%" with the cursor on or before the "<" in "a <tag> is born". + The pattern '<' comes first, so it is preferred over '<tag>', which + also matches. If the cursor is on the "t", however, then '<tag>' is + preferred, because this matches a bit of text containing the cursor. + If the two groups of patterns were reversed then '<' would never be + preferred. + + Suppose you > + :let b:match_words = 'if:end if' +< (Note the space!) and hit "%" with the cursor at the end of "end if". + Then "if" matches, which is probably not what you want, but if the + cursor starts on the "end " then "end if" is chosen. (You can avoid + this problem by using a more complicated pattern.) + +If there is no match, the cursor does not move. (Before version 1.13 of the +script, it would fall back on the usual behavior of |%|). If debugging is +turned on, the matched bit of text is saved as |b:match_match| and the cursor +column of the start of the match is saved as |b:match_col|. + +Next, the script looks through |b:match_words| (original and parsed versions) +for the group and pattern that match. If debugging is turned on, the group is +saved as |b:match_ini| (the first pattern) and |b:match_tail| (the rest). If +there are |backref|s then, in addition, the matching pattern is saved as +|b:match_word| and a table of translations is saved as |b:match_table|. If +there are |backref|s, these are determined from the matching pattern and +|b:match_match| and substituted into each pattern in the matching group. + +The script decides whether to search forwards or backwards and chooses +arguments for the |searchpair()| function. Then, the cursor is moved to the +start of the match, and |searchpair()| is called. By default, matching +structures inside strings and comments are ignored. This can be changed by +setting |b:match_skip|. + +============================================================================== +2. Activation *matchit-activate* + +To use the matchit plugin add this line to your |vimrc|: > + packadd! matchit + +The script should start working the next time you start Vim. + +To use the matching plugin after startup, you can use this command (note the +omitted '!'): > + packadd matchit + +To use the matchit plugin after Vim has started, execute this command: > + packadd matchit + +(Earlier versions of the script did nothing unless a |buffer-variable| named +|b:match_words| was defined. Even earlier versions contained autocommands +that set this variable for various file types. Now, |b:match_words| is +defined in many of the default |filetype-plugin|s instead.) + +For a new language, you can add autocommands to the script or to your vimrc +file, but the recommended method is to add a line such as > + let b:match_words = '\<foo\>:\<bar\>' +to the |filetype-plugin| for your language. See |b:match_words| below for how +this variable is interpreted. + +TROUBLESHOOTING *matchit-troubleshoot* + +The script should work in most installations of Vim. It may not work if Vim +was compiled with a minimal feature set, for example if the |+syntax| option +was not enabled. If your Vim has support for syntax compiled in, but you do +not have |syntax| highlighting turned on, matchit.vim should work, but it may +fail to skip matching groups in comments and strings. If the |filetype| +mechanism is turned off, the |b:match_words| variable will probably not be +defined automatically. + +============================================================================== +3. Configuration *matchit-configure* + +There are several variables that govern the behavior of matchit.vim. Note +that these are variables local to the buffer, not options, so use |:let| to +define them, not |:set|. Some of these variables have values that matter; for +others, it only matters whether the variable has been defined. All of these +can be defined in the |filetype-plugin| or autocommand that defines +|b:match_words| or "on the fly." + +The main variable is |b:match_words|. It is described in the section below on +supporting a new language. + + *MatchError* *matchit-hl* *matchit-highlight* +MatchError is the highlight group for error messages from the script. By +default, it is linked to WarningMsg. If you do not want to be bothered by +error messages, you can define this to be something invisible. For example, +if you use the GUI version of Vim and your command line is normally white, you +can do > + :hi MatchError guifg=white guibg=white +< + *b:match_ignorecase* +If you > + :let b:match_ignorecase = 1 +then matchit.vim acts as if 'ignorecase' is set: for example, "end" and "END" +are equivalent. If you > + :let b:match_ignorecase = 0 +then matchit.vim treats "end" and "END" differently. (There will be no +b:match_infercase option unless someone requests it.) + + *b:match_debug* +Define b:match_debug if you want debugging information to be saved. See +|matchit-debug|, below. + + *b:match_skip* +If b:match_skip is defined, it is passed as the skip argument to +|searchpair()|. This controls when matching structures are skipped, or +ignored. By default, they are ignored inside comments and strings, as +determined by the |syntax| mechanism. (If syntax highlighting is turned off, +nothing is skipped.) You can set b:match_skip to a string, which evaluates to +a non-zero, numerical value if the match is to be skipped or zero if the match +should not be skipped. In addition, the following special values are +supported by matchit.vim: + s:foo becomes (current syntax item) =~ foo + S:foo becomes (current syntax item) !~ foo + r:foo becomes (line before cursor) =~ foo + R:foo becomes (line before cursor) !~ foo +(The "s" is meant to suggest "syntax", and the "r" is meant to suggest +"regular expression".) + +Examples: + + You can get the default behavior with > + :let b:match_skip = 's:comment\|string' +< + If you want to skip matching structures unless they are at the start + of the line (ignoring whitespace) then you can > + :let b:match_skip = 'R:^\s*' +< Do not do this if strings or comments can span several lines, since + the normal syntax checking will not be done if you set b:match_skip. + + In LaTeX, since "%" is used as the comment character, you can > + :let b:match_skip = 'r:%' +< Unfortunately, this will skip anything after "\%", an escaped "%". To + allow for this, and also "\\%" (an escaped backslash followed by the + comment character) you can > + :let b:match_skip = 'r:\(^\|[^\\]\)\(\\\\\)*%' +< +============================================================================== +4. Supporting a New Language *matchit-newlang* + *b:match_words* +In order for matchit.vim to support a new language, you must define a suitable +pattern for |b:match_words|. You may also want to set some of the +|matchit-configure| variables, as described above. If your language has a +complicated syntax, or many keywords, you will need to know something about +Vim's |regular-expression|s. + +The format for |b:match_words| is similar to that of the 'matchpairs' option: +it is a comma (,)-separated list of groups; each group is a colon(:)-separated +list of patterns (regular expressions). Commas and backslashes that are part +of a pattern should be escaped with backslashes ('\:' and '\,'). It is OK to +have only one group; the effect is undefined if a group has only one pattern. +A simple example is > + :let b:match_words = '\<if\>:\<endif\>,' + \ . '\<while\>:\<continue\>:\<break\>:\<endwhile\>' +(In Vim regular expressions, |\<| and |\>| denote word boundaries. Thus "if" +matches the end of "endif" but "\<if\>" does not.) Then banging on the "%" +key will bounce the cursor between "if" and the matching "endif"; and from +"while" to any matching "continue" or "break", then to the matching "endwhile" +and back to the "while". It is almost always easier to use |literal-string|s +(single quotes) as above: '\<if\>' rather than "\\<if\\>" and so on. + +Exception: If the ":" character does not appear in b:match_words, then it is +treated as an expression to be evaluated. For example, > + :let b:match_words = 'GetMatchWords()' +allows you to define a function. This can return a different string depending +on the current syntax, for example. + +Once you have defined the appropriate value of |b:match_words|, you will +probably want to have this set automatically each time you edit the +appropriate file type. The recommended way to do this is by adding the +definition to a |filetype-plugin| file. + +Tips: Be careful that your initial pattern does not match your final pattern. +See the example above for the use of word-boundary expressions. It is usually +better to use ".\{-}" (as many as necessary) instead of ".*" (as many as +possible). See |\{-|. For example, in the string "<tag>label</tag>", "<.*>" +matches the whole string whereas "<.\{-}>" and "<[^>]*>" match "<tag>" and +"</tag>". + + *matchit-spaces* *matchit-s:notend* +If "if" is to be paired with "end if" (Note the space!) then word boundaries +are not enough. Instead, define a regular expression s:notend that will match +anything but "end" and use it as follows: > + :let s:notend = '\%(\<end\s\+\)\@<!' + :let b:match_words = s:notend . '\<if\>:\<end\s\+if\>' +< *matchit-s:sol* +This is a simplified version of what is done for Ada. The s:notend is a +|script-variable|. Similarly, you may want to define a start-of-line regular +expression > + :let s:sol = '\%(^\|;\)\s*' +if keywords are only recognized after the start of a line or after a +semicolon (;), with optional white space. + + *matchit-backref* *matchit-\1* +In any group, the expressions |\1|, |\2|, ..., |\9| refer to parts of the +INITIAL pattern enclosed in |\(|escaped parentheses|\)|. These are referred +to as back references, or backrefs. For example, > + :let b:match_words = '\<b\(o\+\)\>:\(h\)\1\>' +means that "bo" pairs with "ho" and "boo" pairs with "hoo" and so on. Note +that "\1" does not refer to the "\(h\)" in this example. If you have +"\(nested \(parentheses\)\) then "\d" refers to the d-th "\(" and everything +up to and including the matching "\)": in "\(nested\(parentheses\)\)", "\1" +refers to everything and "\2" refers to "\(parentheses\)". If you use a +variable such as |s:notend| or |s:sol| in the previous paragraph then remember +to count any "\(" patterns in this variable. You do not have to count groups +defined by |\%(\)|. + +It should be possible to resolve back references from any pattern in the +group. For example, > + :let b:match_words = '\(foo\)\(bar\):more\1:and\2:end\1\2' +would not work because "\2" cannot be determined from "morefoo" and "\1" +cannot be determined from "andbar". On the other hand, > + :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1' +should work (and have the same effect as "foobar:barfoo:endfoobar"), although +this has not been thoroughly tested. + +You can use |zero-width| patterns such as |\@<=| and |\zs|. (The latter has +not been thoroughly tested in matchit.vim.) For example, if the keyword "if" +must occur at the start of the line, with optional white space, you might use +the pattern "\(^\s*\)\@<=if" so that the cursor will end on the "i" instead of +at the start of the line. For another example, if HTML had only one tag then +one could > + :let b:match_words = '<:>,<\@<=tag>:<\@<=/tag>' +so that "%" can bounce between matching "<" and ">" pairs or (starting on +"tag" or "/tag") between matching tags. Without the |\@<=|, the script would +bounce from "tag" to the "<" in "</tag>", and another "%" would not take you +back to where you started. + +DEBUGGING *matchit-debug* *:MatchDebug* + +If you are having trouble figuring out the appropriate definition of +|b:match_words| then you can take advantage of the same information I use when +debugging the script. This is especially true if you are not sure whether +your patterns or my script are at fault! To make this more convenient, I have +made the command :MatchDebug, which defines the variable |b:match_debug| and +creates a Matchit menu. This menu makes it convenient to check the values of +the variables described below. You will probably also want to read +|matchit-details| above. + +Defining the variable |b:match_debug| causes the script to set the following +variables, each time you hit the "%" key. Several of these are only defined +if |b:match_words| includes |backref|s. + + *b:match_pat* +The b:match_pat variable is set to |b:match_words| with |backref|s parsed. + *b:match_match* +The b:match_match variable is set to the bit of text that is recognized as a +match. + *b:match_col* +The b:match_col variable is set to the cursor column of the start of the +matching text. + *b:match_wholeBR* +The b:match_wholeBR variable is set to the comma-separated group of patterns +that matches, with |backref|s unparsed. + *b:match_iniBR* +The b:match_iniBR variable is set to the first pattern in |b:match_wholeBR|. + *b:match_ini* +The b:match_ini variable is set to the first pattern in |b:match_wholeBR|, +with |backref|s resolved from |b:match_match|. + *b:match_tail* +The b:match_tail variable is set to the remaining patterns in +|b:match_wholeBR|, with |backref|s resolved from |b:match_match|. + *b:match_word* +The b:match_word variable is set to the pattern from |b:match_wholeBR| that +matches |b:match_match|. + *b:match_table* +The back reference '\'.d refers to the same thing as '\'.b:match_table[d] in +|b:match_word|. + +============================================================================== +5. Known Bugs and Limitations *matchit-bugs* + +Repository: https://github.com/chrisbra/matchit/ +Bugs can be reported at the repository and the latest development snapshot can +also be downloaded there. + +Just because I know about a bug does not mean that it is on my todo list. I +try to respond to reports of bugs that cause real problems. If it does not +cause serious problems, or if there is a work-around, a bug may sit there for +a while. Moral: if a bug (known or not) bothers you, let me know. + +It would be nice if "\0" were recognized as the entire pattern. That is, it +would be nice if "foo:\end\0" had the same effect as "\(foo\):\end\1". I may +try to implement this in a future version. (This is not so easy to arrange as +you might think!) + +============================================================================== +vim:tw=78:ts=8:fo=tcq2:ft=help: diff --git a/runtime/pack/dist/opt/matchit/doc/tags b/runtime/pack/dist/opt/matchit/doc/tags new file mode 100644 index 0000000..4ccdc87 --- /dev/null +++ b/runtime/pack/dist/opt/matchit/doc/tags @@ -0,0 +1,50 @@ +:MatchDebug matchit.txt /*:MatchDebug* +MatchError matchit.txt /*MatchError* +[% matchit.txt /*[%* +]% matchit.txt /*]%* +b:match_col matchit.txt /*b:match_col* +b:match_debug matchit.txt /*b:match_debug* +b:match_ignorecase matchit.txt /*b:match_ignorecase* +b:match_ini matchit.txt /*b:match_ini* +b:match_iniBR matchit.txt /*b:match_iniBR* +b:match_match matchit.txt /*b:match_match* +b:match_pat matchit.txt /*b:match_pat* +b:match_skip matchit.txt /*b:match_skip* +b:match_table matchit.txt /*b:match_table* +b:match_tail matchit.txt /*b:match_tail* +b:match_wholeBR matchit.txt /*b:match_wholeBR* +b:match_word matchit.txt /*b:match_word* +b:match_words matchit.txt /*b:match_words* +g% matchit.txt /*g%* +matchit matchit.txt /*matchit* +matchit-% matchit.txt /*matchit-%* +matchit-\1 matchit.txt /*matchit-\\1* +matchit-activate matchit.txt /*matchit-activate* +matchit-backref matchit.txt /*matchit-backref* +matchit-bugs matchit.txt /*matchit-bugs* +matchit-choose matchit.txt /*matchit-choose* +matchit-configure matchit.txt /*matchit-configure* +matchit-debug matchit.txt /*matchit-debug* +matchit-details matchit.txt /*matchit-details* +matchit-highlight matchit.txt /*matchit-highlight* +matchit-hl matchit.txt /*matchit-hl* +matchit-intro matchit.txt /*matchit-intro* +matchit-languages matchit.txt /*matchit-languages* +matchit-modes matchit.txt /*matchit-modes* +matchit-newlang matchit.txt /*matchit-newlang* +matchit-o_% matchit.txt /*matchit-o_%* +matchit-parse matchit.txt /*matchit-parse* +matchit-s:notend matchit.txt /*matchit-s:notend* +matchit-s:sol matchit.txt /*matchit-s:sol* +matchit-spaces matchit.txt /*matchit-spaces* +matchit-troubleshoot matchit.txt /*matchit-troubleshoot* +matchit-v_% matchit.txt /*matchit-v_%* +matchit.txt matchit.txt /*matchit.txt* +matchit.vim matchit.txt /*matchit.vim* +o_[% matchit.txt /*o_[%* +o_]% matchit.txt /*o_]%* +o_g% matchit.txt /*o_g%* +v_[% matchit.txt /*v_[%* +v_]% matchit.txt /*v_]%* +v_a% matchit.txt /*v_a%* +v_g% matchit.txt /*v_g%* diff --git a/runtime/pack/dist/opt/matchit/plugin/matchit.vim b/runtime/pack/dist/opt/matchit/plugin/matchit.vim new file mode 100644 index 0000000..51ba3a7 --- /dev/null +++ b/runtime/pack/dist/opt/matchit/plugin/matchit.vim @@ -0,0 +1,98 @@ +" matchit.vim: (global plugin) Extended "%" matching +" Maintainer: Christian Brabandt +" Version: 1.18 +" Last Change: 2020 Dec 23 +" Repository: https://github.com/chrisbra/matchit +" Previous URL:http://www.vim.org/script.php?script_id=39 +" Previous Maintainer: Benji Fisher PhD <benji@member.AMS.org> + +" Documentation: +" The documentation is in a separate file: ../doc/matchit.txt + +" Credits: +" Vim editor by Bram Moolenaar (Thanks, Bram!) +" Original script and design by Raul Segura Acevedo +" Support for comments by Douglas Potts +" Support for back references and other improvements by Benji Fisher +" Support for many languages by Johannes Zellner +" Suggestions for improvement, bug reports, and support for additional +" languages by Jordi-Albert Batalla, Neil Bird, Servatius Brandt, Mark +" Collett, Stephen Wall, Dany St-Amant, Yuheng Xie, and Johannes Zellner. + +" Debugging: +" If you'd like to try the built-in debugging commands... +" :MatchDebug to activate debugging for the current buffer +" This saves the values of several key script variables as buffer-local +" variables. See the MatchDebug() function, below, for details. + +" TODO: I should think about multi-line patterns for b:match_words. +" This would require an option: how many lines to scan (default 1). +" This would be useful for Python, maybe also for *ML. +" TODO: Maybe I should add a menu so that people will actually use some of +" the features that I have implemented. +" TODO: Eliminate the MultiMatch function. Add yet another argument to +" Match_wrapper() instead. +" TODO: Allow :let b:match_words = '\(\(foo\)\(bar\)\):\3\2:end\1' +" TODO: Make backrefs safer by using '\V' (very no-magic). +" TODO: Add a level of indirection, so that custom % scripts can use my +" work but extend it. + +" Allow user to prevent loading and prevent duplicate loading. +if exists("g:loaded_matchit") || &cp + finish +endif +let g:loaded_matchit = 1 + +let s:save_cpo = &cpo +set cpo&vim + +nnoremap <silent> <Plug>(MatchitNormalForward) :<C-U>call matchit#Match_wrapper('',1,'n')<CR> +nnoremap <silent> <Plug>(MatchitNormalBackward) :<C-U>call matchit#Match_wrapper('',0,'n')<CR> +xnoremap <silent> <Plug>(MatchitVisualForward) :<C-U>call matchit#Match_wrapper('',1,'v')<CR> + \:if col("''") != col("$") \| exe ":normal! m'" \| endif<cr>gv`` +xnoremap <silent> <Plug>(MatchitVisualBackward) :<C-U>call matchit#Match_wrapper('',0,'v')<CR>m'gv`` +onoremap <silent> <Plug>(MatchitOperationForward) :<C-U>call matchit#Match_wrapper('',1,'o')<CR> +onoremap <silent> <Plug>(MatchitOperationBackward) :<C-U>call matchit#Match_wrapper('',0,'o')<CR> + +" Analogues of [{ and ]} using matching patterns: +nnoremap <silent> <Plug>(MatchitNormalMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR> +nnoremap <silent> <Plug>(MatchitNormalMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR> +xnoremap <silent> <Plug>(MatchitVisualMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR>m'gv`` +xnoremap <silent> <Plug>(MatchitVisualMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR>m'gv`` +onoremap <silent> <Plug>(MatchitOperationMultiBackward) :<C-U>call matchit#MultiMatch("bW", "o")<CR> +onoremap <silent> <Plug>(MatchitOperationMultiForward) :<C-U>call matchit#MultiMatch("W", "o")<CR> + +" text object: +xmap <silent> <Plug>(MatchitVisualTextObject) <Plug>(MatchitVisualMultiBackward)o<Plug>(MatchitVisualMultiForward) + +if !exists("g:no_plugin_maps") + nmap <silent> % <Plug>(MatchitNormalForward) + nmap <silent> g% <Plug>(MatchitNormalBackward) + xmap <silent> % <Plug>(MatchitVisualForward) + xmap <silent> g% <Plug>(MatchitVisualBackward) + omap <silent> % <Plug>(MatchitOperationForward) + omap <silent> g% <Plug>(MatchitOperationBackward) + + " Analogues of [{ and ]} using matching patterns: + nmap <silent> [% <Plug>(MatchitNormalMultiBackward) + nmap <silent> ]% <Plug>(MatchitNormalMultiForward) + xmap <silent> [% <Plug>(MatchitVisualMultiBackward) + xmap <silent> ]% <Plug>(MatchitVisualMultiForward) + omap <silent> [% <Plug>(MatchitOperationMultiBackward) + omap <silent> ]% <Plug>(MatchitOperationMultiForward) + + " Text object + xmap a% <Plug>(MatchitVisualTextObject) +endif + +" Call this function to turn on debugging information. Every time the main +" script is run, buffer variables will be saved. These can be used directly +" or viewed using the menu items below. +if !exists(":MatchDebug") + command! -nargs=0 MatchDebug call matchit#Match_debug() +endif + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim:sts=2:sw=2:et: diff --git a/runtime/pack/dist/opt/shellmenu/plugin/shellmenu.vim b/runtime/pack/dist/opt/shellmenu/plugin/shellmenu.vim new file mode 100644 index 0000000..04b48b9 --- /dev/null +++ b/runtime/pack/dist/opt/shellmenu/plugin/shellmenu.vim @@ -0,0 +1,104 @@ +" When you're writing shell scripts and you are in doubt which test to use, +" which shell environment variables are defined, what the syntax of the case +" statement is, and you need to invoke 'man sh'? +" +" Your problems are over now! +" +" Attached is a Vim script file for turning gvim into a shell script editor. +" It may also be used as an example how to use menus in Vim. +" +" Maintainer: Ada (Haowen) Yu <me@yuhaowen.com> +" Original author: Lennart Schultz <les@dmi.min.dk> (mail unreachable) + +" Make sure the '<' and 'C' flags are not included in 'cpoptions', otherwise +" <CR> would not be recognized. See ":help 'cpoptions'". +let s:cpo_save = &cpo +set cpo&vim + +imenu ShellMenu.Statements.for for in <CR>do<CR><CR>done<esc>ki <esc>kk0elli +imenu ShellMenu.Statements.case case in<CR>) ;;<CR>esac<esc>bki <esc>k0elli +imenu ShellMenu.Statements.if if <CR>then<CR><CR>fi<esc>ki <esc>kk0elli +imenu ShellMenu.Statements.if-else if <CR>then<CR><CR>else<CR><CR>fi<esc>ki <esc>kki <esc>kk0elli +imenu ShellMenu.Statements.elif elif <CR>then<CR><CR><esc>ki <esc>kk0elli +imenu ShellMenu.Statements.while while do<CR><CR>done<esc>ki <esc>kk0elli +imenu ShellMenu.Statements.break break +imenu ShellMenu.Statements.continue continue +imenu ShellMenu.Statements.function () {<CR><CR>}<esc>ki <esc>k0i +imenu ShellMenu.Statements.return return +imenu ShellMenu.Statements.return-true return 0 +imenu ShellMenu.Statements.return-false return 1 +imenu ShellMenu.Statements.exit exit +imenu ShellMenu.Statements.shift shift +imenu ShellMenu.Statements.trap trap +imenu ShellMenu.Test.Existence [ -e ]<esc>hi +imenu ShellMenu.Test.Existence\ -\ file [ -f ]<esc>hi +imenu ShellMenu.Test.Existence\ -\ file\ (not\ empty) [ -s ]<esc>hi +imenu ShellMenu.Test.Existence\ -\ directory [ -d ]<esc>hi +imenu ShellMenu.Test.Existence\ -\ executable [ -x ]<esc>hi +imenu ShellMenu.Test.Existence\ -\ readable [ -r ]<esc>hi +imenu ShellMenu.Test.Existence\ -\ writable [ -w ]<esc>hi +imenu ShellMenu.Test.String\ is\ empty [ x = "x$" ]<esc>hhi +imenu ShellMenu.Test.String\ is\ not\ empty [ x != "x$" ]<esc>hhi +imenu ShellMenu.Test.Strings\ are\ equal [ "" = "" ]<esc>hhhhhhhi +imenu ShellMenu.Test.Strings\ are\ not\ equal [ "" != "" ]<esc>hhhhhhhhi +imenu ShellMenu.Test.Value\ is\ greater\ than [ -gt ]<esc>hhhhhhi +imenu ShellMenu.Test.Value\ is\ greater\ equal [ -ge ]<esc>hhhhhhi +imenu ShellMenu.Test.Values\ are\ equal [ -eq ]<esc>hhhhhhi +imenu ShellMenu.Test.Values\ are\ not\ equal [ -ne ]<esc>hhhhhhi +imenu ShellMenu.Test.Value\ is\ less\ than [ -lt ]<esc>hhhhhhi +imenu ShellMenu.Test.Value\ is\ less\ equal [ -le ]<esc>hhhhhhi +imenu ShellMenu.ParmSub.Substitute\ word\ if\ parm\ not\ set ${:-}<esc>hhi +imenu ShellMenu.ParmSub.Set\ parm\ to\ word\ if\ not\ set ${:=}<esc>hhi +imenu ShellMenu.ParmSub.Substitute\ word\ if\ parm\ set\ else\ nothing ${:+}<esc>hhi +imenu ShellMenu.ParmSub.If\ parm\ not\ set\ print\ word\ and\ exit ${:?}<esc>hhi +imenu ShellMenu.SpShVars.Number\ of\ positional\ parameters ${#} +imenu ShellMenu.SpShVars.All\ positional\ parameters\ (quoted\ spaces) ${*} +imenu ShellMenu.SpShVars.All\ positional\ parameters\ (unquoted\ spaces) ${@} +imenu ShellMenu.SpShVars.Flags\ set ${-} +imenu ShellMenu.SpShVars.Return\ code\ of\ last\ command ${?} +imenu ShellMenu.SpShVars.Process\ number\ of\ this\ shell ${$} +imenu ShellMenu.SpShVars.Process\ number\ of\ last\ background\ command ${!} +imenu ShellMenu.Environ.HOME ${HOME} +imenu ShellMenu.Environ.PATH ${PATH} +imenu ShellMenu.Environ.CDPATH ${CDPATH} +imenu ShellMenu.Environ.MAIL ${MAIL} +imenu ShellMenu.Environ.MAILCHECK ${MAILCHECK} +imenu ShellMenu.Environ.PS1 ${PS1} +imenu ShellMenu.Environ.PS2 ${PS2} +imenu ShellMenu.Environ.IFS ${IFS} +imenu ShellMenu.Environ.SHACCT ${SHACCT} +imenu ShellMenu.Environ.SHELL ${SHELL} +imenu ShellMenu.Environ.LC_CTYPE ${LC_CTYPE} +imenu ShellMenu.Environ.LC_MESSAGES ${LC_MESSAGES} +imenu ShellMenu.Builtins.cd cd +imenu ShellMenu.Builtins.echo echo +imenu ShellMenu.Builtins.eval eval +imenu ShellMenu.Builtins.exec exec +imenu ShellMenu.Builtins.export export +imenu ShellMenu.Builtins.getopts getopts +imenu ShellMenu.Builtins.hash hash +imenu ShellMenu.Builtins.newgrp newgrp +imenu ShellMenu.Builtins.pwd pwd +imenu ShellMenu.Builtins.read read +imenu ShellMenu.Builtins.readonly readonly +imenu ShellMenu.Builtins.return return +imenu ShellMenu.Builtins.times times +imenu ShellMenu.Builtins.type type +imenu ShellMenu.Builtins.umask umask +imenu ShellMenu.Builtins.wait wait +imenu ShellMenu.Set.set set +imenu ShellMenu.Set.unset unset +imenu ShellMenu.Set.Mark\ created\ or\ modified\ variables\ for\ export set -a +imenu ShellMenu.Set.Exit\ when\ command\ returns\ non-zero\ status set -e +imenu ShellMenu.Set.Disable\ file\ name\ expansion set -f +imenu ShellMenu.Set.Locate\ and\ remember\ commands\ when\ being\ looked\ up set -h +imenu ShellMenu.Set.All\ assignment\ statements\ are\ placed\ in\ the\ environment\ for\ a\ command set -k +imenu ShellMenu.Set.Read\ commands\ but\ do\ not\ execute\ them set -n +imenu ShellMenu.Set.Exit\ after\ reading\ and\ executing\ one\ command set -t +imenu ShellMenu.Set.Treat\ unset\ variables\ as\ an\ error\ when\ substituting set -u +imenu ShellMenu.Set.Print\ shell\ input\ lines\ as\ they\ are\ read set -v +imenu ShellMenu.Set.Print\ commands\ and\ their\ arguments\ as\ they\ are\ executed set -x + +" Restore the previous value of 'cpoptions'. +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/pack/dist/opt/swapmouse/plugin/swapmouse.vim b/runtime/pack/dist/opt/swapmouse/plugin/swapmouse.vim new file mode 100644 index 0000000..8b85be0 --- /dev/null +++ b/runtime/pack/dist/opt/swapmouse/plugin/swapmouse.vim @@ -0,0 +1,22 @@ +" These macros swap the left and right mouse buttons (for left handed) +" Don't forget to do ":set mouse=a" or the mouse won't work at all +noremap <LeftMouse> <RightMouse> +noremap <2-LeftMouse> <2-RightMouse> +noremap <3-LeftMouse> <3-RightMouse> +noremap <4-LeftMouse> <4-RightMouse> +noremap <LeftDrag> <RightDrag> +noremap <LeftRelease> <RightRelease> +noremap <RightMouse> <LeftMouse> +noremap <2-RightMouse> <2-LeftMouse> +noremap <3-RightMouse> <3-LeftMouse> +noremap <4-RightMouse> <4-LeftMouse> +noremap <RightDrag> <LeftDrag> +noremap <RightRelease> <LeftRelease> +noremap g<LeftMouse> <C-RightMouse> +noremap g<RightMouse> <C-LeftMouse> +noremap! <LeftMouse> <RightMouse> +noremap! <LeftDrag> <RightDrag> +noremap! <LeftRelease> <RightRelease> +noremap! <RightMouse> <LeftMouse> +noremap! <RightDrag> <LeftDrag> +noremap! <RightRelease> <LeftRelease> diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim new file mode 100644 index 0000000..9fd4bcb --- /dev/null +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -0,0 +1,1527 @@ +" Debugger plugin using gdb. +" +" Author: Bram Moolenaar +" Copyright: Vim license applies, see ":help license" +" Last Change: 2022 Nov 10 +" +" WORK IN PROGRESS - The basics works stable, more to come +" Note: In general you need at least GDB 7.12 because this provides the +" frame= response in MI thread-selected events we need to sync stack to file. +" The one included with "old" MingW is too old (7.6.1), you may upgrade it or +" use a newer version from http://www.equation.com/servlet/equation.cmd?fa=gdb +" +" There are two ways to run gdb: +" - In a terminal window; used if possible, does not work on MS-Windows +" Not used when g:termdebug_use_prompt is set to 1. +" - Using a "prompt" buffer; may use a terminal window for the program +" +" For both the current window is used to view source code and shows the +" current statement from gdb. +" +" USING A TERMINAL WINDOW +" +" Opens two visible terminal windows: +" 1. runs a pty for the debugged program, as with ":term NONE" +" 2. runs gdb, passing the pty of the debugged program +" A third terminal window is hidden, it is used for communication with gdb. +" +" USING A PROMPT BUFFER +" +" Opens a window with a prompt buffer to communicate with gdb. +" Gdb is run as a job with callbacks for I/O. +" On Unix another terminal window is opened to run the debugged program +" On MS-Windows a separate console is opened to run the debugged program +" +" The communication with gdb uses GDB/MI. See: +" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html + +" In case this gets sourced twice. +if exists(':Termdebug') + finish +endif + +" Need either the +terminal feature or +channel and the prompt buffer. +" The terminal feature does not work with gdb on win32. +if has('terminal') && !has('win32') + let s:way = 'terminal' +elseif has('channel') && exists('*prompt_setprompt') + let s:way = 'prompt' +else + if has('terminal') + let s:err = 'Cannot debug, missing prompt buffer support' + else + let s:err = 'Cannot debug, +channel feature is not supported' + endif + command -nargs=* -complete=file -bang Termdebug echoerr s:err + command -nargs=+ -complete=file -bang TermdebugCommand echoerr s:err + finish +endif + +let s:keepcpo = &cpo +set cpo&vim + +" The command that starts debugging, e.g. ":Termdebug vim". +" To end type "quit" in the gdb window. +command -nargs=* -complete=file -bang Termdebug call s:StartDebug(<bang>0, <f-args>) +command -nargs=+ -complete=file -bang TermdebugCommand call s:StartDebugCommand(<bang>0, <f-args>) + +let s:pc_id = 12 +let s:asm_id = 13 +let s:break_id = 14 " breakpoint number is added to this +let s:stopped = 1 + +let s:parsing_disasm_msg = 0 +let s:asm_lines = [] +let s:asm_addr = '' + +" Take a breakpoint number as used by GDB and turn it into an integer. +" The breakpoint may contain a dot: 123.4 -> 123004 +" The main breakpoint has a zero subid. +func s:Breakpoint2SignNumber(id, subid) + return s:break_id + a:id * 1000 + a:subid +endfunction + +func s:Highlight(init, old, new) + let default = a:init ? 'default ' : '' + if a:new ==# 'light' && a:old !=# 'light' + exe "hi " . default . "debugPC term=reverse ctermbg=lightblue guibg=lightblue" + elseif a:new ==# 'dark' && a:old !=# 'dark' + exe "hi " . default . "debugPC term=reverse ctermbg=darkblue guibg=darkblue" + endif +endfunc + +call s:Highlight(1, '', &background) +hi default debugBreakpoint term=reverse ctermbg=red guibg=red +hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray + +" Get the command to execute the debugger as a list, defaults to ["gdb"]. +func s:GetCommand() + if exists('g:termdebug_config') + let cmd = get(g:termdebug_config, 'command', 'gdb') + elseif exists('g:termdebugger') + let cmd = g:termdebugger + else + let cmd = 'gdb' + endif + + return type(cmd) == v:t_list ? copy(cmd) : [cmd] +endfunc + +func s:StartDebug(bang, ...) + " First argument is the command to debug, second core file or process ID. + call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang}) +endfunc + +func s:StartDebugCommand(bang, ...) + " First argument is the command to debug, rest are run arguments. + call s:StartDebug_internal({'gdb_args': [a:1], 'proc_args': a:000[1:], 'bang': a:bang}) +endfunc + +func s:StartDebug_internal(dict) + if exists('s:gdbwin') + echoerr 'Terminal debugger already running, cannot run two' + return + endif + let gdbcmd = s:GetCommand() + if !executable(gdbcmd[0]) + echoerr 'Cannot execute debugger program "' .. gdbcmd[0] .. '"' + return + endif + + let s:ptywin = 0 + let s:pid = 0 + let s:asmwin = 0 + + if exists('#User#TermdebugStartPre') + doauto <nomodeline> User TermdebugStartPre + endif + + " Uncomment this line to write logging in "debuglog". + " call ch_logfile('debuglog', 'w') + + let s:sourcewin = win_getid(winnr()) + + " Remember the old value of 'signcolumn' for each buffer that it's set in, so + " that we can restore the value for all buffers. + let b:save_signcolumn = &signcolumn + let s:signcolumn_buflist = [bufnr()] + + let s:save_columns = 0 + let s:allleft = 0 + let wide = 0 + if exists('g:termdebug_config') + let wide = get(g:termdebug_config, 'wide', 0) + elseif exists('g:termdebug_wide') + let wide = g:termdebug_wide + endif + if wide > 0 + if &columns < wide + let s:save_columns = &columns + let &columns = wide + " If we make the Vim window wider, use the whole left half for the debug + " windows. + let s:allleft = 1 + endif + let s:vertical = 1 + else + let s:vertical = 0 + endif + + " Override using a terminal window by setting g:termdebug_use_prompt to 1. + let use_prompt = 0 + if exists('g:termdebug_config') + let use_prompt = get(g:termdebug_config, 'use_prompt', 0) + elseif exists('g:termdebug_use_prompt') + let use_prompt = g:termdebug_use_prompt + endif + if has('terminal') && !has('win32') && !use_prompt + let s:way = 'terminal' + else + let s:way = 'prompt' + endif + + if s:way == 'prompt' + call s:StartDebug_prompt(a:dict) + else + call s:StartDebug_term(a:dict) + endif + + if s:GetDisasmWindow() + let curwinid = win_getid(winnr()) + call s:GotoAsmwinOrCreateIt() + call win_gotoid(curwinid) + endif + + if exists('#User#TermdebugStartPost') + doauto <nomodeline> User TermdebugStartPost + endif +endfunc + +" Use when debugger didn't start or ended. +func s:CloseBuffers() + exe 'bwipe! ' . s:ptybuf + exe 'bwipe! ' . s:commbuf + unlet! s:gdbwin +endfunc + +func s:CheckGdbRunning() + let gdbproc = term_getjob(s:gdbbuf) + if gdbproc == v:null || job_status(gdbproc) !=# 'run' + echoerr string(s:GetCommand()[0]) . ' exited unexpectedly' + call s:CloseBuffers() + return '' + endif + return 'ok' +endfunc + +" Open a terminal window without a job, to run the debugged program in. +func s:StartDebug_term(dict) + let s:ptybuf = term_start('NONE', { + \ 'term_name': 'debugged program', + \ 'vertical': s:vertical, + \ }) + if s:ptybuf == 0 + echoerr 'Failed to open the program terminal window' + return + endif + let pty = job_info(term_getjob(s:ptybuf))['tty_out'] + let s:ptywin = win_getid(winnr()) + if s:vertical + " Assuming the source code window will get a signcolumn, use two more + " columns for that, thus one less for the terminal window. + exe (&columns / 2 - 1) . "wincmd |" + if s:allleft + " use the whole left column + wincmd H + endif + endif + + " Create a hidden terminal window to communicate with gdb + let s:commbuf = term_start('NONE', { + \ 'term_name': 'gdb communication', + \ 'out_cb': function('s:CommOutput'), + \ 'hidden': 1, + \ }) + if s:commbuf == 0 + echoerr 'Failed to open the communication terminal window' + exe 'bwipe! ' . s:ptybuf + return + endif + let commpty = job_info(term_getjob(s:commbuf))['tty_out'] + + let gdb_args = get(a:dict, 'gdb_args', []) + let proc_args = get(a:dict, 'proc_args', []) + + let gdb_cmd = s:GetCommand() + + if exists('g:termdebug_config') && has_key(g:termdebug_config, 'command_add_args') + let gdb_cmd = g:termdebug_config.command_add_args(gdb_cmd, pty) + else + " Add -quiet to avoid the intro message causing a hit-enter prompt. + let gdb_cmd += ['-quiet'] + " Disable pagination, it causes everything to stop at the gdb + let gdb_cmd += ['-iex', 'set pagination off'] + " Interpret commands while the target is running. This should usually only + " be exec-interrupt, since many commands don't work properly while the + " target is running (so execute during startup). + let gdb_cmd += ['-iex', 'set mi-async on'] + " Open a terminal window to run the debugger. + let gdb_cmd += ['-tty', pty] + " Command executed _after_ startup is done, provides us with the necessary + " feedback + let gdb_cmd += ['-ex', 'echo startupdone\n'] + endif + + if exists('g:termdebug_config') && has_key(g:termdebug_config, 'command_filter') + let gdb_cmd = g:termdebug_config.command_filter(gdb_cmd) + endif + + " Adding arguments requested by the user + let gdb_cmd += gdb_args + + call ch_log('executing "' . join(gdb_cmd) . '"') + let s:gdbbuf = term_start(gdb_cmd, { + \ 'term_finish': 'close', + \ }) + if s:gdbbuf == 0 + echoerr 'Failed to open the gdb terminal window' + call s:CloseBuffers() + return + endif + let s:gdbwin = win_getid(winnr()) + + " Wait for the "startupdone" message before sending any commands. + let try_count = 0 + while 1 + if s:CheckGdbRunning() != 'ok' + return + endif + + for lnum in range(1, 200) + if term_getline(s:gdbbuf, lnum) =~ 'startupdone' + let try_count = 9999 + break + endif + endfor + let try_count += 1 + if try_count > 300 + " done or give up after five seconds + break + endif + sleep 10m + endwhile + + " Set arguments to be run. + if len(proc_args) + call term_sendkeys(s:gdbbuf, 'server set args ' . join(proc_args) . "\r") + endif + + " Connect gdb to the communication pty, using the GDB/MI interface. + " Prefix "server" to avoid adding this to the history. + call term_sendkeys(s:gdbbuf, 'server new-ui mi ' . commpty . "\r") + + " Wait for the response to show up, users may not notice the error and wonder + " why the debugger doesn't work. + let try_count = 0 + while 1 + if s:CheckGdbRunning() != 'ok' + return + endif + + let response = '' + for lnum in range(1, 200) + let line1 = term_getline(s:gdbbuf, lnum) + let line2 = term_getline(s:gdbbuf, lnum + 1) + if line1 =~ 'new-ui mi ' + " response can be in the same line or the next line + let response = line1 . line2 + if response =~ 'Undefined command' + echoerr 'Sorry, your gdb is too old, gdb 7.12 is required' + " CHECKME: possibly send a "server show version" here + call s:CloseBuffers() + return + endif + if response =~ 'New UI allocated' + " Success! + break + endif + elseif line1 =~ 'Reading symbols from' && line2 !~ 'new-ui mi ' + " Reading symbols might take a while, try more times + let try_count -= 1 + endif + endfor + if response =~ 'New UI allocated' + break + endif + let try_count += 1 + if try_count > 100 + echoerr 'Cannot check if your gdb works, continuing anyway' + break + endif + sleep 10m + endwhile + + call job_setoptions(term_getjob(s:gdbbuf), {'exit_cb': function('s:EndTermDebug')}) + + " Set the filetype, this can be used to add mappings. + set filetype=termdebug + + call s:StartDebugCommon(a:dict) +endfunc + +" Open a window with a prompt buffer to run gdb in. +func s:StartDebug_prompt(dict) + if s:vertical + vertical new + else + new + endif + let s:gdbwin = win_getid(winnr()) + let s:promptbuf = bufnr('') + call prompt_setprompt(s:promptbuf, 'gdb> ') + set buftype=prompt + file gdb + call prompt_setcallback(s:promptbuf, function('s:PromptCallback')) + call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt')) + + if s:vertical + " Assuming the source code window will get a signcolumn, use two more + " columns for that, thus one less for the terminal window. + exe (&columns / 2 - 1) . "wincmd |" + endif + + let gdb_args = get(a:dict, 'gdb_args', []) + let proc_args = get(a:dict, 'proc_args', []) + + let gdb_cmd = s:GetCommand() + " Add -quiet to avoid the intro message causing a hit-enter prompt. + let gdb_cmd += ['-quiet'] + " Disable pagination, it causes everything to stop at the gdb, needs to be run early + let gdb_cmd += ['-iex', 'set pagination off'] + " Interpret commands while the target is running. This should usually only + " be exec-interrupt, since many commands don't work properly while the + " target is running (so execute during startup). + let gdb_cmd += ['-iex', 'set mi-async on'] + " directly communicate via mi2 + let gdb_cmd += ['--interpreter=mi2'] + + " Adding arguments requested by the user + let gdb_cmd += gdb_args + + call ch_log('executing "' . join(gdb_cmd) . '"') + let s:gdbjob = job_start(gdb_cmd, { + \ 'exit_cb': function('s:EndPromptDebug'), + \ 'out_cb': function('s:GdbOutCallback'), + \ }) + if job_status(s:gdbjob) != "run" + echoerr 'Failed to start gdb' + exe 'bwipe! ' . s:promptbuf + return + endif + " Mark the buffer modified so that it's not easy to close. + set modified + let s:gdb_channel = job_getchannel(s:gdbjob) + + let s:ptybuf = 0 + if has('win32') + " MS-Windows: run in a new console window for maximum compatibility + call s:SendCommand('set new-console on') + elseif has('terminal') + " Unix: Run the debugged program in a terminal window. Open it below the + " gdb window. + belowright let s:ptybuf = term_start('NONE', { + \ 'term_name': 'debugged program', + \ }) + if s:ptybuf == 0 + echoerr 'Failed to open the program terminal window' + call job_stop(s:gdbjob) + return + endif + let s:ptywin = win_getid(winnr()) + let pty = job_info(term_getjob(s:ptybuf))['tty_out'] + call s:SendCommand('tty ' . pty) + + " Since GDB runs in a prompt window, the environment has not been set to + " match a terminal window, need to do that now. + call s:SendCommand('set env TERM = xterm-color') + call s:SendCommand('set env ROWS = ' . winheight(s:ptywin)) + call s:SendCommand('set env LINES = ' . winheight(s:ptywin)) + call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin)) + call s:SendCommand('set env COLORS = ' . &t_Co) + call s:SendCommand('set env VIM_TERMINAL = ' . v:version) + else + " TODO: open a new terminal, get the tty name, pass on to gdb + call s:SendCommand('show inferior-tty') + endif + call s:SendCommand('set print pretty on') + call s:SendCommand('set breakpoint pending on') + + " Set arguments to be run + if len(proc_args) + call s:SendCommand('set args ' . join(proc_args)) + endif + + call s:StartDebugCommon(a:dict) + startinsert +endfunc + +func s:StartDebugCommon(dict) + " Sign used to highlight the line where the program has stopped. + " There can be only one. + call sign_define('debugPC', #{linehl: 'debugPC'}) + + " Install debugger commands in the text window. + call win_gotoid(s:sourcewin) + call s:InstallCommands() + call win_gotoid(s:gdbwin) + + " Enable showing a balloon with eval info + if has("balloon_eval") || has("balloon_eval_term") + set balloonexpr=TermDebugBalloonExpr() + if has("balloon_eval") + set ballooneval + endif + if has("balloon_eval_term") + set balloonevalterm + endif + endif + + " Contains breakpoints that have been placed, key is a string with the GDB + " breakpoint number. + " Each entry is a dict, containing the sub-breakpoints. Key is the subid. + " For a breakpoint that is just a number the subid is zero. + " For a breakpoint "123.4" the id is "123" and subid is "4". + " Example, when breakpoint "44", "123", "123.1" and "123.2" exist: + " {'44': {'0': entry}, '123': {'0': entry, '1': entry, '2': entry}} + let s:breakpoints = {} + + " Contains breakpoints by file/lnum. The key is "fname:lnum". + " Each entry is a list of breakpoint IDs at that position. + let s:breakpoint_locations = {} + + augroup TermDebug + au BufRead * call s:BufRead() + au BufUnload * call s:BufUnloaded() + au OptionSet background call s:Highlight(0, v:option_old, v:option_new) + augroup END + + " Run the command if the bang attribute was given and got to the debug + " window. + if get(a:dict, 'bang', 0) + call s:SendResumingCommand('-exec-run') + call win_gotoid(s:ptywin) + endif +endfunc + +" Send a command to gdb. "cmd" is the string without line terminator. +func s:SendCommand(cmd) + call ch_log('sending to gdb: ' . a:cmd) + if s:way == 'prompt' + call ch_sendraw(s:gdb_channel, a:cmd . "\n") + else + call term_sendkeys(s:commbuf, a:cmd . "\r") + endif +endfunc + +" This is global so that a user can create their mappings with this. +func TermDebugSendCommand(cmd) + if s:way == 'prompt' + call ch_sendraw(s:gdb_channel, a:cmd . "\n") + else + let do_continue = 0 + if !s:stopped + let do_continue = 1 + Stop + sleep 10m + endif + " TODO: should we prepend CTRL-U to clear the command? + call term_sendkeys(s:gdbbuf, a:cmd . "\r") + if do_continue + Continue + endif + endif +endfunc + +" Send a command that resumes the program. If the program isn't stopped the +" command is not sent (to avoid a repeated command to cause trouble). +" If the command is sent then reset s:stopped. +func s:SendResumingCommand(cmd) + if s:stopped + " reset s:stopped here, it may take a bit of time before we get a response + let s:stopped = 0 + call ch_log('assume that program is running after this command') + call s:SendCommand(a:cmd) + else + call ch_log('dropping command, program is running: ' . a:cmd) + endif +endfunc + +" Function called when entering a line in the prompt buffer. +func s:PromptCallback(text) + call s:SendCommand(a:text) +endfunc + +" Function called when pressing CTRL-C in the prompt buffer and when placing a +" breakpoint. +func s:PromptInterrupt() + call ch_log('Interrupting gdb') + if has('win32') + " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to + " the debugger program so that gdb responds again. + if s:pid == 0 + echoerr 'Cannot interrupt gdb, did not find a process ID' + else + call debugbreak(s:pid) + endif + else + call job_stop(s:gdbjob, 'int') + endif +endfunc + +" Function called when gdb outputs text. +func s:GdbOutCallback(channel, text) + call ch_log('received from gdb: ' . a:text) + + " Drop the gdb prompt, we have our own. + " Drop status and echo'd commands. + if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&' + return + endif + if a:text =~ '^\^error,msg=' + let text = s:DecodeMessage(a:text[11:]) + if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context' + " Silently drop evaluation errors. + unlet s:evalexpr + return + endif + elseif a:text[0] == '~' + let text = s:DecodeMessage(a:text[1:]) + else + call s:CommOutput(a:channel, a:text) + return + endif + + let curwinid = win_getid(winnr()) + call win_gotoid(s:gdbwin) + + " Add the output above the current prompt. + call append(line('$') - 1, text) + set modified + + call win_gotoid(curwinid) +endfunc + +" Decode a message from gdb. quotedText starts with a ", return the text up +" to the next ", unescaping characters: +" - remove line breaks +" - change \\t to \t +" - change \0xhh to \xhh (disabled for now) +" - change \ooo to octal +" - change \\ to \ +func s:DecodeMessage(quotedText) + if a:quotedText[0] != '"' + echoerr 'DecodeMessage(): missing quote in ' . a:quotedText + return + endif + return a:quotedText + \ ->substitute('^"\|".*\|\\n', '', 'g') + \ ->substitute('\\t', "\t", 'g') + " multi-byte characters arrive in octal form + " NULL-values must be kept encoded as those break the string otherwise + \ ->substitute('\\000', s:NullRepl, 'g') + \ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g') + " Note: GDB docs also mention hex encodings - the translations below work + " but we keep them out for performance-reasons until we actually see + " those in mi-returns + " \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g') + " \ ->substitute('\\0x00', s:NullRepl, 'g') + \ ->substitute('\\\\', '\', 'g') + \ ->substitute(s:NullRepl, '\\000', 'g') +endfunc +const s:NullRepl = 'XXXNULLXXX' + +" Extract the "name" value from a gdb message with fullname="name". +func s:GetFullname(msg) + if a:msg !~ 'fullname' + return '' + endif + let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', '')) + if has('win32') && name =~ ':\\\\' + " sometimes the name arrives double-escaped + let name = substitute(name, '\\\\', '\\', 'g') + endif + return name +endfunc + +" Extract the "addr" value from a gdb message with addr="0x0001234". +func s:GetAsmAddr(msg) + if a:msg !~ 'addr=' + return '' + endif + let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', '')) + return addr +endfunc + +func s:EndTermDebug(job, status) + if exists('#User#TermdebugStopPre') + doauto <nomodeline> User TermdebugStopPre + endif + + exe 'bwipe! ' . s:commbuf + unlet s:gdbwin + + call s:EndDebugCommon() +endfunc + +func s:EndDebugCommon() + let curwinid = win_getid(winnr()) + + if exists('s:ptybuf') && s:ptybuf + exe 'bwipe! ' . s:ptybuf + endif + + " Restore 'signcolumn' in all buffers for which it was set. + call win_gotoid(s:sourcewin) + let was_buf = bufnr() + for bufnr in s:signcolumn_buflist + if bufexists(bufnr) + exe bufnr .. "buf" + if exists('b:save_signcolumn') + let &signcolumn = b:save_signcolumn + unlet b:save_signcolumn + endif + endif + endfor + if bufexists(was_buf) + exe was_buf .. "buf" + endif + + call s:DeleteCommands() + + call win_gotoid(curwinid) + + if s:save_columns > 0 + let &columns = s:save_columns + endif + + if has("balloon_eval") || has("balloon_eval_term") + set balloonexpr= + if has("balloon_eval") + set noballooneval + endif + if has("balloon_eval_term") + set noballoonevalterm + endif + endif + + if exists('#User#TermdebugStopPost') + doauto <nomodeline> User TermdebugStopPost + endif + + au! TermDebug +endfunc + +func s:EndPromptDebug(job, status) + if exists('#User#TermdebugStopPre') + doauto <nomodeline> User TermdebugStopPre + endif + + let curwinid = win_getid(winnr()) + call win_gotoid(s:gdbwin) + set nomodified + close + if curwinid != s:gdbwin + call win_gotoid(curwinid) + endif + + call s:EndDebugCommon() + unlet s:gdbwin + call ch_log("Returning from EndPromptDebug()") +endfunc + +" Disassembly window - added by Michael Sartain +" +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: ~"Dump of assembler code for function main(int, char**):\n" +" - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n" +" ... +" - CommOutput: ~" 0x0000555556467cd0:\tpop rbp\n" +" - CommOutput: ~" 0x0000555556467cd1:\tret \n" +" - CommOutput: ~"End of assembler dump.\n" +" - CommOutput: ^done + +" - CommOutput: disassemble $pc +" - CommOutput: &"disassemble $pc\n" +" - CommOutput: &"No function contains specified address.\n" +" - CommOutput: ^error,msg="No function contains specified address." +func s:HandleDisasmMsg(msg) + if a:msg =~ '^\^done' + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + silent normal! gg0"_dG + call setline(1, s:asm_lines) + set nomodified + set filetype=asm + + let lnum = search('^' . s:asm_addr) + if lnum != 0 + call sign_unplace('TermDebug', #{id: s:asm_id}) + call sign_place(s:asm_id, 'TermDebug', 'debugPC', '%', #{lnum: lnum}) + endif + + call win_gotoid(curwinid) + endif + + let s:parsing_disasm_msg = 0 + let s:asm_lines = [] + elseif a:msg =~ '^\^error,msg=' + if s:parsing_disasm_msg == 1 + " Disassemble call ran into an error. This can happen when gdb can't + " find the function frame address, so let's try to disassemble starting + " at current PC + call s:SendCommand('disassemble $pc,+100') + endif + let s:parsing_disasm_msg = 0 + elseif a:msg =~ '\&\"disassemble \$pc' + if a:msg =~ '+100' + " This is our second disasm attempt + let s:parsing_disasm_msg = 2 + endif + else + let value = substitute(a:msg, '^\~\"[ ]*', '', '') + let value = substitute(value, '^=>[ ]*', '', '') + let value = substitute(value, '\\n\"\r$', '', '') + let value = substitute(value, '\\n\"$', '', '') + let value = substitute(value, '\r', '', '') + let value = substitute(value, '\\t', ' ', 'g') + + if value != '' || !empty(s:asm_lines) + call add(s:asm_lines, value) + endif + endif +endfunc + +" Handle a message received from gdb on the GDB/MI interface. +func s:CommOutput(chan, msg) + let msgs = split(a:msg, "\r") + + for msg in msgs + " remove prefixed NL + if msg[0] == "\n" + let msg = msg[1:] + endif + + if s:parsing_disasm_msg + call s:HandleDisasmMsg(msg) + elseif msg != '' + if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' + call s:HandleCursor(msg) + elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' + call s:HandleNewBreakpoint(msg, 0) + elseif msg =~ '^=breakpoint-modified,' + call s:HandleNewBreakpoint(msg, 1) + elseif msg =~ '^=breakpoint-deleted,' + call s:HandleBreakpointDelete(msg) + elseif msg =~ '^=thread-group-started' + call s:HandleProgramRun(msg) + elseif msg =~ '^\^done,value=' + call s:HandleEvaluate(msg) + elseif msg =~ '^\^error,msg=' + call s:HandleError(msg) + elseif msg =~ '^disassemble' + let s:parsing_disasm_msg = 1 + let s:asm_lines = [] + endif + endif + endfor +endfunc + +func s:GotoProgram() + if has('win32') + if executable('powershell') + call system(printf('powershell -Command "add-type -AssemblyName microsoft.VisualBasic;[Microsoft.VisualBasic.Interaction]::AppActivate(%d);"', s:pid)) + endif + else + call win_gotoid(s:ptywin) + endif +endfunc + +" Install commands in the current window to control the debugger. +func s:InstallCommands() + let save_cpo = &cpo + set cpo&vim + + command -nargs=? Break call s:SetBreakpoint(<q-args>) + command Clear call s:ClearBreakpoint() + command Step call s:SendResumingCommand('-exec-step') + command Over call s:SendResumingCommand('-exec-next') + command -nargs=? Until call s:Until(<q-args>) + command Finish call s:SendResumingCommand('-exec-finish') + command -nargs=* Run call s:Run(<q-args>) + command -nargs=* Arguments call s:SendResumingCommand('-exec-arguments ' . <q-args>) + + if s:way == 'prompt' + command Stop call s:PromptInterrupt() + command Continue call s:SendCommand('continue') + else + command Stop call s:SendCommand('-exec-interrupt') + " using -exec-continue results in CTRL-C in the gdb window not working, + " communicating via commbuf (= use of SendCommand) has the same result + "command Continue call s:SendCommand('-exec-continue') + command Continue call term_sendkeys(s:gdbbuf, "continue\r") + endif + + command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) + command Gdb call win_gotoid(s:gdbwin) + command Program call s:GotoProgram() + command Source call s:GotoSourcewinOrCreateIt() + command Asm call s:GotoAsmwinOrCreateIt() + command Winbar call s:InstallWinbar() + + let map = 1 + if exists('g:termdebug_config') + let map = get(g:termdebug_config, 'map_K', 1) + elseif exists('g:termdebug_map_K') + let map = g:termdebug_map_K + endif + if map + let s:k_map_saved = maparg('K', 'n', 0, 1) + nnoremap K :Evaluate<CR> + endif + + if has('menu') && &mouse != '' + " install the window toolbar by default, can be disabled in the config + let winbar = 1 + if exists('g:termdebug_config') + let winbar = get(g:termdebug_config, 'winbar', 1) + endif + if winbar + call s:InstallWinbar() + endif + + let popup = 1 + if exists('g:termdebug_config') + let popup = get(g:termdebug_config, 'popup', 1) + elseif exists('g:termdebug_popup') + let popup = g:termdebug_popup + endif + if popup + let s:saved_mousemodel = &mousemodel + let &mousemodel = 'popup_setpos' + an 1.200 PopUp.-SEP3- <Nop> + an 1.210 PopUp.Set\ breakpoint :Break<CR> + an 1.220 PopUp.Clear\ breakpoint :Clear<CR> + an 1.230 PopUp.Run\ until :Until<CR> + an 1.240 PopUp.Evaluate :Evaluate<CR> + endif + endif + + let &cpo = save_cpo +endfunc + +let s:winbar_winids = [] + +" Install the window toolbar in the current window. +func s:InstallWinbar() + if has('menu') && &mouse != '' + nnoremenu WinBar.Step :Step<CR> + nnoremenu WinBar.Next :Over<CR> + nnoremenu WinBar.Finish :Finish<CR> + nnoremenu WinBar.Cont :Continue<CR> + nnoremenu WinBar.Stop :Stop<CR> + nnoremenu WinBar.Eval :Evaluate<CR> + call add(s:winbar_winids, win_getid(winnr())) + endif +endfunc + +" Delete installed debugger commands in the current window. +func s:DeleteCommands() + delcommand Break + delcommand Clear + delcommand Step + delcommand Over + delcommand Until + delcommand Finish + delcommand Run + delcommand Arguments + delcommand Stop + delcommand Continue + delcommand Evaluate + delcommand Gdb + delcommand Program + delcommand Source + delcommand Asm + delcommand Winbar + + if exists('s:k_map_saved') + if empty(s:k_map_saved) + nunmap K + else + call mapset(s:k_map_saved) + endif + unlet s:k_map_saved + endif + + if has('menu') + " Remove the WinBar entries from all windows where it was added. + let curwinid = win_getid(winnr()) + for winid in s:winbar_winids + if win_gotoid(winid) + aunmenu WinBar.Step + aunmenu WinBar.Next + aunmenu WinBar.Finish + aunmenu WinBar.Cont + aunmenu WinBar.Stop + aunmenu WinBar.Eval + endif + endfor + call win_gotoid(curwinid) + let s:winbar_winids = [] + + if exists('s:saved_mousemodel') + let &mousemodel = s:saved_mousemodel + unlet s:saved_mousemodel + aunmenu PopUp.-SEP3- + aunmenu PopUp.Set\ breakpoint + aunmenu PopUp.Clear\ breakpoint + aunmenu PopUp.Run\ until + aunmenu PopUp.Evaluate + endif + endif + + call sign_unplace('TermDebug') + unlet s:breakpoints + unlet s:breakpoint_locations + + call sign_undefine('debugPC') + call sign_undefine(s:BreakpointSigns->map("'debugBreakpoint' .. v:val")) + let s:BreakpointSigns = [] +endfunc + +" :Until - Execute until past a specified position or current line +func s:Until(at) + if s:stopped + " reset s:stopped here, it may take a bit of time before we get a response + let s:stopped = 0 + call ch_log('assume that program is running after this command') + " Use the fname:lnum format + let at = empty(a:at) ? + \ fnameescape(expand('%:p')) . ':' . line('.') : a:at + call s:SendCommand('-exec-until ' . at) + else + call ch_log('dropping command, program is running: exec-until') + endif +endfunc + +" :Break - Set a breakpoint at the cursor position. +func s:SetBreakpoint(at) + " Setting a breakpoint may not work while the program is running. + " Interrupt to make it work. + let do_continue = 0 + if !s:stopped + let do_continue = 1 + Stop + sleep 10m + endif + + " Use the fname:lnum format, older gdb can't handle --source. + let at = empty(a:at) ? + \ fnameescape(expand('%:p')) . ':' . line('.') : a:at + call s:SendCommand('-break-insert ' . at) + if do_continue + Continue + endif +endfunc + +" :Clear - Delete a breakpoint at the cursor position. +func s:ClearBreakpoint() + let fname = fnameescape(expand('%:p')) + let lnum = line('.') + let bploc = printf('%s:%d', fname, lnum) + if has_key(s:breakpoint_locations, bploc) + let idx = 0 + let nr = 0 + for id in s:breakpoint_locations[bploc] + if has_key(s:breakpoints, id) + " Assume this always works, the reply is simply "^done". + call s:SendCommand('-break-delete ' . id) + for subid in keys(s:breakpoints[id]) + call sign_unplace('TermDebug', + \ #{id: s:Breakpoint2SignNumber(id, subid)}) + endfor + unlet s:breakpoints[id] + unlet s:breakpoint_locations[bploc][idx] + let nr = id + break + else + let idx += 1 + endif + endfor + if nr != 0 + if empty(s:breakpoint_locations[bploc]) + unlet s:breakpoint_locations[bploc] + endif + echomsg 'Breakpoint ' . id . ' cleared from line ' . lnum . '.' + else + echoerr 'Internal error trying to remove breakpoint at line ' . lnum . '!' + endif + else + echomsg 'No breakpoint to remove at line ' . lnum . '.' + endif +endfunc + +func s:Run(args) + if a:args != '' + call s:SendResumingCommand('-exec-arguments ' . a:args) + endif + call s:SendResumingCommand('-exec-run') +endfunc + +func s:SendEval(expr) + " check for "likely" boolean expressions, in which case we take it as lhs + if a:expr =~ "[=!<>]=" + let exprLHS = a:expr + else + " remove text that is likely an assignment + let exprLHS = substitute(a:expr, ' *=.*', '', '') + endif + + " encoding expression to prevent bad errors + let expr = a:expr + let expr = substitute(expr, '\\', '\\\\', 'g') + let expr = substitute(expr, '"', '\\"', 'g') + call s:SendCommand('-data-evaluate-expression "' . expr . '"') + let s:evalexpr = exprLHS +endfunc + +" :Evaluate - evaluate what is specified / under the cursor +func s:Evaluate(range, arg) + let expr = s:GetEvaluationExpression(a:range, a:arg) + let s:ignoreEvalError = 0 + call s:SendEval(expr) +endfunc + +" get what is specified / under the cursor +func s:GetEvaluationExpression(range, arg) + if a:arg != '' + " user supplied evaluation + let expr = s:CleanupExpr(a:arg) + " DSW: replace "likely copy + paste" assignment + let expr = substitute(expr, '"\([^"]*\)": *', '\1=', 'g') + elseif a:range == 2 + " no evaluation but provided but range set + let pos = getcurpos() + let reg = getreg('v', 1, 1) + let regt = getregtype('v') + normal! gv"vy + let expr = s:CleanupExpr(@v) + call setpos('.', pos) + call setreg('v', reg, regt) + else + " no evaluation provided: get from C-expression under cursor + " TODO: allow filetype specific lookup #9057 + let expr = expand('<cexpr>') + endif + return expr +endfunc + +" clean up expression that may get in because of range +" (newlines and surrounding whitespace) +" As it can also be specified via ex-command for assignments this function +" may not change the "content" parts (like replacing contained spaces) +func s:CleanupExpr(expr) + " replace all embedded newlines/tabs/... + let expr = substitute(a:expr, '\_s', ' ', 'g') + + if &filetype ==# 'cobol' + " extra cleanup for COBOL: + " - a semicolon nmay be used instead of a space + " - a trailing comma or period is ignored as it commonly separates/ends + " multiple expr + let expr = substitute(expr, ';', ' ', 'g') + let expr = substitute(expr, '[,.]\+ *$', '', '') + endif + + " get rid of leading and trailing spaces + let expr = substitute(expr, '^ *', '', '') + let expr = substitute(expr, ' *$', '', '') + return expr +endfunc + +let s:ignoreEvalError = 0 +let s:evalFromBalloonExpr = 0 + +" Handle the result of data-evaluate-expression +func s:HandleEvaluate(msg) + let value = a:msg + \ ->substitute('.*value="\(.*\)"', '\1', '') + \ ->substitute('\\"', '"', 'g') + \ ->substitute('\\\\', '\\', 'g') + "\ multi-byte characters arrive in octal form, replace everything but NULL values + \ ->substitute('\\000', s:NullRepl, 'g') + \ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g') + "\ Note: GDB docs also mention hex encodings - the translations below work + "\ but we keep them out for performance-reasons until we actually see + "\ those in mi-returns + "\ ->substitute('\\0x00', s:NullRep, 'g') + "\ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g') + \ ->substitute(s:NullRepl, '\\000', 'g') + if s:evalFromBalloonExpr + if s:evalFromBalloonExprResult == '' + let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value + else + let s:evalFromBalloonExprResult .= ' = ' . value + endif + call balloon_show(s:evalFromBalloonExprResult) + else + echomsg '"' . s:evalexpr . '": ' . value + endif + + if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$' + " Looks like a pointer, also display what it points to. + let s:ignoreEvalError = 1 + call s:SendEval('*' . s:evalexpr) + else + let s:evalFromBalloonExpr = 0 + endif +endfunc + +" Show a balloon with information of the variable under the mouse pointer, +" if there is any. +func TermDebugBalloonExpr() + if v:beval_winid != s:sourcewin + return '' + endif + if !s:stopped + " Only evaluate when stopped, otherwise setting a breakpoint using the + " mouse triggers a balloon. + return '' + endif + let s:evalFromBalloonExpr = 1 + let s:evalFromBalloonExprResult = '' + let s:ignoreEvalError = 1 + let expr = s:CleanupExpr(v:beval_text) + call s:SendEval(expr) + return '' +endfunc + +" Handle an error. +func s:HandleError(msg) + if s:ignoreEvalError + " Result of s:SendEval() failed, ignore. + let s:ignoreEvalError = 0 + let s:evalFromBalloonExpr = 0 + return + endif + let msgVal = substitute(a:msg, '.*msg="\(.*\)"', '\1', '') + echoerr substitute(msgVal, '\\"', '"', 'g') +endfunc + +func s:GotoSourcewinOrCreateIt() + if !win_gotoid(s:sourcewin) + new + let s:sourcewin = win_getid(winnr()) + call s:InstallWinbar() + endif +endfunc + +func s:GetDisasmWindow() + if exists('g:termdebug_config') + return get(g:termdebug_config, 'disasm_window', 0) + endif + if exists('g:termdebug_disasm_window') + return g:termdebug_disasm_window + endif + return 0 +endfunc + +func s:GetDisasmWindowHeight() + if exists('g:termdebug_config') + return get(g:termdebug_config, 'disasm_window_height', 0) + endif + if exists('g:termdebug_disasm_window') && g:termdebug_disasm_window > 1 + return g:termdebug_disasm_window + endif + return 0 +endfunc + +func s:GotoAsmwinOrCreateIt() + if !win_gotoid(s:asmwin) + if win_gotoid(s:sourcewin) + exe 'rightbelow new' + else + exe 'new' + endif + + let s:asmwin = win_getid(winnr()) + + setlocal nowrap + setlocal number + setlocal noswapfile + setlocal buftype=nofile + setlocal modifiable + + let asmbuf = bufnr('Termdebug-asm-listing') + if asmbuf > 0 + exe 'buffer' . asmbuf + else + exe 'file Termdebug-asm-listing' + endif + + if s:GetDisasmWindowHeight() > 0 + exe 'resize ' .. s:GetDisasmWindowHeight() + endif + endif + + if s:asm_addr != '' + let lnum = search('^' . s:asm_addr) + if lnum == 0 + if s:stopped + call s:SendCommand('disassemble $pc') + endif + else + call sign_unplace('TermDebug', #{id: s:asm_id}) + call sign_place(s:asm_id, 'TermDebug', 'debugPC', '%', #{lnum: lnum}) + endif + endif +endfunc + +" Handle stopping and running message from gdb. +" Will update the sign that shows the current position. +func s:HandleCursor(msg) + let wid = win_getid(winnr()) + + if a:msg =~ '^\*stopped' + call ch_log('program stopped') + let s:stopped = 1 + elseif a:msg =~ '^\*running' + call ch_log('program running') + let s:stopped = 0 + endif + + if a:msg =~ 'fullname=' + let fname = s:GetFullname(a:msg) + else + let fname = '' + endif + + if a:msg =~ 'addr=' + let asm_addr = s:GetAsmAddr(a:msg) + if asm_addr != '' + let s:asm_addr = asm_addr + + let curwinid = win_getid(winnr()) + if win_gotoid(s:asmwin) + let lnum = search('^' . s:asm_addr) + if lnum == 0 + call s:SendCommand('disassemble $pc') + else + call sign_unplace('TermDebug', #{id: s:asm_id}) + call sign_place(s:asm_id, 'TermDebug', 'debugPC', '%', #{lnum: lnum}) + endif + + call win_gotoid(curwinid) + endif + endif + endif + + if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) + let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') + if lnum =~ '^[0-9]*$' + call s:GotoSourcewinOrCreateIt() + if expand('%:p') != fnamemodify(fname, ':p') +echomsg 'different fname: "' .. expand('%:p') .. '" vs "' .. fnamemodify(fname, ':p') .. '"' + augroup Termdebug + " Always open a file read-only instead of showing the ATTENTION + " prompt, since it is unlikely we want to edit the file. + " The file may be changed but not saved, warn for that. + au SwapExists * echohl WarningMsg + \ | echo 'Warning: file is being edited elsewhere' + \ | echohl None + \ | let v:swapchoice = 'o' + augroup END + if &modified + " TODO: find existing window + exe 'split ' . fnameescape(fname) + let s:sourcewin = win_getid(winnr()) + call s:InstallWinbar() + else + exe 'edit ' . fnameescape(fname) + endif + augroup Termdebug + au! SwapExists + augroup END + endif + exe lnum + normal! zv + call sign_unplace('TermDebug', #{id: s:pc_id}) + call sign_place(s:pc_id, 'TermDebug', 'debugPC', fname, + \ #{lnum: lnum, priority: 110}) + if !exists('b:save_signcolumn') + let b:save_signcolumn = &signcolumn + call add(s:signcolumn_buflist, bufnr()) + endif + setlocal signcolumn=yes + endif + elseif !s:stopped || fname != '' + call sign_unplace('TermDebug', #{id: s:pc_id}) + endif + + call win_gotoid(wid) +endfunc + +let s:BreakpointSigns = [] + +func s:CreateBreakpoint(id, subid, enabled) + let nr = printf('%d.%d', a:id, a:subid) + if index(s:BreakpointSigns, nr) == -1 + call add(s:BreakpointSigns, nr) + if a:enabled == "n" + let hiName = "debugBreakpointDisabled" + else + let hiName = "debugBreakpoint" + endif + call sign_define('debugBreakpoint' .. nr, + \ #{text: substitute(nr, '\..*', '', ''), + \ texthl: hiName}) + endif +endfunc + +func! s:SplitMsg(s) + return split(a:s, '{.\{-}}\zs') +endfunction + +" Handle setting a breakpoint +" Will update the sign that shows the breakpoint +func s:HandleNewBreakpoint(msg, modifiedFlag) + if a:msg !~ 'fullname=' + " a watch or a pending breakpoint does not have a file name + if a:msg =~ 'pending=' + let nr = substitute(a:msg, '.*number=\"\([0-9.]*\)\".*', '\1', '') + let target = substitute(a:msg, '.*pending=\"\([^"]*\)\".*', '\1', '') + echomsg 'Breakpoint ' . nr . ' (' . target . ') pending.' + endif + return + endif + for msg in s:SplitMsg(a:msg) + let fname = s:GetFullname(msg) + if empty(fname) + continue + endif + let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '') + if empty(nr) + return + endif + + " If "nr" is 123 it becomes "123.0" and subid is "0". + " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded. + let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0') + let enabled = substitute(msg, '.*enabled="\([yn]\)".*', '\1', '') + call s:CreateBreakpoint(id, subid, enabled) + + if has_key(s:breakpoints, id) + let entries = s:breakpoints[id] + else + let entries = {} + let s:breakpoints[id] = entries + endif + if has_key(entries, subid) + let entry = entries[subid] + else + let entry = {} + let entries[subid] = entry + endif + + let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '') + let entry['fname'] = fname + let entry['lnum'] = lnum + + let bploc = printf('%s:%d', fname, lnum) + if !has_key(s:breakpoint_locations, bploc) + let s:breakpoint_locations[bploc] = [] + endif + let s:breakpoint_locations[bploc] += [id] + + if bufloaded(fname) + call s:PlaceSign(id, subid, entry) + let posMsg = ' at line ' . lnum . '.' + else + let posMsg = ' in ' . fname . ' at line ' . lnum . '.' + endif + if !a:modifiedFlag + let actionTaken = 'created' + elseif enabled == 'n' + let actionTaken = 'disabled' + else + let actionTaken = 'enabled' + endif + echomsg 'Breakpoint ' . nr . ' ' . actionTaken . posMsg + endfor +endfunc + +func s:PlaceSign(id, subid, entry) + let nr = printf('%d.%d', a:id, a:subid) + call sign_place(s:Breakpoint2SignNumber(a:id, a:subid), 'TermDebug', + \ 'debugBreakpoint' .. nr, a:entry['fname'], + \ #{lnum: a:entry['lnum'], priority: 110}) + let a:entry['placed'] = 1 +endfunc + +" Handle deleting a breakpoint +" Will remove the sign that shows the breakpoint +func s:HandleBreakpointDelete(msg) + let id = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0 + if empty(id) + return + endif + if has_key(s:breakpoints, id) + for [subid, entry] in items(s:breakpoints[id]) + if has_key(entry, 'placed') + call sign_unplace('TermDebug', + \ #{id: s:Breakpoint2SignNumber(id, subid)}) + unlet entry['placed'] + endif + endfor + unlet s:breakpoints[id] + echomsg 'Breakpoint ' . id . ' cleared.' + endif +endfunc + +" Handle the debugged program starting to run. +" Will store the process ID in s:pid +func s:HandleProgramRun(msg) + let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0 + if nr == 0 + return + endif + let s:pid = nr + call ch_log('Detected process ID: ' . s:pid) +endfunc + +" Handle a BufRead autocommand event: place any signs. +func s:BufRead() + let fname = expand('<afile>:p') + for [id, entries] in items(s:breakpoints) + for [subid, entry] in items(entries) + if entry['fname'] == fname + call s:PlaceSign(id, subid, entry) + endif + endfor + endfor +endfunc + +" Handle a BufUnloaded autocommand event: unplace any signs. +func s:BufUnloaded() + let fname = expand('<afile>:p') + for [id, entries] in items(s:breakpoints) + for [subid, entry] in items(entries) + if entry['fname'] == fname + let entry['placed'] = 0 + endif + endfor + endfor +endfunc + +let &cpo = s:keepcpo +unlet s:keepcpo |