4

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.

  • 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 Jun 22 '15 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. – Flurrywinde Jun 22 '15 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? – Flurrywinde Jun 22 '15 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 Jun 22 '15 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 Jun 22 '15 at 9:03
2

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
  • Thanks for the info on search(). However, setting @/ is what I actually wanted to do by using normal instead. – Flurrywinde Jul 2 '15 at 20:04
  • @Gantron Right; updated the answer. – Martin Tournoij Jul 3 '15 at 9:19
  • Good to know. However, if vim works this way, why does the search wind up in the search history? – Flurrywinde Jul 4 '15 at 18:40
0

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.

  • 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? – Flurrywinde Jul 2 '15 at 20:10
0

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.

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.