dgutov / mmm-mode

New official home for mmm-mode, fixed for Emacs >= 23
http://mmm-mode.sourceforge.net/
GNU General Public License v2.0
334 stars 32 forks source link

Indentation and fontlocking in javascript blocks #143

Open sprig opened 3 months ago

sprig commented 3 months ago

Hello!

Thanks for your work on this package!

I've been trying to set up this mode for use inside julia-mode and julia-ts-mode. It seems to work well for inline html and markdown (html"""<>""" and md"""<>""") including indentation and font locking, but breaks down for javascript. As for font-locking, it works when I set the submode to javascript-mode, but not when I set it to js-mode which the former is an alias for, nor if I set it to use js2-mode. Both of the latter work fine on plain .js files.

Indentation is more quirky and I haven't been able to make it work at all for javascript - in md and html blocks it works fine, but in js blocks it seems to revert to julia's indentation function - pressing tab results in a [No matches] message unless tab-always-indent has been set to t or nil, whereas tab seems to do nothing (since julia does not want to indent that line).

Furthermore, the same happens in julia-ts-mode. In that case I get nice fontlocking inside of <script language="Javascript"> tags inside of html""" - but indentation seems to revert to the indentation of the html block (ie all code is indented one indent further than the script tag itself, but not further, eg inside a function). In that case I also notice that emacs is lagging, eg scrolling is much slower than when mmm-mode is turned off. I also get promptw regarding a missing linter and a missing lsp server which lets ignore for now.

emacs 29.2 from emacsforosx.com on macos 14.6 aarch64 with mmm-mode 20240222 from melpa, julia-mode 20240506.1205 from melap, julia-ts-mode 20230921.1433 from melpa, built-in js-ts-mode, js2-mode 20240418.6, and built-in js-mode.

[EDIT] sample code:

md"""
## Hello

world
"""

js"""
console.log("hello");
function x() {

}
let y=1;
"""

html"""
<script language="Javascript">
  let x = 1;
  function y() {
  return true;
  }
</script>
"""

configuration:

  (defun my/mmm-julia-auto-class (lang &optional submode)
    "Define a mmm-mode class for LANG in `markdown-mode' using SUBMODE.
  If SUBMODE is not provided, use `LANG-mode' by default."
    (let ((class (intern (concat "julia-" lang)))
          (submode (or submode (intern (concat lang "-mode"))))
          (front (concat lang "\"\"\""))
          (back "^\"\"\""))
      (mmm-add-classes (list (list class :submode submode :front front :back back)))
      (mmm-add-mode-ext-class 'julia-mode nil class)
      (mmm-add-mode-ext-class 'julia-ts-mode nil class)))
  (my/mmm-julia-auto-class "md" 'markdown-mode)
  (my/mmm-julia-auto-class "js" 'javascript-mode)
  (my/mmm-julia-auto-class "html" 'html-mode)

[EDIT 2]: Interestingly, but not entirely related, I notice that vscode fontifies the js block but not the html or markdown blocks.

dgutov commented 3 months ago

Hi!

If you're familiar with edebug, I suggest you instrument mmm-indent-line and see which indentation function is calls when you press TAB.

On the surface of it, without your submode structure (multiline blocks) it should be dispatched fine, but the indentation function itself could get confused by the surrounding syntax. Anyway, the first step is to check which function gets called.

P.S. js2-mode is not adapted for multi-mode context, so it's better to use the other option. Not sure why js-mode is not working for you, but if javascript-mode works okay in that setup, this seems a secondary issue.

sprig commented 3 months ago

Thanks for your reply!

Regarding mmm-indent-line, I actually tried that prior to writing. I don't remember the prior results but with tab-always-indent set to t, it looks like the correct indentation function in dispatched - js-indent-line in the js block and sgml-indent-line in the html block. In both cases copying the block to a dedicated buffer works in terms of indentation. As far as fontification goes, the mmm blocks actually get better syntax highlighting! :-)

A couple of further observations;

  1. in larger julia buffers julia-ts-mode+mmm-mode make emacs consume 100% cpu and generally be very slow such as to be unusable.
  2. I noticed that if I enable jit-lock-debug-mode, emacs becomes much more responsive. I'm not really familiar with this mode, was just experimenting, so I'm not sure what it changes. I haven't seen any other output from emacs due to enabling this mode.
dgutov commented 3 months ago

If the correct function is called but js-indent-line is still confused by the surrounding code, you could try this:

(setq mmm-indent-line-function #'mmm-indent-line-narrowed)

It uses a simple approach (narrowing) which often works well enough, but not in all cases.

There's also an option of writing some custom indentation code (there is example inside mmm-erb.el) - but it's an order of a magnitude more work. Let me know if you want pointers, though.

As far as fontification goes, the mmm blocks actually get better syntax highlighting! :-)

Sorry, better highlighting than some other editor, or...?

in larger julia buffers julia-ts-mode+mmm-mode make emacs consume 100% cpu and generally be very slow such as to be unusable

There is a separate report here (https://github.com/dgutov/mmm-mode/issues/142#issuecomment-2115315879) which features a usable alternative setup using treesit.el's own functionality. It should be faster than mmm-mode in those cases, though it might support a different subset of features overall.

I noticed that if I enable jit-lock-debug-mode, emacs becomes much more responsive. I'm not really familiar with this mode, was just experimenting, so I'm not sure what it changes. I haven't seen any other output from emacs due to enabling this mode.

That's interesting. With debug mode, do you see all the same highlighting, but faster?

sprig commented 3 months ago

There's also an option of writing some custom indentation code (there is example inside mmm-erb.el) - but it's an order of a magnitude more work. Let me know if you want pointers, though.

I would prefer to avoid custom indentation code, mainly because of the maintenance burden. I think I'll try integrating directly with treesitter as in your suggestion below, and if it turns out to be difficult I'll revisit the narrowed approach.

As far as fontification goes, the mmm blocks actually get better syntax highlighting! :-)

Sorry, better highlighting than some other editor, or...?

Well, that too - other editors don't usually support multi-mode highlighting. However, it seemed that I get better syntax highlighting inside of the mmm block as compared with copying the block to a temporary buffer and setting the mode there. e.g. in an html block that also contained script or style tags. I had those highlighted as well. Likely mmm-mode worked recursively inside those blocks, whereas I did not setup mmm-mode for dedicated html buffers.

There is a separate report here (#142 (comment)) which features a usable alternative setup using treesit.el's own functionality. It should be faster than mmm-mode in those cases, though it might support a different subset of features overall.

Thanks! I've actually seen that but haven't gotten around to setting it up yet. I guess this is the way forward...

I noticed that if I enable jit-lock-debug-mode, emacs becomes much more responsive. I'm not really familiar with this mode, was just experimenting, so I'm not sure what it changes. I haven't seen any other output from emacs due to enabling this mode.

That's interesting. With debug mode, do you see all the same highlighting, but faster?

Correct. I also don't see emacs CPU usage spiking to 100% like I do when debug is off.

dgutov commented 2 months ago

Likely mmm-mode worked recursively inside those blocks, whereas I did not setup mmm-mode for dedicated html buffers.

Yup, that happens.

Correct. I also don't see emacs CPU usage spiking to 100% like I do when debug is off.

Thanks, this sounds worth investigating. Could be a bug in jit-lock (which leads to the performance drop), or a bug in the debug mode (which disables something important, improving the performance by doing less work :sweat_smile:).