6

I'm writing a script that does a search like so: normal /needle^M. This works like I want, except that I want to be able to use n or N to repeat the search.

In case it helps, here's the full function:

function! MakeChoices()
    let start = line('.')

    let choiceStartLine = search('^*choice', 'bW')

    let choiceEndLine = search('^\S.*', 'W') "End is first line that starts with non-whitespace

    "If above search fails, might be at bottom of buffer
    if choiceEndLine == 0
        let choiceEndLine = search('^$', 'W') "End is first empty line
    endif

    "Now go back up to the last *goto
    let choiceEndLine = search('*goto', 'bW')

    "Get the entire *choice block and put it in gotoBlock
    let gotoBlock = getline(choiceStartLine, choiceEndLine)

    "Make labelArray (contains all labels to goto)
    let labelArray = []

    for cur in gotoBlock
        if match(cur, '*goto') != -1
            "echo 'cur: '.cur
            let curParsed = matchlist(cur, '*goto \(\S\+\)')
            "echo curParsed
            if len(curParsed) > 1
                let curLabel = curParsed[1]
            else
                echo 'ERROR: Bad *goto ('.cur.')'
                return -1
            endif
            call add(labelArray, curLabel)  
        endif
    endfor

    "Make newline after choice block if needed
    if strlen(getline(choiceEndLine+1)) > 0
        echo 'big line: '.getline(choiceEndLine+1)
        call cursor(choiceEndLine, 1)
        put=''
    endif

    call cursor(choiceEndLine+1, 1)

    "Put the new label blocks
    let skippedLabels = ''
    let numNewLabels = 0
    for cur in labelArray
        if !search('*label '.cur, 'wn')
            let numNewLabels += 1
            put='*label '.cur
            put='[This option is yet to be written.]'
            put=''
        else
            let skippedLabels .= cur.' '
        endif
    endfor

    "Remove trailing blank lines (Up to a point)
    let nextlines = getline(line('.')+1, line('.')+3)
    if len(nextlines) == 3
        if nextlines[0] == '' && nextlines[1] == '' && nextlines[2] == ''
            normal "_3dd
        elseif nextlines[0] == '' && nextlines[1] == ''
            normal "_2dd
        elseif nextlines[0] == ''
            normal "_dd
        endif
    endif

    "Move to first label's text (use ctrl-v ctrl-m to input the <CR> at
    "end)
    if numNewLabels != 0
        call cursor(choiceEndLine, 1)
        normal /\[This option is yet to be written.\]
    endif

    "Print status message
    if len(skippedLabels) > 0
        echo 'Skipped: '.skippedLabels
    else
        echo 'New labels created: '.numNewLabels
    endif

endfunction

The line in question is near the end, and you can probably just ignore the rest of the code. Basically, this function parses some code (in a language called ChoiceScript) and outputs a set of stock templates based on that code, then jumps (via the vim search command in question) to the first stock template.

For example, let's say I begin with this ChoiceScript code:

*choice
    #Option A
        *goto optionA
    #Option B
        *goto optionB

Then, after running the function, the file becomes:

*choice
    #Option A
        *goto optionA
    #Option B
        *goto optionB

*label optionA
[This option is yet to be written.]

*label optionB
[This option is yet to be written.]

The cursor will be on the first "[This option is yet to be written.]", and I want to be able to hit n to jump to the second if that's the option I want to work on first.

9
  • 3
    I used it in a function and had no problem using n or N to repeat the search. Do you have an interfering plugin? Try with vim -Nu NONE, manually defining the function or action.
    – muru
    Commented Jun 22, 2015 at 3:07
  • @muru, thanks for the suggestion. I just tried it, and it still didn't work. Hitting n repeated a search earlier in the / history, but if I typed / and then hit the up arrow, the latest, function-called search appeared. I hit enter, it worked, and then n worked, so this is very odd. Perhaps it's a bug in vim itself? My version is 7.4.692. Commented Jun 22, 2015 at 7:26
  • I attempted a workaround by putting normal /<Up>^MN after the normal /needle^M line in my function, but the <Up> didn't work. Is it possible somehow or is there some other workaround? Commented Jun 22, 2015 at 7:42
  • 3
    You can set the regexp used for n by assigning it to the / register: let @/="needle". You don't even need to run a search first for that to work.
    – lcd047
    Commented Jun 22, 2015 at 7:43
  • 1
    Please add your full function and possibly more background to your question. What you ask is the normal behavior so there must be something else.
    – romainl
    Commented Jun 22, 2015 at 9:03

3 Answers 3

7

From :help function-search-undo

The last used search pattern and the redo command "." will not be changed by the function. This also implies that the effect of :nohlsearch is undone when the function returns.

This is a bit of a "hidden" feature... Another "hidden" feature is that assigning directly to @/ overrides this behaviour:

fun! X()
    /Hello
    let @/ = 'Hello'
endfun
3
  • Thanks for the info on search(). However, setting @/ is what I actually wanted to do by using normal instead. Commented Jul 2, 2015 at 20:04
  • @Gantron Right; updated the answer. Commented Jul 3, 2015 at 9:19
  • Good to know. However, if vim works this way, why does the search wind up in the search history? Commented Jul 4, 2015 at 18:40
3

One neat trick is to use feedkeys("/pattern\<CR>"). This works differently than using :normal because the keys being fed into vim gets evaluated after the function returns so the problem with search direction and search pattern being reset goes away. ;-) This actually lets you control both search pattern AND search direction from your function. I have found no other way to do this.

1

You probably are doing that inside a function, which will reset the / after it completes. From :help quote_/:

Note that the valued is restored when returning from a function |function-search-undo|.

To overcome this behavior you need to manually update the Last search pattern register by adding the following command to the function along with the search /needle call:

let @/='needle'

Note: using needle only as an example search.

1
  • Forcing its storage by setting @/ worked. Thanks! However, what do you mean / resets after the function completes? The search in question shows up when I hit / followed by the up arrow when back in normal mode after running my function, so doesn't that mean it didn't reset? Commented Jul 2, 2015 at 20:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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