12

I have a function that does a lot of moving and outputting of text into the current vim buffer, and when I run it, seeing all that happening at blinding speed is a bit disconcerting.

How can I freeze the screen until the function's done?

Here's the function in question:

function! MakeChoices()
    :let save_view = winsaveview()
    let start = line('.')

    "Locate previous *choice. (b=backwards, W=nowrap, n=doNot move cursor)
    let choiceStartLine = search('^*choice', 'bW')

    if !choiceStartLine
        echo "No *choice found. (*choice must not be indented. This is to avoid finding *choice blocks nested in another *choice block.)"
        return -1
    endif
    "return getline(target_line_num, target_line_num+4)
    "Locate end of *choice block
    "echo getline(choiceStartLine, choiceStartLine+2)
    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

    "Restore window to what it looked like (in case the searches scrolled
    "it)
    call winrestview(save_view)

    "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.\]
        let @/='\[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
4
  • 2
    Does :set lazyredraw help?
    – VanLaser
    Jul 6, 2015 at 22:57
  • Sorry, no. That only helps for macros. I just tried it, and it didn't work for my function. Jul 7, 2015 at 6:19
  • 2
    I don't know of any way to do this, other than perhaps freezing the terminal window (which won't work for gVim). But perhaps there is another way to make your function run with less screen updates? It would be helpful if you posted your function ;-) Jul 7, 2015 at 8:41
  • You asked for it, @Carpetsmoker. ;-) Function added. (It's rather long.) Jul 9, 2015 at 22:23

2 Answers 2

6

I think the problem is not :lazyredraw which, as I understand from docs, should work for functions (see :help :redraw, it says "Useful to update the screen halfway executing a script or function").

The problem is that you use normal to update buffer and it works like if you actually type something and here :lazyredraw doesn't have an effect.

Instead of normal you need to use text manipulation functions (like setline()) and ex commands (like :delete).

Compare these two functions, first one, MakeChangesNorm() will do some crazy screen updates, while the second one, MakeChangesFunctions() will do the update instantly:

function! MakeChangesNorm()
    let lastline = line('$')
    norm gg
    let linenum = line('.')
    let lastline = line('$')
    while linenum < lastline
        norm ^
        norm s/choice/test/
        norm j
        normal "_3dd
        let linenum = line('.')
        let lastline = line('$')
    endwhile
endfunction


function! MakeChangesFunctions()
    norm gg
    let linenum = line('.')
    let lastline = line('$')
    while linenum < lastline
        let line = getline(linenum)
        " Substitute using substitute() and setline()
        let line = substitute(line, 'choice', 'test', '')
        call setline(linenum, line)
        " Delete lines using :delete
        execute '.,.+2delete _'
        let linenum = line('.')
        let lastline = line('$')
    endwhile
endfunction

The file I tested it on looks like this:

*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
*choice test2 test3 super
... 60 lines like this ...
2
  • To be clear; is there no way to issue several normal commands and entirely postpone updating of the screen until a subsequent "restore the screen" command? My understanding is winsaveview and winrestview simply store the cursor location and relative position of the line in the window.
    – Luke Davis
    Feb 2, 2018 at 21:55
  • I'll ask this in another question.
    – Luke Davis
    Feb 2, 2018 at 21:55
0

I don't have enough points yet to add comments to other questions, so I'll write my experience down as another answer.

I also have some vimscript functions in my ~/.vimrc that scroll the window, and they used to cause screen flickering that was very annoying to me. I spent some time on it, and I was able to eliminate the flickering by employing the following pattern in my functions:

function! MyFunction()
  set lazyredraw

  " do the stuff, using e.g.
  "   execute "normal! " .. n .. "\<C-Y>"
  " to scroll the window

  redraw
  set nolazyredraw
endfunction

See also this answer for a more detailed example.

4
  • Frankly I leave lazyredraw on all the time. I’m curious if you could explain why you leave it off mostly?
    – D. Ben Knoble
    Aug 10, 2023 at 12:46
  • That's a good question. Basically, I just follow the default setting for lazyredraw, which is disabled. If I ever get to know the vim source code well enough to know exactly what lazyredraw does internally, I might have it enabled all the time.
    – dsimic
    Aug 10, 2023 at 13:31
  • @D.BenKnoble FWIW I leave 'lazyredraw' off most of the time for purely aesthetic reasons: I find it satisfying watching macros I've recorded do their stuff, and 99% of the time they're going to complete fast enough anyway.
    – Rich
    Jan 10 at 12:19
  • @Rich I agree about watching the recorded macros do their job, and I also find that rather satisfying. :)
    – dsimic
    Jan 10 at 13:17

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.