vsch / idea-multimarkdown

Markdown language support for IntelliJ IDEA.
https://plugins.jetbrains.com/plugin/7896-markdown-navigator
Apache License 2.0
815 stars 127 forks source link

Smooth scrolling for markdown preview on OS-X? #488

Closed lihaoyi closed 4 years ago

lihaoyi commented 7 years ago

At least for me, the left-hand HTML (non-Swing) rendered-markdown preview doesn't scroll well with my macbook's trackpad. Each "tick" scrolls a large amount, and it often vibrates back and forth. "Up" and "Down" arrows do not work on the preview, since the cursor always seems captured by the text editor

Currently, my most reliable way of scrolling through the markdown preview is placing my cursor in the text editor and using "Up" or "Down" there, or using my house, clicking on the scroll-bar on the right, and manually dragging it down bit by bit. Neither is a great solution.

I noticed the GFM plugin (https://github.com/ShyykoSerhiy/gfm-plugin) seems to have pretty smooth scrolling for it's Markdown previews. I'm not sure what they do differently, but any chance that their solution to the scrolling problems could be ported over to this plugin?

EDIT: FWIW the JavaFX webview also periodically results in tearing in the rendered output, both in the GFM plugin and idea-multimarkdown:

screenshot 2017-08-22 23 51 56

I can't make it happen reliably, but when scrolling around it happens pretty often, maybe once every minute or two

lihaoyi commented 7 years ago

Notably, disabling Add page header as is suggested in https://github.com/vsch/idea-multimarkdown/issues/184 does not seem to improve the experience for me. Nor does the #readme .markdown-body {border-radius: 0;} CSS snippet

Both JavaFX HTML Generator and Default Swing HTML Generator both seem to suffer from this jerky/vibraty scrolling

Note that this is about scrolling performance. I am not making any edits to the document, just trying to scroll with my trackpad.

vsch commented 7 years ago

@lihaoyi, GFM uses JxBrowser which is Chromium as an option so it is running native code browser. Markdown Navigator uses JavaFX WebView which is more limited.

Since both Swing and JavaFX suffer from this it would be related to rendering through Java but how is the scrolling performance in the main editor? It would be doing the same as at least Swing browser?

Can please you try switching the GFM preview to JavaFX to see if it also exhibits jerkiness on scrolling?

lihaoyi commented 7 years ago

The scrolling performance in the main editor works great, as does the "Default Swing" preview. What I mentioned above as not-working-great was the "JavaFX Webview" preview with "Default Swing HTML Generator" set.

Setting the GFM plugin to use JavaFX exhibits the same jerky/vibraty scrolling behavior as idea-multimarkdown.

Notably, the GFM plugin has "replace markdown plugin preview tab", but it doesn't seem to work with idea-multimarkdown. Maybe it's only meant to be used with the default markdown support plugin?

lihaoyi commented 7 years ago

Since the Swing preview works fine, I wouldn't mind just using the Swing preview all the time either, but it's lacking a few features like syncing the scroll-positions of the preview and the source code, or re-styling it with CSS.

Having to constantly manually manage the scroll-positions of two panes kills any benefit of having the right-pane not scroll jankily =P

vsch commented 7 years ago

The GFM replace tab works with tabbed editors. The plugin has a split editor so GFM simply adds its own tab.

It seems to be a JavaFX performance issue. I will see if there is anything to be done on my side or it is simply a resource limitation.

lihaoyi commented 7 years ago

One "good enough" solution for me I think would be implementing the "sync scrolling between preview and source-code pane" functionality in the Swing-preview. That would let me avoid the HTML preview altogether.

(Ideally I'd be able to re-CSS the swing preview to be a bit prettier, but that's less important)

vsch commented 7 years ago

@lihaoyi, the swing preview is a pretty as I could get it. Swing browser is very limited and ignores a lot of attributes and CSS settings.

I got started on this plugin because I thought I could change the CSS for the idea-markdown plugin that I wound up forking and then rewriting. So trying to get Swing to generate a better preview is a futile task.

Same for synchronization. Swing does not have JavaScript and limited element on screen information. I fiddled with trying to get synchronization working with it for a while and gave up.

lihaoyi commented 7 years ago

Sure, I don't mind if you don't think the Swing rendering can get much prettier. That's a low priority for me.

I also don't need the fancy line highlighting, select-highlighting or click-scroll-synchronization behavior that's currently present in the HTML preview; I just want the scroll positions to line up as you scroll up and down the source view or Swing preview

Doesn't Swing allow you to install scroll handlers, and query for the X/Y position of elements? That should (?) be all you need to implement scroll synchronization. You shouldn't need Javascript or the HTML DOM: after all, you have the Swing DOM available, with full access from Java code.

One plausible way I could imagine approaching this is to add "invisible" marker elements to the start of each line/paragraph/block of markdown. Then, when you render these elements to the Swing pane, you can ask Swing for their .getY() positions and end up with a rough mapping of line/paragraph/block <-> Y-coordinate of the invisible swing marker element. From there you can interpolate the in-betweens to figure out, given an arbitrary scroll position on one side (source or preview), what should be the corresponding (approximate) scroll position on the other side.

After that, it's a matter of attaching the scroll handlers to the source/preview panes, and when one pane changes programmatically setting the scroll on the other pane.

Would that approach work? I haven't done Swing for a few years, and don't know if being inside IntelliJ imposes any special limitations, but it seems like this approach should keep the two panes in sync should be doable without too much difficulty.

Anyway, I think this is a great plugin. It's just a pity if half the functionality - the fast, instant preview - doesn't work, especially given the effort you've clearly put into making the preview great. I don't think my setup is anything special (vanilla macbook pro with vanilla IntelliJ, default plugins + Scala/Python + yours) so I'm sure others bump into the same issues.

vjpr commented 6 years ago

It seems to be a JavaFX performance issue.

For me, it does not seem like a performance issue. Its just with the Macbook trackpad. If I scroll to the bottom of a file and scroll down, it jumps up repeatedly. Its the handling of the scroll events or something.

Is this part of the code public?

vjpr commented 6 years ago

@vsch So I took a look at the code, and there looks like a few places where the browser is being scrolled using javascript.

That onScroll method looks suspicious...seems like it could cause the jerkiness. Could you take a look?

https://github.com/vsch/idea-multimarkdown/blob/b202c3b61eb505f6debdff365d13f792fa446461/src/main/java/com/vladsch/idea/multimarkdown/editor/MultiMarkdownFxPreviewEditor.java

    // call backs from JavaScript will be handled by the bridge
    public static class JSBridge {
        final MultiMarkdownFxPreviewEditor editor;

        public JSBridge(MultiMarkdownFxPreviewEditor editor) {
            this.editor = editor;
        }

        public void log(String msg) {
            //logger.info("[" + editor.instance + "] " + msg);
        }

        public void repaint() {
            //logger.info("[" + editor.instance + "] " + "before repaint");
            //jEditorPane.invalidate();
            if (editor.project.isDisposed()) return;

            editor.jfxPanel.repaint();
            //logger.info("[" + editor.instance + "] " + "after repaint");
        }

        public void onScroll() {
            if (editor.project.isDisposed()) return;

            JSObject scrollPos = (JSObject) editor.webEngine.executeScript("({ x: window.pageXOffset, y: window.pageYOffset })");
            //logger.info("[" + editor.instance + "] " + "window scrolled to " + scrollPos.getMember("x") + ", " + scrollPos.getMember("y"));
            editor.scrollOffset = "window.scroll(" + scrollPos.getMember("x") + ", " + scrollPos.getMember("y") + ")";
        }
    }
vjpr commented 6 years ago

Also came across this project for using Chromium in Java apps. Maybe worth a look -> https://bitbucket.org/chromiumembedded/java-cef.

vsch commented 6 years ago

@vsch So I took a look at the code, and there looks like a few places where the browser is being scrolled using javascript.

That onScroll method looks suspicious...seems like it could cause the jerkiness. Could you take a look?

@vjpr, if the solution was this easy, I would not have to spend many days trying to optimize the code and look for bottlenecks.

First lets address the scroll issue. It manifests itself on systems that have this problem even when mouse scrolling is used. In which case the onScroll method stores the resulting top offset into the preview. This means that the jumpy scrolling is not driven by the plugin.

The preview needs to store the scroll offset so it can display the page using the same offset when the page is refreshed. Which occurs every time the document is modified.

BTW, users who had this issue have tested their system with Markdown Support from JetBrains and told me they experience the same issue. Although, my plugin started with the JetBrains version of JavaFX browser, the two have diverged completely in their implementation beyond just the basic JavaFX WebView usage. This confirmed for me that the issue is not on the plugin side but in the JavaFX to native hardware implementation.

Additionally, you need to keep in mind that some elements in the HTML will change their size, like remote images when the final image is downloaded or highlighting is applied to an element. This will cause the vertical view size of the preview to change, causing the preview to first display the page based on initial size then jump again as the final size is determined. This can happen for many remote images in the page. There is no general solution to this problem. If the vertical offset is not adjusted early in the page loading process, then you will see the top of the page flashing. The latter would be more annoying because it will exhibit itself with every page load for every user.

If you are willing to live with the document always displaying from the top every time you edit the text then you can disable the Scroll preview to source position and all the scrolling will not be used so you will see the performance of JavaFX scrolling on your system as close to its capabilities as possible.

Screenshot_Settings_PreviewSourceSynchronization

As for the Chromium browser, it will address all performance issues because it uses native code. However, the down side is that it is a licensed product and the sales volume of the plugin does not justify the expense. It is free for OSS projects but otherwise it is a paid library.

Second, it will push the distributable size of the plugin to 250+ MB which I find to be ridiculous for a markdown plugin.

You can get the functionality by using the gfm plugin which uses chromium as the browser and GitHub markdown to HTML rendering. It adds a preview tab to markdown documents without adding any Markdown editing features so it does not conflict with any Markdown language features.

vjpr commented 6 years ago

It manifests itself on systems that have this problem even when mouse scrolling is used.

When I use mouse scroll set to 3 lines at a time I never see the issue. Using Microsoft IntelliMouse Explorer 3.0.

Still reading your post...

vsch commented 6 years ago

@vjpr, can you please try the same with Markdown Support just to see if it is a JavaFX issue or plugin issue.

vjpr commented 6 years ago

@vsch

can you please try the same with Markdown Support

Okay I see that same behaviour. I guess JavaFX webview is broken.

You can get the functionality by using the gfm plugin

Awesome! This works perfectly. Now if only there could be a side-by-side view with the GFM plugin in mulitmarkdown. Is this possible?

vjpr commented 6 years ago

I added a scroll-hijack to the js -> http://manos.malihu.gr/jquery-custom-content-scroller/

This helps prevent the tearing during scroll. It doesn't feel good, and it doesn't fix the wierd bouncing behaviour at the bottom.

Seems like the JavaFX WebView is very broken with the Mac touchpad.

vjpr commented 6 years ago

Okay scroll hijack was a bad solution.

The gfm plugin works perfectly. It would be cool if it were supported by markdown-navigator. Maybe you could have a separate plugin just for the JxBrowser because of its size. Link to gfm issue regarding size: https://github.com/ShyykoSerhiy/gfm-plugin/issues/32

For now I am just switching back and forth between markdown-navigator and gfm and it works well. Also means https://github.com/vsch/idea-multimarkdown/issues/511 is not bothering me anymore.

vjpr commented 6 years ago

@vsch Regarding using gfm and markdown-navigator together, is there a way to make Gfm Preview the default multi-editor tab that is activated when opening a markdown file?

Does markdown-navigator interfere with this in any way?

vsch commented 6 years ago

@vjpr, the IDE opens the file with the main tab activated.

vjpr commented 6 years ago

@vsch Who decides what is the main tab?

vsch commented 6 years ago

@vjpr, the IDE takes the first editor tab as the main tab, if memory serves correctly.