emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[nongnu] elpa/markdown-mode e096bb97a9 4/4: Merge pull request #820 from


From: ELPA Syncer
Subject: [nongnu] elpa/markdown-mode e096bb97a9 4/4: Merge pull request #820 from kuranari/markdown-beginning-of-line
Date: Sun, 7 Jan 2024 04:00:11 -0500 (EST)

branch: elpa/markdown-mode
commit e096bb97a91fcd4dc2b46d8b6e093194b03b7364
Merge: 141f9a05d1 c51871ab38
Author: Shohei YOSHIDA <syohex@gmail.com>
Commit: GitHub <noreply@github.com>

    Merge pull request #820 from kuranari/markdown-beginning-of-line
    
    Add `markdown-beginning/end-of-line` functions and 
`markdown-special-ctrl-a/e` variable
---
 CHANGES.md             |   3 +
 README.md              |   9 +++
 markdown-mode.el       | 178 +++++++++++++++++++++++++++++++++++++++++++++-
 tests/markdown-test.el | 186 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 375 insertions(+), 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index ec60c06532..1e4608d0ba 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -8,6 +8,9 @@
       `org-open-at-point-functions`, allowing other libraries to
       handle links specially. [GH-780][]
     - Support media handler for images and drag and drop images [GH-804][]
+    - Add functions to move to the beginning and end of lines
+      (`markdown-beginning-of-line` and `markdown-end-of-line`), and the
+      variable `markdown-special-ctrl-a/e`, like Org mode.
 
 *   Bug fixes:
     - Don't highlight superscript/subscript in math inline/block [GH-802][]
diff --git a/README.md b/README.md
index f2eaa4aee1..182141a0d3 100644
--- a/README.md
+++ b/README.md
@@ -935,6 +935,15 @@ provides an interface to all of the possible 
customizations:
   * `markdown-fontify-whole-heading-line` - font lock for highlighting
      the whole line for headings.(default: `nil`)
 
+  * `markdown-special-ctrl-a/e` - set to non-nil to behave specially in
+    headlines and items. When `t`, `C-a` will bring back the cursor to the
+    beginning of the headline text. In an item, this will be the position after
+    bullet and check-box, if any. `C-e` will jump to the end of the headline,
+    ignoring the presence of closing tags in the headline. When set to the
+    symbol `reversed`, the first `C-a` or `C-e` works normally, going to the
+    true line boundary first. Only a directly following, identical keypress 
will
+    bring the cursor to the special positions (default: `nil`).
+
 Additionally, the faces used for syntax highlighting can be modified to
 your liking by issuing <kbd>M-x customize-group RET markdown-faces</kbd>
 or by using the "Markdown Faces" link at the bottom of the mode
diff --git a/markdown-mode.el b/markdown-mode.el
index f1aee9d1c5..8094e0258f 100644
--- a/markdown-mode.el
+++ b/markdown-mode.el
@@ -659,6 +659,44 @@ markdown-header-face-* faces."
   :safe 'booleanp
   :package-version '(markdown-mode . "2.5"))
 
+(defcustom markdown-special-ctrl-a/e nil
+  "Non-nil means `C-a' and `C-e' behave specially in headlines and items.
+
+When t, `C-a' will bring back the cursor to the beginning of the
+headline text. In an item, this will be the position after bullet
+and check-box, if any. When the cursor is already at that
+position, another `C-a' will bring it to the beginning of the
+line.
+
+`C-e' will jump to the end of the headline, ignoring the presence
+of closing tags in the headline. A second `C-e' will then jump to
+the true end of the line, after closing tags. This also means
+that, when this variable is non-nil, `C-e' also will never jump
+beyond the end of the heading of a folded section, i.e. not after
+the ellipses.
+
+When set to the symbol `reversed', the first `C-a' or `C-e' works
+normally, going to the true line boundary first.  Only a directly
+following, identical keypress will bring the cursor to the
+special positions.
+
+This may also be a cons cell where the behavior for `C-a' and
+`C-e' is set separately."
+  :group 'markdown
+  :type '(choice
+         (const :tag "off" nil)
+         (const :tag "on: after hashes/bullet and before closing tags first" t)
+         (const :tag "reversed: true line boundary first" reversed)
+         (cons :tag "Set C-a and C-e separately"
+               (choice :tag "Special C-a"
+                       (const :tag "off" nil)
+                       (const :tag "on: after hashes/bullet first" t)
+                       (const :tag "reversed: before hashes/bullet first" 
reversed))
+               (choice :tag "Special C-e"
+                       (const :tag "off" nil)
+                       (const :tag "on: before closing tags first" t)
+                       (const :tag "reversed: after closing tags first" 
reversed))))
+  :package-version '(markdown-mode . "2.7"))
 
 ;;; Markdown-Specific `rx' Macro ==============================================
 
@@ -5531,6 +5569,9 @@ Assumes match data is available for 
`markdown-regex-italic'."
     (define-key map (kbd "C-x n s") 'markdown-narrow-to-subtree)
     (define-key map (kbd "M-RET") 'markdown-insert-list-item)
     (define-key map (kbd "C-c C-j") 'markdown-insert-list-item)
+    ;; Lines
+    (define-key map [remap move-beginning-of-line] 'markdown-beginning-of-line)
+    (define-key map [remap move-end-of-line] 'markdown-end-of-line)
     ;; Paragraphs (Markdown context aware)
     (define-key map [remap backward-paragraph] 'markdown-backward-paragraph)
     (define-key map [remap forward-paragraph] 'markdown-forward-paragraph)
@@ -6474,6 +6515,130 @@ a list."
 
 ;;; Movement ==================================================================
 
+;; This function was originally derived from `org-beginning-of-line' from 
org.el.
+(defun markdown-beginning-of-line (&optional n)
+  "Go to the beginning of the current visible line.
+
+If this is a headline, and `markdown-special-ctrl-a/e' is not nil
+or symbol `reversed', on the first attempt move to where the
+headline text hashes, and only move to beginning of line when the
+cursor is already before the hashes of the text of the headline.
+
+If `markdown-special-ctrl-a/e' is symbol `reversed' then go to
+the hashes of the text on the second attempt.
+
+With argument N not nil or 1, move forward N - 1 lines first."
+  (interactive "^p")
+  (let ((origin (point))
+        (special (pcase markdown-special-ctrl-a/e
+                   (`(,C-a . ,_) C-a) (_ markdown-special-ctrl-a/e)))
+        deactivate-mark)
+    ;; First move to a visible line.
+    (if visual-line-mode
+        (beginning-of-visual-line n)
+      (move-beginning-of-line n)
+      ;; `move-beginning-of-line' may leave point after invisible
+      ;; characters if line starts with such of these (e.g., with
+      ;; a link at column 0).  Really move to the beginning of the
+      ;; current visible line.
+      (forward-line 0))
+    (cond
+     ;; No special behavior.  Point is already at the beginning of
+     ;; a line, logical or visual.
+     ((not special))
+     ;; `beginning-of-visual-line' left point before logical beginning
+     ;; of line: point is at the beginning of a visual line.  Bail
+     ;; out.
+     ((and visual-line-mode (not (bolp))))
+     ((looking-at markdown-regex-header-atx)
+      ;; At a header, special position is before the title.
+      (let ((refpos (match-beginning 2))
+            (bol (point)))
+        (if (eq special 'reversed)
+            (when (and (= origin bol) (eq last-command this-command))
+              (goto-char refpos))
+          (when (or (> origin refpos) (<= origin bol))
+            (goto-char refpos)))
+        ;; Prevent automatic cursor movement caused by the command loop.
+        ;; Enable disable-point-adjustment to avoid unintended cursor 
repositioning.
+        (when (and markdown-hide-markup
+                   (equal (get-char-property (point) 'display) ""))
+          (setq disable-point-adjustment t))))
+     ((looking-at markdown-regex-list)
+      ;; At a list item, special position is after the list marker or checkbox.
+      (let ((refpos (or (match-end 4) (match-end 3))))
+        (if (eq special 'reversed)
+            (when (and (= (point) origin) (eq last-command this-command))
+              (goto-char refpos))
+          (when (or (> origin refpos) (<= origin (line-beginning-position)))
+          (goto-char refpos)))))
+     ;; No special case, already at beginning of line.
+     (t nil))))
+
+;; This function was originally derived from `org-end-of-line' from org.el.
+(defun markdown-end-of-line (&optional n)
+  "Go to the end of the line, but before ellipsis, if any.
+
+If this is a headline, and `markdown-special-ctrl-a/e' is not nil
+or symbol `reversed', ignore closing tags on the first attempt,
+and only move to after the closing tags when the cursor is
+already beyond the end of the headline.
+
+If `markdown-special-ctrl-a/e' is symbol `reversed' then ignore
+closing tags on the second attempt.
+
+With argument N not nil or 1, move forward N - 1 lines first."
+  (interactive "^p")
+  (let ((origin (point))
+        (special (pcase markdown-special-ctrl-a/e
+                   (`(,_ . ,C-e) C-e) (_ markdown-special-ctrl-a/e)))
+        deactivate-mark)
+    ;; First move to a visible line.
+    (if visual-line-mode
+        (beginning-of-visual-line n)
+      (move-beginning-of-line n))
+    (cond
+     ;; At a headline, with closing tags.
+     ((save-excursion
+        (forward-line 0)
+        (and (looking-at markdown-regex-header-atx) (match-end 3)))
+      (let ((refpos (match-end 2))
+            (visual-end (and visual-line-mode
+                             (save-excursion
+                               (end-of-visual-line)
+                               (point)))))
+        ;; If `end-of-visual-line' brings us before end of line or even closing
+        ;; tags, i.e., the headline spans over multiple visual lines, move
+        ;; there.
+        (cond ((and visual-end
+                    (< visual-end refpos)
+                    (<= origin visual-end))
+               (goto-char visual-end))
+              ((not special) (end-of-line))
+              ((eq special 'reversed)
+               (if (and (= origin (line-end-position))
+                        (eq this-command last-command))
+                   (goto-char refpos)
+                 (end-of-line)))
+              (t
+               (if (or (< origin refpos) (>= origin (line-end-position)))
+                   (goto-char refpos)
+                 (end-of-line))))
+        ;; Prevent automatic cursor movement caused by the command loop.
+        ;; Enable disable-point-adjustment to avoid unintended cursor 
repositioning.
+        (when (and markdown-hide-markup
+                   (equal (get-char-property (point) 'display) ""))
+          (setq disable-point-adjustment t))))
+     (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)))))
+
 (defun markdown-beginning-of-defun (&optional arg)
   "`beginning-of-defun-function' for Markdown.
 This is used to find the beginning of the defun and should behave
@@ -10042,7 +10207,18 @@ rows and columns and the column alignment."
 
   ;; add live preview export hook
   (add-hook 'after-save-hook #'markdown-live-preview-if-markdown t t)
-  (add-hook 'kill-buffer-hook #'markdown-live-preview-remove-on-kill t t))
+  (add-hook 'kill-buffer-hook #'markdown-live-preview-remove-on-kill t t)
+
+  ;; Add a custom keymap for `visual-line-mode' so that activating
+  ;; this minor mode does not override markdown-mode's keybindings.
+  ;; FIXME: Probably `visual-line-mode' should take care of this.
+  (let ((oldmap (cdr (assoc 'visual-line-mode minor-mode-map-alist)))
+        (newmap (make-sparse-keymap)))
+    (set-keymap-parent newmap oldmap)
+    (define-key newmap [remap move-beginning-of-line] nil)
+    (define-key newmap [remap move-end-of-line] nil)
+    (make-local-variable 'minor-mode-overriding-map-alist)
+    (push `(visual-line-mode . ,newmap) minor-mode-overriding-map-alist)))
 
 ;;;###autoload
 (add-to-list 'auto-mode-alist
diff --git a/tests/markdown-test.el b/tests/markdown-test.el
index 8d68269f19..ae8820b01c 100644
--- a/tests/markdown-test.el
+++ b/tests/markdown-test.el
@@ -4957,6 +4957,192 @@ date = 2015-08-13 11:35:25 EST
 
 ;;; Movement tests:
 
+(ert-deftest test-markdown-movement/beginning-of-line ()
+  "Test beginning of line movement"
+  (markdown-test-string "Some text\nSome other text"
+    (goto-char (point-max))
+    (markdown-beginning-of-line)
+    (should (bolp)))
+  (markdown-test-string "## Headline\n### Sub"
+    (goto-char (point-max))
+    (outline-hide-sublevels 2)
+    (markdown-beginning-of-line)
+    (should (= (line-beginning-position) 1)))
+
+  ;; With `visual-line-mode' active, move to beginning of visual line.
+  (markdown-test-string "Text "
+    (visual-line-mode)
+    (dotimes (_ 1000) (insert "Text "))
+    (goto-char (point-max))
+    (markdown-beginning-of-line)
+    (should-not (bolp)))
+
+  ;; In a wide headline, with `visual-line-mode', prefer going to the
+  ;; beginning of a visual line than to the logical beginning of line,
+  ;; even if special movement is active.
+  (markdown-test-string "## Headline"
+    (visual-line-mode)
+    (goto-char (point-max))
+    (dotimes (_ 1000) (insert "Text "))
+    (markdown-beginning-of-line)
+    (should-not (bolp)))
+  (markdown-test-string "## Headline"
+    (visual-line-mode)
+    (goto-char (point-max))
+    (dotimes (_ 1000) (insert "Text "))
+    (let ((markdown-special-ctrl-a/e t))
+      (markdown-beginning-of-line))
+    (should-not (bolp)))
+
+  ;; At an headline with special movement, first move at beginning of
+  ;; title, then at the beginning of line, rinse, repeat.
+  (markdown-test-string "## Headline"
+    (let ((markdown-special-ctrl-a/e t))
+      (goto-char (point-max))
+      (markdown-beginning-of-line)
+      (should (looking-at "Headline"))
+      (markdown-beginning-of-line)
+      (should (bolp))
+      (markdown-beginning-of-line)
+      (should (looking-at "Headline"))))
+  (markdown-test-string "## Headline"
+    (let ((markdown-special-ctrl-a/e nil))
+      (goto-char (point-max))
+      (markdown-beginning-of-line)
+      (should (bolp))))
+
+  ;; At an headline with reversed movement, first move to beginning of
+  ;; line, then to the beginning of title.
+  (markdown-test-string "## Headline"
+    (let ((markdown-special-ctrl-a/e 'reversed)
+               (this-command last-command))
+      (goto-char (point-max))
+      (markdown-beginning-of-line)
+      (should (bolp))
+      (markdown-beginning-of-line)
+      (should (looking-at "Headline"))))
+
+  ;; At an item with special movement, first move after to beginning
+  ;; of title, then to the beginning of line, rinse, repeat.
+  (markdown-test-string "- [ ] Item"
+    (let ((markdown-special-ctrl-a/e nil))
+      (goto-char (point-max))
+      (markdown-beginning-of-line)
+      (should (bolp))))
+  (markdown-test-string "- [ ] Item"
+    (let ((markdown-special-ctrl-a/e t))
+      (goto-char (point-max))
+      (markdown-beginning-of-line)
+      (should (looking-at "Item"))
+      (markdown-beginning-of-line)
+      (should (bolp))
+      (markdown-beginning-of-line)
+      (should (looking-at "Item"))))
+  (markdown-test-string "- [ ] Item"
+    (let ((markdown-special-ctrl-a/e 'reversed)
+               (this-command last-command))
+      (goto-char (point-max))
+      (markdown-beginning-of-line)
+      (should (bolp))
+      (markdown-beginning-of-line)
+      (should (looking-at "Item")))))
+
+(ert-deftest test-markdown-movement/end-of-line ()
+  "Test end of line movement"
+  (markdown-test-string
+   "Some text\nSome other text"
+   (markdown-end-of-line)
+   (should (eolp)))
+
+  ;; With `visual-line-mode' active, move to end of visual line.
+  ;; However, never go past ellipsis.
+  (markdown-test-string
+   "Some Text"
+   (visual-line-mode)
+   (goto-char (point-max))
+   (dotimes (_ 1000) (insert "Text "))
+   (markdown-end-of-line)
+   (should-not (eolp)))
+
+  ;; In a wide headline, with `visual-line-mode', prefer going to end
+  ;; of visible line if tags, or end of line, are farther.
+  (markdown-test-string "Text "
+    (visual-line-mode)
+    (goto-char (point-max))
+    (dotimes (_ 1000) (insert "Text "))
+    (goto-char (point-min))
+    (markdown-end-of-line)
+    (should-not (eolp)))
+  (markdown-test-string "## Heading\nSome contents"
+    (goto-char (point-max))
+    (outline-hide-sublevels 2)
+    (markdown-end-of-line)
+    (should (= (line-beginning-position) 1)))
+
+  ;; In a wide headline, with `visual-line-mode', prefer going to end
+  ;; of visible line if tags, or end of line, are farther.
+  (markdown-test-string "## Heading "
+    (visual-line-mode)
+    (goto-char (point-max))
+    (dotimes (_ 1000) (insert "Text "))
+    (goto-char (point-min))
+    (markdown-end-of-line)
+    (should-not (eolp)))
+  (markdown-test-string "## Heading ##"
+    (visual-line-mode)
+    (re-search-forward "## Heading ")
+    (dotimes (_ 1000) (insert "Text "))
+    (goto-char (point-min))
+    (markdown-end-of-line)
+    (should-not (eolp)))
+  ;; At an headline without special movement, go to end of line.
+  ;; However, never go past ellipsis.
+  (markdown-test-string "## Headline ##"
+    (let ((markdown-special-ctrl-a/e nil))
+      (markdown-end-of-line)
+      (should (eolp))))
+  (markdown-test-string "## Headline ##\n### Sub"
+    (let ((markdown-special-ctrl-a/e nil))
+      (goto-char (point-max))
+      (outline-hide-sublevels 2)
+      (markdown-end-of-line)
+      (should (= (line-beginning-position) 1))))
+
+  ;; At an headline with special movement, first move before close tag,
+  ;; then at the end of line, rinse, repeat.  However, never go past
+  ;; ellipsis.
+  (markdown-test-string "## Headline ##"
+    (let ((markdown-special-ctrl-a/e t))
+      (markdown-end-of-line)
+      (should (looking-at " ##"))
+      (markdown-end-of-line)
+      (should (eolp))
+      (markdown-end-of-line)
+      (should (looking-at " ##"))))
+  (markdown-test-string "## Headline ##\n### Sub"
+    (let ((markdown-special-ctrl-a/e t))
+      (goto-char (point-max))
+      (outline-hide-sublevels 2)
+      (markdown-end-of-line)
+      (should (= (line-beginning-position) 1))))
+
+  ;; At an headline, with reversed movement, first go to end of line,
+  ;; then before tags.  However, never go past ellipsis.
+  (markdown-test-string "## Headline ##"
+    (let ((markdown-special-ctrl-a/e 'reversed)
+               (this-command last-command))
+      (markdown-end-of-line)
+      (should (eolp))
+      (markdown-end-of-line)
+      (should (looking-at " ##"))))
+  (markdown-test-string "## Headline ##\n### Sub"
+    (let ((markdown-special-ctrl-a/e 'reversed)
+               (this-command last-command))
+      (goto-char (point-max))
+      (outline-hide-sublevels 2)
+      (markdown-end-of-line)
+      (should (= (line-beginning-position) 1)))))
+
 (ert-deftest test-markdown-movement/defun ()
   "Test defun navigation."
   (markdown-test-file "outline.text"



reply via email to

[Prev in Thread] Current Thread [Next in Thread]