|
82 | 82 | (const :tag "De Bruijn" de-bruijn)
|
83 | 83 | (const :tag "Default" nil)))
|
84 | 84 |
|
| 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 | + |
85 | 91 | (defvar evilem-map (make-sparse-keymap)
|
86 | 92 | "Keymap used for the default bindings")
|
87 | 93 |
|
|
154 | 160 | (narrow-to-region (max beg (window-start))
|
155 | 161 | (min end (window-end))))
|
156 | 162 |
|
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)) |
162 | 174 |
|
163 | 175 | (while (and making-progress
|
| 176 | + (< iteration-count evilem-max-iterations) |
| 177 | + (< stalled-count 3) ;; Break after no movement 3 times |
164 | 178 | (ignore-errors
|
165 | 179 | (setq this-command func
|
166 | 180 | last-command func)
|
167 | 181 |
|
168 | 182 | ;; 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 |
172 | 201 | (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 |
206 | 228 | t)
|
207 | 229 |
|
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)))) |
211 | 234 |
|
212 |
| - ;; Record this position |
| 235 | + ;; Record the point and update tracking variables |
213 | 236 | (push point points)
|
214 |
| - (setq prev-point (point))))))) |
| 237 | + (setq prev-point (point)) |
| 238 | + (setq iteration-count (1+ iteration-count))))))) |
215 | 239 |
|
| 240 | + ;; Handle multiple functions |
216 | 241 | (setq points (cl-remove-duplicates
|
217 | 242 | (cl-mapcan (lambda (f)
|
218 | 243 | (evilem--collect f scope all-windows))
|
219 | 244 | func))))
|
| 245 | + |
| 246 | + ;; Post-process and return the points |
220 | 247 | (funcall (or collect-postprocess
|
221 | 248 | #'evilem--default-collect-postprocess)
|
222 | 249 | points)))
|
|
0 commit comments