From 433018f21808fba844f9418cf1ce8dc1aefa6f1f Mon Sep 17 00:00:00 2001 From: Ingo Karkat Date: Mon, 20 Dec 2010 00:00:00 +0000 Subject: [PATCH] Version 1.40 - ENH: Added CountJump#Region#TextObject#Make() to easily define text objects for regions. - Interface change: Jump functions again return position (and actual, corrected one for a:isToEndOfLine). Though the position is not used for motions, it is necessary for text objects to differentiate between "already at the begin/end position" and "no such position". --- autoload/CountJump/Region.vim | 18 +++- autoload/CountJump/Region/Motion.vim | 1 + autoload/CountJump/Region/TextObject.vim | 121 +++++++++++++++++++++++ autoload/CountJump/TextObject.vim | 31 +++--- doc/CountJump.txt | 17 ++++ 5 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 autoload/CountJump/Region/TextObject.vim diff --git a/autoload/CountJump/Region.vim b/autoload/CountJump/Region.vim index 6a803fe..8489153 100644 --- a/autoload/CountJump/Region.vim +++ b/autoload/CountJump/Region.vim @@ -8,6 +8,12 @@ " Maintainer: Ingo Karkat " " REVISION DATE REMARKS +" 1.40.008 20-Dec-2010 Jump functions again return position (and +" actual, corrected one for a:isToEndOfLine). +" Though the position is not used for motions, it +" is necessary for text objects to differentiate +" between "already at the begin/end position" and +" "no such position". " 1.30.007 19-Dec-2010 Shuffling of responsibilities in " CountJump#JumpFunc(): " CountJump#Region#JumpToRegionEnd() and @@ -65,10 +71,14 @@ function! s:DoJump( position, isToEndOfLine ) else call setpos('.', [0] + a:position + [0]) if a:isToEndOfLine - normal! $ + normal! $zv + return getpos('.')[1:2] + else + normal! zv endif - normal! zv endif + + return a:position endfunction function! s:SearchInLineMatching( line, pattern, isMatch ) @@ -178,7 +188,7 @@ function! CountJump#Region#SearchForRegionEnd( count, pattern, isMatch, step ) endfunction function! CountJump#Region#JumpToRegionEnd( count, pattern, isMatch, step, isToEndOfLine ) let l:position = CountJump#Region#SearchForRegionEnd(a:count, a:pattern, a:isMatch, a:step) - call s:DoJump(l:position, a:isToEndOfLine) + return s:DoJump(l:position, a:isToEndOfLine) endfunction function! CountJump#Region#SearchForNextRegion( count, pattern, isMatch, step, isAcrossRegion ) @@ -286,7 +296,7 @@ function! CountJump#Region#SearchForNextRegion( count, pattern, isMatch, step, i endfunction function! CountJump#Region#JumpToNextRegion( count, pattern, isMatch, step, isAcrossRegion, isToEndOfLine ) let l:position = CountJump#Region#SearchForNextRegion(a:count, a:pattern, a:isMatch, a:step, a:isAcrossRegion) - call s:DoJump(l:position, a:isToEndOfLine) + return s:DoJump(l:position, a:isToEndOfLine) endfunction " vim: set sts=4 sw=4 noexpandtab ff=unix fdm=syntax : diff --git a/autoload/CountJump/Region/Motion.vim b/autoload/CountJump/Region/Motion.vim index 652a81f..9156a58 100644 --- a/autoload/CountJump/Region/Motion.vim +++ b/autoload/CountJump/Region/Motion.vim @@ -2,6 +2,7 @@ " lines. " " DEPENDENCIES: +" - CountJump.vim, CountJump/Region.vim autoload scripts. " " Copyright: (C) 2010 Ingo Karkat " The VIM LICENSE applies to this script; see ':help copyright'. diff --git a/autoload/CountJump/Region/TextObject.vim b/autoload/CountJump/Region/TextObject.vim new file mode 100644 index 0000000..8868830 --- /dev/null +++ b/autoload/CountJump/Region/TextObject.vim @@ -0,0 +1,121 @@ +" TextObject.vim: Create custom text objects via jumps over matching lines. +" +" DEPENDENCIES: +" - CountJump/Region.vim, CountJump/TextObjects.vim autoload scripts. +" +" Copyright: (C) 2010 Ingo Karkat +" The VIM LICENSE applies to this script; see ':help copyright'. +" +" Maintainer: Ingo Karkat +" +" REVISION DATE REMARKS +" 1.40.001 20-Dec-2010 file creation + +function! s:EscapeForFunctionName( text ) + " Convert all non-alphabetical characters to their hex value to create a + " valid function name. + return substitute(a:text, '\A', '\=char2nr(submatch(0))', 'g') +endfunction +function! s:function(name) + return function(substitute(a:name, '^s:', matchstr(expand(''), '\d\+_\zefunction$'),'')) +endfunction +function! CountJump#Region#TextObject#Make( mapArgs, textObjectKey, types, selectionMode, pattern, isMatch ) +"******************************************************************************* +"* PURPOSE: +" Define a complete set of mappings for inner and/or outer text objects that +" support an optional [count] and select regions of lines which are defined by +" contiguous lines that (don't) match a:pattern. +" The inner text object comprises all lines of the region itself, while the +" outer text object also includes all adjacent lines above and below which do +" not themselves belong to a region. +" +"* ASSUMPTIONS / PRECONDITIONS: +" None. +" +"* EFFECTS / POSTCONDITIONS: +" Creates mappings for operator-pending and visual mode which act upon / +" select the text delimited by the begin and end patterns. +" If there are no regions, a beep is emitted. +" +"* INPUTS: +" a:mapArgs Arguments to the :map command, like '' for a +" buffer-local mapping. +" a:textObjectKey Mapping key [sequence] after the mandatory i/a which +" start the mapping for the text object. +" a:types String containing 'i' for inner and 'a' for outer text +" objects. +" a:selectionMode Type of selection used between the patterns: +" 'v' for characterwise, 'V' for linewise, '' for +" blockwise. Since regions are defined over full lines, +" this should typically be 'V'. +" a:pattern Regular expression that defines the region, i.e. must (not) +" match in all lines belonging to it. +" a:isMatch Flag whether to search matching (vs. non-matching) lines. +" +"* RETURN VALUES: +" None. +"******************************************************************************* + let l:scope = (a:mapArgs =~# '' ? 'b:' : 's:') + + if a:types !~# '^[ai]\+$' + throw "ASSERT: Type must consist of 'a' and/or 'i', but is: '" . a:types . "'" + endif + + " If only either an inner or outer text object is defined, the generated + " function must include the type, so that it is possible to separately + " define a text object of the other type (via a second invocation of this + " function). If the same region definition is used for both inner and outer + " text objects, no such distinction need to be made. + let l:typePrefix = (strlen(a:types) == 1 ? a:types : '') + + let l:functionToBeginName = printf('%sJumpToBegin_%s%s', l:scope, l:typePrefix, s:EscapeForFunctionName(a:textObjectKey)) + let l:functionToEndName = printf('%sJumpToEnd_%s%s', l:scope, l:typePrefix, s:EscapeForFunctionName(a:textObjectKey)) + + let l:regionFunction = " + \ function! %s( count, isInner )\n + \ %s\n + \ let [l:pattern, l:isMatch, l:step, l:isToEndOfLine] = [%s, %d, %d, %d]\n + \ if a:isInner\n + \ return CountJump#Region#JumpToRegionEnd(a:count, l:pattern, l:isMatch, l:step, l:isToEndOfLine)\n + \ else\n + \ let l:isBackward = (l:step < 0)\n + \ let l:regionEndPosition = CountJump#Region#JumpToRegionEnd(a:count, l:pattern, l:isMatch, l:step, 0)\n + \ if l:regionEndPosition == [0, 0] || l:regionEndPosition[0] == (l:isBackward ? 1 : line('$'))\n + \ return l:regionEndPosition\n + \ endif\n + \ execute 'normal!' (l:isBackward ? 'k' : 'j')\n + \ return CountJump#Region#JumpToRegionEnd(1, l:pattern, ! l:isMatch, l:step, l:isToEndOfLine)\n + \ endif\n + \ endfunction" + + " The function-to-end starts at the beginning of the text object. For the + " outer text object, this would make moving back into the region and then + " beyond it complex. To instead, we use the knowledge that the + " function-to-begin is executed first, and set the original cursor line + " there, then start the function-to-end at that position. Since this may + " also slightly speed up the search for the inner text object, we use it + " unconditionally. + execute printf(l:regionFunction, + \ l:functionToBeginName, + \ 'let s:originalLineNum = line(".")', + \ string(a:pattern), + \ a:isMatch, + \ -1, + \ 0 + \) + execute printf(l:regionFunction, + \ l:functionToEndName, + \ 'execute s:originalLineNum', + \ string(a:pattern), + \ a:isMatch, + \ 1, + \ 1 + \) + + " For regions, the inner text object must include the text object's + " boundaries = lines. + let l:types = substitute(a:types, 'i', 'I', 'g') + return CountJump#TextObject#MakeWithJumpFunctions(a:mapArgs, a:textObjectKey, l:types, a:selectionMode, s:function(l:functionToBeginName), s:function(l:functionToEndName)) +endfunction + +" vim: set sts=4 sw=4 noexpandtab ff=unix fdm=syntax : diff --git a/autoload/CountJump/TextObject.vim b/autoload/CountJump/TextObject.vim index 67e22e9..982f6d5 100644 --- a/autoload/CountJump/TextObject.vim +++ b/autoload/CountJump/TextObject.vim @@ -9,6 +9,7 @@ " Maintainer: Ingo Karkat " " REVISION DATE REMARKS +" 1.40.010 20-Dec-2010 Replaced s:Escape() function with string(). " 1.22.009 06-Aug-2010 No more text objects for select mode; as the " mappings start with a printable character ("a" / " "i"), no select-mode mapping should be defined. @@ -62,10 +63,6 @@ let s:save_cpo = &cpo set cpo&vim -function! s:Escape( argumentText ) - return substitute(a:argumentText, "'", "''", 'g') -endfunction - " Select text delimited by ???. "ix Select [count] text blocks delimited by ??? without the " outer delimiters. @@ -242,8 +239,12 @@ function! CountJump#TextObject#MakeWithJumpFunctions( mapArgs, textObjectKey, ty " the complete lines matching the pattern. " a:JumpToBegin Function which is invoked to jump to the begin of the " block. +" The function is invoked at the cursor position where the +" text object was requested. " a:JumpToEnd Function which is invoked to jump to the end of the " block. +" The function is invoked after the call to a:JumpToBegin, +" with the cursor located at the beginning of the text object. " The jump functions must take two arguments: " JumpToBegin( count, isInner ) " JumpToEnd( count, isInner ) @@ -363,30 +364,26 @@ function! CountJump#TextObject#MakeWithCountSearch( mapArgs, textObjectKey, type let l:searchFunction = " \ function! %s( count, isInner )\n \ if a:isInner\n - \ let l:matchPos = CountJump#CountSearch(a:count, ['%s', '%s'])\n + \ let l:matchPos = CountJump#CountSearch(a:count, [%s, %s])\n \ if l:matchPos != [0, 0]\n - \ call CountJump#CountSearch(1, ['%s', '%s'])\n + \ call CountJump#CountSearch(1, [%s, %s])\n \ endif\n \ return l:matchPos\n \ else\n - \ return CountJump#CountSearch(a:count, ['%s', '%s'])\n + \ return CountJump#CountSearch(a:count, [%s, %s])\n \ endif\n \ endfunction" - "execute printf("function! %s( count, isInner )\nreturn CountJump#CountSearch(a:count, ['%s', 'bcW' . (a:isInner ? 'e' : '')])\nendfunction", l:functionToBeginName, s:Escape(a:patternToBegin)) - "execute printf("function! %s( count, isInner )\nif a:isInner\nreturn (CountJump#CountSearch(a:count, ['%s', 'bcW']) ? CountJump#CountSearch(1, ['%s', 'ceW']) : 0)\nelse\nreturn CountJump#CountSearch(a:count, ['%s', 'bcW'])\nendif\nendfunction", l:functionToBeginName, s:Escape(a:patternToBegin), s:Escape(a:patternToBegin), s:Escape(a:patternToBegin)) execute printf(l:searchFunction, \ l:functionToBeginName, - \ s:Escape(a:patternToBegin), 'bcW', - \ s:Escape(a:patternToBegin), 'ceW', - \ s:Escape(a:patternToBegin), 'bcW' + \ string(a:patternToBegin), string('bcW'), + \ string(a:patternToBegin), string('ceW'), + \ string(a:patternToBegin), string('bcW') \) - "execute printf("function! %s( count, isInner )\nreturn CountJump#CountSearch(a:count, ['%s', 'cW' . (a:isInner ? '' : 'e')])\nendfunction", l:functionToEndName, s:Escape(a:patternToEnd)) - "execute printf("function! %s( count, isInner )\nif a:isInner\nreturn (CountJump#CountSearch(a:count, ['%s', 'ceW']) ? CountJump#CountSearch(1, ['%s', 'bcW']) : 0)\nelse\nreturn CountJump#CountSearch(a:count, ['%s', 'ceW'])\nendif\nendfunction", l:functionToEndName, s:Escape(a:patternToEnd), s:Escape(a:patternToEnd), s:Escape(a:patternToEnd)) execute printf(l:searchFunction, \ l:functionToEndName, - \ s:Escape(a:patternToEnd), 'ceW', - \ s:Escape(a:patternToEnd), 'bcW', - \ s:Escape(a:patternToEnd), 'eW' + \ string(a:patternToEnd), string('ceW'), + \ string(a:patternToEnd), string('bcW'), + \ string(a:patternToEnd), string('eW') \) " Note: For the outer jump to end, a:patternToEnd must not match at the " current cursor position (no 'c' flag to search()). This allows to handle diff --git a/doc/CountJump.txt b/doc/CountJump.txt index 1cb82e8..54254d1 100644 --- a/doc/CountJump.txt +++ b/doc/CountJump.txt @@ -112,6 +112,15 @@ This function sets up mappings starting with [ and ] for movement (with optional [count]) relative to the current cursor position, targeting a text region defined by contiguous lines that (don't) match a:pattern. +CountJump#Region#TextObject#Make( mapArgs, textObjectKey, types, selectionMode, pattern, isMatch ) + +Defines a complete set of mappings for inner and/or outer text objects that +support an optional [count] and select regions of lines which are defined by +contiguous lines that (don't) match a:pattern. +The inner text object comprises all lines of the region itself, while the +outer text object also includes all adjacent lines above and below which do +not themselves belong to a region. + ============================================================================== EXAMPLE *CountJump-example* @@ -185,6 +194,14 @@ IDEAS *CountJump-ideas* ============================================================================== HISTORY *CountJump-history* +1.40 20-Dec-2010 +- ENH: Added CountJump#Region#TextObject#Make() to easily define text objects + for regions. +- Interface change: Jump functions again return position (and actual, + corrected one for a:isToEndOfLine). Though the position is not used for + motions, it is necessary for text objects to differentiate between "already + at the begin/end position" and "no such position". + 1.30 20-Dec-2010 - ENH: Added CountJump#Region#Motion#MakeBracketMotion() to easily define bracket motions for regions.