Skip to content

Commit cd4042a

Browse files
committed
fix: Add safety measures to prevent infinite loops
1 parent 6b95442 commit cd4042a

File tree

1 file changed

+73
-46
lines changed

1 file changed

+73
-46
lines changed

evil-easymotion.el

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

85+
(defcustom evilem-max-iterations 1000
86+
"Maximum number of iterations allowed when collecting positions.
87+
This is a safety measure to prevent infinite loops."
88+
:type 'integer
89+
:group 'evilem)
90+
8591
(defvar evilem-map (make-sparse-keymap)
8692
"Keymap used for the default bindings")
8793

@@ -154,69 +160,90 @@
154160
(narrow-to-region (max beg (window-start))
155161
(min end (window-end))))
156162

157-
;; Use a robust approach to prevent infinite loops by tracking overlay interactions
158-
(let ((prev-point nil)
159-
(seen-overlays (make-hash-table :test 'eq))
160-
(same-overlay-count 0)
161-
(making-progress t))
163+
;; Detect motion direction based on function name
164+
(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)))
169+
(prev-point nil)
170+
(position-count (make-hash-table :test 'equal))
171+
(stalled-count 0)
172+
(making-progress t)
173+
(iteration-count 0))
162174

163175
(while (and making-progress
176+
(< iteration-count evilem-max-iterations)
177+
(< stalled-count 3) ;; Break after no movement 3 times
164178
(ignore-errors
165179
(setq this-command func
166180
last-command func)
167181

168182
;; Execute the motion command
169-
(call-interactively func)
170-
171-
;; Check if we're in an overlay
183+
(let ((pre-pos (point)))
184+
(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
172201
(unless include-invisible
173-
(let* ((ov (car (overlays-at (point))))
174-
(overlay-id (and ov (overlay-start ov))))
175-
176-
;; If we're in an overlay and have seen it before
177-
(when (and ov (gethash overlay-id seen-overlays))
178-
;; Calculate if we're getting closer to either edge
179-
(let ((dist-to-start (abs (- (point) (overlay-start ov))))
180-
(dist-to-end (abs (- (point) (overlay-end ov))))
181-
(prev-dist-start (car (gethash overlay-id seen-overlays)))
182-
(prev-dist-end (cdr (gethash overlay-id seen-overlays))))
183-
184-
;; If we're getting closer to either edge, we're making progress
185-
(let ((progress-to-edge (or (< dist-to-start prev-dist-start)
186-
(< dist-to-end prev-dist-end))))
187-
188-
;; If we're not making progress in this overlay
189-
(unless progress-to-edge
190-
(setq same-overlay-count (1+ same-overlay-count))
191-
;; After 2 tries in the same overlay without progress, skip it
192-
(when (>= same-overlay-count 2)
193-
(goto-char (overlay-end ov))
194-
(call-interactively func))))))
195-
196-
;; Record our position in this overlay for next time
197-
(when ov
198-
(puthash overlay-id
199-
(cons (abs (- (point) (overlay-start ov)))
200-
(abs (- (point) (overlay-end ov))))
201-
seen-overlays))))
202-
203-
;; Check if we're making progress at all (point has moved)
204-
(setq making-progress (or (not prev-point)
205-
(/= prev-point (point))))
202+
(let ((overlays (overlays-at (point)))
203+
(skipped nil))
204+
205+
;; Check all overlays at point
206+
(dolist (ov overlays)
207+
;; Skip any overlays with invisible property
208+
(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+
227+
;; Return t to continue the loop
206228
t)
207229

208-
;; Create the position key and check for duplicates
209-
(setq point (cons (point) (get-buffer-window)))
210-
(not (member point points)))
230+
;; Check that the new position is unique
231+
(progn
232+
(setq point (cons (point) (get-buffer-window)))
233+
(not (member point points))))
211234

212-
;; Record this position
235+
;; Record the point and update tracking variables
213236
(push point points)
214-
(setq prev-point (point)))))))
237+
(setq prev-point (point))
238+
(setq iteration-count (1+ iteration-count)))))))
215239

240+
;; Handle multiple functions
216241
(setq points (cl-remove-duplicates
217242
(cl-mapcan (lambda (f)
218243
(evilem--collect f scope all-windows))
219244
func))))
245+
246+
;; Post-process and return the points
220247
(funcall (or collect-postprocess
221248
#'evilem--default-collect-postprocess)
222249
points)))

0 commit comments

Comments
 (0)