Browse Source

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.
Nicolas Goaziou 4 years ago
parent
commit
9e52d2ed01
2 changed files with 106 additions and 81 deletions
  1. 1 1
      etc/ORG-NEWS
  2. 105 80
      lisp/org-src.el

+ 1 - 1
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

+ 105 - 80
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.