Skip to content

Commit cf92277

Browse files
Improve UX for back end startup failures; closes #744
When the back end cannot start, display information in a new, dedicated Emacs buffer -- a better experience than something flashing by in the echo area and then only being viewable if user opens the *Messages* buffer. Furthermore, main.rkt now dynamic-require's command-server.rkt under an exn handler that looks for exn:fail:syntax:missing-module. In that case the dedicated buffer includes a browse-url button to go directly to our documentation about using Minimal Racket.
1 parent eef5e9a commit cf92277

File tree

3 files changed

+100
-14
lines changed

3 files changed

+100
-14
lines changed

racket-cmd.el

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
;;; racket-cmd.el -*- lexical-binding: t; -*-
22

3-
;; Copyright (c) 2013-2022 by Greg Hendershott.
3+
;; Copyright (c) 2013-2025 by Greg Hendershott.
44
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
55

66
;; Author: Greg Hendershott
@@ -75,6 +75,7 @@ Before doing anything runs the hook `racket-stop-back-end-hook'."
7575
;; Avoid excess processes/buffers like "racket-process<1>".
7676
(when (racket--cmd-open-p)
7777
(racket--cmd-close))
78+
(racket--kill-startup-error-buffer)
7879
;; Give the process buffer the current values of some vars; see
7980
;; <https://github.com/purcell/envrc/issues/22>.
8081
(cl-letf* (((default-value 'process-environment) process-environment)
@@ -214,6 +215,8 @@ Although mostly these are 1:1 responses to command requests, some
214215
like \"logger\", \"debug-break\", and \"hash-lang\" are
215216
notifications."
216217
(pcase response
218+
(`(startup-error ,kind ,data)
219+
(run-at-time 0.001 nil #'racket--on-startup-error kind data))
217220
(`(logger ,str)
218221
(run-at-time 0.001 nil #'racket--logger-on-notify back-end str))
219222
(`(debug-break . ,response)
@@ -362,6 +365,48 @@ in a specific namespace."
362365
(error "Unknown response to command %S from %S to %S:\n%S"
363366
command-sexpr buf name v)))))))
364367

368+
;;; Back end startup error buffer
369+
370+
(defconst racket--startup-error-buffer-name
371+
"*Racket Mode back end startup failure*")
372+
373+
(defun racket--kill-startup-error-buffer ()
374+
(let ((buf (get-buffer racket--startup-error-buffer-name)))
375+
(when (buffer-live-p buf)
376+
(kill-buffer buf))))
377+
378+
(defun racket--on-startup-error (kind data)
379+
(let ((buf (get-buffer-create racket--startup-error-buffer-name)))
380+
(with-current-buffer buf
381+
(unless (eq major-mode 'special-mode)
382+
(special-mode))
383+
(visual-line-mode 1)
384+
(let ((buffer-read-only nil))
385+
(erase-buffer)
386+
(pop-to-buffer buf)
387+
(pcase kind
388+
('missing-module
389+
(let ((url "https://racket-mode.com/#Minimal-Racket-1"))
390+
(insert "The Racket Mode back end could not start because it was unable to load the module "
391+
?' data ?' "."
392+
"\n\n"
393+
"This could be because you did not install the full \"main distribution\" of Racket, but instead installed only \"Minimal Racket\" (the default when using homebrew)."
394+
"\n\n"
395+
"In that case, you will need either to install the full main distribution, or, manually install certain additional Racket packages."
396+
"\n\n"
397+
"Please see ")
398+
(save-excursion ;leave point at start of link, for handy RET
399+
(insert-button url
400+
'url url
401+
'face 'link
402+
'follow-link t
403+
'action (lambda (button)
404+
(when-let (url (button-get button 'url))
405+
(browse-url url))))
406+
(insert "."))))
407+
(_
408+
(insert data)))))))
409+
365410
(provide 'racket-cmd)
366411

367412
;; racket-cmd.el ends here

racket/image.rkt

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
;; Copyright (c) 2013-2022 by Greg Hendershott.
1+
;; Copyright (c) 2013-2025 by Greg Hendershott.
22
;; SPDX-License-Identifier: GPL-3.0-or-later
33

44
#lang racket/base
55

6-
;;; Portions Copyright (C) 2012 Jose Antonio Ortega Ruiz.
6+
;; Portions Copyright (C) 2012 Jose Antonio Ortega Ruiz.
77

8+
;; Limit imports to those supplied by Minimal Racket!
89
(require file/convertible
910
racket/file
1011
racket/format

racket/main.rkt

+51-11
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,53 @@
1-
;; Copyright (c) 2013-2022 by Greg Hendershott.
1+
;; Copyright (c) 2013-2025 by Greg Hendershott.
22
;; SPDX-License-Identifier: GPL-3.0-or-later.
33

44
#lang racket/base
55

6+
;; This module acts as a "shim" or "launcher" for command-server.rkt.
7+
;;
8+
;; We dynamic-require command-server.rkt within an exn handler for
9+
;; missing modules, to provide a better error UX when people are using
10+
;; Minimal Racket; see issue #744. Any such error is written to stdout
11+
;; as a "notification" for the Emacs front end, which can display it
12+
;; in a dedicated buffer. Not only is this better than error text
13+
;; flashing by in the echo bar and hiding in the *Messages* buffer,
14+
;; our dedicated can supply a browse-url button to our docs section
15+
;; about Minimal Racket.
16+
;;
17+
;; Note that the exn handler is active only during the dynamic extent
18+
;; of the dynamic-require to extract the command-server-loop function.
19+
;; Subsequently we call that function without any such handler in
20+
;; effect.
21+
;;
22+
;; Use the same notification mechanism for other back end startup
23+
;; failures, such as when they need a newer version of Racket.
24+
25+
;; Limit imports to those supplied by Minimal Racket!
626
(require racket/match
7-
racket/port
27+
(only-in racket/port open-output-nowhere)
28+
racket/runtime-path
829
(only-in racket/string string-trim)
930
(only-in racket/system system/exit-code)
1031
version/utils
11-
"command-server.rkt"
1232
(only-in "image.rkt" set-use-svg?!))
1333

34+
;; Write a "notification" for the Emacs front end and exit.
35+
(define (notify/exit kind data)
36+
(writeln `(startup-error ,kind ,data))
37+
(flush-output)
38+
(exit 13))
39+
1440
(define (assert-racket-version minimum-version)
1541
(define actual-version (version))
1642
(unless (version<=? minimum-version actual-version)
17-
(error '|Racket Mode back end| "Need Racket ~a or newer but ~a is ~a"
18-
minimum-version
19-
(find-executable-path (find-system-path 'exec-file))
20-
actual-version)))
43+
(notify/exit
44+
'other
45+
(format "Racket Mode needs Racket ~a or newer but ~a is ~a."
46+
minimum-version
47+
(find-executable-path (find-system-path 'exec-file))
48+
actual-version))
49+
(flush-output)
50+
(exit 14)))
2151

2252
(define (macos-sequoia-or-newer?)
2353
(and (eq? 'macosx (system-type 'os))
@@ -40,14 +70,24 @@
4070
[(vector "--use-svg" ) (set-use-svg?! #t)]
4171
[(vector "--do-not-use-svg") (set-use-svg?! #f)]
4272
[v
43-
(error '|Racket Mode back end|
44-
"Bad command-line arguments:\n~s\n" v)])
73+
(notify/exit
74+
'other
75+
(format "Bad command-line arguments:\n~s\n" v))])
76+
77+
(define-runtime-path command-server.rkt "command-server.rkt")
78+
(define command-server-loop
79+
(with-handlers ([exn:fail:syntax:missing-module?
80+
(λ (e)
81+
(notify/exit
82+
'missing-module
83+
(format "~a" (exn:fail:syntax:missing-module-path e))))])
84+
(dynamic-require command-server.rkt 'command-server-loop)))
4585

4686
;; Save original current-{input output}-port to give to
47-
;; command-server-loop for command I/O.
87+
;; command-server-loop for command I/O ...
4888
(let ([stdin (current-input-port)]
4989
[stdout (current-output-port)])
50-
;; Set no-ops so e.g. rando print can't bork the command I/O.
90+
;; ... and set no-ops so rando print can't bork the command I/O.
5191
(parameterize ([current-input-port (open-input-bytes #"")]
5292
[current-output-port (open-output-nowhere)])
5393
(command-server-loop stdin stdout))))

0 commit comments

Comments
 (0)