From 9e52d2ed012cf25a66a9a3d1100510974219f967 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Sat, 2 May 2015 08:37:37 +0200 Subject: [PATCH] org-src: Allow to edit inline footnote references * lisp/org-src.el (org-src--edit-element): Do not rely on :value to extract contents. Small refactoring. (org-src--contents-area): Renamed from `org-src--element-contents-area'. Throw an error on unknown elements. (org-src--on-datum-p): Rename from `org-src--on-element-p'. Handle objects. (org-edit-export-block, org-edit-src-code, org-edit-fixed-width-region, org-edit-table.el): Apply renaming. (org-edit-src-save, org-edit-src-exit): Handle inline text. (org-edit-src-exit): Allow empty or blank code. Handle inline text. (org-src--edit-element): Rename an argument (org-edit-footnote-reference): Allow to edit inline definitions. * etc/ORG-NEWS: Document new feature. --- etc/ORG-NEWS | 2 +- lisp/org-src.el | 185 +++++++++++++++++++++++++++--------------------- 2 files changed, 106 insertions(+), 81 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index b31ec86eb..7e75a698f 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -367,7 +367,7 @@ Org can typeset a subtitle in some export backends. See the manual for details. *** Edit remotely a footnote definition Calling ~org-edit-footnote-reference~ (C-c ') on a footnote reference -allows to edit its definition, as long as it is not inline, in +allows to edit its definition, as long as it is not anonymous, in a dedicated buffer. It works even if buffer is currently narrowed. ** Miscellaneous *** Strip all meta data from ITEM special property diff --git a/lisp/org-src.el b/lisp/org-src.el index 34c590709..d82068de7 100644 --- a/lisp/org-src.el +++ b/lisp/org-src.el @@ -266,34 +266,35 @@ which see. BEG and END are buffer positions." (org-move-to-column (max (+ (current-column) (cdr coord)) 0)) (point))))) -(defun org-src--element-contents-area (element) - "Return contents boundaries of ELEMENT. -Return value is a pair (BEG . END) where BEG and END are buffer -positions." - (let ((type (org-element-type element))) +(defun org-src--contents-area (datum) + "Return contents boundaries of DATUM. +DATUM is an element or object. Return a pair (BEG . END) where +BEG and END are buffer positions." + (let ((type (org-element-type datum))) (cond ((eq type 'footnote-definition) (let ((beg (org-with-wide-buffer - (goto-char (org-element-property :post-affiliated element)) + (goto-char (org-element-property :post-affiliated datum)) (search-forward "]")))) - (cons beg (or (org-element-property :contents-end element) beg)))) - ((org-element-property :contents-begin element) - (cons (org-element-property :contents-begin element) - (org-element-property :contents-end element))) + (cons beg (or (org-element-property :contents-end datum) beg)))) + ((org-element-property :contents-begin datum) + (cons (org-element-property :contents-begin datum) + (org-element-property :contents-end datum))) ((memq type '(example-block export-block src-block)) (cons (org-with-wide-buffer - (goto-char (org-element-property :post-affiliated element)) + (goto-char (org-element-property :post-affiliated datum)) (line-beginning-position 2)) (org-with-wide-buffer - (goto-char (org-element-property :end element)) + (goto-char (org-element-property :end datum)) (skip-chars-backward " \r\t\n") (line-beginning-position 1)))) - (t - (cons (org-element-property :post-affiliated element) + ((memq type '(fixed-width table)) + (cons (org-element-property :post-affiliated datum) (org-with-wide-buffer - (goto-char (org-element-property :end element)) + (goto-char (org-element-property :end datum)) (skip-chars-backward " \r\t\n") - (line-beginning-position 2))))))) + (line-beginning-position 2)))) + (t (error "Unsupported element or object: %s" type))))) (defun org-src--make-source-overlay (beg end edit-buffer) "Create overlay between BEG and END positions and return it. @@ -323,14 +324,18 @@ END." "Remove overlay from current source buffer." (when (overlayp org-src--overlay) (delete-overlay org-src--overlay))) -(defun org-src--on-element-p (element) - "Non-nil when point is on ELEMENT." - (and (>= (point) (org-element-property :begin element)) +(defun org-src--on-datum-p (datum) + "Non-nil when point is on DATUM. +DATUM is an element or an object. Consider blank lines or white +spaces after it as being outside." + (and (>= (point) (org-element-property :begin datum)) (<= (point) (org-with-wide-buffer - (goto-char (org-element-property :end element)) + (goto-char (org-element-property :end datum)) (skip-chars-backward " \r\t\n") - (line-end-position))))) + (if (memq (org-element-type datum) org-element-all-elements) + (line-end-position) + (point)))))) (defun org-src--contents-for-write-back () "Return buffer contents in a format appropriate for write back. @@ -352,28 +357,30 @@ Assume point is in the corresponding edit buffer." (buffer-string)))) (defun org-src--edit-element - (element name &optional major write-back contents remote) - "Edit ELEMENT contents in a dedicated buffer NAME. + (datum name &optional major write-back contents remote) + "Edit DATUM contents in a dedicated buffer NAME. MAJOR is the major mode used in the edit buffer. A nil value is equivalent to `fundamental-mode'. When WRITE-BACK is non-nil, assume contents will replace original -region. If it is a function, applied in the edit buffer, from -point min, before returning the contents. +region. Moreover, if it is a function, apply it in the edit +buffer, from point min, before returning the contents. When CONTENTS is non-nil, display them in the edit buffer. -Otherwise, assume they are located in property `:value'. +Otherwise, show DATUM contents as specified by +`org-src--contents-area'. When REMOTE is non-nil, do not try to preserve point or mark when moving from the edit area to the source. Leave point in edit buffer." (setq org-src--saved-temp-window-config (current-window-configuration)) - (let* ((area (org-src--element-contents-area element)) + (let* ((area (org-src--contents-area datum)) (beg (copy-marker (car area))) (end (copy-marker (cdr area) t)) - (old-edit-buffer (org-src--edit-buffer beg end))) + (old-edit-buffer (org-src--edit-buffer beg end)) + (contents (or contents (buffer-substring-no-properties beg end)))) (if (and old-edit-buffer (or (not org-src-ask-before-returning-to-edit-buffer) (y-or-n-p "Return to existing edit buffer ([n] will revert changes)? "))) @@ -384,13 +391,13 @@ Leave point in edit buffer." (with-current-buffer old-edit-buffer (org-src--remove-overlay)) (kill-buffer old-edit-buffer)) (let* ((org-mode-p (derived-mode-p 'org-mode)) - (type (org-element-type element)) + (type (org-element-type datum)) (ind (org-with-wide-buffer - (goto-char (org-element-property :begin element)) + (goto-char (org-element-property :begin datum)) (org-get-indentation))) (preserve-ind (and (memq type '(example-block src-block)) - (or (org-element-property :preserve-indent element) + (or (org-element-property :preserve-indent datum) org-src-preserve-indentation))) ;; Store relative positions of mark (if any) and point ;; within the edited area. @@ -408,7 +415,7 @@ Leave point in edit buffer." ;; Switch to edit buffer. (org-src-switch-to-buffer buffer 'edit) ;; Insert contents. - (insert (or contents (org-element-property :value element))) + (insert contents) (remove-text-properties (point-min) (point-max) '(display nil invisible nil intangible nil)) (unless preserve-ind (org-do-remove-indentation)) @@ -435,17 +442,21 @@ Leave point in edit buffer." (org-src-mode) ;; Move mark and point in edit buffer to the corresponding ;; location. - (when mark-coordinates - (org-src--goto-coordinates mark-coordinates (point-min) (point-max)) - (push-mark (point) 'no-message t) - (setq deactivate-mark nil)) - (if (not remote) - (org-src--goto-coordinates - point-coordinates (point-min) (point-max)) - (goto-char (or (text-property-any - (point-min) (point-max) 'read-only nil) - (point-max))) - (skip-chars-forward " \r\t\n")))))) + (if remote + (progn + ;; Put point at first non read-only character after + ;; leading blank. + (goto-char + (or (text-property-any (point-min) (point-max) 'read-only nil) + (point-max))) + (skip-chars-forward " \r\t\n")) + ;; Set mark and point. + (when mark-coordinates + (org-src--goto-coordinates mark-coordinates (point-min) (point-max)) + (push-mark (point) 'no-message t) + (setq deactivate-mark nil)) + (org-src--goto-coordinates + point-coordinates (point-min) (point-max))))))) @@ -679,39 +690,52 @@ If BUFFER is non-nil, test it instead." (defun org-edit-footnote-reference () "Edit definition of footnote reference at point." (interactive) - (let ((context (org-element-context))) + (let* ((context (org-element-context)) + (label (org-element-property :label context))) (unless (and (eq (org-element-type context) 'footnote-reference) - (< (point) - (org-with-wide-buffer - (goto-char (org-element-property :end context)) - (skip-chars-backward " \t") - (point)))) + (org-src--on-datum-p context)) (user-error "Not on a footnote reference")) - (let* ((label (org-element-property :label context)) - (definition - (org-with-wide-buffer - (org-footnote-goto-definition label) - (beginning-of-line) - (org-element-at-point)))) - (unless (eq (org-element-type definition) 'footnote-definition) - (user-error "Cannot edit remotely inline footnotes")) + (unless label (user-error "Cannot edit remotely anonymous footnotes")) + (let* ((definition (org-with-wide-buffer + (org-footnote-goto-definition label) + (org-element-context))) + (inline (eq (org-element-type definition) 'footnote-reference)) + (contents + (let ((c (org-with-wide-buffer + (org-trim (buffer-substring-no-properties + (org-element-property :begin definition) + (org-element-property :end definition)))))) + (add-text-properties + 0 + (progn (string-match (if inline "\\`\\[fn:.*?:" "\\`.*?\\]") c) + (match-end 0)) + '(read-only "Cannot edit footnote label" front-sticky t + rear-nonsticky t) + c) + (when inline + (let ((l (length c))) + (add-text-properties + (1- l) l + '(read-only "Cannot edit past footnote reference" + front-sticky nil rear-nonsticky nil) + c))) + c))) (org-src--edit-element - definition (format "*Edit footnote [%s]*" label) + definition + (format "*Edit footnote [%s]*" label) #'org-mode - (lambda () (delete-region (point) (search-forward "]"))) - (concat - (org-propertize (format "[%s]" label) - 'read-only "Cannot edit footnote label" - 'front-sticky t - 'rear-nonsticky t) - (and (org-element-property :contents-begin definition) - (org-with-wide-buffer - (buffer-substring-no-properties - (progn - (goto-char (org-element-property :contents-begin definition)) - (skip-chars-backward " \r\t\n") - (point)) - (org-element-property :contents-end definition))))) + `(lambda () + (if ,(not inline) (delete-region (point) (search-forward "]")) + (delete-region (point) (search-forward ":" nil t 2)) + (delete-region (1- (point-max)) (point-max)) + (when (re-search-forward "\n[ \t]*\n" nil t) + (user-error "Inline definitions cannot contain blank lines")) + ;; If footnote reference belongs to a table, make sure to + ;; remove any newline characters in order to preserve + ;; table's structure. + (when ,(org-element-lineage definition '(table-cell)) + (while (search-forward "\n" nil t) (delete-char -1))))) + contents 'remote)) ;; Report success. t)) @@ -729,7 +753,7 @@ Throw an error when not at such a table." (let ((element (org-element-at-point))) (unless (and (eq (org-element-type element) 'table) (eq (org-element-property :type element) 'table.el) - (org-src--on-element-p element)) + (org-src--on-datum-p element)) (user-error "Not in a table.el table")) (org-src--edit-element element @@ -752,7 +776,7 @@ Throw an error when not at an export block." (interactive) (let ((element (org-element-at-point))) (unless (and (eq (org-element-type element) 'export-block) - (org-src--on-element-p element)) + (org-src--on-datum-p element)) (user-error "Not in an export block")) (let* ((type (downcase (org-element-property :type element))) (mode (org-src--get-lang-mode type))) @@ -783,7 +807,7 @@ name of the sub-editing buffer." (let* ((element (org-element-at-point)) (type (org-element-type element))) (unless (and (memq type '(example-block src-block)) - (org-src--on-element-p element)) + (org-src--on-datum-p element)) (user-error "Not in a source or example block")) (let* ((lang (if (eq type 'src-block) (org-element-property :language element) @@ -835,7 +859,7 @@ the area in the Org mode buffer." (interactive) (let ((element (org-element-at-point))) (unless (and (eq (org-element-type element) 'fixed-width) - (org-src--on-element-p element)) + (org-src--on-datum-p element)) (user-error "Not in a fixed-width area")) (org-src--edit-element element @@ -875,8 +899,9 @@ Throw an error if there is no such buffer." ;; insert new contents. (delete-overlay overlay) (delete-region beg end) - (when (org-string-nw-p edited-code) (insert edited-code)) - (unless (bolp) (insert "\n")) + (let ((expecting-bol (bolp))) + (insert edited-code) + (when (and expecting-bol (not (bolp))) (insert "\n"))) (save-buffer) (move-overlay overlay beg (point))))) @@ -902,9 +927,9 @@ Throw an error if there is no such buffer." (undo-boundary) (goto-char beg) (delete-region beg end) - (when (org-string-nw-p code) + (let ((expecting-bol (bolp))) (insert code) - (unless (bolp) (insert "\n"))))) + (when (and expecting-bol (not (bolp))) (insert "\n"))))) ;; If we are to return to source buffer, put point at an ;; appropriate location. In particular, if block is hidden, move ;; to the beginning of the block opening line.