From 7906d7b7fa2d376e95156ab7177494f2cececaff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Miquel?= Date: Tue, 27 Jun 2023 09:23:01 +0200 Subject: [PATCH] org-src.el: Use native value of `indent-tabs-mode' for indentation * lisp/org.el (org-indent-line): Simplify native indentation inside src block. Ensure we add the org indentation if the line is empty. * lisp/org-macs.el (org-do-remove-indentation): Preserve indentation (spaces vs tabs) past the common indentation to remove. Do not empty blank lines. * lisp/org-src.el (org-src--contents-for-write-back): Preserve the native indentation (spaces vs tabs). If necessary, add a common org indentation to the block according to org's `indent-tabs-mode'. (org-src-font-lock-fontify-block): Display the native indentation tab characters with a fixed width, according to the native tab width value, to preserve vertical alignement in the org buffer. * testing/lisp/test-org-src.el (test-org-src/indented-blocks): Update tests. Indentation no longer obeys `indent-tabs-mode' from the org buffer, but is separated in an eventual org part, and the native part. Link: https://list.orgmode.org/87a5wcez7e.fsf@localhost/T/#t --- lisp/org-macs.el | 9 +++-- lisp/org-src.el | 57 ++++++++++++++++----------- lisp/org.el | 23 ++++------- testing/lisp/test-org-src.el | 75 ++++++++++++++++++++++-------------- 4 files changed, 94 insertions(+), 70 deletions(-) diff --git a/lisp/org-macs.el b/lisp/org-macs.el index d6879e8cf..aa5c4845e 100644 --- a/lisp/org-macs.el +++ b/lisp/org-macs.el @@ -402,9 +402,12 @@ line. Return nil if it fails." (when skip-fl (forward-line)) (while (not (eobp)) (let ((ind (progn (skip-chars-forward " \t") (current-column)))) - (cond ((eolp) (delete-region (line-beginning-position) (point))) - ((< ind n) (throw :exit nil)) - (t (indent-line-to (- ind n)))) + (cond ((< ind n) + (if (eolp) (delete-region (line-beginning-position) (point)) + (throw :exit nil))) + (t (delete-region (line-beginning-position) + (progn (move-to-column n t) + (point))))) (forward-line))) ;; Signal success. t)))) diff --git a/lisp/org-src.el b/lisp/org-src.el index 317682844..e1f7d50dc 100644 --- a/lisp/org-src.el +++ b/lisp/org-src.el @@ -326,9 +326,6 @@ is 0.") "File name associated to Org source buffer, or nil.") (put 'org-src-source-file-name 'permanent-local t) -(defvar-local org-src--preserve-blank-line nil) -(put 'org-src--preserve-blank-line 'permanent-local t) - (defun org-src--construct-edit-buffer-name (org-buffer-name lang) "Construct the buffer name for a source editing buffer. Format is \"*Org Src ORG-BUFFER-NAME[ LANG ]*\"." @@ -481,12 +478,17 @@ Assume point is in the corresponding edit buffer." (list (buffer-substring (point-min) eol) (buffer-substring eol (point-max)))))) (write-back org-src--allow-write-back) - (preserve-blank-line org-src--preserve-blank-line) - marker) + marker indent-str) + ;; Compute the exact sequence of tabs and spaces used to indent up + ;; to `indentation-offset' in the Org buffer. + (setq indent-str + (with-temp-buffer + ;; Reproduce indentation parameters from org buffer. + (setq indent-tabs-mode use-tabs?) + (when (> source-tab-width 0) (setq tab-width source-tab-width)) + (indent-to indentation-offset) + (buffer-string))) (with-current-buffer write-back-buf - ;; Reproduce indentation parameters from source buffer. - (setq indent-tabs-mode use-tabs?) - (when (> source-tab-width 0) (setq tab-width source-tab-width)) ;; Apply WRITE-BACK function on edit buffer contents. (insert (org-no-properties (car contents))) (setq marker (point-marker)) @@ -496,15 +498,14 @@ Assume point is in the corresponding edit buffer." ;; Add INDENTATION-OFFSET to every line in buffer, ;; unless indentation is meant to be preserved. (when (> indentation-offset 0) - (when preserve-fl (forward-line)) + ;; LaTeX-fragments are inline. Do not add indentation to their + ;; first line. + (when preserve-fl (forward-line)) (while (not (eobp)) - (skip-chars-forward " \t") - (when (or (not (eolp)) ; not a blank line - (and (eq (point) (marker-position marker)) ; current line - preserve-blank-line)) - (let ((i (current-column))) - (delete-region (line-beginning-position) (point)) - (indent-to (+ i indentation-offset)))) + ;; Keep empty src lines empty, even when src block is + ;; indented on Org side. + ;; See https://list.orgmode.org/725763.1632663635@apollo2.minshall.org/T/ + (when (not (eolp)) (insert indent-str)) ; not an empty line (forward-line))) (set-marker marker nil)))) @@ -557,11 +558,6 @@ Leave point in edit buffer." (org-element-parent datum) nil)) (t (org-current-text-indentation))))) (content-ind org-edit-src-content-indentation) - (blank-line (save-excursion (forward-line 0) - (looking-at-p "^[[:space:]]*$"))) - (empty-line (and blank-line (looking-at-p "^$"))) - (preserve-blank-line (or (and blank-line (not empty-line)) - (and empty-line (= (+ block-ind content-ind) 0)))) (preserve-ind (and (memq type '(example-block src-block)) (or (org-element-property :preserve-indent datum) @@ -611,7 +607,6 @@ Leave point in edit buffer." (setq org-src--overlay overlay) (setq org-src--allow-write-back write-back) (setq org-src-source-file-name source-file-name) - (setq org-src--preserve-blank-line preserve-blank-line) ;; Start minor mode. (org-src-mode) ;; Clear undo information so we cannot undo back to the @@ -645,7 +640,7 @@ Leave point in edit buffer." "Fontify code block between START and END using LANG's syntax. This function is called by Emacs' automatic fontification, as long as `org-src-fontify-natively' is non-nil." - (let ((modified (buffer-modified-p))) + (let ((modified (buffer-modified-p)) native-tab-width) (remove-text-properties start end '(face nil)) (let ((lang-mode (org-src-get-lang-mode lang))) (when (fboundp lang-mode) @@ -659,6 +654,7 @@ as `org-src-fontify-natively' is non-nil." ;; Add string and a final space to ensure property change. (insert string " ")) (unless (eq major-mode lang-mode) (funcall lang-mode)) + (setq native-tab-width tab-width) (font-lock-ensure) (let ((pos (point-min)) next) (while (setq next (next-property-change pos)) @@ -716,6 +712,21 @@ as `org-src-fontify-natively' is non-nil." (when (or (facep src-face) (listp src-face)) (font-lock-append-text-property start end 'face src-face)) (font-lock-append-text-property start end 'face 'org-block)) + ;; Display native tab indentation characters as spaces + (save-excursion + (goto-char start) + (let ((indent-offset + (if org-src-preserve-indentation 0 + (+ (progn (backward-char) + (org-current-text-indentation)) + org-edit-src-content-indentation)))) + (while (re-search-forward "^[ ]*\t" end t) + (let* ((b (and (eq indent-offset (move-to-column indent-offset)) + (point))) + (e (progn (skip-chars-forward "\t") (point))) + (s (and b (make-string (* (- e b) native-tab-width) ? )))) + (when (and b (< b e)) (add-text-properties b e `(display ,s))) + (forward-char))))) ;; Clear abbreviated link folding. (org-fold-region start end nil 'org-link) (add-text-properties diff --git a/lisp/org.el b/lisp/org.el index 57d8082e5..62278ec77 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -19153,21 +19153,14 @@ Also align node properties according to `org-property-format'." (org-with-point-at (org-element-end element) (skip-chars-backward " \t\n") (line-beginning-position)))) - ;; At the beginning of a blank line, do some preindentation. This - ;; signals org-src--edit-element to preserve the indentation on exit - (when (and (looking-at-p "^[[:space:]]*$") - (not org-src-preserve-indentation)) - (let (block-content-ind some-ind) - (org-with-point-at (org-element-begin element) - (setq block-content-ind (+ (org-current-text-indentation) - org-edit-src-content-indentation)) - (forward-line) - (save-match-data (re-search-forward "^[ \t]*\\S-" nil t)) - (backward-char) - (setq some-ind (if (looking-at-p "#\\+end_src") - block-content-ind (org-current-text-indentation)))) - (indent-line-to (min block-content-ind some-ind)))) - (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB"))) + (let ((block-content-ind + (when (not org-src-preserve-indentation) + (org-with-point-at (org-element-property :begin element) + (+ (org-current-text-indentation) + org-edit-src-content-indentation))))) + (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB")) + (when (and block-content-ind (looking-at-p "^$")) + (indent-line-to block-content-ind)))) (t (let ((column (org--get-expected-indentation element nil))) ;; Preserve current column. diff --git a/testing/lisp/test-org-src.el b/testing/lisp/test-org-src.el index e31fcb946..a634d85e8 100644 --- a/testing/lisp/test-org-src.el +++ b/testing/lisp/test-org-src.el @@ -345,11 +345,11 @@ This is a tab:\t. (insert " Foo") (org-edit-src-exit) (buffer-string))))) - ;; Global indentation obeys `indent-tabs-mode' from the original - ;; buffer. - (should + ;; Global indentation does not obey `indent-tabs-mode' from the + ;; original buffer. + (should-not (string-match-p - "^\t+\s*argument2" + "\t" (org-test-with-temp-text " - Item @@ -364,14 +364,15 @@ This is a tab:\t. (org-edit-special) (org-edit-src-exit) (buffer-string))))) + ;; Tab character is preserved (should (string-match-p - "^\s+argument2" + "\targument2" (org-test-with-temp-text " - Item #+BEGIN_SRC emacs-lisp - (progn\n (function argument1\n\t\targument2)) + (progn\n (function argument1\n \targument2)) #+END_SRC" (setq-local indent-tabs-mode nil) (let ((org-edit-src-content-indentation 2) @@ -379,43 +380,59 @@ This is a tab:\t. (org-edit-special) (org-edit-src-exit) (buffer-string))))) - ;; Global indentation also obeys `tab-width' from original buffer. + ;; Indentation does not obey `tab-width' from org buffer. (should (string-match-p - "^\t\\{3\\}\s\\{2\\}argument2" + "^ \targument2" (org-test-with-temp-text " -- Item - #+BEGIN_SRC emacs-lisp +#+BEGIN_SRC emacs-lisp (progn - (function argument1 - argument2)) - #+END_SRC" + (list argument1\n \targument2)) +#+END_SRC" (setq-local indent-tabs-mode t) (setq-local tab-width 4) - (let ((org-edit-src-content-indentation 0) + (let ((org-edit-src-content-indentation 2) (org-src-preserve-indentation nil)) (org-edit-special) + (setq-local indent-tabs-mode t) + (setq-local tab-width 8) + (lisp-indent-line) (org-edit-src-exit) (buffer-string))))) + ;; Tab characters are displayed with `tab-width' from the native + ;; edit buffer. (should - (string-match-p - "^\t\s\\{6\\}argument2" + (equal + 10 (org-test-with-temp-text - " -- Item - #+BEGIN_SRC emacs-lisp + " +#+BEGIN_SRC emacs-lisp (progn - (function argument1 - argument2)) - #+END_SRC" - (setq-local indent-tabs-mode t) - (setq-local tab-width 8) - (let ((org-edit-src-content-indentation 0) - (org-src-preserve-indentation nil)) - (org-edit-special) - (org-edit-src-exit) - (buffer-string)))))) + (list argument1\n \targument2)) +#+END_SRC" + (setq-local indent-tabs-mode t) + (setq-local tab-width 4) + (let ((org-edit-src-content-indentation 2) + (org-src-preserve-indentation nil)) + (font-lock-ensure) + (current-column))))) + ;; The initial tab characters respect org's `tab-width'. + (should + (equal + 10 + (org-test-with-temp-text + " +#+BEGIN_SRC emacs-lisp +\t(progn +\t (list argument1\n\t\targument2)) +#+END_SRC" + (setq-local indent-tabs-mode t) + (setq-local tab-width 2) + (let ((org-edit-src-content-indentation 2) + (org-src-preserve-indentation nil)) + (font-lock-ensure) + (current-column)))))) (ert-deftest test-org-src/indented-latex-fragments () "Test editing multiline indented LaTeX fragment." -- 2.41.0