eglot icon indicating copy to clipboard operation
eglot copied to clipboard

org-edit-special and eglot

Open ghost opened this issue 6 years ago • 15 comments

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"?

ghost avatar Jan 30 '19 10:01 ghost

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?

joaotavora avatar Jan 30 '19 11:01 joaotavora

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

ghost avatar Jan 30 '19 11:01 ghost

@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?

joaotavora avatar Jan 30 '19 13:01 joaotavora

@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.

joaotavora avatar Jan 30 '19 13:01 joaotavora

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.

ghost avatar Jan 30 '19 16:01 ghost

I am also looking for solution for this. Manually changing to buffer-file-name is painful

OmarAshkar avatar May 12 '21 03:05 OmarAshkar

@joaotavora there is this chuck here that works with lsp-mode, but I would need your help to make it work with eglot :)

OmarAshkar avatar May 12 '21 03:05 OmarAshkar

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.

joaotavora avatar May 12 '21 08:05 joaotavora

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)
  )

test

wowhxj avatar Aug 27 '21 01:08 wowhxj

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)))))))

wowhxj avatar Aug 27 '21 01:08 wowhxj

This was marked not-a-bug but I have now relabeled it as enhancement instead.

skangas avatar Jan 09 '22 13:01 skangas

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

mbarton98 avatar Feb 26 '22 09:02 mbarton98

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)
  )

mbarton98 avatar Feb 27 '22 01:02 mbarton98

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.

rexackermann avatar May 24 '23 18:05 rexackermann

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

Anoncheg1 avatar Jul 08 '24 06:07 Anoncheg1