Skip to content

Commit 61f4891

Browse files
committed
fix: async overlays race condition
1 parent d06e1b8 commit 61f4891

File tree

1 file changed

+62
-39
lines changed

1 file changed

+62
-39
lines changed

blamer.el

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -342,27 +342,34 @@ in the echo-area."
342342
:group 'blamer
343343
:type '(repeat symbol))
344344

345-
(defvar blamer-idle-timer nil
345+
(defvar-local blamer--idle-timer nil
346346
"Current timer before commit info showing.")
347347

348-
(defvar blamer--previous-line-number nil
348+
(defvar-local blamer--previous-line-number nil
349349
"Line number of previous popup.")
350350

351-
(defvar blamer--previous-window-width nil
351+
(defvar-local blamer--previous-window-width nil
352352
"Previous window width.")
353353

354-
(defvar blamer--previous-line-length nil
354+
(defvar-local blamer--previous-line-length nil
355355
"Current line number length for detect render function.")
356356

357-
(defvar blamer--previous-point nil
357+
(defvar-local blamer--previous-point nil
358358
"Last preserved blamer point.")
359359

360-
(defvar blamer--previous-region-active-p nil
360+
(defvar-local blamer--previous-region-active-p nil
361361
"Was previous state is active region?")
362362

363-
(defvar blamer--overlays '()
363+
(defvar-local blamer--overlays '()
364364
"Current active overlays for git blame messages.")
365365

366+
(defvar-local blamer--request-id 0
367+
"Incrementing ID to track and cancel outdated async requests.")
368+
369+
(defun blamer--inc-request-id ()
370+
"Increment request ID to invalidate pending async requests."
371+
(setq blamer--request-id (1+ blamer--request-id)))
372+
366373
(defvar blamer--block-render-p nil
367374
"Lock rendering, useful for external packages.")
368375

@@ -396,7 +403,7 @@ Will show the available `blamer-bindings'."
396403

397404
;;;###autoload
398405
(defun blamer--clear-overlay ()
399-
"Clear last overlay."
406+
"Clear last overlay and invalidate pending async requests."
400407
(dolist (ov blamer--overlays)
401408
(delete-overlay ov))
402409
(setq blamer--overlays '()))
@@ -973,14 +980,16 @@ Return list of strings."
973980

974981
(defun blamer--render-line-overlay (commit-info buffer render-point &optional type)
975982
"Render COMMIT-INFO overlay by optional TYPE in the BUFFER at the RENDER-POINT.
976-
when not provided `blamer-type' will be used."
977-
(with-current-buffer buffer
978-
(save-excursion
979-
(cond ((eq (or type blamer-type) 'overlay-popup) (blamer--render-overlay-popup commit-info))
980-
((eq (or type blamer-type) 'echo-area) (blamer--render-echo-area commit-info))
981-
((eq (or type blamer-type) 'posframe-popup) (blamer--render-posframe-popup commit-info))
982-
((eq (or type blamer-type) 'margin-overlay) (blamer--render-margin-overlay commit-info render-point))
983-
(t (blamer--render-right-overlay commit-info render-point))))))
983+
when not provided `blamer-type' will be used.
984+
Does nothing if BUFFER is not alive or RENDER-POINT is nil."
985+
(when (and (buffer-live-p buffer) render-point)
986+
(with-current-buffer buffer
987+
(save-excursion
988+
(cond ((eq (or type blamer-type) 'overlay-popup) (blamer--render-overlay-popup commit-info))
989+
((eq (or type blamer-type) 'echo-area) (blamer--render-echo-area commit-info))
990+
((eq (or type blamer-type) 'posframe-popup) (blamer--render-posframe-popup commit-info))
991+
((eq (or type blamer-type) 'margin-overlay) (blamer--render-margin-overlay commit-info render-point))
992+
(t (blamer--render-right-overlay commit-info render-point)))))))
984993

985994
(defun blamer--get-async-blame-info (file-name start-line end-line callback)
986995
"Get blame info for FILE-NAME from START-LINE to END-LINE.
@@ -1015,7 +1024,8 @@ EXPLICIT - flag this rendering as interactive."
10151024
(line-number-at-pos)))
10161025
(file-name (blamer--get-local-name (buffer-file-name)))
10171026
(include-avatar-p (member type '(posframe-popup overlay-popup)))
1018-
(current-buffer (current-buffer)))
1027+
(request-buffer (current-buffer))
1028+
(request-id (blamer--inc-request-id)))
10191029

10201030
(blamer--clear-overlay)
10211031

@@ -1026,18 +1036,22 @@ EXPLICIT - flag this rendering as interactive."
10261036
(blamer--get-async-blame-info
10271037
file-name start-line-number end-line-number
10281038
(lambda (commit-infos)
1029-
(when (or explicit (buffer-local-value 'blamer-mode current-buffer))
1039+
(when (and (buffer-live-p request-buffer)
1040+
(eq request-id (buffer-local-value 'blamer--request-id request-buffer))
1041+
(or explicit (buffer-local-value 'blamer-mode request-buffer)))
10301042
(blamer--handle-async-blame-info-result
10311043
commit-infos
1032-
current-buffer
1044+
request-buffer
10331045
start-line-number
10341046
include-avatar-p
1047+
request-id
10351048
type)))))))))
10361049

1037-
(defun blamer--handle-async-blame-info-result (commit-infos buffer start-line-number include-avatar-p &optional type)
1050+
(defun blamer--handle-async-blame-info-result (commit-infos buffer start-line-number include-avatar-p request-id &optional type)
10381051
"Handle COMMIT-INFOS for BUFFER and START-LINE-NUMBER.
10391052
INCLUDE-AVATAR-P is optional argument that can replace
1040-
global `blamer-show-avatar-p' variable
1053+
global `blamer-show-avatar-p' variable.
1054+
REQUEST-ID is used to check if this request is still valid.
10411055
TYPE is optional view render type."
10421056
(let* ((commit-infos (if commit-infos
10431057
(butlast (split-string commit-infos "\n"))
@@ -1049,11 +1063,13 @@ TYPE is optional view render type."
10491063
(blamer--async-parse-line-info
10501064
cmd-msg
10511065
(lambda (commit-info)
1052-
(blamer--render-line-overlay
1053-
commit-info
1054-
buffer
1055-
(blamer--get-render-point buffer (plist-get commit-info :line-number))
1056-
type))
1066+
(when (and (buffer-live-p buffer)
1067+
(eq request-id (buffer-local-value 'blamer--request-id buffer)))
1068+
(blamer--render-line-overlay
1069+
commit-info
1070+
buffer
1071+
(blamer--get-render-point buffer (plist-get commit-info :line-number))
1072+
type)))
10571073
line-number
10581074
include-avatar-p)
10591075
(setq line-number (1+ line-number))))))
@@ -1073,12 +1089,16 @@ TYPE is optional view render type."
10731089
(forward-line (- line-number current-line-number)))))
10741090

10751091
(defun blamer--get-render-point (buffer line-number)
1076-
"Return render point by LINE-NUMBER from BUFFER."
1077-
(with-current-buffer buffer
1078-
(save-excursion
1079-
(blamer--goto-line line-number)
1080-
(end-of-line)
1081-
(point))))
1092+
"Return render point by LINE-NUMBER from BUFFER.
1093+
Returns nil if LINE-NUMBER doesn't exist in buffer."
1094+
(when (buffer-live-p buffer)
1095+
(with-current-buffer buffer
1096+
(save-excursion
1097+
(let ((max-line (line-number-at-pos (point-max))))
1098+
(when (<= line-number max-line)
1099+
(blamer--goto-line line-number)
1100+
(end-of-line)
1101+
(point)))))))
10821102

10831103
(defun blamer--safety-render (&optional type)
10841104
"Function for checking current active blamer type before rendering with delay.
@@ -1095,10 +1115,10 @@ Optional TYPE argument will override global `blamer-type'."
10951115

10961116
(defun blamer--render-commit-info-with-delay ()
10971117
"Render commit info with delay."
1098-
(when blamer-idle-timer
1099-
(cancel-timer blamer-idle-timer))
1118+
(when blamer--idle-timer
1119+
(cancel-timer blamer--idle-timer))
11001120

1101-
(setq blamer-idle-timer
1121+
(setq blamer--idle-timer
11021122
(run-with-idle-timer (or blamer-idle-time 0) nil 'blamer--safety-render)))
11031123

11041124
(defun blamer--preserve-state ()
@@ -1124,6 +1144,7 @@ LOCAL-TYPE is force replacement of current `blamer-type' for handle rendering."
11241144
(type (or local-type blamer-type)))
11251145

11261146
(when (and clear-overlays-p (not blamer--block-render-p))
1147+
(blamer--inc-request-id)
11271148
(blamer--clear-overlay))
11281149

11291150
(when (and (not long-region-p)
@@ -1139,22 +1160,24 @@ LOCAL-TYPE is force replacement of current `blamer-type' for handle rendering."
11391160
(not (eq blamer--previous-line-number (line-number-at-pos)))
11401161
(not (eq blamer--previous-line-length (length (thing-at-point 'line))))))
11411162

1163+
(blamer--inc-request-id)
11421164
(blamer--clear-overlay)
11431165
(blamer--render-commit-info-with-delay))
11441166

11451167
(blamer--preserve-state)))
11461168

11471169
(defun blamer--reset-state ()
11481170
"Reset all state after blamer mode is disabled."
1149-
(if blamer-idle-timer
1150-
(cancel-timer blamer-idle-timer))
1171+
(when blamer--idle-timer
1172+
(cancel-timer blamer--idle-timer))
11511173

11521174
(blamer--clear-overlay)
1153-
(setq blamer-idle-timer nil)
1175+
(blamer--inc-request-id)
1176+
(setq blamer--idle-timer nil)
11541177
(setq blamer--previous-line-number nil)
11551178
(setq blamer--previous-window-width nil)
11561179
(setq blamer--previous-point nil)
1157-
(when (not (eq nil (get-buffer blamer--buffer-name)))
1180+
(when (get-buffer blamer--buffer-name)
11581181
(posframe-hide blamer--buffer-name))
11591182
(remove-hook 'post-command-hook #'blamer--try-render t)
11601183
(remove-hook 'window-state-change-hook #'blamer--try-render t))

0 commit comments

Comments
 (0)