On Mon, Nov 25, 2019 at 9:07 PM João Távora wrote: > But even in that particular case, whose frequency I presume to > be minute in proportion to the number of opened-and-closed > strings, it can be argued that the patch has a benefit, > since it's normally easier to spot where you want to end the > new string made of existing non-string text when it's fontified > as non-string text. The amount of CPU spent is also lower. A correction, again. This paragraph is an inaccurate description of how jit-lock-antiblink-grace works when the user wants to insert multi-line strings. In fact, I believe that scenario produce mostly the same behaviour in both antiblink-on/antiblink-off cases. The best way to explain it to show it in action, so I hope the animated gifs illustrate (from Emacs -Q). Eli, I also attach an updated patch (which I hope Gmail doesn't mangle) with the fixes you requested earlier. Thanks, João diff --git a/etc/NEWS b/etc/NEWS index 7e86ccc71e..e66dd4a21c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -604,6 +604,13 @@ If the region is active, the command joins all the lines in the region. When there's no active region, the command works on the current and the previous or the next line, as before. +--- +** New customizable variable 'jit-lock-antiblink-grace'. +When adding strings to source code, this helps avoid +\"blinking\", an unwanted oscillation of certain regions between +string and non-string fontification. + + * Changes in Specialized Modes and Packages in Emacs 27.1 diff --git a/lisp/jit-lock.el b/lisp/jit-lock.el index 48998a81fe..23afb53bf8 100644 --- a/lisp/jit-lock.el +++ b/lisp/jit-lock.el @@ -123,6 +123,20 @@ jit-lock-context-time :type '(number :tag "seconds") :group 'jit-lock) +(defcustom jit-lock-antiblink-grace 2 + "Idle time after which to refontify due to unterminated strings. +If the user creates a temporarily unterminated string up to the +end of the current line, that part of the line is fontified after +`jit-lock-context-time', but an extended idle \"grace\" period of +this many seconds is granted before deciding it is a multi-line +string and fontifying the remainder of the buffer accordingly. +When adding strings to source code, this helps avoid +\"blinking\", an unwanted oscillation of certain regions between +string and non-string fontification. If nil, there is no grace +period." + :type '(number :tag "seconds") + :group 'jit-lock) + (defcustom jit-lock-defer-time nil ;; 0.25 "Idle time after which deferred fontification should take place. If nil, fontification is not deferred. @@ -157,6 +171,15 @@ jit-lock-defer-buffers "List of buffers with pending deferred fontification.") (defvar jit-lock-stealth-buffers nil "List of buffers that are being fontified stealthily.") + +(defvar jit-lock--antiblink-grace-timer nil + "Idle timer for fontifying unterminated string or comment, or nil.") +(defvar jit-lock--antiblink-line-beginning-position (make-marker) + "Last line beginning position after last command (a marker).") +(defvar jit-lock--antiblink-string-or-comment nil + "Non-nil if in string or comment after last command (a boolean).") + + ;;; JIT lock mode @@ -232,7 +255,10 @@ jit-lock-mode (unless jit-lock-context-timer (setq jit-lock-context-timer (run-with-idle-timer jit-lock-context-time t - 'jit-lock-context-fontify))) + (lambda () + (unless jit-lock--antiblink-grace-timer + (jit-lock-context-fontify)))))) + (add-hook 'post-command-hook 'jit-lock--antiblink-post-command nil t) (setq jit-lock-context-unfontify-pos (or jit-lock-context-unfontify-pos (point-max)))) @@ -669,6 +695,55 @@ jit-lock-after-change ;; buffer, only jit-lock-context-* will re-fontify it. (min jit-lock-context-unfontify-pos jit-lock-start)))))) +(defun jit-lock--antiblink-post-command () + (let* ((new-l-b-p (copy-marker (line-beginning-position))) + (l-b-p-2 (line-beginning-position 2)) + (same-line + (and jit-lock-antiblink-grace + (not (= new-l-b-p l-b-p-2)) + (eq (marker-buffer jit-lock--antiblink-line-beginning-position) + (current-buffer)) + (= new-l-b-p jit-lock--antiblink-line-beginning-position))) + (new-s-o-c + (and same-line + (nth 8 (save-excursion (syntax-ppss l-b-p-2)))))) + (cond (;; opened a new multiline string... + (and same-line + (null jit-lock--antiblink-string-or-comment) new-s-o-c) + (setq jit-lock--antiblink-grace-timer + (run-with-idle-timer jit-lock-antiblink-grace nil + (lambda () + (jit-lock-context-fontify) + (setq jit-lock--antiblink-grace-timer + nil))))) + (;; closed an unterminated multiline string. + (and same-line + (null new-s-o-c) jit-lock--antiblink-string-or-comment) + ;; Kill the grace timer, might already have run and died. + ;; Don't refontify immediately: it adds an unreasonable + ;; delay to a well-behaved operation. Leave it for the + ;; `jit-lock-context-timer' as usual. + (when jit-lock--antiblink-grace-timer + (cancel-timer jit-lock--antiblink-grace-timer) + (setq jit-lock--antiblink-grace-timer nil))) + (same-line + ;; in same line, but no state change, leave everything as it was + ) + (t + ;; left the line somehow or customized feature away, etc + ;; kill timer if running, resume normal operation. + (when jit-lock--antiblink-grace-timer + ;; Do refontify immediately, adding a small delay. This + ;; is per Lars' request, and it makes sense because we + ;; should remark somehow that we are leaving the unstable + ;; state. + (jit-lock-context-fontify) + (cancel-timer jit-lock--antiblink-grace-timer) + (setq jit-lock--antiblink-grace-timer nil)))) + ;; update variables + (setq jit-lock--antiblink-line-beginning-position new-l-b-p + jit-lock--antiblink-string-or-comment new-s-o-c))) + (provide 'jit-lock) ;;; jit-lock.el ends here