branch: elpa/evil-emacs-cursor-model-mode
commit 5957816d76115152d8e5066fca6ea0c408935d9a
Author: maxfriis <[email protected]>
Commit: GitHub <[email protected]>

    First commit after name change
---
 evil-emacs-cursor-model-mode.el | 264 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 264 insertions(+)

diff --git a/evil-emacs-cursor-model-mode.el b/evil-emacs-cursor-model-mode.el
new file mode 100644
index 0000000000..6d3cbeca16
--- /dev/null
+++ b/evil-emacs-cursor-model-mode.el
@@ -0,0 +1,264 @@
+;;; evil-emacs-cursor-model-mode.el --- Emacs' cursor model in evil-mode -*- 
lexical-binding: t; -*-
+
+;; ============================================================================
+;;; License:
+;; ============================================================================
+;; Creative Commons Attribution-ShareAlike 4.0 International License
+;; [[https://creativecommons.org/licenses/by-sa/4.0/]]
+
+;; A special thanks to Toby Cubitt who coded the cursor model.
+;; Peter Friis Jensen made it a mode and swapped some keybindings.
+
+;; Author: Toby Cubitt
+;; Maintainer: Peter Friis Jensen <[email protected]>
+;; URL: https://github.com/maxfriis/evil-emacs-cursor-model-mode
+;; Created: 2025-11-15
+;; Version: 0.1.1
+;; Keywords: convenience, files
+;; Package-Requires: ((emacs "29.1") (evil "1.15.0"))
+
+;; ============================================================================
+;;; TODO:
+;; ============================================================================
+
+;; ============================================================================
+;;; Commentary:
+;; ============================================================================
+;; Emacs' cursor between characters model for cursor positioning in
+;; `evil-mode' instead of Vim's normal-state cursor on characters model.
+
+;; ============================================================================
+;;; Code:
+;; ============================================================================
+(require 'evil)
+
+(defvar evil-emacs-cursor-model-move-cursor-back-init evil-move-cursor-back
+  "For toggling the variable with `evil-emacs-cursor-model-mode'.")
+(defvar evil-emacs-cursor-model-move-beyond-eol-init evil-move-beyond-eol
+  "For toggling the variable with `evil-emacs-cursor-model-mode'.")
+(defvar evil-emacs-cursor-model-highlight-closing-paren-at-point-states-init 
evil-highlight-closing-paren-at-point-states
+  "For toggling the variable with `evil-emacs-cursor-model-mode'.")
+
+;; ============================================================================
+;;; The minor mode
+;; ============================================================================
+(define-minor-mode evil-emacs-cursor-model-mode
+  "Mode for using Emacs' cursor model in `evil-mode's normal state.
+\nThe mode swap \"a\"/\"A\", \"o\"/\"O\" and \"p\"/\"P\" compared to Vim's 
normal state keys.
+The idea is to avoid the <shift> layer when dealing with the current line.
+Layers can then be replaced with a motion with equivalent efficiency.
+\nEmbrace the mindset of Emacs' cursor model and motions among line nuggets.
+Maybe fewer layers are better for your Emacs pinky?"
+  :lighter nil
+  :global t
+  :require 'evil-emacs-cursor-model-mode
+  :group 'evil
+  (cond
+   (evil-emacs-cursor-model-mode
+    (unless evil-mode
+      (evil-mode 1))
+    ;; 
----------------------------------------------------------------------------
+    ;; Cursor related `evil-mode' settings.
+    (setq
+     evil-move-cursor-back nil
+     evil-move-beyond-eol t
+     evil-highlight-closing-paren-at-point-states nil)
+    ;; 
----------------------------------------------------------------------------
+    ;; Rebinding relevamt `evil-org-mode' commands.
+    (evil-define-minor-mode-key 'normal 'evil-org-mode
+      "a"  #'evil-org-append-line
+      "A"  nil
+      "o"  #'evil-org-open-above
+      "O"  #'evil-org-open-below))
+   (t ; else
+    ;; 
----------------------------------------------------------------------------
+    ;; Back to `evil-mode' defaults when `evil-emacs-cursor-model-mode' is 
disabled.
+    (setq
+     evil-move-cursor-back evil-emacs-cursor-model-move-cursor-back-init
+     evil-move-beyond-eol evil-emacs-cursor-model-move-beyond-eol-init
+     evil-highlight-closing-paren-at-point-states 
evil-emacs-cursor-model-highlight-closing-paren-at-point-states-init)
+    ;; 
----------------------------------------------------------------------------
+    ;; `evil-org-mode' defaults.
+    (evil-define-minor-mode-key 'normal 'evil-org-mode
+      "a"  nil
+      "A"  #'evil-org-append-line
+      "o"  #'evil-org-open-below
+      "O"  #'evil-org-open-above))))
+
+;; ============================================================================
+;;; Remappings implementing the cursor model
+;; ============================================================================
+(defvar evil-emacs-cursor-model-mode-map (make-sparse-keymap)
+  "Keymap for `evil-emacs-cursor-model-mode'.")
+(add-to-list 'minor-mode-map-alist
+             (cons 'evil-emacs-cursor-model-mode
+                   evil-emacs-cursor-model-mode-map) t)
+;; ----------------------------------------------------------------------------
+;; Motions.
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-find-char-to>" #'evil-find-char)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-find-char>" 
#'evil-emacs-cursor-model-find-char-after)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-forward-word-end>" 
#'evil-emacs-cursor-model-forward-after-word-end)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-forward-WORD-end>" 
#'evil-emacs-cursor-model-forward-after-WORD-end)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-backward-word-end>" 
#'evil-emacs-cursor-model-backward-after-word-end)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-backward-WORD-end>" 
#'evil-emacs-cursor-model-backward-after-WORD-end)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-jump-item>" 
#'evil-emacs-cursor-model-jump-after-item)
+;; ----------------------------------------------------------------------------
+;; Commands.
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-append>" #'evil-append-line)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-open-above>" #'evil-open-below)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-open-below>" #'evil-open-above)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-paste-before>" #'evil-paste-after)
+(keymap-set evil-emacs-cursor-model-mode-map
+            "<remap> <evil-paste-after>" #'evil-paste-before)
+
+;; ============================================================================
+;;; Evil commands implementing Emacs' cursor model
+;; ============================================================================
+(evil-define-motion evil-emacs-cursor-model-find-char-after (count char)
+  "Move point immediately after the next COUNT'th occurrence of CHAR.
+Movement is restricted to the current line unless `evil-cross-lines' is 
non-nil."
+  :type inclusive
+  (interactive "<c><C>")
+  (unless count (setq count 1))
+  (if (< count 0)
+      (evil-find-char-backward (- count) char)
+    ;; else
+    (when (= (char-after) char)
+      (cl-decf count))
+    (evil-find-char count char)
+    (forward-char))
+  (setq evil-last-find (list #'evil-emacs-cursor-model-find-char-after char (> 
count 0))))
+
+(defun evil-emacs-cursor-model-forward-after-end (thing &optional count)
+  "Move forward to end of THING.
+The motion is repeated COUNT times."
+  (setq count (or count 1))
+  (cond
+   ((> count 0)
+    (forward-thing thing count))
+   (t
+    (unless (bobp) (forward-char -1))
+    (let ((bnd (bounds-of-thing-at-point thing))
+          rest)
+      (when bnd
+        (cond
+         ((< (point) (cdr bnd)) (goto-char (car bnd)))
+         ((= (point) (cdr bnd)) (cl-incf count))))
+      (condition-case nil
+          (when (zerop (setq rest (forward-thing thing count)))
+            (end-of-thing thing))
+        (error))
+      rest))))
+
+(defun evil-emacs-cursor-model-backward-after-end (thing &optional count)
+  "Move backward to end of THING.
+The motion is repeated COUNT times.  This is the same as calling
+`evil-emacs-cursor-model-forward-after-word-end' with -COUNT."
+  (evil-emacs-cursor-model-forward-after-end thing (- (or count 1))))
+
+(evil-define-motion evil-emacs-cursor-model-forward-after-word-end (count 
&optional bigword)
+  "Move the cursor to the end of the COUNT'th next word.
+If BIGWORD is non-nil, move by WORDS."
+  :type inclusive
+  (let ((thing (if bigword 'evil-WORD 'evil-word))
+        (count (or count 1)))
+    (evil-signal-at-bob-or-eob count)
+    (evil-emacs-cursor-model-forward-after-end thing count)))
+
+(evil-define-motion evil-emacs-cursor-model-forward-after-WORD-end (count)
+  "Move the cursor to the end of the COUNT'th next WORD."
+  :type inclusive
+  (evil-emacs-cursor-model-forward-after-word-end count t))
+
+(evil-define-motion evil-emacs-cursor-model-backward-after-word-end (count 
&optional bigword)
+  "Move the cursor to the end of the COUNT'th previous word.
+If BIGWORD is non-nil, move by WORDS."
+  :type inclusive
+  (let ((thing (if bigword 'evil-WORD 'evil-word)))
+    (evil-signal-at-bob-or-eob (- (or count 1)))
+    (evil-emacs-cursor-model-backward-after-end thing count)))
+
+(evil-define-motion evil-emacs-cursor-model-backward-after-WORD-end (count)
+  "Move the cursor to the end of the COUNT'th previous WORD."
+  :type inclusive
+  (evil-emacs-cursor-model-backward-after-word-end count t))
+
+;; ----------------------------------------------------------------------------
+;;;; Redefine inclusive motion type to not include character after point.
+(evil-define-type inclusive
+  "Return the positions unchanged, with some exceptions.
+If the end position is at the beginning of a line, then:
+
+* If the beginning position is at or before the first non-blank
+  character on the line, return `line' (expanded)."
+  :expand (lambda (beg end) (evil-range beg end))
+  :contract (lambda (beg end) (evil-range beg end))
+  :normalize (lambda (beg end)
+               (cond
+                ((progn
+                   (goto-char end)
+                   (and (/= beg end) (bolp)))
+                 (setq end (max beg (1- end)))
+                 (cond
+                  ((progn
+                     (goto-char beg)
+                     (looking-back "^[\f\s\t\v]*" (line-beginning-position)))
+                   (evil-expand beg end 'line))
+                  (t
+                   (unless evil-cross-lines
+                     (setq end (max beg (1- end))))
+                   (evil-expand beg end 'inclusive))))
+                (t
+                 (evil-range beg end))))
+  :string (lambda (beg end)
+            (let ((width (- end beg)))
+              (format "%s character%s" width
+                      (if (= width 1) "" "s")))))
+
+;; ----------------------------------------------------------------------------
+;;;; Make "e" search offset put point after last character.
+(defun evil-emacs-cursor-model-ad-evil-ex-search-adjust-offset (offset)
+  "Make `evil-mode's \"e\" search OFFSET put point after last character."
+  (unless (zerop (length offset))
+    (save-match-data
+      (string-match
+       "^\\([esb]\\)?\\(\\([+-]\\)?\\([0-9]*\\)\\)$"
+       offset)
+      (when (and (= (aref offset (match-beginning 1)) ?e)
+                 (not (bobp)))
+        (forward-char 1)))))
+
+(advice-add
+ 'evil-ex-search-goto-offset
+ :after #'evil-emacs-cursor-model-ad-evil-ex-search-adjust-offset)
+
+;; ----------------------------------------------------------------------------
+;;;; `evil-jump-item' move point after matching delimeter if it jumps forward.
+(evil-define-motion evil-emacs-cursor-model-jump-after-item (count)
+  "Find the next item in this line immediately before
+or somewhere after the cursor and jump to the corresponding one."
+  :jump t
+  :type inclusive
+  (let ((pos (point)))
+    (unless (or (bolp) (bobp)) (backward-char))
+    (condition-case nil
+        (evil-jump-item count)
+      (user-error (goto-char pos)))
+    (unless (< (point) pos)
+      (goto-char pos)
+      (evil-jump-item count)
+      (when (> (point) pos) (forward-char)))))
+
+(provide 'evil-emacs-cursor-model-mode)
+;;; evil-emacs-cursor-model-mode.el ends here

Reply via email to