Vim Tips Wiki
(Remove html character entities)
(→‎Alternative Solutions: questions about the proposal)
(11 intermediate revisions by 7 users not shown)
Line 1: Line 1:
{{review}}
 
 
{{TipImported
 
{{TipImported
 
|id=616
 
|id=616
 
|previous=615
 
|previous=615
 
|next=618
 
|next=618
|created=December 10, 2003
+
|created=2003
 
|complexity=basic
 
|complexity=basic
|author=David Fishburn
+
|author=David Fishburn and Tyler Rick
 
|version=6.0
 
|version=6.0
 
|rating=6/5
 
|rating=6/5
Line 13: Line 12:
 
}}
 
}}
 
You can place this in your vimrc file, and then run:
 
You can place this in your vimrc file, and then run:
 
 
<pre>
 
<pre>
  +
:WatchForChanges
:CheckForUpdates
 
 
</pre>
 
</pre>
   
 
This will toggle the behaviour for the given buffer.
 
This will toggle the behaviour for the given buffer.
 
 
<pre>
 
<pre>
 
" If you are using a console version of Vim, or dealing
 
" If you are using a console version of Vim, or dealing
Line 28: Line 25:
 
"
 
"
 
" There can be cases where you can be working away, and Vim does not
 
" There can be cases where you can be working away, and Vim does not
" realize the file has changed.
+
" realize the file has changed. This command will force Vim to check
  +
" more often.
 
"
 
"
  +
" Calling this command sets up autocommands that check to see if the
" This function will force Vim to check more often.
 
  +
" current buffer has been modified outside of vim (using checktime)
  +
" and, if it has, reload it for you.
 
"
 
"
  +
" This check is done whenever any of the following events are triggered:
" The function will turn on the :checktime command so that the
 
  +
" * BufEnter
" file is checked based on the CursorHold event.
 
  +
" * CursorMoved
  +
" * CursorMovedI
  +
" * CursorHold
  +
" * CursorHoldI
 
"
 
"
  +
" In other words, this check occurs whenever you enter a buffer, move the cursor,
" CheckForUpdates will toggle the behaviour on the current buffer.
 
  +
" or just wait without doing anything for 'updatetime' milliseconds.
function! CheckForUpdates( autoload )
 
  +
"
" Save the current default register
 
  +
" Normally it will ask you if you want to load the file, even if you haven't made
let saveB = @"
 
  +
" any changes in vim. This can get annoying, however, if you frequently need to reload
  +
" the file, so if you would rather have it to reload the buffer *without*
  +
" prompting you, add a bang (!) after the command (WatchForChanges!).
  +
" This will set the autoread option for that buffer in addition to setting up the
  +
" autocommands.
  +
"
  +
" If you want to turn *off* watching for the buffer, just call the command again while
  +
" in the same buffer. Each time you call the command it will toggle between on and off.
  +
"
  +
" WatchForChanges sets autocommands that are triggered while in *any* buffer.
  +
" If you want vim to only check for changes to that buffer while editing the buffer
  +
" that is being watched, use WatchForChangesWhileInThisBuffer instead.
  +
"
  +
command! -bang WatchForChanges :call WatchForChanges(@%, {'toggle': 1, 'autoread': <bang>0})
  +
command! -bang WatchForChangesWhileInThisBuffer :call WatchForChanges(@%, {'toggle': 1, 'autoread': <bang>0, 'while_in_this_buffer_only': 1})
  +
command! -bang WatchForChangesAllFile :call WatchForChanges('*', {'toggle': 1, 'autoread': <bang>0})
  +
" WatchForChanges function
  +
"
  +
" This is used by the WatchForChanges* commands, but it can also be
  +
" useful to call this from scripts. For example, if your script executes a
  +
" long-running process, you can have your script run that long-running process
  +
" in the background so that you can continue editing other files, redirects its
  +
" output to a file, and open the file in another buffer that keeps reloading itself
  +
" as more output from the long-running command becomes available.
  +
"
  +
" Arguments:
  +
" * bufname: The name of the buffer/file to watch for changes.
  +
" Use '*' to watch all files.
  +
" * options (optional): A Dict object with any of the following keys:
  +
" * autoread: If set to 1, causes autoread option to be turned on for the buffer in
  +
" addition to setting up the autocommands.
  +
" * toggle: If set to 1, causes this behavior to toggle between on and off.
  +
" Mostly useful for mappings and commands. In scripts, you probably want to
  +
" explicitly enable or disable it.
  +
" * disable: If set to 1, turns off this behavior (removes the autocommand group).
  +
" * while_in_this_buffer_only: If set to 0 (default), the events will be triggered no matter which
  +
" buffer you are editing. (Only the specified buffer will be checked for changes,
  +
" though, still.) If set to 1, the events will only be triggered while
  +
" editing the specified buffer.
  +
" * more_events: If set to 1 (the default), creates autocommands for the events
  +
" listed above. Set to 0 to not create autocommands for CursorMoved, CursorMovedI,
  +
" (Presumably, having too much going on for those events could slow things down,
  +
" since they are triggered so frequently...)
  +
function! WatchForChanges(bufname, ...)
  +
" Figure out which options are in effect
 
if a:bufname == '*'
  +
let id = 'WatchForChanges'.'AnyBuffer'
  +
" If you try to do checktime *, you'll get E93: More than one match for * is given
  +
let bufspec = ''
  +
else
 
if bufnr(a:bufname) == -1
  +
echoerr "Buffer " . a:bufname . " doesn't exist"
  +
return
 
end
  +
let id = 'WatchForChanges'.bufnr(a:bufname)
  +
let bufspec = a:bufname
  +
end
  +
if len(a:000) == 0
  +
let options = {}
  +
else
  +
if type(a:1) == type({})
  +
let options = a:1
  +
else
  +
echoerr "Argument must be a Dict"
  +
end
  +
end
  +
let autoread = has_key(options, 'autoread') ? options['autoread'] : 0
  +
let toggle = has_key(options, 'toggle') ? options['toggle'] : 0
  +
let disable = has_key(options, 'disable') ? options['disable'] : 0
  +
let more_events = has_key(options, 'more_events') ? options['more_events'] : 1
  +
let while_in_this_buffer_only = has_key(options, 'while_in_this_buffer_only') ? options['while_in_this_buffer_only'] : 0
  +
if while_in_this_buffer_only
  +
let event_bufspec = a:bufname
  +
else
  +
let event_bufspec = '*'
  +
end
 
let reg_saved = @"
  +
"let autoread_saved = &autoread
 
let msg = "\n"
 
let msg = "\n"
" Check to see if the checkforupdates autocommand exists
+
" Check to see if the autocommand already exists
 
redir @"
 
redir @"
silent! exec 'au CheckForUpdates'.bufnr('%')
+
silent! exec 'au '.id
 
redir END
 
redir END
if @" =~ 'E216'
+
let l:defined = (@" !~ 'E216: No such group or event:')
  +
" If not yet defined...
if a:autoload == 1
 
  +
if !l:defined
let msg = msg . 'AutoRead enabled - '
 
setlocal autoread
+
if l:autoread
 
let msg = msg . 'Autoread enabled - '
endif
 
  +
if a:bufname == '*'
silent! exec 'augroup CheckForUpdates'.bufnr('%')
 
  +
set autoread
exec "au Cursorhold " . expand("%:p") . " :checktime"
 
  +
else
 
setlocal autoread
  +
end
  +
end
 
silent! exec 'augroup '.id
  +
if a:bufname != '*'
  +
"exec "au BufDelete ".a:bufname . " :silent! au! ".id . " | silent! augroup! ".id
  +
"exec "au BufDelete ".a:bufname . " :echomsg 'Removing autocommands for ".id."' | au! ".id . " | augroup! ".id
  +
exec "au BufDelete ".a:bufname . " execute 'au! ".id."' | execute 'augroup! ".id."'"
  +
end
  +
exec "au BufEnter ".event_bufspec . " :checktime ".bufspec
  +
exec "au CursorHold ".event_bufspec . " :checktime ".bufspec
 
exec "au CursorHoldI ".event_bufspec . " :checktime ".bufspec
  +
" The following events might slow things down so we provide a way to disable them...
  +
" vim docs warn:
  +
" Careful: Don't do anything that the user does
  +
" not expect or that is slow.
  +
if more_events
  +
exec "au CursorMoved ".event_bufspec . " :checktime ".bufspec
  +
exec "au CursorMovedI ".event_bufspec . " :checktime ".bufspec
  +
end
 
augroup END
 
augroup END
let msg = msg . 'Now checking for updates...'
+
let msg = msg . 'Now watching ' . bufspec . ' for external updates...'
else
+
end
  +
" If they want to disable it, or it is defined and they want to toggle it,
if a:autoload == 1
 
  +
if l:disable || (l:toggle && l:defined)
let msg = msg . 'AutoRead disabled - '
 
  +
if l:autoread
setlocal noautoread
 
 
let msg = msg . 'Autoread disabled - '
endif
 
  +
if a:bufname == '*'
" Using an autogroup allows us to remove it easily with the following
 
  +
set noautoread
" command. If we do not use an autogroup, we cannot remove this
 
  +
else
" single :checktime command
 
  +
setlocal noautoread
" augroup! checkforupdates
 
  +
end
silent! exec 'au! CheckForUpdates'.bufnr('%')
 
  +
end
silent! exec 'augroup! CheckForUpdates'.bufnr('%')
 
 
" Using an autogroup allows us to remove it easily with the following
let msg = msg . 'No longer checking for updates.'
 
 
" command. If we do not use an autogroup, we cannot remove this
endif
 
 
" single :checktime command
echo msg
 
 
" augroup! checkforupdates
let @"=saveB
 
 
silent! exec 'au! '.id
 
silent! exec 'augroup! '.id
 
let msg = msg . 'No longer watching ' . bufspec . ' for external updates.'
  +
elseif l:defined
  +
let msg = msg . 'Already watching ' . bufspec . ' for external updates'
  +
end
 
echo msg
  +
let @"=reg_saved
 
endfunction
 
endfunction
command! -bang CheckForUpdates :call CheckForUpdates(<bang>0)
 
 
</pre>
 
</pre>
   
 
==Comments==
 
==Comments==
  +
Awesome script, shame autoread doesn't do this by default.
  +
  +
I got the best results with the following.
  +
  +
:let autoreadargs={'autoread':1}
  +
:execute WatchForChanges("*",autoreadargs)
  +
  +
I want to run ipython/vim/screen combo so nice if the buffers just blur all into one.
  +
  +
----
  +
  +
If you want to remove the need for pressing ENTER at startup, just remove the "echo msg" line at the bottom of the script.
  +
  +
==== '''Alternative Solutions''' ====
  +
A simpler and perhaps a more deterministic approach has been outlined in this stack-overflow answer http://stackoverflow.com/a/20418591/850996. Basically, check for and reload (or discard) external changes when vim or the current buffer gains focus, and optionally, auto-save when leaving focus. This seems more in line with how other more modern GUI editors work. Here are the commands, copied verbatim from the SO answer:
  +
  +
-- reload when entering the buffer or gaining focus
  +
<code>au FocusGained,BufEnter * :silent! !</code>
  +
-- save when exiting the buffer or losing focus
  +
<code>au FocusLost,WinLeave * :silent! w</code>
  +
  +
----
  +
Why is this needed? {{help|timestamp}} says:
  +
  +
<blockquote>
  +
After a shell command is run (|:!cmd| |suspend| |:read!| |K|) timestamps are
  +
compared for all buffers in a window. Vim will run any associated
  +
|FileChangedShell| autocommands or display a warning for any files that have
  +
changed. In the GUI this happens when Vim regains input focus.
  +
</blockquote>.
  +
  +
So the FocusGained event should not be needed at all.
  +
  +
Also, rather than shelling out with an empty command and ignoring the error...it would be better to use the {{help|prefix=no|:checktime}} command directly.
  +
  +
--[[User:Fritzophrenic|Fritzophrenic]] ([[User talk:Fritzophrenic|talk]]) 21:03, May 30, 2014 (UTC)

Revision as of 21:03, 30 May 2014

Tip 616 Printable Monobook Previous Next

created 2003 · complexity basic · author David Fishburn and Tyler Rick · version 6.0


You can place this in your vimrc file, and then run:

:WatchForChanges

This will toggle the behaviour for the given buffer.

" If you are using a console version of Vim, or dealing
" with a file that changes externally (e.g. a web server log)
" then Vim does not always check to see if the file has been changed.
" The GUI version of Vim will check more often (for example on Focus change),
" and prompt you if you want to reload the file.
"
" There can be cases where you can be working away, and Vim does not
" realize the file has changed. This command will force Vim to check
" more often.
"
" Calling this command sets up autocommands that check to see if the
" current buffer has been modified outside of vim (using checktime)
" and, if it has, reload it for you.
"
" This check is done whenever any of the following events are triggered:
" * BufEnter
" * CursorMoved
" * CursorMovedI
" * CursorHold
" * CursorHoldI
"
" In other words, this check occurs whenever you enter a buffer, move the cursor,
" or just wait without doing anything for 'updatetime' milliseconds.
"
" Normally it will ask you if you want to load the file, even if you haven't made
" any changes in vim. This can get annoying, however, if you frequently need to reload
" the file, so if you would rather have it to reload the buffer *without*
" prompting you, add a bang (!) after the command (WatchForChanges!).
" This will set the autoread option for that buffer in addition to setting up the
" autocommands.
"
" If you want to turn *off* watching for the buffer, just call the command again while
" in the same buffer. Each time you call the command it will toggle between on and off.
"
" WatchForChanges sets autocommands that are triggered while in *any* buffer.
" If you want vim to only check for changes to that buffer while editing the buffer
" that is being watched, use WatchForChangesWhileInThisBuffer instead.
"
command! -bang WatchForChanges                  :call WatchForChanges(@%,  {'toggle': 1, 'autoread': <bang>0})
command! -bang WatchForChangesWhileInThisBuffer :call WatchForChanges(@%,  {'toggle': 1, 'autoread': <bang>0, 'while_in_this_buffer_only': 1})
command! -bang WatchForChangesAllFile           :call WatchForChanges('*', {'toggle': 1, 'autoread': <bang>0})
" WatchForChanges function
"
" This is used by the WatchForChanges* commands, but it can also be
" useful to call this from scripts. For example, if your script executes a
" long-running process, you can have your script run that long-running process
" in the background so that you can continue editing other files, redirects its
" output to a file, and open the file in another buffer that keeps reloading itself
" as more output from the long-running command becomes available.
"
" Arguments:
" * bufname: The name of the buffer/file to watch for changes.
"     Use '*' to watch all files.
" * options (optional): A Dict object with any of the following keys:
"   * autoread: If set to 1, causes autoread option to be turned on for the buffer in
"     addition to setting up the autocommands.
"   * toggle: If set to 1, causes this behavior to toggle between on and off.
"     Mostly useful for mappings and commands. In scripts, you probably want to
"     explicitly enable or disable it.
"   * disable: If set to 1, turns off this behavior (removes the autocommand group).
"   * while_in_this_buffer_only: If set to 0 (default), the events will be triggered no matter which
"     buffer you are editing. (Only the specified buffer will be checked for changes,
"     though, still.) If set to 1, the events will only be triggered while
"     editing the specified buffer.
"   * more_events: If set to 1 (the default), creates autocommands for the events
"     listed above. Set to 0 to not create autocommands for CursorMoved, CursorMovedI,
"     (Presumably, having too much going on for those events could slow things down,
"     since they are triggered so frequently...)
function! WatchForChanges(bufname, ...)
  " Figure out which options are in effect
  if a:bufname == '*'
    let id = 'WatchForChanges'.'AnyBuffer'
    " If you try to do checktime *, you'll get E93: More than one match for * is given
    let bufspec = ''
  else
    if bufnr(a:bufname) == -1
      echoerr "Buffer " . a:bufname . " doesn't exist"
      return
    end
    let id = 'WatchForChanges'.bufnr(a:bufname)
    let bufspec = a:bufname
  end
  if len(a:000) == 0
    let options = {}
  else
    if type(a:1) == type({})
      let options = a:1
    else
      echoerr "Argument must be a Dict"
    end
  end
  let autoread    = has_key(options, 'autoread')    ? options['autoread']    : 0
  let toggle      = has_key(options, 'toggle')      ? options['toggle']      : 0
  let disable     = has_key(options, 'disable')     ? options['disable']     : 0
  let more_events = has_key(options, 'more_events') ? options['more_events'] : 1
  let while_in_this_buffer_only = has_key(options, 'while_in_this_buffer_only') ? options['while_in_this_buffer_only'] : 0
  if while_in_this_buffer_only
    let event_bufspec = a:bufname
  else
    let event_bufspec = '*'
  end
  let reg_saved = @"
  "let autoread_saved = &autoread
  let msg = "\n"
  " Check to see if the autocommand already exists
  redir @"
    silent! exec 'au '.id
  redir END
  let l:defined = (@" !~ 'E216: No such group or event:')
  " If not yet defined...
  if !l:defined
    if l:autoread
      let msg = msg . 'Autoread enabled - '
      if a:bufname == '*'
        set autoread
      else
        setlocal autoread
      end
    end
    silent! exec 'augroup '.id
      if a:bufname != '*'
        "exec "au BufDelete    ".a:bufname . " :silent! au! ".id . " | silent! augroup! ".id
        "exec "au BufDelete    ".a:bufname . " :echomsg 'Removing autocommands for ".id."' | au! ".id . " | augroup! ".id
        exec "au BufDelete    ".a:bufname . " execute 'au! ".id."' | execute 'augroup! ".id."'"
      end
        exec "au BufEnter     ".event_bufspec . " :checktime ".bufspec
        exec "au CursorHold   ".event_bufspec . " :checktime ".bufspec
        exec "au CursorHoldI  ".event_bufspec . " :checktime ".bufspec
      " The following events might slow things down so we provide a way to disable them...
      " vim docs warn:
      "   Careful: Don't do anything that the user does
      "   not expect or that is slow.
      if more_events
        exec "au CursorMoved  ".event_bufspec . " :checktime ".bufspec
        exec "au CursorMovedI ".event_bufspec . " :checktime ".bufspec
      end
    augroup END
    let msg = msg . 'Now watching ' . bufspec . ' for external updates...'
  end
  " If they want to disable it, or it is defined and they want to toggle it,
  if l:disable || (l:toggle && l:defined)
    if l:autoread
      let msg = msg . 'Autoread disabled - '
      if a:bufname == '*'
        set noautoread
      else
        setlocal noautoread
      end
    end
    " Using an autogroup allows us to remove it easily with the following
    " command. If we do not use an autogroup, we cannot remove this
    " single :checktime command
    " augroup! checkforupdates
    silent! exec 'au! '.id
    silent! exec 'augroup! '.id
    let msg = msg . 'No longer watching ' . bufspec . ' for external updates.'
  elseif l:defined
    let msg = msg . 'Already watching ' . bufspec . ' for external updates'
  end
  echo msg
  let @"=reg_saved
endfunction

Comments

Awesome script, shame autoread doesn't do this by default.

I got the best results with the following.

let autoreadargs={'autoread':1}
execute WatchForChanges("*",autoreadargs)

I want to run ipython/vim/screen combo so nice if the buffers just blur all into one.


If you want to remove the need for pressing ENTER at startup, just remove the "echo msg" line at the bottom of the script.

Alternative Solutions

A simpler and perhaps a more deterministic approach has been outlined in this stack-overflow answer http://stackoverflow.com/a/20418591/850996. Basically, check for and reload (or discard) external changes when vim or the current buffer gains focus, and optionally, auto-save when leaving focus. This seems more in line with how other more modern GUI editors work. Here are the commands, copied verbatim from the SO answer:

-- reload when entering the buffer or gaining focus

au FocusGained,BufEnter * :silent! !

-- save when exiting the buffer or losing focus

au FocusLost,WinLeave * :silent! w

Why is this needed? :help timestamp says:

After a shell command is run (|:!cmd| |suspend| |:read!| |K|) timestamps are compared for all buffers in a window. Vim will run any associated |FileChangedShell| autocommands or display a warning for any files that have changed. In the GUI this happens when Vim regains input focus.

.

So the FocusGained event should not be needed at all.

Also, rather than shelling out with an empty command and ignoring the error...it would be better to use the :checktime command directly.

--Fritzophrenic (talk) 21:03, May 30, 2014 (UTC)