├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CountJump.manifest ├── README.md ├── autoload ├── CountJump.vim └── CountJump │ ├── Mappings.vim │ ├── Motion.vim │ ├── Region.vim │ ├── Region │ ├── Motion.vim │ └── TextObject.vim │ └── TextObject.vim ├── doc └── CountJump.txt └── tests ├── CountJump.txt ├── CountJumpRegion.txt ├── Region.txt └── TextObject.txt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: The plugin isn't working at all or shows wrong or unexpected behavior 4 | title: '' 5 | labels: '' 6 | 7 | --- 8 | **Frequent Issues** 9 | 10 | * **E117: Unknown function: ingo#...**: Have you installed the [ingo-library plugin](http://www.vim.org/scripts/script.php?script_id=4433) (or via [GitHub](https://github.com/inkarkat/vim-ingo-library)) as well, as documented in the _dependencies_ section of the readme and plugin help? 11 | * **Sudden problems after updating**: Did you check out the default _master_ branch? Unless you want to participate in feature development and alpha testing, I would recommend that you switch from _master_ to the _stable_ branch; this way, you'll only update to released versions that are (hopefully) better tested and documented. 12 | * **Neovim**: I don't explicitly consider nor test Neovim compatibility; my plugins are written for Vim. If a plugin can be made to work on Neovim with trivial changes, I wouldn't mind including them, but anything more involved should in my opinion be filed as a compatibility bug against Neovim (as its homepage proclaims: _Fully compatible with Vim's editing model and the Vimscript language._) 13 | 14 | **Describe the bug** 15 | 16 | _A clear and concise description of what the bug is._ 17 | 18 | **How to Reproduce** 19 | 20 | _Detailed steps to reproduce the behavior._ 21 | 22 | **Expected Behavior** 23 | 24 | _A clear and concise description of what you expected to happen._ 25 | 26 | **Environment** 27 | - Plugin version (e.g. stable version _1.10_) / revision _a1b2c3d4_ from the master branch 28 | - Dependency versions (e.g. [ingo-library plugin](https://github.com/inkarkat/vim-ingo-library), or external tool versions) 29 | - Vim version (e.g. _8.1.1234_) Or paste the result of `vim --version`. 30 | - OS: (e.g. _Ubuntu 18.04_, _Windows 10 1809_, _macOS 10.14_) 31 | - Install method: (e.g. manually via Vimball or ZIP file, GitHub clone as pack plugin, Plugin manager _NAME_) 32 | - Other plugins / additional context if you think this could be important 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an enhancement for the plugin 4 | title: '' 5 | labels: enhancement 6 | 7 | --- 8 | 9 | **Motivation** 10 | 11 | _A clear and concise description of what currently is hard to do._ 12 | 13 | **Request and Purpose** 14 | 15 | _A clear and concise description of what you want to happen. Possible fit criteria._ 16 | 17 | **Alternatives** 18 | 19 | _A clear and concise description of any alternative solutions or features you've considered._ 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.msgout 2 | *.msgresult 3 | *.out 4 | *.tap 5 | doc/*.description 6 | doc/*.install 7 | tags 8 | -------------------------------------------------------------------------------- /CountJump.manifest: -------------------------------------------------------------------------------- 1 | autoload/CountJump.vim 2 | autoload/CountJump/Mappings.vim 3 | autoload/CountJump/Motion.vim 4 | autoload/CountJump/Region.vim 5 | autoload/CountJump/Region/Motion.vim 6 | autoload/CountJump/Region/TextObject.vim 7 | autoload/CountJump/TextObject.vim 8 | doc/CountJump.txt 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | COUNT JUMP 2 | =============================================================================== 3 | _by Ingo Karkat_ 4 | 5 | DESCRIPTION 6 | ------------------------------------------------------------------------------ 7 | 8 | Though it is not difficult to write a custom movement (basically a :map 9 | that executes some kind of search or jump) and a custom text-object (an 10 | :omap that selects a range of text), this is too complex for a novice user 11 | and often repetitive. 12 | This plugin covers the common use case where the movement and boundaries of a 13 | text object can be specified via start and end patterns, and offers a single 14 | function to set up related mappings. With it, you can enhance some built-in 15 | Vim mappings to take an optional [count], and quickly define new mappings for 16 | help file sections, diff hunks, embedded macros, and so on... 17 | 18 | As a generalization of the start and end patterns, the movement and boundaries 19 | of a text object can also be specified via jump functions, i.e. Funcrefs of 20 | functions that position the cursor on the appropriate location and return that 21 | location. This can be used where the jump is difficult to express with a 22 | single regular expression, the jump may need adapting depending on the 23 | context, or other uses. 24 | This plugin contains some support for movement and text objects consisting of 25 | text regions that can be defined by continuous lines that match a particular 26 | pattern, e.g. comment blocks that all start with /^\\s\*#/. 27 | 28 | ### SEE ALSO 29 | 30 | The following ftplugins use this plugin: 31 | 32 | - diff\_movement.vim ([vimscript #3180](http://www.vim.org/scripts/script.php?script_id=3180)): 33 | Movement over diff hunks with ]] etc. 34 | - diffwindow\_movement.vim ([vimscript #3719](http://www.vim.org/scripts/script.php?script_id=3719)): 35 | Movement over changes in a diff window. 36 | - dosbatch\_movement.vim ([vimscript #4004](http://www.vim.org/scripts/script.php?script_id=4004)): 37 | Movement over MSDOS batch file functions / labels with ]m etc. 38 | - fortunes\_movement.vim ([vimscript #3181](http://www.vim.org/scripts/script.php?script_id=3181)): 39 | Movement over email fortunes with ]] etc. 40 | - help\_movement.vim ([vimscript #3179](http://www.vim.org/scripts/script.php?script_id=3179)): 41 | Movement over Vim help sections with ]] etc. 42 | - mail\_movement.vim ([vimscript #3182](http://www.vim.org/scripts/script.php?script_id=3182)): 43 | Movement over email quotes with ]] etc. 44 | - vbs\_movement.vim ([vimscript #4003](http://www.vim.org/scripts/script.php?script_id=4003)): 45 | Movement over VBScript classes / functions / properties / subs with ]m etc. 46 | - vim\_movement.vim ([vimscript #4002](http://www.vim.org/scripts/script.php?script_id=4002)): 47 | Movement over Vim functions with ]m etc. 48 | 49 | The following plugins offer new motions through this plugin: 50 | 51 | - ConflictMotions.vim ([vimscript #3991](http://www.vim.org/scripts/script.php?script_id=3991)): 52 | Motions to and inside SCM conflict markers. 53 | - JumpToTrailingWhitespace.vim ([vimscript #3968](http://www.vim.org/scripts/script.php?script_id=3968)): 54 | Motions to locate unwanted whitespace at the end of lines. 55 | - JumpToVerticalBlock.vim ([vimscript #5657](http://www.vim.org/scripts/script.php?script_id=5657)): 56 | Like W / E, but vertically in the same column. 57 | - JumpToVerticalOccurrence.vim ([vimscript #4841](http://www.vim.org/scripts/script.php?script_id=4841)): 58 | Like f{char}, but searching the same screen column, not line. 59 | - SameSyntaxMotion.vim ([vimscript #4338](http://www.vim.org/scripts/script.php?script_id=4338)): 60 | Motions to the borders of the same syntax highlighting. 61 | - TaskMotion.vim ([vimscript #3990](http://www.vim.org/scripts/script.php?script_id=3990)): 62 | Motions to task and TODO markers. 63 | 64 | ### RELATED WORKS 65 | 66 | - motpat.vim ([vimscript #3030](http://www.vim.org/scripts/script.php?script_id=3030)) offers similar functions to setup motion 67 | mappings, but no text objects (yet). 68 | - textobj-user ([vimscript #2100](http://www.vim.org/scripts/script.php?script_id=2100)) has support for user-defined text objects via 69 | regular expressions, but they don't support selecting multiple via [count]. 70 | - movealong.vim ([vimscript #4691](http://www.vim.org/scripts/script.php?script_id=4691)) provides a :Movealong command (and optional 71 | mappings) that repeatedly executes a motion until a condition of a syntax, 72 | pattern match, or arbitrary expression is met. 73 | 74 | USAGE 75 | ------------------------------------------------------------------------------ 76 | 77 | The plugin defines several functions, which set up the appropriate mappings 78 | based on the arguments that you supply. The following is an overview; you'll 79 | find the details directly in the implementation files in the 80 | autoload/CountJump/ directory. 81 | 82 | CountJump#Motion#MakeBracketMotion( mapArgs, keyAfterBracket, inverseKeyAfterBracket, patternToBegin, patternToEnd, isEndPatternToEnd, ... ) 83 | 84 | This function sets up mappings starting with [ and ] for movement (with 85 | optional [count]) relative to the current cursor position, targeting either a 86 | text pattern at the beginning ([{keyAfterBracket} mapping) or a text pattern 87 | at the end (]{inverseKeyAfterBracket} mapping) of whatever you want to treat 88 | as a text block. 89 | 90 | CountJump#Motion#MakeBracketMotionWithJumpFunctions( mapArgs, keyAfterBracket, inverseKeyAfterBracket, JumpToBeginForward, JumpToBeginBackward, JumpToEndForward, JumpToEndBackward, isEndJumpToEnd, ... ) 91 | 92 | This function sets up mappings starting with [ and ] for movement (with 93 | optional [count]) relative to the current cursor position, but rely on four 94 | passed jump functions instead of text patterns to do the movement. 95 | 96 | CountJump#TextObject#MakeWithCountSearch( mapArgs, textObjectKey, types, selectionMode, patternToBegin, patternToEnd ) 97 | 98 | Defines a complete set of mappings for inner and/or outer text objects that 99 | support an optional [count] and are driven by search patterns for the 100 | beginning and end of a block. Outer text objects include the matched pattern 101 | text, inner ones not. Selection can be characterwise, linewise or blockwise. 102 | 103 | CountJump#TextObject#MakeWithJumpFunctions( mapArgs, textObjectKey, types, selectionMode, JumpToBegin, JumpToEnd ) 104 | 105 | This is a generalization of CountJump#TextObject#MakeWithCountSearch() that 106 | invokes custom functions instead of searching for a fixed pattern. This is 107 | useful if the check for a match is too complex for a single regular 108 | expression, or if you need to adjust the match position depending on the 109 | circumstances. 110 | 111 | Often, a region can be defined as a block of continuous lines that all match a 112 | certain pattern (or, even more generic, where a provided predicate function 113 | returns a match position). The following functions aid in implementing 114 | movements to the boundaries of these regions and text objects consisting of 115 | the region: 116 | 117 | CountJump#Region#JumpToRegionEnd( count, Expr, isMatch, step, isToEndOfLine ) 118 | 119 | Starting from the current line, search for the position where the count'th 120 | region ends. Use this function to build Funcrefs for forward / backward jumps 121 | that can then be passed to CountJump#TextObject#MakeWithJumpFunctions(). 122 | 123 | CountJump#Region#JumpToNextRegion( count, Expr, isMatch, step, isAcrossRegion, isToEndOfLine ) 124 | 125 | Starting from the current line, search for the position where the count'th 126 | region begins/ends. 127 | 128 | CountJump#Region#Motion#MakeBracketMotion( mapArgs, keyAfterBracket, inverseKeyAfterBracket, Expr, isMatch, ... ) 129 | 130 | This function sets up mappings starting with [ and ] for movement (with 131 | optional [count]) relative to the current cursor position, targeting a text 132 | region defined by contiguous lines that (don't) match a:Expr. 133 | 134 | CountJump#Region#TextObject#Make( mapArgs, textObjectKey, types, selectionMode, Expr, isMatch ) 135 | 136 | Defines a complete set of mappings for inner and/or outer text objects that 137 | support an optional [count] and select regions of lines which are defined by 138 | contiguous lines that (don't) match a:Expr. 139 | The inner text object comprises all lines of the region itself, while the 140 | outer text object also includes all adjacent lines above and below which do 141 | not themselves belong to a region. 142 | 143 | The custom Funcrefs for jumps and predicates of lines belonging to a range may 144 | be invoked multiple times until the CountJump function arrives at its 145 | destination. To help the Funcrefs to determine where in this sequence they 146 | are, an empty g:CountJump_MotionContext dictionary is initialized at the 147 | start of a range motion (for pattern / jump functions, this isn't necessary; 148 | there's either no custom code or just a single invocation), and an empty 149 | g:CountJump_TextObjectContext dictionary is initialized at the start of a text 150 | object. Funcrefs can put custom information (e.g. the particular comment 151 | prefix on the current line) in there and evaluate this in subsequent 152 | invocations. 153 | 154 | EXAMPLE 155 | ------------------------------------------------------------------------------ 156 | 157 | Let's illustrate the usage by developing custom motions and text objects for 158 | Pascal begin..end blocks. 159 | 160 | We want to move around blocks, and override the default section movements for 161 | it: 162 | ]] Go to [count] next start of a block. 163 | ][ Go to [count] next end of a block. 164 | [[ Go to [count] previous start of a block. 165 | [] Go to [count] previous end of a block. 166 | 167 | call CountJump#Motion#MakeBracketMotion('', '', '', '\c^begin\n\zs', '\c^.*\nend', 0) 168 | The begin pattern positions the cursor on the beginning of the line following 169 | the "begin" keyword, the end pattern on the beginning of the line 170 | preceding the "end" keyword. 171 | 172 | We want to select a block, either including or excluding the lines with the 173 | begin..end keywords: 174 | ib "inner block" text object, select [count] contents of 175 | a block. 176 | ab "a block" text object, select [count] blocks. 177 | 178 | call CountJump#TextObject#MakeWithCountSearch('', 'b', 'ai', 'V', '\c^begin\n', '\c^end.*$') 179 | 180 | If there is a filetype detection for Pascal files, we can simply put the 181 | above calls in a ~/.vim/ftplugin/pascal\_movement.vim script and are done. 182 | 183 | INSTALLATION 184 | ------------------------------------------------------------------------------ 185 | 186 | The code is hosted in a Git repo at 187 | https://github.com/inkarkat/vim-CountJump 188 | You can use your favorite plugin manager, or "git clone" into a directory used 189 | for Vim packages. Releases are on the "stable" branch, the latest unstable 190 | development snapshot on "master". 191 | 192 | This script is also packaged as a vimball. If you have the "gunzip" 193 | decompressor in your PATH, simply edit the \*.vmb.gz package in Vim; otherwise, 194 | decompress the archive first, e.g. using WinZip. Inside Vim, install by 195 | sourcing the vimball or via the :UseVimball command. 196 | 197 | vim CountJump*.vmb.gz 198 | :so % 199 | 200 | To uninstall, use the :RmVimball command. 201 | 202 | ### DEPENDENCIES 203 | 204 | - Requires Vim 7.0 or higher. 205 | - Requires the ingo-library.vim plugin ([vimscript #4433](http://www.vim.org/scripts/script.php?script_id=4433)), version 1.041 or 206 | higher. 207 | 208 | INTEGRATION 209 | ------------------------------------------------------------------------------ 210 | 211 | If you want to define motions that do not start with [ / ], and the plugin 212 | that employs CountJump offers a configuration variable like 213 | g:PluginName\_mapping to influence the mapped key(s), you can define 214 | intermediate <Plug>-mappings (using-<Plug>), and then define your own custom 215 | mappings based on them: 216 | 217 | let g:PluginName_mapping = 'PluginName%s' 218 | nmap { PluginNameBackward 219 | nmap } PluginNameForward 220 | omap { PluginNameBackward 221 | omap } PluginNameForward 222 | vmap { PluginNameBackward 223 | vmap } PluginNameForward 224 | 225 | If you want to define text objects that do not start with i / a, and the plugin 226 | that employs CountJump offers a configuration variable like 227 | g:PluginName\_mapping to influence the mapped key(s), you can define 228 | intermediate <Plug>-mappings (using-<Plug>), and then define your own custom 229 | mappings based on them: 230 | 231 | let g:PluginName_mapping = 'PluginName%s' 232 | omap ,p PluginNameInner 233 | omap ,P PluginNameOuter 234 | vmap ,p PluginNameInner 235 | vmap ,P PluginNameOuter 236 | 237 | KNOWN PROBLEMS 238 | ------------------------------------------------------------------------------ 239 | 240 | - An outer text object cannot consist of the same, multiple characters; 241 | nothing will be selected (because the end pattern also matches at the begin 242 | position). A same single character pattern works, though. 243 | - For blockwise text objects, the original cursor position should be required 244 | to be inside the selection. However, this requires translation of the 245 | byte-indices here into screen columns, and is thus non-trivial to implement. 246 | - The behavior with wrap messages is slightly inconsistent: Like normal 247 | /pattern search, we print the wrap message, but don't print an error message 248 | (like "Pattern not found"), but beep instead (like the built-in ]m etc. 249 | mappings (but not the ), }, ]] mappings, which fail silently?!)). 250 | - A repeat (via .) of the operator-pending mapping loses the previously 251 | given [count], and operates on just one search / jump. 252 | 253 | ### IDEAS 254 | 255 | - Add customization parameter so that the motion / text object includes the 256 | start / end of buffer in case patternToBegin / patternToEnd do not match any 257 | more. 258 | 259 | ### CONTRIBUTING 260 | 261 | Report any bugs, send patches, or suggest features via the issue tracker at 262 | https://github.com/inkarkat/vim-CountJump/issues or email (address below). 263 | 264 | HISTORY 265 | ------------------------------------------------------------------------------ 266 | 267 | ##### 1.91 03-Apr-2020 268 | - ENH: Allow Funcref for a:searchArguments / first search argument / 269 | a:patternTo{Begin,End} 270 | - ENH: Allow to influence given [count] via 271 | CountJump#CountCountJumpWithWrapMessage() variant of 272 | CountJump#CountJumpWithWrapMessage(). 273 | - FIX: Avoid "Error detected while processing :" additional line above an 274 | exception thrown (from a client-supplied Funcref). 275 | - ENH: Allow client-supplied Funcrefs to throw a "CountJump: <error text>" 276 | exception to issue custom errors. 277 | - Prevent a custom text object from clobbering the register passed to a custom 278 | operator (as :omaps defined by this plugin). 279 | 280 | __You need to update to ingo-library ([vimscript #4433](http://www.vim.org/scripts/script.php?script_id=4433)) version 1.041!__ 281 | 282 | ##### 1.90 11-Feb-2018 283 | - Rename g:CountJump\_Context to g:CountJump\_MotionContext, to avoid a clash 284 | with the text object in CountJump#TextObject#TextObjectWithJumpFunctions(). 285 | Clear g:CountJump\_MotionContext at the end of the function. 286 | - Rename g:CountJump\_Context to g:CountJump\_TextObjectContext. When using 287 | CountJump#Region#TextObject#Make(), the generated jump functions also set 288 | that context, and there's a clash with 289 | CountJump#TextObject#TextObjectWithJumpFunctions(). In particular, one 290 | cannot store a context for the entire text object (both jumps to begin and 291 | end), as the individual jump functions clear the identical context. Clear 292 | g:CountJump\_TextObjectContext at the end of the function. 293 | - ENH: Support non-argument Funcref for a:Expr that gets evaluated once at the 294 | beginning, and should yield a regular expression that is then used in its 295 | stead. 296 | 297 | ##### 1.86 24-Jul-2017 298 | - CountJump#Motion#MakeBracketMotion(): The a:patternToBegin, a:patternToEnd, 299 | a:searchName arguments may contain special characters that need escaping in 300 | a map. Use ingo#escape#command#mapescape(). 301 | - CountJump#Region#Motion#MakeBracketMotion(): The a:Expr argument may contain 302 | special characters that need escaping in a map. Use 303 | ingo#escape#command#mapescape(). 304 | - Retire duplicated fallback for ingo#motion#helper#AdditionalMovement(); 305 | since version 1.85, the ingo-library is now a mandatory dependency. 306 | - CountJump#Motion#MakeBracketMotionWithJumpFunctions(), 307 | CountJump#TextObject#MakeWithJumpFunctions(), 308 | CountJump#Region#Motion#MakeBracketMotion(): Catch all exceptions and 309 | report only the text. My ErrorMotion.vim plugin could be slow to find the 310 | next error if there is none. Aborting with <C-c> would print a long 311 | multi-line exception: "Error detected while processing function 312 | ErrorMotion#Forward[1]..CountJump#JumpFunc[39]..HlgroupMotion#JumpWithWrapMessage[26]..CountJump#CountJumpFuncWithWrapMessage[35]..HlgroupMotion#SearchFirstHlgroup: 313 | line 67: Interrupted". As we apparently cannot avoid the printing of "Type 314 | :quit<Enter> to exit Vim", suppress the Vim:Interrupt exception, and 315 | :echoerr all others. 316 | 317 | ##### 1.85 23-Dec-2014 318 | - Use ingo/pos.vim. 319 | - Use ingo#msg#WarningMsg(). 320 | - Make test for 'virtualedit' option values also account for multiple values. 321 | 322 | __You need to install / update to ingo-library ([vimscript #4433](http://www.vim.org/scripts/script.php?script_id=4433)) version 323 | 1.019! It is now mandatory for the plugin.__ 324 | 325 | ##### 1.84 25-Apr-2014 326 | - Pin down the 'virtualedit' setting (to "onemore") during 327 | CountJump#TextObject#TextObjectWithJumpFunctions() to avoid that a 328 | characterwise outer text object that ends at the end of a line includes the 329 | line's newline character when 'selection' is "exclusive". 330 | - FIX: There are no buffer-local functions with a b: scope prefix, and Vim 331 | 7.4.264 disallows those invalid function names now. Previously, multiple 332 | buffer-local text objects with the same key would override each other. 333 | Instead, make the functions created by 334 | CountJump#TextObject#MakeWithCountSearch() and 335 | CountJump#Region#TextObject#Make() buffer-scoped by prefixing "s:B" and the 336 | buffer number. 337 | 338 | ##### 1.83 23-Jan-2014 339 | - Use more canonical way of invoking the Funcrefs in 340 | CountJump#Motion#MakeBracketMotionWithJumpFunctions(); this will then also 341 | work with passed String function names. 342 | - FIX: Need to save v:count1 before issuing the normal mode "gv" command. 343 | - Minor: Make substitute() robust against 'ignorecase'. 344 | - Add optional dependency to ingo-library ([vimscript #4433](http://www.vim.org/scripts/script.php?script_id=4433)). 345 | 346 | ##### 1.82 30-Oct-2012 (unreleased) 347 | - FIX: In text objects, when the end position is before the begin position, 348 | that's not a valid selection. Test for this and abort in that case. 349 | - For linewise selections, always position the cursor at the start of the end 350 | line to be consistent with the built-in text objects, and to avoid 351 | complicating the search patterns when attempting to do this through them. 352 | 353 | ##### 1.81 16-Oct-2012 354 | - ENH: Add optional a:searchName argument to 355 | CountJump#Motion#MakeBracketMotion() to make searches wrap around when 356 | 'wrapscan' is set. Custom jump functions can do this since version 1.70; 357 | now, this can also be utilized by motions defined via a search pattern. 358 | - BUG: Wrong variable scope for copied a:isBackward in 359 | CountJump#CountSearchWithWrapMessage(). 360 | 361 | ##### 1.80 15-Oct-2012 362 | - FIX: In CountJump#TextObject#TextObjectWithJumpFunctions(), do not beep when 363 | there's no end position. In this case, the jump function (often 364 | CountJump#CountSearch()) should have emitted a beep already, and we want to 365 | avoid a double beep. 366 | - Also handle move to the buffer's very last character in operator-pending 367 | mode with a pattern to end "O" motion. 368 | - Add CountJump#CountJumpFuncWithWrapMessage() / CountJump#CountJumpFunc() to 369 | help implement custom motions with only a simple function that performs a 370 | single jump. 371 | - FIX: Visual end pattern / jump to end with 'selection' set to "exclusive" 372 | also requires the special additional treatment of moving one right, like 373 | operator-pending mode. 374 | - BUG: Operator-pending motion with end pattern / jump to end operates on one 375 | character too few when moving to begin. 376 | - Clear any previous wrap message when wrapping is enabled; it's confusing 377 | otherwise. 378 | 379 | ##### 1.70 03-Sep-2012 380 | - ENH: Check for searches wrapping around the buffer and issue a corresponding 381 | warning, like the built-in searches do. Though the mappings that can be made 382 | with CountJump currently do not use 'wrapscan', other plugins that define 383 | their own jump functions and use the CountJump#CountJump() function for it 384 | may use it. Create function overloads CountJump#CountJumpWithWrapMessage() 385 | and CountJump#CountSearchWithWrapMessage(). 386 | 387 | ##### 1.60 27-Mar-2012 388 | - ENH: Allow motions that do not start with [ / ] and text objects that do not 389 | start with i / a by passing keys that begin with <Plug>. With this, plugins 390 | using CountJump can offer the expected customizability. Since most users 391 | probably still prefer the default keys, it is recommended that plugins do 392 | not use <Plug> mappings from the start, but make the a:keyAfterBracket / 393 | a:inverseKeyAfterBracket / a:textObjectKey configurable via a 394 | g:PluginName\_mapping variable, and instruct users to set this to 395 | "<Plug>PluginName%s" and create their own mappings based on them, as 396 | described in CountJump-integration. 397 | 398 | ##### 1.50 30-Aug-2011 399 | - For regions of lines, also support a match()-like Funcref instead of a 400 | pattern to define the range. This for example enables to define a range of 401 | diff changes via a predicate function that checks diff\_hlID() != 0. 402 | - Initialize global g:CountJump\_Context object for custom use by Funcrefs. 403 | 404 | ##### 1.41 13-Jun-2011 405 | - FIX: Directly ring the bell to avoid problems when running under :silent!. 406 | 407 | ##### 1.40 20-Dec-2010 408 | - ENH: Added CountJump#Region#TextObject#Make() to easily define text objects 409 | for regions. 410 | - Interface change: Jump functions again return position (and actual, 411 | corrected one for a:isToEndOfLine). Though the position is not used for 412 | motions, it is necessary for text objects to differentiate between "already 413 | at the begin/end position" and "no such position". 414 | 415 | ##### 1.30 20-Dec-2010 416 | - ENH: Added CountJump#Region#Motion#MakeBracketMotion() to easily define 417 | bracket motions for regions. 418 | - Interface changes: 419 | - Jump functions don't necessarily return jump position any more; this 420 | special case is only required for text objects. 421 | - Moved CountJump#Region#Jump() to CountJump#JumpFunc(). 422 | - Added a:isToEndOfLine argument to CountJump#Region#JumpToRegionEnd() and 423 | CountJump#Region#JumpToNextRegion(), which is useful for operator-pending 424 | and characterwise visual mode mappings; the entire last line will then be 425 | operated on / selected. 426 | - Added a:isMatch argument to CountJump#Region#SearchForRegionEnd(), 427 | CountJump#Region#JumpToRegionEnd(), 428 | CountJump#Region#SearchForNextRegion(), 429 | CountJump#Region#JumpToNextRegion(). This allows definition of regions via 430 | non-matches, which can be substantially simpler (and faster to match) than 431 | coming up with a "negative" regular expression. 432 | 433 | ##### 1.22 06-Aug-2010 434 | - No more motion mappings and text objects for select mode; as the mappings 435 | start with a printable character, no select-mode mapping should be defined. 436 | 437 | ##### 1.21 03-Aug-2010 438 | - FIX: A 2]] jump inside a region (unless last line) jumped like a 1]] jump. 439 | The search for next region must not decrease the iteration counter when 440 | _not_ searching _across_ the region. 441 | - FIX: Must not do (characterwise) end position adaptation for linewise text 442 | object that does not exclude boundaries. 443 | - Switched example from email fortunes to Pascal begin..end blocks, as they 444 | are conceptually easier. 445 | 446 | ##### 1.20 02-Aug-2010 447 | - ENH: In CountJump#Motion#MakeBracketMotion(), a:keyAfterBracket and 448 | a:inverseKeyAfterBracket can now be empty, the resulting mappings are then 449 | omitted. Likewise, any jump function can be empty in 450 | CountJump#Motion#MakeBracketMotionWithJumpFunctions(). 451 | - With the added CountJump#Motion#MakeBracketMotionWithJumpFunctions() motions 452 | can be defined via jump functions, similar to how text objects can be 453 | defined. 454 | - Added CountJump/Region.vim to move to borders of a region defined by lines 455 | matching a pattern. 456 | - FIX: CountJump#CountJump() with mode "O" didn't add original position to 457 | jump list. 458 | - The previous visual selection is kept when the text object could not be 459 | selected. (Beforehand, a new selection of the text object's selection type 460 | was created.) 461 | - The adjustment movements after the jumps to the text object boundaries now 462 | do not cause beeps if that movement cannot be done (e.g. a 'j' at the end of 463 | the buffer). 464 | 465 | ##### 1.10 19-Jul-2010 466 | - Changed behavior if there aren't [count] matches: Instead of jumping to the 467 | last available match (and ringing the bell), the cursor stays at the 468 | original position, like with the old vi-compatible motions. 469 | - ENH: Only adding to jump list if there actually is a match. This is like the 470 | built-in Vim motions work. 471 | - FIX: For a linewise text object, the end cursor column is not important; do 472 | not compare with the original cursor column in this case. 473 | 474 | ##### 1.00 22-Jun-2010 475 | - First published version. 476 | 477 | ##### 0.01 14-Feb-2009 478 | - Started development. 479 | 480 | ------------------------------------------------------------------------------ 481 | Copyright: (C) 2009-2020 Ingo Karkat - 482 | The [VIM LICENSE](http://vimdoc.sourceforge.net/htmldoc/uganda.html#license) applies to this plugin. 483 | 484 | Maintainer: Ingo Karkat <ingo@karkat.de> 485 | -------------------------------------------------------------------------------- /autoload/CountJump.vim: -------------------------------------------------------------------------------- 1 | " CountJump.vim: Move to a buffer position via repeated jumps (or searches). 2 | " 3 | " DEPENDENCIES: 4 | " - ingo-library.vim plugin 5 | " 6 | " Copyright: (C) 2009-2019 Ingo Karkat 7 | " The VIM LICENSE applies to this script; see ':help copyright'. 8 | " 9 | " Maintainer: Ingo Karkat 10 | 11 | function! s:WrapMessage( searchName, isBackward ) 12 | if &shortmess !~# 's' 13 | call ingo#msg#WarningMsg(a:searchName . ' ' . (a:isBackward ? 'hit TOP, continuing at BOTTOM' : 'hit BOTTOM, continuing at TOP')) 14 | endif 15 | endfunction 16 | function! CountJump#CountSearchWithWrapMessage( count, searchName, SearchArguments ) 17 | "******************************************************************************* 18 | "* PURPOSE: 19 | " Search for the a:count'th occurrence of the passed search() pattern and 20 | " arguments. 21 | " 22 | "* ASSUMPTIONS / PRECONDITIONS: 23 | " None. 24 | " 25 | "* EFFECTS / POSTCONDITIONS: 26 | " Jumps to the a:count'th occurrence and opens any closed folds there. 27 | " If the pattern doesn't match (a:count times), a beep is emitted. 28 | " 29 | "* INPUTS: 30 | " a:count Number of occurrence to jump to. 31 | " a:searchName Object to be searched; used as the subject in the message 32 | " when the search wraps: "a:searchName hit BOTTOM, continuing 33 | " at TOP". When empty, no wrap message is issued. 34 | " a:SearchArguments Arguments to search() as a List [{pattern}, {flags}, ...] 35 | " Or Funcref to a function that takes no arguments and 36 | " returns the search arguments (as a List). 37 | " First search argument (pattern) may also be a Funcref 38 | " that takes no arguments and returns the pattern. 39 | " 40 | "* RETURN VALUES: 41 | " List with the line and column position, or [0, 0], like searchpos(). 42 | "******************************************************************************* 43 | let l:save_view = winsaveview() 44 | let l:searchArguments = (type(a:SearchArguments) == 2 ? call(a:SearchArguments, []) : copy(a:SearchArguments)) 45 | if type(l:searchArguments[0]) == 2 | let l:searchArguments[0] = call(l:searchArguments[0], []) | endif 46 | let l:isWrapped = 0 47 | let l:isBackward = (get(l:searchArguments, 1, '') =~# 'b') 48 | let [l:prevLine, l:prevCol] = [line('.'), col('.')] 49 | 50 | for l:i in range(1, a:count) 51 | let l:matchPosition = call('searchpos', l:searchArguments) 52 | if l:matchPosition == [0, 0] 53 | if l:i > 1 54 | " (Due to the count,) we've already moved to an intermediate 55 | " match. Undo that to behave like the old vi-compatible 56 | " motions. (Only the ]s motion has different semantics; it obeys 57 | " the 'wrapscan' setting and stays at the last possible match if 58 | " the setting is off.) 59 | call winrestview(l:save_view) 60 | endif 61 | 62 | " Ring the bell to indicate that no further match exists. 63 | execute "normal! \\\" 64 | 65 | return l:matchPosition 66 | endif 67 | 68 | if len(l:searchArguments) > 1 && l:i == 1 69 | " In case the search accepts a match at the cursor position 70 | " (i.e. search(..., 'c')), the flag must only be active on the very 71 | " first iteration; otherwise, all subsequent iterations will just 72 | " stay put at the current match. 73 | let l:searchArguments[1] = substitute(l:searchArguments[1], '\Cc', '', 'g') 74 | endif 75 | 76 | " Note: No need to check s:searchArguments and 'wrapscan'; the wrapping 77 | " can only occur if 'wrapscan' is actually on. 78 | if ! l:isBackward && ingo#pos#IsOnOrAfter([l:prevLine, l:prevCol], l:matchPosition) 79 | let l:isWrapped = 1 80 | elseif l:isBackward && ingo#pos#IsOnOrBefore([l:prevLine, l:prevCol], l:matchPosition) 81 | let l:isWrapped = 1 82 | endif 83 | let [l:prevLine, l:prevCol] = l:matchPosition 84 | endfor 85 | 86 | " Open the fold at the final search result. This makes the search work like 87 | " the built-in motions, and avoids that some visual selections get stuck at 88 | " a match inside a closed fold. 89 | normal! zv 90 | 91 | if ! empty(a:searchName) 92 | if l:isWrapped 93 | redraw 94 | call s:WrapMessage(a:searchName, l:isBackward) 95 | else 96 | " We need to clear any previous wrap message; it's confusing 97 | " otherwise. /pattern searches do not have that problem, as they 98 | " echo the search pattern. 99 | echo 100 | endif 101 | endif 102 | 103 | return l:matchPosition 104 | endfunction 105 | function! CountJump#CountSearch( count, SearchArguments ) 106 | return CountJump#CountSearchWithWrapMessage(a:count, '', a:SearchArguments) 107 | endfunction 108 | function! CountJump#CountCountJumpWithWrapMessage( count, mode, searchName, ... ) 109 | "******************************************************************************* 110 | "* PURPOSE: 111 | " Implement a custom motion by jumping to the th occurrence of the 112 | " passed pattern. 113 | " 114 | "* ASSUMPTIONS / PRECONDITIONS: 115 | " None. 116 | " 117 | "* EFFECTS / POSTCONDITIONS: 118 | " Normal mode: Jumps to the th occurrence. 119 | " Visual mode: Extends the selection to the th occurrence. 120 | " If the pattern doesn't match (a:count times), a beep is emitted. 121 | " 122 | "* INPUTS: 123 | " a:count Which match should be jumped to. 124 | " a:mode Mode in which the search is invoked. Either 'n', 'v' or 'o'. 125 | " Uppercase letters indicate special additional treatment for end 126 | " patterns to end. 127 | " a:searchName Object to be searched; used as the subject in the message 128 | " when the search wraps: "a:searchName hit BOTTOM, continuing 129 | " at TOP". When empty, no wrap message is issued. 130 | " ... Arguments to search(). 131 | " Or Funcref to a function that takes no arguments and returns the 132 | " search arguments (as a List). 133 | " First search argument (pattern) may also be a Funcref that takes no 134 | " arguments and returns the pattern. 135 | " 136 | "* RETURN VALUES: 137 | " None. 138 | "******************************************************************************* 139 | let l:save_view = winsaveview() 140 | let l:count = a:count 141 | 142 | if a:mode ==? 'v' 143 | normal! gv 144 | endif 145 | 146 | let l:matchPosition = CountJump#CountSearchWithWrapMessage(l:count, a:searchName, a:000) 147 | if l:matchPosition != [0, 0] 148 | " Add the original cursor position to the jump list. 149 | call winrestview(l:save_view) 150 | normal! m' 151 | call setpos('.', [0] + l:matchPosition + [0]) 152 | 153 | if a:mode ==# 'V' && &selection ==# 'exclusive' || a:mode ==# 'O' 154 | " Special additional treatment for end patterns to end. 155 | call ingo#motion#helper#AdditionalMovement(a:mode ==# 'O') 156 | endif 157 | endif 158 | endfunction 159 | function! CountJump#CountJumpWithWrapMessage( mode, searchName, ... ) 160 | return call('CountJump#CountCountJumpWithWrapMessage', [v:count1, a:mode, a:searchName] + a:000) 161 | endfunction 162 | function! CountJump#CountJump( mode, ... ) 163 | " See CountJump#CountJumpWithWrapMessage(). 164 | return call('CountJump#CountJumpWithWrapMessage', [a:mode, ''] + a:000) 165 | endfunction 166 | function! CountJump#JumpFunc( mode, JumpFunc, ... ) 167 | "******************************************************************************* 168 | "* PURPOSE: 169 | " Implement a custom motion by invoking a jump function that is passed the 170 | " and the optional arguments. 171 | " 172 | "* ASSUMPTIONS / PRECONDITIONS: 173 | " None. 174 | " 175 | "* EFFECTS / POSTCONDITIONS: 176 | " Normal mode: Jumps to the th occurrence. 177 | " Visual mode: Extends the selection to the th occurrence. 178 | " If the jump doesn't work, a beep is emitted. 179 | " 180 | "* INPUTS: 181 | " a:mode Mode in which the search is invoked. Either 'n', 'v' or 'o'. 182 | " Uppercase letters indicate special additional treatment for end jump 183 | " to end. 184 | " a:JumpFunc Function which is invoked to jump. 185 | " The jump function must take at least one argument: 186 | " a:count Number of matches to jump to. 187 | " It can take more arguments which must then be passed in here: 188 | " ... Arguments to the passed a:JumpFunc 189 | " The jump function should position the cursor to the appropriate position in 190 | " the current window, and open any folds there. It is expected to beep and 191 | " keep the cursor at its original position when no appropriate position can be 192 | " found. 193 | " 194 | "* RETURN VALUES: 195 | " None. 196 | "******************************************************************************* 197 | let l:save_view = winsaveview() 198 | let l:originalPosition = getpos('.') 199 | let l:count = v:count1 200 | 201 | if a:mode ==? 'v' 202 | normal! gv 203 | endif 204 | 205 | call call(a:JumpFunc, [l:count] + a:000) 206 | let l:matchPosition = getpos('.') 207 | if l:matchPosition != l:originalPosition 208 | " Add the original cursor position to the jump list. 209 | call winrestview(l:save_view) 210 | normal! m' 211 | call setpos('.', l:matchPosition) 212 | 213 | if a:mode ==# 'V' && &selection ==# 'exclusive' || a:mode ==# 'O' 214 | " Special additional treatment for end jumps to end. 215 | " The difference between normal mode, operator-pending and visual 216 | " mode with 'selection' set to "exclusive" is that in the latter 217 | " two, the motion must go _past_ the final "word" character, so that 218 | " all characters of the "word" are selected. This is done by 219 | " appending a 'l' motion after the search for the next "word". 220 | " 221 | " The 'l' motion only works properly at the end of the line (i.e. 222 | " when the moved-over "word" is at the end of the line) when the 'l' 223 | " motion is allowed to move over to the next line. Thus, the 'l' 224 | " motion is added temporarily to the global 'whichwrap' setting. 225 | " Without this, the motion would leave out the last character in the 226 | " line. 227 | let l:save_ww = &whichwrap 228 | set whichwrap+=l 229 | if a:mode ==# 'O' && line('.') == line('$') && ! ingo#option#ContainsOneOf(&virtualedit, ['all', 'onemore']) 230 | " For the last line in the buffer, that still doesn't work, 231 | " unless we can do virtual editing. 232 | let l:save_virtualedit = &virtualedit 233 | set virtualedit=onemore 234 | normal! l 235 | augroup TempVirtualEdit 236 | execute 'autocmd! CursorMoved * set virtualedit=' . l:save_virtualedit . ' | autocmd! TempVirtualEdit' 237 | augroup END 238 | else 239 | normal! l 240 | endif 241 | let &whichwrap = l:save_ww 242 | endif 243 | endif 244 | endfunction 245 | function! CountJump#CountJumpFuncWithWrapMessage( count, searchName, isBackward, SingleJumpFunc, ... ) 246 | "******************************************************************************* 247 | "* PURPOSE: 248 | " Invoke a:JumpFunc and its arguments a:count'th times. 249 | " This function can be passed to CountJump#JumpFunc() to implement a custom 250 | " motion with a simple jump function that only performs single jumps. 251 | " 252 | "* ASSUMPTIONS / PRECONDITIONS: 253 | " None. 254 | " 255 | "* EFFECTS / POSTCONDITIONS: 256 | " Jumps a:count times and opens any closed folds there. 257 | " If it cannot jump ( times), a beep is emitted. 258 | " 259 | "* INPUTS: 260 | " a:count Number of occurrence to jump to. 261 | " a:searchName Object to be searched; used as the subject in the message 262 | " when the search wraps: "a:searchName hit BOTTOM, continuing 263 | " at TOP". When empty, no wrap message is issued. 264 | " a:SingleJumpFunc Function which is invoked to perform a single jump. 265 | " It can take more arguments which must then be passed in here: 266 | " ... Arguments to the passed a:JumpFunc 267 | " The jump function should position the cursor to the appropriate position in 268 | " the current window and return the position. It is expected to keep the 269 | " cursor at its original position and return [0, 0] when no appropriate 270 | " position can be found. 271 | " 272 | "* RETURN VALUES: 273 | " List with the line and column position, or [0, 0], like searchpos(). 274 | "******************************************************************************* 275 | let l:save_view = winsaveview() 276 | let l:isWrapped = 0 277 | let [l:prevLine, l:prevCol] = [line('.'), col('.')] 278 | "****D echomsg '****' a:currentSyntaxId.':' string(synIDattr(a:currentSyntaxId, 'name')) 'colored in' synIDattr(a:currentHlgroupId, 'name') 279 | for l:i in range(1, a:count) 280 | let l:matchPosition = call(a:SingleJumpFunc, a:000) 281 | if l:matchPosition == [0, 0] 282 | if l:i > 1 283 | " (Due to the count,) we've already moved to an intermediate 284 | " match. Undo that to behave like the old vi-compatible 285 | " motions. (Only the ]s motion has different semantics; it obeys 286 | " the 'wrapscan' setting and stays at the last possible match if 287 | " the setting is off.) 288 | call winrestview(l:save_view) 289 | endif 290 | 291 | " Ring the bell to indicate that no further match exists. 292 | execute "normal! \\\" 293 | 294 | return l:matchPosition 295 | endif 296 | 297 | if ! a:isBackward && ingo#pos#IsOnOrAfter([l:prevLine, l:prevCol], l:matchPosition) 298 | let l:isWrapped = 1 299 | elseif a:isBackward && ingo#pos#IsOnOrBefore([l:prevLine, l:prevCol], l:matchPosition) 300 | let l:isWrapped = 1 301 | endif 302 | let [l:prevLine, l:prevCol] = l:matchPosition 303 | endfor 304 | 305 | " Open the fold at the final search result. This makes the search work like 306 | " the built-in motions, and avoids that some visual selections get stuck at 307 | " a match inside a closed fold. 308 | normal! zv 309 | 310 | if ! empty(a:searchName) 311 | if l:isWrapped 312 | redraw 313 | call s:WrapMessage(a:searchName, a:isBackward) 314 | else 315 | " We need to clear any previous wrap message; it's confusing 316 | " otherwise. /pattern searches do not have that problem, as they 317 | " echo the search pattern. 318 | echo 319 | endif 320 | endif 321 | 322 | return l:matchPosition 323 | endfunction 324 | function! CountJump#CountJumpFunc( count, SingleJumpFunc, ... ) 325 | " See CountJump#CountJumpFuncWithWrapMessage(). 326 | return call('CountJump#CountJumpFuncWithWrapMessage', [a:count, '', 0, a:SingleJumpFunc] + a:000) 327 | endfunction 328 | function! CountJump#Mapping( Function, arguments ) abort 329 | try 330 | call call(a:Function, a:arguments) 331 | return 1 332 | catch /^CountJump:/ 333 | call ingo#err#SetCustomException('CountJump') 334 | return 0 335 | catch '^\%(Vim:\)\?Interrupt$' 336 | return 1 337 | catch 338 | call ingo#err#SetVimException() 339 | return 0 340 | endtry 341 | endfunction 342 | function! CountJump#OMapping( Function, arguments ) abort 343 | return ingo#register#pending#ExecuteOrFunc(function('CountJump#Mapping'), a:Function, a:arguments) 344 | endfunction 345 | 346 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 347 | -------------------------------------------------------------------------------- /autoload/CountJump/Mappings.vim: -------------------------------------------------------------------------------- 1 | " CountJump/Mappings.vim: Utility functions to create the mappings. 2 | " 3 | " DEPENDENCIES: 4 | " 5 | " Copyright: (C) 2012-2019 Ingo Karkat 6 | " The VIM LICENSE applies to this script; see ':help copyright'. 7 | " 8 | " Maintainer: Ingo Karkat 9 | let s:save_cpo = &cpo 10 | set cpo&vim 11 | 12 | function! CountJump#Mappings#MakeMotionKey( isForward, keys ) 13 | return (a:keys =~# '^' ? 14 | \ printf(a:keys, (a:isForward ? 'Forward' : 'Backward')) : 15 | \ (a:isForward ? ']' : '[') . a:keys 16 | \) 17 | endfunction 18 | function! CountJump#Mappings#MakeTextObjectKey( type, keys ) 19 | return (a:keys =~# '^' ? 20 | \ printf(a:keys, (a:type ==# 'i' ? 'Inner' : 'Outer')) : 21 | \ a:type . a:keys 22 | \) 23 | endfunction 24 | function! CountJump#Mappings#EscapeForFunctionName( text ) 25 | let l:text = a:text 26 | 27 | " Strip off a prefix. 28 | let l:text = substitute(l:text, '^\C', '', '') 29 | 30 | " Convert all non-alphabetical characters to their hex value to create a 31 | " valid function name. 32 | let l:text = substitute(l:text, '\A', '\=char2nr(submatch(0))', 'g') 33 | 34 | return l:text 35 | endfunction 36 | 37 | let &cpo = s:save_cpo 38 | unlet s:save_cpo 39 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 40 | -------------------------------------------------------------------------------- /autoload/CountJump/Motion.vim: -------------------------------------------------------------------------------- 1 | " CountJump/Motion.vim: Create custom motions via repeated jumps (or searches). 2 | " 3 | " DEPENDENCIES: 4 | " - ingo-library.vim plugin 5 | " 6 | " Copyright: (C) 2009-2019 Ingo Karkat 7 | " The VIM LICENSE applies to this script; see ':help copyright'. 8 | " 9 | " Maintainer: Ingo Karkat 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | " Move around ??? 14 | "]x, ]] Go to [count] next start of ???. 15 | "]X, ][ Go to [count] next end of ???. 16 | "[x, [[ Go to [count] previous start of ???. 17 | "[X, [] Go to [count] previous end of ???. 18 | " 19 | " This mapping scheme is extracted from $VIMRUNTIME/ftplugin/vim.vim. It 20 | " enhances the original mappings so that a [count] can be specified, and folds 21 | " at the found search position are opened. 22 | function! CountJump#Motion#MakeBracketMotion( mapArgs, keyAfterBracket, inverseKeyAfterBracket, PatternToBegin, PatternToEnd, isEndPatternToEnd, ... ) 23 | "******************************************************************************* 24 | "* PURPOSE: 25 | " Define a complete set of mappings for a [x / ]x motion (e.g. like the 26 | " built-in ]m "Jump to start of next method") that support an optional [count] 27 | " and are driven by search patterns for the beginning and end of a block. 28 | " The mappings work in normal mode (jump), visual mode (expand selection) and 29 | " operator-pending mode (execute operator). 30 | " 31 | "* ASSUMPTIONS / PRECONDITIONS: 32 | " None. 33 | " 34 | "* EFFECTS / POSTCONDITIONS: 35 | " Creates mappings for normal, visual and operator-pending mode: 36 | " Normal mode: Jumps to the th occurrence. 37 | " Visual mode: Extends the selection to the th occurrence. 38 | " Operator-pending mode: Applies the operator to the covered text. 39 | " If the pattern doesn't match ( times), a beep is emitted. 40 | " 41 | "* INPUTS: 42 | " a:mapArgs Arguments to the :map command, like '' for a 43 | " buffer-local mapping. 44 | " a:keyAfterBracket Mapping key [sequence] after the mandatory ] / [ which 45 | " start the mapping for a motion to the beginning of a 46 | " block. 47 | " When this starts with , the key sequence is taken 48 | " as a template and a %s is replaced with "Forward" / 49 | " "Backward" instead of prepending ] / [. Through this, 50 | " plugins can define configurable mappings that not 51 | " necessarily start with ] / [. 52 | " Can be empty; the resulting mappings are then omitted. 53 | " a:inverseKeyAfterBracket Likewise, but for the motions to the end of a 54 | " block. Usually the uppercased version of 55 | " a:keyAfterBracket. 56 | " Can be empty; the resulting mappings are then 57 | " omitted. 58 | " If both a:keyAfterBracket and a:inverseKeyAfterBracket are empty, the 59 | " default [[ and ]] mappings are overwritten. (Note that this is different 60 | " from passing ']' and '[', respectively, because the back motions are 61 | " swapped.) 62 | " a:PatternToBegin Search pattern to locate the beginning of a block. 63 | " Or Funcref to a function that takes no arguments and 64 | " returns the search arguments (as a List). 65 | " a:PatternToEnd Search pattern to locate the end of a block. 66 | " Or Funcref to a function that takes no arguments and 67 | " returns the search arguments (as a List). 68 | " a:isEndPatternToEnd Flag that specifies whether a jump to the end of a block 69 | " will be to the end of the match. This makes it easier to 70 | " write an end pattern for characterwise motions (like 71 | " e.g. a block delimited by {{{ and }}}). 72 | " Linewise motions best not set this flag, so that the 73 | " end match positions the cursor in the first column. 74 | " a:mapModes Optional string containing 'n', 'o' and/or 'v', 75 | " representing the modes for which mappings should be 76 | " created. Defaults to all modes. 77 | " a:searchName Object to be searched; used as the subject in the message 78 | " when the search wraps: "a:searchName hit BOTTOM, continuing 79 | " at TOP". Wrapping is determined by the 'wrapscan' setting. 80 | " Optional; when not given or empty, searches never wrap. 81 | " 82 | "* NOTES: 83 | " - If your motion is linewise, the patterns should have the start of match 84 | " at the first column. This results in the expected behavior in normal mode, 85 | " and in characterwise visual mode selects up to that line, and in (more 86 | " likely) linewise visual mode includes the complete end line. 87 | " - Depending on the 'selection' setting, the first matched character is 88 | " either included or excluded in a characterwise visual selection. 89 | " 90 | "* RETURN VALUES: 91 | " None. 92 | "******************************************************************************* 93 | let l:endMatch = (a:isEndPatternToEnd ? 'e' : '') 94 | let l:mapModes = split((a:0 ? a:1 : 'nov'), '\zs') 95 | let l:searchName = (a:0 >= 2 ? a:2 : '') 96 | let l:wrapFlag = (empty(l:searchName) ? 'W' : '') 97 | 98 | if empty(a:keyAfterBracket) && empty(a:inverseKeyAfterBracket) 99 | let l:dataset = [ 100 | \ [ 0, '[[', a:PatternToBegin, 'b' . l:wrapFlag ], 101 | \ [ 0, ']]', a:PatternToBegin, '' . l:wrapFlag ], 102 | \ [ 1, '[]', a:PatternToEnd, 'b' . l:wrapFlag . l:endMatch ], 103 | \ [ 1, '][', a:PatternToEnd, '' . l:wrapFlag . l:endMatch ], 104 | \] 105 | else 106 | let l:dataset = [] 107 | if ! empty(a:keyAfterBracket) 108 | call add(l:dataset, [ 0, CountJump#Mappings#MakeMotionKey(0, a:keyAfterBracket), a:PatternToBegin, 'b' . l:wrapFlag ]) 109 | call add(l:dataset, [ 0, CountJump#Mappings#MakeMotionKey(1, a:keyAfterBracket), a:PatternToBegin, '' . l:wrapFlag ]) 110 | endif 111 | if ! empty(a:inverseKeyAfterBracket) 112 | call add(l:dataset, [ 1, CountJump#Mappings#MakeMotionKey(0, a:inverseKeyAfterBracket), a:PatternToEnd, 'b' . l:wrapFlag . l:endMatch ]) 113 | call add(l:dataset, [ 1, CountJump#Mappings#MakeMotionKey(1, a:inverseKeyAfterBracket), a:PatternToEnd, '' . l:wrapFlag . l:endMatch ]) 114 | endif 115 | endif 116 | for l:mode in l:mapModes 117 | for l:data in l:dataset 118 | execute escape( 119 | \ printf("%snoremap %s %s :if ! CountJump#%sMapping('CountJump#CountJumpWithWrapMessage', %s)echoerr ingo#err#Get()endif", 120 | \ (l:mode ==# 'v' ? 'x' : l:mode), 121 | \ a:mapArgs, 122 | \ l:data[1], 123 | \ (l:mode ==# 'o' ? 'O' : ''), 124 | \ ingo#escape#command#mapescape(string([ 125 | \ (l:data[0] && a:isEndPatternToEnd ? toupper(l:mode) : l:mode), 126 | \ l:searchName, 127 | \ l:data[2], 128 | \ l:data[3] 129 | \ ])) 130 | \ ), '|' 131 | \) 132 | endfor 133 | endfor 134 | endfunction 135 | 136 | function! s:AddTupleIfValue( list, tuple, value ) 137 | if ! empty(a:value) 138 | call add(a:list, a:tuple + [a:value]) 139 | endif 140 | endfunction 141 | function! CountJump#Motion#MakeBracketMotionWithJumpFunctions( mapArgs, keyAfterBracket, inverseKeyAfterBracket, JumpToBeginForward, JumpToBeginBackward, JumpToEndForward, JumpToEndBackward, isEndJumpToEnd, ... ) 142 | "******************************************************************************* 143 | "* PURPOSE: 144 | " Define a complete set of mappings for a [x / ]x motion (e.g. like the 145 | " built-in ]m "Jump to start of next method") that support an optional [count] 146 | " and are driven by two functions that jump to the beginning and end of a block. 147 | " The mappings work in normal mode (jump), visual mode (expand selection) and 148 | " operator-pending mode (execute operator). 149 | " 150 | "* ASSUMPTIONS / PRECONDITIONS: 151 | " None. 152 | " 153 | "* EFFECTS / POSTCONDITIONS: 154 | " Creates mappings for normal, visual and operator-pending mode: 155 | " Normal mode: Jumps to the th occurrence. 156 | " Visual mode: Extends the selection to the th occurrence. 157 | " Operator-pending mode: Applies the operator to the covered text. 158 | " If the pattern doesn't match ( times), a beep is emitted. 159 | " 160 | "* INPUTS: 161 | " a:mapArgs Arguments to the :map command, like '' for a 162 | " buffer-local mapping. 163 | " a:keyAfterBracket Mapping key [sequence] after the mandatory ]/[ which 164 | " start the mapping for a motion to the beginning of a 165 | " block. 166 | " When this starts with , the key sequence is taken 167 | " as a template and a %s is replaced with "Forward" / 168 | " "Backward" instead of prepending ] / [. Through this, 169 | " plugins can define configurable mappings that not 170 | " necessarily start with ] / [. 171 | " Can be empty; the resulting mappings are then omitted. 172 | " a:inverseKeyAfterBracket Likewise, but for the motions to the end of a 173 | " block. Usually the uppercased version of 174 | " a:keyAfterBracket. 175 | " Can be empty; the resulting mappings are then 176 | " omitted. 177 | " If both a:keyAfterBracket and a:inverseKeyAfterBracket are empty, the 178 | " default [[ and ]] mappings are overwritten. (Note that this is different 179 | " from passing ']' and '[', respectively, because the back motions are 180 | " swapped.) 181 | " a:JumpToBeginForward Function which is invoked to jump to the begin of the 182 | " block in forward direction. 183 | " a:JumpToBeginBackward Function which is invoked to jump to the begin of the 184 | " block in backward direction. 185 | " a:JumpToEndForward Function which is invoked to jump to the end of the 186 | " block in forward direction. 187 | " a:JumpToEndBackward Function which is invoked to jump to the end of the 188 | " block in backward direction. 189 | " The jump functions must take one argument: 190 | " JumpTo...( mode ) 191 | " a:mode Mode in which the search is invoked. Either 'n', 'v' or 'o'. 192 | " Uppercase letters indicate special additional treatment for end 193 | " jump to end. 194 | " All Funcrefs should position the cursor to the appropriate position in the 195 | " current window. See also CountJump#CountJumpFuncWithWrapMessage(). 196 | " If no jump function is passed, the corresponding mappings are omitted. 197 | 198 | " a:isEndJumpToEnd Flag that specifies whether a jump to the end of a block 199 | " will be to the last character of the block delimiter 200 | " (vs. to the first character of the block delimiter or 201 | " completely after the block delimiter). 202 | " a:mapModes Optional string containing 'n', 'o' and/or 'v', 203 | " representing the modes for which mappings should be 204 | " created. Defaults to all modes. 205 | " 206 | "* NOTES: 207 | " - If your motion is linewise, the jump functions should jump to the first 208 | " column. This results in the expected behavior in normal mode, and in 209 | " characterwise visual mode selects up to that line, and in (more likely) 210 | " linewise visual mode includes the complete end line. 211 | " 212 | "* RETURN VALUES: 213 | " None. 214 | "******************************************************************************* 215 | let l:mapModes = split((a:0 ? a:1 : 'nov'), '\zs') 216 | 217 | let l:dataset = [] 218 | if empty(a:keyAfterBracket) && empty(a:inverseKeyAfterBracket) 219 | call s:AddTupleIfValue(l:dataset, [0, '[['], a:JumpToBeginBackward) 220 | call s:AddTupleIfValue(l:dataset, [0, ']]'], a:JumpToBeginForward) 221 | call s:AddTupleIfValue(l:dataset, [1, '[]'], a:JumpToEndBackward) 222 | call s:AddTupleIfValue(l:dataset, [1, ']['], a:JumpToEndForward) 223 | else 224 | if ! empty(a:keyAfterBracket) 225 | call s:AddTupleIfValue(l:dataset, [0, CountJump#Mappings#MakeMotionKey(0, a:keyAfterBracket)], a:JumpToBeginBackward) 226 | call s:AddTupleIfValue(l:dataset, [0, CountJump#Mappings#MakeMotionKey(1, a:keyAfterBracket)], a:JumpToBeginForward) 227 | endif 228 | if ! empty(a:inverseKeyAfterBracket) 229 | call s:AddTupleIfValue(l:dataset, [1, CountJump#Mappings#MakeMotionKey(0, a:inverseKeyAfterBracket)], a:JumpToEndBackward) 230 | call s:AddTupleIfValue(l:dataset, [1, CountJump#Mappings#MakeMotionKey(1, a:inverseKeyAfterBracket)], a:JumpToEndForward) 231 | endif 232 | endif 233 | 234 | for l:mode in l:mapModes 235 | for l:data in l:dataset 236 | execute escape( 237 | \ printf('%snoremap %s %s :if ! CountJump#%sMapping(%s, %s)echoerr ingo#err#Get()endif', 238 | \ (l:mode ==# 'v' ? 'x' : l:mode), 239 | \ a:mapArgs, 240 | \ l:data[1], 241 | \ (l:mode ==# 'o' ? 'O' : ''), 242 | \ string(l:data[2]), 243 | \ string([ 244 | \ (l:data[0] && a:isEndJumpToEnd ? toupper(l:mode) : l:mode) 245 | \ ]) 246 | \ ), '|' 247 | \) 248 | endfor 249 | endfor 250 | endfunction 251 | 252 | let &cpo = s:save_cpo 253 | unlet s:save_cpo 254 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 255 | -------------------------------------------------------------------------------- /autoload/CountJump/Region.vim: -------------------------------------------------------------------------------- 1 | " CountJump/Region.vim: Move to borders of a region defined by lines matching a pattern. 2 | " 3 | " DEPENDENCIES: 4 | " 5 | " Copyright: (C) 2010-2019 Ingo Karkat 6 | " The VIM LICENSE applies to this script; see ':help copyright'. 7 | " 8 | " Maintainer: Ingo Karkat 9 | 10 | function! s:DoJump( position, isToEndOfLine ) 11 | if a:position == [0, 0] 12 | " Ring the bell to indicate that no further match exists. 13 | execute "normal! \\\" 14 | else 15 | call setpos('.', [0] + a:position + [0]) 16 | if a:isToEndOfLine 17 | normal! $zv 18 | return getpos('.')[1:2] 19 | else 20 | normal! zv 21 | endif 22 | endif 23 | 24 | return a:position 25 | endfunction 26 | 27 | function! s:SearchInLineMatching( line, Expr, isMatch ) 28 | "****************************************************************************** 29 | "* PURPOSE: 30 | " Search for the first (depending on a:isMatch, non-)match with a:Expr in a:line. 31 | "* ASSUMPTIONS / PRECONDITIONS: 32 | " None. 33 | "* EFFECTS / POSTCONDITIONS: 34 | " None. 35 | "* INPUTS: 36 | " a:line Line in the current buffer to search. Can be an invalid one. 37 | " a:Expr Regular expression to (not) match. 38 | " Or Funcref to a function that takes a line number and returns 39 | " the matching byte offset (or -1), just like |match()|. 40 | " a:isMatch Flag whether to match. 41 | "* RETURN VALUES: 42 | " Screen column of the first match, 1 in case of desired non-match, 0 if there 43 | " is no (non-)match. 44 | "****************************************************************************** 45 | if a:line < 1 || a:line > line('$') 46 | return 0 47 | endif 48 | 49 | if type(a:Expr) == type('') 50 | let l:col = match(getline(a:line), a:Expr) 51 | elseif type(a:Expr) == 2 " Funcref 52 | let l:col = call(a:Expr, [a:line]) 53 | else 54 | throw 'ASSERT: Wrong type, must be either a regexp or a Funcref' 55 | endif 56 | 57 | if (l:col == -1 && a:isMatch) || (l:col != -1 && ! a:isMatch) 58 | return 0 59 | endif 60 | 61 | return (a:isMatch ? l:col + 1 : 1) " Screen columns start at 1, match returns zero-based index. 62 | endfunction 63 | function! s:SearchForLastLineContinuouslyMatching( startLine, Expr, isMatch, step ) 64 | "****************************************************************************** 65 | "* PURPOSE: 66 | " Search for the last line (from a:startLine, using a:step as direction) that 67 | " matches (or not, according to a:isMatch) a:Expr. 68 | "* ASSUMPTIONS / PRECONDITIONS: 69 | " None. 70 | "* EFFECTS / POSTCONDITIONS: 71 | " None. Does not change the cursor position. 72 | "* INPUTS: 73 | " a:startLine Line in the current buffer where the search starts. Can be an 74 | " invalid one. 75 | " a:Expr Regular expression to (not) match. 76 | " Or Funcref to a function that takes a line number and returns 77 | " the matching byte offset (or -1), just like |match()|. 78 | " a:isMatch Flag whether to search matching (vs. non-matching) lines. 79 | " a:step Increment to go to next line. Use 1 for forward, -1 for backward 80 | " search. 81 | "* RETURN VALUES: 82 | " [ line, col ] of the (first match) in the last line that continuously (not) 83 | " matches, or [0, 0] if no such (non-)match. 84 | "****************************************************************************** 85 | let l:line = a:startLine 86 | let l:foundPosition = [0, 0] 87 | while 1 88 | let l:col = s:SearchInLineMatching(l:line, a:Expr, a:isMatch) 89 | if l:col == 0 | break | endif 90 | let l:foundPosition = [l:line, l:col] 91 | let l:line += a:step 92 | endwhile 93 | return l:foundPosition 94 | endfunction 95 | 96 | function! s:TryEvaluateExpr( Expr ) 97 | if type(a:Expr) == 2 " Funcref 98 | try 99 | let l:pattern = call(a:Expr, []) 100 | 101 | if exists('g:CountJump_TextObjectContext') 102 | " In an (outer) text object, the current line after the first 103 | " jump is outside of the matching region. If we picked up the 104 | " pattern again, it would be wrong. In any case, we should avoid 105 | " re-querying the pattern. Therefore, save the pattern in the 106 | " text object context, and recall it from there. 107 | if ! has_key(g:CountJump_TextObjectContext, 'ExprFuncrefPattern') 108 | let g:CountJump_TextObjectContext.ExprFuncrefPattern = l:pattern 109 | endif 110 | return g:CountJump_TextObjectContext.ExprFuncrefPattern 111 | endif 112 | 113 | return l:pattern 114 | catch /^Vim\%((\a\+)\)\=:E119:/ " E119: Not enough arguments for function 115 | " This is a type 2 Funcref, to be called with a line number. Keep 116 | " it. 117 | endtry 118 | endif 119 | return a:Expr 120 | endfunction 121 | 122 | function! CountJump#Region#SearchForRegionEnd( count, Expr, isMatch, step ) 123 | "****************************************************************************** 124 | "* PURPOSE: 125 | " Starting from the current line, search for the position where the a:count'th 126 | " region (as defined by contiguous lines that (don't) match a:Expr) ends. 127 | "* ASSUMPTIONS / PRECONDITIONS: 128 | " None. 129 | "* EFFECTS / POSTCONDITIONS: 130 | " None. 131 | "* INPUTS: 132 | " a:count Number of regions to cover. 133 | " a:Expr Regular expression that defines the region, i.e. must (not) 134 | " match in all lines belonging to it. 135 | " Or Funcref to a function that takes no arguments and returns the 136 | " regular expression. 137 | " Or Funcref to a function that takes a line number and returns 138 | " the matching byte offset (or -1), just like |match()|. 139 | " a:isMatch Flag whether to search matching (vs. non-matching) lines. 140 | " a:step Increment to go to next line. Use 1 for forward, -1 for backward 141 | " search. 142 | "* RETURN VALUES: 143 | " [ line, col ] of the (first match) in the last line that continuously (not) 144 | " matches, or [0, 0] if no such (non-)match. 145 | "****************************************************************************** 146 | let l:c = a:count 147 | let l:line = line('.') 148 | let g:CountJump_MotionContext = {} 149 | try 150 | let l:Expr = s:TryEvaluateExpr(a:Expr) 151 | 152 | while 1 153 | " Search for the current region's end. 154 | let [l:line, l:col] = s:SearchForLastLineContinuouslyMatching(l:line, l:Expr, a:isMatch, a:step) 155 | if l:line == 0 156 | return [0, 0] 157 | endif 158 | 159 | " If this is the last region to be found, we're done. 160 | let l:c -= 1 161 | if l:c == 0 162 | break 163 | endif 164 | 165 | " Otherwise, search for the next region's start. 166 | let l:line += a:step 167 | let [l:line, l:col] = s:SearchForLastLineContinuouslyMatching(l:line, l:Expr, ! a:isMatch, a:step) 168 | if l:line == 0 169 | return [0, 0] 170 | endif 171 | 172 | let l:line += a:step 173 | endwhile 174 | 175 | return [l:line, l:col] 176 | finally 177 | unlet! g:CountJump_MotionContext 178 | endtry 179 | endfunction 180 | function! CountJump#Region#JumpToRegionEnd( count, Expr, isMatch, step, isToEndOfLine ) 181 | let l:position = CountJump#Region#SearchForRegionEnd(a:count, a:Expr, a:isMatch, a:step) 182 | return s:DoJump(l:position, a:isToEndOfLine) 183 | endfunction 184 | 185 | function! CountJump#Region#SearchForNextRegion( count, Expr, isMatch, step, isAcrossRegion ) 186 | "****************************************************************************** 187 | "* PURPOSE: 188 | " Starting from the current line, search for the position where the a:count'th 189 | " region (as defined by contiguous lines that (don't) match a:Expr) 190 | " begins/ends. 191 | " If the current line is inside the border of a region, jumps to the next one. 192 | " If it is actually inside a region, jumps to the current region's border. 193 | " This makes it work like the built-in motions: [[, ]], etc. 194 | "* ASSUMPTIONS / PRECONDITIONS: 195 | " None. 196 | "* EFFECTS / POSTCONDITIONS: 197 | " Moves cursor to match if it exists. 198 | "* INPUTS: 199 | " a:count Number of regions to cover. 200 | " a:Expr Regular expression that defines the region, i.e. must (not) 201 | " match in all lines belonging to it. 202 | " Or Funcref to a function that takes no arguments and returns the 203 | " regular expression. 204 | " Or Funcref to a function that takes a line number and returns 205 | " the matching byte offset (or -1), just like |match()|. 206 | " a:isMatch Flag whether to search matching (vs. non-matching) lines. 207 | " a:step Increment to go to next line. Use 1 for forward, -1 for backward 208 | " search. 209 | " a:isAcrossRegion Flag whether to search across the region for the last 210 | " (vs. first) line belonging to the region (while moving 211 | " in a:step direction). 212 | "* RETURN VALUES: 213 | " [ line, col ] of the (first match) in the last line that continuously (not) 214 | " matches, or [0, 0] if no such (non-)match. 215 | "****************************************************************************** 216 | let l:c = a:count 217 | let l:isDone = 0 218 | let l:line = line('.') 219 | let g:CountJump_MotionContext = {} 220 | try 221 | let l:Expr = s:TryEvaluateExpr(a:Expr) 222 | 223 | " Check whether we're currently on the border of a region. 224 | let l:isInRegion = (s:SearchInLineMatching(l:line, l:Expr, a:isMatch) != 0) 225 | let l:isNextInRegion = (s:SearchInLineMatching((l:line + a:step), l:Expr, a:isMatch) != 0) 226 | "****D echomsg '**** in region:' (l:isInRegion ? 'current' : '') (l:isNextInRegion ? 'next' : '') 227 | if l:isInRegion 228 | if l:isNextInRegion 229 | " We're inside a region; search for the current region's end. 230 | let [l:line, l:col] = s:SearchForLastLineContinuouslyMatching(l:line, l:Expr, a:isMatch, a:step) 231 | if a:isAcrossRegion 232 | if l:c == 1 233 | " We're done already! 234 | let l:isDone = 1 235 | else 236 | " We've moved to the border, resume the search for following 237 | " regions... 238 | let l:c = max([l:c - 1, 1]) 239 | " ...from the next line so that we move out of the current 240 | " region. 241 | let l:line += a:step 242 | endif 243 | else 244 | " We're on the border, start the search from the next line so 245 | " that we move out of the current region. 246 | let l:line += a:step 247 | endif 248 | else 249 | " We're on the border, start the search from the next line so that we 250 | " move out of the current region. 251 | let l:line += a:step 252 | endif 253 | endif 254 | 255 | "****D echomsg '**** starting iteration on line' l:line 256 | while ! l:isDone 257 | " Search for the next region's start. 258 | let [l:line, l:col] = s:SearchForLastLineContinuouslyMatching(l:line, l:Expr, ! a:isMatch, a:step) 259 | if l:line == 0 260 | return [0, 0] 261 | endif 262 | let l:line += a:step 263 | 264 | " If this is the last region to be found, we're almost done. 265 | "****D echomsg '**** iteration' l:c 'on line' l:line 266 | let l:c -= 1 267 | if l:c == 0 268 | if a:isAcrossRegion 269 | " Search for the current region's end. 270 | let [l:line, l:col] = s:SearchForLastLineContinuouslyMatching(l:line, l:Expr, a:isMatch, a:step) 271 | if l:line == 0 272 | return [0, 0] 273 | endif 274 | else 275 | " Check whether another region starts at the current line. 276 | let l:col = s:SearchInLineMatching(l:line, l:Expr, a:isMatch) 277 | if l:col == 0 278 | return [0, 0] 279 | endif 280 | endif 281 | 282 | break 283 | endif 284 | 285 | " Otherwise, we're not done; skip over the next region. 286 | let [l:line, l:col] = s:SearchForLastLineContinuouslyMatching(l:line, l:Expr, a:isMatch, a:step) 287 | if l:line == 0 288 | return [0, 0] 289 | endif 290 | let l:line += a:step 291 | endwhile 292 | 293 | return [l:line, l:col] 294 | finally 295 | unlet! g:CountJump_MotionContext 296 | endtry 297 | endfunction 298 | function! CountJump#Region#JumpToNextRegion( count, Expr, isMatch, step, isAcrossRegion, isToEndOfLine ) 299 | let l:position = CountJump#Region#SearchForNextRegion(a:count, a:Expr, a:isMatch, a:step, a:isAcrossRegion) 300 | return s:DoJump(l:position, a:isToEndOfLine) 301 | endfunction 302 | 303 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 304 | -------------------------------------------------------------------------------- /autoload/CountJump/Region/Motion.vim: -------------------------------------------------------------------------------- 1 | " CountJump/Region/Motion.vim: Create custom motions via jumps over matching lines. 2 | " 3 | " DEPENDENCIES: 4 | " - ingo-library.vim plugin 5 | " 6 | " Copyright: (C) 2010-2019 Ingo Karkat 7 | " The VIM LICENSE applies to this script; see ':help copyright'. 8 | " 9 | " Maintainer: Ingo Karkat 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | " Move around ??? 14 | "]x, ]] Go to [count] next start of ???. 15 | "]X, ][ Go to [count] next end of ???. 16 | "[x, [[ Go to [count] previous start of ???. 17 | "[X, [] Go to [count] previous end of ???. 18 | 19 | function! CountJump#Region#Motion#MakeBracketMotion( mapArgs, keyAfterBracket, inverseKeyAfterBracket, Expr, isMatch, ... ) 20 | "******************************************************************************* 21 | "* PURPOSE: 22 | " Define a complete set of mappings for a [x / ]x motion (e.g. like the 23 | " built-in ]m "Jump to start of next method") that support an optional [count] 24 | " and jump over regions of lines which are defined by contiguous lines that 25 | " (don't) match a:Expr. 26 | " The mappings work in normal mode (jump), visual mode (expand selection) and 27 | " operator-pending mode (execute operator). 28 | 29 | " Normally, it will jump to the column of the first match (typically, that is 30 | " column 1, always so for non-matches). But for the ]X or ][ mapping, it will 31 | " include the entire line in operator-pending and visual mode; operating over 32 | " / selecting the entire region is typically what the user expects. 33 | " In visual mode, the mode will NOT be changed to linewise, though that, due 34 | " to the linewise definition of a region, is usually the best mode to use the 35 | " mappings in. Likewise, an operator will only work from the cursor position, 36 | " not the entire line the cursor was on. If you want to force linewise mode, 37 | " either go into linewise visual mode first or try the corresponding text 38 | " object (if one exists); text objects DO usually switch the selection mode 39 | " into what's more appropriate for them. (Compare the behavior of the built-in 40 | " paragraph motion |}| vs. the "a paragraph" text object |ap|.) 41 | " 42 | "* ASSUMPTIONS / PRECONDITIONS: 43 | " None. 44 | " 45 | "* EFFECTS / POSTCONDITIONS: 46 | " Creates mappings for normal, visual and operator-pending mode: 47 | " Normal mode: Jumps to the th region. 48 | " Visual mode: Extends the selection to the th region. 49 | " Operator-pending mode: Applies the operator to the covered text. 50 | " If there aren't more regions, a beep is emitted. 51 | " 52 | "* INPUTS: 53 | " a:mapArgs Arguments to the :map command, like '' for a 54 | " buffer-local mapping. 55 | " a:keyAfterBracket Mapping key [sequence] after the mandatory ]/[ which 56 | " start the mapping for a motion to the beginning of a 57 | " block. 58 | " When this starts with , the key sequence is taken 59 | " as a template and a %s is replaced with "Forward" / 60 | " "Backward" instead of prepending ] / [. Through this, 61 | " plugins can define configurable mappings that not 62 | " necessarily start with ] / [. 63 | " Can be empty; the resulting mappings are then omitted. 64 | " a:inverseKeyAfterBracket Likewise, but for the motions to the end of a 65 | " block. Usually the uppercased version of 66 | " a:keyAfterBracket. 67 | " Can be empty; the resulting mappings are then 68 | " omitted. 69 | " If both a:keyAfterBracket and a:inverseKeyAfterBracket are empty, the 70 | " default [[ and ]] mappings are overwritten. (Note that this is different 71 | " from passing ']' and '[', respectively, because the back motions are 72 | " swapped.) 73 | " a:Expr Regular expression that defines the region, i.e. must (not) 74 | " match in all lines belonging to it. 75 | " Or Funcref to a function that takes no arguments and returns the 76 | " regular expression. 77 | " Or Funcref to a function that takes a line number and returns 78 | " the matching byte offset (or -1), just like |match()|. 79 | " a:isMatch Flag whether to search matching (vs. non-matching) lines. 80 | " a:mapModes Optional string containing 'n', 'o' and/or 'v', 81 | " representing the modes for which mappings should be 82 | " created. Defaults to all modes. 83 | " 84 | "* RETURN VALUES: 85 | " None. 86 | "******************************************************************************* 87 | let l:mapModes = split((a:0 ? a:1 : 'nov'), '\zs') 88 | 89 | let l:dataset = [] " List of [ mapping keys, step, isAcrossRegion, isToEndOfLine ] 90 | if empty(a:keyAfterBracket) && empty(a:inverseKeyAfterBracket) 91 | call add(l:dataset, ['[[', -1, 1, 0]) 92 | call add(l:dataset, [']]', 1, 0, 0]) 93 | call add(l:dataset, ['[]', -1, 0, 0]) 94 | call add(l:dataset, ['][', 1, 1, 1]) 95 | else 96 | if ! empty(a:keyAfterBracket) 97 | call add(l:dataset, [CountJump#Mappings#MakeMotionKey(0, a:keyAfterBracket), -1, 1, 0]) 98 | call add(l:dataset, [CountJump#Mappings#MakeMotionKey(1, a:keyAfterBracket) , 1, 0, 0]) 99 | endif 100 | if ! empty(a:inverseKeyAfterBracket) 101 | call add(l:dataset, [CountJump#Mappings#MakeMotionKey(0, a:inverseKeyAfterBracket), -1, 0, 0]) 102 | call add(l:dataset, [CountJump#Mappings#MakeMotionKey(1, a:inverseKeyAfterBracket), 1, 1, 1]) 103 | endif 104 | endif 105 | 106 | for l:mode in l:mapModes 107 | for l:data in l:dataset 108 | let l:useToEndOfLine = (l:mode ==# 'n' ? 0 : l:data[3]) 109 | execute escape( 110 | \ printf("%snoremap %s %s :if ! CountJump#%sMapping('CountJump#JumpFunc', %s)echoerr ingo#err#Get()endif", 111 | \ (l:mode ==# 'v' ? 'x' : l:mode), 112 | \ a:mapArgs, 113 | \ l:data[0], 114 | \ (l:mode ==# 'o' ? 'O' : ''), 115 | \ ingo#escape#command#mapescape(string([ 116 | \ (l:mode ==# 'o' && l:useToEndOfLine ? 'O' : l:mode), 117 | \ 'CountJump#Region#JumpToNextRegion', 118 | \ a:Expr, 119 | \ a:isMatch, 120 | \ l:data[1], 121 | \ l:data[2], 122 | \ l:useToEndOfLine 123 | \ ])) 124 | \ ), '|' 125 | \) 126 | endfor 127 | endfor 128 | endfunction 129 | 130 | let &cpo = s:save_cpo 131 | unlet s:save_cpo 132 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 133 | -------------------------------------------------------------------------------- /autoload/CountJump/Region/TextObject.vim: -------------------------------------------------------------------------------- 1 | " CountJump/Region/TextObject.vim: Create custom text objects via jumps over matching lines. 2 | " 3 | " DEPENDENCIES: 4 | " 5 | " Copyright: (C) 2010-2019 Ingo Karkat 6 | " The VIM LICENSE applies to this script; see ':help copyright'. 7 | " 8 | " Maintainer: Ingo Karkat 9 | let s:save_cpo = &cpo 10 | set cpo&vim 11 | 12 | function! s:EscapeForFunctionName( text ) 13 | " Convert all non-alphabetical characters to their hex value to create a 14 | " valid function name. 15 | return substitute(a:text, '\A', '\=char2nr(submatch(0))', 'g') 16 | endfunction 17 | function! s:function(name) 18 | return function(substitute(a:name, '^\Cs:', matchstr(expand(''), '\d\+_\zefunction$'),'')) 19 | endfunction 20 | function! CountJump#Region#TextObject#Make( mapArgs, textObjectKey, types, selectionMode, Expr, isMatch ) 21 | "******************************************************************************* 22 | "* PURPOSE: 23 | " Define a complete set of mappings for inner and/or outer text objects that 24 | " support an optional [count] and select regions of lines which are defined by 25 | " contiguous lines that (don't) match a:pattern. 26 | " The inner text object comprises all lines of the region itself, while the 27 | " outer text object also includes all adjacent lines above and below which do 28 | " not themselves belong to a region. 29 | " 30 | "* ASSUMPTIONS / PRECONDITIONS: 31 | " None. 32 | " 33 | "* EFFECTS / POSTCONDITIONS: 34 | " Creates mappings for operator-pending and visual mode which act upon / 35 | " select the text delimited by the begin and end patterns. 36 | " If there are no regions, a beep is emitted. 37 | " 38 | "* INPUTS: 39 | " a:mapArgs Arguments to the :map command, like '' for a 40 | " buffer-local mapping. 41 | " a:textObjectKey Mapping key [sequence] after the mandatory i/a which 42 | " start the mapping for the text object. 43 | " When this starts with , the key sequence is taken 44 | " as a template and a %s is replaced with "Inner" / 45 | " "Outer" instead of prepending i / a. Through this, 46 | " plugins can define configurable text objects that not 47 | " necessarily start with i / a. 48 | " a:types String containing 'i' for inner and 'a' for outer text 49 | " objects. 50 | " a:selectionMode Type of selection used between the patterns: 51 | " 'v' for characterwise, 'V' for linewise, '' for 52 | " blockwise. Since regions are defined over full lines, 53 | " this should typically be 'V'. 54 | " a:Expr Regular expression that defines the region, i.e. must (not) 55 | " match in all lines belonging to it. 56 | " Or Funcref to a function that takes no arguments and returns the 57 | " regular expression. 58 | " Or Funcref to a function that takes a line number and returns 59 | " the matching byte offset (or -1), just like |match()|. 60 | " a:isMatch Flag whether to search matching (vs. non-matching) lines. 61 | " 62 | "* RETURN VALUES: 63 | " None. 64 | "******************************************************************************* 65 | if a:types !~# '^[ai]\+$' 66 | throw "ASSERT: Type must consist of 'a' and/or 'i', but is: '" . a:types . "'" 67 | endif 68 | 69 | let l:scope = (a:mapArgs =~# '' ? 's:B' . bufnr('') : 's:') 70 | 71 | " If only either an inner or outer text object is defined, the generated 72 | " function must include the type, so that it is possible to separately 73 | " define a text object of the other type (via a second invocation of this 74 | " function). If the same region definition is used for both inner and outer 75 | " text objects, no such distinction need to be made. 76 | let l:typePrefix = (strlen(a:types) == 1 ? a:types : '') 77 | let l:functionName = CountJump#Mappings#EscapeForFunctionName(CountJump#Mappings#MakeTextObjectKey(l:typePrefix, a:textObjectKey)) 78 | 79 | let l:functionToBeginName = printf('%sJumpToBegin_%s', l:scope, l:functionName) 80 | let l:functionToEndName = printf('%sJumpToEnd_%s', l:scope, l:functionName) 81 | 82 | let l:regionFunction = " 83 | \ function! %s( count, isInner )\n 84 | \ %s\n 85 | \ let [l:Expr, l:isMatch, l:step, l:isToEndOfLine] = [%s, %d, %d, %d]\n 86 | \ if a:isInner\n 87 | \ return CountJump#Region#JumpToRegionEnd(a:count, l:Expr, l:isMatch, l:step, l:isToEndOfLine)\n 88 | \ else\n 89 | \ let l:isBackward = (l:step < 0)\n 90 | \ let l:regionEndPosition = CountJump#Region#JumpToRegionEnd(a:count, l:Expr, l:isMatch, l:step, 0)\n 91 | \ if l:regionEndPosition == [0, 0] || l:regionEndPosition[0] == (l:isBackward ? 1 : line('$'))\n 92 | \ return l:regionEndPosition\n 93 | \ endif\n 94 | \ execute 'normal!' (l:isBackward ? 'k' : 'j')\n 95 | \ return CountJump#Region#JumpToRegionEnd(1, l:Expr, ! l:isMatch, l:step, l:isToEndOfLine)\n 96 | \ endif\n 97 | \ endfunction" 98 | 99 | " The function-to-end starts at the beginning of the text object. For the 100 | " outer text object, this would make moving back into the region and then 101 | " beyond it complex. To instead, we use the knowledge that the 102 | " function-to-begin is executed first, and set the original cursor line 103 | " there, then start the function-to-end at that position. Since this may 104 | " also slightly speed up the search for the inner text object, we use it 105 | " unconditionally. 106 | execute printf(l:regionFunction, 107 | \ l:functionToBeginName, 108 | \ 'let s:originalLineNum = line(".")', 109 | \ string(a:Expr), 110 | \ a:isMatch, 111 | \ -1, 112 | \ 0 113 | \) 114 | execute printf(l:regionFunction, 115 | \ l:functionToEndName, 116 | \ 'execute s:originalLineNum', 117 | \ string(a:Expr), 118 | \ a:isMatch, 119 | \ 1, 120 | \ 1 121 | \) 122 | 123 | " For regions, the inner text object must include the text object's 124 | " boundaries = lines. 125 | let l:types = substitute(a:types, '\Ci', 'I', 'g') 126 | return CountJump#TextObject#MakeWithJumpFunctions(a:mapArgs, a:textObjectKey, l:types, a:selectionMode, s:function(l:functionToBeginName), s:function(l:functionToEndName)) 127 | endfunction 128 | 129 | let &cpo = s:save_cpo 130 | unlet s:save_cpo 131 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 132 | -------------------------------------------------------------------------------- /autoload/CountJump/TextObject.vim: -------------------------------------------------------------------------------- 1 | " CountJump/TextObject.vim: Create custom text objects via repeated jumps (or searches). 2 | " 3 | " DEPENDENCIES: 4 | " - ingo-library.vim plugin 5 | " 6 | " Copyright: (C) 2009-2019 Ingo Karkat 7 | " The VIM LICENSE applies to this script; see ':help copyright'. 8 | " 9 | " Maintainer: Ingo Karkat 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | " Select text delimited by ???. 14 | "ix Select [count] text blocks delimited by ??? without the 15 | " outer delimiters. 16 | "ax Select [count] text blocks delimited by ??? including 17 | " the delimiters. 18 | function! CountJump#TextObject#TextObjectWithJumpFunctions( mode, isInner, isExcludeBoundaries, selectionMode, JumpToBegin, JumpToEnd ) 19 | "******************************************************************************* 20 | "* PURPOSE: 21 | " Creates a visual selection (in a:selectionMode) around the 'th 22 | " inner / outer text object delimited by the a:JumpToBegin and a:JumpToEnd 23 | " functions. 24 | " If there is no match, or the jump is not around the cursor position, the 25 | " failure to select the text object is indicated via a beep. In visual mode, 26 | " the selection is maintained then (using a:selectionMode). the built-in text 27 | " objects work in the same way. 28 | " 29 | "* ASSUMPTIONS / PRECONDITIONS: 30 | " None. 31 | " 32 | "* EFFECTS / POSTCONDITIONS: 33 | " Creates / modifies visual selection. 34 | " 35 | "* INPUTS: 36 | " a:mode Mode for the text object; either 'o' (operator-pending) or 'v' 37 | " (visual). 38 | " a:isInner Flag whether this is an "inner" text object (i.e. it excludes 39 | " the boundaries, or an "outer" one. This variable is passed to 40 | " the a:JumpToBegin and a:JumpToEnd functions. 41 | " a:isExcludeBoundaries Flag whether the matching boundaries should not be 42 | " part of the text object. Except for special cases, 43 | " the value should correspond with a:isInner. 44 | " a:selectionMode Specifies how the text object selects text; either 'v', 'V' 45 | " or "\". 46 | " a:JumpToBegin Funcref that jumps to the beginning of the text object. 47 | " The function must take a count (always 1 here) and the 48 | " a:isInner flag (which determines whether the jump should be 49 | " to the end of the boundary text). 50 | " The function is invoked at the cursor position where the 51 | " text object was requested. 52 | " a:JumpToEnd Funcref that jumps to the end of the text object. 53 | " The function must take a count and the a:isInner flag. 54 | " The function is invoked after the call to a:JumpToBegin, 55 | " with the cursor located at the beginning of the text object. 56 | " 57 | " Both Funcrefs must return a list [lnum, col], like 58 | " searchpos(). This should be the jump position (or [0, 0] if 59 | " a jump wasn't possible). Normally, this should correspond to 60 | " the cursor position set by the jump function. However, for 61 | " an inner jump, this could also be the outer jump position. 62 | " This function will use this position for the check that the 63 | " jump is around the cursor position; if the returned position 64 | " is the outer jump position, an inner text object will allow 65 | " selection even when the cursor is on the boundary text (like 66 | " the built-in text objects). 67 | "* RETURN VALUES: 68 | " None. 69 | " 70 | "* KNOWN PROBLEMS: 71 | " At the beginning and end of the buffer, the inner text objects may select 72 | " one character / line less than it should, because the compensating motions 73 | " are always executed, but the jump cannot position the cursor "outside" the 74 | " buffer (i.e. before the first / after the last line). 75 | "******************************************************************************* 76 | let l:count = v:count1 77 | let l:isExclusive = (&selection ==# 'exclusive') 78 | let l:isLinewise = (a:selectionMode ==# 'V') 79 | let l:save_view = winsaveview() 80 | let [l:cursorLine, l:cursorCol] = [line('.'), col('.')] 81 | let l:isSelected = 0 82 | let g:CountJump_TextObjectContext = {} 83 | 84 | let l:save_whichwrap = &whichwrap 85 | let l:save_virtualedit = &virtualedit 86 | set virtualedit=onemore " Need to move beyond the current line for proper selection of an end position at the end of the line when 'selection' is "exclusive"; otherwise, the "l" motion would select the newline, too. 87 | set whichwrap+=h,l 88 | try 89 | let l:beginPosition = call(a:JumpToBegin, [1, a:isInner]) 90 | "****D echomsg '**** begin' string(l:beginPosition) 'cursor:' string(getpos('.')) 91 | if l:beginPosition != [0, 0] 92 | if a:isExcludeBoundaries 93 | if l:isLinewise 94 | silent! normal! j0 95 | else 96 | silent! normal! l 97 | endif 98 | endif 99 | let l:beginPosition = getpos('.') 100 | 101 | "****D echomsg '**** end search from' string(l:beginPosition) 102 | let l:endPosition = call(a:JumpToEnd, [l:count, a:isInner]) 103 | "****D echomsg '**** end ' string(l:endPosition) 'cursor:' string(getpos('.')) 104 | if l:endPosition == [0, 0] || 105 | \ l:endPosition[0] < l:cursorLine || 106 | \ (! l:isLinewise && l:endPosition[0] == l:cursorLine && l:endPosition[1] < l:cursorCol) 107 | " The end has not been found or is located before the original 108 | " cursor position; abort. (And in the latter case, beep; in the 109 | " former case, the jump function has done that already.) 110 | " For the check, the returned jump position is used, not the 111 | " current cursor position. This enables the jump functions to 112 | " return the outer jump position for an inner jump, and allows 113 | " to select an inner text object when the cursor is on the 114 | " boundary text. 115 | " Note: For linewise selections, the returned column doesn't matter. 116 | " FIXME: For blockwise selections, the original cursor screen 117 | " column should be inside the selection. However, this requires 118 | " translation of the byte-indices used here into screen columns. 119 | " 120 | if l:endPosition != [0, 0] 121 | " We need to cancel visual mode in case an end has been 122 | " found. This is done via . 123 | " When the end is located before the original cursor 124 | " position, beep. In the other case, when the end has not 125 | " been found, the jump function has done that already. 126 | execute "normal! \\\" 127 | endif 128 | 129 | call winrestview(l:save_view) 130 | else 131 | if l:isLinewise 132 | if a:isExcludeBoundaries 133 | silent! normal! k0 134 | endif 135 | else 136 | if ! l:isExclusive && a:isExcludeBoundaries 137 | silent! normal! h 138 | elseif l:isExclusive && ! a:isExcludeBoundaries 139 | silent! normal! l 140 | endif 141 | endif 142 | 143 | let l:endPosition = getpos('.') 144 | "****D echomsg '**** text object from' string(l:beginPosition) 'to' string(l:endPosition) 145 | " When the end position is before the begin position, that's not 146 | " a valid selection. 147 | if ingo#pos#IsBefore(l:endPosition[1:2], l:beginPosition[1:2]) 148 | execute "normal! \\\" 149 | 150 | call winrestview(l:save_view) 151 | else 152 | " Now that we know that both begin and end positions exist, 153 | " create the visual selection using the corrected positions. 154 | let l:isSelected = 1 155 | 156 | if l:isLinewise 157 | " For linewise selections, always position the cursor at 158 | " the start of the end line. This is consistent with the 159 | " built-in text objects (e.g. |ap|), and avoids that the 160 | " window is horizontally scrolled to the right. 161 | let l:beginPosition[2] = 1 162 | let l:endPosition[2] = 1 163 | endif 164 | 165 | call setpos('.', l:beginPosition) 166 | execute 'normal!' a:selectionMode 167 | call setpos('.', l:endPosition) 168 | endif 169 | endif 170 | endif 171 | 172 | if ! l:isSelected && a:mode ==# 'v' 173 | " Re-enter the previous visual mode if no text object could be 174 | " selected. 175 | " This must not be done in operator-pending mode, or the 176 | " operator would work on the selection! 177 | normal! gv 178 | endif 179 | finally 180 | unlet! g:CountJump_TextObjectContext 181 | let &virtualedit = l:save_virtualedit 182 | let &whichwrap = l:save_whichwrap 183 | endtry 184 | endfunction 185 | function! CountJump#TextObject#MakeWithJumpFunctions( mapArgs, textObjectKey, types, selectionMode, JumpToBegin, JumpToEnd ) 186 | "******************************************************************************* 187 | "* PURPOSE: 188 | " Define a complete set of mappings for inner and/or outer text objects that 189 | " support an optional [count] and are driven by two functions that jump to the 190 | " beginning and end of a block. 191 | " 192 | "* ASSUMPTIONS / PRECONDITIONS: 193 | " None. 194 | " 195 | "* EFFECTS / POSTCONDITIONS: 196 | " Creates mappings for operator-pending and visual mode which act upon / 197 | " select the text delimited by the locations where the two functions jump to. 198 | " 199 | "* INPUTS: 200 | " a:mapArgs Arguments to the :map command, like '' for a 201 | " buffer-local mapping. 202 | " a:textObjectKey Mapping key [sequence] after the mandatory i/a which 203 | " start the mapping for the text object. 204 | " When this starts with , the key sequence is taken 205 | " as a template and a %s is replaced with "Inner" / 206 | " "Outer" instead of prepending i / a. Through this, 207 | " plugins can define configurable text objects that not 208 | " necessarily start with i / a. 209 | " a:types String containing 'i' for inner and 'a' for outer text 210 | " objects. 211 | " Use 'I' if you want the inner jump _include_ the text 212 | " object's boundaries, and 'A' if you want the outer jump 213 | " to _exclude_ the boundaries. This is only necessary in 214 | " special cases. 215 | " a:selectionMode Type of selection used between the patterns: 216 | " 'v' for characterwise, 'V' for linewise, '' for 217 | " blockwise. 218 | " In linewise mode, the inner text objects do not contain 219 | " the complete lines matching the pattern. 220 | " a:JumpToBegin Function which is invoked to jump to the begin of the 221 | " block. 222 | " The function is invoked at the cursor position where the 223 | " text object was requested. 224 | " a:JumpToEnd Function which is invoked to jump to the end of the 225 | " block. 226 | " The function is invoked after the call to a:JumpToBegin, 227 | " with the cursor located at the beginning of the text object. 228 | " The jump functions must take two arguments: 229 | " JumpToBegin( count, isInner ) 230 | " JumpToEnd( count, isInner ) 231 | " a:count Number of blocks to jump to. 232 | " a:isInner Flag whether the jump should be to the inner or outer 233 | " delimiter of the block. 234 | " Both Funcrefs must return a list [lnum, col], like searchpos(). This should 235 | " be the jump position (or [0, 0] if a jump wasn't possible). 236 | " They should position the cursor to the appropriate position in the current 237 | " window. 238 | " 239 | "* RETURN VALUES: 240 | " None. 241 | "******************************************************************************* 242 | for l:type in split(a:types, '\zs') 243 | if l:type ==# 'a' 244 | let [l:isInner, l:isExcludeBoundaries] = [0, 0] 245 | elseif l:type ==# 'A' 246 | let [l:isInner, l:isExcludeBoundaries] = [0, 1] 247 | elseif l:type ==# 'i' 248 | let [l:isInner, l:isExcludeBoundaries] = [1, 1] 249 | elseif l:type ==# 'I' 250 | let [l:isInner, l:isExcludeBoundaries] = [1, 0] 251 | else 252 | throw 'ASSERT: Unknown type ' . string(l:type) . ' in ' . string(a:types) 253 | endif 254 | for l:mode in ['o', 'v'] 255 | execute escape( 256 | \ printf("%snoremap %s %s :if ! CountJump#%sMapping('CountJump#TextObject#TextObjectWithJumpFunctions', %s)echoerr ingo#err#Get()endif", 257 | \ (l:mode ==# 'v' ? 'x' : l:mode), 258 | \ a:mapArgs, 259 | \ CountJump#Mappings#MakeTextObjectKey(tolower(l:type), a:textObjectKey), 260 | \ (l:mode ==# 'o' ? 'O' : ''), 261 | \ string([ 262 | \ l:mode, 263 | \ l:isInner, 264 | \ l:isExcludeBoundaries, 265 | \ a:selectionMode, 266 | \ a:JumpToBegin, 267 | \ a:JumpToEnd 268 | \ ]) 269 | \ ), '|' 270 | \) 271 | endfor 272 | endfor 273 | endfunction 274 | 275 | function! s:function(name) 276 | return function(substitute(a:name, '^\Cs:', matchstr(expand(''), '\d\+_\zefunction$'),'')) 277 | endfunction 278 | function! CountJump#TextObject#MakeWithCountSearch( mapArgs, textObjectKey, types, selectionMode, patternToBegin, patternToEnd ) 279 | "******************************************************************************* 280 | "* PURPOSE: 281 | " Define a complete set of mappings for inner and/or outer text objects that 282 | " support an optional [count] and are driven by search patterns for the 283 | " beginning and end of a block. 284 | " 285 | "* ASSUMPTIONS / PRECONDITIONS: 286 | " None. 287 | " 288 | "* EFFECTS / POSTCONDITIONS: 289 | " Creates mappings for operator-pending and visual mode which act upon / 290 | " select the text delimited by the begin and end patterns. 291 | " If the pattern doesn't match ( times), a beep is emitted. 292 | " 293 | "* INPUTS: 294 | " a:mapArgs Arguments to the :map command, like '' for a 295 | " buffer-local mapping. 296 | " a:textObjectKey Mapping key [sequence] after the mandatory i/a which 297 | " start the mapping for the text object. 298 | " When this starts with , the key sequence is taken 299 | " as a template and a %s is replaced with "Inner" / 300 | " "Outer" instead of prepending i / a. Through this, 301 | " plugins can define configurable text objects that not 302 | " necessarily start with i / a. 303 | " a:types String containing 'i' for inner and 'a' for outer text 304 | " objects. 305 | " a:selectionMode Type of selection used between the patterns: 306 | " 'v' for characterwise, 'V' for linewise, '' for 307 | " blockwise. 308 | " In linewise mode, the inner text objects do not contain 309 | " the complete lines matching the pattern. 310 | " a:patternToBegin Search pattern to locate the beginning of a block. 311 | " a:patternToEnd Search pattern to locate the end of a block. 312 | " Note: The patterns should always match a non-empty 313 | " boundary text; zero-width or matches at the end of the 314 | " buffer are problematic. 315 | " Note: Inner text objects first make an outer jump, then 316 | " go to the other (inner) side of the boundary text in 317 | " order to make a selection when the cursor is on the 318 | " boundary text, so fancy patterns that take the current 319 | " position into account are problematic, too. 320 | " If this simple matching doesn't work for you, define 321 | " your own jump function and graduate to the more powerful 322 | " CountJump#TextObject#MakeWithJumpFunctions() function 323 | " instead. 324 | "* RETURN VALUES: 325 | " None. 326 | "******************************************************************************* 327 | if a:types !~# '^[ai]\+$' 328 | throw "ASSERT: Type must consist of 'a' and/or 'i', but is: '" . a:types . "'" 329 | endif 330 | 331 | let l:scope = (a:mapArgs =~# '' ? 's:B' . bufnr('') : 's:') 332 | 333 | " If only either an inner or outer text object is defined, the generated 334 | " function must include the type, so that it is possible to separately 335 | " define a text object of the other type (via a second invocation of this 336 | " function). If the same pattern to begin / end can be used for both inner 337 | " and outer text objects, no such distinction need to be made. 338 | let l:typePrefix = (strlen(a:types) == 1 ? a:types : '') 339 | let l:functionName = CountJump#Mappings#EscapeForFunctionName(CountJump#Mappings#MakeTextObjectKey(l:typePrefix, a:textObjectKey)) 340 | 341 | let l:functionToBeginName = printf('%sJumpToBegin_%s', l:scope, l:functionName) 342 | let l:functionToEndName = printf('%sJumpToEnd_%s', l:scope, l:functionName) 343 | 344 | " In case of an inner jump, we first make an outer jump, store the position, 345 | " then go to the other (inner) side of the boundary text, and return the 346 | " outer jump position. This allows the text object to select an inner text 347 | " object when the cursor is on the boundary text. 348 | let l:searchFunction = " 349 | \ function! %s( count, isInner )\n 350 | \ if a:isInner\n 351 | \ let l:matchPos = CountJump#CountSearch(a:count, [%s, %s])\n 352 | \ if l:matchPos != [0, 0]\n 353 | \ call CountJump#CountSearch(1, [%s, %s])\n 354 | \ endif\n 355 | \ return l:matchPos\n 356 | \ else\n 357 | \ return CountJump#CountSearch(a:count, [%s, %s])\n 358 | \ endif\n 359 | \ endfunction" 360 | execute printf(l:searchFunction, 361 | \ l:functionToBeginName, 362 | \ string(a:patternToBegin), string('bcW'), 363 | \ string(a:patternToBegin), string('ceW'), 364 | \ string(a:patternToBegin), string('bcW') 365 | \) 366 | execute printf(l:searchFunction, 367 | \ l:functionToEndName, 368 | \ string(a:patternToEnd), string('ceW'), 369 | \ string(a:patternToEnd), string('bcW'), 370 | \ string(a:patternToEnd), string('eW') 371 | \) 372 | " Note: For the outer jump to end, a:patternToEnd must not match at the 373 | " current cursor position (no 'c' flag to search()). This allows to handle 374 | " outer text objects that are delimited by the same, single character. 375 | 376 | return CountJump#TextObject#MakeWithJumpFunctions(a:mapArgs, a:textObjectKey, a:types, a:selectionMode, s:function(l:functionToBeginName), s:function(l:functionToEndName)) 377 | endfunction 378 | 379 | let &cpo = s:save_cpo 380 | unlet s:save_cpo 381 | " vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax : 382 | -------------------------------------------------------------------------------- /doc/CountJump.txt: -------------------------------------------------------------------------------- 1 | *CountJump.txt* Create custom motions and text objects via repeated jumps. 2 | 3 | COUNT JUMP by Ingo Karkat 4 | *CountJump.vim* 5 | description |CountJump-description| 6 | usage |CountJump-usage| 7 | example |CountJump-example| 8 | integration |CountJump-integration| 9 | installation |CountJump-installation| 10 | limitations |CountJump-limitations| 11 | known problems |CountJump-known-problems| 12 | todo |CountJump-todo| 13 | history |CountJump-history| 14 | 15 | ============================================================================== 16 | DESCRIPTION *CountJump-description* 17 | 18 | Though it is not difficult to write a custom |movement| (basically a |:map| 19 | that executes some kind of search or jump) and a custom |text-object| (an 20 | |:omap| that selects a range of text), this is too complex for a novice user 21 | and often repetitive. 22 | This plugin covers the common use case where the movement and boundaries of a 23 | text object can be specified via start and end patterns, and offers a single 24 | function to set up related mappings. With it, you can enhance some built-in 25 | Vim mappings to take an optional [count], and quickly define new mappings for 26 | help file sections, diff hunks, embedded macros, and so on... 27 | 28 | As a generalization of the start and end patterns, the movement and boundaries 29 | of a text object can also be specified via jump functions, i.e. Funcrefs of 30 | functions that position the cursor on the appropriate location and return that 31 | location. This can be used where the jump is difficult to express with a 32 | single regular expression, the jump may need adapting depending on the 33 | context, or other uses. 34 | This plugin contains some support for movement and text objects consisting of 35 | text regions that can be defined by continuous lines that match a particular 36 | pattern, e.g. comment blocks that all start with /^\s*#/. 37 | 38 | SEE ALSO * 39 | 40 | The following ftplugins use this plugin: 41 | 42 | - |diff_movement.vim| (vimscript #3180): 43 | Movement over diff hunks with ]] etc. 44 | - |diffwindow_movement.vim| (vimscript #3719): 45 | Movement over changes in a diff window. 46 | - |dosbatch_movement.vim| (vimscript #4004): 47 | Movement over MSDOS batch file functions / labels with ]m etc. 48 | - |fortunes_movement.vim| (vimscript #3181): 49 | Movement over email fortunes with ]] etc. 50 | - |help_movement.vim| (vimscript #3179): 51 | Movement over Vim help sections with ]] etc. 52 | - |mail_movement.vim| (vimscript #3182): 53 | Movement over email quotes with ]] etc. 54 | - |vbs_movement.vim| (vimscript #4003): 55 | Movement over VBScript classes / functions / properties / subs with ]m etc. 56 | - |vim_movement.vim| (vimscript #4002): 57 | Movement over Vim functions with ]m etc. 58 | 59 | The following plugins offer new motions through this plugin: 60 | 61 | - |ConflictMotions.vim| (vimscript #3991): 62 | Motions to and inside SCM conflict markers. 63 | - |ErrorMotion.vim| (vimscript #0000): 64 | Motions to text highlighted as error. 65 | - |JumpToNextTag.vim| (vimscript #0000): 66 | Motions to the next definition or occurrence of a tag inside the current 67 | buffer. 68 | - |JumpToTrailingWhitespace.vim| (vimscript #3968): 69 | Motions to locate unwanted whitespace at the end of lines. 70 | - |JumpToVerticalBlock.vim| (vimscript #5657): 71 | Like W / E, but vertically in the same column. 72 | - |JumpToVerticalOccurrence.vim| (vimscript #4841): 73 | Like f{char}, but searching the same screen column, not line. 74 | - |SameSyntaxMotion.vim| (vimscript #4338): 75 | Motions to the borders of the same syntax highlighting. 76 | - |TaskMotion.vim| (vimscript #3990): 77 | Motions to task and TODO markers. 78 | 79 | RELATED WORKS * 80 | 81 | - motpat.vim (vimscript #3030) offers similar functions to setup motion 82 | mappings, but no text objects (yet). 83 | - textobj-user (vimscript #2100) has support for user-defined text objects via 84 | regular expressions, but they don't support selecting multiple via [count]. 85 | - movealong.vim (vimscript #4691) provides a :Movealong command (and optional 86 | mappings) that repeatedly executes a motion until a condition of a syntax, 87 | pattern match, or arbitrary expression is met. 88 | 89 | ============================================================================== 90 | USAGE *CountJump-usage* 91 | 92 | The plugin defines several functions, which set up the appropriate mappings 93 | based on the arguments that you supply. The following is an overview; you'll 94 | find the details directly in the implementation files in the 95 | autoload/CountJump/ directory. 96 | 97 | CountJump#Motion#MakeBracketMotion( mapArgs, keyAfterBracket, inverseKeyAfterBracket, patternToBegin, patternToEnd, isEndPatternToEnd, ... ) 98 | 99 | This function sets up mappings starting with [ and ] for movement (with 100 | optional [count]) relative to the current cursor position, targeting either a 101 | text pattern at the beginning ([{keyAfterBracket} mapping) or a text pattern 102 | at the end (]{inverseKeyAfterBracket} mapping) of whatever you want to treat 103 | as a text block. 104 | 105 | CountJump#Motion#MakeBracketMotionWithJumpFunctions( mapArgs, keyAfterBracket, inverseKeyAfterBracket, JumpToBeginForward, JumpToBeginBackward, JumpToEndForward, JumpToEndBackward, isEndJumpToEnd, ... ) 106 | 107 | This function sets up mappings starting with [ and ] for movement (with 108 | optional [count]) relative to the current cursor position, but rely on four 109 | passed jump functions instead of text patterns to do the movement. 110 | 111 | 112 | CountJump#TextObject#MakeWithCountSearch( mapArgs, textObjectKey, types, selectionMode, patternToBegin, patternToEnd ) 113 | 114 | Defines a complete set of mappings for inner and/or outer text objects that 115 | support an optional [count] and are driven by search patterns for the 116 | beginning and end of a block. Outer text objects include the matched pattern 117 | text, inner ones not. Selection can be characterwise, linewise or blockwise. 118 | 119 | 120 | CountJump#TextObject#MakeWithJumpFunctions( mapArgs, textObjectKey, types, selectionMode, JumpToBegin, JumpToEnd ) 121 | 122 | This is a generalization of CountJump#TextObject#MakeWithCountSearch() that 123 | invokes custom functions instead of searching for a fixed pattern. This is 124 | useful if the check for a match is too complex for a single regular 125 | expression, or if you need to adjust the match position depending on the 126 | circumstances. 127 | 128 | 129 | Often, a region can be defined as a block of continuous lines that all match a 130 | certain pattern (or, even more generic, where a provided predicate function 131 | returns a match position). The following functions aid in implementing 132 | movements to the boundaries of these regions and text objects consisting of 133 | the region: 134 | 135 | CountJump#Region#JumpToRegionEnd( count, Expr, isMatch, step, isToEndOfLine ) 136 | 137 | Starting from the current line, search for the position where the count'th 138 | region ends. Use this function to build Funcrefs for forward / backward jumps 139 | that can then be passed to CountJump#TextObject#MakeWithJumpFunctions(). 140 | 141 | CountJump#Region#JumpToNextRegion( count, Expr, isMatch, step, isAcrossRegion, isToEndOfLine ) 142 | 143 | Starting from the current line, search for the position where the count'th 144 | region begins/ends. 145 | 146 | CountJump#Region#Motion#MakeBracketMotion( mapArgs, keyAfterBracket, inverseKeyAfterBracket, Expr, isMatch, ... ) 147 | 148 | This function sets up mappings starting with [ and ] for movement (with 149 | optional [count]) relative to the current cursor position, targeting a text 150 | region defined by contiguous lines that (don't) match a:Expr. 151 | 152 | CountJump#Region#TextObject#Make( mapArgs, textObjectKey, types, selectionMode, Expr, isMatch ) 153 | 154 | Defines a complete set of mappings for inner and/or outer text objects that 155 | support an optional [count] and select regions of lines which are defined by 156 | contiguous lines that (don't) match a:Expr. 157 | The inner text object comprises all lines of the region itself, while the 158 | outer text object also includes all adjacent lines above and below which do 159 | not themselves belong to a region. 160 | 161 | *g:CountJump_MotionContext* *g:CountJump_TextObjectContext* 162 | The custom Funcrefs for jumps and predicates of lines belonging to a range may 163 | be invoked multiple times until the CountJump function arrives at its 164 | destination. To help the Funcrefs to determine where in this sequence they 165 | are, an empty g:CountJump_MotionContext |dictionary| is initialized at the 166 | start of a range motion (for pattern / jump functions, this isn't necessary; 167 | there's either no custom code or just a single invocation), and an empty 168 | g:CountJump_TextObjectContext dictionary is initialized at the start of a text 169 | object. Funcrefs can put custom information (e.g. the particular comment 170 | prefix on the current line) in there and evaluate this in subsequent 171 | invocations. 172 | 173 | ============================================================================== 174 | EXAMPLE *CountJump-example* 175 | 176 | Let's illustrate the usage by developing custom motions and text objects for 177 | Pascal begin..end blocks. 178 | 179 | We want to move around blocks, and override the default section movements for 180 | it: 181 | ]] Go to [count] next start of a block. 182 | ][ Go to [count] next end of a block. 183 | [[ Go to [count] previous start of a block. 184 | [] Go to [count] previous end of a block. 185 | > 186 | call CountJump#Motion#MakeBracketMotion('', '', '', '\c^begin\n\zs', '\c^.*\nend', 0) 187 | The begin pattern positions the cursor on the beginning of the line following 188 | the "begin" keyword, the end pattern on the beginning of the line 189 | preceding the "end" keyword. 190 | 191 | 192 | We want to select a block, either including or excluding the lines with the 193 | begin..end keywords: 194 | ib "inner block" text object, select [count] contents of 195 | a block. 196 | ab "a block" text object, select [count] blocks. 197 | > 198 | call CountJump#TextObject#MakeWithCountSearch('', 'b', 'ai', 'V', '\c^begin\n', '\c^end.*$') 199 | 200 | If there is a filetype detection for Pascal files, we can simply put the 201 | above calls in a ~/.vim/ftplugin/pascal_movement.vim script and are done. 202 | 203 | ============================================================================== 204 | INSTALLATION *CountJump-installation* 205 | 206 | The code is hosted in a Git repo at 207 | https://github.com/inkarkat/vim-CountJump 208 | You can use your favorite plugin manager, or "git clone" into a directory used 209 | for Vim |packages|. Releases are on the "stable" branch, the latest unstable 210 | development snapshot on "master". 211 | 212 | This script is also packaged as a |vimball|. If you have the "gunzip" 213 | decompressor in your PATH, simply edit the *.vmb.gz package in Vim; otherwise, 214 | decompress the archive first, e.g. using WinZip. Inside Vim, install by 215 | sourcing the vimball or via the |:UseVimball| command. > 216 | vim CountJump*.vmb.gz 217 | :so % 218 | To uninstall, use the |:RmVimball| command. 219 | 220 | DEPENDENCIES *CountJump-dependencies* 221 | 222 | - Requires Vim 7.0 or higher. 223 | - Requires the |ingo-library.vim| plugin (vimscript #4433), version 1.041 or 224 | higher. 225 | 226 | ============================================================================== 227 | INTEGRATION *CountJump-integration* 228 | *CountJump-remap-motions* *CountJump-plug-motions* 229 | If you want to define motions that do not start with [ / ], and the plugin 230 | that employs CountJump offers a configuration variable like 231 | g:PluginName_mapping to influence the mapped key(s), you can define 232 | intermediate -mappings (|using-|), and then define your own custom 233 | mappings based on them: > 234 | let g:PluginName_mapping = 'PluginName%s' 235 | nmap { PluginNameBackward 236 | nmap } PluginNameForward 237 | omap { PluginNameBackward 238 | omap } PluginNameForward 239 | vmap { PluginNameBackward 240 | vmap } PluginNameForward 241 | < 242 | *CountJump-remap-text-objects* *CountJump-plug-text-objects* 243 | If you want to define text objects that do not start with i / a, and the plugin 244 | that employs CountJump offers a configuration variable like 245 | g:PluginName_mapping to influence the mapped key(s), you can define 246 | intermediate -mappings (|using-|), and then define your own custom 247 | mappings based on them: > 248 | let g:PluginName_mapping = 'PluginName%s' 249 | omap ,p PluginNameInner 250 | omap ,P PluginNameOuter 251 | vmap ,p PluginNameInner 252 | vmap ,P PluginNameOuter 253 | < 254 | ============================================================================== 255 | LIMITATIONS *CountJump-limitations* 256 | 257 | KNOWN PROBLEMS *CountJump-known-problems* 258 | 259 | - An outer text object cannot consist of the same, multiple characters; 260 | nothing will be selected (because the end pattern also matches at the begin 261 | position). A same single character pattern works, though. 262 | - For blockwise text objects, the original cursor position should be required 263 | to be inside the selection. However, this requires translation of the 264 | byte-indices here into screen columns, and is thus non-trivial to implement. 265 | - The behavior with wrap messages is slightly inconsistent: Like normal 266 | /pattern search, we print the wrap message, but don't print an error message 267 | (like "Pattern not found"), but beep instead (like the built-in ]m etc. 268 | mappings (but not the ), }, ]] mappings, which fail silently?!)). 269 | - A repeat (via |.|) of the operator-pending mapping loses the previously 270 | given [count], and operates on just one search / jump. 271 | 272 | TODO *CountJump-todo* 273 | 274 | IDEAS *CountJump-ideas* 275 | 276 | - Add customization parameter so that the motion / text object includes the 277 | start / end of buffer in case patternToBegin / patternToEnd do not match any 278 | more. 279 | 280 | CONTRIBUTING *CountJump-contribute* 281 | 282 | Report any bugs, send patches, or suggest features via the issue tracker at 283 | https://github.com/inkarkat/vim-CountJump/issues or email (address below). 284 | 285 | ============================================================================== 286 | HISTORY *CountJump-history* 287 | 288 | 1.91 03-Apr-2020 289 | - ENH: Allow Funcref for a:searchArguments / first search argument / 290 | a:patternTo{Begin,End} 291 | - ENH: Allow to influence given [count] via 292 | CountJump#CountCountJumpWithWrapMessage() variant of 293 | CountJump#CountJumpWithWrapMessage(). 294 | - FIX: Avoid "Error detected while processing :" additional line above an 295 | exception thrown (from a client-supplied Funcref). 296 | - ENH: Allow client-supplied Funcrefs to throw a "CountJump: " 297 | exception to issue custom errors. 298 | - Prevent a custom text object from clobbering the register passed to a custom 299 | operator (as :omaps defined by this plugin). 300 | *** You need to update to ingo-library (vimscript #4433) version 1.041! *** 301 | 302 | 1.90 11-Feb-2018 303 | - Rename g:CountJump_Context to g:CountJump_MotionContext, to avoid a clash 304 | with the text object in CountJump#TextObject#TextObjectWithJumpFunctions(). 305 | Clear g:CountJump_MotionContext at the end of the function. 306 | - Rename g:CountJump_Context to g:CountJump_TextObjectContext. When using 307 | CountJump#Region#TextObject#Make(), the generated jump functions also set 308 | that context, and there's a clash with 309 | CountJump#TextObject#TextObjectWithJumpFunctions(). In particular, one 310 | cannot store a context for the entire text object (both jumps to begin and 311 | end), as the individual jump functions clear the identical context. Clear 312 | g:CountJump_TextObjectContext at the end of the function. 313 | - ENH: Support non-argument Funcref for a:Expr that gets evaluated once at the 314 | beginning, and should yield a regular expression that is then used in its 315 | stead. 316 | 317 | 1.86 24-Jul-2017 318 | - CountJump#Motion#MakeBracketMotion(): The a:patternToBegin, a:patternToEnd, 319 | a:searchName arguments may contain special characters that need escaping in 320 | a map. Use ingo#escape#command#mapescape(). 321 | - CountJump#Region#Motion#MakeBracketMotion(): The a:Expr argument may contain 322 | special characters that need escaping in a map. Use 323 | ingo#escape#command#mapescape(). 324 | - Retire duplicated fallback for ingo#motion#helper#AdditionalMovement(); 325 | since version 1.85, the ingo-library is now a mandatory dependency. 326 | - CountJump#Motion#MakeBracketMotionWithJumpFunctions(), 327 | CountJump#TextObject#MakeWithJumpFunctions(), 328 | CountJump#Region#Motion#MakeBracketMotion(): Catch all exceptions and 329 | report only the text. My ErrorMotion.vim plugin could be slow to find the 330 | next error if there is none. Aborting with would print a long 331 | multi-line exception: "Error detected while processing function 332 | ErrorMotion#Forward[1]..CountJump#JumpFunc[39]..HlgroupMotion#JumpWithWrapMessage[26]..CountJump#CountJumpFuncWithWrapMessage[35]..HlgroupMotion#SearchFirstHlgroup: 333 | line 67: Interrupted". As we apparently cannot avoid the printing of "Type 334 | :quit to exit Vim", suppress the Vim:Interrupt exception, and 335 | :echoerr all others. 336 | 337 | 1.85 23-Dec-2014 338 | - Use ingo/pos.vim. 339 | - Use ingo#msg#WarningMsg(). 340 | - Make test for 'virtualedit' option values also account for multiple values. 341 | *** You need to install / update to ingo-library (vimscript #4433) version 342 | 1.019! It is now mandatory for the plugin. *** 343 | 344 | 1.84 25-Apr-2014 345 | - Pin down the 'virtualedit' setting (to "onemore") during 346 | CountJump#TextObject#TextObjectWithJumpFunctions() to avoid that a 347 | characterwise outer text object that ends at the end of a line includes the 348 | line's newline character when 'selection' is "exclusive". 349 | - FIX: There are no buffer-local functions with a b: scope prefix, and Vim 350 | 7.4.264 disallows those invalid function names now. Previously, multiple 351 | buffer-local text objects with the same key would override each other. 352 | Instead, make the functions created by 353 | CountJump#TextObject#MakeWithCountSearch() and 354 | CountJump#Region#TextObject#Make() buffer-scoped by prefixing "s:B" and the 355 | buffer number. 356 | 357 | 1.83 23-Jan-2014 358 | - Use more canonical way of invoking the Funcrefs in 359 | CountJump#Motion#MakeBracketMotionWithJumpFunctions(); this will then also 360 | work with passed String function names. 361 | - FIX: Need to save v:count1 before issuing the normal mode "gv" command. 362 | - Minor: Make substitute() robust against 'ignorecase'. 363 | - Add optional dependency to ingo-library (vimscript #4433). 364 | 365 | 1.82 30-Oct-2012 (unreleased) 366 | - FIX: In text objects, when the end position is before the begin position, 367 | that's not a valid selection. Test for this and abort in that case. 368 | - For linewise selections, always position the cursor at the start of the end 369 | line to be consistent with the built-in text objects, and to avoid 370 | complicating the search patterns when attempting to do this through them. 371 | 372 | 1.81 16-Oct-2012 373 | - ENH: Add optional a:searchName argument to 374 | CountJump#Motion#MakeBracketMotion() to make searches wrap around when 375 | 'wrapscan' is set. Custom jump functions can do this since version 1.70; 376 | now, this can also be utilized by motions defined via a search pattern. 377 | - BUG: Wrong variable scope for copied a:isBackward in 378 | CountJump#CountSearchWithWrapMessage(). 379 | 380 | 1.80 15-Oct-2012 381 | - FIX: In CountJump#TextObject#TextObjectWithJumpFunctions(), do not beep when 382 | there's no end position. In this case, the jump function (often 383 | CountJump#CountSearch()) should have emitted a beep already, and we want to 384 | avoid a double beep. 385 | - Also handle move to the buffer's very last character in operator-pending 386 | mode with a pattern to end "O" motion. 387 | - Add CountJump#CountJumpFuncWithWrapMessage() / CountJump#CountJumpFunc() to 388 | help implement custom motions with only a simple function that performs a 389 | single jump. 390 | - FIX: Visual end pattern / jump to end with 'selection' set to "exclusive" 391 | also requires the special additional treatment of moving one right, like 392 | operator-pending mode. 393 | - BUG: Operator-pending motion with end pattern / jump to end operates on one 394 | character too few when moving to begin. 395 | - Clear any previous wrap message when wrapping is enabled; it's confusing 396 | otherwise. 397 | 398 | 1.70 03-Sep-2012 399 | - ENH: Check for searches wrapping around the buffer and issue a corresponding 400 | warning, like the built-in searches do. Though the mappings that can be made 401 | with CountJump currently do not use 'wrapscan', other plugins that define 402 | their own jump functions and use the CountJump#CountJump() function for it 403 | may use it. Create function overloads CountJump#CountJumpWithWrapMessage() 404 | and CountJump#CountSearchWithWrapMessage(). 405 | 406 | 1.60 27-Mar-2012 407 | - ENH: Allow motions that do not start with [ / ] and text objects that do not 408 | start with i / a by passing keys that begin with . With this, plugins 409 | using CountJump can offer the expected customizability. Since most users 410 | probably still prefer the default keys, it is recommended that plugins do 411 | not use mappings from the start, but make the a:keyAfterBracket / 412 | a:inverseKeyAfterBracket / a:textObjectKey configurable via a 413 | g:PluginName_mapping variable, and instruct users to set this to 414 | "PluginName%s" and create their own mappings based on them, as 415 | described in |CountJump-integration|. 416 | 417 | 1.50 30-Aug-2011 418 | - For regions of lines, also support a match()-like Funcref instead of a 419 | pattern to define the range. This for example enables to define a range of 420 | diff changes via a predicate function that checks diff_hlID() != 0. 421 | - Initialize global g:CountJump_Context object for custom use by Funcrefs. 422 | 423 | 1.41 13-Jun-2011 424 | - FIX: Directly ring the bell to avoid problems when running under :silent!. 425 | 426 | 1.40 20-Dec-2010 427 | - ENH: Added CountJump#Region#TextObject#Make() to easily define text objects 428 | for regions. 429 | - Interface change: Jump functions again return position (and actual, 430 | corrected one for a:isToEndOfLine). Though the position is not used for 431 | motions, it is necessary for text objects to differentiate between "already 432 | at the begin/end position" and "no such position". 433 | 434 | 1.30 20-Dec-2010 435 | - ENH: Added CountJump#Region#Motion#MakeBracketMotion() to easily define 436 | bracket motions for regions. 437 | - Interface changes: 438 | - Jump functions don't necessarily return jump position any more; this 439 | special case is only required for text objects. 440 | - Moved CountJump#Region#Jump() to CountJump#JumpFunc(). 441 | - Added a:isToEndOfLine argument to CountJump#Region#JumpToRegionEnd() and 442 | CountJump#Region#JumpToNextRegion(), which is useful for operator-pending 443 | and characterwise visual mode mappings; the entire last line will then be 444 | operated on / selected. 445 | - Added a:isMatch argument to CountJump#Region#SearchForRegionEnd(), 446 | CountJump#Region#JumpToRegionEnd(), 447 | CountJump#Region#SearchForNextRegion(), 448 | CountJump#Region#JumpToNextRegion(). This allows definition of regions via 449 | non-matches, which can be substantially simpler (and faster to match) than 450 | coming up with a "negative" regular expression. 451 | 452 | 1.22 06-Aug-2010 453 | - No more motion mappings and text objects for select mode; as the mappings 454 | start with a printable character, no select-mode mapping should be defined. 455 | 456 | 1.21 03-Aug-2010 457 | - FIX: A 2]] jump inside a region (unless last line) jumped like a 1]] jump. 458 | The search for next region must not decrease the iteration counter when 459 | _not_ searching _across_ the region. 460 | - FIX: Must not do (characterwise) end position adaptation for linewise text 461 | object that does not exclude boundaries. 462 | - Switched example from email fortunes to Pascal begin..end blocks, as they 463 | are conceptually easier. 464 | 465 | 1.20 02-Aug-2010 466 | - ENH: In CountJump#Motion#MakeBracketMotion(), a:keyAfterBracket and 467 | a:inverseKeyAfterBracket can now be empty, the resulting mappings are then 468 | omitted. Likewise, any jump function can be empty in 469 | CountJump#Motion#MakeBracketMotionWithJumpFunctions(). 470 | - With the added CountJump#Motion#MakeBracketMotionWithJumpFunctions() motions 471 | can be defined via jump functions, similar to how text objects can be 472 | defined. 473 | - Added CountJump/Region.vim to move to borders of a region defined by lines 474 | matching a pattern. 475 | - FIX: CountJump#CountJump() with mode "O" didn't add original position to 476 | jump list. 477 | - The previous visual selection is kept when the text object could not be 478 | selected. (Beforehand, a new selection of the text object's selection type 479 | was created.) 480 | - The adjustment movements after the jumps to the text object boundaries now 481 | do not cause beeps if that movement cannot be done (e.g. a 'j' at the end of 482 | the buffer). 483 | 484 | 1.10 19-Jul-2010 485 | - Changed behavior if there aren't [count] matches: Instead of jumping to the 486 | last available match (and ringing the bell), the cursor stays at the 487 | original position, like with the old vi-compatible motions. 488 | - ENH: Only adding to jump list if there actually is a match. This is like the 489 | built-in Vim motions work. 490 | - FIX: For a linewise text object, the end cursor column is not important; do 491 | not compare with the original cursor column in this case. 492 | 493 | 1.00 22-Jun-2010 494 | First published version. 495 | 496 | 0.01 14-Feb-2009 497 | Started development. 498 | 499 | ============================================================================== 500 | Copyright: (C) 2009-2020 Ingo Karkat 501 | The VIM LICENSE applies to this plugin; see |copyright|. 502 | 503 | Maintainer: Ingo Karkat 504 | ============================================================================== 505 | vim:tw=78:ts=8:ft=help:norl: 506 | -------------------------------------------------------------------------------- /tests/CountJump.txt: -------------------------------------------------------------------------------- 1 | begin 2 | This {{{ Mauris neque ante, placerat at, mollis vitae, faucibus quis, leo. }}} Ut feugiat. 3 | {{{Vivamus}}} urna quam, congue vulputate, convallis non, cursus cursis, risus. 4 | Quisque aliquet. {{{Donec vulputate egestas elit. Morbi dictum, sem sit amet 5 | aliquam euismod, odio tortor pellentesque odio, ac ultrices enim nibh sed quam.}}} 6 | {{{ 7 | Integer tortor velit, condimentum a, vestibulum eget, sagittis nec, neque. 8 | Aenean est urna, bibendum et, imperdiet at, rhoncus in, arcu. In hac habitasse 9 | platea dictumst. Vestibulum blandit dignissim dui. Maecenas vitae magna non 10 | felis ornare consectetuer. Sed lorem. Nam leo. In eget pede. Donec porta. 11 | }}} 12 | end 13 | 14 | {{{ ... 15 | Integer tortor velit, condimentum a, vestibulum eget, sagittis nec, neque. 16 | Aenean est urna, bibendum et, imperdiet at, rhoncus in, arcu. In hac habitasse 17 | platea dictumst. Vestibulum blandit dignissim dui. Maecenas vitae magna non 18 | felis ornare consectetuer. Sed lorem. Nam leo. In eget pede. Donec porta. 19 | ... }}} 20 | 21 | {{{Etiam facilisis. {{{Nam suscipit.}}} {{{Ut consectetuer {{{leo}}} vehicula augue.}}} Aliquam 22 | cursus. Integer.}}} 23 | [[[Etiam facilisis. [[[Nam suscipit.]]] [[[Ut consectetuer [[[leo]]] vehicula augue.]]] Aliquam 24 | cursus. Integer.]]] 25 | 26 | The {{{one}}} and {{{two}}} and {{{three}}} in a row. Plus {{{four}}}! 27 | Maybe {{{five}}}, or {{{six}}} at most. Definitely stop at {{{seven}}}. 28 | 29 | begin 30 | call CountJump#Motion#MakeBracketMotion('', 'x', 'X', '{{{', '}}}', 1) 31 | call CountJump#Motion#MakeBracketMotion('', 'y', 'Y', '[[[', ']]]', 0) 32 | call CountJump#TextObject#MakeWithCountSearch('', 'x', 'ai', 'v', '{{{', '}}}') 33 | call CountJump#TextObject#MakeWithCountSearch('', 'X', 'ai', 'V', '{{{', '}}}') 34 | call CountJump#TextObject#MakeWithCountSearch('', 'y', 'ai', 'v', '[[[', ']]]') 35 | end 36 | begin 37 | call CountJump#Motion#MakeBracketMotion('', '(CurlyBegin%s)', '(CurlyEnd%s)', '{{{', '}}}', 1) 38 | call CountJump#Motion#MakeBracketMotion('', '(SquareBegin%s)', '(SquareEnd%s)', '[[[', ']]]', 0) 39 | call CountJump#TextObject#MakeWithCountSearch('', '(Curly%sObject)', 'ai', 'v', '{{{', '}}}') 40 | nmap [x (CurlyBeginBackward) 41 | nmap ]x (CurlyBeginForward) 42 | nmap [X (CurlyEndBackward) 43 | nmap ]X (CurlyEndForward) 44 | vmap ax (CurlyOuterObject) 45 | vmap ix (CurlyInnerObject) 46 | end 47 | 48 | begin 49 | call CountJump#Motion#MakeBracketMotion('', '', '', '\c^begin\n\zs', '\c^.*\nend', 0) 50 | call CountJump#TextObject#MakeWithCountSearch('', 'b', 'a', 'V', '\c^begin\n', '\c^end.*$') 51 | call CountJump#TextObject#MakeWithCountSearch('', 'b', 'i', 'V', '\c^begin\n', '\c^end.*$') 52 | end 53 | begin 54 | call CountJump#Motion#MakeBracketMotion('', '(PascalBegin%s)', '(PascalEnd%s)', '\c^begin\n\zs', '\c^.*\nend', 0) 55 | call CountJump#TextObject#MakeWithCountSearch('', '(Pascal%s)', 'a', 'V', '\c^begin\n', '\c^end.*$') 56 | call CountJump#TextObject#MakeWithCountSearch('', '(Pascal%s)', 'i', 'V', '\c^begin\n', '\c^end.*$') 57 | nmap [[ (PascalBeginBackward) 58 | nmap ]] (PascalBeginForward) 59 | nmap [] (PascalEndBackward) 60 | nmap ][ (PascalEndForward) 61 | vmap ab (PascalOuter) 62 | vmap ib (PascalInner) 63 | end 64 | 65 | begin 66 | end 67 | -------------------------------------------------------------------------------- /tests/CountJumpRegion.txt: -------------------------------------------------------------------------------- 1 | begin 2 | |This {{{ Mauris neque ante, placerat at, mollis vitae, faucibus quis, leo. }}} Ut feugiat. 3 | |{{{Vivamus}}} urna quam, congue vulputate, convallis non, cursus cursis, risus. 4 | |Quisque aliquet. {{{Donec vulputate egestas elit. Morbi dictum, sem sit amet 5 | |aliquam euismod, odio tortor pellentesque odio, ac ultrices enim nibh sed quam.}}} 6 | {{{ 7 | |Integer tortor velit, condimentum a, vestibulum eget, sagittis nec, neque. 8 | |Aenean est urna, bibendum et, imperdiet at, rhoncus in, arcu. In hac habitasse 9 | |platea dictumst. Vestibulum blandit dignissim dui. Maecenas vitae magna non 10 | |felis ornare consectetuer. Sed lorem. Nam leo. In eget pede. Donec porta. 11 | }}} 12 | end 13 | 14 | {{{ ... 15 | |Integer tortor velit, condimentum a, vestibulum eget, sagittis nec, neque. 16 | |Aenean est urna, bibendum et, imperdiet at, rhoncus in, arcu. In hac habitasse 17 | |platea dictumst. Vestibulum blandit dignissim dui. Maecenas vitae magna non 18 | |felis ornare consectetuer. Sed lorem. Nam leo. In eget pede. Donec porta. 19 | ... }}} 20 | 21 | |{{{Etiam facilisis. {{{Nam suscipit.}}} {{{Ut consectetuer {{{leo}}} vehicula augue.}}} Aliquam 22 | |cursus. Integer.}}} 23 | |[[[Etiam facilisis. [[[Nam suscipit.]]] [[[Ut consectetuer [[[leo]]] vehicula augue.]]] Aliquam 24 | |cursus. Integer.]]] 25 | 26 | |The {{{one}}} and {{{two}}} and {{{three}}} in a row. Plus {{{four}}}! 27 | |Maybe {{{five}}}, or {{{six}}} at most. Definitely stop at {{{seven}}}. 28 | 29 | begin 30 | call CountJump#Region#Motion#MakeBracketMotion('', 'x', 'X', '^|', 1) 31 | call CountJump#Region#TextObject#Make('', 'x', 'a', 'V', '^|', 1) 32 | call CountJump#Region#TextObject#Make('', 'x', 'i', 'V', '^|', 1) 33 | end 34 | begin 35 | call CountJump#Region#Motion#MakeBracketMotion('', '(BarBegin%s)', '(BarEnd%s)', '^|', 1) 36 | call CountJump#Region#TextObject#Make('', '(Bar%sObject)', 'ai', 'V', '^|', 1) 37 | nmap [x (BarBeginBackward) 38 | nmap ]x (BarBeginForward) 39 | nmap [X (BarEndBackward) 40 | nmap ]X (BarEndForward) 41 | vmap ax (BarOuterObject) 42 | vmap ix (BarInnerObject) 43 | end 44 | 45 | begin 46 | call CountJump#Region#Motion#MakeBracketMotion('', '', '', '^begin\|^end', 0) 47 | call CountJump#Region#TextObject#Make('', 'b', 'a', 'V', '^begin\|^end', 0) 48 | call CountJump#Region#TextObject#Make('', 'b', 'i', 'V', '^begin\|^end', 0) 49 | end 50 | begin 51 | call CountJump#Region#Motion#MakeBracketMotion('', '(PascalBegin%s)', '(PascalEnd%s)', '^begin\|^end', 0) 52 | call CountJump#Region#TextObject#Make('', '(Pascal%s)', 'a', 'V', '^begin\|^end', 0) 53 | call CountJump#Region#TextObject#Make('', '(Pascal%s)', 'i', 'V', '^begin\|^end', 0) 54 | nmap [[ (PascalBeginBackward) 55 | nmap ]] (PascalBeginForward) 56 | nmap [] (PascalEndBackward) 57 | nmap ][ (PascalEndForward) 58 | vmap ab (PascalOuter) 59 | vmap ib (PascalInner) 60 | end 61 | 62 | begin 63 | end 64 | -------------------------------------------------------------------------------- /tests/Region.txt: -------------------------------------------------------------------------------- 1 | $ on warning then goto nopriv 2 | $ set on 3 | $ set process/privilege=(sysnam,sysprv) 4 | $!... 5 | 6 | 7 | $nopriv: 8 | $ write sys$output "Required privs: SYSNAM, SYSPRV" 9 | $ status = 36 10 | $ goto exit 11 | $!... 12 | $exit: 13 | $ set process/privilege=(nosysnam,nosysprv) 14 | 15 | $ set mine 16 | $ set yours 17 | $ 18 | $ set all 19 | $ 20 | $ 21 | $ goto exit 22 | call CountJump#Region#Motion#MakeBracketMotion( '', '', '', '^\$\s*\S', 1) 23 | call CountJump#Region#TextObject#Make( '', 'p', 'ai', 'V', '^\$\s*\S', 1 ) 24 | 25 | -------------------------------------------------------------------------------- /tests/TextObject.txt: -------------------------------------------------------------------------------- 1 | For more intricate $formula in here$ patterns like 2 | this \[ math formula \] 3 | scripts could (should?) be $$written$$. 4 | 5 | call CountJump#TextObject#MakeWithCountSearch('', '$', 'ai', 'v', '\$', '\$') 6 | call CountJump#TextObject#MakeWithCountSearch('', 's', 'ai', 'v', '\$', '\$') 7 | call CountJump#TextObject#MakeWithCountSearch('', 'S', 'ai', 'v', '\$\$', '\$\$') 8 | call CountJump#TextObject#MakeWithCountSearch('', 'm', 'ai', 'v', '\\\[', '\\\]') 9 | 10 | --------------------------------------------------------------------------------