" ingo/query/motion.vim: Functions for querying a motion over text. " " DEPENDENCIES: " " Copyright: (C) 2022 Ingo Karkat " The VIM LICENSE applies to this script; see ':help copyright'. " " Maintainer: Ingo Karkat if ! exists('g:IngoLibrary_QueryMotionIgnoredMotions') let g:IngoLibrary_QueryMotionIgnoredMotions = {} endif if ! exists('g:IngoLibrary_QueryMotionCustomMotions') let g:IngoLibrary_QueryMotionCustomMotions = {} endif if ! exists('g:IngoLibrary_QueryMotionCustomMotionModifiers') let g:IngoLibrary_QueryMotionCustomMotionModifiers = 'default value' endif let s:builtInMotions = ingo#dict#FromKeys(['h', "\", "\", "\", 'l', "\", ' ', '0', "\", '^', '$', "\", 'g_', 'g0', "g\", 'g^', 'gm', 'gM', 'g$', "g\", '|', ';', ',', 'k', "\", "\", 'j', "\", "\", "\", 'gk', "g\", 'gj', "g\", '-', '+', "\", "\", '_', 'G', "\", "\", 'gg', 'go', "\", 'w', "\", 'W', 'e', 'E', "\", 'b', "\", 'B', 'ge', 'gE', '(', ')', '{', '}', ']]', '][', '[[', '[]', 'aw', 'iw', 'aW', 'iW', 'as', 'is', 'ap', 'ip', 'a]', 'a[', 'i]', 'i[', 'a)', 'a(', 'ab', 'i)', 'i(', 'ib', 'a>', 'a<', 'i>', 'i<', 'at', 'it', 'a}', 'a{', 'aB', 'i}', 'i{', 'iB', 'a"', "a'", 'a`', 'i"', "i'", 'i`', "\", "\t", "\", 'g;', 'g,', '%', '[(', '[{', '])', ']}', ']m', ']M', '[m', '[M', '[#', ']#', '[*', '[/', ']*', ']/', 'H', 'M', 'L', 'n', 'N', '*', '#', 'g*', 'g#', 'gd', 'gD'], '') call extend(s:builtInMotions, {'f': '\(.\)', 'F': '\(.\)', 't': '\(.\)' , 'T': '\(.\)', ':': '[^\r]*\(\r\)\?', "'": '\([a-zA-Z0-9''`"[\]<>^.(){}]\)', '`': '\([a-zA-Z0-9''`"[\]<>^.(){}])', "g'": '\([a-zA-Z0-9''`"[\]<>^.(){}]\)', 'g`': '\([a-zA-Z0-9''`"[\]<>^.(){}]\)', '/': '[^\r]*\(\r\)\?', '?': '[^\r]*\(\r\)\?'}) let s:builtInMotionModifiers = ingo#collections#ToDict(['v', 'V', "\"]) call extend(s:builtInMotionModifiers, g:IngoLibrary_QueryMotionCustomMotionModifiers, 'force') function! ingo#query#motion#Get( ... ) abort "****************************************************************************** "* PURPOSE: " Obtain a Vim motion / text object from the user. Includes any counts and " registers. Covers both built-in and custom operator-pending mode mappings. "* LIMITATIONS: " - Mappings that consume additional characters (via getchar()) require " definition of what their additional keys look like in " g:IngoLibrary_QueryMotionCustomMotions. " - Unlike the built-in motions, does not handle 'timeoutlen', but will wait " indefinitely for additional keys. " - :[range] and / ? searches do not show the command-line and have to be " typed blindly. "* ASSUMPTIONS / PRECONDITIONS: " None. "* EFFECTS / POSTCONDITIONS: " None. "* INPUTS: " a:options.isAllowRegister Flag that allows capture of "{register} before " or after a count. Off by default, as " operator-pending motions cannot take a register; " it has to be specified before the operation. "* RETURN VALUES: " A motion that can then be appended to an operator command to apply the " operator to the text. Or empty String if the motion was aborted (via ). "****************************************************************************** let l:options = (a:0 ? a:1 : {}) let l:isAllowRegister = get(l:options, 'isAllowRegister', 0) let l:count = '' let l:register = '' let l:motionModifier = '' let l:motion = '' let l:appendagePattern = '' let l:motionAppendage = '' while 1 let l:key = ingo#compat#getcharstr() if l:key ==# "\" return '' elseif empty(l:motion) if l:isAllowRegister && l:key ==# '"' && empty(l:register) let l:register = l:key " Start of register continue elseif l:key =~# '^[1-9]$' && empty(l:count) let l:count = l:key " Start of count continue elseif len(l:register) == 1 let l:register .= l:key " Completion of register continue elseif l:key =~# '^\d$' && ! empty(l:count) let l:count .= l:key " More count continue elseif has_key(s:builtInMotionModifiers, l:key) " Forcing a motion (:help forced-motion) to be character- / " line- / blockwise. Last one wins here. let l:motionModifier = l:key continue endif endif if empty(l:appendagePattern) let l:motion .= l:key if ! empty(maparg(l:motion, 'o')) && ! has_key(g:IngoLibrary_QueryMotionIgnoredMotions, l:motion) if has_key(g:IngoLibrary_QueryMotionCustomMotions, l:motion) let l:appendagePattern = g:IngoLibrary_QueryMotionCustomMotions[l:motion] if empty(l:appendagePattern) break " Plain custom motion; we're done. endif else break " A complete custom mapping has been input. endif elseif has_key(s:builtInMotions, l:motion) && (empty(mapcheck(l:motion, 'o')) || has_key(g:IngoLibrary_QueryMotionIgnoredMotions, l:motion)) let l:appendagePattern = s:builtInMotions[l:motion] if empty(l:appendagePattern) break " Plain built-in motion; we're done. endif endif else let l:motionAppendage .= l:key let l:appendageMatches = matchlist(l:motionAppendage, ingo#regexp#Anchored(l:appendagePattern)) if empty(l:appendageMatches) return '' " Invalid appendage. elseif ! empty(l:appendageMatches[1]) " Something is in the first capture group. break " The motion appendage is complete; we're done. endif " This queries more than one key. " Handle for a minimal command-line experience (as those " motions that take multiple keys are likely : or / or ?). if l:key ==# "\" && l:motionAppendage =~# "\" let l:motionAppendage = substitute(l:motionAppendage, ".\$", '', '') redraw | echo l:motion . l:motionAppendage else " Echo what's getting typed to give better user feedback. if l:motionAppendage =~# '^.$' echo l:motion . l:motionAppendage else echon l:key endif endif endif endwhile return l:count . l:register . l:motionModifier . l:motion . l:motionAppendage endfunction " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :