In recent past, I've adopted Greg Hendershott's racket-mode for Emacs, added keyword completion, hover help, documentation lookup, customized syntax highlighting and indentation and such for my personal tastes, but one thing I haven't really looked at so far is code navigation support for Racket. What seemed like an easy place to start was implementing a function for loading a Racket source file by its module path, as would appear within a
racket-open-module-at-point, my attempt at such a function. When the cursor is on a quoted string, the function just assumes that the quoted string is a (relative) filename, and loads it directly using
find-file. Otherwise it grabs the (smallest) S-expression at point (if any), and—after a minor sanity check—passes it over to Racket and its
resolve-module-path function, to hopefully receive a fully resolved pathname.
;; Only works when positioned inside the string, and not at the quotes. (defun bounds-of-quoted-string-at-point () (let ((p (nth 8 (syntax-ppss)))) (and p (cons p (save-excursion (goto-char (1+ p)) (search-forward "\"") (point)))))) (defun racket-open-module-at-point () (interactive) (let ((p (bounds-of-quoted-string-at-point))) (cond (p ;; Point is inside a quoted string. (let ((fn (buffer-substring-no-properties (1+ (car p)) (1- (cdr p))))) (when (equal "" fn) (error "point is inside an empty quoted string")) (message "%s" fn) (find-file fn))) (t (require 'thingatpt) (let ((mp (thing-at-point 'sexp))) (cond ((not mp) (error "no module path at point")) (t (set-text-properties 0 (length mp) nil mp) ;; modifies `mp`! (cond ((string-match "\\`\"\\([^\"]*\\)\"\\'" mp) ;; The module path is a quoted string. (let ((fn (match-string 1 mp))) (when (equal "" fn) (error "point is at an empty quoted string")) (message "%s" fn) (find-file fn))) ((string-match "['\\]" mp) ;; The module path contains characters that might cause shell ;; escaping. Could be a module path like '#%kernel, for which ;; we cannot get a source file. (error "non-resolvable module path: %s" mp)) (t (let* ((bfn (buffer-file-name)) (cmd (format "racket -e '(require syntax/modresolve) (write (with-handlers ([exn:fail? (lambda (exn) (quote resolution-failed))]) (let ([r (resolve-module-path (quote %s) %s)]) (match r [(? path?) (path->string r)] [(? symbol?) (quote symbolic-path)] [(list (quote submod) (and (or (? path?) (? symbol?)) sub-r) rest ...) (cond [(path? sub-r) (path->string sub-r)] [else (quote symbolic-path)])] [_ (quote unexpected-result)]))))'" mp (if bfn (format "%S" bfn) "#f")))) (let ((out-s (shell-command-to-string cmd))) (let ((fn (car (read-from-string out-s)))) (unless (stringp fn) (error "failed to resolve module path: %s: %S" mp fn)) (unless (file-exists-p fn) ;; Expecting an absolute path. (error "resolved to non-existent file: %s -> %S" mp fn)) (message "%s" fn) (find-file fn)))))))))))))
thing-at-point doesn't appear to come with predefined support for double-quoted strings, I hacked together a
bounds-of-quoted-string-at-point function for the purpose of determining the start and end positions of any such string at point. I'm not sure exactly what
parse-partial-sexp) does, or if it's a good idea to use it for this purpose, but it has worked well enough so far; its use was suggested on Stack Overflow.
Note (13 Oct 2015): Using
syntax-ppss turns out to be less than ideal for the above purpose, as it relies on the relevant syntax being recognized. Not all modes care to do so, nor is S-expression recognition even that relevant for e.g. a mode designed to deal with @-expressions.
I'm not particularly familiar with
'thingatpt, but it looks like the
bounds-of-quoted-string-at-point function may even be usable for defining a
thing-at-point "thing", named
(put 'quoted-string 'bounds-of-thing-at-point 'bounds-of-quoted-string-at-point)