omap with 'f' and 't' and getchar() and nr2char()

248 views
Skip to first unread message

dfishb...@gmail.com

unread,
Feb 11, 2008, 11:29:41 PM2/11/08
to vim_use

:ver
VIM - Vi IMproved 7.1 (2007 May 12, compiled Feb 6 2008 14:53:08)
MS-Windows 32 bit GUI version with OLE support
Included patches: 1-243

I have run into an off by 1 error that I was hoping someone could
explain.

Start Vim using:
gvim --noplugin -U none -U NONE omap_cf.vim

Save the following into this file.
"********
function! MyOp(op)
let c = getchar()
echoerr "Executing ".a:op.nr2char(c)
exec "normal! ".a:op.nr2char(c)
endfunction

onoremap f :call MyOp('f')<CR>

" Now run:
"********

If you place your cursor at the beginning of the Now run line run the
following command:
cfw

This changes up to and including the w, so you end up with:
" Now run:
run:

<Esc> and undo the change.
Source the file :so %
Try the same command (cfw).
This time you end up with:
" Now run:
w run:

Notice the "w" was not included in the change.
If you did this using 't', same thing happens, you are one character
back.

This function works fine if you do not use the getchar() function to
ask the user what character they want to change up to.

Comments?

TIA,
Dave

Ben Schmidt

unread,
Feb 12, 2008, 3:01:50 AM2/12/08
to vim...@googlegroups.com

This is expected behaviour.

Because :call is part of an Ex command, it behaves as described at

:help {motion}

(the fifth of the 'dot points'), i.e. it is always characterwise exclusive. A way
to solve it is to use an <expr> map to avoid an Ex command altogether. Idea
implementation:

function! MyOp(op)
let c = getchar()
echoerr "Executing ".a:op.nr2char(c)

return a:op.nr2char(c)
endfunction

onoremap <expr> f MyOp('f')

Ben.


Send instant messages to your online friends http://au.messenger.yahoo.com

Charles E Campbell Jr

unread,
Feb 12, 2008, 12:54:16 PM2/12/08
to vim...@googlegroups.com
dfishb...@gmail.com wrote:

I'm probably doing something incorrectly. Instead of off-by-one or
whatever, I get:

gvim --noplugin -U none -U NONE omap_cf.vim

/Now run/
cf

:call MyOp('f')<CR>[] <---- this shows up on the display where [] marks
the cursor position

now type that w...
:call MyOp('f')<CR>w[]

Beyond the directions: I then typed a <cr>. I got a half-height cursor
at the : on the command line, but nothing was otherwise happening.
I then pressed yet another <cr> on in:

Error detected while processing function MyOp:
line 2:
Executing f^M
E488: Trailing characters
Press ENTER or type command to continue

(I have a huge version of vim, 7.1.245)

Regards,
Chip Campbell


I'm using a huge version

Andy Wokula

unread,
Feb 12, 2008, 9:55:42 AM2/12/08
to vim...@googlegroups.com, dfishb...@gmail.com
Ben Schmidt schrieb:
> to solve it is to use an <expr> map [...]

Another way:
:h o_v

:onoremap f v:call MyOp('f')<CR>

The only change is inserting a "v" in the mapping.

--
Andy

David Fishburn

unread,
Feb 15, 2008, 11:04:01 PM2/15/08
to vim...@googlegroups.com

Thanks Ben and Andy.

I keep getting closer and closer, I can almost taste it.

Using v: has got me almost where I need to be, but yet again I find a
minor discrepancy.

I have the following text:
foo(1,2)

Placing the cursor on foo and hitting cw changes up to and including
the "(". It should only include "foo".

Looking at :h o_v it says:
*o_v*
v When used after an operator, before the motion command: Force
the operator to work characterwise, also when the motion is
linewise. If the motion was linewise, it will become
|exclusive|.
If the motion already was characterwise, toggle
inclusive/exclusive. This can be used to make an exclusive
motion inclusive and an inclusive motion exclusive.

:h w
<S-Right> or *<S-Right>* *w*
w [count] words forward. |exclusive| motion.

:h e
*e*
e Forward to the end of word [count] |inclusive|.

So, using v:, can change an exclusive motion into an inclusive one.
So if anything that should result in basically "cw" becoming the same
as "ce" (if I interpret this correctly).

ce works as expected and only changes "foo".

Here is the script you can execute to demonstrate the issue. Just
source this script.
*****
set nocompatible

function! MyOp(op)
let op_motion = a:op
" Check if we are in operator-pending mode
if op_motion =~? '\(f\|t\)'
" If the operator pending mode is f or t
" request the final character from the user
let c = getchar()
let op_motion = op_motion . nr2char(c)
endif

let v_count = ((v:count > 0)?(v:count):'')
echoerr "Executing ".v_count.op_motion
exec "normal! ".v_count.op_motion
" return a:op.nr2char(c)
endfunction

onoremap f v:call MyOp('f')<CR>

onoremap t v:call MyOp('t')<CR>
onoremap w v:call MyOp('w')<CR>
onoremap e v:call MyOp('e')<CR>

" Now run:
" foo(1,2)
*****

Comments?

Thanks,
Dave

Andy Wokula

unread,
Feb 16, 2008, 10:32:09 AM2/16/08
to vim...@googlegroups.com
David Fishburn schrieb:

For "t" and "w" the "v" is to be omitted.

> onoremap e v:call MyOp('e')<CR>
>
> " Now run:
> " foo(1,2)
> *****
>
> Comments?
>
> Thanks,
> Dave

The builtin "w" motion is exclusive. If you omap "w" to an Ex command
you'll get an exclusive motion, per default. That's just what you want;
prepending "v" to the Ex command makes your "w" motion inclusive (not
wanted).

"cw" is a confusing example, because it is an exception (works like
"ce").
:h cw
Indeed, "e" is inclusive, but it stops earlier than an inclusive "w"
would do:
"foo bar" -- ce --> "| bar"
"foo bar" -- cw --> "|ar"

v:operator can help to detect the "c" operator. Maybe you could try
this mapping:
:ono <expr> w v:operator=="c" ? "v:call MyOp('e')<cr>"
\ : ":call MyOp('w')<cr>"

What are you going to do?
Is it still for the yankring plugin, where you need the omap-motion to
yank what it moves over (AIUI), as a side effect? In this case you
cannot use :map-<expr> (see Ben's post) -- although I'd also prefer it,
because it is more reliable. Remapping all the motions your way is
difficult because of the subtle exceptions -- there is not only "cw".

Minor:
- I'd rename "MyOp" into "MyOmap" or "MyMotion" ...

--
Andy

Andy Wokula

unread,
Feb 17, 2008, 5:49:17 AM2/17/08
to vim...@googlegroups.com
Andy Wokula schrieb:

> Minor:
> - I'd rename "MyOp" into "MyOmap" or "MyMotion" ...

Ah, wrong, it was a little different. You want to cancel
Omap-mode and then execute v:operator + motion argument in
MyOp(). I just looked up
:h v:operator
Some day I'll get it ;-)

--
Andy

dfishb...@gmail.com

unread,
Apr 9, 2008, 5:06:39 PM4/9/08
to vim_use
...

> "cw" is a confusing example, because it is an exception (works like
> "ce").
>     :h cw
> Indeed, "e" is inclusive, but it stops earlier than an inclusive "w"
> would do:
>     "foo bar"     -- ce -->      "| bar"
>     "foo bar"     -- cw -->      "|ar"
>
> v:operator can help to detect the "c" operator.  Maybe you could try
> this mapping:
>     :ono <expr> w  v:operator=="c" ? "v:call MyOp('e')<cr>"
>                                  \ : ":call MyOp('w')<cr>"
>
> What are you going to do?
> Is it still for the yankring plugin, where you need theomap-motion to
> yank what it moves over (AIUI), as a side effect?  In this case you
> cannot use :map-<expr> (see Ben's post) -- although I'd also prefer it,
> because it is more reliable.  Remapping all the motions your way is
> difficult because of the subtle exceptions -- there is not only "cw".

Thanks for the response Andy.

I tried to use <expr> maps, but had to bail on them.

So I have tried a few techniques at this point:
1. omap - simply execute "normal! motion" that the user typed
2. omap <ESC>... redo the whole operation using v:operator and the
motion passed in
3. omap <expr> ... return exactly what the user typed

Each has its own flaws.

The expr seemed to generate the most consistent results (re inclusive
vs exclusive) and I would use it if I could only solve this problem.
Any suggestions are very welcome.

The problem
-----------------
The goal, after the operation completes, I want to record the register
changes.
So, dw, I want to record the word just deleted.

Since the registers do not change until the function completes, the
function itself cannot record the change. I worked around this in 1.
by using feedkeys() to call my function to record the change. This
function gets called after the omap completes. This does not work for
change operators (cw), so I record that using an autocmd, InsertLeave.

It seems when using <expr> feedkeys() does not work the same way. So
really, I have no way to telling my plugin to record the register
changes once the function completes. I thought about using CursorHold
to do it, but a user could hit:
dw
dw
dw
dw
Very quickly and I am doubt that would record them all.

Any possibilities?

Thanks,
Dave

Andy Wokula

unread,
Apr 10, 2008, 3:27:39 AM4/10/08
to vim...@googlegroups.com
dfishb...@gmail.com schrieb:

I just got a simple idea. Works even for "cw".

nn <script> yy yy<sid>cr
ono <script> w w<sid>cr

" copy register
nn <silent> <sid>cr :call <sid>cr()<cr>
ino <script> <sid>cr <c-o><sid>cr

func! <sid>cr()
let reg = getreg(v:register)
echo reg
endfunc

--
Andy

Andy Wokula

unread,
Apr 10, 2008, 7:43:04 AM4/10/08
to vim...@googlegroups.com
Andy Wokula schrieb:

> nn <script> yy yy<sid>cr
> ono <script> w w<sid>cr
>
> " copy register
> nn <silent> <sid>cr :call <sid>cr()<cr>

> ino <script> <sid>cr <c-o><sid>cr

Hmm, this breaks the redo command "." after "cw", therefore:

:ino <silent> <sid>cr <c-r>=<sid>cr()<cr>

> func! <sid>cr()
> let reg = getreg(v:register)
> echo reg

return ""
> endfunc

--
Andy

dfishb...@gmail.com

unread,
Apr 11, 2008, 9:30:04 AM4/11/08
to vim_use


On Apr 10, 7:43 am, Andy Wokula <anw...@yahoo.de> wrote:
> Andy Wokula schrieb:
>
> > nn <script> yy yy<sid>cr
> > ono <script> w w<sid>cr
>
> > " copy register
> > nn <silent> <sid>cr :call <sid>cr()<cr>
> > ino <script> <sid>cr <c-o><sid>cr
>
> Hmm, this breaks the redo command "." after "cw", therefore:
>
> :ino <silent> <sid>cr <c-r>=<sid>cr()<cr>

Hot Diggity Dog!

Andy, I had run into the exact "redo" issue after you last post and
was attempting to come up with a work around. I doubt I would have
tried this and it works perfectly.

Using . mapped the same way will also record future redos.

I have all motions and all text objects working as well.

I have one remaining issue, that I was hoping you might have an idea
for.

Mapping f and t, these require and additional character specified. I
was able to do this:
omap f :<C-U>call RepeatMotion('f')<CR>

This function basically did this:

function! RepeatMotion(op_motion) range
if a:op_motion =~? '\(f\|t\)'
let op_motion = op_motion . s:YRGetChar()
endif

let cmd = 'normal! '.a:op_motion
exec cmd
endfunction

This works, it prompts the user for an additional character.

On a redo, this function is called again, but unfortunately, it
prompts the user again. So I can't get a seemless redo.

Based on your previous posts I have tried a few options listed here:
************
set nocompatible

nn <script> yy yy<sid>cr
ono <script> w w<sid>cr
ono <script> t t<sid>motion
ono <script> t t<sid>cr
" ono <script> <expr> t <sid>motion
" ono <script> <expr> t <sid>motion<sid>cr
" ono <script> <expr> t Motion()
" ono <script> <expr> t Motion()<bar><sid>cr
" ono <script> <expr> t Motion()<bar>CR()
" ono <script> <expr> t Motion()<bar>CR()<CR>

" copy register
nn <silent> <sid>cr :call <sid>cr()<cr>
" This breaks the redo feature
" ino <script> <sid>cr <c-o><sid>cr
ino <script> <sid>cr <c-r>=<sid>cr()<CR>
nn <silent> <sid>motion :call <sid>motion()<cr>
" This breaks the redo feature
" ino <script> <sid>motion <c-o><sid>motion
ino <script> <sid>motion <c-r>=<sid>motion()<CR>

func! <sid>cr()
let reg = getreg(v:register)
echo "reg:".v:register.':'.reg
return "t"
endfunc

func! CR()
let reg = getreg(v:register)
echo "reg:".v:register.':'.reg
return ""
endfunc

func! <sid>motion()
return 't'
endfunc

func! Motion()
" doautocmd User
return 'tt'
endfunc

augroup YankRingTest
autocmd User * :echo "YRT:".@"
augroup END

" test is a
" test line
" test testing
************

I was hoping I could use another <c-r> function call to return the
character, I was also tried additional <expr> maps, but had the usual
issue is no additonal functions are called after the first function.

Do you have any other suggestions that I might try out?

Thanks for all your help so far.
Dave

Andy Wokula

unread,
Apr 11, 2008, 10:24:39 AM4/11/08
to vim...@googlegroups.com
dfishb...@gmail.com schrieb:

" Current try (works well for me :-) :

ono <expr><script> f <sid>getzapchar("f"). "<sid>cr"
ono <expr><script> F <sid>getzapchar("F"). "<sid>cr"

func! <sid>getzapchar(fcmd)
return a:fcmd. s:Getchar()
endfunc

func! s:Getchar()
let c = getchar()
if c != 0
let c = nr2char(c)
endif
return c
endfunc


Looks like "." doesn't repeat the expr-eval, only the result.

--
Andy

David Fishburn

unread,
May 28, 2008, 4:07:46 PM5/28/08
to vim...@googlegroups.com
> " Current try (works well for me :-) :
>
> ono <expr><script> f <sid>getzapchar("f"). "<sid>cr"
> ono <expr><script> F <sid>getzapchar("F"). "<sid>cr"
>
> func! <sid>getzapchar(fcmd)
> return a:fcmd. s:Getchar()
> endfunc
>
> func! s:Getchar()
> let c = getchar()
> if c != 0
> let c = nr2char(c)
> endif
> return c
> endfunc
>
>
> Looks like "." doesn't repeat the expr-eval, only the result.

Thanks for the response Andy.

I tried this out when you originally posted it (sometime ago) and have
tried a few work arounds.

> " Current try (works well for me :-) :

Yes, what you provided works but not in the original case.

Your map:


> ono <expr><script> F <sid>getzapchar("F"). "<sid>cr"

Successfully calls getzapchar(), but once getzapchar() completes, it
does not call "<sid>cr" which is the reason I cannot use <expr> maps.

Not sure why this limitation exists, but everything else is working
great except the f and t maps.

Unforutnately, I use this religiously and have to have them working
and repeatable before I release the next version of the YankRing.

Any other possible suggestions?

TIA,
Dave

John Little

unread,
May 30, 2008, 1:21:24 AM5/30/08
to vim_use
Like Dr Chip, I didn't know where you guys are at, but I'm getting
there.

> gvim --noplugin -U none -U NONE omap_cf.vim

AFAICT, "-U none" is being overridden by "-U NONE". If vim running
without any customization is intended, "-u NONE" is needed. However,
the use of <CR> in the script implies :set nocp, and you get cp with "-
u NONE". I think that explains Dr Chip's first result, "<CR>" shown
on the command line.

My next mistake was to cut and paste the example from firefox (or
Konqueror). I get an extra space on the end of the omap line. I think
Dr Chip got this too.

Regards, John

Tony Mechelynck

unread,
May 30, 2008, 11:09:26 AM5/30/08
to vim...@googlegroups.com

For no vimrc, no gvimrc, no plugins, but 'nocompatible' mode, use:

vim -N -u NONE

for the same, but with global plugins, use

vim -N -u NORC

This, however, will also load any additional global plugins in
$HOME/.vim/plugin (Unix), $HOME/vimfiles/plugin (Windows),
$VIM/vimfiles/plugin (all). To avoid these, you may want to temporarily
rename their .vim and vimfiles parents to something where Vim won't look
into.

To avoid sourcing your own vimrc and gvimrc, and still enable (among
others) syntax highlighting in a way that Bram can reproduce, you can use:
(Windows)
gvim -u C:\PROGRA~1\vim\vim71\vimrc_example.vim -U NONE
(Unix)
gvim -u /usr/local/share/vim/vim71/vimrc_example.vim -U NONE

assuming default install paths in both cases, and 8.3 dirnames on
Windows to avoid the problems sometimes caused by spaces in them. "-N"
can be omitted in this case because the vimrc_example.vim sets
'nocompatible' as it starts.

The same remarks about own-installed plugins applies; see under -u NORC
above.


Best regards,
Tony.
--
There were the Scots
Who kept the Sabbath
And everything else they could lay their hands on.
Then there were the Welsh
Who prayed on their knees and their neighbors.
Thirdly there were the Irish
Who never knew what they wanted
But were willing to fight for it anyway.
Lastly there were the English
Who considered themselves a self-made nation
Thus relieving the Almighty of a dreadful responsibility.

David Fishburn

unread,
May 31, 2008, 10:07:07 PM5/31/08
to vim_use
Sorry, this went to Andy only, sorry.

---------- Forwarded message ----------

Okay, here is a recap.

Goal:
For every operation in Vim that effects a register, record the changed
value in the YankRing plugin for later use.

The current version of the YankRing (3.0) does this but not for all
motions and text objects. I also does not handle any changes (like
cw).

The new version handles everything (including all text objects :h
text-objects), changes, clipboard.
It has been accomplished using this technique for the maps:
nnoremap <script> w w<SID>YRRecord2

Since the above is an "omap", if I issue a dw, cw, yw it triggers the
map. Thanks to Andy, it executes the 'w' to complete the motion, then
calls a function in the YankRing plugin to record the changed
register. This technique also supports "." (repeat). Hot diggidy
dog!

This works for all motions and text-objects _except_ 't' and 'f'.

t and f are special cases since we must ask the user for the character
to perform the motion up to.
If you have this word in your buffer: my_word_with_underscores
df_ will delete up to and including the first _, resulting in:
word_with_underscores
dt_ will delete up to the first _, resulting in: _word_with_underscores

Handling these 2 special cases has been troublesome.

Andy and Ben suggested using a <expr> map.
This works great. My function knows it is a 't' or 'f' map and
prompts for the following character. And it is executed.

The problem with the above is for some reason when an <expr> map is
used, any command following the function call is not called by Vim.
So with the previous technique:
nnoremap <script> w w<SID>YRRecord2

We execute w, the omap simply returns 'w', telling Vim to do the usual
operation and once it finishes, it calls the YRRecord2 function.

The same approach with the <expr> map, execute the f motion but will
NOT call YRRecord2.

The YRRecord2 function must be called _after_ the map (or function
completes) since the registers are _not_ updated until the function
completes. Hence I cannot record any changed registers.


Ben had another suggested approach. Instead of using an <expr> map,
use an omap, but cancel the omap first and then execute the command in
your own function. Something like this:
onoremap w <Esc>:<C-U>call TestOMap()<CR>

TestOMap() will execute the v:operator.w and attempt to record the changes made.
This fails for a number of reasons:
- The registers are not updated until the function completes
- Cancelling the omap with <Esc> and re-executing it runs into
inclusive vs exclusive problems (which <expr> maps avoid entirely)

I attempted to solve this by using feedkeys(). So in the TestOMap
function, after executing the command I did this:
call feedkeys(":silent! :call YRRecord('".user_register."')\<CR>", 't')

So, when the function completed, I "fed" a call to my function. This
only partially worked.

Some of the test cases when starting Vim using:
gvim -u NONE -U NONE --noplugin omap_cf.vim
***** omap_cf.vim *****
set nocompatible
set hidden

function! MyOp(op)
let op_motion = a:op
" Check if we are in operator-pending mode
if op_motion =~? '\(f\|t\)'
" If the operator pending mode is f or t
" request the final character from the user
let c = getchar()
let op_motion = op_motion . nr2char(c)
endif

let v_count = ((v:count > 0)?(v:count):'')

" echoerr "Executing ".v:count.op_motion
exec "normal! ".v:count.op_motion
" return a:op.nr2char(c)
endfunction

function! MyDisplay()
echo 'reg:'.@"
let @a = @"
endfunction

function! MyExprOp(op)
let c = getchar()
return a:op.nr2char(c)
endfunction

onoremap f v:call MyOp('f')<CR>
onoremap t v:call MyOp('t')<CR>

onoremap w :call MyOp('w')<CR>
" onoremap e v:call MyOp('e')<CR>
" onoremap iw v:call MyOp('iw')<CR>

onoremap f v:call MyOp('f')<CR>

onoremap t v:call MyOp('t')<bar>:call MyDisplay()<CR>
onoremap w :call MyOp('w')<CR>

onoremap f v:call MyOp('f')<CR>

onoremap t v:call MyOp('t')<bar>:call MyDisplay()<CR>
onoremap <expr> f MyExprOp('f') |:call MyDisplay()<CR>
onoremap <expr> f MyExprOp('f')<bar>:call MyDisplay()<CR>


onoremap w :call MyOp('w')<CR>
" Now run:
" foo(1,2)

**********


I am happy to use expression maps if I can somehow get a function to
be called after them using the technique (or otherwise) above:
onoremap <expr> f MyExprOp('f')<bar>:call MyDisplay()<CR>

Just wondering if there is anyway how this might be accomplished.

TIA,
Dave

Ben Schmidt

unread,
Jun 1, 2008, 2:56:02 AM6/1/08
to vim...@googlegroups.com
> I am happy to use expression maps if I can somehow get a function to
> be called after them using the technique (or otherwise) above:
> onoremap <expr> f MyExprOp('f')<bar>:call MyDisplay()<CR>
>
> Just wondering if there is anyway how this might be accomplished.

The ":call MyDisplay()<CR>" should either be part of the expression that MyExprOp
returns, or appended to it with the "." operator. So far you've tried including it
as for a regular map, not an <expr> map. Try (roughly)

func! MyExprOp(arg)
let char=getchar()
return "f".char.":call MyDisplay()\<CR>"
endfunc

or

onoremap <expr> f MyExprOp('f').":call MyDisplay()\<CR>"

and let me know how you go...

Ben.


David Fishburn

unread,
Jun 2, 2008, 10:05:58 AM6/2/08
to vim...@googlegroups.com
...

> The ":call MyDisplay()<CR>" should either be part of the expression that MyExprOp
> returns, or appended to it with the "." operator. So far you've tried including it
> as for a regular map, not an <expr> map. Try (roughly)
>
> func! MyExprOp(arg)
> let char=getchar()
> return "f".char.":call MyDisplay()\<CR>"
> endfunc
>
> or
>
> onoremap <expr> f MyExprOp('f').":call MyDisplay()\<CR>"

YES!

That works like a charm and it makes sense too!

Reply all
Reply to author
Forward
0 new messages