New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lsp-mode getting a bit confused #305
Comments
OK I have looked closely and I believe I understand WHY this is happening: each mode sets up its own ;; Modification hooks are run only in current buffer and not in other (base or
;; indirect) buffers. Thus some actions like flush of ppss cache must be taken
;; care explicitly. We run some safety hooks checks here as well. lsp-mode adds What I don't yet understand is how this could be addressed. Ideally we could say "go ahead and forward these modification updates over to the base buffer's before/after change functions". That's effectively what you are doing with So perhaps adding our own "forwarding" before/after-change functions (for me, in the indirect buffer) that does a (notional): (with-current-buffer (buffer-base-buffer (current-buffer))
(dolist (func before-change-functions)
(run-hook-with-args func beg end ))) and similar for after-change. Thoughts? |
The following seems to be working well: (defun my/lsp-before (&rest r)
(with-current-buffer (buffer-base-buffer)
(apply #'lsp-before-change r)))
(defun my/lsp-after (&rest r)
(with-current-buffer (buffer-base-buffer)
(apply #'lsp-on-change r)))
(defun my/lsp-conduit (type)
(when (eq type 'body)
(add-hook 'before-change-functions #'my/lsp-before t t)
(add-hook 'after-change-functions #'my/lsp-after t t))) with |
@jdtsmith Thanks for figuring this out and sorry for inactivity. I would like to integrate this into polymode proper. Would it be possible for you to share your lsp+python+polymode config so that I could replicate the problem? Is this markdown inside python a standard thing for python? Would it make sense to create a dedicated polymode for it? |
So, if I understand correctly is that inside markdown chunks there are no lsp-on-change hooks. Thus, in order to have a generic solution for this one would need to trigger lsp-on-change from inside inner modes whenever lsp mode in base buffer is detected. |
Thanks for getting back to me. It seems there are a few people working on "lightweight" literate setups with fully syntactically-correct files (wrapped markdown/etc. in block comments or multi-line strings, so in principle any language). In some ways this would compete with org-mode and its source blocks, which have more features, but are harder to get working well with tools that expect to "own" the entire file (linters, language servers, etc.). The lsp-mode folks are trying to get this to work, but it's hard. Tagging @astoff as well in case he is interested (author of code-cells).
That's correct. Maybe it's as simple as documenting "how to forward modification hooks and custom local variables" between inner and outer modes, with some examples. But you might also think about a more generic framework for this. I suspect most people who report that polymode is "brittle" or mysterious probably haven't fully understood they are dealing with two basically independent buffers syncing only content between them, even for counterintuitive things like modification hooks. Here's my working markdown+python(+lsp-mode) polymode, which uses raw multi-line strings to hold markdown. See also this discussion, which has a picture. BTW, one other thing I noticed is that the ;;;;;; polymode: mix modes via indirect buffers
(use-package polymode
:ensure t
:init
(defun my/lsp-before (&rest r)
(with-current-buffer (buffer-base-buffer)
(apply #'lsp-before-change r)))
(defun my/lsp-after (&rest r)
(with-current-buffer (buffer-base-buffer)
(apply #'lsp-on-change r)))
(defun my/lsp-conduit (type)
(when (eq type 'body)
(add-hook 'before-change-functions #'my/lsp-before t t)
(add-hook 'after-change-functions #'my/lsp-after t t)))
:config
(define-hostmode my/poly-python-hostmode)
(define-innermode my/poly-python-markdown-innermode
:mode 'markdown-mode
:name "Markdown Cell"
:head-matcher (rx bol ?# ? (>= 1 ?*) (* nonl) ?\n
"r'''" (* nonl) ?\n)
:tail-matcher (rx bol "'''" (* nonl) ?\n)
:head-mode 'host
:tail-mode 'host
:head-adjust-face `(:background ,(alect-get-color 'dark 'bg+1) :extend t)
:adjust-face nil
:init-functions '(my/lsp-conduit))
(define-polymode my/poly-python-mode
:hostmode 'my/poly-python-hostmode
:innermodes '(my/poly-python-markdown-innermode))
:hook ((python-mode) . my/poly-python-mode)) |
This is very interesting. I would like to get involved a bit more closely. Is there a public repo where you keep all of the stuff? There have been a few requests for python specific polymodes over the years (#153, #180) and I think a poly-python repo as part of this organization is really overdue. So maybe it's a good time to create one right now with all of the commonly used poly-python modes. I can give you owner rights for it if you are interested in participation. I am not a heavy python user (not at all recently), nor even polymode user to be frank, so it would be difficult for me to pull all that by myself. In any case, I see you don't use code-cells at all. I guess code-cells would be primarily useful for ipynb conversion back and forth, right?
This is a good idea. I think that disabling lsp hooks in inner modes might be a good idea, at least till they figure out how to handle chunks in lsp servers.
Interesting. If they went so far with org-mode I bet it shouldn't be hard to do it generically with arbitrary polymode. |
Very glad to hear of your interest. No repo so far, this is just playing around; it was surprisingly easy to get this far. In fact I was only planning a gist or similar on how to "roll your own" simple literate setup which works with lsp-mode and friends (and future friends like tree-sitter), for people who don't like the idea of tangling files and all that. Since I suspect external processes which scan, report & operate in real time on your (full) code file will be even more common going forward, it just seems like a simple and obvious approach (with credit to @astoff and code-cells for inspiration!). I don't see that this type of thing should pertain only to python, since any language which supports block-level comments would work (e.g.
I started with code-cells, hoping to get lsp-mode and multi-markdown working with it. But outshine, polymode, and code-cells all parse and act on the header lines
This is indeed a very useful feature, but it comes with some constraints on the header format (e.g. "percent" format from Spyder, etc.). I would like to be able to write to and read from ipynb notebooks retaining Emacs normal (nested!) outline format (
What other custom polymodes are there? Do they go beyond just "enabling multiple specific modes in a buffer"? I envision cell-multiline command sending (ala, or via code-cells), rich TOC/outline navigation/editing (via outshine), lsp-mode/flycheck, and likely some custom bindings (e.g. to create a new code/markdown cell, to evaluate a cell and move to the next one, etc.). Perhaps a poly-python-markdown mode could be rolled that handles a portion of this? But what I envision does rely on several other package. E.g. some people use eglot instead of lsp-mode, so you'd hate to hard-code those things (and yet they need some real non-obvious config to work in polymodes). I also don't want to duplicate what @astoff is already doing. Maybe a reasonable approach is to start with code-cells, integrate polymode-based support for markdown (or other doc format) blocks, and make room for outline management via outshine. One issue is my inner (markdown) mode's tail-matcher is not robust, since of course you can have multi-line strings in valid python code blocks. I should probably let the tail matcher continue on to match the next header, or the eob, but in testing I wasn't able to get that working (tail and head overlap!). Any thoughts you had would be appreciated.
Maybe this is what you meant, but in fact you must explicitly call these modification hooks as I've done, or the server gets very confused about the state of the file (the full file is sent only once on first open). |
This is of course nice but then other editors won't be able to understand that. Maybe a better option would be to stick with So I would start a support for the default matchers first and then we see. Though, I am not entirely clear how the default matchers work in the jupytext. Do they support both "light" delimiters and percentage delimiters simultaneously?
Yes. This part makes me really exited. There are many languages supported by jupytext but not all have multiline constructs. In the docs they say something that the block matchers can be customized, with one explicit option
My taste would be to have it similarly to how markdown does it but within string delimiters
I think code folding and TOC/outline intergration in polymode needs to be implemented generically. It has been requested here before. Let's start a new issue for this if you don't mind. I am curious what are you thoughts on it.
To my mind lsp-mode is a clear winner by now and it looks like for such a complex tooling people should focus on one thing. Bringing a full support for lsp-mode to polymode is a good starting point IMO. It looks to me that LSP support for markdown proper is not that difficult. So maybe the right approach is to focus on bringing LSP to polymode generically and then have jpynb converted back and forth to markdown. I plan to spend some time with lsp-mode and lsp-org let's see what comes out of it.
This is why a code block of the form |
FWIW, code cells already takes care of outlines. Specifically,
It does that, but as noted above it also provides a bunch of additional features to work with "lightweight notebooks". I think that the ability to understand markdown in the text cells is the only big feature that's missing.
I'm not sure I understand this. Is this about whether markdown cells go inside a multiline string or a comment? Note that some languages don't have multiline strings, and some don't have multiline comments.
I thought exactly the opposite, so we are probably both wrong :-) |
I wonder how much of that functionality is a generic functionality that would apply equally well to an arbitrary multi mode buffer. If so then maybe the most utility can be derived by integrating such features into polymode and making a lightweight jypytext mode with converter on top of it. Remains to be seen.
It could be that extending code-cells might be the best option in the long run. No-one wants to do double (or triple) work. But how can we do that more concretely? I guess it would not make sense to add polymode dependency to code-cells, right? Maybe there could be a check, if polymode is available, kick in the integration. Alternatively, a separate code-cells-poly-mode can be conceived separately. In any case, the code-cells repo seems like the best place for this. WDYT?
Hmm. Interesting. In any case if one is integrated, I don't see why the other could not be done similarly. |
I find nested hierarchy an incredibly useful feature of Jupyter notebooks (which use
and (in an ipynb file):
in both directions.
I've not used it so will defer to @astoff, but it appears you have to select your format.
I mean even languages with an interpreter which are not supported in jupyter would be useful to have this.
I'm not an expert on all the various blended/literate formats, but a requirement for me is "syntactically valid at all times", which limits the markdown-wrapping to either multi-line strings, or block comments (assuming you don't want to go to extra trouble filling/refilling all the line-level comments + adapting markdown-mode to deal with them!).
I mean outshine is just some fancy bindings, navigation, fontification, and conveniences on top of the in-built outline-mode. Both work great, and are the clear choice IMO.
That could be in interesting approach: multiple LSP servers servicing one document. But AFAIK the entire protocol assumes full and complete access to an entire code file.
OK thanks. I think matching:
makes for a perfectly cogent head, just the tail is problematic. Could go with:
but then the final line serves double duty for tail of one and head of the next cell. But yeah "no-tail" (just heads) would be good for this too. |
Yeah python-mode does some shenanigans with outline-regexp, which I disable. I don't want to see for loops etc. in my outline. That was the point of the demotion, right? |
With the most recent polymode you can now do (add-to-list 'polymode-run-these-after-change-functions-in-other-buffers 'lsp-on-change)
(add-to-list 'polymode-run-these-before-change-functions-in-other-buffers 'lsp-before-change) instead of your workarounds. In fact I have a preliminary generic support for LSP in lsp branch. A bit more work is needed but it's almost there. |
Did you figure out the tailless section matching? |
I was confused. There are slots for not allowing nested modes ( |
OK you're right, to be syntactically correct, you can't nest multi-line strings. So it means you have to be careful not to use
Just a limitation of this approach. You could always make the code starter/ender more special, like:
or similar. But that probably doesn't help you, since it won't be syntactically valid for most C-compilers (nested block comments). Can you briefly mention how your LSP support will work? Setup the on-changes? What else? |
It is already working and it's very simple. It constructs document content and document changes by filling blank lines for other modes and actual text for current mode. Thus line numbers are the same across servers and no buffers or extra files are constructed. It's here. Works for python. But then I realized today that some servers (java and clangd) require real files and that's a bit more complicated, but well not much more complicated. I don't really know how people are using java and C++ in multimode environments so it's a bit of a stopper ATM. I think I can merge already, because those servers won't work anyhow, but at least smart servers would work. |
Very interesting approach. That’s also how python.el preserves line numbers in tracebacks. Is it similar to lsp-org’s approach? Does it work reliably with changes that span multiple regions? I guess then you fall back on resending the entire file. So the idea is you don’t need syntactically valid files to keep LSP happy? Not sure about linters/fly-check etc. though. And of course you may have other reasons to do this, like wanting to run the file as a script unmodified. |
It's the simplest approach. No need for indirection, extra buffers or files. For python it works except that linter complains about extra spaces. An edge case for python could be if two inline chunks are on the same line then the "spaced" file might not be syntactically valid, but that's a corner case I am not sure it's worth explicitly dealing with. I merged lsp branch. let's start experimenting! |
It's definitely a very interesting approach! It does seem much simpler than "translating the point to the LSP positions back and forth so the language server thinks that Emacs has opened the original file". I still think the absolute SIMPLEST approach (from the tooling perspective) is to keep files always syntactically valid, but I understand that does require extra attention and care (e.g. don't ruin it by including nested block comments/multi-line strings inside your markdown region!). Even this could be finessed if you do something like:
But honestly that sounds complicated to do reliably. There are also a couple other cases where always-syntactically-valid is desirable:
So it would be nice if there were some help maintaining syntactic validity, if that's your preference. |
Right, but this approach has it's limitations, most notably - no multiple inner modes within a file. So I would prefer to make it work generically and disable polymode lsp support for special cases like yours in the host buffer. |
Why would that be? I could for example: function do_it():
print("Do it")
# * A markdown cell [markdown]
r'''
- This
- list
'''
# ** A code cell
do_it()
# * An ORG-mode cell [org-mode]
r'''
* A Headline
Some text.
'''
# * A perl cell [perl]
r'''
$var = "testing"
$d{$var}++
''' |
Sure, but then don't you want LSP for perl? |
Mind blown! So you want to talk to multiple different LSP servers all from one file! That will be quite the trick. BTW, I was also looking into tree-sitter highlighting; ever tried that? It replaces normal regex-based font-lock highlighting. Not sure if polymode has any way of dealing with many matchers all fighting to highlight the same text. |
That's the current implementation. They all should work because they work independently from different buffers.
Not yet. But I will have a look how to integrate it. Polymode has an abstraction layer over font-lock, so it should be pretty easy I think. |
I recently started trying to use lsp in conjunction with poly-org mode (with inner R code blocks) and am finding that if I cut&paste an org section to eslewhere in the document then I find emacs' insertion-point has been relocated to some odd location, oftento end of (outer) buffer, sometimes to first line of next inner buffer. Before I try and characterize this reproducibly, I looked to see if I should even expect polymode to play nice with lsp and I found this open issue, so I thought I'd ask here what I should expect to work at this point.... I'm using: GNU Emacs 29.0.50 (build 1, x86_64-pc-linux-gnu, X toolkit, cairo Org mode version 9.5.2 (release_9.5.2-9-g7ba24c ) poly-R 20210930.1921 installed Various polymodes for R language |
I'd like to bump this: defmodule<spc> which would then create defmodule do
end (smartparens adds the do/end, which I think is messing up polymode) |
Did you try the approach in this comment? BTW, for people trying to roll their own always-syntactically-valid-literate setup, you might be interested in outli, a lightweight outliner based on outline-mode which includes the styling in the image above. |
I did, and it didn’t help matters. |
Thank you so much, your post helped me solve a different issue I had with Eglot! (joaotavora/eglot#1229) |
I've had success setting up python-mode and markdown-mode together in syntactically valid python files, with file layout like:
It works beautifully for keymaps, font-locking, navigation, etc. (though there are a few markdown commands the "leak" out of the markdown chunks).
I wanted to ask specifically about lsp-mode though. I had hoped that since the file was and remains fully syntactically valid python, lsp-mode would have no issues with it. And in fact that's mostly the case. What I find however is that after some editing, lsp-mode get slightly lost, for example placing fly check errors in the wrong spot in the buffer, a few lines off. This situation persists and requires a reset of the server. Clearly it has "lost sync"; its idea of the source code layout is no longer fully correct. The way lsp works is the full file is sent to the server initially, but after that, only a series of
textDocument/didChange
events, like:get sent. Clearly these lsp-mode generated updates to the server are getting confused by polymode, and stop reflecting the actual structure of the file. Can you hazard a guess as to how that might come to pass?
Thanks for polymode, it's really very powerful.
The text was updated successfully, but these errors were encountered: