From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp11.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id iKIUEQYGQWO9NwAAbAwnHQ (envelope-from ) for ; Sat, 08 Oct 2022 07:09:26 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp11.migadu.com with LMTPS id qKggEQYGQWO8SAAA9RJhRA (envelope-from ) for ; Sat, 08 Oct 2022 07:09:26 +0200 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id C964E31CF2 for ; Sat, 8 Oct 2022 07:09:25 +0200 (CEST) Received: from localhost ([::1]:41456 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1oh25U-0006w4-FN for larch@yhetil.org; Sat, 08 Oct 2022 01:09:24 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:48708) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oh255-0006vv-2h for emacs-orgmode@gnu.org; Sat, 08 Oct 2022 01:08:59 -0400 Received: from mail-pl1-x62c.google.com ([2607:f8b0:4864:20::62c]:43573) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1oh252-00016Z-6x for emacs-orgmode@gnu.org; Sat, 08 Oct 2022 01:08:58 -0400 Received: by mail-pl1-x62c.google.com with SMTP id z20so6192713plb.10 for ; Fri, 07 Oct 2022 22:08:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=mime-version:message-id:date:subject:to:from:from:to:cc:subject :date:message-id:reply-to; bh=0RG02lGs5QhAlf00xDGKx6VsQYkjGbsAj0+i7Drln0k=; b=mtaOts4tyhcU4sj7ow1oOzUS/hRKKbjRo9aZV4tyEdOz9WXuEx2C4Ds8lO2YCR1pbE HDo8myttSeQjymRx34SrKK1qPFQB/diHdXO9qnR1PxjfYzAunKr5kIruQWalHpHg+oi0 H7yUqbSHprCs0m8HOCjTM6nmTn/j61XVodytKjNJlIMYJ8nrQBEtvVgKtAaj0cJJCKQ8 3fH258crQCvoUk18+fmfStbvErsUHZfkSa+sKfFO8HBY6MTWrczag53nRSRkjXXWkAy2 wHj5BiYYzjFPEEdbbm9ZOchrpRvXpCKGpcENVKWH9AI11/qcxFWAjwlTFKJn4uc4cCqf qEEg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=mime-version:message-id:date:subject:to:from:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=0RG02lGs5QhAlf00xDGKx6VsQYkjGbsAj0+i7Drln0k=; b=ktrHbs4D9ijWpqviPa+dpWOmJ2WVlmgxzprDSW1No47qEJXVyEmiqRstghaiHJzsik i5noFxO/GvnayfcGnp6ev9rxogXOfW4k+FLpuNHyLRLn3OKMeI97KX0hGIsNDzclvny2 LvCbVpgllr7bByoDmo/VGjRwevYlHasASf5I+vpUNaE5wjnF6A+gitKwJlqiKpQiQum1 0qgkCyFUuLEBJK/YTEiIEAOJfgUPxIGawAUvMfUKC0AkLrZdUddaqTsMhdm3aQZM0wDM MjnwQRnTxSElWG61yNskW1q0K1AKR5hVbloHesjusVmKOCqbT9LzYskYw6THwfSebOCo n89g== X-Gm-Message-State: ACrzQf3EX2+c/rnOOIAHUa4h88jLpVkqTeTFNx1ClzMmhoQsYYAnbp5L tbDI31viCt/07psSB8Oobx+SWp9j51dAsg== X-Google-Smtp-Source: AMsMyM4aeV65ZtsXQYpoTOwdB7+K+Q8VtqECcpaDqzJV8PP1lZ+eaxJyuyKnWbXDKEdsx3z0M/EDdA== X-Received: by 2002:a17:90b:3b47:b0:202:a81f:4059 with SMTP id ot7-20020a17090b3b4700b00202a81f4059mr20347640pjb.150.1665205733913; Fri, 07 Oct 2022 22:08:53 -0700 (PDT) Received: from localhost (ip184-189-240-75.sb.sd.cox.net. [184.189.240.75]) by smtp.gmail.com with ESMTPSA id a1-20020aa78e81000000b00540e1117c98sm2531152pfr.122.2022.10.07.22.08.52 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 07 Oct 2022 22:08:52 -0700 (PDT) From: Karthik Chikmagalur To: emacs-orgmode@gnu.org Subject: [PATCH] LSP support in org-src buffers Date: Fri, 07 Oct 2022 22:08:52 -0700 Message-ID: <87bkqmdhqz.fsf@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::62c; envelope-from=karthikchikmagalur@gmail.com; helo=mail-pl1-x62c.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: "Emacs-orgmode" X-Migadu-Flow: FLOW_IN X-Migadu-Country: US ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1665205766; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=0RG02lGs5QhAlf00xDGKx6VsQYkjGbsAj0+i7Drln0k=; b=tYXdjyV16Gzp2dFs5F3QL7cAeyqhk7IoJy0cYrk0j+ubDDNE8YY2gVUSih/zuhQk/s/z4S PfAK1kYQKXXw95kBYbwP8Tuz/R4Iw2oo7ZZ/hZZ6AhOUuVNdeUOfofogHx4qUE5lOaX6jW LifOQpI7YBmkpdcCJHrygLbIN0L/HrSUBy4l7mKBADyoMxJZaw/azanUoeuOdyuhPW0SCp k/On8j+FPbEdH0A2QvO3T75KEIAkM5RfEWEj32D73kjFM31cmbTGrMxJ0pORfSh1rj8atA 5u4KkwK2FGAAx37TzK3puzEuh2AkfiM7+xtBVgO+4TrtLLq+IjiPDUHQW1T87A== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1665205766; a=rsa-sha256; cv=none; b=O9TCOl/bKo3diFSTi1DBP0YEhbuiLYSgzTwQFaLD0vRK47g5+63XYh1mkFpeU62ZW+gqcw yz1m3s1AZa3Kd8Mat/Q/1nO1j8FLLWt7e/moBx5JOM9iAVHUNNFaUINKWGtSny3JsZufVM Y3VsrZN090CAUgp7R3w6/Qg0o0Wd07slwOk46UVZUG2hnD3+DRQ6phgBTtO5VVCnJABL5W WpVn2/uRrFc7ymTDPSWrH/hKn9jHTyp+1U1PARfjaunz06xB6r9w2MVRYxUfq+urRE3e8L 54lrfuICIZyKzmLLyGGWWTLbt6kl920rXaObmyZrUt+ifnNS8w2KMvmrOBDYEA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=pass header.d=gmail.com header.s=20210112 header.b=mtaOts4t; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Migadu-Spam-Score: -3.88 Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=gmail.com header.s=20210112 header.b=mtaOts4t; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Migadu-Queue-Id: C964E31CF2 X-Spam-Score: -3.88 X-Migadu-Scanner: scn1.migadu.com X-TUID: KAz4QvuaNvxy --=-=-= Content-Type: text/plain Hi folks, I've added limited support for LSP via Eglot in org-src-mode buffers. I was intending to publish it as a package but it was suggested to me that it could live as part of Org instead, especially now that Eglot is intended to be part of the upcoming Emacs release. Here are some details: I. What it does. II. How to use it. III. How it works. IV. Limitations and concerns. I. What it does =============== It allows you to run Eglot in org-src buffers opened with org-edit-special (C-c '), giving you context-aware completion, linting, code actions etc. II. How to use it ================ (i) Turn on org-src-context-mode. (ii) Add :tangle header args to code blocks that you intend the Language Server (LS) to parse sequentially, as one file. To parse all code blocks in the Org document using the LS as one file, you can set a global ":tangle yes" header argument. (You don't have to tangle anything -- this is to identify which code blocks constitute a "file".) (iii) Use org-edit-special on a code block. (iv) Run eglot or eglot-connect in the org-src-mode buffer. >From this point on, you can use org-edit-special and org-edit-src-exit (both bound by default to C-c ') as usual, and you should have LSP support via Eglot. This LSP connection is persistent across this Emacs session, you can exit the org-src buffer or kill the Org buffer, and come back to it at will. (v) You can shutdown the LSP connection with eglot-shutdown, as usual. III. How it works ================ The main problem with reconciling org-src-mode and Language Server (LS) support is that the LSP requires and expects files, not buffers. By default, org-src buffers are not associated with files. Even if one were to associate an org-src-mode buffer with a file, set the correct default-directory for a project and start Eglot, it would only contain the small chunk of code from the current code block. The LS cannot access enough code to form a useful picture of the project. org-src-context-mode reuses the tangling machinery to populate an org-src buffer with code from all blocks associated with the current tangle file, and associates it with a temporary file. This way, the LS has a better picture -- if still limited to a single file -- of the project. org-src-context-mode then checks if there's an appropriate Eglot LSP connection active, and reconnects to it. Only the contents of the current code block are editable. The other blocks are marked read-only and (by default) only visible by widening the buffer. No actual tangling is done -- the default-directory of the org file is not touched at all. If there's no :tangle header arg, org-src-context-mode does nothing. IV. Limitations and concerns ============================= (i) This creates temporary files with (ostensibly) the contents of code blocks in the Org file. (ii) org-src-context-mode is implemented by advising org-edit-src-code and org-edit-src-exit. This is because it was originally intended to be a third-party package. These functions will need to be modified a bit otherwise. (iii) I'm assuming this design will go through revisions, so I haven't updated the Org manual yet. (I did update the changelog.) (iv) It is quite straightforward to add lsp-mode support with a user-option. (The LSP-specific part of this package is tiny.) Since lsp-mode is not part of Emacs and Eglot will be soon, I decided to focus on Eglot support. (v) org-src-context-mode does some pseudo-tangling -- this is required to specify what constitutes a "file" for the LS to parse. This adds a performance penalty to org-edit-src-code that can be noticeable if you have many (100+?) code blocks with the same tangle file as the current block. (vi) Consider this scenario: The code for your entire project resides in one or more Org files, and is intended to be tangled to several files under a project root directory. Then the nature of LSP support depends on the state of tangling. - Before tangling anything: LSP support with org-src-context-mode remains limited since it can only "see" one file, the one being edited. - Post-tangling the entire project: You have full and veracious LSP support in all org-src buffers. - Post-tangling and after edits to multiple code blocks: LSP support is now *incorrect* since it "sees" a combination of the current state of the "file" being edited in the org-src buffer, and the past state of tangled versions of other code blocks. Still, I've found this to be a big improvement over having no LSP support for Org code blocks. Please let me know if you have any feedback. Karthik --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-Add-org-src-context.el.patch >From 2798a292d293f1d0aeed34bd0014c6bb97079491 Mon Sep 17 00:00:00 2001 From: Karthik Chikmagalur Date: Fri, 7 Oct 2022 21:14:42 -0700 Subject: [PATCH] Add org-src-context.el --- etc/ORG-NEWS | 27 ++++++ lisp/org-src-context.el | 186 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 lisp/org-src-context.el diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 34ec099..97f28c3 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -314,6 +314,15 @@ This provides a proper counterpart to ~org-babel-pre-tangle-hook~, as per-tangle-destination. ~org-babel-tangle-finished-hook~ is just run once after the post tangle hooks. +*** Support for Eglot in =org-src-mode= buffers via new minor-mode =org-src-context-mode= + +Turning on =org-src-context-mode= will allow connecting to LSP servers +using Eglot from =org-src= buffers. Enabling this requires setting +the =:tangle= header argument on the code block being edited with +=org-edit-special=, as well as other code blocks that are intended to +be part of the same file. The =:tangle= headers indicate which code +blocks are visible to the Language Server, no actual tangling is +carried out by =org-src-context-mode=. ** New options *** New custom settings =org-icalendar-scheduled-summary-prefix= and =org-icalendar-deadline-summary-prefix= @@ -350,6 +359,13 @@ The folding state can also be controlled on per-file basis using The new setting, when set to non-nil, makes Org create alarm at the event time when the alarm time is set to 0. The default value is nil -- do not create alarms at the event time. +*** New custom setting ~org-src-context-narrow-p~ + +This setting applies when =org-src-context-mode= is turned on. When +set to nil, Org will display all the code blocks corresponding to the +=:tangle= header argument of the code block currently being edited in +=org-src-mode=. Only the contents of the current code block are +editable, the rest of the buffer is marked read-only. ** New functions and changes in function arguments *** ~org-fold-show-entry~ does not fold drawers by default anymore @@ -418,6 +434,17 @@ Previously, executing PlantUML src blocks always exported to a file. Now, if :results is set to a value which does not include "file", no file will be exported and an ASCII graph will be inserted below the src block. +*** New function ~org-src-context--connect-maybe~ + +This function prepares =org-src-mode= buffers for LSP connections via +Eglot. + +*** New function ~org-src-context--lsp-connect~ + +This function connects to an LSP server managing the current +=org-src-mode= buffer using Eglot if one is found. This is intended +for use with =org-src-context-mode=. + ** Removed or renamed functions and variables *** =org-plantump-executable-args= is renamed and applies to jar as well diff --git a/lisp/org-src-context.el b/lisp/org-src-context.el new file mode 100644 index 0000000..1c5c358 --- /dev/null +++ b/lisp/org-src-context.el @@ -0,0 +1,186 @@ +;;; org-src-context.el --- LSP support for org-src buffers -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author: Karthik Chikmagalur +;; Keywords: tools, languages, extensions + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This file contains the code dealing with Language Server Protocol support via +;; other packages in Org Source buffers. + +;;; Code: + +(require 'org) +(require 'ob) +(require 'ob-tangle) +(require 'org-src) +(require 'cl-lib) + +(declare-function eglot--maybe-activate-editing-mode "eglot") +(declare-function eglot-current-server "eglot") +(declare-function lsp-deferred "lsp-mode") + +(defgroup org-src-context nil + "Provides LSP support in org-src buffers." + :group 'org) + +(defcustom org-src-context-narrow-p t + "Whether org-src buffers should be narrowed to the code block +with Eglot enabled." + :type 'boolean + :group 'org-src-context) + +(defface org-src-context-read-only + '((((class color) (min-colors 257) (background light)) + :background "#ffeeee" :extend t) + (((class color) (min-colors 88) (background light)) + :background "#ffdddd" :extend t) + (((class color) (min-colors 88) (background dark)) + :background "#553333" :extend t)) + "Face for read-only sections of org-src buffer" + :group 'org-src-context) + +(defvar-local org-src-context--before-block-marker nil) +(defvar-local org-src-context--after-block-marker nil) + +(defun org-src-context--edit-src-ad (orig-fn &rest args) + "Set up `org-src-mode' buffers for use with Eglot, Emacs' LSP client. + +This does the following: + +- Include all the code blocks associated with the current tangle + file in the org-src buffer. +- Associate the buffer with a temporary file. +- Connect to a running LSP server with Eglot." + (if-let* ((info (org-babel-get-src-block-info 'light)) + (lang (car info)) + (this-block-data + (save-excursion + (goto-char + (org-element-property :begin (org-element-at-point))) + (car (org-babel-tangle-single-block 1 t)))) + (tangle-file (car this-block-data)) + (this-block (cadr this-block-data)) + (all-blocks (cdar (org-babel-tangle-collect-blocks + lang (alist-get :tangle (caddr info))))) + (extra-blocks (list nil))) + + (prog1 (apply orig-fn args) + (setq extra-blocks + (cl-loop for block in all-blocks + until (equal (nth 1 block) (nth 1 this-block)) + collect block into before-blocks + finally return + (cons before-blocks (nthcdr (1+ (length before-blocks)) + all-blocks)))) + + (when (or (car extra-blocks) (cdr extra-blocks)) + (save-excursion + ;; TODO: Handle :padlines, :shebang + + ;; Code blocks before the current one + (cl-loop initially do + (progn (goto-char (point-min)) + (when (car extra-blocks) (insert "\n") (backward-char 1))) + for block in (car extra-blocks) + for code = (propertize (concat "\n" (nth 6 block) + (propertize "\n" 'rear-nonsticky t)) + 'read-only t + 'font-lock-face 'org-src-context-read-only) + do (insert code)) + (setq-local org-src-context--before-block-marker (point-marker)) + (set-marker-insertion-type org-src-context--before-block-marker nil) + + (setq-local org-src-context--after-block-marker (point-max-marker)) + (set-marker-insertion-type org-src-context--after-block-marker nil) + ;; Code blocks after the current one + (cl-loop initially do (goto-char (point-max)) + for block in (cdr extra-blocks) + for code = (propertize (concat "\n" (nth 6 block) + (propertize "\n" 'rear-nonsticky t)) + 'read-only t + 'font-lock-face 'org-src-context-read-only) + do (insert code)) + + (when org-src-context-narrow-p + (narrow-to-region (marker-position org-src-context--before-block-marker) + (marker-position org-src-context--after-block-marker))))) + + (org-src-context--connect-maybe info tangle-file)) + + ;; No tangle file, don't do anything + (apply orig-fn args))) + +(defun org-src-context--exit-src-ad () + "Format `org-src-mode' buffers before updating the associated +Org buffer." + (when-let ((markerp org-src-context--before-block-marker) + (markerp org-src-context--after-block-marker) + (beg (marker-position org-src-context--before-block-marker)) + (end (marker-position org-src-context--after-block-marker)) + (inhibit-read-only t)) + (when org-src-context-narrow-p + (widen)) + (delete-region end (point-max)) + (delete-region (point-min) beg))) + +(defun org-src-context--lsp-connect () + "Connect to an LSP server managing the current buffer's file." + (when-let (((fboundp 'eglot-current-server)) + (current-server (eglot-current-server))) + (eglot--maybe-activate-editing-mode))) + +(defun org-src-context--connect-maybe (info tangle-file) + "Prepare org source block buffer for an LSP connection" + (when tangle-file + ;; Handle directory paths in tangle-file + (let* ((fnd (file-name-directory tangle-file)) + (mkdirp (thread-last info caddr (alist-get :mkdirp))) + ;;`file-name-concat' is emacs 28.1+ only + (fnd-absolute (concat (temporary-file-directory) (or fnd "")))) + (cond + ((not fnd) t) + ((file-directory-p fnd-absolute) t) + ((and fnd (and (stringp mkdirp) (string= (downcase mkdirp) "yes"))) + (make-directory fnd-absolute 'parents)) + (t (user-error + (format "Cannot create directory \"%s\", please use the :mkdirp header arg." fnd)))) + + (setq buffer-file-name (concat (temporary-file-directory) tangle-file)) + (org-src-context--lsp-connect)))) + +(define-minor-mode org-src-context-mode + "Toggle Org-Src-Context mode. When turned on, you can start persistent +LSP connections using Eglot in org-src buffers. + +To inform the Language Server about files corresponding to code +blocks to track, use `:tangle' headers with code blocks. LSP +support is limited to the current file being edited." + :global t + :lighter nil + :group 'org-src-context + (if org-src-context-mode + (progn + (advice-add 'org-edit-src-code :around #'org-src-context--edit-src-ad) + (advice-add 'org-edit-src-exit :before #'org-src-context--exit-src-ad)) + (advice-remove 'org-edit-src-code #'org-src-context--edit-src-ad) + (advice-remove 'org-edit-src-exit #'org-src-context--exit-src-ad))) + +(provide 'org-src-context) +;;; org-src-context.el ends here + -- 2.37.2 --=-=-=--