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
This commit is contained in:
Sébastien Miquel 2023-06-27 09:23:01 +02:00 committed by Ihor Radchenko
parent 501be358bb
commit 2e2ed40553
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
4 changed files with 94 additions and 70 deletions

View File

@ -402,9 +402,12 @@ line. Return nil if it fails."
(when skip-fl (forward-line)) (when skip-fl (forward-line))
(while (not (eobp)) (while (not (eobp))
(let ((ind (progn (skip-chars-forward " \t") (current-column)))) (let ((ind (progn (skip-chars-forward " \t") (current-column))))
(cond ((eolp) (delete-region (line-beginning-position) (point))) (cond ((< ind n)
((< ind n) (throw :exit nil)) (if (eolp) (delete-region (line-beginning-position) (point))
(t (indent-line-to (- ind n)))) (throw :exit nil)))
(t (delete-region (line-beginning-position)
(progn (move-to-column n t)
(point)))))
(forward-line))) (forward-line)))
;; Signal success. ;; Signal success.
t)))) t))))

View File

@ -326,9 +326,6 @@ is 0.")
"File name associated to Org source buffer, or nil.") "File name associated to Org source buffer, or nil.")
(put 'org-src-source-file-name 'permanent-local t) (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) (defun org-src--construct-edit-buffer-name (org-buffer-name lang)
"Construct the buffer name for a source editing buffer. "Construct the buffer name for a source editing buffer.
Format is \"*Org Src ORG-BUFFER-NAME[ LANG ]*\"." 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) (list (buffer-substring (point-min) eol)
(buffer-substring eol (point-max)))))) (buffer-substring eol (point-max))))))
(write-back org-src--allow-write-back) (write-back org-src--allow-write-back)
(preserve-blank-line org-src--preserve-blank-line) marker indent-str)
marker) ;; Compute the exact sequence of tabs and spaces used to indent up
(with-current-buffer write-back-buf ;; to `indentation-offset' in the Org buffer.
;; Reproduce indentation parameters from source buffer. (setq indent-str
(with-temp-buffer
;; Reproduce indentation parameters from org buffer.
(setq indent-tabs-mode use-tabs?) (setq indent-tabs-mode use-tabs?)
(when (> source-tab-width 0) (setq tab-width source-tab-width)) (when (> source-tab-width 0) (setq tab-width source-tab-width))
(indent-to indentation-offset)
(buffer-string)))
(with-current-buffer write-back-buf
;; Apply WRITE-BACK function on edit buffer contents. ;; Apply WRITE-BACK function on edit buffer contents.
(insert (org-no-properties (car contents))) (insert (org-no-properties (car contents)))
(setq marker (point-marker)) (setq marker (point-marker))
@ -496,15 +498,14 @@ Assume point is in the corresponding edit buffer."
;; Add INDENTATION-OFFSET to every line in buffer, ;; Add INDENTATION-OFFSET to every line in buffer,
;; unless indentation is meant to be preserved. ;; unless indentation is meant to be preserved.
(when (> indentation-offset 0) (when (> indentation-offset 0)
;; LaTeX-fragments are inline. Do not add indentation to their
;; first line.
(when preserve-fl (forward-line)) (when preserve-fl (forward-line))
(while (not (eobp)) (while (not (eobp))
(skip-chars-forward " \t") ;; Keep empty src lines empty, even when src block is
(when (or (not (eolp)) ; not a blank line ;; indented on Org side.
(and (eq (point) (marker-position marker)) ; current line ;; See https://list.orgmode.org/725763.1632663635@apollo2.minshall.org/T/
preserve-blank-line)) (when (not (eolp)) (insert indent-str)) ; not an empty line
(let ((i (current-column)))
(delete-region (line-beginning-position) (point))
(indent-to (+ i indentation-offset))))
(forward-line))) (forward-line)))
(set-marker marker nil)))) (set-marker marker nil))))
@ -557,11 +558,6 @@ Leave point in edit buffer."
(org-element-parent datum) nil)) (org-element-parent datum) nil))
(t (org-current-text-indentation))))) (t (org-current-text-indentation)))))
(content-ind org-edit-src-content-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 (preserve-ind
(and (memq type '(example-block src-block)) (and (memq type '(example-block src-block))
(or (org-element-property :preserve-indent datum) (or (org-element-property :preserve-indent datum)
@ -611,7 +607,6 @@ Leave point in edit buffer."
(setq org-src--overlay overlay) (setq org-src--overlay overlay)
(setq org-src--allow-write-back write-back) (setq org-src--allow-write-back write-back)
(setq org-src-source-file-name source-file-name) (setq org-src-source-file-name source-file-name)
(setq org-src--preserve-blank-line preserve-blank-line)
;; Start minor mode. ;; Start minor mode.
(org-src-mode) (org-src-mode)
;; Clear undo information so we cannot undo back to the ;; 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. "Fontify code block between START and END using LANG's syntax.
This function is called by Emacs' automatic fontification, as long This function is called by Emacs' automatic fontification, as long
as `org-src-fontify-natively' is non-nil." 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)) (remove-text-properties start end '(face nil))
(let ((lang-mode (org-src-get-lang-mode lang))) (let ((lang-mode (org-src-get-lang-mode lang)))
(when (fboundp lang-mode) (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. ;; Add string and a final space to ensure property change.
(insert string " ")) (insert string " "))
(unless (eq major-mode lang-mode) (funcall lang-mode)) (unless (eq major-mode lang-mode) (funcall lang-mode))
(setq native-tab-width tab-width)
(font-lock-ensure) (font-lock-ensure)
(let ((pos (point-min)) next) (let ((pos (point-min)) next)
(while (setq next (next-property-change pos)) (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)) (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 src-face))
(font-lock-append-text-property start end 'face 'org-block)) (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. ;; Clear abbreviated link folding.
(org-fold-region start end nil 'org-link) (org-fold-region start end nil 'org-link)
(add-text-properties (add-text-properties

View File

@ -19153,21 +19153,14 @@ Also align node properties according to `org-property-format'."
(org-with-point-at (org-element-end element) (org-with-point-at (org-element-end element)
(skip-chars-backward " \t\n") (skip-chars-backward " \t\n")
(line-beginning-position)))) (line-beginning-position))))
;; At the beginning of a blank line, do some preindentation. This (let ((block-content-ind
;; signals org-src--edit-element to preserve the indentation on exit (when (not org-src-preserve-indentation)
(when (and (looking-at-p "^[[:space:]]*$") (org-with-point-at (org-element-property :begin element)
(not org-src-preserve-indentation)) (+ (org-current-text-indentation)
(let (block-content-ind some-ind) org-edit-src-content-indentation)))))
(org-with-point-at (org-element-begin element) (org-babel-do-key-sequence-in-edit-buffer (kbd "TAB"))
(setq block-content-ind (+ (org-current-text-indentation) (when (and block-content-ind (looking-at-p "^$"))
org-edit-src-content-indentation)) (indent-line-to block-content-ind))))
(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")))
(t (t
(let ((column (org--get-expected-indentation element nil))) (let ((column (org--get-expected-indentation element nil)))
;; Preserve current column. ;; Preserve current column.

View File

@ -345,11 +345,11 @@ This is a tab:\t.
(insert " Foo") (insert " Foo")
(org-edit-src-exit) (org-edit-src-exit)
(buffer-string))))) (buffer-string)))))
;; Global indentation obeys `indent-tabs-mode' from the original ;; Global indentation does not obey `indent-tabs-mode' from the
;; buffer. ;; original buffer.
(should (should-not
(string-match-p (string-match-p
"^\t+\s*argument2" "\t"
(org-test-with-temp-text (org-test-with-temp-text
" "
- Item - Item
@ -364,14 +364,15 @@ This is a tab:\t.
(org-edit-special) (org-edit-special)
(org-edit-src-exit) (org-edit-src-exit)
(buffer-string))))) (buffer-string)))))
;; Tab character is preserved
(should (should
(string-match-p (string-match-p
"^\s+argument2" "\targument2"
(org-test-with-temp-text (org-test-with-temp-text
" "
- Item - Item
#+BEGIN_SRC emacs-lisp<point> #+BEGIN_SRC emacs-lisp<point>
(progn\n (function argument1\n\t\targument2)) (progn\n (function argument1\n \targument2))
#+END_SRC" #+END_SRC"
(setq-local indent-tabs-mode nil) (setq-local indent-tabs-mode nil)
(let ((org-edit-src-content-indentation 2) (let ((org-edit-src-content-indentation 2)
@ -379,43 +380,59 @@ This is a tab:\t.
(org-edit-special) (org-edit-special)
(org-edit-src-exit) (org-edit-src-exit)
(buffer-string))))) (buffer-string)))))
;; Global indentation also obeys `tab-width' from original buffer. ;; Indentation does not obey `tab-width' from org buffer.
(should (should
(string-match-p (string-match-p
"^\t\\{3\\}\s\\{2\\}argument2" "^ \targument2"
(org-test-with-temp-text (org-test-with-temp-text
" "
- Item #+BEGIN_SRC emacs-lisp
#+BEGIN_SRC emacs-lisp<point>
(progn (progn
(function argument1 (list argument1\n \t<point>argument2))
argument2)) #+END_SRC"
#+END_SRC"
(setq-local indent-tabs-mode t) (setq-local indent-tabs-mode t)
(setq-local tab-width 4) (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-src-preserve-indentation nil))
(org-edit-special) (org-edit-special)
(org-edit-src-exit)
(buffer-string)))))
(should
(string-match-p
"^\t\s\\{6\\}argument2"
(org-test-with-temp-text
"
- Item
#+BEGIN_SRC emacs-lisp<point>
(progn
(function argument1
argument2))
#+END_SRC"
(setq-local indent-tabs-mode t) (setq-local indent-tabs-mode t)
(setq-local tab-width 8) (setq-local tab-width 8)
(let ((org-edit-src-content-indentation 0) (lisp-indent-line)
(org-src-preserve-indentation nil))
(org-edit-special)
(org-edit-src-exit) (org-edit-src-exit)
(buffer-string)))))) (buffer-string)))))
;; Tab characters are displayed with `tab-width' from the native
;; edit buffer.
(should
(equal
10
(org-test-with-temp-text
"
#+BEGIN_SRC emacs-lisp
(progn
(list argument1\n \t<point>argument2))
#+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\t<point>argument2))
#+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 () (ert-deftest test-org-src/indented-latex-fragments ()
"Test editing multiline indented LaTeX fragment." "Test editing multiline indented LaTeX fragment."