jannis-baum / vivify.vim

Live Markdown viewer for Vim and Neovim powered by Vivify

Home Page:https://github.com/jannis-baum/vivify

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Configure refreshing speed

jannis-baum opened this issue · comments

I'm kinda bummed out about how slowly the page refreshes after saving file. Would this be easy to change or configurable if necessary?

I have a couple related questions with that:

  • Would it be possible to refresh page even without having to save?
  • Sometimes I run into this: trying to open an empty file doesn't work, it needs to have > like at least one character in the contents

^ Both of the above occur to me when I use Vivify as sort of a live-notetaking side preview

Edit:
Oh yeah, some of this might be about the vim plugin

Originally posted by @tuurep in jannis-baum/Vivify#35 (comment)

Hello @tuurep! Sorry for taking a while to reply, I forgot. I moved your question here since this is, as you suspected, related to the plugin, not main Vivify.

I will write the solution(s) first and then give some more details in case you are interested

Solution(s)

The plugin updates Vivify on Vim's CursorHold event, so it will update whenever you stop moving your cursor/typing for whatever your updatetime option in Vim is set to (by default 4 seconds). If you want this to happen faster,

  1. reduce updatetime; I personally have set updatetime=300 in my Vim config for 0.3 seconds
  2. open a PR on the plugin that gives the option to update on a different event, such as when updating the buffer

Option (2) would get as close to instant updates as possible but creates lots of network traffic which I personally wouldn't want. I suggest you try and play with your updatetime settings first and resort to (2) if that's not enough :)

Details

Vivify doesn't refresh anything on its own, auto-updates are taken care of by the editor plugin. Let's take a quick look into the architecture here.

When you just open Vivify, it reads the file's contents from disk, and then renders them for you to look at. The editor integration works differently: Here, the editor (in this case Vim) sends a POST request with updated file contents and/or cursor position to Vivify, which will then re-render it (and scroll if necessary).

This means that the updates you are referring to don't happen when you save the file, but whenever Vim sends the request to Vivify's server.

Ah this reveals so much where the issue lies for me.

I'll take a better look at everything you explained but one thing I should say first is that I also use updatetime (and CursorHold event by extension) for another thing which makes sense to be ~4 seconds long

https://github.com/tuurep/dotfiles/blob/master/.config/nvim/plugin/autoclear-cmdline.lua

I'll have to think of something, now one plugin wants a long updatetime and another wants it short.

Sounds good!

I have had a short CursorHold for a long time for coc.vim and never thought of the refresh being slow because of this. Funnily enough, I also have the auto-clearing command line the same way you have it ^^ I always press g< or use :mes when I actually want to see stuff there

Interesting 😄 yeah I got fed up which each nvim window having some irrelevant stuff lingering at the bottom and did that

Did you ever consider BufWritePost in place of CursorHold?

https://vimdoc.sourceforge.net/htmldoc/autocmd.html#BufWrite

I just tried it out, seems like a perfect fix if we don't yet consider updating outside of file saves. Would also make it faster for you (instant vs the 300ms 😄 )

Also I recalled that https://github.com/iamcco/markdown-preview.nvim updated the preview after a small delay when typing, and the README video confirms it. I'd like to study that, but do you suspect it does it in a way that's too heavy in terms of network traffic?

Hid the above comment because I misunderstood, just tried that it does update even without saving but I tricked myself into believing otherwise because my delay is so long

Well, I'm interested in trying to come up with a way that could achieve a similar result to your updatetime=300 but without having to rely on the updatetime option. I might look at iamcco/markdown-preview.nvim how it does that.

(Alternatively my autoclear-cmdline could use a different method, but I've tried that in the past with a number of issues I couldn't work out. Might try again.)

I looked at markdown-preview.nvim and it uses CursorMoved and CursorMovedI, however on Vim 8 and above, those should definitely be replaced with TextChanged and TextChangedI for this use case

I'm very happy after adding those two events. Do you however think it's still too much network traffic?


Refreshing on TextChanged and TextChangedI

Pros:

  • impression of instant sync with buffer changes
    • if we could ignore technical limitations, would be the perfect event to consider
  • only refreshes with actual changes unlike for example CursorMoved

Cons:

  • in insert mode or granular delete operations, creates network traffic on every keystroke

I'm very happy after adding those two events. Do you however think it's still too much network traffic?

Feel free to test it: You can create a very big file, let's say a few thousand lines, view it with Vivify, and type quickly. Then see how your computer feels about it🙈 If you think it's fine we can make these changes. If not I'd make it optional and/or give the option to only use this for files shorter than some fixed size :)

That's a great idea

I pasted ~2000 lines of lorem ipsum to a buffer, I notice 2 things:

1.

I get this error:

image

Error detected while processing CursorHold Autocommands for "*"..function vivify#sync_content[Error detected while processing CursorHold Autocommands for "*"..function vivify#sync_content[1]..<SNR>64_post:
line    1:
E903: Process failed to start: argument list too long: "/usr/bin/curl"

I had run into this in another context which I was planning to investigate separately, but looks like I also run into it here. The other was with a file that had extremely long URLs (in some cases, i really mean extremely) in single lines

I also could reproduce this by creating a file with a line of aaaaaaaa... about 5000 times with no spaces in between.

So unfortunately I can't really test the syncing performance, but am interested in this curl issue.

2.

With such long files/lines, before even getting hit by potential poor Vivify performance, my NeoVim performance slogs down to a near halt when doing normal vim movements. (Note: confirmed even with vivify.vim uninstalled)

This gets worse the more lines there are: 10.000 lines it was unbearable, 2000 lines it was pretty awful. (both cases are Lorem Ipsum spam file)

I think it might be related to treesitter performance in large files, there are some issues open about that on neovim repo.

Nice, very important findings! I unfortunately won't have a lot of time to work on Vivify in the next couple of months but I will be happy to assist where I can if you want to look into it :)


So unfortunately I can't really test the syncing performance, but am interested in this curl issue.

You can see here how the data is transmitted to the Vivify server: we POST it using curl, and the data (i.e. the entire file content as JSON) is an argument we call curl with. So the error you get makes sense.

We can fix this by sending the data (file content) through stdin instead, from man curl:

If you start the data with the letter @, the rest should be a file name to read the data from, or - if you want curl to read the data from stdin.

So it seems we can do curl [...] --data - and then write the data to stdin.


I think it might be related to treesitter performance in large files, there are some issues open about that on neovim repo.

This is very interesting! If a plugin as big as treesitter can't deal with large files I'll feel much more comfortable if Vivify can't lol

Thanks for the pointers, I'll look into these

Also interested in the

Sometimes I run into this: trying to open an empty file doesn't work, it needs to have like at least one character in the contents

With the TextChanged sync I noticed that if you completely empty a buffer, it'll show the last non-empty contents in browser

And empty md file behavior with command line viv:

  • if file had contents at one point, will show last nonempty content
  • if file never had contents, browser will open a file save dialog for the empty md file

I gave the curl issue a good shot today,

I can update an open viewers contents directly from bash using HereDoc (<<) or HereString (<<<) directly from bash:

HereDoc:

$ curl http://localhost:31622/viewer/path/to/doc.md -H "Content-type: application/json" -d @- <<EOF
{ "content": "FOOOBar" }
EOF

HereString:

$ curl http://localhost:31622/viewer/path/to/doc.md -H "Content-type: application/json" -d @- <<< '{ "content": "FOOOBar" }'

(Note: -X POST is implicit when -d/--data flag is used)

But I can't figure out how to bake this into the vimscript plugin, and I can't get any feedback in terms of warnings/errors/verbosity after editing the s:post func and trying to sync a viewer. It's as if nothing happens.

Do you have any idea -- also did I correctly understand this way of reading from stdin would solve the issue to begin with?

Ah,

I was able to do like my previous comment in the VimScript like this:

function! s:post(data)
    call s:job_start([
        \ '/bin/sh', '-c',
        \ 'curl ' .  s:viv_url . '/viewer' . expand('%:p') .
        \ '-H "Content-type: application/json"' .
        \ '-d @- <<< ' . json_encode(a:data)
    \])
endfunction

However this just shifts the "argument list too long" to the /bin/sh so clearly HereDocs/Strings aren't the answer.

Do we need to use a temporary file?

Hello! My apologies, I forgot about this issue.

My idea of writing the contents to stdin of the curl process would not use a shell at all but instead create a pipe, connect it to stdin, and then write the JSON to it, all from Vim. I just took a couple of minutes looking into this and my guess is that it might get annoyingly complicated (especially figuring it out for Vim and Neovim) for little advantage over the other idea you already had:

Do we need to use a temporary file?

This now seems like the easiest way of doing it to me. We can have Vim create a temporary JSON file and then just send the file contents with curl by prefixing an @: curl [...] --data "@path_to_file". This will definitely fix the "argument list too long" and introduce no new issue about the job_start differences between Vim and Neovim.

The only consideration will again be speed, i.e. does writing the content to a temporary file before each request slow us down too much, but thinking about the experiments you already made I am quite sure this won't be an issue :)

Thanks! I was gonna try to work on this no matter what, but was feeling a little stuck, so your advice is greatly appreciated :)

I can definitely experiment on the temporary file approach next.

Ok, the curl issue is split into its own issue to get Refresh Speed out of the way sooner.

So do you think it should be configurable as an option? TextChanged with TextChangedI works really great for practically instant refresh. I've been using it the whole time.

The only issue with performance I remember having was with a markdown file that that has a large HTML-formatted table inline.

But if I recall correctly, that was more of a treesitter issue than vivify, and I worked around it by setting filetype=txt for that file only.

But! Now that I went to try and reproduce, this file has no issues either way :D

In the meanwhile neovim 0.10 has released and all that, could be just due to gradual improvements in either nvim and/or nvim-treesitter.

TL;DR:

  • TextChanged + TextChangedI sensible enough?
  • Do we need option to set any events?
    • TextChanged + TextChangedI default?
  • Do you still need to be able to use updatetime for this?

So do you think it should be configurable as an option? TextChanged with TextChangedI works really great for practically instant refresh. I've been using it the whole time.

Yes, sure, especially since you have already tested it so extensively!

The only issue with performance I remember having was with a markdown file that that has a large HTML-formatted table inline.

But if I recall correctly, that was more of a treesitter issue than vivify, and I worked around it by setting filetype=txt for that file only.

But! Now that I went to try and reproduce, this file has no issues either way :D

In the meanwhile neovim 0.10 has released and all that, could be just due to gradual improvements in either nvim and/or nvim-treesitter.

Interesting! Changing the filetype however also disabled Vivify's autorefresh, right?

TL;DR:

  • TextChanged + TextChangedI sensible enough?
  • Do we need option to set any events?
    • TextChanged + TextChangedI default?

Good points. I think it's good enough to have an option that switches between using TextChanged(I) and CursorHold. What should be the default is a good question... maybe TextChanged(I) will look better as a default to someone trying it out the first time, while the people who prefer CursorHold are probably more likely to check deeper and find the config option ^^ I.e. I would argue TextChanged(I) is the more sensible default. What do you think?

  • Do you still need to be able to use updatetime for this?

Not sure I understand this point

Interesting! Changing the filetype however also disabled Vivify's autorefresh, right?

Yeah that memory pretty much reveals that it was slow regardless of vivify and the cause was treesitter parsing

Good points. I think it's good enough to have an option that switches between using TextChanged(I) and CursorHold

Ok, lets do this!

Do you still need to be able to use updatetime for this?

Not sure I understand this point

Ah yeah, I was using updatetime as a synonym for CursorHold but didn't remember the whole cause here was that it was tied to this event :D so yes

And regarding default, yes in my opinion TextChanged(I) is perfect

But I do want to test the performance in very large files when we get around to the temporary-files issue

Sounds good! :)