7

I want to have the command which is stated in the title. I looked for it in intrinsic Vim command but did not find it. I ran some tutorial online about functions in vim, The idea would be to have something like that in my .vimrc:

map <command> <argument>: call nextWordWithLetter(argument)

function nextWordWithLetter(letter)
    " for word i in line
    " if word i's first letter = letter
        " col = column of word i
        " break
    col | "The command to jump to column number 'col'
end function
11

You can use getchar() and search() to accomplish your goal.

nnoremap <key> :call search('\<' . nr2char(getchar()), 'W', line('.'))<cr>

The idea is we use nr2char(getchar()) to wait for a character to be typed and use search() to search for the next instance of the character at the beginning of a word via \<. We limit this to the current line by supplying the current line, line('.'), as the third parameter to search().

Personally I think I would rather just use / or f, however there are other plugins that provide similar functionality:

For more help see:

:h search(
:h getchar(
:h /\<
7

Edit : Peter Rincker's answer is shorter, easier to explain, and can be repeated as many times as you want. My solution is too long, and can't be repeated for several words. I should delete the answer but maybe someone will be interested in it, so I let it here.


I'm not sure this is what you want, but try the following code :

function! NextWordWithLetter(type)

    normal! `]vy

    let letter = @0
    let line = getline('.')
    let list = split(line)

    for word in list
        if matchstr(word, '\%1c.') ==# letter
            let col = match(line, word) + 1
            execute "normal! " . col . "|"
            break
        endif
    endfor

endfunction

nnoremap <silent> <leader>g :set operatorfunc=NextWordWithLetter<cr>g@

The mapping to call the function is <leader>g<motion>.

If you want to move your cursor to the next word beginning with the letter p, and , is your leader key, then you could type ,gfp.


Goal

You want to go to the next word beginning with a specific letter. I don't know if vim already has such a motion built-in, but let's assume it doesn't.

We need to define a new motion (like w, b, e etc.). Usually, to define a new motion or object, you use the onoremap command. It's explained in detail here.

I don't know how to achieve your goal this way, but I know how to define a new operator that indirectly will do what you want.
I say indirectly because the purpose of a new operator, usually, is not to move somewhere, but to do something on a piece of text.

Vim explains how to create a new operator in the help : :h map-operator. It's also explained here, here and here.

The syntax may seem weird to you, but here's how it seems to work in vimscript :

g@ is a special operator (like c to change, d to delete, y to copy etc.) that allows you to build your own operator.

If you type g@{motion} vim will look for the opfunc option whose value is supposed to be the name of a function, set the marks `[ and `] around the text you would have moved over if you had just typed {motion} (more on this later) and execute the function which can do anything you want on this piece of text.

You can create different operators by writing different functions and setting the opfunc option to the name of the function you want to use.
But before delving into what our function does, let's take a look at the mapping we need to trigger it.


Mapping

At the end of the piece of code, there's this line :

nnoremap <silent> <leader>g :set operatorfunc=NextWordWithLetter<cr>g@

It tells vim that whenever you hit <leader>g, it must silently (<silent>) set the value of the operatorfunc option to NextWordWithLetter and then hit in normal mode g@.

The operator g@ waits for a motion to set the marks `[ and `] around the text you would have moved over with {motion} before executing the function stored in opfunc. Here the function is NextWordWithLetter().

You could do the same by typing in command mode :

:set operatorfunc=NextWordWithLetter

Then in normal mode : g@
Then always in normal mode : {motion}

But of course it would be tedious, so we create the mapping <leader>g that automatically sets the operatorfunc option to the desired function name and hit g@ for us.

Let's assume your leader key is , and you are looking for the next word beginning with the letter p. Using our mapping, you could type ,gfp.
As a result vim will set the marks `[ and `] around the text between your cursor and the next character p, set opfunc to NextWordWithLetter, and because of the value of this option it will execute the function NextWordWithLetter().

Next, let's take a look at what the function does.


Setting variables

First we need to tell the function which character we're looking for :

normal! `]vy

The normal command types any sequence of characters you pass to it as if you were in normal mode. Here the sequence of characters is `]vy.

When you're in a buffer you can set a mark anywhere with the normal command m{letter}.

If you wanted to put the mark a where your cursor is, you would type ma. Then, you could move around and go back to the same line as the mark a with the command 'a or to the exact character where you set the mark a with the command `a.
You can see all your marks with the command :marks. The uppercase marks are global and can be used to jump across buffers, the lowercase ones are local to a buffer.

There are two special marks that vim automatically sets for us : `[ and `]. Whenever you change or yank some text, vim puts those marks around it (:h '[).

Let's get back to our example : you wanted to go to the next word beginning with the letter p, so you hit ,gfp. fp is a command that moves the cursor to the next character p.

At this point, vim is going to automatically set the marks `[ and `] around the text that we would have moved over if we had just typed {motion} (fp in our example).
It's written in the help (:h g@):

g@{motion} Call the function set by the 'operatorfunc' option. The '[ mark is positioned at the start of the text moved over by {motion}, the '] mark on the last character of the text.

We haven't changed or yanked anything yet, so why does vim already put those marks ? Because it thinks that the function wants to do something on the text, and the function will need those marks to identify the text to operate on.

So when the normal command types `]vy, it can be read as :
go to the mark `] (last character of the text moved over), go into visual mode (v) and copy the visual selection (y).

So now we've copied your searched character (character p). Everything you copy is in a register. There are different registers in vim, each of them being used for a different purpose . To take a look at them, type :reg, and to know which register stores what :h registers.

The last text you copied is stored in the register 0. You can access it with @0 : echo @0.

Your searched character p is in this register. We assign it to the variable letter :

let letter = @0

Next we assign the current line to the variable line :

let line = getline('.')

getline() is a built-in function which returns the line whose number has been passed as an argument, and '.' is expanded as the number of the current line.

Next we assign to the variable list a list containing each word of the line :

let list = split(line)

split() is a built-in function that splits a string whenever it finds a whitespace (space, tab character...). You can tell it to split at other locations by giving a second argument. For example, if you wanted to split whenever there's a colon character, you would use split(line, ':').


For loop

The for loop iterates over the variable list, and stores each of its item inside the variable word :

for word in list
...
endfor

Then, it checks whether the first letter of word is the letter you're looking for :

if matchstr(word, '\%1c.') ==# letter
...
endif

matchstr() is a built-in function which returns a pattern if it's found inside a string.

Here we're looking for the pattern '\%1c.' inside word. In a vim regex, \%1c is a zero-width atom. It doesn't add anything to the pattern, it just tells the regex engine which parses the regex that our pattern begins on the first column of the text (for more info :h /\%c). The dot at the end of the regex means any character.

So '\%1c.' means : any character on the first column of the text, and matchstr(word, '\%1c.') will return the first character of word.

To compare the first letter of word with letter we use the ==# operator. It's an operator that is case sensitive (no matter what the value of the ignorecase option is). You could also use ==? which is case insensitive, or a bare == (which is case sensitive or not depending on ignorecase ; not recommended to use this one).

Then, if the first character of word is letter, here's what the function does :

let col = match(line, word) + 1

It stores the index of the first character of word plus one inside the variable col. match() is a built-in function, whose base syntax is match(string, pattern) and which returns the index in string where pattern matches. We add one because vim begins indexing with 0.

Finally it executes this :

execute "normal! " . col . "|"

execute is a command that executes the content of any string you pass to it. Here the string is : "normal! " . col . "|"

The dot operator concatenates the 3 strings "normal !", col (it's a number but vim's coercion turns it into a string automatically), and "|".

Let's say your searched character is on the 10th column of the line. In this case, the previous string would be evaluated to : "normal! 10|". So execute executes "normal! 10|", which results in the normal command typing 10| for us.

  • Thanks, that's exactly what I was looking for. I don't understand half the commands you used tho :/ – solalito Nov 24 '15 at 14:07
  • 2
    I'm sorry for not explaining the code, I'm going to edit the post to explain it so that you can tweak it to your liking if you want. It's just going to take me a moment. – saginaw Nov 24 '15 at 14:13
  • Thank you, take your time! I'm always interested in knowing exactly the tools I am using. – solalito Nov 24 '15 at 14:14
  • I've tried to simplify the code and did my best to explain it in detail. I hope after reading it, the code will make more sense. – saginaw Nov 24 '15 at 16:42
  • I never hoped to get such a complete answer. I read it but I'll need to go over it carefully. +2 if I could! – solalito Nov 24 '15 at 19:33

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.