Skip to content

Commit df9dcb7

Browse files
committed
refactor: Improve motion collection and error handling in easymotion
1 parent cd4042a commit df9dcb7

File tree

1 file changed

+91
-62
lines changed

1 file changed

+91
-62
lines changed

evil-easymotion.el

Lines changed: 91 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
(const :tag "De Bruijn" de-bruijn)
8383
(const :tag "Default" nil)))
8484

85-
(defcustom evilem-max-iterations 1000
85+
(defcustom evilem-max-iterations 4000
8686
"Maximum number of iterations allowed when collecting positions.
8787
This is a safety measure to prevent infinite loops."
8888
:type 'integer
@@ -159,91 +159,120 @@ This is a safety measure to prevent infinite loops."
159159

160160
(narrow-to-region (max beg (window-start))
161161
(min end (window-end))))
162-
163-
;; Detect motion direction based on function name
162+
163+
;; Advanced motion collection with better error handling
164164
(let* ((motion-name (if (symbolp func) (symbol-name func) "unknown"))
165-
(direction (cond
166-
((string-match-p "previous\\|backward\\|up" motion-name) 'backward)
167-
((string-match-p "next\\|forward\\|down" motion-name) 'forward)
168-
(t 'unknown)))
165+
;; More fine-grained motion classification
166+
(motion-type
167+
(cond
168+
((string-match-p "line\\|paragraph" motion-name) 'line)
169+
((string-match-p "word\\|char\\|sentence" motion-name) 'char)
170+
(t 'unknown)))
171+
(direction
172+
(cond
173+
((string-match-p "previous\\|backward\\|up" motion-name) 'backward)
174+
((string-match-p "next\\|forward\\|down" motion-name) 'forward)
175+
(t 'unknown)))
176+
;; Track positions more efficiently with a hash table
177+
(seen-positions (make-hash-table :test 'eql :size 100))
169178
(prev-point nil)
170-
(position-count (make-hash-table :test 'equal))
171179
(stalled-count 0)
172-
(making-progress t)
173180
(iteration-count 0))
174-
175-
(while (and making-progress
176-
(< iteration-count evilem-max-iterations)
177-
(< stalled-count 3) ;; Break after no movement 3 times
181+
182+
(while (and (< iteration-count evilem-max-iterations)
183+
(< stalled-count 3) ;; Break after too many failures
178184
(ignore-errors
179185
(setq this-command func
180186
last-command func)
181-
187+
182188
;; Execute the motion command
183189
(let ((pre-pos (point)))
190+
;; Run the motion function
184191
(call-interactively func)
185-
186-
;; Track if we're stuck at the same position
187-
(if (= pre-pos (point))
188-
(setq stalled-count (1+ stalled-count))
189-
(setq stalled-count 0))
190-
191-
;; For direction-based movements, also check if we're
192-
;; moving in the expected direction
193-
(when (and prev-point (not (eq direction 'unknown)))
194-
(when (or (and (eq direction 'backward) (>= (point) prev-point))
195-
(and (eq direction 'forward) (<= (point) prev-point)))
196-
;; If we're not moving in the expected direction,
197-
;; increment stall counter
198-
(setq stalled-count (1+ stalled-count)))))
199-
200-
;; Handle overlays - completely mode-agnostic approach
201-
(unless include-invisible
202-
(let ((overlays (overlays-at (point)))
203-
(skipped nil))
204-
205-
;; Check all overlays at point
192+
193+
;; Check if we're making progress
194+
(cond
195+
;; Not moving at all
196+
((= pre-pos (point))
197+
(setq stalled-count (1+ stalled-count)))
198+
199+
;; Moving in the wrong direction
200+
((and prev-point
201+
(not (eq direction 'unknown))
202+
(or (and (eq direction 'backward) (> (point) prev-point))
203+
(and (eq direction 'forward) (< (point) prev-point))))
204+
(setq stalled-count (1+ stalled-count)))
205+
206+
;; Otherwise, we're moving well
207+
(t (setq stalled-count 0))))
208+
209+
;; Handle invisible text properties and overlays
210+
(when (and (not include-invisible)
211+
(or (invisible-p (point)) ;; Check text properties too
212+
(let ((ovs (overlays-at (point))))
213+
(seq-some (lambda (ov)
214+
(overlay-get ov 'invisible))
215+
ovs))))
216+
;; Skip over the invisible area in the appropriate direction
217+
(let* ((pos (point))
218+
;; Find extent of invisibility
219+
(invisible-beg pos)
220+
(invisible-end pos)
221+
;; Find overlays that might cause invisibility
222+
(overlays (overlays-at pos)))
223+
224+
;; Process text properties first (they're often faster)
225+
(while (and (> invisible-beg (point-min))
226+
(invisible-p (1- invisible-beg)))
227+
(setq invisible-beg (1- invisible-beg)))
228+
229+
(while (and (< invisible-end (point-max))
230+
(invisible-p (1+ invisible-end)))
231+
(setq invisible-end (1+ invisible-end)))
232+
233+
;; Then check overlays to see if they extend the invisible region
206234
(dolist (ov overlays)
207-
;; Skip any overlays with invisible property
208235
(when (overlay-get ov 'invisible)
209-
;; Jump to the appropriate end of the overlay based on motion direction
210-
(goto-char (if (eq direction 'backward)
211-
(max (point-min) (1- (overlay-start ov)))
212-
(min (point-max) (1+ (overlay-end ov)))))
213-
(setq skipped t)))
214-
215-
;; Re-run motion if we skipped an overlay
216-
(when skipped
217-
(call-interactively func))))
218-
219-
;; Cycle detection - if we've seen this position too many times
220-
(let ((pos-key (format "%s-%s" (point) (get-buffer-window))))
221-
(puthash pos-key
222-
(1+ (gethash pos-key position-count 0))
223-
position-count)
224-
(when (> (gethash pos-key position-count 0) 2)
225-
(setq making-progress nil)))
226-
236+
(setq invisible-beg (min invisible-beg (overlay-start ov))
237+
invisible-end (max invisible-end (overlay-end ov)))))
238+
239+
;; Now jump past the invisible region based on direction
240+
(goto-char (if (eq direction 'backward)
241+
(max (point-min) (1- invisible-beg))
242+
(min (point-max) (1+ invisible-end))))
243+
244+
;; Rerun the motion for consistency
245+
(call-interactively func)))
246+
247+
;; Efficiently track position visit counts using the hash table
248+
(let ((pos-hash (point)))
249+
(puthash pos-hash
250+
(1+ (gethash pos-hash seen-positions 0))
251+
seen-positions)
252+
;; If we've seen this position too many times, stop
253+
(when (> (gethash pos-hash seen-positions) 2)
254+
(setq stalled-count 3)))
255+
227256
;; Return t to continue the loop
228257
t)
229-
258+
230259
;; Check that the new position is unique
231260
(progn
232261
(setq point (cons (point) (get-buffer-window)))
233262
(not (member point points))))
234-
235-
;; Record the point and update tracking variables
263+
264+
;; Record this position and advance counters
236265
(push point points)
237266
(setq prev-point (point))
238267
(setq iteration-count (1+ iteration-count)))))))
239-
240-
;; Handle multiple functions
268+
269+
;; Handle non-function case (list of functions)
241270
(setq points (cl-remove-duplicates
242271
(cl-mapcan (lambda (f)
243272
(evilem--collect f scope all-windows))
244273
func))))
245-
246-
;; Post-process and return the points
274+
275+
;; Return the sorted points
247276
(funcall (or collect-postprocess
248277
#'evilem--default-collect-postprocess)
249278
points)))
@@ -601,4 +630,4 @@ This is a safety measure to prevent infinite loops."
601630
(define-key evilem-map "+" #'evilem-motion-next-line-first-non-blank)
602631

603632
(provide 'evil-easymotion)
604-
;;; evil-easymotion.el ends here
633+
;;; evil-easymotion.el ends here

0 commit comments

Comments
 (0)