Regarding this command:

f{char}                 To [count]'th occurrence of {char} to the right.  The
                        cursor is placed on {char} |inclusive|.
                        {char} can be entered as a digraph |digraph-arg|.
                        When 'encoding' is set to Unicode, composing
                        characters may be used, see |utf-8-char-arg|.
                        |:lmap| mappings apply to {char}.  The CTRL-^ command
                        in Insert mode can be used to switch this on/off

I would like to change its behaviour as such. Take this sample string, in which ^ indicates the cursor position:

foobar(); // boat programming SO meme

The keystroke: f; would leave the cursor in place, because there is no ; right of the cursor. I would rather have it function that it would search left instead:

foobar(); // boat programming SO meme

Make sense? The reverse should apply, as the t; sequence.

  • 1
    Are you aware of F and T which are the opposite of f and t (:h F and :h T)? They will not work automatically but that you didn't mentioned them in your question I thought they could be useful :) – statox Feb 26 '18 at 17:13
  • 1
    @statox I am aware. Just saving myself some keystrokes. – Akiva Feb 26 '18 at 17:14
  • Also, the comma to reverse direction. – D. Ben Knoble Feb 27 '18 at 12:20

Here is another approach:

nnoremap <silent><expr> \f MySearch('f')
nnoremap <silent><expr> \t MySearch('t')

func! MySearch(cmd)
    let arg=getchar()
    if arg !~ '^\d\+'
        " no real key, mouse click  or something unusual, so abort
        return a:cmd."\<esc>"
        let arg=nr2char(arg)
    let pat='\%'.line('.').'l\V'.escape(arg,'\\')
    if search(pat,'nWz')
        return a:cmd.arg
        return nr2char(char2nr(a:cmd)-32).arg

This uses an expression mapping and searches from the current cursor position if after it the requested character is found. If yes, the original command with the entered character is returned, else If dynamically calculates the uppercase command of the given command and returns that.

| improve this answer | |
  • I am choosing this one instead as it works better with fakevim (in that it does not break f like the other solution does for me.) – Akiva Feb 26 '18 at 20:01
  • what is fakevim? – Christian Brabandt Feb 26 '18 at 20:15
  • github.com/hluk/FakeVim – Its used in QtCreator; the ide I do 99% of my coding in. – Akiva Feb 26 '18 at 20:36
  • 1
    This is an elegant solution! Also @Akiva how does my answer break f for you? – statox Feb 27 '18 at 8:13
  • 1
    you are not allowed to move the cursor in "expression" mappings. Rather let the expression return where the cursor should move to. – Christian Brabandt Feb 27 '18 at 16:38

Before the actual answer, a personnal opinion:

I wrote the code to show that it can work but I don't think it is a good idea to use this code: having f and t going backward and F and T going forward might be pretty disturbing.

I think it is better to use , and ;: if f; doesn't find a character then simply use , to replay the motion in the other direction: it is one more keystroke but a much more predictable behavior.

Now for the answer, you can try something like this:

nnoremap <silent> f :call MyPreciseMotions('f', 'F')<CR>
nnoremap <silent> F :call MyPreciseMotions('F', 'f')<CR>
nnoremap <silent> t :call MyPreciseMotions('t', 'T')<CR>
nnoremap <silent> T :call MyPreciseMotions('T', 't')<CR>
xnoremap <silent> f :call MyPreciseMotions('f', 'F')<CR>
xnoremap <silent> F :call MyPreciseMotions('F', 'f')<CR>
xnoremap <silent> t :call MyPreciseMotions('t', 'T')<CR>
xnoremap <silent> T :call MyPreciseMotions('T', 't')<CR>

function! MyPreciseMotions(firstCmd, secondCmd)
    " Get a character from the user
    let charToReach = nr2char(getchar())

    " Save the cursor position
    let cursor_save = getpos('.')

    " Try to reach the character
    execute "norm! " . a:firstCmd . charToReach

    " If we didn't move try the other direction
    if (getpos('.')[2] == cursor_save[2])
        execute "norm! " . a:secondCmd . charToReach

You create four mappings for your four motions.

The function takes as arguments the first motion to try then the second one. It uses getchar() to get the character to reach and then try to reach it, using the first then the second command.

Note that it seems to be working properly but , and ; might not work as you expect anymore.

Some interesting help topics related to the code:

| improve this answer | |
  • interesting suggestions. I ultimately mapped ; to : so it doesnt work out for me. This function works fine though. – Akiva Feb 26 '18 at 17:41
  • 2
    You can still map : to ; :) but it's cool if it works for you! – statox Feb 26 '18 at 17:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.