branch: elpa/racket-mode
commit dfd9c2369292b0ea6a6e3f2b86b9b9c5ad59f9c9
Author: Greg Hendershott <[email protected]>
Commit: Greg Hendershott <[email protected]>
Work with highlight-indent-guides-mode; fixes #669
Make racket-hash-lang-mode initialization more resilient:
- Don't set hooks or functions that depend on racket--hash-lang-id
until the back end responds that it has created the hash-lang object
with that id. (Previously we believed disabling font-lock-mode was a
reliable guard, but it's not.) Furthermore, for belt+suspenders,
wait to set racket--hash-lang-id, and have those functions check
that it's not nil.
- Just set buffer-read-only instead of using read-only-mode. The
latter also enables view-mode; although that wasn't causing a
problem AFAIK, we don't need it.
Improve our fontify-region function:
- Check whether the positions are markers; if so convert them to
numbers before supplying to back end. Otherwise they will get
serialized as unreadable "#<marker ...>". (Which, until the previous
commit, would even abend the back end.)
- Call font-lock-default-fontify-region so that other font-lock can
work (e.g. highlight-indent-guides-mode font-lock-keywords).
Change racket--hash-lang-delete not to start the back end when
it's not running.
Don't clear fontified prop in racket--xp-add-binding-face; this causes
excessive re-fontification. I can't recall why I believed this was
necessary; in my testing things seem OK without this (?).
---
racket-cmd.el | 4 +-
racket-hash-lang.el | 247 ++++++++++++++++++++++++++++------------------------
racket-repl.el | 27 +++---
racket-xp.el | 3 +-
4 files changed, 149 insertions(+), 132 deletions(-)
diff --git a/racket-cmd.el b/racket-cmd.el
index 12ec0273a4..2a6e5e7d12 100644
--- a/racket-cmd.el
+++ b/racket-cmd.el
@@ -137,7 +137,9 @@ Before doing anything runs the hook
`racket-stop-back-end-hook'."
(delete-process/buffer (racket--back-end-process-name-stderr
back-end)))))
(defun racket--cmd-process-sentinel (proc event)
- (when (string-match-p "exited abnormally|failed|connection broken" event)
+ (unless (eq 'open (process-status proc))
+ (when (string-match-p "exited abnormally" event)
+ (run-hooks 'racket-stop-back-end-hook))
(message "{%s} %s" (process-name proc) (substring event 0 -1))))
(defun racket--cmd-process-stderr-filter (proc string)
diff --git a/racket-hash-lang.el b/racket-hash-lang.el
index 837f621d7c..f4a2f1b831 100644
--- a/racket-hash-lang.el
+++ b/racket-hash-lang.el
@@ -109,6 +109,24 @@ plainer `electric-pair-mode'.
#+END_SRC
")
+(defvar-local racket--hash-lang-id nil
+ "Unique integer used to identify the back end hash-lang object.
+Although it's tempting to use `buffer-file-name' for the ID, not
+all buffers have files. Although it's tempting to use
+`buffer-name', buffers can be renamed. Although it's tempting to
+use the buffer object, we can't serialize that.")
+(defvar racket--hash-lang-next-id 0
+ "Increment when we need a new id.")
+
+(defvar-local racket--hash-lang-generation 1
+ "Monotonic increasing value for hash-lang updates.
+
+This is set to 1 when we hash-lang create, incremented every time
+we do a hash-lang update, and then supplied for all other, query
+hash-lang operations. That way the queries can block if necessary
+until the back end has handled the update commands and also
+re-tokenization has progressed sufficiently.")
+
;;;###autoload
(define-derived-mode racket-hash-lang-mode prog-mode
"#lang"
@@ -142,17 +160,12 @@ can contribute more colors; see the customization variable
nil t)
(set-syntax-table racket--plain-syntax-table)
(setq-local font-lock-defaults nil)
- (setq-local font-lock-fontify-region-function
- #'racket--hash-lang-font-lock-fontify-region)
(font-lock-set-defaults) ;issue #642
(setq-local syntax-propertize-function nil)
(setq-local text-property-default-nonsticky
(append
(racket--hash-lang-text-prop-list #'cons t)
text-property-default-nonsticky))
- (add-hook 'after-change-functions #'racket--hash-lang-after-change-hook t t)
- (add-hook 'kill-buffer-hook #'racket--hash-lang-delete t t)
- (add-hook 'change-major-mode-hook #'racket--hash-lang-delete t t)
(electric-indent-local-mode -1)
(setq-local electric-indent-inhibit t)
(setq-local blink-paren-function nil)
@@ -160,85 +173,60 @@ can contribute more colors; see the customization variable
(setq-local completion-at-point-functions nil) ;rely on racket-xp-mode
(setq-local eldoc-documentation-function nil)
(setq racket-submodules-at-point-function nil) ;might change in on-new-lang
- (racket--hash-lang-create))
-
-(defvar-local racket--hash-lang-id nil
- "Unique integer used to identify the back end hash-lang object.
-Although it's tempting to use `buffer-file-name' for the ID, not
-all buffers have files. Although it's tempting to use
-`buffer-name', buffers can be renamed. Although it's tempting to
-use the buffer object, we can't serialize that.")
-(defvar racket--hash-lang-next-id 0
- "Increment when we need a new id.")
-
-(defvar-local racket--hash-lang-generation 1
- "Monotonic increasing value for hash-lang updates.
-
-This is set to 1 when we hash-lang create, incremented every time
-we do a hash-lang update, and then supplied for all other, query
-hash-lang operations. That way the queries can block if necessary
-until the back end has handled the update commands and also
-re-tokenization has progressed sufficiently.")
-
-;; For use by both racket-hash-lang-mode and racket-repl-mode
-(defun racket--hash-lang-create (&optional other-buffer)
- (setq-local racket--hash-lang-id (cl-incf racket--hash-lang-next-id))
+ ;; Create back end hash-lang object.
+ ;;
+ ;; On the one hand, `racket--cmd/await' would be simpler to use
+ ;; here. On the other hand, when the back end isn't running, there's
+ ;; a delay for that to start, during which the buffer isn't
+ ;; displayed and Emacs seems frozen. On the third hand, if we use
+ ;; `racket--cmd/async' naively the buffer could try to interact with
+ ;; a back end object that doesn't yet exist, and error.
+ ;;
+ ;; Warm bowl of porridge: Make buffer read-only and use async
+ ;; command to create hash-lang object. Only when the response
+ ;; arrives, i.e. the back end object is ready, enable read/write and
+ ;; set various hook functions that depend on `racket--hash-lang-id'.
+ ;;
+ ;; Also, handle the back end returning nil for the create -- meaning
+ ;; there's no sufficiently new syntax-color-lib -- by downgrading to
+ ;; plain `prog-mode'.
+ (setq-local racket--hash-lang-id nil) ;until async command response
(setq-local racket--hash-lang-generation 1)
- (cl-case major-mode
- ((racket-hash-lang-mode)
- (let ((text (save-restriction
- (widen)
- (buffer-substring-no-properties (point-min) (point-max)))))
- ;; On the one hand, racket--cmd/await would be simpler to use
- ;; here. On the other hand, when someone visits a file without the
- ;; back end running yet, there's a delay for that to start, during
- ;; which the buffer isn't displayed and Emacs seems frozen. On the
- ;; third hand, if we use async the buffer could try to interact
- ;; with a back end object that doesn't yet exist, and error.
- ;;
- ;; Warm bowl of porridge: Make buffer read-only and not font-lock.
- ;; Set a timer to show a message in the header-line after awhile.
- ;; Send command async. Only when the response arrives, i.e. the
- ;; back end object is ready, enable read/write and font-lock.
- ;;
- ;; Finally, handle the back end returning nil for the create,
- ;; meaning there's no sufficiently new syntax-color-lib.
- (font-lock-mode -1)
- (read-only-mode 1)
- (unless (racket--cmd-open-p)
- (setq-local header-line-format "Waiting for back end to start..."))
- (racket--cmd/async
- nil
- `(hash-lang create ,racket--hash-lang-id ,nil ,text)
- (lambda (maybe-id)
- (font-lock-mode 1)
- (read-only-mode -1)
- (setq-local header-line-format nil)
- (unless maybe-id
- (prog-mode)
- (message "hash-lang support not available; needs newer
syntax-color-lib"))))))
- ((racket-repl-mode)
- (let ((other-lang-source
- (when other-buffer
- (with-current-buffer other-buffer
- (save-restriction
- (widen)
- (buffer-substring-no-properties (point-min) (min 4096
(point-max)))))))
- (text
- (racket--hash-lang-repl-buffer-string (point-min) (point-max))))
- (racket--cmd/async
- nil
- `(hash-lang create ,racket--hash-lang-id ,other-lang-source ,text))))
- (otherwise
- (error "racket--hash-lang-create doesn't work for %s" major-mode))))
+ (unless (racket--cmd-open-p)
+ (setq-local header-line-format "Waiting for back end to start..."))
+ (setq-local buffer-read-only t)
+ (racket--cmd/async
+ nil
+ `(hash-lang create
+ ,(cl-incf racket--hash-lang-next-id)
+ ,nil
+ ,(buffer-substring-no-properties (point-min) (point-max)))
+ (lambda (maybe-id)
+ (setq-local header-line-format nil)
+ (cond
+ (maybe-id
+ (setq-local racket--hash-lang-id maybe-id)
+ ;; These depend on `racket--hash-lang-id':
+ (setq-local font-lock-fontify-region-function
#'racket--hash-lang-fontify-region)
+ (add-hook 'after-change-functions #'racket--hash-lang-after-change-hook
t t)
+ (add-hook 'kill-buffer-hook #'racket--hash-lang-delete t t)
+ (add-hook 'change-major-mode-hook #'racket--hash-lang-delete t t)
+ (setq-local buffer-read-only nil))
+ (t
+ (prog-mode) ;wipes all local variables including buffer-read-only
+ (message "hash-lang support not available; needs newer
syntax-color-lib"))))) )
(defun racket--hash-lang-delete ()
(when racket--hash-lang-id
- (ignore-errors
- (racket--cmd/await
- (when (eq major-mode 'racket-repl-mode) racket--repl-session-id)
- `(hash-lang delete ,racket--hash-lang-id)))
- (setq racket--hash-lang-id nil)
+ ;; When back end running, delete the hash-lang object. (Otherwise,
+ ;; don't start the back end just to delete something that doesn't
+ ;; exist.)
+ (when (racket--cmd-open-p)
+ (ignore-errors
+ (racket--cmd/await
+ (when (eq major-mode 'racket-repl-mode) racket--repl-session-id)
+ `(hash-lang delete ,racket--hash-lang-id))))
+ (setq-local racket--hash-lang-id nil)
(setq-local racket--hash-lang-generation 1)))
;;; Handle back end stopping
@@ -309,16 +297,17 @@ live prompt."
;;;(message "racket--hash-lang-after-change-hook %s %s %s" beg end len)
;; This might be called as frequently as once per single changed
;; character.
- (racket--cmd/async
- nil
- `(hash-lang update
- ,racket--hash-lang-id
- ,(cl-incf racket--hash-lang-generation)
- ,beg
- ,len
- ,(if (eq major-mode 'racket-repl-mode)
- (racket--hash-lang-repl-buffer-string beg end)
- (buffer-substring-no-properties beg end)))))
+ (when racket--hash-lang-id
+ (racket--cmd/async
+ nil
+ `(hash-lang update
+ ,racket--hash-lang-id
+ ,(cl-incf racket--hash-lang-generation)
+ ,beg
+ ,len
+ ,(if (eq major-mode 'racket-repl-mode)
+ (racket--hash-lang-repl-buffer-string beg end)
+ (buffer-substring-no-properties beg end))))))
;;; Notifications: Front end <-- back end
@@ -410,26 +399,30 @@ jit-lock do its thing if/when this span ever becomes
visible."
;;; Fontification
-(defun racket--hash-lang-font-lock-fontify-region (beg end &optional _loudly)
+(defun racket--hash-lang-fontify-region (beg end loudly)
"Our value for the variable `font-lock-fontify-region-function'.
We ask the back end for tokens, and handle its response
asynchronously in `racket--hash-lang-on-tokens' which does the
actual application of faces and syntax. It wouldn't be
appropriate to wait for a response while being called from Emacs
-C redisplay engine, as is the case with `jit-lock-mode'."
- ;;;(message "racket--hash-lang-font-lock-fontify-region %s %s" beg end)
- (racket--cmd/async
- nil
- `(hash-lang get-tokens
- ,racket--hash-lang-id
- ,racket--hash-lang-generation
- ,beg
- ,end)
- #'racket--hash-lang-on-tokens)
- `(jit-lock-bounds ,beg . ,end))
+C redisplay engine."
+ ;;;(message "racket--hash-lang-fontify-region %s %s" beg end)
+ (when racket--hash-lang-id
+ (font-lock-default-fontify-region beg end loudly)
+ (let ((beg (if (markerp beg) (marker-position beg) beg))
+ (end (if (markerp end) (marker-position end) end)))
+ (racket--cmd/async nil
+ `(hash-lang get-tokens
+ ,racket--hash-lang-id
+ ,racket--hash-lang-generation
+ ,beg
+ ,end)
+ #'racket--hash-lang-on-tokens))
+ `(jit-lock-bounds ,beg . ,end)))
(defun racket--hash-lang-on-tokens (tokens)
+ ;;;(message "racket--hash-lang-on-tokens %S" tokens)
(save-restriction
(widen)
(with-silent-modifications
@@ -473,7 +466,7 @@ C redisplay engine, as is the case with `jit-lock-mode'."
(put-face beg end face)))))))))))
(defconst racket--hash-lang-text-properties
- '(face syntax-table racket-token)
+ '(syntax-table racket-token)
"The text properties we use.")
(defun racket--hash-lang-text-prop-list (f val)
@@ -659,23 +652,39 @@ commands. Even if various edit buffers all use
one buffer is \"#lang racket\" while another is \"#lang
rhombus\"."
;;;(message "racket--hash-lang-configure-repl called from buffer %s"
(buffer-name))
- (let ((hl (and (eq major-mode 'racket-hash-lang-mode)
- racket--hash-lang-id))
+ (let ((hl (eq major-mode 'racket-hash-lang-mode))
(edit-buffer (current-buffer)))
+ ;; FIXME: On the very first run, the before-run hook is called
+ ;; before the REPL buffer exists so none of the following happens.
(with-racket-repl-buffer
;; Clean up from previous hash-lang use of REPL, if any
(racket--hash-lang-delete)
+
+ ;; Maybe create hash-lang object, synchronously.
+ (when hl
+ (setq-local
+ racket--hash-lang-id
+ (racket--cmd/await
+ nil
+ `(hash-lang
+ create
+ ,(cl-incf racket--hash-lang-next-id)
+ ,(with-current-buffer edit-buffer
+ (save-restriction
+ (widen)
+ (buffer-substring-no-properties (point-min) (min 4096
(point-max)))))
+ ,(racket--hash-lang-repl-buffer-string (point-min) (point-max))))))
+
;; char-syntax
(set-syntax-table (with-current-buffer edit-buffer (syntax-table)))
(setq-local syntax-propertize-function
(with-current-buffer edit-buffer syntax-propertize-function))
;; font-lock
- (setq-local font-lock-defaults
- (with-current-buffer edit-buffer font-lock-defaults))
- (setq-local font-lock-fontify-region-function
- (racket--repl-limited-fontify-region
- (with-current-buffer edit-buffer
font-lock-fontify-region-function)))
- (font-lock-set-defaults)
+ (setq-local font-lock-keywords
+ (with-current-buffer edit-buffer font-lock-keywords))
+ (setq-local racket--repl-fontify-region-function
+ (with-current-buffer edit-buffer
font-lock-fontify-region-function))
+ (font-lock-flush)
;; indent
(setq-local indent-line-function
(with-current-buffer edit-buffer indent-line-function))
@@ -685,8 +694,6 @@ rhombus\"."
(setq-local forward-sexp-function
(with-current-buffer edit-buffer forward-sexp-function))
(racket-hash-lang-repl-mode (if hl 1 -1)) ;keybindings
- (when hl
- (racket--hash-lang-create edit-buffer))
(if hl
(add-hook 'after-change-functions
#'racket--hash-lang-after-change-hook t t)
(remove-hook 'after-change-functions
#'racket--hash-lang-after-change-hook t))
@@ -695,6 +702,20 @@ rhombus\"."
(add-hook 'racket--repl-before-run-hook
#'racket--hash-lang-configure-repl-buffer-from-edit-buffer)
+(defun racket--hash-lang-repl-on-stop-back-end ()
+ (dolist (buf (buffer-list))
+ (with-current-buffer buf
+ (when (and (eq major-mode 'racket-repl-mode)
+ (buffer-live-p buf))
+ ;; Clean up from previous hash-lang use of REPL, if any
+ (racket-hash-lang-repl-mode -1)
+ (setq-local racket--repl-fontify-region-function
+ #'font-lock-default-fontify-region)
+ (setq-local racket--hash-lang-id nil)
+ (setq-local racket--hash-lang-generation 1)))))
+(add-hook 'racket-stop-back-end-hook
+ #'racket--hash-lang-repl-on-stop-back-end)
+
(defun racket-hash-lang-submit (input)
""
(or (not racket--hash-lang-submit-predicate-p)
diff --git a/racket-repl.el b/racket-repl.el
index ee16aaf162..a0631261df 100644
--- a/racket-repl.el
+++ b/racket-repl.el
@@ -1333,20 +1333,16 @@ The command varies based on how many
\\[universal-argument] command prefixes you
"---"
["Switch to Edit Buffer" racket-repl-switch-to-edit]))
-(defun racket--repl-limited-fontify-region (original)
- "Limit a `font-lock-fontify-region-function' to certain spans.
-
-The resulting function uses ORIGINAL only to fontify input and
-value output spans since the last run -- see also
-`racket--hash-lang-configure-repl-buffer-from-edit-buffer'. Other
-spans are just marked fontified with no action."
- (lambda (beg end loudly)
- (racket--repl-call-with-value-and-input-ranges
- beg end
- (lambda (beg end v)
- (when v (funcall original beg end loudly))))
- (put-text-property beg end 'fontified t)
- `(jit-lock-bounds ,beg . ,end)))
+(defvar-local racket--repl-fontify-region-function
#'font-lock-default-fontify-buffer)
+(defun racket--repl-fontify-region (beg end loudly)
+ "Limit to input and value spans."
+ (racket--repl-call-with-value-and-input-ranges
+ beg end
+ (lambda (beg end v)
+ (when v
+ (funcall racket--repl-fontify-region-function beg end loudly))))
+ (put-text-property beg end 'fontified t)
+ `(jit-lock-bounds ,beg . ,end))
(define-derived-mode racket-repl-mode fundamental-mode "Racket-REPL"
"Major mode for Racket REPL.
@@ -1365,8 +1361,7 @@ identifier bindings and modules from the REPL's namespace.
;; `racket--repl-before-run-hook', drawing values from the
;; `racket-mode' or `racket-hash-lang-mode' edit buffer to also use
;; in the repl.
- (setq-local font-lock-fontify-region-function
- (racket--repl-limited-fontify-region
#'font-lock-default-fontify-region))
+ (setq-local font-lock-fontify-region-function #'racket--repl-fontify-region)
(font-lock-set-defaults)
(setq-local window-point-insertion-type t)
(setq-local indent-line-function #'racket-indent-line)
diff --git a/racket-xp.el b/racket-xp.el
index 779dae8203..95a20c7491 100644
--- a/racket-xp.el
+++ b/racket-xp.el
@@ -522,8 +522,7 @@ manually."
(defun racket--xp-add-binding-face (beg end face)
(add-text-properties beg end
- (list 'font-lock-face face
- 'fontified nil)))
+ (list 'font-lock-face face)))
(defun racket--xp-add-def-face (beg end arrow-kind)
(racket--xp-add-binding-face