Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 'dw' not to delete to the next line. #999

Merged
merged 2 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 48 additions & 10 deletions extensions/vi-mode/commands.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,37 @@
(previous-line n)
(fall-within-line (current-point)))

(defun on-only-space-line-p (point)
(with-point ((p point))
(line-end p)
(skip-whitespace-backward p t)
(bolp p)))

(define-command vi-forward-word-begin (&optional (n 1)) ("p")
(dotimes (i n)
(forward-word-begin #'char-type)))
(let ((start-line (line-number-at-point (current-point))))
(dotimes (i n)
(forward-word-begin #'char-type))
;; In operator-pending mode, this motion behaves differently.
(when (operator-pending-mode-p)
(with-point ((p (current-point)))
;; Go back to the end of the previous line when the END point is in the next line.
;; For example, when the cursor is at [b],
;; foo [b]ar
;; baz
;; 'dw' deletes only the 'bar', instead of deleting to the beginning of the next word.
(skip-whitespace-backward p t)
(when (bolp p)
(line-offset p -1)
(line-end p)
(loop while (and (< start-line
(line-number-at-point p))
(on-only-space-line-p p))
do (line-offset p -1)
(line-end p))
;; Skip this line if the previous line is empty
(when (bolp p)
(character-offset p 1))
(move-point (current-point) p))))))

(define-command vi-backward-word-begin (&optional (n 1)) ("p")
(dotimes (i n)
Expand Down Expand Up @@ -229,6 +257,13 @@
(max 0
(min (1- (length (line-string (current-point)))) pos))))
(when (eq 'vi-delete (command-name (this-command)))
;; After 'dw' or 'dW', move to the first non-blank char
(when (and (this-motion-command)
(member (command-name (this-motion-command))
'(vi-forward-word-begin
vi-forward-word-begin-broad)
:test 'eq))
(skip-chars-forward (current-point) '(#\Space #\Tab)))
(fall-within-line (current-point)))))

(define-vi-operator vi-delete-line (start end type) ("<R>")
Expand Down Expand Up @@ -575,14 +610,14 @@
(back-to-indentation p))
(line-start p))))))
(line-end p)
(insert-character p #\newline)
(move-to-column p column t)
(change-state 'insert)))
(change-state 'insert)
(insert-character p #\Newline)
(move-to-column p column t)))

(define-command vi-open-above () ()
(line-start (current-point))
(open-line 1)
(change-state 'insert))
(change-state 'insert)
(open-line 1))

(define-command vi-jump-back (&optional (n 1)) ("p")
(dotimes (i n)
Expand All @@ -594,8 +629,8 @@

(define-command vi-repeat (n) ("P")
(when *last-repeat-keys*
(let ((lem:*pre-command-hook* nil)
(lem:*post-command-hook* nil))
(let ((*enable-repeat-recording* nil)
(prev-state (current-state)))
(let ((keyseq (if n
(append
(map 'list (lambda (char) (lem:make-key :sym (string char)))
Expand All @@ -604,7 +639,10 @@
*last-repeat-keys*))
;; Clear the universal argument for vi-repeat
(lem/universal-argument::*argument* (lem/universal-argument::make-arg-state)))
(execute-key-sequence keyseq)))))
(execute-key-sequence keyseq)
(unless (state= prev-state (current-state))
(change-state prev-state))
(fall-within-line (current-point))))))

(define-command vi-normal () ()
(change-state 'normal))
Expand Down
19 changes: 19 additions & 0 deletions extensions/vi-mode/commands/utils.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
:eolp
:goto-eol
:fall-within-line
:operator-pending-mode-p
:this-motion-command
:read-universal-argument
:*cursor-offset*
:vi-command
Expand All @@ -33,6 +35,8 @@
(in-package :lem-vi-mode/commands/utils)

(defvar *cursor-offset* -1)
(defvar *operator-pending-mode* nil)
(defvar *this-motion-command* nil)

(defun bolp (point)
"Return t if POINT is at the beginning of a line."
Expand All @@ -55,6 +59,12 @@
(when (eolp point)
(goto-eol point)))

(defun operator-pending-mode-p ()
*operator-pending-mode*)

(defun this-motion-command ()
*this-motion-command*)

(defun read-universal-argument ()
(loop :for key := (read-key)
:for char := (key-to-char key)
Expand Down Expand Up @@ -85,6 +95,14 @@

(defclass vi-operator (vi-command) ())

(defmethod execute :around (mode (command vi-operator) uarg)
(declare (ignore mode uarg))
;; XXX: This flag will be rewritten as a code to check the current state
;; when operator-pending state is implemented.
(let ((*operator-pending-mode* t)
(*this-motion-command* nil))
(call-next-method)))

(defvar *vi-origin-point*)

(defun parse-vi-motion-arg-list (arg-list)
Expand Down Expand Up @@ -130,6 +148,7 @@
(check-type motion (or null symbol))
(with-point ((start (current-point)))
(labels ((call-motion (command uarg)
(setf *this-motion-command* command)
(let ((*cursor-offset* 0))
(save-excursion
(ignore-errors
Expand Down
9 changes: 9 additions & 0 deletions extensions/vi-mode/core.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
(:export :*enable-hook*
:*disable-hook*
:*last-repeat-keys*
:*enable-repeat-recording*
:vi-state
:vi-mode
:define-vi-state
:current-state
:state=
:change-state
:with-state
:*command-keymap*
Expand All @@ -34,6 +36,8 @@
(defvar *enable-hook* '())
(defvar *disable-hook* '())

(defvar *enable-repeat-recording* t)

(defun enable-hook ()
(run-hooks *enable-hook*))

Expand Down Expand Up @@ -123,6 +127,11 @@

(defvar *current-state* nil)

(defun state= (state1 state2)
(and (typep state1 'vi-state)
(typep state2 'vi-state)
(eq (state-name state1) (state-name state2))))

;;; vi-state methods
(defmacro define-vi-state (name direct-super-classes direct-slot-specs &rest options)
(let ((cleaned-super-classes (if (null direct-super-classes) '(vi-state) direct-super-classes)))
Expand Down
11 changes: 9 additions & 2 deletions extensions/vi-mode/tests/commands.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
(ok (buf= #?"[1]\n2\n3\n"))
(cmd "<C-r>")
(ok (buf= #?"1: Hello[!]\n2\n3\n"))
(cmd "o4: World!<Esc>")
(cmd "o4: World!<Esc>a<Esc>")
(ok (buf= #?"1: Hello!\n4: World[!]\n2\n3\n"))
(cmd "2u")
(ok (buf= #?"[1]\n2\n3\n")))))
(ok (buf= #?"[1]\n2\n3\n")))
(with-vi-buffer (#?"[1]\n2\n3\n")
(cmd "a: Hello!<Esc>")
(ok (buf= #?"1: Hello[!]\n2\n3\n"))
(cmd "ja: World!<Esc>")
(ok (buf= #?"1: Hello!\n2: World[!]\n3\n"))
(cmd "u")
(ok (buf= #?"1: Hello!\n[2]\n3\n")))))
15 changes: 13 additions & 2 deletions extensions/vi-mode/tests/operator.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
(ok (buf= #?"ghi\n[]")))
(with-vi-buffer (#?"[a]bc\ndef\nghi\njkl\n")
(cmd "1000dd")
(ok (buf= "[]")))))
(ok (buf= "[]")))
(testing "with vi-forward-word-begin"
(with-vi-buffer (#?"[a]bc\n def\n")
(cmd "dw")
(ok (buf= #?"[\n] def\n"))
(cmd "dw")
(ok (buf= #?" [d]ef\n"))))))

(deftest vi-join-line
(with-fake-interface ()
Expand All @@ -46,4 +52,9 @@
(cmd "2d2d")
(ok (buf= #?"[5]:mno\n6:opq\n7:rst\n8:uvw"))
(cmd "2.")
(ok (buf= #?"[7]:rst\n8:uvw")))))
(ok (buf= #?"[7]:rst\n8:uvw")))
(with-vi-buffer (#?"[f]oo\nbar\nbaz\n")
(cmd "A-fighters<Esc>")
(ok (buf= #?"foo-fighter[s]\nbar\nbaz\n"))
(cmd "j^.")
(ok (buf= #?"foo-fighters\nbar-fighter[s]\nbaz\n")))))
3 changes: 1 addition & 2 deletions extensions/vi-mode/tests/utils.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,7 @@
(defun cmd (keys)
(check-type keys string)
(diag (format nil "[cmd] ~A~%" keys))
(let ((*this-command-keys* nil)
(*input-hook* (cons (cons (lambda (event)
(let ((*input-hook* (cons (cons (lambda (event)
(push event *this-command-keys*))
0)
*input-hook*)))
Expand Down
37 changes: 31 additions & 6 deletions extensions/vi-mode/vi-mode.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
:lem-vi-mode/ex)
(:import-from :lem-vi-mode/options
:vi-option-value)
(:import-from :lem-vi-mode/commands
:vi-open-below
:vi-open-above)
(:import-from :lem-vi-mode/commands/utils
:vi-command
:vi-command-repeat)
(:import-from :alexandria
:appendf)
(:export :vi-mode
:define-vi-state
:*command-keymap*
Expand All @@ -17,14 +22,34 @@
(in-package :lem-vi-mode)

(defmethod post-command-hook ((state normal))
(let ((command (this-command)))
(when (and (typep command 'vi-command)
(eq (vi-command-repeat command) t))
(setf *last-repeat-keys* (vi-this-command-keys)))))
(when *enable-repeat-recording*
(let ((command (this-command)))
(when (and (typep command 'vi-command)
(eq (vi-command-repeat command) t))
(setf *last-repeat-keys* (vi-this-command-keys))))))

(defmethod post-command-hook ((state insert))
(when (eq :separator (lem-base::last-edit-history (current-buffer)))
(vector-pop (lem-base::buffer-edit-history (current-buffer)))))
(let ((command (this-command)))
(when *enable-repeat-recording*
(unless (or (and (typep command 'vi-command)
(eq (vi-command-repeat command) nil))
(eq (command-name (this-command)) 'vi-end-insert))
(appendf *last-repeat-keys*
(vi-this-command-keys))))
(when (and (member (command-name command)
'(self-insert
;; XXX: lem:call-command always adds a undo boundary
;; Delete the last boundary after these commands executed.
vi-open-below
vi-open-above)
:test 'eq)
(eq :separator (lem-base::last-edit-history (current-buffer))))
(vector-pop (lem-base::buffer-edit-history (current-buffer))))))

(defmethod state-enabled-hook ((state insert))
(when *enable-repeat-recording*
(setf *last-repeat-keys* nil))
(buffer-undo-boundary))

(defmethod state-disabled-hook ((state insert))
(unless (eq :separator (lem-base::last-edit-history (current-buffer)))
Expand Down
3 changes: 2 additions & 1 deletion src/input.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
(do-command-loop (:interactive nil)
(when (null *unread-keys*)
(return))
(call-command (read-command) nil))))
(let ((*this-command-keys* nil))
(call-command (read-command) nil)))))

(defun sit-for (seconds &optional (update-window-p t) (force-update-p nil))
(when update-window-p (redraw-display force-update-p))
Expand Down