Skip to content

Commit

Permalink
Revise handling of paths between Emacs and Racket
Browse files Browse the repository at this point in the history
Two goals:

- Avoid adjusting paths from Racket to Emacs style (on Windows) down
  in the Racket back end. Do any such adjustment up in Emacs. This
  adjustment is to do the thing where c:\path\to\foo.rkt is regarded
  by Enacs as c:/path/to/foo.rkt.

- Support the scenario where someone is running Emacs on Windows
  Subystem for Linux, but instead of running Racket for Linux wants to
  run Windows Racket.exe. In that case, a Windows path like
  c:\path\to\foo.rkt is a Linux path /mnt/c/path/to/foo.rkt. In this
  scenario, we want to adjust paths in both directions. When we tell
  the Windows Racket.exe to run a .rkt file, we want to translate it
  from /mnt/c* to c:\*. In the other direction, we want to translate
  c:\* paths in the response from the `path+md5` command, as well as
  when using compilation-mode to make error messages and #<path>
  structs navigable. Fortunately this is easy to do using the WSL
  command-line utility `wslpath`.

- While reviewing compilation-mode, fix and simplify the regexps. They
  were failing to handle Windows paths starting with drive letters.
  • Loading branch information
Greg Hendershott committed Feb 21, 2019
1 parent d1e5a35 commit 978fb68
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 83 deletions.
62 changes: 50 additions & 12 deletions Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ See also [`racket-run-and-switch-to-repl`](#racket-run-and-switch-to-repl), whic
DrRacket's Run because it selects the REPL window (gives it the
focus), too.

If your source file has a syntax or runtime error, a "skeleton"
of your file is evaluated to get identifiers from module
languages, `require`s, and definitions. That way, things like
completion and [`racket-describe`](#racket-describe) are more likely to work while
you edit the file to fix the error. If not even the "skeleton"
evaluation succeeds, you'll have only identifiers provided by
racket/base, until you fix the error and run again.
When [`racket-retry-as-skeleton`](#racket-retry-as-skeleton) is true, if your source file has
an error, a "skeleton" of your file is evaluated to get
identifiers from module languages, `require`s, and definitions.
That way, things like completion and [`racket-describe`](#racket-describe) are more
likely to work while you edit the file to fix the error. If not
even the "skeleton" evaluation succeeds, you'll have only
identifiers provided by racket/base, until you fix the error and
run again.

Output in the `*Racket REPL*` buffer that describes a file and
position is automatically "linkified". Examples of such text
Expand Down Expand Up @@ -109,7 +110,7 @@ z racket--profile-show-zero


In addition to any hooks its parent mode `special-mode` might have run,
this mode runs the hook [`racket-profile-mode-hook`](#racket-profile-mode-hook), as the final or penultimate step
this mode runs the hook [`racket-profile-mode-hook`](#racket-profile-mode-hook), as the final step
during initialization.

### racket-logger
Expand Down Expand Up @@ -153,7 +154,7 @@ C-c C-z racket-repl


In addition to any hooks its parent mode `special-mode` might have run,
this mode runs the hook [`racket-logger-mode-hook`](#racket-logger-mode-hook), as the final or penultimate step
this mode runs the hook [`racket-logger-mode-hook`](#racket-logger-mode-hook), as the final step
during initialization.

### racket-debug-mode
Expand Down Expand Up @@ -669,7 +670,7 @@ other examples:
To see a table of all key sequences use `M-x
describe-input-method <RET> racket-unicode`.

If you dont like the highlighting of partially matching tokens you
If you don't like the highlighting of partially matching tokens you
can turn it off by setting `input-method-highlight-flag` to nil via
`M-x customize-variable`.

Expand Down Expand Up @@ -783,7 +784,7 @@ p racket-stepper-previous-item


In addition to any hooks its parent mode `special-mode` might have run,
this mode runs the hook [`racket-stepper-mode-hook`](#racket-stepper-mode-hook), as the final or penultimate step
this mode runs the hook [`racket-stepper-mode-hook`](#racket-stepper-mode-hook), as the final step
during initialization.

### racket-expand-file
Expand Down Expand Up @@ -841,7 +842,7 @@ installing it does _not_ do the `raco setup` that is normally
done for Racket packages.

This command will do a `raco make` of racket-mode's .rkt files,
creating bytecode files in a `compiled/` subdirectory. As a
creating bytecode files in `compiled/` subdirectories. As a
result, when a [`racket-run`](#racket-run) or [`racket-repl`](#racket-repl) command must start
the Racket process, it will start faster.

Expand Down Expand Up @@ -900,6 +901,22 @@ for a specific [`racket-run`](#racket-run) using a C-u prefix. This lets you
normally run with a faster setting, and temporarily re-run to get
a more-helpful error message.

### racket-retry-as-skeleton
Retry a "skeleton" of files with errors, for identifier names?

When true: If your source file has an error, a "skeleton" of
your file is evaluated to get identifiers from module languages,
`require`s, and definitions. That way, things like completion and
[`racket-describe`](#racket-describe) are more likely to work while you edit the file
to fix the error.

Otherwise, you'll have only identifiers provided by racket/base,
until you fix the error and run again.

You might want to disable this if you work with files that take a
very long time to expand -- because this feature needs to expand
again when there is an error.

### racket-user-command-line-arguments
List of command-line arguments to supply to your Racket program.

Expand All @@ -924,6 +941,27 @@ but NOT:
(list "-f" "bar")


### racket-path-from-emacs-to-racket-function
A function used to transform Emacs Lisp pathnames before supplying to the Racket back end.

If you run Emacs on Windows Subsystem for Linux, and want to run
Racket programs using Windows Racket.exe rather than Linux
racket, you can set this to [`racket-wsl-to-windows`](#racket-wsl-to-windows). In that case
you probably also want to customize the "reverse":
[`racket-path-from-racket-to-emacs-function`](#racket-path-from-racket-to-emacs-function).

### racket-path-from-racket-to-emacs-function
A function used to transform pathnames supplied by the Racket back end before using them in Emacs.

The default on Windows replaces back with forward slashes. The
default elsewhere is `identity`.

If you run Emacs on Windows Subsystem for Linux, and want to run
Racket programs using Windows Racket.exe rather than Linux
racket, you can set this to [`racket-windows-to-wsl`](#racket-windows-to-wsl). In that case
you probably also want to customize the "reverse":
[`racket-path-from-emacs-to-racket-function`](#racket-path-from-emacs-to-racket-function).

## REPL

### racket-history-filter-regexp
Expand Down
7 changes: 4 additions & 3 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ help:
@echo "Targets: clean, compile, deps, doc, test, test-racket, test-elisp"

show-versions:
racket --version
- racket --version
echo `which $(EMACSBIN)`
$(EMACSBIN) --version

Expand All @@ -26,7 +26,7 @@ clean:
$(BYTECOMP) $<

compile: clean \
show-versions\
show-versions \
racket-bug-report.elc \
racket-common.elc \
racket-collection.elc \
Expand All @@ -46,7 +46,8 @@ compile: clean \
racket-stepper.elc \
racket-tests.elc \
racket-unicode-input-method.elc \
racket-util.elc
racket-util.elc \
racket-wsl.elc

# Install packages we depend on. Intended for one-time use by
# developers and for Travis CI. (Normal users of the package get these
Expand Down
3 changes: 2 additions & 1 deletion racket-common.el
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,8 @@ Allows #; to be followed by zero or more space or newline chars."
;;; racket--what-to-run

(defun racket--what-to-run ()
(cons (racket--buffer-file-name) (racket--submod-path)))
(cons (racket--buffer-file-name)
(racket--submod-path)))

(defun racket--submod-path ()
(and (racket--lang-p)
Expand Down
36 changes: 33 additions & 3 deletions racket-custom.el
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,44 @@ until you fix the error and run again.
You might want to disable this if you work with files that take a
very long time to expand -- because this feature needs to expand
again when there is an error.
Note: The retry "
again when there is an error."
:tag "Retry as Skeleton?"
:type 'boolean
:safe #'booleanp
:group 'racket)

(defcustom racket-path-from-emacs-to-racket-function
#'identity
"A function used to transform Emacs Lisp pathnames before supplying to the Racket back end.
If you run Emacs on Windows Subsystem for Linux, and want to run
Racket programs using Windows Racket.exe rather than Linux
racket, you can set this to `racket-wsl-to-windows'. In that case
you probably also want to customize the \"reverse\":
`racket-path-from-racket-to-emacs-function'."
:tag "Path from Emacs to Racket Function"
:type 'function
:safe 'functionp
:group 'racket)

(defcustom racket-path-from-racket-to-emacs-function
(if racket--winp
(lambda (path) (subst-char-in-string ?\\ ?/ path))
#'identity)
"A function used to transform pathnames supplied by the Racket back end before using them in Emacs.
The default on Windows replaces back with forward slashes. The
default elsewhere is `identity'.
If you run Emacs on Windows Subsystem for Linux, and want to run
Racket programs using Windows Racket.exe rather than Linux
racket, you can set this to `racket-windows-to-wsl'. In that case
you probably also want to customize the \"reverse\":
`racket-path-from-emacs-to-racket-function'."
:tag "Path from Racket to Emacs Function"
:type 'function
:safe #'functionp)

;;; REPL

(defgroup racket-repl nil
Expand Down
4 changes: 2 additions & 2 deletions racket-edit.el
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ Please keep in mind the following limitations:
(pcase (racket--symbol-at-point-or-prompt prefix "Visit definition of: ")
(`nil nil)
(str (if (and (eq major-mode 'racket-mode)
(not (equal (racket--cmd/await `(path+md5))
(cons (racket--buffer-file-name) (md5 (current-buffer)))))
(not (equal (racket--repl-file-name+md5)
(cons (racket--buffer-file-name t) (md5 (current-buffer)))))
(y-or-n-p "Run current buffer first? "))
(racket--repl-run nil nil
(lambda (_what)
Expand Down
2 changes: 2 additions & 0 deletions racket-make-doc.el
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@
racket-error-context
racket-retry-as-skeleton
racket-user-command-line-arguments
racket-path-from-emacs-to-racket-function
racket-path-from-racket-to-emacs-function
"REPL"
racket-history-filter-regexp
racket-images-inline
Expand Down
107 changes: 68 additions & 39 deletions racket-repl.el
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
(require 'compile)
(require 'easymenu)
(require 'cl-lib)
(require 'rx)

;; Don't (require 'racket-debug). Mutual dependency. Instead:
(declare-function racket--debug-send-definition "racket-debug" (beg end))
Expand Down Expand Up @@ -125,7 +126,17 @@ fallbacks. The version number here is a baseline for run.rkt to
be able to load at all.")

(defvar racket--run.rkt (expand-file-name "run.rkt" racket--rkt-source-dir)
"Pathname of run.rkt")
"Pathname of run.rkt.")

(defvar racket-adjust-run-rkt #'identity
"A function used to transform the variable `racket--run.rkt'.
You probably don't need to change this unless you are developing
racket-mode, AND run Emacs on Windows Subsystem for Linux, AND
want to run your programs using Windows Racket.exe, AND have the
racket-mode source code under \"/mnt\". Whew. In that case you
can set this `racket-wsl-to-windows' so that racket-mode can find
its own run.rkt file.")

(defvar-local racket-user-command-line-arguments
nil
Expand Down Expand Up @@ -223,33 +234,38 @@ Non-nil RUN-COMMAND is supplied as the second command-line
argument to `racket--run.rkt' so the process can start by
immediately running a desired file.
Never changes selected window."
Never changes selected window.
Note that the value of the variable `racket--run.rkt' is applied
to the function variable `racket-adjust-run-rkt' before being
used. See the latter for more information."
(if (racket--repl-live-p)
(when display
(display-buffer racket--repl-buffer-name))
(racket--require-version racket--minimum-required-version)
(with-current-buffer
(make-comint racket--repl-buffer-name/raw ;w/o *stars*
racket-program
nil
racket--run.rkt
(number-to-string racket-command-port)
(setq racket--cmd-auth (format "%S" `(auth ,(random))))
(format "%S" (or run-command
(racket--repl-make-run-command nil))))
(let ((proc (get-buffer-process racket--repl-buffer-name)))
;; Display now so users see startup and banner sooner.
(when display
(display-buffer (current-buffer)))
(message "Starting %s to run %s ..." racket-program racket--run.rkt)
;; Ensure command server connection closed when racket process dies.
(set-process-sentinel proc
(lambda (_proc event)
(with-racket-repl-buffer
(insert (concat "Process Racket REPL " event)))
(racket--cmd-disconnect)))
(set-process-coding-system proc 'utf-8 'utf-8) ;for e.g. λ
(racket-repl-mode))))
(let ((run.rkt (funcall racket-adjust-run-rkt racket--run.rkt)))
(with-current-buffer
(make-comint racket--repl-buffer-name/raw ;w/o *stars*
racket-program
nil
run.rkt
(number-to-string racket-command-port)
(setq racket--cmd-auth (format "%S" `(auth ,(random))))
(format "%S" (or run-command
(racket--repl-make-run-command nil))))
(let ((proc (get-buffer-process racket--repl-buffer-name)))
;; Display now so users see startup and banner sooner.
(when display
(display-buffer (current-buffer)))
(message "Starting %s to run %s ..." racket-program run.rkt)
;; Ensure command server connection closed when racket process dies.
(set-process-sentinel proc
(lambda (_proc event)
(with-racket-repl-buffer
(insert (concat "Process Racket REPL " event)))
(racket--cmd-disconnect)))
(set-process-coding-system proc 'utf-8 'utf-8) ;for e.g. λ
(racket-repl-mode)))))
(unless (racket--cmd-connected-or-connecting-p)
(racket--cmd-connect-start)))

Expand Down Expand Up @@ -459,16 +475,25 @@ mistake."

;;; Misc

(defun racket--repl-file-name+md5 ()
"Return the file and MD5 running in the REPL, or nil.
The result can be nil if the REPL is not started, or if it is
running no particular file as with the `,top` command."
(when (comint-check-proc racket--repl-buffer-name)
(pcase (racket--cmd/await `(path+md5))
(`(,(and (pred stringp) path) . ,md5)
(cons (funcall racket-path-from-racket-to-emacs-function path)
md5))
(_ nil))))

(defun racket-repl-file-name ()
"Return the file running in the REPL, or nil.
The result can be nil if the REPL is not started, or if it is
running no particular file as with the `,top` command.
On Windows this will replace \ with / in an effort to match the
Unix style names used by Emacs on Windows."
running no particular file as with the `,top` command."
(when (comint-check-proc racket--repl-buffer-name)
(pcase (racket--cmd/await `(path+md5))
(pcase (racket--repl-file-name+md5)
(`(,(and (pred stringp) path) . ,_md5) path)
(_ nil))))

Expand Down Expand Up @@ -732,16 +757,20 @@ With prefix arg, open the N-th last shown image."
(compilation-setup t)
(setq-local
compilation-error-regexp-alist
'(;; error
("^;?[ ]*\\([^ :]+\\):\\([0-9]+\\)[:.]\\([0-9]+\\)" 1 2 3)
;; contract
("^;?[ ]*at:[ ]+\\([^ :]+\\):\\([0-9]+\\)[.]\\([0-9]+\\)$" 1 2 3)
;; rackunit check-xxx
("#<path:\\([^>]+\\)> \\([0-9]+\\) \\([0-9]+\\)" 1 2 3)
;;rackunit/text-ui test-suite
("^location:[ ]+\\(\\([^:]+\\):\\([0-9]+\\):\\([0-9]+\\)\\)" 2 3 4 2 1)
;; path struct
("#<path:\\([^>]+\\)>" 1 nil nil 0))))
(list
;; Any apparent file:line:col
(list (rx (group-n 1 (+? (not (syntax whitespace))))
(any ?\: ?\.)
(group-n 2 (+ digit))
(any ?\: ?\.)
(group-n 3 (+ digit)))
#'racket--adjust-group-1 2 3)
;; Any path struct
(list (rx "#<path:" (group-n 1 (+? (not (any ?\>)))) ?\>)
#'racket--adjust-group-1 nil nil 0))))

(defun racket--adjust-group-1 ()
(list (funcall racket-path-from-racket-to-emacs-function (match-string 1))))

(provide 'racket-repl)

Expand Down
15 changes: 11 additions & 4 deletions racket-util.el
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@ strings."
spec)
m))

(defun racket--buffer-file-name ()
"Like `buffer-file-name' but always a non-propertized string."
(and (buffer-file-name)
(substring-no-properties (buffer-file-name))))
(defun racket--buffer-file-name (&optional no-adjust)
"Like `buffer-file-name' but always a non-propertized string.
Unless NO-ADJUST is not nil, applies the name to the function
variable `racket-path-from-emacs-to-racket-function'."
(let ((v (and (buffer-file-name)
(substring-no-properties (buffer-file-name)))))
(if no-adjust
v
(funcall racket-path-from-emacs-to-racket-function
v))))

(defun racket--save-if-changed ()
(unless (eq major-mode 'racket-mode)
Expand Down
Loading

0 comments on commit 978fb68

Please sign in to comment.