eglot
eglot copied to clipboard
org-edit-special and eglot
Hello, thanks for the eglot, it looks very promising.
Right now, I am trying to configure eglot. I have added to my init.el the following:
(use-package eglot :ensure t :hook ((python-mode . eglot-ensure)))
So that every time i open *.py file, eglot starts automatically. But, i prefer to keep my python small programs in org file.
#+begin_src python
.....
#+end_src.
I edit programs with C-c ' (org-edit-special) which opens the buffer with python-mode and a program. Somehow, the eglot doesnot start in this case. Moreover, when i start eglot manually, eglot asks me: "Start a server to manage buffers of what major mode?". Which is strange, because the major mode of the buffer is the python-mode. Moreover, when i choose the python-mode, eglot starts, but doesnot seem to work at all.
Is it possible to start eglot even if i edit python file through "org-edit-special"?
Which is strange, because the major mode of the buffer is the python-mode.
Is it though? What does C-h v RET major-mode RET
say?
Which is strange, because the major mode of the buffer is the python-mode.
Is it though? What does
C-h v RET major-mode RET
say?
Yes it is the python-mode
major-mode is a variable defined in ‘C source code’. Its value is ‘python-mode’ Original value was fundamental-mode Local in buffer Org Src Review.org[ python ]; global value is fundamental-mode
@ReichKV, this is strange indeed, it would seem org
is violating some central assumption of eglot
. Either the violation must be fixed, or eglot
shouldn't be making that assumption.
I'm going to need a fool-proof reproduction recipe so that I can debug this. What is the smallest .org
file that I need to get this working?
@ReichKV, I just realized that the problem here is probably that the buffer you are trying to use eglot for doesn't have a buffer-file-name
, i.e. it's not a Python file, just a transient bit of python code. LSP is not designed at all to be used under those circunstances.
We could fake it, though, i.e. pretend to LSP that we're editing a one-file project.
I'll investigate some more, but this isn't really an Eglot bug, or org
for that matter.
What is the smallest .org file that I need to get this working? This is the example cat /tmp/1.org:
#+begin_src python print(2+2) #+end_src
I just realized that the problem here is probably that the buffer you are trying to use eglot for doesn't have a buffer-file-name
Yes, indeed. It looks like the problem. I changed the value of buffer-file-name in the "Org Src Review.org[ python ];" and eglot works.
I am also looking for solution for this. Manually changing to buffer-file-name is painful
@joaotavora there is this chuck here that works with lsp-mode, but I would need your help to make it work with eglot :)
What a dreadful piece of Elisp there. But maybe you can substitute lsp-deferred
with eglot-ensure
and it may miraculously work. If it doesn't, someone with Elisp skills and time has to look at it. Maybe transform that naive macro and eval strategy into functions, so that one can understand what's going on.
You may try following config which I found from lsp-mode and modified for eglot:
(defun org-babel-edit-prep:jupyter (babel-info)
(setq-local buffer-file-name (->> babel-info caddr (alist-get :file)))
(setq-local lsp-buffer-uri (->> babel-info caddr (alist-get :file) lsp--path-to-uri))
(eglot-ensure)
)
Also there is a function lsp-org
in lsp-mode which will allow you to enable lsp mode just in org mode code block without entering org-edit-special
.
I paste here, and maybe someone can convert to eglot version:
(defun lsp-org ()
(interactive)
(-if-let ((virtual-buffer &as &plist :workspaces) (-first (-lambda ((&plist :in-range))
(funcall in-range))
lsp--virtual-buffer-connections))
(unless (equal lsp--virtual-buffer virtual-buffer)
(setq lsp--buffer-workspaces workspaces)
(setq lsp--virtual-buffer virtual-buffer)
(setq lsp-buffer-uri nil)
(lsp-mode 1)
(lsp-managed-mode 1)
(lsp-patch-on-change-event))
(save-excursion
(-let* (virtual-buffer
(wcb (lambda (f)
(with-current-buffer (plist-get virtual-buffer :buffer)
(-let* (((&plist :major-mode :buffer-file-name
:goto-buffer :workspaces) virtual-buffer)
(lsp--virtual-buffer virtual-buffer)
(lsp--buffer-workspaces workspaces))
(save-excursion
(funcall goto-buffer)
(funcall f))))))
((&plist :begin :end :post-blank :language) (cl-second (org-element-context)))
((&alist :tangle file-name) (cl-third (org-babel-get-src-block-info 'light)))
(file-name (if file-name
(f-expand file-name)
(user-error "You should specify file name in the src block header.")))
(begin-marker (progn
(goto-char begin)
(forward-line)
(set-marker (make-marker) (point))))
(end-marker (progn
(goto-char end)
(forward-line (1- (- post-blank)))
(set-marker (make-marker) (1+ (point)))))
(buf (current-buffer))
(src-block (buffer-substring-no-properties begin-marker
(1- end-marker)))
(indentation (with-temp-buffer
(insert src-block)
(goto-char (point-min))
(let ((indentation (current-indentation)))
(plist-put lsp--virtual-buffer :indentation indentation)
(org-do-remove-indentation)
(goto-char (point-min))
(- indentation (current-indentation))))))
(add-hook 'post-command-hook #'lsp--virtual-buffer-update-position nil t)
(when (fboundp 'flycheck-add-mode)
(lsp-flycheck-add-mode 'org-mode))
(setq lsp--virtual-buffer
(list
:in-range (lambda (&optional point)
(<= begin-marker (or point (point)) (1- end-marker)))
:goto-buffer (lambda () (goto-char begin-marker))
:buffer-string
(lambda ()
(let ((src-block (buffer-substring-no-properties
begin-marker
(1- end-marker))))
(with-temp-buffer
(insert src-block)
(goto-char (point-min))
(while (not (eobp))
(delete-region (point) (if (> (+ (point) indentation) (point-at-eol))
(point-at-eol)
(+ (point) indentation)))
(forward-line))
(buffer-substring-no-properties (point-min)
(point-max)))))
:buffer buf
:begin begin-marker
:end end-marker
:indentation indentation
:last-point (lambda () (1- end-marker))
:cur-position (lambda ()
(lsp-save-restriction-and-excursion
(list :line (- (lsp--cur-line)
(lsp--cur-line begin-marker))
:character (let ((character (- (point)
(line-beginning-position)
indentation)))
(if (< character 0)
0
character)))))
:line/character->point (-lambda (line character)
(-let [inhibit-field-text-motion t]
(+ indentation
(lsp-save-restriction-and-excursion
(goto-char begin-marker)
(forward-line line)
(-let [line-end (line-end-position)]
(if (> character (- line-end (point)))
line-end
(forward-char character)
(point)))))))
:major-mode (org-src-get-lang-mode language)
:buffer-file-name file-name
:buffer-uri (lsp--path-to-uri file-name)
:with-current-buffer wcb
:buffer-live? (lambda (_) (buffer-live-p buf))
:buffer-name (lambda (_)
(propertize (format "%s(%s:%s)%s"
(buffer-name buf)
begin-marker
end-marker
language)
'face 'italic))
:real->virtual-line (lambda (line)
(+ line (line-number-at-pos begin-marker) -1))
:real->virtual-char (lambda (char) (+ char indentation))
:cleanup (lambda ()
(set-marker begin-marker nil)
(set-marker end-marker nil))))
(setf virtual-buffer lsp--virtual-buffer)
(puthash file-name virtual-buffer lsp--virtual-buffer-mappings)
(push virtual-buffer lsp--virtual-buffer-connections)
;; TODO: tangle only connected sections
(add-hook 'after-save-hook 'org-babel-tangle nil t)
(add-hook 'lsp-after-open-hook #'lsp-patch-on-change-event nil t)
(add-hook 'kill-buffer-hook #'lsp-kill-virtual-buffers nil t)
(setq lsp--buffer-workspaces
(lsp-with-current-buffer virtual-buffer
(lsp)
(plist-put virtual-buffer :workspaces (lsp-workspaces))
(lsp-workspaces)))))))
This was marked not-a-bug
but I have now relabeled it as enhancement
instead.
Thanks to the previous tips in this thread, I was able to get a single python block to work fully with completion, flake8 linting, and black formatting via the pylsp server. Needs some cleanup, but maybe this will help some others.
I added these two functions to my emacs init:
(defun org-babel-edit-prep:python (babel-info)
(setq-local buffer-file-name (expand-file-name (->> babel-info caddr (alist-get :tangle))))
(eglot-ensure)
)
(defun mb/org-babel-edit:python ()
(interactive)
(org-babel-tangle '(4))
(org-edit-special)
)
Here is a sample org subtree I used. By specifying the filename with the :tangle
header arg and using M-x mb/org-babel-edit:python
The function tangles that one src block and then calls org-edit-special
that in turn invokes the prep function that gets the file name used and expands it to the absolute path.
** NEXT test eglot with babel
#+begin_src python :tangle testing_eglot.py :results output replace
import random
def pick_random_number() -> int:
"""test"""
return random.randint(1, 10)
print(pick_random_number())
#+end_src
#+RESULTS:
: 7
Here is my latest iteration where I consolidated to one function that begins execution while in org mode so that I could eventually check for a missing :tangle filename. This might be the motivation to learn elisp rather than paste code fragments found. I only tested with python so far, but it looks generic enough. In this test I tangled to a directory that is not managed by git, so dependencies on project root may require that.
(defun mb/org-babel-edit:python ()
"Edit python src block with lsp support by tangling the block and
then setting the org-edit-special buffer-file-name to the
absolute path. Finally load eglot."
(interactive)
;; org-babel-get-src-block-info returns lang, code_src, and header
;; params; Use nth 2 to get the params and then retrieve the :tangle
;; to get the filename
(setq mb/tangled-file-name (expand-file-name (assoc-default :tangle (nth 2 (org-babel-get-src-block-info)))))
;; tangle the src block at point
(org-babel-tangle '(4))
(org-edit-special)
;; Now we should be in the special edit buffer with python-mode. Set
;; the buffer-file-name to the tangled file so that pylsp and
;; plugins can see an actual file.
(setq-local buffer-file-name mb/tangled-file-name)
(eglot-ensure)
)
Here is my latest iteration where I consolidated to one function that begins execution while in org mode so that I could eventually check for a missing :tangle filename. This might be the motivation to learn elisp rather than paste code fragments found. I only tested with python so far, but it looks generic enough. In this test I tangled to a directory that is not managed by git, so dependencies on project root may require that.
(defun mb/org-babel-edit:python () "Edit python src block with lsp support by tangling the block and then setting the org-edit-special buffer-file-name to the absolute path. Finally load eglot." (interactive) ;; org-babel-get-src-block-info returns lang, code_src, and header ;; params; Use nth 2 to get the params and then retrieve the :tangle ;; to get the filename (setq mb/tangled-file-name (expand-file-name (assoc-default :tangle (nth 2 (org-babel-get-src-block-info))))) ;; tangle the src block at point (org-babel-tangle '(4)) (org-edit-special) ;; Now we should be in the special edit buffer with python-mode. Set ;; the buffer-file-name to the tangled file so that pylsp and ;; plugins can see an actual file. (setq-local buffer-file-name mb/tangled-file-name) (eglot-ensure) )
Did you package it ? I worked with bash.So ,I will hope it's universal for the most part.
Here is my latest iteration where I consolidated to one function that begins execution while in org mode so that I could eventually check for a missing :tangle filename. This might be the motivation to learn elisp rather than paste code fragments found. I only tested with python so far, but it looks generic enough. In this test I tangled to a directory that is not managed by git, so dependencies on project root may require that.
(defun mb/org-babel-edit:python () "Edit python src block with lsp support by tangling the block and then setting the org-edit-special buffer-file-name to the absolute path. Finally load eglot." (interactive) ;; org-babel-get-src-block-info returns lang, code_src, and header ;; params; Use nth 2 to get the params and then retrieve the :tangle ;; to get the filename (setq mb/tangled-file-name (expand-file-name (assoc-default :tangle (nth 2 (org-babel-get-src-block-info))))) ;; tangle the src block at point (org-babel-tangle '(4)) (org-edit-special) ;; Now we should be in the special edit buffer with python-mode. Set ;; the buffer-file-name to the tangled file so that pylsp and ;; plugins can see an actual file. (setq-local buffer-file-name mb/tangled-file-name) (eglot-ensure) )
Did you package it ? I worked with bash.So ,I will hope it's universal for the most part.
I did: https://github.com/Anoncheg1/org-eglot