From 45048eb78359fc742097982fc1adc5ca5e4b2509 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Sat, 15 Oct 2016 00:48:47 +0200 Subject: [PATCH] Fix `C-e' with visible lines and arguments * lisp/org.el (org-end-of-line): Correctly go to the end of visible line, when appropriate. * testing/lisp/test-org.el (test-org/end-of-line): Add tests. --- lisp/org.el | 87 +++++++++++++++++++++++++--------------- testing/lisp/test-org.el | 82 +++++++++++++++++++++++++++++-------- 2 files changed, 120 insertions(+), 49 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 8664e281a..85e7807ed 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -23766,42 +23766,63 @@ the cursor is already beyond the end of the headline." (when (and (= (point) pos) (eq last-command this-command)) (goto-char after-bullet)))))))) -(defun org-end-of-line (&optional arg) - "Go to the end of the line. +(defun org-end-of-line (&optional n) + "Go to the end of the line, but before ellipsis, if any. + If this is a headline, and `org-special-ctrl-a/e' is set, ignore tags on the first attempt, and only move to after the tags when -the cursor is already beyond the end of the headline." - (interactive "P") - (let ((special (if (consp org-special-ctrl-a/e) (cdr org-special-ctrl-a/e) - org-special-ctrl-a/e)) - (move-fun (cond ((bound-and-true-p visual-line-mode) - 'end-of-visual-line) - ((fboundp 'move-end-of-line) 'move-end-of-line) - (t 'end-of-line))) +the cursor is already beyond the end of the headline. + +With argument N not nil or 1, move forward N - 1 lines first." + (interactive "^p") + (let ((origin (point)) + (special (pcase org-special-ctrl-a/e + (`(_ . ,C-e) C-e) (_ org-special-ctrl-a/e))) deactivate-mark) - (if (or (not special) arg) (call-interactively move-fun) - (let* ((element (save-excursion (beginning-of-line) - (org-element-at-point))) - (type (org-element-type element))) - (cond - ((memq type '(headline inlinetask)) - (let ((pos (point))) - (beginning-of-line 1) - (if (looking-at ".*?\\(?:\\([ \t]*\\)\\(:[[:alnum:]_@#%:]+:\\)?[ \t]*\\)?$") - (if (eq special t) - (if (or (< pos (match-beginning 1)) (= pos (match-end 0))) - (goto-char (match-beginning 1)) - (goto-char (match-end 0))) - (if (or (< pos (match-end 0)) - (not (eq this-command last-command))) - (goto-char (match-end 0)) - (goto-char (match-beginning 1)))) - (call-interactively move-fun)))) - ((outline-invisible-p (line-end-position)) - ;; If element is hidden, `move-end-of-line' would put point - ;; after it. Use `end-of-line' to stay on current line. - (call-interactively 'end-of-line)) - (t (call-interactively move-fun)))))) + ;; First move to a visible line. + (if (bound-and-true-p visual-line-mode) + (beginning-of-visual-line n) + (move-beginning-of-line n)) + (cond + ;; At a headline, with tags. + ((and special + (save-excursion + (beginning-of-line) + (looking-at org-complex-heading-regexp)) + (match-end 5)) + (let ((tags (save-excursion + (goto-char (match-beginning 5)) + (skip-chars-backward " \t") + (point))) + (visual-end (and (bound-and-true-p visual-line-mode) + (save-excursion + (end-of-visual-line) + (point))))) + ;; If `end-of-visual-line' brings us before end of line or + ;; even tags, i.e., the headline spans over multiple visual + ;; lines, move there. + (cond ((and visual-end + (< visual-end tags) + (<= origin visual-end)) + (goto-char visual-end)) + ((eq special 'reversed) + (if (and (= origin (line-end-position)) + (eq this-command last-command)) + (goto-char tags) + (end-of-line))) + (t + (if (or (< origin tags) (= origin (line-end-position))) + (goto-char tags) + (end-of-line)))))) + ((bound-and-true-p visual-line-mode) + (let ((bol (line-beginning-position))) + (end-of-visual-line) + ;; If `end-of-visual-line' gets us past the ellipsis at the + ;; end of a line, backtrack and use `end-of-line' instead. + (when (/= bol (line-beginning-position)) + (goto-char bol) + (end-of-line)))) + (t (end-of-line)))) (setq disable-point-adjustment (or (not (invisible-p (point))) (not (invisible-p (max (point-min) (1- (point)))))))) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 164ed8968..08ce4d8b1 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -2511,35 +2511,80 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/" (should (org-test-with-temp-text "Some text\nSome other text" (progn (org-end-of-line) (eolp)))) - ;; Standard test with `visual-line-mode'. + ;; With `visual-line-mode' active, move to end of visible line. + ;; However, never go past ellipsis. (should-not - (org-test-with-temp-text "A long line of text\nSome other text" - (progn (visual-line-mode) - (forward-char 2) - (dotimes (i 1000) (insert "very ")) - (goto-char (point-min)) - (org-end-of-line) - (eolp)))) - ;; At an headline with special movement. + (org-test-with-temp-text "A long line of text\nSome other text" + (visual-line-mode) + (dotimes (i 1000) (insert "very ")) + (goto-char (point-min)) + (org-end-of-line) + (eolp))) + (should-not + (org-test-with-temp-text "* A short headline\nSome contents" + (visual-line-mode) + (org-overview) + (org-end-of-line) + (eobp))) + ;; In a wide headline, with `visual-line-mode', prefer going to end + ;; of visible line if tags, or end of line, are farther. + (should-not + (org-test-with-temp-text "* A long headline" + (visual-line-mode) + (dotimes (i 1000) (insert "very ")) + (goto-char (point-min)) + (org-end-of-line) + (eolp))) + (should-not + (org-test-with-temp-text "* A long headline :tag:" + (visual-line-mode) + (dotimes (i 1000) (insert "very ")) + (goto-char (point-min)) + (org-end-of-line) + (eolp))) + ;; At an headline without special movement, go to end of line. + ;; However, never go past ellipsis. + (should + (org-test-with-temp-text "* Headline2b :tag:\n" + (let ((org-special-ctrl-a/e nil)) + (and (progn (org-end-of-line) (eolp)) + (progn (org-end-of-line) (eolp)))))) + (should + (org-test-with-temp-text "* Headline2a :tag:\n** Sub" + (org-overview) + (let ((org-special-ctrl-a/e nil)) + (org-end-of-line) + (= 1 (line-beginning-position))))) + ;; At an headline with special movement, first move before tags, + ;; then at the end of line, rinse, repeat. However, never go past + ;; ellipsis. (should (org-test-with-temp-text "* Headline1 :tag:\n" (let ((org-special-ctrl-a/e t)) (and (progn (org-end-of-line) (looking-at " :tag:")) (progn (org-end-of-line) (eolp)) (progn (org-end-of-line) (looking-at " :tag:")))))) - ;; At an headline without special movement. (should - (org-test-with-temp-text "* Headline2 :tag:\n" - (let ((org-special-ctrl-a/e nil)) - (and (progn (org-end-of-line) (eolp)) - (progn (org-end-of-line) (eolp)))))) - ;; At an headline, with reversed movement. + (org-test-with-temp-text "* Headline2a :tag:\n** Sub" + (org-overview) + (let ((org-special-ctrl-a/e t)) + (org-end-of-line) + (org-end-of-line) + (= 1 (line-beginning-position))))) + ;; At an headline, with reversed movement, first go to end of line, + ;; then before tags. However, never go past ellipsis. (should (org-test-with-temp-text "* Headline3 :tag:\n" (let ((org-special-ctrl-a/e 'reversed) (this-command last-command)) (and (progn (org-end-of-line) (eolp)) (progn (org-end-of-line) (looking-at " :tag:")))))) + (should + (org-test-with-temp-text "* Headline2a :tag:\n** Sub" + (org-overview) + (let ((org-special-ctrl-a/e 'reversed)) + (org-end-of-line) + (= 1 (line-beginning-position))))) ;; At a block without hidden contents. (should (org-test-with-temp-text "#+BEGIN_CENTER\nContents\n#+END_CENTER" @@ -2550,7 +2595,12 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/" (let ((org-special-ctrl-a/e t)) (org-hide-block-toggle) (org-end-of-line) - (eobp))))) + (eobp)))) + ;; Get past invisible characters at the end of line. + (should + (org-test-with-temp-text "[[http://orgmode.org]]" + (org-end-of-line) + (eolp)))) (ert-deftest test-org/open-line () "Test `org-open-line' specifications."