From 57e027f753e5f3c77030c97d3de7ad5a7dd61f86 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Mon, 12 Apr 2021 17:58:33 -0500 Subject: Add `acdw-org/delete-backward-char' and advise The advice I've added untabifies in addition to deleting the character, which enables the function to look at `delete-backward-char-untabify-method' or whatev. Also ... whitespace apparently? --- lisp/acdw-org.el | 268 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 146 insertions(+), 122 deletions(-) (limited to 'lisp') diff --git a/lisp/acdw-org.el b/lisp/acdw-org.el index 9fa8767..47e8eb2 100644 --- a/lisp/acdw-org.el +++ b/lisp/acdw-org.el @@ -29,7 +29,7 @@ ELEMENT should be a list like that returned by ;; MAYBE: Use `org-element-lineage'. (when-let* ((parent (org-element-property :parent element))) (or (eq type (car parent)) - (unpackaged/org-element-descendant-of type parent)))) + (unpackaged/org-element-descendant-of type parent)))) (defun unpackaged/org-return-dwim (&optional default) "A helpful replacement for `org-return'. With prefix, @@ -44,97 +44,97 @@ appropriate. In tables, insert a new row or end the table." (if default (org-return) (cond - ;; Act depending on context around point. - - ;; NOTE: I prefer RET to not follow links, but by uncommenting - ;; this block, links will be followed. - ;; FURTHER NOTE: Ideally, I would follow links unless point - ;; /appeared/ to be at the end of the line (even if it's still - ;; inside the link) -- when it would do `org-return'. That - ;; would take some /doing/, however. - - ;; ((eq 'link (car (org-element-context))) - ;; ;; Link: Open it. - ;; (org-open-at-point-global)) - - ((org-at-heading-p) - ;; Heading: Move to position after entry content. NOTE: This is - ;; probably the most interesting feature of this function. - (let ((heading-start (org-entry-beginning-position))) - (goto-char (org-entry-end-position)) - (cond ((and (org-at-heading-p) - (= heading-start (org-entry-beginning-position))) - ;; Entry ends on its heading; add newline after - (end-of-line) - (insert "\n\n")) - (t - ;; Entry ends after its heading; back up - (forward-line -1) - (end-of-line) - (when (org-at-heading-p) - ;; At the same heading - (forward-line) - (insert "\n") - (forward-line -1)) - ;; FIXME: looking-back is supposed to be called with - ;; more arguments. - (while (not (looking-back (rx - (repeat 3 - (seq (optional blank) - "\n"))) - nil)) - (insert "\n")) - (forward-line -1))))) - - ((org-at-item-checkbox-p) - ;; Checkbox: Insert new item with checkbox. - (org-insert-todo-heading nil)) - - ((org-in-item-p) - ;; Plain list. Yes, this gets a little complicated... - (let ((context (org-element-context))) - (if (or (eq 'plain-list (car context)) ; First item in list - (and (eq 'item (car context)) - (not (eq (org-element-property - :contents-begin context) - (org-element-property - :contents-end context)))) - ;; Element in list item, e.g. a link - (unpackaged/org-element-descendant-of 'item context)) - ;; Non-empty item: Add new item. - (org-insert-item) - ;; Empty item: Close the list. - ;; TODO: Do this with org functions rather than operating - ;; on the text. Can't seem to find the right function. - (delete-region (line-beginning-position) (line-end-position)) - (insert "\n")))) - - ((when (fboundp 'org-inlinetask-in-task-p) - (org-inlinetask-in-task-p)) - ;; Inline task: Don't insert a new heading. - (org-return)) - - ((org-at-table-p) - (cond ((save-excursion - (beginning-of-line) - ;; See `org-table-next-field'. - (cl-loop with end = (line-end-position) - for cell = (org-element-table-cell-parser) - always (equal (org-element-property - :contents-begin cell) - (org-element-property - :contents-end cell)) - while (re-search-forward "|" end t))) - ;; Empty row: end the table. - (delete-region (line-beginning-position) - (line-end-position)) - (org-return)) - (t - ;; Non-empty row: call `org-return'. - (org-return)))) - (t - ;; All other cases: call `org-return'. - (org-return))))) + ;; Act depending on context around point. + + ;; NOTE: I prefer RET to not follow links, but by uncommenting + ;; this block, links will be followed. + ;; FURTHER NOTE: Ideally, I would follow links unless point + ;; /appeared/ to be at the end of the line (even if it's still + ;; inside the link) -- when it would do `org-return'. That + ;; would take some /doing/, however. + + ;; ((eq 'link (car (org-element-context))) + ;; ;; Link: Open it. + ;; (org-open-at-point-global)) + + ((org-at-heading-p) + ;; Heading: Move to position after entry content. NOTE: This is + ;; probably the most interesting feature of this function. + (let ((heading-start (org-entry-beginning-position))) + (goto-char (org-entry-end-position)) + (cond ((and (org-at-heading-p) + (= heading-start (org-entry-beginning-position))) + ;; Entry ends on its heading; add newline after + (end-of-line) + (insert "\n\n")) + (t + ;; Entry ends after its heading; back up + (forward-line -1) + (end-of-line) + (when (org-at-heading-p) + ;; At the same heading + (forward-line) + (insert "\n") + (forward-line -1)) + ;; FIXME: looking-back is supposed to be called with + ;; more arguments. + (while (not (looking-back (rx + (repeat 3 + (seq (optional blank) + "\n"))) + nil)) + (insert "\n")) + (forward-line -1))))) + + ((org-at-item-checkbox-p) + ;; Checkbox: Insert new item with checkbox. + (org-insert-todo-heading nil)) + + ((org-in-item-p) + ;; Plain list. Yes, this gets a little complicated... + (let ((context (org-element-context))) + (if (or (eq 'plain-list (car context)) ; First item in list + (and (eq 'item (car context)) + (not (eq (org-element-property + :contents-begin context) + (org-element-property + :contents-end context)))) + ;; Element in list item, e.g. a link + (unpackaged/org-element-descendant-of 'item context)) + ;; Non-empty item: Add new item. + (org-insert-item) + ;; Empty item: Close the list. + ;; TODO: Do this with org functions rather than operating + ;; on the text. Can't seem to find the right function. + (delete-region (line-beginning-position) (line-end-position)) + (insert "\n")))) + + ((when (fboundp 'org-inlinetask-in-task-p) + (org-inlinetask-in-task-p)) + ;; Inline task: Don't insert a new heading. + (org-return)) + + ((org-at-table-p) + (cond ((save-excursion + (beginning-of-line) + ;; See `org-table-next-field'. + (cl-loop with end = (line-end-position) + for cell = (org-element-table-cell-parser) + always (equal (org-element-property + :contents-begin cell) + (org-element-property + :contents-end cell)) + while (re-search-forward "|" end t))) + ;; Empty row: end the table. + (delete-region (line-beginning-position) + (line-end-position)) + (org-return)) + (t + ;; Non-empty row: call `org-return'. + (org-return)))) + (t + ;; All other cases: call `org-return'. + (org-return))))) ;;; ORG-FIX-BLANK-LINES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -145,40 +145,64 @@ appropriate. In tables, insert a new row or end the table." headings's drawers." (interactive "P") (org-map-entries (lambda () - (org-with-wide-buffer - ;; `org-map-entries' narrows the buffer, which - ;; prevents us from seeing newlines before the - ;; current heading, so we do this part widened. - (while (not (looking-back "\n\n" nil)) - ;; Insert blank lines before heading. - (insert "\n"))) - (let ((end (org-entry-end-position))) - ;; Insert blank lines before entry content - (forward-line) - (while (and (org-at-planning-p) - (< (point) (point-max))) - ;; Skip planning lines - (forward-line)) - (while (re-search-forward - org-drawer-regexp end t) - ;; Skip drawers. You might think that - ;; `org-at-drawer-p' would suffice, but for - ;; some reason it doesn't work correctly when - ;; operating on hidden text. This works, taken - ;; from `org-agenda-get-some-entry-text'. - (re-search-forward "^[ \t]*:END:.*\n?" end t) - (goto-char (match-end 0))) - (unless (or (= (point) (point-max)) - (org-at-heading-p) - (looking-at-p "\n")) - (insert "\n")))) - t (if prefix - nil - 'tree))) + (org-with-wide-buffer + ;; `org-map-entries' narrows the buffer, which + ;; prevents us from seeing newlines before the + ;; current heading, so we do this part widened. + (while (not (looking-back "\n\n" nil)) + ;; Insert blank lines before heading. + (insert "\n"))) + (let ((end (org-entry-end-position))) + ;; Insert blank lines before entry content + (forward-line) + (while (and (org-at-planning-p) + (< (point) (point-max))) + ;; Skip planning lines + (forward-line)) + (while (re-search-forward + org-drawer-regexp end t) + ;; Skip drawers. You might think that + ;; `org-at-drawer-p' would suffice, but for + ;; some reason it doesn't work correctly when + ;; operating on hidden text. This works, taken + ;; from `org-agenda-get-some-entry-text'. + (re-search-forward "^[ \t]*:END:.*\n?" end t) + (goto-char (match-end 0))) + (unless (or (= (point) (point-max)) + (org-at-heading-p) + (looking-at-p "\n")) + (insert "\n")))) + t (if prefix + nil + 'tree))) (defun acdw/hook--org-mode-fix-blank-lines () (when (eq major-mode 'org-mode) (let ((current-prefix-arg 4)) (call-interactively #'unpackaged/org-fix-blank-lines)))) +;;; ADVICE + +;; Correct `org-delete-backward-char' to use `backward-delete-char-untabify' +(defun acdw-org/delete-backward-char (N) + "Like `delete-backward-char-untabify', insert whitespace at field end in tables. +When deleting backwards, in tables this function will insert +whitespace in front of the next \"|\" separator, to keep the +table aligned. The table will still be marked for re-alignment +if the field did fill the entire column, because, in this case +the deletion might narrow the column." + (interactive "p") + (save-match-data + (org-check-before-invisible-edit 'delete-backward) + (if (and (= N 1) + (not overwrite-mode) + (not (org-region-active-p)) + (not (eq (char-before) ?|)) + (save-excursion (skip-chars-backward " \t") (not (bolp))) + (looking-at-p ".*?|") + (org-at-table-p)) + (progn (forward-char -1) (org-delete-char 1)) + (backward-delete-char-untabify N) + (org-fix-tags-on-the-fly)))) + (provide 'acdw-org) -- cgit 1.4.1-21-gabe81