From a43104c2eb3bba660aa15a945de34a3aa79fcfd5 Mon Sep 17 00:00:00 2001 From: Gerard Vermeulen Date: Fri, 23 Feb 2024 11:17:46 +0100 Subject: [PATCH] Overhaul the code to let Eglot work with Org Babel source blocks --- README.org | 223 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 164 insertions(+), 59 deletions(-) diff --git a/README.org b/README.org index 3b69169..1e63b5f 100644 --- a/README.org +++ b/README.org @@ -4750,11 +4750,14 @@ out of your way. [[info:eglot#Top][Eglot (info)]] is a builtin since Emacs-29.1 listings contribute to a programming language mode independent [[https://github.com/joaotavora/eglot][Eglot]] configuration: 1. Listing [[lst:minimal-eglot-setup][minimal Eglot setup]] adds key bindings to =eglot-mode-keymap=. -2. Listing [[lst:help-setup-org-src-mode-for-eglot]] and +2. Listing [[lst:help-setup-org-src-mode-for-eglot-1]], + [[lst:help-setup-org-src-mode-for-eglot-2]], + [[lst:help-setup-org-src-mode-for-eglot-3]], and [[lst:setup-python-org-src-mode-for-eglot]] try to prepare any =org-src-mode= buffers for use with [[https://github.com/joaotavora/eglot][Eglot]]. They are a refactored implementation of the post [[https://www.reddit.com/r/emacs/comments/w4f4u3/using_rustic_eglot_and_orgbabel_for_literate/][Using rustic, eglot, and org-babel with LSP support in Emacs]] for my use with - Python. + Python. Listing [[lst:help-setup-org-src-mode-for-eglot-1]] is bad practice, + because it signals ~user-errors~. 3. Listing [[lst:eglot-maybe-ensure]] starts [[https://github.com/joaotavora/eglot][Eglot]] in case of proper programming modes and proper directory local variables (meaning in presence of a proper file [[info:emacs#Directory Variables][.dir-locals.el]] in the root directory of any project using proper @@ -4772,94 +4775,196 @@ configuration: (keymap-set eglot-mode-map "C-c r" 'eglot-rename)) #+end_src -#+caption[Help to setup any =org-src-mode= buffers for =eglot=]: -#+caption: Help to setup any =org-src-mode= buffers for =eglot=. -#+name: lst:help-setup-org-src-mode-for-eglot +#+caption[1st help to setup any =org-src-mode= buffers for =eglot=]: +#+caption: 1st help to setup any =org-src-mode= buffers for =eglot=. +#+caption: Bad practice. +#+name: lst:help-setup-org-src-mode-for-eglot-1 #+begin_src emacs-lisp -n :results silent -(with-eval-after-load 'emacs - (defcustom eglot-maybe-ensure-modes '(python-mode) - "Modes where maybe `eglot-ensure' should be or has been called. -This may be in the case of proper directory local variables or in -the case of proper `org-src-mode' buffers.") +;;; Begin: Eglot and Org Babel source blocks - ;; https://www.reddit.com/r/emacs/comments/w4f4u3 - ;; /using_rustic_eglot_and_orgbabel_for_literate/ - (defun eglot-org-babel-edit-prep (info) - "Try to setup an `org-mode-src' buffer to make `eglot-ensure' succeed. +;; https://www.reddit.com/r/emacs/comments/w4f4u3 +;; /using_rustic_eglot_and_orgbabel_for_literate/ +;; +;; One should not signal `user-error's in `org-babel-edit-prep:LANG' +;; functions. +(defun eglot-org-babel-edit-prep-user-error (info) + "Try to setup an `org-mode-src' buffer to make `eglot-ensure' succeed. INFO has a form similar to the return value of `org-babel-get-src-block-info'. Try to load the tangled file into the `org-src-mode' buffer as well as to narrow the region to the Org-mode source block code before calling `eglot-ensure'." - (unless (bound-and-true-p org-src-mode) - (user-error "Buffer %s is no `org-src-mode' buffer" (buffer-name))) - (let ((mark (point)) - (body (nth 1 info)) - (filename (cdr (assq :tangle (nth 2 info))))) - (when (string= filename "no") - (user-error "Org source block has no tangled file")) + (unless (bound-and-true-p org-src-mode) + (user-error "Buffer %s is no `org-src-mode' buffer" (buffer-name))) + (let ((point (point)) + (body (nth 1 info)) + (filename (cdr (assq :tangle (nth 2 info))))) + (when (string= filename "no") + (user-error "Org source block has no tangled file")) + (setq filename (expand-file-name filename)) + (unless (file-readable-p filename) + (user-error "Tangled file %s is not readable" filename)) + (with-temp-buffer + (insert-file-contents filename 'visit nil nil 'replace) + (unless (search-forward body nil 'noerror) + (user-error "Org source block does not occur in tangled file %s" + filename)) + (when (search-forward body nil 'noerror) + (user-error "Org source block occurs twice or more in tangled file %s" + filename))) + (goto-char (point-min)) + (insert-file-contents filename 'visit nil nil 'replace) + (search-forward body) + ;; The next line preserves point in the `org-src-mode' buffer. + (goto-char (+ (match-beginning 0) point -1)) + (narrow-to-region (match-beginning 0) (match-end 0)) + (eglot-ensure))) +#+end_src + +#+caption[2nd help to setup any =org-src-mode= buffers for =eglot=]: +#+caption: 2nd help to setup any =org-src-mode= buffers for =eglot=. +#+caption: Best practice. +#+name: lst:help-setup-org-src-mode-for-eglot-2 +#+begin_src emacs-lisp -n :results silent +;; https://www.reddit.com/r/emacs/comments/w4f4u3 +;; /using_rustic_eglot_and_orgbabel_for_literate/ +(defun eglot-org-babel-edit-prep (info) + "Try to setup an `org-mode-src' buffer to make `eglot-ensure' succeed. +INFO has a form similar to the return value of +`org-babel-get-src-block-info'. Try to load the tangled file +into the `org-src-mode' buffer as well as to narrow the region to +the Org-mode source block code before calling `eglot-ensure'." + (when-let ((ok (bound-and-true-p org-src-mode)) + (point (point)) + (body (nth 1 info)) + (filename (cdr (assq :tangle (nth 2 info))))) + (when (string= filename "no") + (setq ok nil) + (message "Org source block has no tangled file")) + (when ok (setq filename (expand-file-name filename)) (unless (file-readable-p filename) - (user-error "Tangled file %s is not readable" filename)) + (setq ok nil) + (message "Tangled file %s is not readable" filename))) + (when ok (with-temp-buffer (insert-file-contents filename 'visit nil nil 'replace) (unless (search-forward body nil 'noerror) - (user-error "Org source block does not occur in tangled file %s" - filename)) + (setq ok nil) + (message "Org source block does not occur in tangled file %s" + filename)) (when (search-forward body nil 'noerror) - (user-error "Org source block occurs twice or more in tangled file %s" - filename))) + (setq ok nil) + (message + "Org source block occurs twice or more in tangled file %s" + filename)))) + (when ok (goto-char (point-min)) (insert-file-contents filename 'visit nil nil 'replace) (search-forward body) + ;; The next line preserves point in the `org-src-mode' buffer. + (goto-char (+ (match-beginning 0) point -1)) (narrow-to-region (match-beginning 0) (match-end 0)) - (goto-char mark) - (eglot-ensure))) + (eglot-ensure)))) +#+end_src - (defun org-babel-edit-prep:python (info) - (eglot-org-babel-edit-prep info))) +#+caption[3rd help to setup any =org-src-mode= buffers for =eglot=]: +#+caption: 3rd help to setup any =org-src-mode= buffers for =eglot=. +#+caption: Good practice. +#+name: lst:help-setup-org-src-mode-for-eglot-3 +#+begin_src emacs-lisp -n :results silent +;; https://www.reddit.com/r/emacs/comments/w4f4u3 +;; /using_rustic_eglot_and_orgbabel_for_literate/ +(defun eglot-org-babel-edit-prep-if-let (info) + "Try to setup an `org-mode-src' buffer to make `eglot-ensure' succeed. +INFO has a form similar to the return value of +`org-babel-get-src-block-info'. Try to load the tangled file +into the `org-src-mode' buffer as well as to narrow the region to +the Org-mode source block code before calling `eglot-ensure'." + (if-let ((ok (bound-and-true-p org-src-mode)) + (point (point)) + (body (nth 1 info)) + (filename (cdr (assq :tangle (nth 2 info))))) + (progn + (when (string= filename "no") + (setq ok nil) + (message "Org source block has no tangled file")) + (when ok + (setq filename (expand-file-name filename)) + (unless (file-readable-p filename) + (setq ok nil) + (message "Tangled file %s is not readable" filename))) + (when ok + (with-temp-buffer + (insert-file-contents filename 'visit nil nil 'replace) + (unless (search-forward body nil 'noerror) + (setq ok nil) + (message "Org source block does not occur in tangled file %s" + filename)) + (when (search-forward body nil 'noerror) + (setq ok nil) + (message + "Org source block occurs twice or more in tangled file %s" + filename)))) + (when ok + (goto-char (point-min)) + (insert-file-contents filename 'visit nil nil 'replace) + (search-forward body) + ;; The next line preserves point in the `org-src-mode' buffer. + (goto-char (+ (match-beginning 0) point -1)) + (narrow-to-region (match-beginning 0) (match-end 0)) + (eglot-ensure))) + (message "Buffer %s is not `eglot' ready" (buffer-name)))) #+end_src #+caption[Setup Python =org-src-mode= buffers for =eglot=]: #+caption: Setup Python =org-src-mode= buffers for =eglot=. #+name: lst:setup-python-org-src-mode-for-eglot #+begin_src emacs-lisp -n :results silent -(with-eval-after-load 'emacs - ;; https://www.reddit.com/r/emacs/comments/w4f4u3 - ;; /using_rustic_eglot_and_orgbabel_for_literate/ - (defun undo-eglot-org-babel-edit-prep() - "Undo the `eglot' setup by deleting the text hidden by narrowing. -This is to advice `org-edit-src-exit' and `org-edit-src-save'." - (when (and (bound-and-true-p org-src-mode) - (buffer-file-name) - (apply #'derived-mode-p eglot-maybe-ensure-modes)) - (save-excursion - (goto-char (point-min)) - (save-restriction - (widen) - (delete-region (point-min) (point))) - (goto-char (point-max)) - (save-restriction - (widen) - (delete-region (point) (point-max)))))) +;; https://www.reddit.com/r/emacs/comments/w4f4u3 +;; /using_rustic_eglot_and_orgbabel_for_literate/ +(defun org-babel-edit-prep:python (info) + (eglot-org-babel-edit-prep info)) - (advice-add 'org-edit-src-exit :before #'undo-eglot-org-babel-edit-prep) - (advice-add 'org-edit-src-save :before #'undo-eglot-org-babel-edit-prep)) +(defcustom eglot-maybe-ensure-modes '(python-mode) + "Modes where calling `eglot-ensure' may have occurred. +This may be in the case of proper directory local variables or in +the case of proper `org-src-mode' buffers.") + +(defun undo-eglot-org-babel-edit-prep() + "Undo the `eglot' setup by deleting the text hidden by narrowing. +This is to advice `org-edit-src-exit' and `org-edit-src-save'." + (when (and (bound-and-true-p org-src-mode) + (buffer-file-name) + (apply #'derived-mode-p eglot-maybe-ensure-modes)) + (save-excursion + (goto-char (point-min)) + (save-restriction + (widen) + (delete-region (point-min) (point))) + (goto-char (point-max)) + (save-restriction + (widen) + (delete-region (point) (point-max)))))) + +(advice-add 'org-edit-src-exit :before #'undo-eglot-org-babel-edit-prep) +(advice-add 'org-edit-src-save :before #'undo-eglot-org-babel-edit-prep) #+end_src #+caption[Start =eglot= in case of a proper =dir-local-variables-alist=]: #+caption: Start =eglot= in case of a proper =dir-local-variables-alist=. #+name: lst:eglot-maybe-ensure #+begin_src emacs-lisp -n :results silent -(with-eval-after-load 'emacs - (defun eglot-maybe-ensure () - (when (and (apply #'derived-mode-p eglot-maybe-ensure-modes) - (assoc 'eglot-workspace-configuration dir-local-variables-alist)) - (eglot-ensure))) +(defun eglot-maybe-ensure () + (when (and (apply #'derived-mode-p eglot-maybe-ensure-modes) + (assoc 'eglot-workspace-configuration dir-local-variables-alist)) + (eglot-ensure))) - ;; The two hooks `after-change-major-mode-hook' and - ;; `hack-local-variables-hook' are OK, but language mode hooks like - ;; `python-mode-hook' are not. - (add-hook 'after-change-major-mode-hook #'eglot-maybe-ensure)) +;; The two hooks `after-change-major-mode-hook' and +;; `hack-local-variables-hook' are OK, but language mode hooks like +;; `python-mode-hook' are not. +(add-hook 'after-change-major-mode-hook #'eglot-maybe-ensure) + +;;; End: Eglot and Org Babel source blocks #+end_src #+caption[Whiten Black]: