6

I've recently installed a plugin called vim-troll-stopper.

Some unicode characters are drawn exactly the same but have different meanings. I think they are called homoglyphs.
Some programmers may use this to troll people using their code, by replacing for example the semicolon (; unicode 003b) with a greek question mark (; unicode 037e).

The purpose of the plugin is to warn you by highlighting all suspicious characters in red, and by replacing them with their legitimate counterparts if you type the command :TrollStop.

However I've noticed that the more I switch between buffers, the more the plugin causes lag.

Here's how to reproduce the lag :

  1. Install the plugin ; if you use Vundle as a plugin manager, it simply means adding the line Plugin 'vim-utils/vim-troll-stopper' inside your vimrc, writing and sourcing it, then typing the command :PluginInstall.

  2. open 2 files foo and bar : vim foo bar

  3. write some text inside foo : here's some text
  4. copy and paste the line a lot : yy99p
  5. switch to buffer bar : :b2
  6. write some text inside bar : more text
  7. copy and paste the line a lot : yy99p
  8. Now, switch back and forth a lot between the two buffers with the default mapping <C-^>, and look at the cpu load.

When I do this on my system, vim usually doesn't use more than 2% of the cpu and the movements inside the files (hjkl) remain quick.
However, with the plugin enabled, switching a lot between buffers causes vim to use between 25% and 30% of the cpu, and then the movements inside the files are very slow.

I don't know a lot about vimscript, but I noticed that the problem is not binary : vim is not quick then suddenly completely slow.
The more I switch buffers, the more it is slow.
So I supposed that something was happening every time I entered a buffer.

I looked at the code, and noticed this line :

autocmd BufEnter * call <SID>HighlighTrolling()

This command causes vim to call the HighlighTrolling() function whenever you enter a buffer. I suppose the function highlights the suspicious characters in red.

In the 14th chapter of Learn Vimscript the Hard Way, the author says that if you create an autocommand and define it twice, even though the autocommand is the same, it will be executed twice :

When you create an autocommand like this Vim has no way of knowing if you want it to replace an existing one. In our case, Vim created two separate autocommands that each happen to do the same thing.

He also says that the solution is to wrap the autocommand inside an autocommand group and clear the group :

Vim has a solution to the problem. The first step is to group related autocommands into named groups.

[...]

If you want to clear a group you can use autocmd! inside the group.

Now, going back to the plugin lag, I thought this could explain it. I tried fixing the code like this :

augroup highlight
    autocmd!
    autocmd BufEnter * call <SID>HighlighTrolling()
augroup END

Unfortunately, it didn't solve the lag issue. So I tried to simply change the event that triggers the autocommand from BufEnter to BufRead,BufNewFile :

autocmd BufRead,BufNewFile * call <SID>HighlighTrolling()

It worked, the lag is now gone.

However, out of curiosity, I would like to know 3 things :

  1. Why is the plugin causing lag ? Is it because it duplicates an autocommand every time I enter a buffer ? If so, is there a way to see those duplicates autocommands ? I typed :com and didn't see them.
  2. In the context of this plugin, what do you think would be the best event to trigger the autocommand that highlights the suspicious characters ?
  3. More generally, how would you modify the plugin so that it doesn't cause lag anymore ?
3

vim-troll-stopper author here. First of all thank you for identifying the problem and contributing a solution.

After further investigating and testing the approach with BufRead,BufNewFile proposed by @Sato above I must say that's unfortunately not the solution. There are 2 scenarios where this doesn't work (this comes down to situations where the above auto commands are not triggered):

  1. start vim with no arguments (eg $ vim in the command line). At this moment neither BufRead nor BufNewFile auto commands are triggered.
    If you now enter insert mode and copy-paste these troll characters !!ǃ︕﹗ they will not be highlighted by the plugin.

  2. open an existing file with vim (eg $ vim foo.txt) and copy-paste these troll characters in the buffer: !!ǃ︕﹗. The chars will be properly highlighted.
    Now invoke :split command (BufRead,BufNewFile will not trigger!). Troll characters in new vim window will not be highlighted.

Also, even though we try to prevent unnecessary matchadd()s with BufRead,BufNewFile they will still pile up. This can be checked with :echo getmatches() after opening a couple files.

The core to figuring this out is to understand that matchadd() works with vim's windows not buffers. As :h matchadd() at the beginning states:

Defines a pattern to be highlighted in the current window

This is really counter intuitive and what made this hard to get right. I see another popular plugin author had a lot of issues because of this.

After a lot of experimenting, I found this to be working:

  • use BufEnter,WinEnter auto-commands
  • limit invoking matchadd() command to only once per vim window by using guard variables:

    if !exists('w:guard_var')
      call matchadd(...)
      let w:guard_var = 1
    endif
    

"Troll" characters are now highlighted in all scenarios. Also, if you check window matches with :echo getmatches() at any point while using vim, there will be only one TrollStopper group.

  • Sure, I removed my answer. Now, if only your plugin wouldn't bail out if encoding is not utf8... :) – Sato Katsura Nov 3 '15 at 5:20

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.