Update packages

This commit is contained in:
Mateus Pinto Rodrigues
2018-10-04 13:56:56 -03:00
parent 5d03e5e124
commit d272c43bcd
785 changed files with 367265 additions and 25 deletions

View File

@@ -0,0 +1,81 @@
;;; racket-bug-report.el
;; Copyright (c) 2013-2016 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'cl-lib)
(require 'racket-util)
;;;###autoload
(defun racket-bug-report ()
"Fill a buffer with data to make a racket-mode bug report."
(interactive)
(unless (memq major-mode '(racket-mode racket-repl-mode))
(user-error "Please run this from a racket-mode or racket-repl-mode buffer."))
(with-help-window "*racket-mode bug report*"
(princ "TIP: If you get an `invalid function' error, be aware that Emacs package\n")
(princ "updates don't necessarily fully update Emacs' state. In some cases, you\n")
(princ "might even need to:\n\n")
(princ " 1. Uninstall racket-mode\n")
(princ " 2. Exit and restart Emacs\n")
(princ " 3. Install racket-mode\n\n\n")
(princ "When you submit a bug report at:\n\n")
(princ " https://github.com/greghendershott/racket-mode/issues/new\n\n")
(princ "Please copy and paste ALL OF THE FOLLOWING LINES from\n")
(princ "`<details>' through `</details>':\n\n\n")
(princ "<details>\n")
(princ "```\n")
(cl-labels ((id-val (id) (list id
(condition-case () (symbol-value id)
(error 'UNDEFINED)))))
(let ((emacs-uptime (emacs-uptime)))
(pp `(,@(mapcar #'id-val
`(emacs-version
emacs-uptime
system-type
major-mode
racket--el-source-dir
racket--rkt-source-dir
racket-program
racket-memory-limit
racket-error-context
racket-history-filter-regexp
racket-images-inline
racket-images-keep-last
racket-images-system-viewer
racket-pretty-print
racket-indent-curly-as-sequence
racket-indent-sequence-depth
racket-pretty-lambda
racket-smart-open-bracket-enable)))))
;; Show lists of enabled and disabled minor modes, each sorted by name.
(let* ((minor-modes (cl-remove-duplicates
(append minor-mode-list
(mapcar #'car minor-mode-alist))))
(modes/values (mapcar #'id-val minor-modes))
(sorted (sort modes/values
(lambda (a b)
(string-lessp (format "%s" (car a))
(format "%s" (car b)))))))
(cl-labels ((f (x) (list (car x)))) ;car as a list so pp line-wraps
(pp `(enabled-minor-modes ,@(mapcar #'f (cl-remove-if-not #'cadr sorted))))
(pp `(disabled-minor-modes ,@(mapcar #'f (cl-remove-if #'cadr sorted)))))))
(princ "```\n")
(princ "</details>\n")))
(provide 'racket-bug-report)
;;; racket-bug-report.el ends here

View File

@@ -0,0 +1,320 @@
;;; racket-collection.el
;; Copyright (c) 2013-2016 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'ido)
(require 'tq)
(require 'racket-repl)
(require 'racket-complete) ;for `racket--symbol-at-point-or-prompt'
(require 'racket-custom) ;for `racket-program'
(require 'racket-util)
;;; racket-find-collection
(defun racket-find-collection (&optional prefix)
"Given a collection name, try to find its directory and files.
Takes a collection name from point (or, with a prefix, prompts you).
If only one directory is found, `ido-find-file-in-dir' lets you
pick a file there.
If more than one directory is found, `ido-completing-read' lets
you pick one, then `ido-find-file-in-dir' lets you pick a file
there.
Note: This requires the `raco-find-collection' package to be
installed. To install it, in `shell' enter:
raco pkg install raco-find-collection
Tip: This works best with `ido-enable-flex-matching' set to t.
Also handy is the `flx-ido' package from MELPA.
See also: `racket-visit-module' and `racket-open-require-path'."
(interactive "P")
(pcase (racket--symbol-at-point-or-prompt prefix "Collection name: ")
(`() nil)
(coll
(pcase (racket--cmd/await `(find-collection ,coll))
(`()
(user-error (format "Collection `%s' not found" coll)))
(`(,path)
(racket--find-file-in-dir path))
(paths
(let ((done nil))
(while (not done)
;; `(ido-find-file-in-dir (ido-completing-read paths))`
;; -- except we want to let the user press C-g inside
;; ido-find-file-in-dir to back up and pick a different
;; module path.
(let ((dir (ido-completing-read "Directory: " paths)))
(condition-case ()
(progn (racket--find-file-in-dir dir)
(setq done t))
(quit nil))))))))))
(defun racket--find-file-in-dir (dir)
"Like `ido-find-file-in-dir', but allows C-d to `dired' as does `ido-find-file'."
(ido-file-internal ido-default-file-method nil dir))
;;; racket-open-require-path
;; From looking at ido-mode and ido-vertical-mode:
;;
;; Just use read-from-minibuffer.
;;
;; We're doing vertical mode, so we don't need var like ido-eoinput.
;; We can simply look for the first \n in the minibuffer -- that's the
;; end of user input.
;;
;; Everything after the input and first \n, is the candiates we
;; display, \n separated. The minibuffer automatically grows
;; vertically.
;;
;; Have some maximum number of candidates to display (10?). If > 10, print
;; last line 10 as "...", like ido-vertical-mode.
;;
;; Also use a keymap for commands:
;; - C-n and C-p, which move through the candidates
;; - ENTER
;; - on a dir will add its contents to the candidates (like DrR's
;; "Enter Subsellection" button.
;; - on a file will exit and open the file.
;;
;; Remember that typing a letter triggers `self-insert-command'.
;; Therefore the pre and post command hooks will run then, too.
;;
;; Early version of this used racket--eval/sexpr. Couldn't keep up
;; with typing. Instead: run dedicated Racket process and more direct
;; pipe style; the process does a read-line and responds with each
;; choice on its own line, terminated by a blank like (like HTTP
;; headers).
(defvar racket--orp/tq nil
"tq queue")
(defvar racket--orp/active nil ;;FIXME: Use minibuffer-exit-hook instead?
"Is `racket-open-require-path' using the minibuffer?")
(defvar racket--orp/input ""
"The current user input. Unless user C-g's this persists, as with DrR.")
(defvar racket--orp/matches nil
"The current user matches. Unless user C-g's this persists, as with DrR.")
(defvar racket--orp/match-index 0
"The index of the current match selected by the user.")
(defvar racket--orp/max-height 10
"The maximum height of the minibuffer.")
(defvar racket--orp/keymap
(racket--easy-keymap-define
'((("RET" "C-j") racket--orp/enter)
("C-g" racket--orp/quit)
(("C-p" "<up>") racket--orp/prev)
(("C-n" "<down>") racket--orp/next)
;; Some keys should be no-ops.
(("SPC" "TAB" "C-v" "<next>" "M-v" "<prior>" "M-<" "<home>" "M->" "<end>")
racket--orp/nop))))
(defun racket--orp/begin ()
(let ((proc (start-process "racket-find-module-path-completions-process"
"*racket-find-module-path-completions*"
racket-program
(expand-file-name "find-module-path-completions.rkt"
racket--rkt-source-dir))))
(setq racket--orp/tq (tq-create proc))))
(defun racket--orp/request-tx-matches (input)
"Request matches from the Racket process; delivered to `racket--orp/rx-matches'."
(when racket--orp/tq
(tq-enqueue racket--orp/tq
(concat input "\n")
".*\n\n"
(current-buffer)
'racket--orp/rx-matches)))
(defun racket--orp/rx-matches (buffer answer)
"Completion proc; receives answer to request by `racket--orp/request-tx-matches'."
(when racket--orp/active
(setq racket--orp/matches (split-string answer "\n" t))
(setq racket--orp/match-index 0)
(with-current-buffer buffer
(racket--orp/draw-matches))))
(defun racket--orp/end ()
(when racket--orp/tq
(tq-close racket--orp/tq)
(setq racket--orp/tq nil)))
(defun racket-open-require-path ()
"Like Dr Racket's Open Require Path.
Type (or delete) characters that are part of a module path name.
\"Fuzzy\" matches appear. For example try typing \"t/t/r\".
Choices are displayed in a vertical list. The current choice is
at the top, marked with \"->\".
- C-n and C-p move among the choices.
- RET on a directory adds its contents to the choices.
- RET on a file exits doing `find-file'.
- C-g aborts.
Note: This requires Racket 6.1.1.6 or newer. Otherwise it won't
error, it will just never return any matches."
(interactive)
(racket--orp/begin)
(setq racket--orp/active t)
(setq racket--orp/match-index 0)
;; We do NOT initialize `racket--orp/input' or `racket--orp/matches'
;; here. Like DrR, we remember from last time invoked. We DO
;; initialize them in racket--orp/quit i.e. user presses C-g.
(add-hook 'minibuffer-setup-hook #'racket--orp/minibuffer-setup)
(condition-case ()
(progn
(read-from-minibuffer "Open require path: "
racket--orp/input
racket--orp/keymap)
(when racket--orp/matches
(find-file (elt racket--orp/matches racket--orp/match-index))))
(error (setq racket--orp/input "")
(setq racket--orp/matches nil)))
(setq racket--orp/active nil)
(racket--orp/end))
(defun racket--orp/minibuffer-setup ()
(add-hook 'pre-command-hook #'racket--orp/pre-command nil t)
(add-hook 'post-command-hook #'racket--orp/post-command nil t)
(when racket--orp/active
(racket--orp/draw-matches)))
(defun racket--orp/eoinput ()
"Return position where user input ends, i.e. the first \n before the
candidates or (point-max)."
(save-excursion
(goto-char (point-min))
(condition-case ()
(1- (re-search-forward "\n"))
(error (point-max)))))
(defun racket--orp/get-user-input ()
"Get the user's input from the mini-buffer."
(buffer-substring-no-properties (minibuffer-prompt-end)
(racket--orp/eoinput)))
(defun racket--orp/pre-command ()
nil)
(defun racket--orp/post-command ()
"Update matches if input changed.
Also constrain point in case user tried to navigate past
`racket--orp/eoinput'."
(when racket--orp/active
(let ((input (racket--orp/get-user-input)))
(when (not (string-equal input racket--orp/input))
(racket--orp/on-input-changed input)))
(let ((eoi (racket--orp/eoinput)))
(when (> (point) eoi)
(goto-char eoi)))))
(defun racket--orp/on-input-changed (input)
(setq racket--orp/input input)
(cond ((string-equal input "") ;"" => huge list; ignore like DrR
(setq racket--orp/match-index 0)
(setq racket--orp/matches nil)
(racket--orp/draw-matches))
(t (racket--orp/request-tx-matches input))))
(defun racket--orp/draw-matches ()
(save-excursion
(let* ((inhibit-read-only t)
(eoi (racket--orp/eoinput))
(len (length racket--orp/matches))
(n (min racket--orp/max-height len))
(i racket--orp/match-index))
(delete-region eoi (point-max)) ;delete existing
(while (> n 0)
(insert "\n")
(cond ((= i racket--orp/match-index) (insert "-> "))
(t (insert " ")))
(insert (elt racket--orp/matches i))
(setq n (1- n))
(cond ((< (1+ i) len) (setq i (1+ i)))
(t (setq i 0))))
(when (< racket--orp/max-height len)
(insert "\n ..."))
(put-text-property eoi (point-max) 'read-only 'fence))))
(defun racket--orp/enter ()
"On a dir, adds its contents to choices. On a file, opens the file."
(interactive)
(when racket--orp/active
(let ((match (and racket--orp/matches
(elt racket--orp/matches racket--orp/match-index))))
(cond (;; Pressing RET on a directory inserts its contents, like
;; "Enter subcollection" button in DrR.
(and match (file-directory-p match))
(racket--trace "enter" 'add-subdir)
(setq racket--orp/matches
(delete-dups ;if they RET same item more than once
(sort (append racket--orp/matches
(directory-files match t "[^.]+$"))
#'string-lessp)))
(racket--orp/draw-matches))
(;; Pressing ENTER on a file selects it. We exit the
;; minibuffer; our main function treats non-nil
;; racket--orp/matches and racket--orp/match-index as a
;; choice (as opposed to quitting w/o a choice.
t
(racket--trace "enter" 'exit-minibuffer)
(exit-minibuffer))))))
(defun racket--orp/quit ()
"Our replacement for `keyboard-quit'."
(interactive)
(when racket--orp/active
(racket--trace "quit")
(setq racket--orp/input "")
(setq racket--orp/matches nil)
(exit-minibuffer)))
(defun racket--orp/next ()
"Select the next match."
(interactive)
(when racket--orp/active
(setq racket--orp/match-index (1+ racket--orp/match-index))
(when (>= racket--orp/match-index (length racket--orp/matches))
(setq racket--orp/match-index 0))
(racket--orp/draw-matches)))
(defun racket--orp/prev ()
"Select the previous match."
(interactive)
(when racket--orp/active
(setq racket--orp/match-index (1- racket--orp/match-index))
(when (< racket--orp/match-index 0)
(setq racket--orp/match-index (max 0 (1- (length racket--orp/matches)))))
(racket--orp/draw-matches)))
(defun racket--orp/nop ()
"A do-nothing command target."
(interactive)
nil)
(provide 'racket-collection)
;; racket-collection.el ends here

View File

@@ -0,0 +1,778 @@
;;; racket-common.el
;; Copyright (c) 2013-2016 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
;; Things used by both racket-mode and racket-repl-mode
(require 'cl-lib)
(require 'thingatpt)
(require 'racket-custom)
(require 'racket-keywords-and-builtins)
(require 'racket-font-lock)
(require 'racket-indent)
(require 'racket-ppss)
(require 'racket-util)
(declare-function racket-complete-at-point "racket-complete.el" (&optional predicate))
(defvar racket-mode-abbrev-table nil)
(define-abbrev-table 'racket-mode-abbrev-table ())
;;; syntax-table and syntax-propertize-function
(defvar racket-mode-syntax-table
(let ((st (make-syntax-table))
(i 0))
;; Symbol constituents
(while (< i ?0)
(modify-syntax-entry i "_ " st)
(setq i (1+ i)))
(setq i (1+ ?9))
(while (< i ?A)
(modify-syntax-entry i "_ " st)
(setq i (1+ i)))
(setq i (1+ ?Z))
(while (< i ?a)
(modify-syntax-entry i "_ " st)
(setq i (1+ i)))
(setq i (1+ ?z))
(while (< i 128)
(modify-syntax-entry i "_ " st)
(setq i (1+ i)))
;; Whitespace (except ?\n, see below in comment section)
(modify-syntax-entry ?\t " " st)
(modify-syntax-entry ?\f " " st)
(modify-syntax-entry ?\r " " st)
(modify-syntax-entry ?\s " " st)
;; These characters are delimiters but otherwise undefined.
;; Brackets and braces balance for editing convenience.
(modify-syntax-entry ?\( "() " st)
(modify-syntax-entry ?\) ")( " st)
(modify-syntax-entry ?\[ "(] " st)
(modify-syntax-entry ?\] ")[ " st)
(modify-syntax-entry ?{ "(} " st)
(modify-syntax-entry ?} "){ " st)
;; Other atom delimiters
(modify-syntax-entry ?\" "\" " st)
(modify-syntax-entry ?' "' " st)
(modify-syntax-entry ?` "' " st)
(modify-syntax-entry ?, "' " st)
(modify-syntax-entry ?@ "' " st)
(modify-syntax-entry ?\\ "\\ " st)
;; Comment related
(modify-syntax-entry ?\; "< " st) ;line comments but NOT sexp #;
(modify-syntax-entry ?\n "> " st)
(modify-syntax-entry ?# "w 14" st) ;not necessarily prefix
(modify-syntax-entry ?| "_ 23bn" st)
st))
(defun racket-syntax-propertize-function (start end)
(goto-char start)
(racket--syntax-propertize-here-string end)
(funcall
(syntax-propertize-rules
;; here strings: The main responsibility here is to set the "|"
;; char syntax around the "body" so it's treated as a string for
;; indent, nav, font-lock. Think of the \n in #<<ID\n as the open
;; | quote and the \n in ^ID\n as the close | quote.
((rx "#<<" (group (+? (not (any blank ?\n)))) (group ?\n))
(2 (racket--syntax-propertize-open-here-string
(match-beginning 0)
(match-string-no-properties 1)
(match-beginning 2))))
((rx (syntax string-delimiter))
(0 (ignore (racket--syntax-propertize-here-string end))))
;; sexp comments should LOOK like comments but NOT ACT like
;; comments: Give the #; itself the syntax class "prefix" [1], but
;; allow the following sexp to get the usual syntaxes. That way
;; things like indent and sexp nav work within the sexp. Only
;; font-lock handles the sexp specially; see racket-font-lock.el.
;;
;; [1]: Although it's tempting to use punctuation -- so things like
;; `backward-sexp' and `racket-send-last-sexp' ignore the #; --
;; that would mess up indentation of things following the sexp
;; comment. Instead special-case `racket-send-last-sexp'.
((rx "#;")
(0 "'"))
;; Treat "complex" reader literals as a single sexp for nav and
;; indent, by marking the stuff after the # as prefix syntax.
;; Racket predefines reader literals like #"" #rx"" #px"" #hash()
;; #hasheq() #fx3(0 1 2) #s() and so on. I think these -- plus any
;; user defined reader extensions -- can all be covered with the
;; following general rx. Also it seems sufficient to look for just
;; the opening delimiter -- the ( [ { or " -- here.
((rx (group ?#
(zero-or-more (or (syntax symbol)
(syntax word))))
(or ?\" ?\( ?\[ ?\{))
(1 "'"))
;; Syntax quoting
((rx ?# (or ?` ?' ?,))
(0 "'"))
;; Treat '|symbol with spaces| as word syntax
((rx ?' ?| (+ any) ?|)
(0 "w"))
;; Treat |identifier with spaces| -- but not #|comment|# -- as
;; word syntax
((rx (not (any ?#))
(group ?| (+? (not (any "|\"\r\n"))) ?|)
(not (any ?#)))
(1 "w")))
(point)
end))
(defun racket--syntax-propertize-open-here-string (start string eol)
"Determine the syntax of the \\n after a #<<HERE
START is the position of #<<.
STRING is the actual word used as delimiter (e.g. \"HERE\").
EOL is the position of the \\n.
Point is at the beginning of the next line.
This sets the open | syntax and sets a 'racket-here-string
property whose value is STRING. The close | syntax is set by
`racket--syntax-propertize-here-string'."
(unless (save-excursion
(let ((ppss (syntax-ppss start)))
(or (racket--ppss-string-p ppss)
(racket--ppss-comment-p ppss))))
(let ((ppss (save-excursion (syntax-ppss eol))))
(if (racket--ppss-comment-p ppss)
;; The \n not only starts the heredoc but also closes a comment.
;; Let's close the comment just before the \n.
(put-text-property (1- eol) eol 'syntax-table '(12))) ;">"
(if (or (racket--ppss-quote-p ppss)
(< 1 (count-lines start eol)))
;; If we matched several lines, make sure we refontify them
;; together. Furthermore, if the \n is quoted, it means the
;; right \n is actually further down. Don't bother fixing it
;; now, but place a multiline property so that when
;; jit-lock-context-* refontifies the rest of the buffer, it
;; also refontifies the current line with it.
(put-text-property start (1+ eol) 'syntax-multiline t))
(put-text-property eol (1+ eol) 'racket-here-string string)
(goto-char (+ 3 start))
(string-to-syntax "|"))))
(defun racket--syntax-propertize-here-string (end)
"If in a here string that ends before END, add | syntax for its close."
(let ((ppss (syntax-ppss)))
(when (eq (racket--ppss-string-p ppss) t) ;t as opposed to ?" or ?'
(let ((key (get-text-property (racket--ppss-string/comment-start ppss)
'racket-here-string)))
(when (and key
(re-search-forward (concat "^" (regexp-quote key) "\\(\n\\)")
end t))
(let ((eol (match-beginning 1)))
(put-text-property eol (1+ eol)
'syntax-table
(string-to-syntax "|"))))))))
;;;
(defun racket--common-variables ()
"Set variables common to `racket-mode' and `racket-repl-mode'."
;;; Syntax
(set-syntax-table racket-mode-syntax-table)
(setq-local multibyte-syntax-as-symbol t)
(setq-local parse-sexp-ignore-comments t)
(setq-local syntax-propertize-function #'racket-syntax-propertize-function)
(syntax-propertize (point-max)) ;for e.g. paredit: see issue #222
;; -----------------------------------------------------------------
;; Font-lock
(setq-local font-lock-defaults
(list racket-font-lock-keywords ;keywords
nil ;keywords-only?
nil ;case-fold?
nil ;syntax-alist
nil ;syntax-begin
;; Additional variables:
(cons 'font-lock-mark-block-function #'mark-defun)
(cons 'parse-sexp-lookup-properties t)
(cons 'font-lock-multiline t)
(cons 'font-lock-syntactic-face-function
#'racket-font-lock-syntactic-face-function)
(list 'font-lock-extend-region-functions
#'font-lock-extend-region-wholelines
#'font-lock-extend-region-multiline)))
;; -----------------------------------------------------------------
;; Comments. Mostly borrowed from lisp-mode and/or scheme-mode
(setq-local comment-start ";")
(setq-local comment-add 1) ;default to `;;' in comment-region
(setq-local comment-start-skip ";+ *")
(setq-local comment-column 40)
(setq-local comment-multi-line t) ;for auto-fill-mode and #||# comments
;; Font lock mode uses this only when it knows a comment is starting:
(setq-local font-lock-comment-start-skip ";+ *")
;; -----------------------------------------------------------------
;; Indent
(setq-local indent-line-function #'racket-indent-line)
(racket--set-indentation)
(setq-local indent-tabs-mode nil)
;; -----------------------------------------------------------------
;;; Misc
(setq-local local-abbrev-table racket-mode-abbrev-table)
(setq-local paragraph-start (concat "$\\|" page-delimiter))
(setq-local paragraph-separate paragraph-start)
(setq-local paragraph-ignore-fill-prefix t)
(setq-local fill-paragraph-function #'lisp-fill-paragraph)
(setq-local adaptive-fill-mode nil)
(setq-local outline-regexp ";;; \\|(....")
(setq-local completion-at-point-functions (list #'racket-complete-at-point))
(setq-local eldoc-documentation-function nil)
(setq-local beginning-of-defun-function #'racket--beginning-of-defun-function))
;;; Insert lambda char (like DrRacket)
(defconst racket-lambda-char (make-char 'greek-iso8859-7 107)
"Character inserted by `racket-insert-labmda'.")
(defun racket-insert-lambda ()
(interactive)
(insert-char racket-lambda-char 1))
(put 'racket-insert-lambda 'delete-selection t)
;;; racket--self-insert
(defun racket--self-insert (event)
"Simulate a `self-insert-command' of EVENT.
Using this intead of `insert' allows self-insert hooks to run,
which is important for things like `'electric-pair-mode'.
A command using this should probably set its 'delete-selection
property to t so that `delete-selection-mode' works:
(put 'racket-command 'delete-selection t)
If necessary the value of the property can be a function, for
example `racket--electric-pair-mode-not-active'."
(let ((last-command-event event)) ;set this for hooks
(self-insert-command (prefix-numeric-value nil))))
(defun racket--electric-pair-mode-not-active ()
"A suitable value for the 'delete-selection property of
commands that insert parens: Inserted text should replace the
selection unless a mode like `electric-pair-mode' is enabled, in
which case the selection is to be wrapped in parens."
(not (and (boundp 'electric-pair-mode)
electric-pair-mode)))
;;; Automatically insert matching \?) \?] or \?}
(defconst racket--matching-parens
'(( ?\( . ?\) )
( ?\[ . ?\] )
( ?\{ . ?\} )))
(defun racket-insert-closing (&optional prefix)
"Insert a matching closing delimiter.
With a prefix, insert the typed character as-is.
This is handy if you're not yet using `paredit-mode',
`smartparens-mode', or simply `electric-pair-mode' added in Emacs
24.5."
(interactive "P")
(let* ((do-it (not (or prefix
(and (string= "#\\"
(buffer-substring-no-properties
(- (point) 2) (point) )))
(racket--ppss-string-p (syntax-ppss)))))
(open-char (and do-it (racket--open-paren #'backward-up-list)))
(close-pair (and open-char (assq open-char racket--matching-parens)))
(close-char (and close-pair (cdr close-pair))))
(racket--self-insert (or close-char last-command-event))))
(put 'racket-insert-closing 'delete-selection
#'racket--electric-pair-mode-not-active)
;;; Smart open bracket
(defconst racket--smart-open-bracket-data
(eval-when-compile
`(;; cond-like
(0 0 ,(rx (seq "("
(or "augment"
"augment-final"
"augride"
"cond"
"field"
"inherit"
"inherit-field"
"inherit/super"
"inherit/inner"
"init"
"init-field"
"match-lambda"
"match-lambda*"
"match-lambda**"
"overment"
"override"
"override-final"
"public"
"pubment"
"public-final"
"rename-inner"
"rename-super"
"super-new")
(or space line-end))))
;; case-like
(2 0 ,(rx (seq "("
(or "case"
"new"
"match"
"match*"
"syntax-parse"
"syntax-rules")
(or space line-end))))
;; syntax-case
(3 0 ,(rx (seq "("
(or "syntax-case")
(or space line-end))))
;; syntax-case*
(4 0 ,(rx (seq "("
(or "syntax-case*")
(or space line-end))))
;; let-like
;;
;; In addition to the obvious suspects with 'let' in the name,
;; handles forms like 'parameterize', 'with-handlers', 'for',
;; and 'for/fold' accumulator bindings.
(0 1 ,(rx (seq (or "for"
"for/list"
"for/vector"
"for/hash"
"for/hasheq"
"for/hasheqv"
"for/and"
"for/or"
"for/lists"
"for/first"
"for/last"
"for/fold"
"for/flvector"
"for/extflvector"
"for/set"
"for/sum"
"for/product"
"for*"
"for*/list"
"for*/vector"
"for*/hash"
"for*/hasheq"
"for*/hasheqv"
"for*/and"
"for*/or"
"for*/lists"
"for*/first"
"for*/last"
"for*/fold"
"for*/flvector"
"for*/extflvector"
"for*/set"
"for*/sum"
"for*/product"
"fluid-let"
"let"
"let*"
"let*-values"
"let-struct"
"let-syntax"
"let-syntaxes"
"let-values"
"let/cc"
"let/ec"
"letrec"
"letrec-syntax"
"letrec-syntaxes"
"letrec-syntaxes+values"
"letrec-values"
"match-let"
"match-let*"
"match-let-values"
"match-let*-values"
"match-letrec"
"parameterize"
"parameterize*"
"with-handlers"
"with-handlers*"
"with-syntax"
"with-syntax*")
(or space line-end))))
;; for/fold bindings
;;
;; Note: Previous item handles the first, accumulators subform.
(0 2 ,(rx (seq (or "for/fold"
"for*/fold")
(or space line-end))))
;; named-let bindings
;;
(0 2 ,(rx (seq "let" (1+ whitespace) (1+ (not (in "()[]{}\",'`;#|\" "))))))))
"A list of lists. Each sub list is arguments to supply to
`racket--smart-open-bracket-helper'.")
(defun racket--smart-open-bracket-helper (pre-backward-sexps
post-backward-sexps
regexp)
"Is point is a subform (of a known form REGEXP) that should open with '['.
Returns '[' or nil."
(and (save-excursion
(ignore-errors
(backward-sexp pre-backward-sexps) t))
(save-excursion
(ignore-errors
(let ((pt (point)))
(backward-up-list)
(backward-sexp post-backward-sexps)
(when (looking-at-p regexp)
?\[))))))
(defun racket-smart-open-bracket ()
"Automatically insert a `(` or a `[` as appropriate.
When `racket-smart-open-bracket-enable' is nil, this simply
inserts `[`. Otherwise, this behaves like the \"Automatically
adjust opening square brackets\" feature in Dr. Racket:
By default, inserts a `(`. Inserts a `[` in the following cases:
- `let`-like bindings -- forms with `let` in the name as well
as things like `parameterize`, `with-handlers`, and
`with-syntax`.
- `case`, `cond`, `match`, `syntax-case`, `syntax-parse`, and
`syntax-rules` clauses.
- `for`-like bindings and `for/fold` accumulators.
- `class` declaration syntax, such as `init` and `inherit`.
When the previous s-expression in a sequence is a compound
expression, uses the same kind of delimiter.
To force insert `[`, use `quoted-insert': \\[quoted-insert] [.
Combined with `racket-insert-closing' this means that
you can press the unshifted `[` and `]` keys to get whatever
delimiters follow the Racket conventions for these forms. (When
`electric-pair-mode' or `paredit-mode' is active, you need not
even press `]`."
(interactive)
(let ((ch (or (and (not racket-smart-open-bracket-enable)
?\[)
(and (save-excursion
(let ((pt (point)))
(beginning-of-defun)
(let ((state (parse-partial-sexp (point) pt)))
(or (racket--ppss-string-p state)
(racket--ppss-comment-p state)))))
?\[)
(cl-some (lambda (xs)
(apply #'racket--smart-open-bracket-helper xs))
racket--smart-open-bracket-data)
(racket--open-paren #'backward-sexp)
?\()))
(if (fboundp 'racket--paredit-aware-open)
(racket--paredit-aware-open ch)
(racket--self-insert ch))))
(put 'racket-smart-open-bracket 'delete-selection
#'racket--electric-pair-mode-not-active)
(eval-after-load 'paredit
'(progn
(defvar paredit-mode-map nil) ;byte compiler
(declare-function paredit-open-round 'paredit)
(declare-function paredit-open-square 'paredit)
(declare-function paredit-open-curly 'paredit)
(defvar racket--paredit-original-open-bracket-binding
(lookup-key paredit-mode-map (kbd "["))
"The previous `paredit-mode-map' binding for [.
Rather than assuming that it's `paredit-open-square', we store
the actual value. This seems like the right thing to do in case
someone else is doing similar hackery.")
(add-hook 'paredit-mode-hook
(lambda ()
(define-key paredit-mode-map
(kbd "[") 'racket--paredit-open-square)))
(defun racket--paredit-open-square ()
"`racket-smart-open-bracket' or original `paredit-mode-map' binding.
To be compatible with `paredit-mode', `racket-smart-open-bracket'
must intercept [ and decide whether to call `paredit-open-round'
or `paredit-open-square'. To do so it must modify
`paredit-mode-map', which affects all major modes. Therefore we
check whether the current buffer's major mode is `racket-mode'.
If not we call the function in the variable
`racket--paredit-original-open-bracket-binding'."
(interactive)
(if (racket--mode-edits-racket-p)
(racket-smart-open-bracket)
(funcall racket--paredit-original-open-bracket-binding)))
(defun racket--paredit-aware-open (ch)
"A paredit-aware helper for `racket-smart-open-bracket'.
When `paredit-mode' is active, use its functions (such as
`paredit-open-round') Note: This function isn't defined unless
paredit is loaded, so check for this function's existence using
`fboundp'."
(let ((paredit-active (and (boundp 'paredit-mode) paredit-mode)))
(cond ((not paredit-active) (racket--self-insert ch))
((eq ch ?\() (paredit-open-round))
((eq ch ?\[) (paredit-open-square))
((eq ch ?\{) (paredit-open-curly))
(t (racket--self-insert ch)))))))
;;; paredit and reader literals
(defun racket--reader-literal-paredit-space-for-delimiter-predicate (endp delimiter)
"`paredit-mode' shouldn't insert space beteween # and open delimiters.
Examples: #() #2() #fl() #hasheq etc.
This function is a suitable element for the list variable
`paredit-space-for-delimiter-predicates'. "
(if (and (racket--mode-edits-racket-p)
(not endp))
(not (looking-back (rx ?# (* (or (syntax word) (syntax symbol))))
nil))
t))
(eval-after-load 'paredit
'(add-hook 'paredit-space-for-delimiter-predicates
#'racket--reader-literal-paredit-space-for-delimiter-predicate))
;;; paredit and at-expressions
(defun racket--at-expression-paredit-space-for-delimiter-predicate (endp delimiter)
"`paredit-mode' shouldn't insert space before [ or { in Racket at-expressions.
This function is a suitable element for the list variable
`paredit-space-for-delimiter-predicates'. "
(if (and (racket--mode-edits-racket-p)
(not endp))
(not (or
;; @foo[ @foo{
(and (memq delimiter '(?\[ ?\{))
(looking-back (rx ?@ (* (or (syntax word) (syntax symbol))))
nil))
;; @foo[]{
(and (eq delimiter ?\{)
(looking-back (rx ?@ (* (or (syntax word) (syntax symbol)))
?\[
(* (or (syntax word) (syntax symbol)))
?\])
nil))))
t))
(eval-after-load 'paredit
'(add-hook 'paredit-space-for-delimiter-predicates
#'racket--at-expression-paredit-space-for-delimiter-predicate))
;;; Cycle paren shapes
(defconst racket--paren-shapes
'( (?\( ?\[ ?\] )
(?\[ ?\{ ?\} )
(?\{ ?\( ?\) ))
"This is not user-configurable because we expect them have to
have actual ?\( and ?\) char syntax.")
(defun racket-cycle-paren-shapes ()
"Cycle the sexpr among () [] {}."
(interactive)
(save-excursion
(unless (eq ?\( (char-syntax (char-after)))
(backward-up-list))
(pcase (assq (char-after) racket--paren-shapes)
(`(,_ ,open ,close)
(delete-char 1)
(insert open)
(backward-char 1)
(forward-sexp 1)
(backward-delete-char 1)
(insert close))
(_
(user-error "Don't know that paren shape")))))
;;; racket--beginning-of-defun
(defun racket--beginning-of-defun-function ()
"Like `beginning-of-defun' but aware of Racket module forms."
(let ((orig (point)))
(racket--escape-string-or-comment)
(pcase (racket--module-level-form-start)
(`() (ignore-errors (backward-sexp 1)))
(pos (goto-char pos)))
(/= orig (point))))
(defun racket--module-level-form-start ()
"Start position of the module-level form point is within.
A module-level form is the outermost form not nested in a Racket
module form.
If point is not within a module-level form, returns nil.
If point is already exactly at the start of a module-level form,
-- i.e. on the opening ?\( -- returns nil.
If point is within a string or comment, returns nil.
This is NOT suitable for the variable `syntax-begin-function'
because it (i) doesn't move point, and (ii) doesn't know how to
find the start of a string or comment."
(save-excursion
(ignore-errors
(let ((pos nil)
(parse-sexp-ignore-comments t))
(while (ignore-errors
(goto-char (scan-lists (point) -1 1))
(unless (looking-at racket-module-forms)
(setq pos (point)))
t))
(and pos
(or (racket--sexp-comment-start pos)
pos))))))
(defun racket--sexp-comment-start (pos)
"Start pos of sexp comment (if any) immediately before POS.
Allows #; to be followed by zero or more space or newline chars."
(save-excursion
(goto-char pos)
(while (memq (char-before) '(32 ?\n))
(goto-char (1- (point))))
(when (string= "#;" (buffer-substring-no-properties (- (point) 2) (point)))
(- (point) 2))))
;;; racket--what-to-run
(defun racket--what-to-run ()
(cons (racket--buffer-file-name) (racket--submod-path)))
(defun racket--submod-path ()
(and (racket--lang-p)
(racket--modules-at-point)))
(defun racket--lang-p ()
"Is #lang the first sexpr in the file?"
(save-excursion
(goto-char (point-min))
(ignore-errors
(forward-sexp)
(backward-sexp)
(looking-at (rx "#lang")))))
(defun racket--modules-at-point ()
"List of module names that point is within, from outer to inner.
Ignores module forms nested (at any depth) in any sort of plain
or syntax quoting, because those won't be valid Racket syntax."
(let ((xs nil))
(condition-case ()
(save-excursion
(save-match-data
(racket--escape-string-or-comment)
(while t
(when (racket--looking-at-module-form)
(push (intern (match-string-no-properties 1)) xs))
(when (racket--looking-at-quoted-form)
(push nil xs))
(backward-up-list))))
(scan-error xs))
(racket--take-while xs #'identity)))
(defun racket--looking-at-module-form ()
"Sets match data group 1 to the module name."
(looking-at (rx ?\(
(or "module" "module*" "module+")
(1+ " ")
(group (+ (or (syntax symbol)
(syntax word)))))))
(defun racket--looking-at-quoted-form ()
(or (memq (char-before) '(?\' ?\` ?\,))
(and (eq (char-before (1- (point))) ?\,)
(eq (char-before) ?\@))
(looking-at
(rx ?\(
(or "quote" "quasiquote"
"unquote" "unquote-splicing"
"quote-syntax"
"syntax" "syntax/loc"
"quasisyntax" "quasisyntax/loc"
"unsyntax" "unsyntax-splicing")
" "))))
;;; Misc
(defun racket--escape-string-or-comment ()
"If point is in a string or comment, move to its start.
Note that this can be expensive, as it uses `syntax-ppss' which
parses from the start of the buffer. Although `syntax-ppss' uses
a cache, that is invalidated after any changes to the buffer. As
a result, the worst case would be to call this function after
every character is inserted to a buffer."
(pcase (racket--ppss-string/comment-start (syntax-ppss))
(`() nil)
(pos (goto-char pos))))
(defun racket-backward-up-list ()
"Like `backward-up-list' but works when point is in a string or comment.
Typically you should not use this command in Emacs Lisp --
especially not repeatedly. Instead, initially use
`racket--escape-string-or-comment' to move to the start of a
string or comment, if any, then use normal `backward-up-list'
repeatedly."
(interactive)
(racket--escape-string-or-comment)
(backward-up-list 1))
(defun racket--open-paren (back-func)
"Use BACK-FUNC to find an opening ( [ or { if any.
BACK-FUNC should be something like #'backward-sexp or #'backward-up-list."
(save-excursion
(ignore-errors
(funcall back-func)
(let ((ch (char-after)))
(and (eq ?\( (char-syntax ch))
ch)))))
(provide 'racket-common)
;; racket-common.el ends here

View File

@@ -0,0 +1,308 @@
;;; racket-complete.el -*- lexical-binding: t -*-
;; Copyright (c) 2013-2018 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'cl-lib)
(require 'ido)
(require 'racket-custom)
(require 'racket-repl)
(require 'racket-keywords-and-builtins)
(require 'shr)
(require 's)
(declare-function racket--do-visit-def-or-mod "racket-edit.el")
;;; namespace symbols i.e. completion candidates
(defvar-local racket--namespace-symbols nil
"A cache of the list of all Racket namespace symbols.
This var is local to each buffer, including the REPL buffer.
`racket-run' should call `racket--invalidate-completion-cache'.
See `racket--get-namespace-symbols'.")
(defun racket--invalidate-completion-cache ()
"Both current `racket-mode' buffer and `racket-repl-mode' buffer (if any)."
(setq racket--namespace-symbols nil)
(with-racket-repl-buffer
(setq racket--namespace-symbols nil)))
(add-hook 'racket--repl-before-run-hook #'racket--invalidate-completion-cache)
(defun racket--completion-candidates ()
"Completion candidates, as a list of list of strings.
Gets from the cache, or if nil from the Racket process, or if
that's not running from the `defconst' lists of strings we use
for font-lock. To support the last case -- while avoiding
`append' and allocation of such large lists of strings -- is why
we always return a list of list of strings."
(unless racket--namespace-symbols
(when (racket--in-repl-or-its-file-p)
(setq racket--namespace-symbols
(list (racket--cmd/await '(syms))))))
(or racket--namespace-symbols
(list racket-type-list
racket-keywords
racket-builtins-1-of-2
racket-builtins-2-of-2)))
(defun racket--completion-candidates-for-prefix (prefix)
(cl-reduce (lambda (results strs)
(append results (all-completions prefix strs)))
(racket--completion-candidates)
:initial-value ()))
(defun racket-complete-at-point (&optional _predicate)
"Default value for the variable `completion-at-point-functions'.
Completion candidates are drawn from the namespace symbols
resulting from the most recent `racket-run' of each .rkt file. If
a file has never been run, candidates default to values also used
for font-lock -- an assortment of symbols from common Racket
modules such as `racket`, `typed/racket`, and `syntax/parse`.
Returns extra :company-doc-buffer and :company-location
properties for use by the `company-mode' backend `company-capf'
-- but not :company-docsig, because it is frequently impossible
to supply this quickly enough or at all."
(let ((beg (save-excursion (skip-syntax-backward "^-()>") (point))))
(unless (or (eq beg (point-max))
(member (char-syntax (char-after beg)) '(?\" ?\( ?\))))
(condition-case nil
(save-excursion
(goto-char beg)
(forward-sexp 1)
(let ((end (point)))
(and
(<= (+ beg 2) end) ;prefix at least 2 chars
(list beg
end
(completion-table-dynamic
#'racket--completion-candidates-for-prefix)
:predicate #'identity
;; racket--get-type is too slow for :company-docsig
:company-doc-buffer #'racket--do-describe
:company-location #'racket--get-def-file+line))))
(scan-error nil)))))
(defun racket--get-def-file+line (sym)
"Return a value suitable for use as :company-location."
(pcase (racket--cmd/await `(def ,sym))
(`(,path ,line ,_) (cons path line))
(_ nil)))
;;; "types" (i.e. TR types, contracts, and/or function signatures)
(defvar-local racket--type-cache (make-hash-table :test #'eq)
"Memoize \",type\" commands in Racket REPL.
This var is local to each buffer, including the REPL buffer.
`racket-run' should call `racket-invalidate-type-cache'.")
(defun racket--invalidate-type-cache ()
"Both current `racket-mode' buffer and `racket-repl-mode' buffer (if any)."
(setq racket--type-cache (make-hash-table :test #'eq))
(with-racket-repl-buffer
(setq racket--type-cache (make-hash-table :test #'eq))))
(add-hook 'racket--repl-before-run-hook #'racket--invalidate-type-cache)
(defun racket--get-type (str)
(let* ((sym (intern str))
(v (gethash sym racket--type-cache)))
(or v
(and (racket--in-repl-or-its-file-p)
(let ((v (racket--cmd/await `(type ,str))))
(puthash sym v racket--type-cache) v)))))
;;; at-point
(defun racket--symbol-at-point-or-prompt (force-prompt-p prompt)
"Helper for functions that want symbol-at-point, or, to prompt
when there is no symbol-at-point or FORCE-PROMPT-P is true. The
prompt uses `read-from-minibuffer'. Returns `stringp' not
`symbolp' to simplify using the result in a sexpr that can be
passed to Racket backend. Likewise text properties are stripped."
(let ((sap (racket--thing-at-point 'symbol t)))
(if (or force-prompt-p (not sap))
(let ((s (read-from-minibuffer prompt sap)))
(if (equal "" (s-trim s))
nil
s))
sap)))
;;; eldoc
(defun racket-eldoc-function ()
"A value suitable for the variable `eldoc-documentation-function'.
By default racket-mode sets `eldoc-documentation-function' to nil
-- no `eldoc-mode' support. You may set it to this function in a
`racket-mode-hook' if you really want to use `eldoc-mode' with
Racket. But it is not a very satisfying experience because Racket
is not a very \"eldoc friendly\" language. Although racket-mode
attempts to discover argument lists, contracts, or types this
doesn't work in many common cases:
- Many Racket functions are defined in #%kernel. There's no easy
way to determine their argument lists. Most are not provided
with a contract.
- Many of the interesting Racket forms are syntax (macros) not
functions. There's no easy way to determine their \"argument
lists\".
A more satisfying experience is to use `racket-describe' or
`racket-doc'."
(and (racket--repl-live-p)
(> (point) (point-min))
(save-excursion
(condition-case nil
;; The char-before and looking-at checks below are to
;; avoid calling `racket--get-type' when the sexp is
;; quoted or when its first elem couldn't be a Racket
;; function name.
(let* ((beg (progn
(backward-up-list)
(and (not (memq (char-before) '(?` ?' ?,)))
(progn (forward-char 1) (point)))))
(beg (and beg (looking-at "[^0-9#'`,\"]") beg))
(end (and beg (progn (forward-sexp) (point))))
(end (and end
(char-after (point))
(eq ?\s (char-syntax (char-after (point))))
end))
(sym (and beg end (buffer-substring-no-properties beg end)))
(str (and sym (racket--get-type sym))))
str)
(scan-error nil)))))
;;; describe
(defun racket-describe (&optional prefix)
"Describe the identifier at point in a `*Racket Describe*` buffer.
The intent is to give a quick reminder or introduction to
something, regardless of whether it has installed documentation
-- and to do so within Emacs, without switching to a web browser.
This buffer is also displayed when you use `company-mode' and
press F1 or C-h in its pop up completion list.
- If the identifier has installed Racket documentation, then a
simplified version of the HTML is presented in the buffer,
including the \"blue box\", documentation prose, and examples.
- Otherwise, if the identifier is a function, then its signature
is displayed, for example `(name arg-1-name arg-2-name)`. If it
has a contract or a Typed Racket type, that is also displayed.
You can quit the buffer by pressing q. Also, at the bottom of the
buffer are Emacs buttons -- which you may navigate among using
TAB, and activate using RET -- for `racket-visit-definition' and
`racket-doc'."
(interactive "P")
(pcase (racket--symbol-at-point-or-prompt prefix "Describe: ")
(`nil nil)
(str (racket--do-describe str t))))
(defun racket--do-describe (str &optional pop-to)
"A helper for `racket-describe' and company-mode.
POP-TO should be t for the former (in which case some buttons are
added) and nil for the latter.
Returns the buffer in which the description was written."
(let* ((bufname "*Racket Describe*")
(html (racket--cmd/await `(describe ,str)))
;; Emacs shr renderer removes leading &nbsp; from <td> elements
;; -- which messes up the indentation of s-expressions including
;; contracts. So replace &nbsp with `spc' in the source HTML,
;; and replace `spc' with " " after shr-insert-document outputs.
(spc (string #x2020)) ;unlikely character (hopefully)
(dom (with-temp-buffer
(insert html)
(goto-char (point-min))
(while (re-search-forward "&nbsp;" nil t)
(replace-match spc t t))
(libxml-parse-html-region (point-min) (point-max))))
;; Work around what seems to be a bug with shr -- inserting
;; elements out of order, when an existing Racket Describe buffer
;; hasn't had a quit-window -- by re-creating the bufer.
(buf (get-buffer bufname))
(_ (and buf (kill-buffer buf)))
(buf (get-buffer-create bufname)))
(with-current-buffer buf
(racket-describe-mode)
(read-only-mode -1)
(erase-buffer)
(let ((shr-use-fonts nil))
(shr-insert-document dom))
(goto-char (point-min))
(while (re-search-forward spc nil t)
(replace-match " " t t))
(goto-char (point-max))
(when pop-to
(insert "\n")
(insert-text-button "Definition"
'action
(lambda (_btn)
(racket--do-visit-def-or-mod 'def str)))
(insert " ")
(insert-text-button "Documentation in Browser"
'action
(lambda (_btn)
(racket--cmd/await `(doc ,str))))
(insert " [q]uit"))
(read-only-mode 1)
(goto-char (point-min))
(display-buffer (current-buffer) t)
(when pop-to
(pop-to-buffer (current-buffer))
(message "Type TAB to move to links, 'q' to restore previous window"))
(current-buffer))))
(defvar racket-describe-mode-map
(let ((m (make-sparse-keymap)))
(set-keymap-parent m special-mode-map)
(mapc (lambda (x)
(define-key m (kbd (car x)) (cadr x)))
'(("<tab>" racket-describe--next-button)
("S-<tab>" racket-describe--prev-button)))
m)
"Keymap for Racket Describe mode.")
(define-derived-mode racket-describe-mode special-mode
"RacketDescribe"
"Major mode for describing Racket functions.
\\{racket-describe-mode-map}"
(setq show-trailing-whitespace nil))
(defun racket-describe--next-button ()
(interactive)
(forward-button 1 t t))
(defun racket-describe--prev-button ()
(interactive)
(forward-button -1 t t))
(provide 'racket-complete)
;; racket-complete.el ends here

View File

@@ -0,0 +1,359 @@
;;; racket-custom.el
;; Copyright (c) 2013-2018 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
;;; All `defcustom's and `defface's go here.
;;; This makes it easier to provide a consistent UI.
;; NOTE: `:prefix` is disabled as of Emacs 24.3, so I'm using explicit
;; `:tag`s. But also note that options are sorted (by default; user
;; can turn that off) based on the identifier name not the `:tag`. As
;; a result, I'm defining `:tag`s AS IF `:prefix "racket-"` did work.
;; In other words defcustom of racket-foo-bar has a :tag "Foo Bar".
(require 'rx)
(require 'sh-script) ;for sh-heredoc-face
(defgroup racket nil
"Editing and REPL for the Racket language."
:group 'languages
:link '(url-link :tag "README on GitHub" "https://github.com/greghendershott/racket-mode/blob/master/README.md"))
;; This should be _before_ the `defcustom' of `racket-program' (see
;; note in doc for `define-obsolete-variable-alias').
(define-obsolete-variable-alias
'racket-racket-program
'racket-program
"2017-06-02")
(make-obsolete-variable
'racket-raco-program
"You need only set `racket-program' to the Racket executable pathname."
"2017-06-02")
(defvar racket--winp (string-match "windows" (symbol-name system-type)))
(defcustom racket-program (cond (racket--winp "Racket.exe")
(t "racket"))
"Pathname of the racket executable."
:tag "Racket Program"
:type '(file :must-match t)
:risky t
:group 'racket)
(defcustom racket-command-port 55555
"Port number for Racket REPL command server."
:tag "Command Port"
:type 'integer
:risky t
:group 'racket)
(defcustom racket-command-timeout 10
"Timeout for Racket REPL command server."
:tag "Command Timeout"
:type 'integer
:risky t
:group 'racket)
(defcustom racket-memory-limit 2048
"Terminate the Racket process if memory use exceeds this value in MB.
Changes to this value take effect upon the next `racket-run'. A value
of 0 means no limit.
Caveat: This uses Racket's `custodian-limit-memory`, which does
not enforce the limit exactly. Instead, the program will be
terminated upon the first garbage collection where memory exceeds
the limit (maybe by a significant amount)."
:tag "Memory Limit"
:type 'integer
:safe #'integerp
:group 'racket)
(defcustom racket-error-context 'medium
"The level of context used for `racket-run' error stack traces.
Each level improves stack trace information, but causes your
program to run more slowly.
- 'low corresponds to `compile-context-preservation-enabled`
`#f`.
- 'medium corresponds to `compile-context-preservation-enabled`
`#t`, which disables some optimizations like inlining.
- 'high corresponds to `compile-context-preservation-enabled`
`#t` and to use of `errortrace`, which heavily instruments
your code and therefore may be significantly slower.
Tip: Regardless of this setting, you can enable 'high errortrace
for a specific `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."
:tag "Error Context"
:type '(radio (const :tag "Low" low)
(const :tag "Medium (slower)" medium)
(const :tag "High (much slower)" high))
:risky t
:group 'racket)
;;; REPL
(defgroup racket-repl nil
"REPL Options"
:tag "REPL"
:group 'racket)
(defcustom racket-history-filter-regexp "\\`\\s *\\S ?\\S ?\\s *\\'"
"Input matching this regexp are not saved on the history list.
Defaults to a regexp ignoring all inputs of 0, 1, or 2 letters."
:tag "History Filter Regexp"
:type 'regexp
:safe #'stringp
:group 'racket-repl)
(defcustom racket-images-inline t
"Whether to display inline images in the REPL."
:tag "Images Inline"
:type 'boolean
:safe #'booleanp
:group 'racket-repl)
(defcustom racket-images-keep-last 100
"How many images to keep in the image cache."
:tag "Images Keep Last"
:type 'integer
:safe #'integerp
:group 'racket-repl)
(defcustom racket-images-system-viewer "display"
"Which system image viewer program to invoke upon M-x
`racket-view-last-image'."
:tag "Images System Viewer"
:type 'string
:risky t
:group 'racket-repl)
(defcustom racket-pretty-print t
"Use pretty-print instead of print in REPL."
:tag "Pretty Print"
:type 'boolean
:safe #'booleanp
:group 'racket-repl)
(defcustom racket-use-repl-submit-predicate nil
"Should `racket-repl-submit' use a drracket:submit-predicate? A
language can provide such a predicate, for example when the
language syntax is not s-expressions. When t `racket-repl-submit'
will use this to decide whether to submit your input, yet."
:tag "Use REPL Submit Predicate"
:type 'boolean
:safe #'booleanp
:group 'racket-repl)
;;; Other
(defgroup racket-other nil
"Other Options"
:tag "Other"
:group 'racket)
(defcustom racket-indent-curly-as-sequence t
"Indent `{}` with items aligned with the head item?
This is indirectly disabled if `racket-indent-sequence-depth' is 0.
This is safe to set as a file-local variable."
:tag "Indent Curly As Sequence"
:type 'boolean
:safe #'booleanp
:group 'racket-other)
(defcustom racket-indent-sequence-depth 0
"To what depth should `racket-indent-line' search.
This affects the indentation of forms like `` '()` `() #() `` --
and `{}` if `racket-indent-curly-as-sequence' is t -- but not
`` #'() #`() ,() ,@() ``. A zero value disables, giving the
normal indent behavior of DrRacket or Emacs `lisp-mode' derived
modes like `scheme-mode'. Setting this to a high value can make
indentation noticeably slower. This is safe to set as a
file-local variable."
:tag "Indent Sequence Depth"
:type 'integerp
:safe #'integerp
:group 'racket-other)
(defcustom racket-pretty-lambda nil
"Display lambda keywords using λ. This is DEPRECATED.
Instead use `prettify-symbols-mode' in newer verisons of Emacs,
or, use `racket-insert-lambda' to insert actual λ characters."
:tag "Pretty Lambda"
:type 'boolean
:safe #'booleanp
:group 'racket-other)
(defcustom racket-smart-open-bracket-enable nil
"Use `racket-smart-open-bracket' when `[` is pressed?"
:tag "Smart Open Bracket Enable"
:type 'boolean
:safe #'booleanp
:group 'racket-other)
(defcustom racket-module-forms
(rx (syntax ?\()
(or (seq "module" (zero-or-one (any ?* ?+)))
"library"))
"Regexp for the start of a `module`-like form.
Affects what `beginning-of-defun' will move to.
This is safe to set as a file-local variable."
:tag "Top Level Forms"
:type 'string
:safe #'stringp
:group 'racket-other)
(defcustom racket-logger-config
'((cm-accomplice . warning)
(GC . info)
(module-prefetch . warning)
(optimizer . info)
(racket/contract . error)
(sequence-specialization . info)
(* . fatal))
"Configuration of `racket-logger-mode' topics and levels
The topic '* respresents the default level used for topics not
assigned a level. Otherwise, the topic symbols are the same as
used by Racket's `define-logger`.
The levels are those used by Racket's logging system: 'debug,
'info, 'warning, 'error, 'fatal.
For more information see:
<https://docs.racket-lang.org/reference/logging.html>
The default value sets some known \"noisy\" topics to be one
level quieter. That way you can set the '* topic to a level like
'debug and not get overhwelmed by these noisy topics."
:tag "Logger Configuration"
:type '(alist :key-type symbol :value-type symbol)
:safe (lambda (xs)
(cl-every (lambda (x)
(and (symbolp (car x))
(symbolp (cdr x))))
xs))
:group 'racket-other)
;;; Faces
(defgroup racket-faces nil
"Racket Faces"
:tag "Racket Faces"
:group 'faces
:group 'racket)
(defmacro defface-racket (id facespec docstr tag)
`(progn
(defconst ,id ',id)
(defface ,id
,facespec
,docstr
:tag ,tag
:group 'racket-faces)))
(defface-racket racket-check-syntax-def-face
'((t (:foreground "Black" :background "SeaGreen1" :weight bold)))
"Face `racket-check-syntax' uses to highlight definitions."
"Check Syntax Def Face")
(defface-racket racket-check-syntax-use-face
'((t (:foreground "Black" :background "PaleGreen1" :slant italic)))
"Face `racket-check-syntax' uses to highlight uses."
"Check Syntax Use Face")
(defface-racket racket-keyword-argument-face
'((((background dark))
(:foreground "IndianRed"))
(((background light))
(:foreground "Red3")))
"Face for `#:keyword` arguments."
"Keyword Argument Face")
(define-obsolete-face-alias
'racket-paren-face
"Instead use the `paren-face' package: <https://melpa.org/#/paren-face>."
"2017-06-13")
(defface-racket racket-selfeval-face
'((t (:foreground "SeaGreen")))
"Face for self-evaluating expressions like numbers, symbols, strings."
"Selfeval Face")
(defface-racket racket-here-string-face
'((t (:inherit sh-heredoc-face)))
"Face for here strings."
"Here String Face")
(defface-racket racket-logger-config-face
'((t (:inherit font-lock-comment-face :slant italic)))
"Face for `racket-logger-mode' configuration."
"Racket Logger Config Face")
(defface-racket racket-logger-topic-face
'((t (:inherit font-lock-function-name-face :slant italic)))
"Face for `racket-logger-mode' topics."
"Racket Logger Config Face")
(defface-racket racket-logger-fatal-face
'((t (:inherit error :weight bold)))
"Face for `racket-logger-mode' fatal level."
"Racket Logger Fatal Face")
(defface-racket racket-logger-error-face
'((t (:inherit error)))
"Face for `racket-logger-mode' error level."
"Racket Logger Error Face")
(defface-racket racket-logger-warning-face
'((t (:inherit warning)))
"Face for `racket-logger-mode' warning level."
"Racket Logger Warning Face")
(defface-racket racket-logger-info-face
'((t (:inherit font-lock-string-face)))
"Face for `racket-logger-mode' info level."
"Racket Logger Info Face")
(defface-racket racket-logger-debug-face
'((t (:inherit font-lock-constant-face)))
"Face for `racket-logger-mode' debug level."
"Racket Logger Debug Face")
(defface-racket racket-debug-break-face
'((t (:background "red")))
"Face for `racket-debug-mode' break position."
"Racket Debug Break Face")
(defface-racket racket-debug-locals-face
'((t (:inherit racket-selfeval-face :box (:line-width -1) :slant italic)))
"Face for `racket-debug-mode' local variables."
"Racket Debug Locals Face")
(defface-racket racket-debug-result-face
'((t (:inherit racket-selfeval-face :box (:line-width -1) :slant italic :weight bold)))
"Face for `racket-debug-mode' result values."
"Racket Debug Result Face")
(provide 'racket-custom)
;; racket-custom.el ends here

View File

@@ -0,0 +1,319 @@
;;; racket-debug.el -*- lexical-binding: t; -*-
;; Copyright (c) 2018 by Greg Hendershott.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'racket-repl)
(require 'easymenu)
(require 'cl-lib)
(require 'rx)
(defun racket-project-files (file-to-run)
"A suitable value for the variable `racket-debuggable-files'.
When projectile is installed and we're in a project, return all
its Racket files. Else return all Racket files in the same
directory as `file-to-run'. In all cases, include `file-to-run'.
In all cases, return absolute path names."
(cons
file-to-run
(or
(ignore-errors
(require 'projectile)
(when (and (fboundp 'projectile-project-root)
(fboundp 'projectile-dir-files))
(let ((root (projectile-project-root)))
(mapcar (lambda (v)
(expand-file-name v root))
(cl-remove-if-not (lambda (v)
(and (member (file-name-extension v)
'("rkt" "ss" "scm" "scrbl"))
v))
(projectile-dir-files root))))))
(directory-files (file-name-directory file-to-run)
t
(rx "." (or "rkt" "ss" "scm" "scrbl") eos)))))
(defvar racket-debuggable-files #'racket-project-files
"Used to tell `racket-run' what files may be instrumented for debugging.
Must be a list of strings that are pathnames, such as from
`racket--buffer-file-name', -or-, a function that returns such a
list given the pathname of the file being run. If any path
strings are relative, they are made absolute using
`expand-file-name' with the directory of the file being run. The
symbol 'run-file may be supplied in the list; it will be replaced
with the pathname of the file being run. Safe to set as a
file-local variable.")
(defun racket--debuggable-files (file-to-run)
"Do the work described in doc str for variable `racket-debuggable-files'."
(cl-labels ((err (&rest args)
(user-error (concat "racket-debuggable-files: must be "
(apply #'format args)))))
(let* ((dir (file-name-directory file-to-run))
(xs (if (functionp racket-debuggable-files)
(funcall racket-debuggable-files file-to-run)
racket-debuggable-files))
(xs (if (listp xs) xs (err "a list but is `%S'" xs)))
(xs (mapcar
(lambda (v)
(pcase v
(`run-file file-to-run)
((pred stringp) (expand-file-name v dir))
(_ (err "string or 'run-file but is `%S' in `%S'"
v xs))))
xs)))
xs)))
(defvar racket--debug-break-positions nil)
(defvar racket--debug-break-locals nil)
(defvar racket--debug-break-info nil)
;; (U nil (cons break-id
;; (U (list 'before)
;; (list 'after string-of-racket-write-values))))
;;;###autoload
(defun racket--debug-send-definition (beg end)
(racket--cmd/async
(save-excursion
(goto-char beg)
(list 'debug-eval
(racket--buffer-file-name)
(line-number-at-pos)
(current-column)
(point)
(buffer-substring-no-properties (point) end)))
(lambda (_)
;; TODO: Also set fringe, and/or set marker on function
;; name to show it's debuggable.
(message "Now you can call the function in the REPL to step debug it."))) )
;;;###autoload
(defun racket--debug-on-break (response)
(pcase response
(`((,src . ,pos) ,positions ,locals ,vals)
(pcase (find-buffer-visiting src)
(`nil (other-window 1) (find-file src))
(buf (pop-to-buffer buf)))
(goto-char pos)
(pcase vals
(`(,_id before) (message "Break before expression"))
(`(,_id after ,s) (message "Break after expression: (values %s" (substring s 1))))
(setq racket--debug-break-positions positions)
(setq racket--debug-break-locals locals)
(setq racket--debug-break-info vals)
(racket-debug-mode 1))))
(defun racket--debug-resume (next-break value-prompt-p)
(unless racket--debug-break-info (user-error "Not debugging"))
(let ((info (if value-prompt-p
(racket--debug-prompt-for-new-values)
racket--debug-break-info)))
(racket--cmd/async `(debug-resume (,next-break ,info))))
(racket-debug-mode -1)
(setq racket--debug-break-positions nil)
(setq racket--debug-break-locals nil)
(setq racket--debug-break-info nil))
(defun racket--debug-prompt-for-new-values ()
(pcase racket--debug-break-info
(`(,id before)
(pcase (read-from-minibuffer "Skip step, substituting values: " "()")
((or `nil "" "()") `(,id before))
(str `(,id before ,str))))
(`(,id after ,orig)
(pcase (read-from-minibuffer "Step, replacing result values: " orig)
((or `nil "" "()") `(,id after ,orig))
(new `(,id after ,new))))))
(defun racket-debug-step (&optional prefix)
"Resume to next breakable position. With prefix, substitute values."
(interactive "P")
(racket--debug-resume 'all prefix))
(defun racket-debug-step-over (&optional prefix)
"Resume over next expression. With prefix, substitute values."
(interactive "P")
(racket--debug-resume 'over prefix))
(defun racket-debug-step-out (&optional prefix)
"Resume out. With prefix, substitute values."
(interactive "P")
(racket--debug-resume 'out prefix))
(defun racket-debug-continue (&optional prefix)
"Resume; don't break anymore. With prefix, substitute values."
(interactive "P")
(racket--debug-resume 'none prefix))
(defun racket-debug-run-to-here (&optional prefix)
"Resume until point (if possible). With prefix, substitute values."
(interactive)
(racket--debug-resume (cons (racket--buffer-file-name) (point)) prefix))
(defun racket-debug-next-breakable ()
"Move point to next breakable position."
(interactive)
(racket--debug-goto-breakable t))
(defun racket-debug-prev-breakable ()
"Move point to previous breakable position."
(interactive)
(racket--debug-goto-breakable nil))
(defun racket--debug-goto-breakable (forwardp)
(pcase (assoc (racket--buffer-file-name) racket--debug-break-positions)
(`(,_src . ,ps)
(let ((ps (if forwardp ps (reverse ps)))
(pred (apply-partially (if forwardp #'< #'>) (point))))
(goto-char (pcase (cl-find-if pred ps)
(`nil (car ps))
(v v)))))
(_ (user-error "No breakable positions in this buffer"))))
(defun racket-debug-disable ()
(interactive)
(racket--cmd/async `(debug-disable))
(racket-debug-mode -1)
(setq racket--debug-break-positions nil)
(setq racket--debug-break-locals nil)
(setq racket--debug-break-info nil))
(add-hook 'racket--repl-before-run-hook #'racket-debug-disable)
(defun racket-debug-help ()
(interactive)
(describe-function 'racket-debug-mode))
(defvar racket--debug-overlays nil)
(define-minor-mode racket-debug-mode
"Minor mode for debug breaks.
> This feature is **EXPERIMENTAL**!!! It is likely to have
> significant limitations and bugs. You are welcome to open an
> issue to provide feedback. Please understand that this feature
> might never be improved -- it might even be removed someday if
> it turns out to have too little value and/or too much cost.
How to debug:
1. \"Instrument\" code for step debugging. You can instrument
entire files, and also individual functions.
a. Entire Files
Choose `racket-run' with two prefixes -- C-u C-u C-c C-c. The
file will be instrumented for step debugging before it is run.
Also instrumented are files determined by the variable
`racket-debuggable-files'.
The run will break at the first breakable position.
Tip: After you run to completion and return to a normal
REPL prompt, the code remains instrumented. You may enter
expressions that evaluate instrumented code and it will
break so you can step debug again.
b. Function Definitions
Put point in a function `define` form and C-u C-M-x to
\"instrument\" the function for step debugging. Then in the
REPL, enter an expression that causes the instrumented
function to be run, directly or indirectly.
You can instrument any number of functions.
You can even instrument while stopped at a break. For
example, to instrument a function you are about to call, so
you can \"step into\" it:
- M-. a.k.a. `racket-visit-definition'.
- C-u C-M-x to instrument the definition.
- M-, a.k.a. `racket-unvisit'.
- Continue stepping.
Limitation: Instrumenting a function `require`d from
another module won't redefine that function. Instead, it
attempts to define an instrumented function of the same
name, in the module the REPL is inside. The define will
fail if it needs definitions visible only in that other
module. In that case you'll probably need to use
entire-file instrumentation as described above.
2. When a break occurs, the `racket-repl-mode' prompt changes. In
this debug REPL, local variables are available for you to use
and even to `set!`.
Also, in the `racket-mode' buffer where the break is located,
`racket-debug-mode' is enabled. This minor mode makes the
buffer read-only, provides visual feedback -- about the break
position, local variable values, and result values -- and
provides shortcut keys:
```
\\{racket-debug-mode-map}
```
"
:lighter " RacketDebug"
:keymap (racket--easy-keymap-define
'(("SPC" racket-debug-step)
("o" racket-debug-step-over)
("u" racket-debug-step-out)
("c" racket-debug-continue)
("n" racket-debug-next-breakable)
("p" racket-debug-prev-breakable)
("h" racket-debug-run-to-here)
("?" racket-debug-help)))
(unless (eq major-mode 'racket-mode)
(setq racket-debug-mode nil)
(user-error "racket-debug-mode only works with racket-mode"))
(cond
(racket-debug-mode
(racket--debug-make-overlay
(point) (1+ (point))
'face racket-debug-break-face
'priority 99)
(dolist (local racket--debug-break-locals)
(pcase-let ((`(,_src ,pos ,span ,_name ,val) local))
(racket--debug-make-overlay
pos (+ pos span)
'after-string (propertize val 'face racket-debug-locals-face))))
(pcase racket--debug-break-info
(`(,_id after ,str)
(let ((eol (line-end-position)))
(racket--debug-make-overlay
(1- eol) eol
'after-string (propertize (concat "⇒ (values " (substring str 1))
'face racket-debug-result-face)))))
(read-only-mode 1))
(t
(read-only-mode -1)
(dolist (o racket--debug-overlays)
(delete-overlay o))
(setq racket--debug-overlays nil))))
(defun racket--debug-make-overlay (beg end &rest props)
(let ((o (make-overlay beg end)))
(push o racket--debug-overlays)
(overlay-put o 'name 'racket-debug-overlay)
(overlay-put o 'priority 100)
(while props
(overlay-put o (pop props) (pop props)))
o))
(provide 'racket-debug)
;; racket-debug.el ends here

View File

@@ -0,0 +1,835 @@
;;; racket-edit.el -*- lexical-binding: t -*-
;; Copyright (c) 2013-2018 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
;; racket-mode per se, i.e. the .rkt file buffers
(require 'cl-lib)
(require 'cl-macs)
(require 'racket-custom)
(require 'racket-common)
(require 'racket-complete)
(require 'racket-util)
(require 'hideshow)
(require 'tooltip)
(defun racket-run (&optional prefix)
"Save and evaluate the buffer in REPL.
With one C-u prefix, uses errortrace for improved stack traces.
Otherwise follows the `racket-error-context' setting.
With two C-u prefixes, instruments code for step debugging. See
`racket-debug-mode' and the variable `racket-debuggable-files'.
If point is within a Racket `module` form, the REPL \"enters\"
that submodule (uses its language info and namespace).
When you run again, the file is evaluated from scratch -- the
custodian releases resources like threads and the evaluation
environment is reset to the contents of the file. In other words,
like DrRacket, this provides the predictability of a \"static\"
baseline, plus the ability to explore interactively using the
REPL.
See also `racket-run-and-switch-to-repl', which is even more like
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' 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
include:
- Racket error messages.
- `rackunit` test failure location messages.
- `print`s of `#<path>` objects.
To visit these locations, move point there and press RET or mouse
click. Or, use the standard `next-error' and `previous-error'
commands."
(interactive "P")
(racket--repl-run (racket--what-to-run)
(pcase prefix
(`(4) 'high)
(`(16) 'debug)
(_ racket-error-context))
nil))
(defun racket-run-with-errortrace ()
"Run with `racket-error-context' temporarily set to 'high.
This is just `racket-run' with a C-u prefix. Defined as a function so
it can be a menu target."
(interactive)
(racket-run '(4)))
(defun racket-run-with-debugging ()
"Run with `racket-error-context' temporarily set to 'debug.
This is just `racket-run' with a double C-u prefix. Defined as a
function so it can be a menu target."
(interactive)
(racket-run '(16)))
(defun racket-run-and-switch-to-repl (&optional prefix)
"This is `racket-run' followed by `racket-switch-to-repl'."
(interactive "P")
(racket-run prefix)
(racket-repl))
(defun racket-racket ()
"Do `racket <file>` in `*shell*` buffer."
(interactive)
(racket--shell (concat racket-program
" "
(shell-quote-argument (racket--buffer-file-name)))))
(defun racket-test (&optional coverage)
"Run the `test` submodule.
With prefix, runs with coverage instrumentation and highlights
uncovered code.
Put your tests in a `test` submodule. For example:
(module+ test
(require rackunit)
(check-true #t))
rackunit test failure messages show the location. You may use
`next-error' to jump to the location of each failing test.
See also:
- `racket-fold-all-tests'
- `racket-unfold-all-tests'
"
(interactive "P")
(let ((mod-path (list 'submod (racket--buffer-file-name) 'test))
(buf (current-buffer)))
(if (not coverage)
(racket--repl-run mod-path)
(message "Running test submodule with coverage instrumentation...")
(racket--repl-run
mod-path
'coverage
(lambda (_what)
(message "Getting coverage results...")
(racket--cmd/async
`(get-uncovered)
(lambda (xs)
(pcase xs
(`() (message "Full coverage."))
((and xs `((,beg0 . ,_) . ,_))
(message "Missing coverage in %s place(s)." (length xs))
(with-current-buffer buf
(dolist (x xs)
(let ((o (make-overlay (car x) (cdr x) buf)))
(overlay-put o 'name 'racket-uncovered-overlay)
(overlay-put o 'priority 100)
(overlay-put o 'face font-lock-warning-face)))
(goto-char beg0)))))))))))
(add-hook 'racket--repl-before-run-hook #'racket--remove-coverage-overlays)
(defun racket--remove-coverage-overlays ()
(remove-overlays (point-min) (point-max) 'name 'racket-uncovered-overlay))
(defun racket-raco-test ()
"Do `raco test -x <file>` in `*shell*` buffer.
To run <file>'s `test` submodule."
(interactive)
(racket--shell (concat racket-program
" -l raco test -x "
(shell-quote-argument (racket--buffer-file-name)))))
(defun racket--shell (cmd)
(racket--save-if-changed)
(let ((w (selected-window)))
(pcase (get-buffer-window "*shell*" t)
(`() (other-window -1))
(win (select-window win)))
(with-temp-message cmd
(shell)
(pop-to-buffer-same-window "*shell*")
(comint-send-string "*shell*" (concat cmd "\n"))
(select-window w)
(sit-for 3))))
;;; visiting defs and mods
(defun racket-visit-definition (&optional prefix)
"Visit definition of symbol at point.
Use \\[racket-unvisit] to return.
Please keep in mind the following limitations:
- Only finds symbols defined in the current namespace. You may
need to `racket-run' the current buffer, first.
- Only visits the definition of module-level identifiers --
things for which Racket's `identifier-binding` function returns
information. This does _not_ include things such as
local (nested) function definitions or `racket/class` member
functions. To find those in the same file, you'll need to use a
normal Emacs text search function like `isearch-forward'.
- If the definition is found in Racket's `#%kernel` module, it
will tell you so but won't visit the definition site."
(interactive "P")
(pcase (racket--symbol-at-point-or-prompt prefix "Visit definition of: ")
(`nil nil)
(str (racket--do-visit-def-or-mod 'def str))))
(defun racket-visit-module (&optional prefix)
"Visit definition of module at point, e.g. net/url or \"file.rkt\".
Use \\[racket-unvisit] to return.
Note: Only works if you've `racket-run' the buffer so that its
namespace is active.
See also: `racket-find-collection'."
(interactive "P")
(let* ((v (racket--thing-at-point 'filename t)) ;matches both net/url and "file.rkt"
(v (if (or prefix (not v))
(read-from-minibuffer "Visit module: " (or v ""))
v)))
(racket--do-visit-def-or-mod 'mod v)))
(defun racket--do-visit-def-or-mod (cmd str)
"CMD must be 'def or 'mod. STR must be `stringp`."
(if (and (eq major-mode 'racket-mode)
(not (equal (racket--cmd/await `(path+md5))
(cons (racket--buffer-file-name) (md5 (current-buffer)))))
(y-or-n-p "Run current buffer first? "))
(racket--repl-run nil nil
(lambda (_what)
(racket--do-visit-def-or-mod cmd str)))
(cl-case major-mode
(racket-mode
t)
((racket-repl-mode racket-describe-mode)
(racket--repl-ensure-buffer-and-process))
(otherwise
(user-error "Requires racket-mode or racket-repl-mode")))
(pcase (racket--cmd/await (list cmd str))
(`(,path ,line ,col)
(racket--push-loc)
(find-file path)
(goto-char (point-min))
(forward-line (1- line))
(forward-char col)
(message "Type M-, to return"))
(`kernel
(message "`%s' defined in #%%kernel -- source not available." str))
(_
(message "Not found.")))))
(defun racket-doc (&optional prefix)
"View documentation of the identifier or string at point.
Uses the default external web browser.
If point is an identifier required in the current namespace that
has help, opens the web browser directly at that help
topic. (i.e. Uses the identifier variant of racket/help.)
Otherwise, opens the 'search for a term' page, where you can
choose among multiple possibilities. (i.e. Uses the string
variant of racket/help.)
With a C-u prefix, prompts for the identifier or quoted string,
instead of looking at point."
(interactive "P")
(pcase (racket--symbol-at-point-or-prompt prefix "Racket help for: ")
(`nil nil)
(str (racket--cmd/async `(doc ,str)))))
(defvar racket--loc-stack '())
(defun racket--push-loc ()
(push (cons (current-buffer) (point))
racket--loc-stack))
(defun racket-unvisit ()
"Return from previous `racket-visit-definition' or `racket-visit-module'."
(interactive)
(if racket--loc-stack
(pcase (pop racket--loc-stack)
(`(,buffer . ,pt)
(pop-to-buffer-same-window buffer)
(goto-char pt)))
(message "Stack empty.")))
;;; code folding
;;;###autoload
(add-to-list 'hs-special-modes-alist
'(racket-mode "(" ")" ";" nil nil))
(defun racket--for-all-tests (verb f)
(save-excursion
(goto-char (point-min))
(let ((n 0))
(while (re-search-forward "^(module[+*]? test" (point-max) t)
(funcall f)
(cl-incf n)
(goto-char (match-end 0)))
(message "%s %d test submodules" verb n))))
(defun racket-fold-all-tests ()
"Fold (hide) all test submodules."
(interactive)
(racket--for-all-tests "Folded" 'hs-hide-block))
(defun racket-unfold-all-tests ()
"Unfold (show) all test submodules."
(interactive)
(racket--for-all-tests "Unfolded" 'hs-show-block))
;;; requires
(defun racket-tidy-requires ()
"Make a single top-level `require`, modules sorted, one per line.
All top-level `require` forms are combined into a single form.
Within that form:
- A single subform is used for each phase level, sorted in this
order: for-syntax, for-template, for-label, for-meta, and
plain (phase 0).
- Within each level subform, the modules are sorted:
- Collection path modules -- sorted alphabetically.
- Subforms such as `only-in`.
- Quoted relative requires -- sorted alphabetically.
At most one module is listed per line.
Note: This only works for requires at the top level of a source
file using `#lang`. It does *not* work for `require`s inside
`module` forms.
See also: `racket-trim-requires' and `racket-base-requires'."
(interactive)
(unless (eq major-mode 'racket-mode)
(user-error "Current buffer is not a racket-mode buffer"))
(pcase (racket--top-level-requires 'find)
(`nil (user-error "The file module has no requires; nothing to do"))
(reqs (pcase (racket--cmd/await `(requires/tidy ,reqs))
("" nil)
(new (goto-char (racket--top-level-requires 'kill))
(insert (concat new "\n")))))))
(defun racket-trim-requires ()
"Like `racket-tidy-requires' but also deletes unnecessary requires.
Note: This only works when the source file can be evaluated with
no errors.
Note: This only works for requires at the top level of a source
file using `#lang`. It does *not* work for `require`s inside
`module` forms. Furthermore, it is not smart about `module+` or
`module*` forms -- it may delete top level requires that are
actually needed by such submodules.
See also: `racket-base-requires'."
(interactive)
(unless (eq major-mode 'racket-mode)
(user-error "Current buffer is not a racket-mode buffer"))
(when (racket--ok-with-module+*)
(racket--save-if-changed)
(pcase (racket--top-level-requires 'find)
(`nil (user-error "The file module has no requires; nothing to do"))
(reqs (pcase (racket--cmd/await `(requires/trim
,(racket--buffer-file-name)
,reqs))
(`nil (user-error "Syntax error in source file"))
("" (goto-char (racket--top-level-requires 'kill)))
(new (goto-char (racket--top-level-requires 'kill))
(insert (concat new "\n"))))))))
(defun racket-base-requires ()
"Change from `#lang racket` to `#lang racket/base`.
Adds explicit requires for modules that are provided by `racket`
but not by `racket/base`.
This is a recommended optimization for Racket applications.
Avoiding loading all of `racket` can reduce load time and memory
footprint.
Also, as does `racket-trim-requires', this removes unneeded
modules and tidies everything into a single, sorted require form.
Note: This only works when the source file can be evaluated with
no errors.
Note: This only works for requires at the top level of a source
file using `#lang`. It does *not* work for `require`s inside
`module` forms. Furthermore, it is not smart about `module+` or
`module*` forms -- it may delete top level requires that are
actually needed by such submodules.
Note: Currently this only helps change `#lang racket` to
`#lang racket/base`. It does *not* help with other similar conversions,
such as changing `#lang typed/racket` to `#lang typed/racket/base`."
(interactive)
(unless (eq major-mode 'racket-mode)
(user-error "Current buffer is not a racket-mode buffer"))
(when (racket--buffer-start-re "^#lang.*? racket/base$")
(user-error "Already using #lang racket/base. Nothing to change."))
(unless (racket--buffer-start-re "^#lang.*? racket$")
(user-error "File does not use use #lang racket. Cannot change."))
(when (racket--ok-with-module+*)
(racket--save-if-changed)
(let ((reqs (racket--top-level-requires 'find)))
(pcase (racket--cmd/await `(requires/base
,(racket--buffer-file-name)
,reqs))
(`nil (user-error "Syntax error in source file"))
(new (goto-char (point-min))
(re-search-forward "^#lang.*? racket$")
(insert "/base")
(goto-char (or (racket--top-level-requires 'kill)
(progn (insert "\n\n") (point))))
(unless (string= "" new)
(insert (concat new "\n"))))))))
(defun racket--ok-with-module+* ()
(save-excursion
(goto-char (point-min))
(or (not (re-search-forward (rx ?\( "module" (or "+" "*")) nil t))
(prog1
(y-or-n-p "Analysis will be unreliable due to module+ or module* forms -- proceed anyway? ")
(message "")))))
(defun racket--buffer-start-re (re)
(save-excursion
(ignore-errors
(goto-char (point-min))
(re-search-forward re)
t)))
(defun racket--top-level-requires (what)
"Identify all top-level requires and do WHAT.
When WHAT is 'find, returns the top-level require forms.
When WHAT is 'kill, kill the top-level requires, returning the
location of the first one."
(save-excursion
(goto-char (point-min))
(let ((first-beg nil)
(requires nil))
(while (re-search-forward "^(require " nil t)
(let* ((beg (progn (up-list -1) (point)))
(end (progn (forward-sexp) (point)))
(str (buffer-substring-no-properties beg end))
(sexpr (read str)))
(unless first-beg (setq first-beg beg))
(setq requires (cons sexpr requires))
(when (eq 'kill what)
(kill-sexp -1)
(delete-blank-lines))))
(if (eq 'kill what) first-beg requires))))
;;; racket-check-syntax
(defvar racket--highlight-overlays nil)
(defun racket--highlight (beg end defp)
;; Unless one of our highlight overlays already exists there...
(let ((os (overlays-at beg)))
(unless (cl-some (lambda (o) (member o racket--highlight-overlays)) os)
(let ((o (make-overlay beg end)))
(setq racket--highlight-overlays (cons o racket--highlight-overlays))
(overlay-put o 'name 'racket-check-syntax-overlay)
(overlay-put o 'priority 100)
(overlay-put o 'face (if defp
racket-check-syntax-def-face
racket-check-syntax-use-face))))))
(defun racket--unhighlight-all ()
(while racket--highlight-overlays
(delete-overlay (car racket--highlight-overlays))
(setq racket--highlight-overlays (cdr racket--highlight-overlays))))
(defun racket--non-empty-string-p (v)
(and (stringp v)
(not (string-match-p "\\`[ \t\n\r]*\\'" v)))) ;`string-blank-p'
(defun racket--point-entered (_old new)
(pcase (get-text-property new 'help-echo)
((and s (pred racket--non-empty-string-p))
(if (and (boundp 'tooltip-mode)
tooltip-mode
(fboundp 'window-absolute-pixel-position))
(pcase (window-absolute-pixel-position new)
(`(,left . ,top)
(let ((tooltip-frame-parameters `((left . ,left)
(top . ,top)
,@tooltip-frame-parameters)))
(tooltip-show s))))
(message "%s" s))))
(pcase (get-text-property new 'racket-check-syntax-def)
((and uses `((,beg ,_end) . ,_))
(pcase (get-text-property beg 'racket-check-syntax-use)
(`(,beg ,end) (racket--highlight beg end t)))
(dolist (use uses)
(pcase use (`(,beg ,end) (racket--highlight beg end nil))))))
(pcase (get-text-property new 'racket-check-syntax-use)
(`(,beg ,end)
(racket--highlight beg end t)
(dolist (use (get-text-property beg 'racket-check-syntax-def))
(pcase use (`(,beg ,end) (racket--highlight beg end nil)))))))
(defun racket--point-left (_old _new)
(racket--unhighlight-all))
(defun racket-check-syntax-mode-quit ()
(interactive)
(racket-check-syntax-mode -1))
(defun racket-check-syntax-mode-goto-def ()
"When point is on a use, go to its definition."
(interactive)
(pcase (get-text-property (point) 'racket-check-syntax-use)
(`(,beg ,_end) (goto-char beg))))
(defun racket-check-syntax-mode-forward-use (amt)
"When point is on a use, go AMT uses forward. AMT may be negative.
Moving before/after the first/last use wraps around.
If point is instead on a definition, then go to its first use."
(pcase (get-text-property (point) 'racket-check-syntax-use)
(`(,beg ,_end)
(pcase (get-text-property beg 'racket-check-syntax-def)
(uses (let* ((pt (point))
(ix-this (cl-loop for ix from 0 to (1- (length uses))
for use = (nth ix uses)
when (and (<= (car use) pt) (< pt (cadr use)))
return ix))
(ix-next (+ ix-this amt))
(ix-next (if (> amt 0)
(if (>= ix-next (length uses)) 0 ix-next)
(if (< ix-next 0) (1- (length uses)) ix-next)))
(next (nth ix-next uses)))
(goto-char (car next))))))
(_ (pcase (get-text-property (point) 'racket-check-syntax-def)
(`((,beg ,_end) . ,_) (goto-char beg))))))
(defun racket-check-syntax-mode-goto-next-use ()
"When point is on a use, go to the next (sibling) use."
(interactive)
(racket-check-syntax-mode-forward-use 1))
(defun racket-check-syntax-mode-goto-prev-use ()
"When point is on a use, go to the previous (sibling) use."
(interactive)
(racket-check-syntax-mode-forward-use -1))
(defun racket-check-syntax-mode-help ()
(interactive)
(describe-function #'racket-check-syntax-mode))
(defun racket-check-syntax-mode-rename ()
(interactive)
;; If we're on a def, get its uses. If we're on a use, get its def.
(let* ((pt (point))
(uses (get-text-property pt 'racket-check-syntax-def))
(def (get-text-property pt 'racket-check-syntax-use)))
;; If we got one, get the other.
(when (or uses def)
(let* ((uses (or uses (get-text-property (car def) 'racket-check-syntax-def)))
(def (or def (get-text-property (caar uses) 'racket-check-syntax-use)))
(locs (cons def uses))
(strs (mapcar (lambda (loc)
(apply #'buffer-substring-no-properties loc))
locs)))
;; Proceed only if all the strings are the same. (They won't
;; be for e.g. import bindings.)
(when (cl-every (lambda (s) (equal (car strs) s))
(cdr strs))
(let ((new (read-from-minibuffer (format "Rename %s to: " (car strs))))
(marker-pairs
(mapcar (lambda (loc)
(let ((beg (make-marker))
(end (make-marker)))
(set-marker beg (nth 0 loc) (current-buffer))
(set-marker end (nth 1 loc) (current-buffer))
(list beg end)))
locs))
(point-marker (let ((m (make-marker)))
(set-marker m (point) (current-buffer)))))
(racket-check-syntax-mode -1)
(dolist (marker-pair marker-pairs)
(let ((beg (marker-position (nth 0 marker-pair)))
(end (marker-position (nth 1 marker-pair))))
(delete-region beg end)
(goto-char beg)
(insert new)))
(goto-char (marker-position point-marker))
(racket-check-syntax-mode 1)))))))
(defun racket-check-syntax-mode-goto-next-def ()
(interactive)
(let ((pos (next-single-property-change (point) 'racket-check-syntax-def)))
(when pos
(unless (get-text-property pos 'racket-check-syntax-def)
(setq pos (next-single-property-change pos 'racket-check-syntax-def)))
(and pos (goto-char pos)))))
(defun racket-check-syntax-mode-goto-prev-def ()
(interactive)
(let ((pos (previous-single-property-change (point) 'racket-check-syntax-def)))
(when pos
(unless (get-text-property pos 'racket-check-syntax-def)
(setq pos (previous-single-property-change pos 'racket-check-syntax-def)))
(and pos (goto-char pos)))))
(define-minor-mode racket-check-syntax-mode
"Analyze the buffer and annotate with information.
The buffer becomes read-only until you exit this minor mode.
However you may navigate the usual ways. When point is on a
definition or use, related items are highlighted and
information is displayed in the echo area. You may also use
special commands to navigate among the definition and its uses.
```
\\{racket-check-syntax-mode-map}
```
"
:lighter " CheckSyntax"
:keymap (racket--easy-keymap-define
'(("q" racket-check-syntax-mode-quit)
("h" racket-check-syntax-mode-help)
(("j" "TAB") racket-check-syntax-mode-goto-next-def)
(("k" "<backtab>") racket-check-syntax-mode-goto-prev-def)
("." racket-check-syntax-mode-goto-def)
("n" racket-check-syntax-mode-goto-next-use)
("p" racket-check-syntax-mode-goto-prev-use)
("r" racket-check-syntax-mode-rename)))
(unless (eq major-mode 'racket-mode)
(setq racket-check-syntax-mode nil)
(user-error "racket-check-syntax-mode only works with racket-mode"))
(racket--check-syntax-stop)
(when racket-check-syntax-mode
(racket--check-syntax-start)))
(defun racket--check-syntax-start ()
(let ((buf (current-buffer)))
(message "Running check-syntax analysis...")
(racket--cmd/async-raw
`(check-syntax ,(racket--buffer-file-name))
(lambda (response)
(with-current-buffer buf
(pcase response
(`(error ,m)
(racket-check-syntax-mode -1)
(error m))
(`(ok ())
(racket-check-syntax-mode -1)
(user-error "No bindings found"))
(`(ok ,xs)
(message "Marking up buffer...")
(racket--check-syntax-insert xs)
(message ""))))))))
(defun racket--check-syntax-insert (xs)
(with-silent-modifications
(dolist (x xs)
(pcase x
(`(,`info ,beg ,end ,str)
(put-text-property beg end 'help-echo str))
(`(,`def/uses ,def-beg ,def-end ,uses)
(add-text-properties def-beg
def-end
(list 'racket-check-syntax-def uses
'point-entered #'racket--point-entered
'point-left #'racket--point-left))
(dolist (use uses)
(pcase-let* ((`(,use-beg ,use-end) use))
(add-text-properties use-beg
use-end
(list 'racket-check-syntax-use (list def-beg
def-end)
'point-entered #'racket--point-entered
'point-left #'racket--point-left)))))))
(setq buffer-read-only t)
(setq header-line-format
"Check Syntax. Buffer is read-only. Press h for help, q to quit.")
;; Make 'point-entered and 'point-left work in Emacs 25+. Note
;; that this is somewhat of a hack -- I spent a lot of time trying
;; to Do the Right Thing using the new cursor-sensor-mode, but
;; could not get it to work satisfactorily. See:
;; http://emacs.stackexchange.com/questions/29813/point-motion-strategy-for-emacs-25-and-older
(setq-local inhibit-point-motion-hooks nil)
;; Go to next definition, as an affordance/hint what this does:
(racket-check-syntax-mode-goto-next-def)))
(defun racket--check-syntax-stop ()
(setq header-line-format nil)
(with-silent-modifications
(remove-text-properties (point-min)
(point-max)
'(help-echo nil
racket-check-syntax-def nil
racket-check-syntax-use nil
point-entered
point-left))
(racket--unhighlight-all)
(setq buffer-read-only nil)))
;;; align
(defun racket-align ()
"Align values in the same column.
Useful for binding forms like `let` and `parameterize`,
conditionals like `cond` and `match`, association lists, and any
series of couples like the arguments to `hash`.
Before choosing this command, put point on the first of a series
of \"couples\". A couple is:
- A list of two or more sexprs: `[sexpr val sexpr ...]`
- Two sexprs: `sexpr val`.
Each `val` moves to the same column and is
`prog-indent-sexp'-ed (in case it is a multi-line form).
For example with point on the `[` before `a`:
Before After
(let ([a 12] (let ([a 12]
[bar 23]) [bar 23])
....) ....)
'([a . 12] '([a . 12]
[bar . 23]) [bar . 23])
(cond [a? #t] (cond [a? #t]
[b? (f x [b? (f x
y)] y)]
[else #f]) [else #f])
Or with point on the `'` before `a`:
(list 'a 12 (list 'a 12
'bar 23) 'bar 23)
If more than one couple is on the same line, none are aligned,
because it is unclear where the value column should be. For
example the following form will not change; `racket-align' will
display an error message:
(let ([a 0][b 1]
[c 2]) error; unchanged
....)
When a couple's sexprs start on different lines, that couple is
ignored. Other, single-line couples in the series are aligned as
usual. For example:
(let ([foo (let ([foo
0] 0]
[bar 1] [bar 1]
[x 2]) [x 2])
....) ....)
See also: `racket-unalign'."
(interactive)
(save-excursion
(let ((listp (eq ?\( (char-syntax (char-after))))
(prev-line 0)
(max-col 0))
(racket--for-each-couple listp
(lambda ()
(setq max-col (max max-col (current-column)))
(let ((this-line (line-number-at-pos)))
(when (= prev-line this-line)
(user-error
"Can't align if any couples are on same line"))
(setq prev-line this-line))))
(racket--for-each-couple listp
(lambda ()
(indent-to max-col)
(prog-indent-sexp))))))
(defun racket-unalign ()
"The opposite of `racket-align'.
Effectively does M-x `just-one-space' and `prog-indent-sexp' for
each couple's value."
(interactive)
(save-excursion
(let ((listp (eq ?\( (char-syntax (char-after)))))
(racket--for-each-couple listp
(lambda ()
(just-one-space)
(prog-indent-sexp))))))
(defun racket--for-each-couple (listp f)
"Move point to each value sexp of a couple, and `funcall' F.
Only call F when the couple's sexprs are on the same line.
When LISTP is true, expects couples to be `[id val]`, else `id val`."
(save-excursion
(condition-case ()
(while t
(when listp
(down-list))
(forward-sexp)
(let ((line (line-number-at-pos)))
(forward-sexp)
(backward-sexp)
(when (= line (line-number-at-pos))
;; Defensive: Backup over any prefix or punctuation
;; chars just in case backward-sexp didn't (although it
;; should have if our syntax table is correct).
(while (memq (char-syntax (char-before)) '(?\' ?\.))
(goto-char (1- (point))))
(funcall f)))
;; On to the next couple...
(if listp
(up-list)
(forward-sexp)))
(scan-error nil))))
(provide 'racket-edit)
;; racket-edit.el ends here

View File

@@ -0,0 +1,351 @@
;;; racket-font-lock.el
;; Copyright (c) 2013-2018 by Greg Hendershott.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'cl-lib)
(require 'racket-custom)
(require 'racket-keywords-and-builtins)
(require 'racket-ppss)
(require 'racket-util)
;; Define 3 levels of font-lock, as documented in 23.6.5 "Levels of
;; Font Lock". User may control using `font-lock-maximum-decoration'.
;; Note: font-lock iterates by matcher, doing an re-search-forward
;; over the entire region. As a result, it's faster to consolidate
;; matchers that will yield the same result (unless they need to be
;; tried in a certain order).
;; Note: This relies on our character syntax already having been
;; applied. For example a Racket identifier like `|name with spaces|`
;; will already have word/symbol syntax on everything including the
;; pipe and space chars.
(defconst racket-font-lock-keywords-0
(eval-when-compile
`(
;; #shebang
(,(rx bol "#!" (+ nonl) eol) . font-lock-comment-face)
;; #lang
(,(rx (group (group "#lang")
(1+ " ")
(group (1+ not-newline))))
(2 font-lock-keyword-face nil t)
(3 font-lock-variable-name-face nil t))
;; #; sexp comments
;;
;; We don't put any comment syntax on these -- that way things
;; like indent and nav work within the sexp. They are solely
;; font-locked as comments, here.
(,#'racket--font-lock-sexp-comments
(1 font-lock-comment-delimiter-face t)
(2 font-lock-comment-face t))
;; #<< here strings
;;
;; We only handle the opening #<<ID here. The remainder is
;; handled in `racket-font-lock-syntatic-face-function'.
(,(rx (group "#<<" (+? (not (any blank ?\n)))) ?\n)
(1 racket-here-string-face nil t))
))
"Strings, comments, #lang.")
(defconst racket-font-lock-keywords-1
(eval-when-compile
`(
;; keyword argument
(,(rx "#:" (1+ (or (syntax word) (syntax symbol))))
. racket-keyword-argument-face)
;; Various things for racket-selfeval-face
(,(rx (or
;; symbol
(seq ?' ?| (+ any) ?|)
(seq ?' (1+ (or (syntax word) (syntax symbol))))
(seq "#\\" (1+ (or (syntax word) (syntax symbol))))))
. racket-selfeval-face)
;; #rx #px
(,(rx (group (or "#rx" "#px")) ?\")
1 racket-selfeval-face)
;; Some self-eval constants
(,(regexp-opt '("#t" "#true" "#f" "#false" "+inf.0" "-inf.0" "+nan.0") 'symbols)
. racket-selfeval-face)
;; Numeric literals including Racket reader hash prefixes.
(,(rx
(seq symbol-start
(or
;; #d #e #i or no hash prefix
(seq (? "#" (any "dei"))
(or (seq (? (any "-+"))
(1+ digit)
(? (any "./") (1+ digit)))
(seq (1+ digit)
?e
(? (any "-+"))
(1+ digit))))
;; #x
(seq "#x"
(? (any "-+"))
(1+ hex-digit)
(? (any "./") (1+ hex-digit)))
;; #b
(seq "#b"
(or (seq (? (any "-+"))
(1+ (any "01"))
(? (any "./") (1+ (any "01"))))
(seq (1+ (any "01"))
?e
(? (any "-+"))
(1+ (any "01")))))
;; #o
(seq "#o"
(or (seq (? (any "-+"))
(1+ (any "0-7"))
(? (any "./") (1+ (any "0-7"))))
(seq (1+ (any "0-7"))
?e
(? (any "-+"))
(1+ (any "0-7"))))))
symbol-end))
. racket-selfeval-face)
))
"Self-evals")
(defconst racket-font-lock-keywords-2
(eval-when-compile
`(
;; def* -- variables
(,(rx (syntax open-parenthesis)
"def" (0+ (or (syntax word) (syntax symbol)))
(1+ space)
(group (1+ (or (syntax word) (syntax symbol)))))
1 font-lock-variable-name-face)
(,(rx (syntax open-parenthesis)
"define-values"
(1+ space)
(syntax open-parenthesis)
(group (1+ (or (syntax word) (syntax symbol) space)))
(syntax close-parenthesis))
1 font-lock-variable-name-face)
;; def* -- functions
(,(rx (syntax open-parenthesis)
"def" (0+ (or (syntax word) (syntax symbol)))
(1+ space)
(1+ (syntax open-parenthesis)) ;1+ b/c curried define
(group (1+ (or (syntax word) (syntax symbol)))))
1 font-lock-function-name-face)
;; let identifiers
(,#'racket--font-lock-let-identifiers . font-lock-variable-name-face)
;; module and module*
(,(rx (syntax open-parenthesis)
(group "module" (? "*"))
(1+ space)
(group (1+ (or (syntax word) (syntax symbol))))
(1+ space)
(group (1+ (or (syntax word) (syntax symbol)))))
(1 font-lock-keyword-face nil t)
(2 font-lock-function-name-face nil t)
(3 font-lock-variable-name-face nil t))
;; module+
(,(rx (syntax open-parenthesis)
(group "module+")
(1+ space)
(group (1+ (or (syntax word) (syntax symbol)))))
(1 font-lock-keyword-face nil t)
(2 font-lock-function-name-face nil t))
))
"Parens, modules, function/variable identifiers, syntax-")
(defconst racket-font-lock-keywords-3
(eval-when-compile
`(
(,(regexp-opt racket-keywords 'symbols) . font-lock-keyword-face)
(,(regexp-opt racket-builtins-1-of-2 'symbols) . font-lock-builtin-face)
(,(regexp-opt racket-builtins-2-of-2 'symbols) . font-lock-builtin-face)
(,(regexp-opt racket-type-list 'symbols) . font-lock-type-face)
;; pretty lambda (deprecated)
(,(rx (syntax open-parenthesis)
(? (or "case-" "match-" "opt-"))
(group "lambda")
(or word-end symbol-end))
1
(ignore
(when racket-pretty-lambda
(compose-region (match-beginning 1)
(match-end 1)
racket-lambda-char)))
nil t)
))
"Function/variable identifiers, Typed Racket types.
Note: To the extent you use #lang racket or #typed/racket, this
may be handy. But Racket is also a tool to make #lang's, and this
doesn't really fit that.")
(defconst racket-font-lock-keywords-level-0
(append racket-font-lock-keywords-0))
(defconst racket-font-lock-keywords-level-1
(append racket-font-lock-keywords-0
racket-font-lock-keywords-1))
(defconst racket-font-lock-keywords-level-2
(append racket-font-lock-keywords-0
racket-font-lock-keywords-1
racket-font-lock-keywords-2))
(defconst racket-font-lock-keywords-level-3
(append racket-font-lock-keywords-0
racket-font-lock-keywords-1
racket-font-lock-keywords-2
racket-font-lock-keywords-3))
(defconst racket-font-lock-keywords
'(racket-font-lock-keywords-level-0
racket-font-lock-keywords-level-1
racket-font-lock-keywords-level-2
racket-font-lock-keywords-level-3))
(defun racket-font-lock-syntactic-face-function (state)
(let ((q (racket--ppss-string-p state)))
(if q
(let ((startpos (racket--ppss-string/comment-start state)))
(if (eq (char-after startpos) ?|)
nil ;a |...| symbol
(if (characterp q)
font-lock-string-face
racket-here-string-face)))
font-lock-comment-face)))
;;; sexp comments
(defun racket--font-lock-sexp-comments (limit)
"Font-lock sexp comments.
Note that the syntax table does NOT show these as comments in
order to let indent and nav work within the sexp. We merely
font-lock them as comments."
(ignore-errors
(when (re-search-forward (rx (group-n 1 "#;" (* " "))
(group-n 2 (not (any " "))))
limit t)
(let ((md (match-data)))
(goto-char (match-beginning 2))
(forward-sexp 1)
(setf (elt md 5) (point)) ;set (match-end 2)
(set-match-data md)
t))))
;;; let forms
(defun racket--font-lock-let-identifiers (limit)
"In let forms give identifiers `font-lock-variable-name-face'.
This handles both let and let-values style forms (bindings with
with single identifiers or identifier lists).
Note: This works only when the let form has a closing paren.
\(Otherwise, when you type an incomplete let form before existing
code, this would mistakenly treat the existing code as part of
the let form.) The font-lock will kick in after you type the
closing paren. Or if you use electric-pair-mode, paredit, or
similar, it will already be there."
(while (re-search-forward
(rx (syntax open-parenthesis)
(* (syntax whitespace))
(group-n 1 "let" (* (or (syntax word) (syntax symbol)))))
limit
t)
(ignore-errors
(when (and (not (member (match-string-no-properties 1) '("let/ec" "let/cc")))
(racket--inside-complete-sexp))
;; Resume search before this let's bindings list, so we can
;; check rhs of bindings for more lets.
(save-excursion
;; Check for named let
(when (looking-at (rx (+ space) (+ (or (syntax word) (syntax symbol)))))
(forward-sexp 1)
(backward-sexp 1)
(racket--sexp-set-face font-lock-function-name-face))
;; Set font-lock-multiline property on entire identifier
;; list. Avoids need for font-lock-extend-region function.
(put-text-property (point)
(save-excursion (forward-sexp 1) (point))
'font-lock-multiline t)
(down-list 1) ;to the open paren of the first binding form
(while (ignore-errors
(down-list 1) ;to the id or list of id's
(if (not (looking-at "[([{]"))
(racket--sexp-set-face font-lock-variable-name-face)
;; list of ids, e.g. let-values
(down-list 1) ;to first id
(cl-loop
do (racket--sexp-set-face font-lock-variable-name-face)
while (ignore-errors (forward-sexp 1) (backward-sexp 1) t))
(backward-up-list))
(backward-up-list) ;to open paren of this binding form
(forward-sexp 1) ;to open paren of next binding form
t))))))
nil)
;;; misc
(defun racket--inside-complete-sexp ()
"Return whether point is inside a complete sexp."
(condition-case ()
(save-excursion (backward-up-list) (forward-sexp 1) t)
(error nil)))
(defun racket--sexp-set-face (face &optional forcep)
"Set 'face prop to FACE, rear-nonsticky, for the sexp starting at point.
Unless FORCEP is t, does so only if not already set in the
region.
Moves point to the end of the sexp."
(racket--region-set-face (point)
(progn (forward-sexp 1) (point))
face
forcep))
(defun racket--region-set-face (beg end face &optional forcep)
"Set 'face prop to FACE, rear-nonsticky, in the region BEG..END.
Unless FORCEP is t, does so only if not already set in the
region."
(when (or forcep (not (text-property-not-all beg end 'face nil)))
(add-text-properties beg end
`(face ,face
;;rear-nonsticky (face)
))))
(provide 'racket-font-lock)
;; racket-font-lock.el ends here

View File

@@ -0,0 +1,89 @@
;;; racket-imenu.el
;; Copyright (c) 2013-2016 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'cl-lib)
(require 'imenu)
(defun racket--variables-imenu ()
(set (make-local-variable 'imenu-case-fold-search) t)
(set (make-local-variable 'imenu-create-index-function)
#'racket--imenu-create-index-function))
(defun racket--imenu-create-index-function ()
"A function for the variable `imenu-create-index-function'.
Knows about Racket module forms, and prefixes identiers with
their parent module name(s)."
(save-excursion
(goto-char (point-min))
(racket--next-sexp)
(racket--walk-sexps "")))
(defun racket--walk-sexps (prefix)
"With point at the start of a sexp, walk all the sepxs.
`racket--menu-sexp' will walk into Racket module forms and call
us recursively."
(cl-loop append (racket--menu-sexp prefix) into xs
while (racket--next-next-sexp)
finally return xs))
(defun racket--menu-sexp (prefix)
"Return the identifier for the sexp at point if any, else nil.
If sexp at point is a Racket module form, descend and walk that."
(cond ((looking-at (rx "(define" (* (or (syntax word) (syntax symbol)))
(+ (syntax whitespace))
(? ?\()
(group (+ (or (syntax word) (syntax symbol))))))
(let* ((beg (match-beginning 1))
(beg (if imenu-use-markers
(save-excursion (goto-char beg) (point-marker))
beg)))
(list (cons (concat prefix (match-string-no-properties 1))
beg))))
((looking-at (rx "(module" (? (any ?+ ?*))
(+ (syntax whitespace))
(group (+ (or (syntax word) (syntax symbol))))))
(save-excursion
(goto-char (match-end 1))
(racket--next-sexp)
(racket--walk-sexps (concat prefix (match-string-no-properties 1) ":"))))
(t nil)))
(defun racket--next-sexp ()
"Move point to start of next sexp in buffer."
(forward-sexp 1)
(forward-sexp -1))
(defun racket--next-next-sexp ()
"If another sexp, move point to its start and return t, else return nil."
(condition-case nil
(progn
(forward-sexp 1)
(let ((orig (point)))
(forward-sexp 1)
(if (or (eobp) (equal orig (point)))
nil
(forward-sexp -1)
t)))
(scan-error nil)))
(provide 'racket-imenu)
;;; racket-imenu.el ends here

View File

@@ -0,0 +1,530 @@
;;; racket-indent.el -*- lexical: t; -*-
;; Copyright (c) 2013-2017 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'cl-lib)
(require 'racket-util)
(require 'racket-custom)
(require 'racket-ppss)
;; The two top-level commands we care about are:
;; 1. `prog-indent-sexp' C-M-q
;; 2. `indent-region' C-M-\
;;
;; 1. `prog-indent-sexp' thinly wraps `indent-region'.
;;
;; 2. `indent-region' calls `indent-according-to-mode', which in turn
;; calls the mode-specific `indent-line-function'. In lisp-mode that's
;; `lisp-indent-line', which in turn calls `calculate-lisp-indent'.
;; That in turn calls the mode-specific `indent-function'; in
;; lisp-mode that's `lisp-indent-function'.
;;
;; However `calculate-lisp-indent' is complicated and doesn't always
;; behave the way we want. So we use a simplified version of that
;; (`racket--calculate-indent') in our `indent-line-function',
;; `racket-indent-line'. That just directly calls
;; `racket-indent-function'.
;; Having said all that, we still have the matter of `paredit-mode'.
;; It directly calls `lisp-indent-line' instead of `indent-function'.
;; And, it directly calls `indent-sexp' instead of `prog-indent-sep'.
;; Therefore it gets `lisp-mode' indent, not ours. To address this,
;; advise those two functions to do the right thing when one of our
;; major modes is active.
(defun racket--lisp-indent-line-advice (orig &rest args)
"When `racket--mode-edits-racket-p' instead use `racket-indent-line'."
(apply (if (racket--mode-edits-racket-p) #'racket-indent-line orig)
args))
(defun racket--indent-sexp-advice (orig &rest args)
"When `racket--mode-edits-racket-p' instead use `prog-indent-sexp'."
(apply (if (racket--mode-edits-racket-p) #'prog-indent-sexp orig)
args))
;; I don't want to muck with the old `defadvice' for this. Instead use
;; `advice-add' in Emacs 24.4+. Although we still support Emacs 24.3,
;; not sure how much longer; I'm OK having it silently not work.
(when (fboundp 'advice-add)
(advice-add 'lisp-indent-line :around #'racket--lisp-indent-line-advice)
(advice-add 'indent-sexp :around #'racket--indent-sexp-advice))
(defun racket-indent-line (&optional whole-exp)
"Indent current line as Racket code.
This behaves like `lisp-indent-line', except that whole-line
comments are treated the same regardless of whether they start
with single or double semicolons.
- Automatically indents forms that start with `begin` in the usual
way that `begin` is indented.
- Automatically indents forms that start with `def` or `with-` in the
usual way that `define` is indented.
- Has rules for many specific standard Racket forms.
To extend, use your Emacs init file to
(put SYMBOL 'racket-indent-function INDENT)
where `SYMBOL` is the name of the Racket form (e.g. `'test-case`)
and `INDENT` is an integer or the symbol `'defun`. When `INDENT`
is an integer, the meaning is the same as for
`lisp-indent-function` and `scheme-indent-function`: Indent the
first `n` arguments specially and then indent any further
arguments like a body.
For example in your `.emacs` file you could use:
(put 'test-case 'racket-indent-function 1)
to change the indent of `test-case` from this:
(test-case foo
blah
blah)
to this:
(test-case foo
blah
blah)
If `racket-indent-function` has no property for a symbol,
`scheme-indent-function` is also considered (although the with-x
indents defined by `scheme-mode` are ignored). This is only to
help people who may have extensive `scheme-indent-function`
settings, particularly in the form of file or dir local
variables. Otherwise prefer `racket-indent-function`."
(interactive)
(pcase (racket--calculate-indent)
(`() nil)
;; When point is within the leading whitespace, move it past the
;; new indentation whitespace. Otherwise preserve its position
;; relative to the original text.
(amount (let ((pos (- (point-max) (point)))
(beg (progn (beginning-of-line) (point))))
(skip-chars-forward " \t")
(unless (= amount (current-column))
(delete-region beg (point))
(indent-to amount))
(when (< (point) (- (point-max) pos))
(goto-char (- (point-max) pos)))))))
(defun racket--calculate-indent ()
"Return appropriate indentation for current line as Lisp code.
In usual case returns an integer: the column to indent to.
If the value is nil, that means don't change the indentation
because the line starts inside a string.
This is `calculate-lisp-indent' distilled to what we actually
need."
(save-excursion
(beginning-of-line)
(let ((indent-point (point))
(state nil))
(racket--plain-beginning-of-defun)
(while (< (point) indent-point)
(setq state (parse-partial-sexp (point) indent-point 0)))
(let ((strp (racket--ppss-string-p state))
(last (racket--ppss-last-sexp state))
(cont (racket--ppss-containing-sexp state)))
(cond
(strp nil)
((and state last cont) (racket-indent-function indent-point state))
(cont (goto-char (1+ cont)) (current-column))
(t (current-column)))))))
(defun racket--plain-beginning-of-defun ()
"Like default/plain `beginning-of-function'.
Our `racket--beginning-of-defun-function' is aware of module
forms and tailored to using C-M-a to navigate interactively. But
it is too slow to be used here -- especially in \"degenerate\"
cases like a 3000 line file consisting of one big `module` or
`library` sexpr."
(when (re-search-backward (rx bol (syntax open-parenthesis))
nil
'move)
(goto-char (1- (match-end 0)))))
(defun racket-indent-function (indent-point state)
"Called by `racket--calculate-indent' to get indent column.
INDENT-POINT is the position at which the line being indented begins.
STATE is the `parse-partial-sexp' state for that position.
There is special handling for:
- forms that begin with a #:keyword (as found in contracts)
- forms like #hasheq()
- data sequences when `racket-indent-sequence-depth' is > 0
- {} forms when `racket-indent-curly-as-sequence' is not nil
See `racket-indent-line' for more information about users setting
the `racket-indent-function` property."
(goto-char (racket--ppss-containing-sexp state))
(let ((body-indent (+ (current-column) lisp-body-indent)))
(forward-char 1)
(if (or (racket--hash-literal-or-keyword-p)
(racket--data-sequence-p))
(progn (backward-prefix-chars) (current-column))
(let* ((head (buffer-substring (point) (progn (forward-sexp 1) (point))))
(method (racket--get-indent-function-method head)))
(cond ((integerp method)
(racket--indent-special-form method indent-point state))
((eq method 'defun)
body-indent)
(method
(funcall method indent-point state))
((string-match (rx bos (or "def" "with-")) head)
body-indent) ;just like 'defun
((string-match (rx bos "begin") head)
(racket--indent-special-form 0 indent-point state))
(t
(racket--normal-indent indent-point state)))))))
(defun racket--hash-literal-or-keyword-p ()
"Looking at things like #fl() #hash() or #:keyword ?
The last occurs in Racket contract forms, e.g. (->* () (#:kw kw)).
Returns nil for #% identifiers like #%app."
(looking-at (rx ?\# (or ?\:
(not (any ?\%))))))
(defun racket--data-sequence-p ()
"Looking at \"data\" sequences where we align under head item?
These sequences include '() `() #() -- and {} when
`racket-indent-curly-as-sequence' is t -- but never #'() #`() ,()
,@().
To handle nested items, we search `backward-up-list' up to
`racket-indent-sequence-depth' times."
(and (< 0 racket-indent-sequence-depth)
(save-excursion
(ignore-errors
(let ((answer 'unknown)
(depth racket-indent-sequence-depth))
(while (and (eq answer 'unknown)
(< 0 depth))
(backward-up-list)
(cl-decf depth)
(cond ((or
;; a quoted '( ) or quasiquoted `( ) list --
;; but NOT syntax #'( ) or quasisyntax #`( )
(and (memq (char-before (point)) '(?\' ?\`))
(eq (char-after (point)) ?\()
(not (eq (char-before (1- (point))) ?#)))
;; a vector literal: #( )
(and (eq (char-before (point)) ?#)
(eq (char-after (point)) ?\())
;; { }
(and racket-indent-curly-as-sequence
(eq (char-after (point)) ?{)))
(setq answer t))
(;; unquote or unquote-splicing
(and (or (eq (char-before (point)) ?,)
(and (eq (char-before (1- (point))) ?,)
(eq (char-before (point)) ?@)))
(eq (char-after (point)) ?\())
(setq answer nil))))
(eq answer t))))))
(defun racket--normal-indent (indent-point state)
;; Credit: Substantially borrowed from clojure-mode
(goto-char (racket--ppss-last-sexp state))
(backward-prefix-chars)
(let ((last-sexp nil))
(if (ignore-errors
;; `backward-sexp' until we reach the start of a sexp that is the
;; first of its line (the start of the enclosing sexp).
(while (string-match (rx (not blank))
(buffer-substring (line-beginning-position)
(point)))
(setq last-sexp (prog1 (point)
(forward-sexp -1))))
t)
;; Here we've found an arg before the arg we're indenting
;; which is at the start of a line.
(current-column)
;; Here we've reached the start of the enclosing sexp (point is
;; now at the function name), so the behavior depends on whether
;; there's also an argument on this line.
(when (and last-sexp
(< last-sexp (line-end-position)))
;; There's an arg after the function name, so align with it.
(goto-char last-sexp))
(current-column))))
(defun racket--indent-special-form (method indent-point state)
"METHOD must be a nonnegative integer -- the number of
\"special\" args that get extra indent when not on the first
line. Any additinonl args get normal indent."
;; Credit: Substantially borrowed from clojure-mode
(let ((containing-column (save-excursion
(goto-char (racket--ppss-containing-sexp state))
(current-column)))
(pos -1))
(condition-case nil
(while (and (<= (point) indent-point)
(not (eobp)))
(forward-sexp 1)
(cl-incf pos))
;; If indent-point is _after_ the last sexp in the current sexp,
;; we detect that by catching the `scan-error'. In that case, we
;; should return the indentation as if there were an extra sexp
;; at point.
(scan-error (cl-incf pos)))
(cond ((= method pos) ;first non-distinguished arg
(+ containing-column lisp-body-indent))
((< method pos) ;more non-distinguished args
(racket--normal-indent indent-point state))
(t ;distinguished args
(+ containing-column (* 2 lisp-body-indent))))))
(defun racket--conditional-indent (indent-point state looking-at-regexp true false)
(skip-chars-forward " \t")
(let ((n (if (looking-at looking-at-regexp) true false)))
(racket--indent-special-form n indent-point state)))
(defconst racket--identifier-regexp
(rx (or (syntax symbol) (syntax word) (syntax punctuation)))
"A regexp matching valid Racket identifiers.")
(defun racket--indent-maybe-named-let (indent-point state)
"Indent a let form, handling named let (let <id> <bindings> <expr> ...)"
(racket--conditional-indent indent-point state
racket--identifier-regexp
2 1))
(defun racket--indent-for (indent-point state)
"Indent function for all for/ and for*/ forms EXCEPT
for/fold and for*/fold.
Checks for either of:
- maybe-type-ann e.g. (for/list : T ([x xs]) x)
- for/vector optional length, (for/vector #:length ([x xs]) x)"
(racket--conditional-indent indent-point state
(rx (or ?\: ?\#))
3 1))
(defun racket--indent-for/fold (indent-point state)
"Indent function for for/fold and for*/fold."
;; check for maybe-type-ann e.g. (for/fold : T ([n 0]) ([x xs]) x)
(skip-chars-forward " \t\n")
(if (looking-at ":")
(racket--indent-special-form 4 indent-point state)
(racket--indent-for/fold-untyped indent-point state)))
(defun racket--indent-for/fold-untyped (indent-point state)
(let* ((containing-sexp-start (racket--ppss-containing-sexp state))
(_ (goto-char containing-sexp-start))
(containing-sexp-column (current-column))
(containing-sexp-line (line-number-at-pos))
(body-indent (+ containing-sexp-column lisp-body-indent))
(clause-indent nil))
;; Move to the open paren of the first, accumulator sexp
(forward-char 1) ;past the open paren
(forward-sexp 2) ;to the next sexp, past its close paren
(backward-sexp 1) ;back to its open paren
;; If the first, accumulator sexp is not on the same line as
;; `for/fold`, then this is simply specform 2.
(if (/= (line-number-at-pos) containing-sexp-line) ;expensive?
(racket--indent-special-form 2 indent-point state)
(setq clause-indent (current-column))
(forward-sexp 1) ;past close paren
;; Now go back to the beginning of the line holding
;; the indentation point. Count the sexps on the way.
(parse-partial-sexp (point) indent-point 1 t)
(let ((n 1))
(while (and (< (point) indent-point)
(ignore-errors
(cl-incf n)
(forward-sexp 1)
(parse-partial-sexp (point) indent-point 1 t))))
(if (= 1 n) clause-indent body-indent)))))
(defun racket--get-indent-function-method (head)
"Get property of racket- or scheme-indent-function.
Ignores certain with-xxx indents defined by scheme-mode --
because we automatically indent with- forms just like def forms.
However if a _user_ has defined their own legacy scheme-mode
indents for _other_ with- forms, those _will_ be used. We only
ignore a short list defined by scheme-mode itself."
(let ((sym (intern-soft head)))
(or (get sym 'racket-indent-function)
(and (not (memq sym '(call-with-values
with-mode
with-input-from-file
with-input-from-port
with-output-to-file
with-output-to-port
with-input-from-string
with-output-to-string
with-values)))
(get sym 'scheme-indent-function)))))
(defun racket--set-indentation ()
"Set indentation for various Racket forms.
Note that `beg*`, `def*` and `with-*` aren't listed here because
`racket-indent-function' handles those.
Note that indentation is set for the symbol alone, and also with
a : suffix for legacy Typed Racket. For example both `let` and
`let:`. Although this is overzealous in the sense that Typed
Racket does not define its own variant of all of these, it
doesn't hurt to do so."
(mapc (lambda (x)
(put (car x) 'racket-indent-function (cadr x))
(let ((typed (intern (format "%s:" (car x)))))
(put typed 'racket-indent-function (cadr x))))
'(;; begin* forms default to 0 unless otherwise specified here
(begin0 1)
(c-declare 0)
(c-lambda 2)
(call-with-input-file defun)
(call-with-input-file* defun)
(call-with-output-file defun)
(call-with-output-file* defun)
(case 1)
(case-lambda 0)
(catch 1)
(class defun)
(class* defun)
(compound-unit/sig 0)
(cond 0)
;; def* forms default to 'defun unless otherwise specified here
(delay 0)
(do 2)
(dynamic-wind 0)
(fn 1) ;alias for lambda (although not officially in Racket)
(for 1)
(for/list racket--indent-for)
(for/vector racket--indent-for)
(for/hash racket--indent-for)
(for/hasheq racket--indent-for)
(for/hasheqv racket--indent-for)
(for/and racket--indent-for)
(for/or racket--indent-for)
(for/lists racket--indent-for/fold)
(for/first racket--indent-for)
(for/last racket--indent-for)
(for/fold racket--indent-for/fold)
(for/flvector racket--indent-for)
(for/set racket--indent-for)
(for/seteq racket--indent-for)
(for/seteqv racket--indent-for)
(for/sum racket--indent-for)
(for/product racket--indent-for)
(for* 1)
(for*/list racket--indent-for)
(for*/vector racket--indent-for)
(for*/hash racket--indent-for)
(for*/hasheq racket--indent-for)
(for*/hasheqv racket--indent-for)
(for*/and racket--indent-for)
(for*/or racket--indent-for)
(for*/lists racket--indent-for/fold)
(for*/first racket--indent-for)
(for*/last racket--indent-for)
(for*/fold racket--indent-for/fold)
(for*/flvector racket--indent-for)
(for*/set racket--indent-for)
(for*/seteq racket--indent-for)
(for*/seteqv racket--indent-for)
(for*/sum racket--indent-for)
(for*/product racket--indent-for)
(instantiate 2)
(interface 1)
(λ 1)
(lambda 1)
(lambda/kw 1)
(let racket--indent-maybe-named-let)
(let* 1)
(letrec 1)
(letrec-values 1)
(let-values 1)
(let*-values 1)
(let+ 1)
(let-syntax 1)
(let-syntaxes 1)
(letrec-syntax 1)
(letrec-syntaxes 1)
(letrec-syntaxes+values racket--indent-for/fold-untyped)
(local 1)
(let/cc 1)
(let/ec 1)
(match 1)
(match* 1)
(match-define defun)
(match-lambda 0)
(match-lambda* 0)
(match-let 1)
(match-let* 1)
(match-let*-values 1)
(match-let-values 1)
(match-letrec 1)
(match-letrec-values 1)
(match/values 1)
(mixin 2)
(module 2)
(module+ 1)
(module* 2)
(opt-lambda 1)
(parameterize 1)
(parameterize-break 1)
(parameterize* 1)
(quasisyntax/loc 1)
(receive 2)
(require/typed 1)
(require/typed/provide 1)
(send* 1)
(shared 1)
(sigaction 1)
(splicing-let 1)
(splicing-letrec 1)
(splicing-let-values 1)
(splicing-letrec-values 1)
(splicing-let-syntax 1)
(splicing-letrec-syntax 1)
(splicing-let-syntaxes 1)
(splicing-letrec-syntaxes 1)
(splicing-letrec-syntaxes+values racket--indent-for/fold-untyped)
(splicing-local 1)
(splicing-syntax-parameterize 1)
(struct defun)
(syntax-case 2)
(syntax-case* 3)
(syntax-rules 1)
(syntax-id-rules 1)
(syntax-parse 1)
(syntax-parser 0)
(syntax-parameterize 1)
(syntax/loc 1)
(syntax-parse 1)
(test-begin 0)
(test-case 1)
(unit defun)
(unit/sig 2)
(unless 1)
(when 1)
(while 1)
;; with- forms default to 1 unless otherwise specified here
)))
(provide 'racket-indent)
;; racket-indent.el ends here

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
;;; racket-logger.el -*- lexical-binding: t; -*-
;; Copyright (c) 2013-2016 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'easymenu)
(require 'rx)
(require 'racket-custom)
(require 'racket-repl)
;; Need to define this before racket-logger-mode
(defvar racket-logger-mode-map
(racket--easy-keymap-define
'(("l" racket-logger-topic-level)
("w" toggle-truncate-lines)
("n" racket-logger-next-item)
("p" racket-logger-previous-item)
("g" racket-logger-clear)
("x" racket-logger-exit)
("C-c C-z" racket-repl))))
(easy-menu-define racket-logger-mode-menu racket-logger-mode-map
"Menu for Racket logger mode."
'("Racket"
["Configure Topic and Level" racket-logger-topic-level]
["Toggle Truncate Lines" toggle-truncate-lines]
"---"
["Switch to REPL" racket-repl]
"---"
["Clear and Reconnect" racket-logger-clear]
["Exit Logger" racket-logger-exit]))
(defconst racket-logger-font-lock-keywords
(eval-when-compile
`((,#'racket--font-lock-config . racket-logger-config-face)
(,(rx bol "[ fatal]") . racket-logger-fatal-face)
(,(rx bol "[ error]") . racket-logger-error-face)
(,(rx bol "[warning]") . racket-logger-warning-face)
(,(rx bol "[ info]") . racket-logger-info-face)
(,(rx bol "[ debug]") . racket-logger-debug-face)
(,(rx bol ?\[ (+? anything) ?\] space
(group (+? anything) ?:) space)
1 racket-logger-topic-face))))
(defconst racket-logger--print-config-prefix
"racket-logger-config:\n")
(defun racket--font-lock-config (limit)
"Handle multi-line font-lock of the configuration info."
(ignore-errors
(when (re-search-forward (concat "^" racket-logger--print-config-prefix) limit t)
(let ((md (match-data)))
(goto-char (match-end 0))
(forward-sexp 1)
(setf (elt md 1) (point)) ;; set (match-end 0)
(set-match-data md)
t))))
(define-derived-mode racket-logger-mode special-mode "Racket-Logger"
"Major mode for Racket logger output.
\\<racket-logger-mode-map>
The customization variable `racket-logger-config' determines the
levels for topics. During a session you may change topic levels
using `racket-logger-topic-level', bound to
\"\\[racket-logger-topic-level]\".
For more information see:
<https://docs.racket-lang.org/reference/logging.html>
```
\\{racket-logger-mode-map}
```
"
(setq-local font-lock-defaults (list racket-logger-font-lock-keywords))
(setq-local truncate-lines t))
(defvar racket-logger--buffer-name "*Racket Logger*")
(defvar racket-logger--process nil)
(defvar racket-logger--connect-timeout 3)
(defun racket-logger--connect ()
(unless racket-logger--process
(with-temp-message "Connecting to logger process..."
(with-timeout (racket-logger--connect-timeout
(error "Could not connect; try `racket-run' first"))
(while (not racket-logger--process)
(condition-case ()
(setq racket-logger--process
(let ((process-connection-type nil)) ;use pipe not pty
(open-network-stream "racket-logger"
(get-buffer-create racket-logger--buffer-name)
"127.0.0.1"
(1+ racket-command-port))))
(error (sit-for 0.1)))))
(process-send-string racket-logger--process racket--cmd-auth)
(racket-logger--activate-config)
(set-process-sentinel racket-logger--process
#'racket-logger--process-sentinel))))
(defun racket-logger--process-sentinel (proc change)
(funcall (process-filter proc) proc change) ;display in buffer
(unless (memq (process-status proc) '(run open connect))
(setq racket-logger--process nil)))
(defun racket-logger--disconnect ()
(when racket-logger--process
(with-temp-message "Disconnecting from logger process..."
(set-process-sentinel racket-logger--process (lambda (_p _c)))
(delete-process racket-logger--process)
(setq racket-logger--process nil))))
(defun racket-logger--activate-config ()
"Send config to Racket process, and, display it in the buffer."
(process-send-string racket-logger--process
(format "%S" racket-logger-config))
(funcall (process-filter racket-logger--process)
racket-logger--process
(propertize (concat racket-logger--print-config-prefix
(pp-to-string racket-logger-config))
'font-lock-multiline t)))
(defun racket-logger--set (topic level)
(unless (symbolp topic) (error "TOPIC must be symbolp"))
(unless (symbolp level) (error "LEVEL must be symbolp"))
(pcase (assq topic racket-logger-config)
(`() (add-to-list 'racket-logger-config (cons topic level)))
(v (setcdr v level)))
(racket-logger--activate-config))
(defun racket-logger--unset (topic)
(unless (symbolp topic) (error "TOPIC must be symbolp"))
(when (eq topic '*)
(user-error "Cannot unset the level for the '* topic"))
(setq racket-logger-config
(assq-delete-all topic racket-logger-config))
(racket-logger--activate-config))
(defun racket-logger--topics ()
"Effectively (sort (dict-keys racket-logger-config))."
(sort (mapcar (lambda (x) (format "%s" (car x)))
racket-logger-config)
#'string<))
(defun racket-logger--topic-level (topic not-found)
"Effectively (dict-ref racket-logger-config topic not-found)."
(or (cdr (assq topic racket-logger-config))
not-found))
;;; commands
(defun racket-logger ()
"Create the `racket-logger-mode' buffer and connect to logger output.
If the `racket-repl-mode' buffer is displayed in a window, split
that window and put the logger in the bottom window. Otherwise,
use `pop-to-buffer'."
(interactive)
;; Create buffer if necessary
(unless (get-buffer racket-logger--buffer-name)
(with-current-buffer (get-buffer-create racket-logger--buffer-name)
(racket-logger-mode))
(racket-logger--connect))
;; Give it a window if necessary
(unless (get-buffer-window racket-logger--buffer-name)
(pcase (get-buffer-window racket--repl-buffer-name)
(`() (pop-to-buffer (get-buffer racket-logger--buffer-name)))
(win (set-window-buffer (split-window win)
(get-buffer racket-logger--buffer-name)))))
;; Select the window
(select-window (get-buffer-window racket-logger--buffer-name)))
(defun racket-logger-exit ()
"Disconnect, kill the buffer, and delete the window."
(interactive)
(when (y-or-n-p "Disconnect and kill buffer? ")
(racket-logger--disconnect)
(kill-buffer)
(delete-window)))
(defun racket-logger-clear ()
"Clear the buffer and reconnect."
(interactive)
(when (y-or-n-p "Clear buffer and reconnect? ")
(let ((inhibit-read-only t))
(delete-region (point-min) (point-max)))
(racket-logger--disconnect)
(racket-logger--connect)))
(defconst racket-logger--item-rx
(rx bol ?\[ (0+ space) (or "fatal" "error" "warning" "info" "debug") ?\] space))
(defun racket-logger-next-item (&optional count)
"Move point N items forward.
An \"item\" is a line starting with a log level in brackets.
Interactively, N is the numeric prefix argument.
If N is omitted or nil, move point 1 item forward."
(interactive "P")
(forward-char 1)
(if (re-search-forward racket-logger--item-rx nil t count)
(beginning-of-line)
(backward-char 1)))
(defun racket-logger-previous-item (&optional count)
"Move point N items backward.
An \"item\" is a line starting with a log level in brackets.
Interactively, N is the numeric prefix argument.
If N is omitted or nil, move point 1 item backward."
(interactive "P")
(re-search-backward racket-logger--item-rx nil t count))
(defun racket-logger-topic-level ()
"Set or unset the level for a topic.
For convenience, input choices using `ido-completing-read'.
The topic labeled \"*\" is the level to use for all topics not
specifically assigned a level.
The level choice \"*\" means the topic will no longer have its
own level, therefore will follow the level specified for the
\"*\" topic."
(interactive)
(let* ((topic (ido-completing-read
"Topic: "
(racket-logger--topics)))
(topic (pcase topic
("" "*")
(v v)))
(topic (intern topic))
(levels (list "fatal" "error" "warning" "info" "debug"))
(levels (if (eq topic '*) levels (cons "*" levels)))
(level (ido-completing-read
(format "Level for topic `%s': " topic)
levels
nil t nil nil
(format "%s" (racket-logger--topic-level topic "*"))))
(level (pcase level
("" nil)
("*" nil)
(v (intern v)))))
(if level
(racket-logger--set topic level)
(racket-logger--unset topic))))
(provide 'racket-logger)
;;; racket-logger.el ends here

View File

@@ -0,0 +1,265 @@
;;; racket-make-doc.el --- Major mode for Racket language.
;; Copyright (c) 2013-2018 by Greg Hendershott.
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
;;; Generate a markdown format file for Reference documentation.
(require 'racket-mode)
(require 'racket-debug)
(require 'racket-profile)
(require 'racket-edit)
(require 'racket-util)
(require 'racket-unicode-input-method)
(require 'cl-lib)
(require 's)
;;; Top
(defun racket-make-doc/write-reference-file ()
(interactive)
(with-temp-buffer
(insert (racket-make-doc/reference))
(write-region nil nil
(expand-file-name "Reference.md" racket--el-source-dir)
nil)))
(defun racket-make-doc/reference ()
(let ((text-quoting-style 'grave))
(concat "# Reference\n\n"
(racket-make-doc/toc)
"# Commands\n\n"
(racket-make-doc/commands)
"# Variables\n\n"
"> Note: You may also set these via Customize.\n\n"
(racket-make-doc/variables)
"# Faces\n\n"
"> Note: You may also set these via Customize.\n\n"
(racket-make-doc/faces))))
;;; Commands
(defconst racket-make-doc/commands
'("Run"
racket-run
racket-racket
racket-profile
racket-profile-mode
racket-logger
racket-logger-mode
racket-debug-mode
"Test"
racket-test
racket-raco-test
"Eval"
racket-send-region
racket-send-definition
racket-send-last-sexp
"Visit"
racket-visit-definition
racket-visit-module
racket-unvisit
racket-open-require-path
racket-find-collection
"Learn"
racket-describe
racket-doc
"Edit"
racket-fold-all-tests
racket-unfold-all-tests
racket-tidy-requires
racket-trim-requires
racket-base-requires
racket-indent-line
racket-smart-open-bracket
racket-cycle-paren-shapes
racket-backward-up-list
racket-check-syntax-mode
racket-unicode-input-method-enable
racket-align
racket-unalign
racket-complete-at-point
"Macro expand"
racket-stepper-mode
racket-expand-file
racket-expand-region
racket-expand-definition
racket-expand-last-sexp
"Other"
racket-mode-start-faster)
"Commands to include in the Reference.")
(defun racket-make-doc/commands ()
(apply #'concat
(mapcar #'racket-make-doc/command racket-make-doc/commands)))
(defun racket-make-doc/command (s)
(if (stringp s)
(format "## %s\n\n" s)
(concat (format "### %s\n" s)
(and (interactive-form s)
(racket-make-doc/bindings-as-kbd s))
(racket-make-doc/tweak-quotes
(racket-make-doc/linkify
(or (documentation s) "No documentation.\n\n")))
"\n\n")))
(defun racket-make-doc/bindings-as-kbd (symbol)
(let* ((bindings (racket-make-doc/bindings symbol))
(strs (and bindings
(cl-remove-if-not
#'identity
(mapcar (lambda (binding)
(unless (eq (aref binding 0) 'menu-bar)
(format "<kbd>%s</kbd>"
(racket-make-doc/html-escape
(key-description binding)))))
bindings))))
(str (if strs
(mapconcat #'identity strs " or ")
(format "<kbd>M-x %s</kbd>" symbol))))
(concat str "\n\n")))
(defun racket-make-doc/bindings (symbol)
(where-is-internal symbol racket-mode-map))
(defun racket-make-doc/html-escape (str)
(with-temp-buffer
(insert str)
(format-replace-strings '(("&" . "&amp;")
("<" . "&lt;")
(">" . "&gt;")))
(buffer-substring-no-properties (point-min) (point-max))))
;;; Variables
(defconst racket-make-doc/variables
'("General"
racket-program
racket-command-port
racket-command-timeout
racket-memory-limit
racket-error-context
racket-user-command-line-arguments
"REPL"
racket-history-filter-regexp
racket-images-inline
racket-images-keep-last
racket-images-system-viewer
racket-pretty-print
"Other"
racket-indent-curly-as-sequence
racket-indent-sequence-depth
racket-pretty-lambda
racket-smart-open-bracket-enable
racket-logger-config
"Experimental debugger"
racket-debuggable-files)
"Variables to include in the Reference.")
(defun racket-make-doc/variables ()
(apply #'concat
(mapcar #'racket-make-doc/variable racket-make-doc/variables)))
(defun racket-make-doc/variable (s)
(if (stringp s)
(format "## %s\n\n" s)
(concat (format "### %s\n" s)
(racket-make-doc/tweak-quotes
(racket-make-doc/linkify
(or (documentation-property s 'variable-documentation)
"No documentation.\n\n")))
"\n\n")))
;;; Faces
(defconst racket-make-doc/faces
'(racket-keyword-argument-face
racket-selfeval-face
racket-here-string-face
racket-check-syntax-def-face
racket-check-syntax-use-face
racket-logger-config-face
racket-logger-topic-face
racket-logger-fatal-face
racket-logger-error-face
racket-logger-warning-face
racket-logger-info-face
racket-logger-debug-face)
"Faces to include in the Reference.")
(defun racket-make-doc/faces ()
(apply #'concat
(mapcar #'racket-make-doc/face racket-make-doc/faces)))
(defun racket-make-doc/face (symbol)
(concat (format "### %s\n" symbol)
(racket-make-doc/tweak-quotes
(racket-make-doc/linkify
(or (documentation-property symbol 'face-documentation)
"No documentation.\n\n")))
"\n\n"))
;;; TOC
(defun racket-make-doc/toc ()
(concat "- [Commands](#commands)\n"
(racket-make-doc/subheads racket-make-doc/commands)
"- [Variables](#variables)\n"
(racket-make-doc/subheads racket-make-doc/variables)
"- [Faces](#faces)\n"
"\n"))
(defun racket-make-doc/subheads (xs)
(apply #'concat
(mapcar #'racket-make-doc/subhead
(cl-remove-if-not #'stringp xs))))
(defun racket-make-doc/subhead (x)
(format " - [%s](#%s)\n"
x
(s-dashed-words x)))
;;; Utility
(defun racket-make-doc/linkify (s)
(with-temp-buffer
(insert s)
(goto-char (point-min))
(while (re-search-forward (rx ?\`
(group "racket-" (+ (or (syntax word)
(syntax symbol))))
?\')
nil t)
(let ((name (buffer-substring-no-properties (match-beginning 1)
(match-end 1))))
(replace-match (format "[`%s`](#%s)" name name)
nil nil)))
(buffer-substring-no-properties (point-min) (point-max))))
(defun racket-make-doc/tweak-quotes (s)
"Change \` \' style quotes to \` \` style."
(with-temp-buffer
(insert s)
(goto-char (point-min))
(while (re-search-forward (rx ?\`
(group (+ (or (syntax word)
(syntax symbol))))
?\')
nil t)
(let ((name (buffer-substring-no-properties (match-beginning 1)
(match-end 1))))
(replace-match (format "`%s`" name)
nil nil)))
(buffer-substring-no-properties (point-min) (point-max))))
;;; racket-make-doc.el ends here

View File

@@ -0,0 +1,273 @@
;;; racket-mode-autoloads.el --- automatically extracted autoloads
;;
;;; Code:
(add-to-list 'load-path (directory-file-name
(or (file-name-directory #$) (car load-path))))
;;;### (autoloads nil "racket-bug-report" "racket-bug-report.el"
;;;;;; (0 0 0 0))
;;; Generated autoloads from racket-bug-report.el
(autoload 'racket-bug-report "racket-bug-report" "\
Fill a buffer with data to make a racket-mode bug report.
\(fn)" t nil)
;;;***
;;;### (autoloads nil "racket-collection" "racket-collection.el"
;;;;;; (0 0 0 0))
;;; Generated autoloads from racket-collection.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-collection" '("racket-")))
;;;***
;;;### (autoloads nil "racket-common" "racket-common.el" (0 0 0 0))
;;; Generated autoloads from racket-common.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-common" '("racket-")))
;;;***
;;;### (autoloads nil "racket-complete" "racket-complete.el" (0 0
;;;;;; 0 0))
;;; Generated autoloads from racket-complete.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-complete" '("racket-")))
;;;***
;;;### (autoloads nil "racket-custom" "racket-custom.el" (0 0 0 0))
;;; Generated autoloads from racket-custom.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-custom" '("racket-" "defface-racket")))
;;;***
;;;### (autoloads nil "racket-debug" "racket-debug.el" (0 0 0 0))
;;; Generated autoloads from racket-debug.el
(autoload 'racket--debug-send-definition "racket-debug" "\
\(fn BEG END)" nil nil)
(autoload 'racket--debug-on-break "racket-debug" "\
\(fn RESPONSE)" nil nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-debug" '("racket-")))
;;;***
;;;### (autoloads nil "racket-edit" "racket-edit.el" (0 0 0 0))
;;; Generated autoloads from racket-edit.el
(add-to-list 'hs-special-modes-alist '(racket-mode "(" ")" ";" nil nil))
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-edit" '("racket-")))
;;;***
;;;### (autoloads nil "racket-font-lock" "racket-font-lock.el" (0
;;;;;; 0 0 0))
;;; Generated autoloads from racket-font-lock.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-font-lock" '("racket-")))
;;;***
;;;### (autoloads nil "racket-imenu" "racket-imenu.el" (0 0 0 0))
;;; Generated autoloads from racket-imenu.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-imenu" '("racket--")))
;;;***
;;;### (autoloads nil "racket-indent" "racket-indent.el" (0 0 0 0))
;;; Generated autoloads from racket-indent.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-indent" '("racket-")))
;;;***
;;;### (autoloads nil "racket-keywords-and-builtins" "racket-keywords-and-builtins.el"
;;;;;; (0 0 0 0))
;;; Generated autoloads from racket-keywords-and-builtins.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-keywords-and-builtins" '("racket-")))
;;;***
;;;### (autoloads nil "racket-logger" "racket-logger.el" (0 0 0 0))
;;; Generated autoloads from racket-logger.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-logger" '("racket-")))
;;;***
;;;### (autoloads nil "racket-make-doc" "racket-make-doc.el" (0 0
;;;;;; 0 0))
;;; Generated autoloads from racket-make-doc.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-make-doc" '("racket-make-doc/")))
;;;***
;;;### (autoloads nil "racket-mode" "racket-mode.el" (0 0 0 0))
;;; Generated autoloads from racket-mode.el
(autoload 'racket-mode "racket-mode" "\
Major mode for editing Racket.
\\{racket-mode-map}
\(fn)" t nil)
(add-to-list 'auto-mode-alist '("\\.rkt[dl]?\\'" . racket-mode))
(modify-coding-system-alist 'file "\\.rkt[dl]?\\'" 'utf-8)
(add-to-list 'interpreter-mode-alist '("racket" . racket-mode))
(autoload 'racket-mode-start-faster "racket-mode" "\
Compile racket-mode's .rkt files for faster startup.
racket-mode is implemented as an Emacs Lisp \"front end\" that
talks to a Racket process \"back end\". Because racket-mode is
delivered as an Emacs package instead of a Racket package,
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 `compiled/` subdirectories. As a
result, when a `racket-run' or `racket-repl' command must start
the Racket process, it will start faster.
If you run this command, _ever_, you should run it _again_ after:
- Installing an updated version of racket-mode. Otherwise, you
might lose some of the speed-up.
- Installing a new version of Racket and/or changing the value of
the variable `racket-program'. Otherwise, you might get an
error message due to the bytecode being different versions.
\(fn)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-mode" '("racket-")))
;;;***
;;;### (autoloads nil "racket-ppss" "racket-ppss.el" (0 0 0 0))
;;; Generated autoloads from racket-ppss.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-ppss" '("racket--ppss-")))
;;;***
;;;### (autoloads nil "racket-profile" "racket-profile.el" (0 0 0
;;;;;; 0))
;;; Generated autoloads from racket-profile.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-profile" '("racket-")))
;;;***
;;;### (autoloads nil "racket-repl" "racket-repl.el" (0 0 0 0))
;;; Generated autoloads from racket-repl.el
(autoload 'racket-repl "racket-repl" "\
Run the Racket REPL and display its buffer in some window.
If the Racket process is not already running, it is started.
If NOSELECT is not nil, does not select the REPL
window (preserves the originally selected window).
Commands that don't want the REPL to be displayed can instead use
`racket--repl-ensure-buffer-and-process'.
\(fn &optional NOSELECT)" t nil)
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-repl" '("racket-" "with-racket-repl-buffer")))
;;;***
;;;### (autoloads nil "racket-stepper" "racket-stepper.el" (0 0 0
;;;;;; 0))
;;; Generated autoloads from racket-stepper.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-stepper" '("racket-")))
;;;***
;;;### (autoloads nil "racket-unicode-input-method" "racket-unicode-input-method.el"
;;;;;; (0 0 0 0))
;;; Generated autoloads from racket-unicode-input-method.el
(autoload 'racket-unicode-input-method-enable "racket-unicode-input-method" "\
Set input method to `racket-unicode`.
The `racket-unicode` input method lets you easily type various
Unicode symbols that might be useful when writing Racket
code.
To automatically enable the `racket-unicode` input method in
`racket-mode` buffers use `M-x customize-variable <RET>
racket-mode-hook` or put the following code in your Emacs init
file:
(add-hook 'racket-mode-hook #'racket-unicode-input-method-enable)
Likewise for `racket-repl-mode` buffers:
(add-hook 'racket-repl-mode-hook #'racket-unicode-input-method-enable)
To temporarily enable this input method for a single buffer you
can use `M-x racket-unicode-input-method-enable`.
Use `C-\\` to toggle the input method.
When the `racket-unicode` input method is active, you can for
example type `All` and it is immediately replaced with ``. A few
other examples:
omega ω
x_1 x₁
x^1 x¹
|A| 𝔸
test-->>E test-->>∃ (racket/redex)
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
can turn it off by setting `input-method-highlight-flag' to nil via
`M-x customize-variable`.
\(fn)" t nil)
;;;***
;;;### (autoloads nil "racket-util" "racket-util.el" (0 0 0 0))
;;; Generated autoloads from racket-util.el
(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "racket-util" '("racket--")))
;;;***
;;;### (autoloads nil nil ("racket-mode-pkg.el") (0 0 0 0))
;;;***
;; Local Variables:
;; version-control: never
;; no-byte-compile: t
;; no-update-autoloads: t
;; coding: utf-8
;; End:
;;; racket-mode-autoloads.el ends here

View File

@@ -0,0 +1,12 @@
(define-package "racket-mode" "20181004.309" "Major mode for Racket language."
'((emacs "24.3")
(faceup "0.0.2")
(s "1.9.0"))
:authors
'(("Greg Hendershott"))
:maintainer
'("Greg Hendershott")
:url "https://github.com/greghendershott/racket-mode")
;; Local Variables:
;; no-byte-compile: t
;; End:

View File

@@ -0,0 +1,188 @@
;;; racket-mode.el --- Major mode for Racket language.
;; Copyright (c) 2013-2018 by Greg Hendershott.
;; Package: racket-mode
;; Package-Requires: ((emacs "24.3") (faceup "0.0.2") (s "1.9.0"))
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
;;; Commentary:
;; Goals:
;; - Focus on Racket lang.
;; - Follow DrRacket concepts where applicable.
;; - Thorough font-lock and indent.
;; - Compatible with Emacs 24.3+ and Racket 6.0+.
;;
;; Details: https://github.com/greghendershott/racket-mode
;;; Code:
(require 'racket-edit)
(require 'racket-imenu)
(require 'racket-profile)
(require 'racket-logger)
(require 'racket-stepper)
(require 'racket-repl)
(require 'racket-collection)
(require 'racket-bug-report)
(require 'racket-util)
(require 'easymenu)
(defvar racket-mode-map
(racket--easy-keymap-define
'((("C-c C-c"
"C-c C-k") racket-run)
("C-c C-z" racket-repl)
("<f5>" racket-run-and-switch-to-repl)
("M-C-<f5>" racket-racket)
("C-<f5>" racket-test)
("C-c C-t" racket-test)
("C-c C-l" racket-logger)
("C-c C-o" racket-profile)
("M-C-x" racket-send-definition)
("C-x C-e" racket-send-last-sexp)
("C-c C-r" racket-send-region)
("C-c C-e f" racket-expand-file)
("C-c C-e x" racket-expand-definition)
("C-c C-e e" racket-expand-last-sexp)
("C-c C-e r" racket-expand-region)
("C-c C-x C-f" racket-open-require-path)
("TAB" indent-for-tab-command)
("M-C-u" racket-backward-up-list)
("[" racket-smart-open-bracket)
(")" racket-insert-closing)
("]" racket-insert-closing)
("}" racket-insert-closing)
("C-c C-p" racket-cycle-paren-shapes)
("M-C-y" racket-insert-lambda)
("C-c C-d" racket-doc)
("C-c C-." racket-describe)
("M-." racket-visit-definition)
("M-C-." racket-visit-module)
("M-," racket-unvisit)
("C-c C-f" racket-fold-all-tests)
("C-c C-u" racket-unfold-all-tests)))
"Keymap for Racket mode.")
(easy-menu-define racket-mode-menu racket-mode-map
"Menu for Racket mode."
'("Racket"
("Run"
["in REPL" racket-run]
["in REPL and switch to REPL" racket-run-and-switch-to-repl]
["in *shell* using `racket`" racket-racket])
("Tests"
["in REPL" racket-test]
["in *shell* using `raco test`" racket-raco-test]
"---"
["Fold All" racket-fold-all-tests]
["Unfold All" racket-unfold-all-tests])
("Eval"
["Region" racket-send-region :active (region-active-p)]
["Definition" racket-send-definition]
["Last S-Expression" racket-send-last-sexp])
("Macro Expand"
["File" racket-expand-file]
["Region" racket-expand-region :active (region-active-p)]
["Definition" racket-expand-definition]
["Last S-Expression" racket-expand-last-sexp])
["Switch to REPL" racket-repl]
("Tools"
["Profile" racket-profile]
["Check Syntax" racket-check-syntax-mode]
["Error Trace" racket-run-with-errortrace]
["Step Debug" racket-run-with-debugging])
"---"
["Comment" comment-dwim]
["Insert λ" racket-insert-lambda]
["Indent Region" indent-region]
["Cycle Paren Shapes" racket-cycle-paren-shapes]
["Align" racket-align]
["Unalign" racket-unalign]
"---"
["Visit Definition" racket-visit-definition]
["Visit Module" racket-visit-module]
["Return from Visit" racket-unvisit]
"---"
["Open Require Path" racket-open-require-path]
["Find Collection" racket-find-collection]
"---"
["Next Error or Link" next-error]
["Previous Error" previous-error]
"---"
["Tidy Requires" racket-tidy-requires]
["Trim Requires" racket-trim-requires]
["Use #lang racket/base" racket-base-requires]
"---"
["Racket Documentation" racket-doc]
["Describe" racket-describe]
["Start Faster" racket-mode-optimize-startup]
["Customize..." customize-mode]))
(defun racket--variables-imenu ()
(setq-local imenu-case-fold-search t)
(setq-local imenu-create-index-function #'racket--imenu-create-index-function))
;;;###autoload
(define-derived-mode racket-mode prog-mode
"Racket"
"Major mode for editing Racket.
\\{racket-mode-map}"
(racket--common-variables)
(racket--variables-imenu)
(hs-minor-mode t))
;;;###autoload
(progn
(add-to-list 'auto-mode-alist '("\\.rkt[dl]?\\'" . racket-mode))
(modify-coding-system-alist 'file "\\.rkt[dl]?\\'" 'utf-8)
(add-to-list 'interpreter-mode-alist '("racket" . racket-mode)))
;;;###autoload
(defun racket-mode-start-faster ()
"Compile racket-mode's .rkt files for faster startup.
racket-mode is implemented as an Emacs Lisp \"front end\" that
talks to a Racket process \"back end\". Because racket-mode is
delivered as an Emacs package instead of a Racket package,
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 `compiled/` subdirectories. As a
result, when a `racket-run' or `racket-repl' command must start
the Racket process, it will start faster.
If you run this command, _ever_, you should run it _again_ after:
- Installing an updated version of racket-mode. Otherwise, you
might lose some of the speed-up.
- Installing a new version of Racket and/or changing the value of
the variable `racket-program'. Otherwise, you might get an
error message due to the bytecode being different versions."
(interactive)
(dolist (dir (list racket--rkt-source-dir
(concat racket--rkt-source-dir "/commands/")))
(let* ((command (format "%s -l raco make -v %s"
racket-program
(expand-file-name "*.rkt" dir)))
(prompt (format "Do `%s` " command)))
(when (y-or-n-p prompt)
(async-shell-command command)))))
(provide 'racket-mode)
;;; racket-mode.el ends here

View File

@@ -0,0 +1,79 @@
;;; racket-ppss.el
;; Copyright (c) 2013-2017 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
;; Note: These doc strings are from the Parser State info topic, as of
;; Emacs 25.1.
(defun racket--ppss-paren-depth (xs)
"The depth in parentheses, counting from 0.
*Warning:* this can be negative if there are more close parens
than open parens between the parsers starting point and end
point."
(elt xs 0))
(defun racket--ppss-containing-sexp (xs)
"The character position of the start of the innermost parenthetical
grouping containing the stopping point; nil if none."
(elt xs 1))
(defun racket--ppss-last-sexp (xs)
"The character position of the start of the last complete
subexpression terminated; nil if none.
Valid only for `parse-partial-sexp' -- NOT `syntax-ppss'."
(elt xs 2))
(defun racket--ppss-string-p (xs)
"Non-nil if inside a string.
More precisely, this is the character that will terminate the
string, or t if a generic string delimiter character should
terminate it."
(elt xs 3))
(defun racket--ppss-comment-p (xs)
"t if inside a non-nestable comment (of any comment style;
*note Syntax Flags::); or the comment nesting level if inside a
comment that can be nested."
(elt xs 4))
(defun racket--ppss-quote-p (xs)
"t if the end point is just after a quote character."
(elt xs 5))
(defun racket--ppss-min-paren-depth (xs)
"The minimum parenthesis depth encountered during this scan.
Valid only for `parse-partial-sexp' -- NOT `syntax-ppss'."
(elt xs 6))
(defun racket--ppss-comment-type (xs)
"What kind of comment is active: nil if not in a comment or
in a comment of style a; 1 for a comment of style b; 2 for a
comment of style c; and syntax-table for a comment that
should be ended by a generic comment delimiter character."
(elt xs 7))
(defun racket--ppss-string/comment-start (xs)
"The string or comment start position.
While inside a comment, this is the position where the comment
began; while inside a string, this is the position where the
string began. When outside of strings and comments, this element
is nil."
(elt xs 8))
(provide 'racket-ppss)
;; racket-ppss.el ends here

View File

@@ -0,0 +1,175 @@
;;; racket-profile.el -*- lexical-binding: t -*-
;; Copyright (c) 2013-2016 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'cl-lib)
(require 'racket-edit)
(require 'racket-repl)
(defvar racket--profile-results nil)
(defvar racket--profile-sort-col 1) ;0=Calls, 1=Msec
(defvar racket--profile-show-zero nil)
(defvar racket--profile-overlay-this nil)
(defvar racket--profile-overlay-that nil)
(defun racket-profile ()
"Runs with profiling instrumentation and shows results.
Results are presented in a `racket-profile-mode' buffer, which
also lets you quickly view the source code.
You may evaluate expressions in the REPL. They are also profiled.
Use `racket--profile-refresh' to see the updated results. (In
other words a possible workflow is: `racket-profile' a .rkt file,
call one its functions in the REPL, and refresh the profile
results.)
Caveat: Only source files are instrumented. You may need to
delete compiled/*.zo files."
(interactive)
(unless (eq major-mode 'racket-mode)
(user-error "Works only in a racket-mode buffer"))
(message "Running with profiling instrumentation...")
(racket--repl-run
nil
'profile
(lambda (_what)
(message "Getting profile results...")
(racket--cmd/async
`(get-profile)
(lambda (results)
(message "")
(setq racket--profile-results results)
(setq racket--profile-sort-col 1)
(with-current-buffer (get-buffer-create "*Racket Profile*")
(racket-profile-mode)
(racket--profile-draw)
(pop-to-buffer (current-buffer))))))))
(defun racket--profile-refresh ()
(interactive)
(setq racket--profile-results (racket--cmd/await `(get-profile)))
(racket--profile-draw))
(defun racket--profile-draw ()
(read-only-mode -1)
(erase-buffer)
(setq truncate-lines t) ;let run off right edge
;; TODO: Would be nice to set the Calls and Msec column widths based
;; on max values.
(setq header-line-format
(format " %8s %6s %-20.20s %s"
(if (= 0 racket--profile-sort-col) "CALLS" "Calls")
(if (= 1 racket--profile-sort-col) "MSEC" "Msec")
"Name (inferred)"
"File"))
(insert
(mapconcat (lambda (xs)
(cl-destructuring-bind (calls msec name file beg end) xs
(propertize (format "%8d %6d %-20.20s %s"
calls msec (or name "") (or file ""))
'racket-profile-location
(and file beg end
(list file beg end)))))
(sort (cl-remove-if-not (lambda (x)
(or racket--profile-show-zero
(/= 0 (nth 0 x))
(/= 0 (nth 1 x))))
(cl-copy-list racket--profile-results))
(lambda (a b) (> (nth racket--profile-sort-col a)
(nth racket--profile-sort-col b))))
"\n"))
(read-only-mode 1)
(goto-char (point-min)))
(defun racket--profile-sort ()
"Toggle sort between Calls and Msec."
(interactive)
(setq racket--profile-sort-col (if (= racket--profile-sort-col 0) 1 0))
(racket--profile-draw))
(defun racket--profile-show-zero ()
"Toggle between showing results with zero Calls or Msec."
(interactive)
(setq racket--profile-show-zero (not racket--profile-show-zero))
(racket--profile-draw))
(defun racket--profile-visit ()
(interactive)
(let ((win (selected-window)))
(pcase (get-text-property (point) 'racket-profile-location)
(`(,file ,beg ,end)
(setq racket--profile-overlay-this
(make-overlay (save-excursion (beginning-of-line) (point))
(save-excursion (end-of-line) (point))
(current-buffer)))
(overlay-put racket--profile-overlay-this 'face 'next-error)
(find-file-other-window file)
(setq racket--profile-overlay-that (make-overlay beg end (current-buffer)))
(overlay-put racket--profile-overlay-that 'face 'next-error)
(goto-char beg)
(add-hook 'pre-command-hook #'racket--profile-remove-overlay)
(select-window win)))))
(defun racket--profile-remove-overlay ()
(delete-overlay racket--profile-overlay-this)
(delete-overlay racket--profile-overlay-that)
(remove-hook 'pre-command-hook #'racket--profile-remove-overlay))
(defun racket--profile-next ()
(interactive)
(forward-line 1)
(racket--profile-visit))
(defun racket--profile-prev ()
(interactive)
(forward-line -1)
(racket--profile-visit))
(defun racket--profile-quit ()
(interactive)
(setq racket--profile-results nil)
(quit-window))
(defvar racket-profile-mode-map
(let ((m (make-sparse-keymap)))
(set-keymap-parent m nil)
(mapc (lambda (x)
(define-key m (kbd (car x)) (cadr x)))
'(("q" racket--profile-quit)
("g" racket--profile-refresh)
("n" racket--profile-next)
("p" racket--profile-prev)
("z" racket--profile-show-zero)
("RET" racket--profile-visit)
("," racket--profile-sort)))
m)
"Keymap for Racket Profile mode.")
(define-derived-mode racket-profile-mode special-mode
"RacketProfile"
"Major mode for results of `racket-profile'.
```
\\{racket-profile-mode-map}
```
"
(setq show-trailing-whitespace nil))
(provide 'racket-profile)
;; racket-profile.el ends here

View File

@@ -0,0 +1,746 @@
;;; racket-repl.el -*- lexical-binding: t; -*-
;; Copyright (c) 2013-2018 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Image portions Copyright (C) 2012 Jose Antonio Ortega Ruiz.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'racket-custom)
(require 'racket-common)
(require 'racket-util)
(require 'comint)
(require 'compile)
(require 'easymenu)
(require 'cl-lib)
;; Don't (require 'racket-debug). Mutual dependency. Instead:
(declare-function racket--debug-send-definition "racket-debug" (beg end))
(autoload 'racket--debug-send-definition "racket-debug")
(declare-function racket--debug-on-break "racket-debug" (response))
(autoload 'racket--debug-on-break "racket-debug")
(declare-function racket--debuggable-files "racket-debug" (file-to-run))
(autoload 'racket--debuggable-files "racket-debug")
(defconst racket--repl-buffer-name/raw
"Racket REPL"
"The base buffer name, NOT surrounded in *stars*")
(defconst racket--repl-buffer-name
(concat "*" racket--repl-buffer-name/raw "*")
"The actual buffer name as created by comint-mode")
(defmacro with-racket-repl-buffer (&rest body)
"Execute the forms in BODY with `racket-repl-mode' temporarily current.
The value returned is the value of the last form in BODY --
unless no `racket-repl-mode' buffer exists, in which case no BODY
forms are evaluated and nil is returned. See also
`with-current-buffer'."
(declare (indent 0) (debug t))
(let ((repl-buffer (make-symbol "repl-buffer")))
`(let ((,repl-buffer (get-buffer racket--repl-buffer-name)))
(when ,repl-buffer
(with-current-buffer ,repl-buffer
,@body)))))
(defun racket-repl--input-filter (str)
"Don't save anything matching `racket-history-filter-regexp'."
(not (string-match racket-history-filter-regexp str)))
(defalias 'racket-repl-eval-or-newline-and-indent #'racket-repl-submit)
(defun racket-repl-submit (&optional prefix)
"Submit your input to the Racket REPL.
If the REPL is running a Racket lang whose language-info has a
'drracket:submit-predicate, that is first called to see if the
input is valid to be submitted.
With a prefix: After sending your input and a newline, also calls
`process-send-eof' -- because some langs require EOF to mark the
end of an interactive expression/statement."
(interactive "P")
(let* ((proc (get-buffer-process (current-buffer)))
(_ (unless proc (user-error "Current buffer has no process")))
(text (substring-no-properties (funcall comint-get-old-input))))
(cl-case (if racket-use-repl-submit-predicate
(racket--cmd/await `(repl-submit? ,text t))
'default)
((nil)
(user-error "Not a complete expression, according to the current lang's submit-predicate."))
((t default)
(comint-send-input)
(remove-text-properties comint-last-input-start
comint-last-input-end
'(font-lock-face comint-highlight-input))
;; Hack for datalog/lang
(when prefix (process-send-eof proc))))))
(defun racket-repl-exit (&optional quitp)
"End the Racket REPL process.
Effectively the same as entering `(exit)` at the prompt, but
works even when the module language doesn't provide any binding
for `exit`.
With a prefix, uses `comint-quit-subjob' to send a quit signal."
(interactive "P")
(if quitp
(comint-quit-subjob)
(newline)
(racket--cmd/async `(exit))))
;;;###autoload
(defun racket-repl (&optional noselect)
"Run the Racket REPL and display its buffer in some window.
If the Racket process is not already running, it is started.
If NOSELECT is not nil, does not select the REPL
window (preserves the originally selected window).
Commands that don't want the REPL to be displayed can instead use
`racket--repl-ensure-buffer-and-process'."
(interactive "P")
(racket--repl-ensure-buffer-and-process t)
(unless noselect
(select-window (get-buffer-window racket--repl-buffer-name t))))
(defconst racket--minimum-required-version "6.0"
"The minimum version of Racket required by run.rkt.
Although some functionality may require an even newer version of
Racket, run.rkt will handle that via `dynamic-require` and
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")
(defvar-local racket-user-command-line-arguments
nil
"List of command-line arguments to supply to your Racket program.
Accessible in your Racket program in the usual way -- the
parameter `current-command-line-arguments` and friends.
This is an Emacs buffer-local variable -- convenient to set as a
file local variable. For example at the end of your .rkt file:
;; Local Variables:
;; racket-user-command-line-arguments: (\"-f\" \"bar\")
;; End:
Set this way the value must be an unquoted list of strings such
as:
(\"-f\" \"bar\")
but NOT:
'(\"-f\" \"bar\")
(list \"-f\" \"bar\")
")
(defun racket--repl-live-p ()
"Does the Racket REPL buffer exist and have a live Racket process?"
(comint-check-proc racket--repl-buffer-name))
(defvar racket--repl-before-run-hook nil
"Thunks to do before each `racket--repl-run' -- except an initial run.")
(defun racket--repl-run (&optional what-to-run context-level callback)
"Do an initial or subsequent run.
WHAT-TO-RUN should be a cons of a file name to a list of
submodule symbols. Or if nil, defaults to `racket--what-to-run'.
CONTEXT-LEVEL should be a valid value for the variable
`racket-error-context', 'coverage, or 'profile. Or if nil,
defaults to the variable `racket-error-context'.
CALLBACK is supplied to `racket--repl-run' and is used as the
callback for `racket--cmd/async'; it may be nil which is
equivalent to #'ignore.
- If the REPL is _not_ live, start our backend run.rkt passing
the file to run as a command-line argument. The Emacs UI will
_not_ be blocked during this.
- If the REPL _is_ live, send a run command to the backend's TCP
server. If the server isn't live yet -- e.g. if Racket run.rkt
are still starting up -- this _will_ block the Emacs UI."
(let ((cmd (racket--repl-make-run-command (or what-to-run (racket--what-to-run))
(or context-level racket-error-context))))
(cond ((racket--repl-live-p)
(run-hook-with-args 'racket--repl-before-run-hook)
(racket--cmd/async cmd callback)
(racket--repl-show-and-move-to-end))
(t
(when callback
;; Not sure how to do a callback, here, unless maybe
;; issuing a ``prompt` command?
(message "Warning: run command callback ignored for startup run"))
(racket--repl-ensure-buffer-and-process t cmd)))))
(defun racket--repl-make-run-command (what-to-run &optional context-level)
"Form a `run` command sexpr for the backend.
WHAT-TO-RUN may be nil, meaning just a `racket/base` namespace."
(let ((context-level (or context-level racket-error-context)))
(list 'run
what-to-run
racket-memory-limit
racket-pretty-print
context-level
racket-user-command-line-arguments
(when (and what-to-run (eq context-level 'debug))
(racket--debuggable-files (car what-to-run))))))
(defvar racket--cmd-auth nil
"A value we give the Racket back-end when we launch it and when we connect.
See issue #327.")
(defun racket--repl-ensure-buffer-and-process (&optional display run-command)
"Ensure Racket REPL buffer exists and has live Racket process.
If the Racket process is not already running, it is started and
the buffer is put in `racket-repl-mode'.
Non-nil DISPLAY means `display-buffer'.
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."
(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\n" `(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))))
(unless (racket--cmd-connected-or-connecting-p)
(racket--cmd-connect-start)))
(defun racket--version ()
"Get the `racket-program' version as a string."
(with-temp-message "Checking Racket version ..."
(with-temp-buffer
(call-process racket-program nil t nil "--version")
(goto-char (point-min))
;; Welcome to Racket v6.12.
;; Welcome to Racket v7.0.0.6.
(save-match-data
(re-search-forward "[0-9]+\\(?:\\.[0-9]+\\)*")
(match-string 0)))))
(defun racket--require-version (at-least)
"Raise a `user-error' unless Racket is version AT-LEAST."
(let ((have (racket--version)))
(unless (version<= at-least have)
(user-error "racket-mode requires at least Racket version %s but you have %s"
at-least have))
t))
;;; Connection to command process
(defvar racket--cmd-proc nil
"Process when connection to the command server is established.")
(defvar racket--cmd-buf nil
"Process buffer when connection to the command server is established.")
(defvar racket--cmd-connecting-p nil)
(defvar racket--cmd-nonce->callback (make-hash-table :test 'eq)
"A hash from nonce to callback function.")
(defvar racket--cmd-nonce 0
"Increments for each command request we send.")
(defvar racket--cmd-connect-attempts 15)
(defvar racket--cmd-connect-timeout 15)
(defun racket--cmd-connected-or-connecting-p ()
(and (or racket--cmd-proc racket--cmd-connecting-p) t))
(defun racket--cmd-connect-start (&optional attempt)
"Start to connect to the Racket command process.
If already connected, disconnects first.
The command server may might not be ready to accept connections,
because Racket itself and our backend are still starting up.
After calling this, call `racket--cmd-connect-finish' to
wait for the connection to be established."
(unless (featurep 'make-network-process '(:nowait t))
(error "racket-mode needs Emacs to support the :nowait feature"))
(let ((attempt (or attempt 1)))
(when (= attempt 1)
(racket--cmd-disconnect)
(setq racket--cmd-connecting-p t))
(make-network-process
:name "racket-command"
:host "127.0.0.1"
:service racket-command-port
:nowait t
:sentinel
(lambda (proc event)
;;(message "sentinel process %S event %S attempt %s" proc event attempt)
(cond
((string-match-p "^open" event)
(setq racket--cmd-proc proc)
(setq racket--cmd-buf (generate-new-buffer
(concat " " (process-name proc))))
(buffer-disable-undo racket--cmd-buf)
(set-process-filter proc #'racket--cmd-process-filter)
(process-send-string proc racket--cmd-auth)
(message "Connected to %s process on port %s after %s attempt(s)"
proc racket-command-port attempt))
((string-match-p "^failed" event)
(delete-process proc)
(when (<= attempt racket--cmd-connect-attempts)
(run-at-time 1.0 nil
#'racket--cmd-connect-start
(1+ attempt))))
(t (racket--cmd-disconnect)))))))
(defun racket--cmd-connect-finish ()
(with-timeout (racket--cmd-connect-timeout
(setq racket--cmd-connecting-p nil)
(error "Could not connect to racket-command process on port %s"
racket-command-port))
(while (not racket--cmd-proc)
(message "Still trying to connect to racket-command process on port %s ..."
racket-command-port)
(sit-for 0.2))
(setq racket--cmd-connecting-p nil)))
(defun racket--cmd-disconnect ()
"Disconnect from the Racket command process."
(when racket--cmd-proc
;; Sentinel calls us for "deleted" event, which we ourselves will
;; trigger with the `delete-process' below. So set
;; racket--cmd-proc nil before calling `delete-process'.
(let ((proc (prog1 racket--cmd-proc (setq racket--cmd-proc nil)))
(buf (prog1 racket--cmd-buf (setq racket--cmd-buf nil))))
(delete-process proc)
(kill-buffer buf)
(clrhash racket--cmd-nonce->callback)
(setq racket--cmd-connecting-p nil))))
(defun racket--cmd-process-filter (_proc string)
(let ((buffer racket--cmd-buf))
(when (buffer-live-p buffer)
(with-current-buffer buffer
(goto-char (point-max))
(insert string)
(goto-char (point-min))
(while
(condition-case ()
(progn
(forward-sexp 1)
(let ((sexp (buffer-substring (point-min) (point))))
(delete-region (point-min) (point))
(ignore-errors
(racket--cmd-dispatch-response (read sexp))
t)))
(scan-error nil)))))))
(defun racket--cmd-dispatch-response (response)
(pcase response
(`(debug-break . ,response)
(run-at-time 0.001 nil #'racket--debug-on-break response))
(`(,nonce . ,response)
(let ((callback (gethash nonce racket--cmd-nonce->callback)))
(when callback
(remhash nonce racket--cmd-nonce->callback)
(run-at-time 0.001 nil callback response))))
(_ nil)))
(defun racket--cmd/async-raw (command-sexpr &optional callback)
"Send COMMAND-SEXPR and return. Later call CALLBACK with the response sexp.
If CALLBACK is not supplied or nil, defaults to `ignore'.
Otherwise CALLBACK is called after the command server returns a
response. Because command responses are obtained from the dynamic
extent of a `set-process-filter' proc -- which may have
limitations on what it can or should do -- CALLBACK is not called
immediately but instead using `run-at-time' with a very small
delay.
Important: Do not assume that `current-buffer' is the same when
CALLBACK is called, as it was when the command was sent. If you
need to do something to do that original buffer, save the
`current-buffer' in a `let' and use it in a `with-current-buffer'
form."
(racket--repl-ensure-buffer-and-process nil)
(racket--cmd-connect-finish)
(cl-incf racket--cmd-nonce)
(when (and callback
(not (equal callback #'ignore)))
(puthash racket--cmd-nonce callback racket--cmd-nonce->callback))
(process-send-string racket--cmd-proc
(format "%S\n" (cons racket--cmd-nonce
command-sexpr))))
(defun racket--cmd/async (command-sexpr &optional callback)
"You probably want to use this instead of `racket--cmd/async-raw'.
CALLBACK is only called for 'ok responses, with (ok v ...)
unwrapped to (v ...).
'error responses are handled here. Note: We use `message' not
`error' here because:
1. It would show \"error running timer:\" which, although true,
is confusing or at best N/A for end users.
2. More simply, we don't need to escape any call stack, we only
need to ... not call the callback!
The original value of `current-buffer' is temporarily restored
during CALLBACK, because neglecting to do so is a likely
mistake."
(let ((buf (current-buffer)))
(racket--cmd/async-raw
command-sexpr
(if callback
(lambda (response)
(pcase response
(`(ok ,v) (with-current-buffer buf (funcall callback v)))
(`(error ,m) (message "%s" m))
(v (message "Unknown command response: %S" v))))
#'ignore))))
(defun racket--cmd/await (command-sexpr)
"Send COMMAND-SEXPR. Await and return an 'ok response value, or raise `error'."
(let* ((awaiting 'RACKET-REPL-AWAITING)
(response awaiting))
(racket--cmd/async-raw command-sexpr
(lambda (v) (setq response v)))
(with-timeout (racket-command-timeout
(error "racket-command process timeout"))
(while (eq response awaiting)
(accept-process-output nil 0.001))
(pcase response
(`(ok ,v) v)
(`(error ,m) (error "%s" m))
(v (error "Unknown command response: %S" v))))))
;;; Misc
(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."
(when (comint-check-proc racket--repl-buffer-name)
(pcase (racket--cmd/await `(path+md5))
(`(,(and (pred stringp) path) . ,_md5)
(cl-case system-type
(windows-nt (subst-char-in-string ?\\ ?/ path))
(otherwise path)))
(_ nil))))
(defun racket--in-repl-or-its-file-p ()
"Is current-buffer `racket-repl-mode' or buffer for file active in it?"
(or (eq (current-buffer)
(get-buffer racket--repl-buffer-name))
(string-equal (racket--buffer-file-name)
(racket-repl-file-name))))
(defun racket-repl-switch-to-edit ()
"Switch to the window for the buffer of the file running in the REPL.
If no buffer is visting the file, `find-file' it in `other-window'.
If the REPL is running no file -- if the prompt is `>` -- use the
most recent `racket-mode' buffer, if any."
(interactive)
(pcase (racket-repl-file-name)
(`() (let ((buffer (racket--most-recent-racket-mode-buffer)))
(unless buffer
(user-error "There are no racket-mode buffers"))
(pop-to-buffer buffer t)))
(path (let ((buffer (find-buffer-visiting path)))
(if buffer
(pop-to-buffer buffer t)
(other-window 1)
(find-file path))))))
(defun racket--most-recent-racket-mode-buffer ()
(cl-some (lambda (b)
(with-current-buffer b
(and (eq major-mode 'racket-mode) b)))
(buffer-list)))
;;; send to REPL
(defun racket--send-region-to-repl (start end)
"Internal function to send the region to the Racket REPL.
Before sending the region, calls `racket-repl' and
`racket--repl-forget-errors'. Also inserts a ?\n at the process
mark so that output goes on a fresh line, not on the same line as
the prompt.
Afterwards call `racket--repl-show-and-move-to-end'."
(when (and start end)
(racket-repl t)
(racket--repl-forget-errors)
(let ((proc (get-buffer-process racket--repl-buffer-name)))
(with-racket-repl-buffer
(save-excursion
(goto-char (process-mark proc))
(insert ?\n)
(set-marker (process-mark proc) (point))))
(comint-send-region proc start end)
(comint-send-string proc "\n"))
(racket--repl-show-and-move-to-end)))
(defun racket-send-region (start end)
"Send the current region (if any) to the Racket REPL."
(interactive "r")
(unless (region-active-p)
(user-error "No region"))
(racket--send-region-to-repl start end))
(defun racket-send-definition (&optional prefix)
"Send the current definition to the Racket REPL."
(interactive "P")
(save-excursion
(end-of-defun)
(let ((end (point)))
(beginning-of-defun)
(if prefix
(racket--debug-send-definition (point) end)
(racket--send-region-to-repl (point) end)))))
(defun racket-send-last-sexp ()
"Send the previous sexp to the Racket REPL.
When the previous sexp is a sexp comment the sexp itself is sent,
without the #; prefix."
(interactive)
(racket--send-region-to-repl (racket--repl-last-sexp-start)
(point)))
(defun racket-eval-last-sexp ()
"Eval the previous sexp asynchronously and `message' the result."
(interactive)
(racket--cmd/async
`(eval
,(buffer-substring-no-properties (racket--repl-last-sexp-start)
(point)))
(lambda (v)
(message "%s" v))))
(defun racket--repl-last-sexp-start ()
(save-excursion
(condition-case ()
(progn
(backward-sexp)
(if (save-match-data (looking-at "#;"))
(+ (point) 2)
(point)))
(scan-error (user-error "There isn't a complete s-expression before point")))))
(defun racket--repl-forget-errors ()
"Forget existing errors in the REPL.
Although they remain clickable they will be ignored by
`next-error' and `previous-error'"
(with-racket-repl-buffer
(compilation-forget-errors)
;; `compilation-forget-errors' may have just set
;; `compilation-messages-start' to a marker at position 1. But in
;; that case process output (including error messages) will be
;; inserted ABOVE the marker, in which case `next-error' won't see
;; them. Instead use a non-marker position like 1 or use nil.
(when (and (markerp compilation-messages-start)
(equal (marker-position compilation-messages-start) 1)
(equal (marker-buffer compilation-messages-start) (current-buffer)))
(setq compilation-messages-start nil))))
(add-hook 'racket--repl-before-run-hook #'racket--repl-forget-errors)
(defun racket--repl-show-and-move-to-end ()
"Make the Racket REPL visible, and move point to end.
Keep original window selected."
(display-buffer racket--repl-buffer-name)
(save-selected-window
(select-window (get-buffer-window racket--repl-buffer-name t))
(comint-show-maximum-output)))
;;; Inline images in REPL
(defvar racket-image-cache-dir nil)
(defun racket-repl--list-image-cache ()
"List all the images in the image cache."
(and racket-image-cache-dir
(file-directory-p racket-image-cache-dir)
(let ((files (directory-files-and-attributes
racket-image-cache-dir t "racket-image-[0-9]*.png")))
(mapcar #'car
(sort files (lambda (a b)
(< (float-time (nth 6 a))
(float-time (nth 6 b)))))))))
(defun racket-repl--clean-image-cache ()
"Clean all except for the last `racket-images-keep-last'
images in 'racket-image-cache-dir'."
(interactive)
(dolist (file (butlast (racket-repl--list-image-cache)
racket-images-keep-last))
(delete-file file)))
(defun racket-repl--replace-images ()
"Replace all image patterns with actual images"
(with-silent-modifications
(save-excursion
(goto-char (point-min))
(while (re-search-forward "\"#<Image: \\(.+racket-image-.+\\.png\\)>\"" nil t)
;; can't pass a filename to create-image because emacs might
;; not display it before it gets deleted (race condition)
(let* ((file (match-string 1))
(begin (match-beginning 0))
(end (match-end 0)))
(delete-region begin end)
(goto-char begin)
(if (and racket-images-inline (display-images-p))
(insert-image (create-image file) "[image]")
(goto-char begin)
(insert "[image] ; use M-x racket-view-last-image to view"))
(setq racket-image-cache-dir (file-name-directory file))
(racket-repl--clean-image-cache))))))
(defun racket-view-last-image (n)
"Open the last displayed image using `racket-images-system-viewer'.
With prefix arg, open the N-th last shown image."
(interactive "p")
(let ((images (reverse (racket-repl--list-image-cache))))
(if (>= (length images) n)
(start-process "Racket image view"
nil
racket-images-system-viewer
(nth (- n 1) images))
(error "There aren't %d recent images" n))))
(defun racket-repl--output-filter (_txt)
(racket-repl--replace-images))
;;; racket-repl-mode
(defvar racket-repl-mode-map
(racket--easy-keymap-define
'(("C-m" racket-repl-submit)
("C-j" newline-and-indent)
("TAB" indent-for-tab-command)
("C-M-u" racket-backward-up-list)
("C-M-q" prog-indent-sexp)
("C-a" comint-bol)
("C-w" comint-kill-region)
("[C-S-backspace]" comint-kill-whole-line)
("[" racket-smart-open-bracket)
(")" racket-insert-closing)
("]" racket-insert-closing)
("}" racket-insert-closing)
("C-c C-e f" racket-expand-file)
("C-c C-e x" racket-expand-definition)
("C-c C-e e" racket-expand-last-sexp)
("C-c C-e r" racket-expand-region)
("M-C-y" racket-insert-lambda)
("C-c C-d" racket-doc)
("C-c C-." racket-describe)
("M-." racket-visit-definition)
("C-M-." racket-visit-module)
("M-," racket-unvisit)
("C-c C-z" racket-repl-switch-to-edit)
("C-c C-l" racket-logger)
("C-c C-\\" racket-repl-exit)))
"Keymap for Racket REPL mode.")
(easy-menu-define racket-repl-mode-menu racket-repl-mode-map
"Menu for Racket REPL mode."
'("Racket"
["Break" comint-interrupt-subjob]
["Exit" racket-repl-exit]
"---"
["Insert Lambda" racket-insert-lambda] ;λ in string breaks menu
["Indent Region" indent-region]
["Cycle Paren Shapes" racket-cycle-paren-shapes]
("Macro Expand"
["File" racket-expand-file]
["Region" racket-expand-region :active (region-active-p)]
["Definition" racket-expand-definition]
["Last S-Expression" racket-expand-last-sexp])
"---"
["Visit Definition" racket-visit-definition]
["Visit Module" racket-visit-module]
["Return from Visit" racket-unvisit]
"---"
["Racket Documentation" racket-doc]
["Describe" racket-describe]
"---"
["Switch to Edit Buffer" racket-repl-switch-to-edit]))
(define-derived-mode racket-repl-mode comint-mode "Racket-REPL"
"Major mode for Racket REPL.
\\{racket-repl-mode-map}"
(racket--common-variables)
(setq-local comint-use-prompt-regexp nil)
(setq-local comint-prompt-read-only t)
(setq-local comint-scroll-show-maximum-output nil) ;t slow for big outputs
(setq-local mode-line-process nil)
(setq-local comint-input-filter #'racket-repl--input-filter)
(add-hook 'comint-output-filter-functions #'racket-repl--output-filter nil t)
(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))))
(provide 'racket-repl)
;; racket-repl.el ends here

View File

@@ -0,0 +1,202 @@
;;; racket-stepper.el -*- lexical-binding: t; -*-
;; Copyright (c) 2018 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'easymenu)
(require 'rx)
(require 'racket-common)
(require 'racket-custom)
(require 'racket-repl)
(require 'racket-util)
;; Need to define this before racket-stepper-mode
(defvar racket-stepper-mode-map
(racket--easy-keymap-define
'((("C-m") racket-stepper-step)
(("n" "j") racket-stepper-next-item)
(("p" "k") racket-stepper-previous-item))))
(easy-menu-define racket-stepper-mode-menu racket-stepper-mode-map
"Menu for Racket stepper mode."
'("Racket"
["Step" racket-stepper-step]
["Next" racket-stepper-next-item]
["Previous" racket-stepper-previous-item]))
(defconst racket-stepper-font-lock-keywords
(eval-when-compile
`((,(rx bol "! " (zero-or-more any) eol) . font-lock-warning-face)
(,(rx bol alphanumeric (zero-or-more any) eol) . font-lock-function-name-face)
(,(rx bol "@@" (zero-or-more any) "@@" eol) . font-lock-comment-face)
(,(rx bol "-" (zero-or-more any) eol) . 'diff-removed)
(,(rx bol "+" (zero-or-more any) eol) . 'diff-added))))
(define-derived-mode racket-stepper-mode special-mode "Racket-Stepper"
"Major mode for Racket stepper output.
Used by the commands `racket-expand-file',
`racket-expand-definition', `racket-expand-region', and
`racket-expand-last-sexp'.
\\<racket-stepper-mode-map>
```
\\{racket-stepper-mode-map}
```
"
(setq header-line-format
"Press RET to step. C-h m to see help.")
(setq-local font-lock-defaults
(list racket-stepper-font-lock-keywords
t))) ;keywords only -- not strings/comments
(defvar racket-stepper--buffer-name "*Racket Stepper*")
;;; commands
(defun racket-expand-file (&optional into-base)
"Expand the `racket-mode' buffer's file in `racket-stepper-mode'.
Uses the `macro-debugger` package to do the expansion.
You do _not_ need to `racket-run' the file first; the namespace
active in the REPL is not used.
If the file is non-trivial and/or is not compiled to a .zo
bytecode file, then it might take many seconds before the
original form is displayed and you can start stepping.
With a prefix, also expands syntax from racket/base -- which can
result in very many expansion steps."
(interactive "P")
(unless (eq major-mode 'racket-mode)
(user-error "Only works in racket-mode buffer"))
(racket--save-if-changed)
(racket-stepper--start 'file (racket--buffer-file-name) into-base))
(defun racket-expand-region (start end &optional into-base)
"Expand the active region using `racket-stepper-mode'.
Uses Racket's `expand-once` in the namespace from the most recent
`racket-run'."
(interactive "rP")
(unless (region-active-p)
(user-error "No region"))
(racket-stepper--expand-text into-base
(lambda ()
(cons start end))))
(defun racket-expand-definition (&optional into-base)
"Expand the definition around point using `racket-stepper-mode'.
Uses Racket's `expand-once` in the namespace from the most recent
`racket-run'."
(interactive "P")
(racket-stepper--expand-text into-base
(lambda ()
(save-excursion
(cons (progn (beginning-of-defun) (point))
(progn (end-of-defun) (point)))))))
(defun racket-expand-last-sexp (&optional into-base)
"Expand the sexp before point using `racket-stepper-mode'.
Uses Racket's `expand-once` in the namespace from the most recent
`racket-run'."
(interactive "P")
(racket-stepper--expand-text into-base
(lambda ()
(save-excursion
(cons (progn (backward-sexp) (point))
(progn (forward-sexp) (point)))))))
(defun racket-stepper--expand-text (prefix get-region)
(pcase (funcall get-region)
(`(,beg . ,end)
(racket-stepper--start 'expr
(buffer-substring-no-properties beg end)
prefix))))
(defun racket-stepper--start (which str into-base)
"Ensure buffer and issue initial command.
WHICH should be 'expr or 'file.
STR should be the expression or pathname.
INTO-BASE is treated as a raw prefix arg and converted to boolp."
;; Create buffer if necessary
(unless (get-buffer racket-stepper--buffer-name)
(with-current-buffer (get-buffer-create racket-stepper--buffer-name)
(racket-stepper-mode)))
;; Give it a window if necessary
(unless (get-buffer-window racket-stepper--buffer-name)
(pop-to-buffer (get-buffer racket-stepper--buffer-name)))
;; Select the stepper window and insert
(select-window (get-buffer-window racket-stepper--buffer-name))
(let ((inhibit-read-only t))
(delete-region (point-min) (point-max))
(insert "Starting macro expansion stepper... please wait...\n"))
(racket--cmd/async `(macro-stepper (,which . ,str)
,(and into-base t))
#'racket-stepper--insert))
(defun racket-stepper--insert (step)
(with-current-buffer racket-stepper--buffer-name
(let ((inhibit-read-only t))
(goto-char (point-max))
(pcase step
(`(original . ,text)
(delete-region (point-min) (point-max))
(insert "Original\n" text "\n" "\n"))
(`(final . ,text) (insert "Final\n" text "\n"))
(`(,label . ,diff) (insert label "\n" diff "\n")))
(racket-stepper-previous-item)
(when (equal (selected-window) (get-buffer-window (current-buffer)))
(recenter)))))
(defun racket-stepper-step ()
(interactive)
(racket--cmd/async `(macro-stepper/next)
#'racket-stepper--insert))
(defconst racket-stepper--item-rx
(rx bol alphanumeric (zero-or-more any) eol))
(defun racket-stepper-next-item (&optional count)
"Move point N items forward.
An \"item\" is a line starting with a log level in brackets.
Interactively, N is the numeric prefix argument.
If N is omitted or nil, move point 1 item forward."
(interactive "P")
(forward-char 1)
(if (re-search-forward racket-stepper--item-rx nil t count)
(beginning-of-line)
(backward-char 1)))
(defun racket-stepper-previous-item (&optional count)
"Move point N items backward.
An \"item\" is a line starting with a log level in brackets.
Interactively, N is the numeric prefix argument.
If N is omitted or nil, move point 1 item backward."
(interactive "P")
(re-search-backward racket-stepper--item-rx nil t count))
(provide 'racket-stepper)
;;; racket-stepper.el ends here

View File

@@ -0,0 +1,224 @@
;;; racket-unicode-input-method.el --- Racket Unicode helper functions
;; Copyright (c) 2015-2016 by Greg Hendershott
;; Portions Copyright (c) 2010-2011 by Roel van Dijk
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
;; Following the example of haskell-unicode-input-method.el
(require 'quail)
;;;###autoload
(defun racket-unicode-input-method-enable ()
"Set input method to `racket-unicode`.
The `racket-unicode` input method lets you easily type various
Unicode symbols that might be useful when writing Racket
code.
To automatically enable the `racket-unicode` input method in
`racket-mode` buffers use `M-x customize-variable <RET>
racket-mode-hook` or put the following code in your Emacs init
file:
(add-hook 'racket-mode-hook #'racket-unicode-input-method-enable)
Likewise for `racket-repl-mode` buffers:
(add-hook 'racket-repl-mode-hook #'racket-unicode-input-method-enable)
To temporarily enable this input method for a single buffer you
can use `M-x racket-unicode-input-method-enable`.
Use `C-\\` to toggle the input method.
When the `racket-unicode` input method is active, you can for
example type `All` and it is immediately replaced with ``. A few
other examples:
omega ω
x_1 x₁
x^1 x¹
|A| 𝔸
test-->>E test-->>∃ (racket/redex)
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
can turn it off by setting `input-method-highlight-flag' to nil via
`M-x customize-variable`."
(interactive)
(set-input-method "racket-unicode"))
(quail-define-package
"racket-unicode" ;name
"UTF-8" ;language
"λ" ;title (in mode line)
t ;guidance
"Racket Unicode input method." ;docstring
nil ;translation-keys
nil ;forget-last-selection
nil ;deterministic
nil ;kbd-translate
nil ;show-layout
nil ;create-decode-map
nil ;maximum-shortest
nil ;overlay-plist
nil ;update-translation-function
nil ;conversion-keys
t) ;simple
(quail-define-rules
;; Typed Racket
("All" [""])
("Union" ["U"])
("Intersection" [""])
;; These would be nice except no such aliases provided by racket/contract.
;; ("->" ["→"])
;; ("case->" ["case→"])
;; Redex
("test-->>E" ["test-->>∃"])
;; Greek letters
("alpha " ["α"])
("Alpha " ["Α"])
("beta " ["β"])
("Beta " ["Β"])
("gamma " ["γ"])
("Gamma " ["Γ"])
("delta " ["δ"])
("Delta " ["Δ"])
("epsilon " ["ε"])
("Epsilon " ["Ε"])
("zeta " ["ζ"])
("Zeta " ["Ζ"])
("eta " ["η"])
("Eta " ["Η"])
("theta " ["θ"])
("Theta " ["Θ"])
("iota " ["ι"])
("Iota " ["Ι"])
("kappa " ["κ"])
("Kappa " ["Κ"])
("lambda " ["λ"])
("Lambda " ["Λ"])
("lamda " ["λ"])
("Lamda " ["Λ"])
("mu " ["μ"])
("Mu " ["Μ"])
("nu " ["ν"])
("Nu " ["Ν"])
("xi " ["ξ"])
("Xi " ["Ξ"])
("omicron " ["ο"])
("Omicron " ["Ο"])
("pi " ["π"])
("Pi " ["Π"])
("rho " ["ρ"])
("Rho " ["Ρ"])
("sigma " ["σ"])
("Sigma " ["Σ"])
("tau " ["τ"])
("Tau " ["Τ"])
("upsilon " ["υ"])
("Upsilon " ["Υ"])
("phi " ["φ"])
("Phi " ["Φ"])
("chi " ["χ"])
("Chi " ["Χ"])
("psi " ["ψ"])
("Psi " ["Ψ"])
("omega " ["ω"])
("Omega " ["Ω"])
("digamma " ["ϝ"])
("Digamma " ["Ϝ"])
("san " ["ϻ"])
("San " ["Ϻ"])
("qoppa " ["ϙ"])
("Qoppa " ["Ϙ"])
("sampi " ["ϡ"])
("Sampi " ["Ϡ"])
("stigma " ["ϛ"])
("Stigma " ["Ϛ"])
("heta " ["ͱ"])
("Heta " ["Ͱ"])
("sho " ["ϸ"])
("Sho " ["Ϸ"])
;; Double-struck letters
("|A|" ["𝔸"])
("|B|" ["𝔹"])
("|C|" [""])
("|D|" ["𝔻"])
("|E|" ["𝔼"])
("|F|" ["𝔽"])
("|G|" ["𝔾"])
("|H|" [""])
("|I|" ["𝕀"])
("|J|" ["𝕁"])
("|K|" ["𝕂"])
("|L|" ["𝕃"])
("|M|" ["𝕄"])
("|N|" [""])
("|O|" ["𝕆"])
("|P|" [""])
("|Q|" [""])
("|R|" [""])
("|S|" ["𝕊"])
("|T|" ["𝕋"])
("|U|" ["𝕌"])
("|V|" ["𝕍"])
("|W|" ["𝕎"])
("|X|" ["𝕏"])
("|Y|" ["𝕐"])
("|Z|" [""])
("|gamma|" [""])
("|Gamma|" [""])
("|pi|" [""])
("|Pi|" [""])
;; Quantifiers
("forall" [""])
("exists" [""])
;; Numeric subscripts
("_0 " [""])
("_1 " [""])
("_2 " [""])
("_3 " [""])
("_4 " [""])
("_5 " [""])
("_6 " [""])
("_7 " [""])
("_8 " [""])
("_9 " [""])
;; Numeric superscripts
("^0 " [""])
("^1 " ["¹"])
("^2 " ["²"])
("^3 " ["³"])
("^4 " [""])
("^5 " [""])
("^6 " [""])
("^7 " [""])
("^8 " [""])
("^9 " [""]))
(provide 'racket-unicode-input-method)
;;; racket-unicode-input-method.el ends here

View File

@@ -0,0 +1,123 @@
;;; racket-util.el
;; Copyright (c) 2013-2016 by Greg Hendershott.
;; Portions Copyright (C) 1985-1986, 1999-2013 Free Software Foundation, Inc.
;; Author: Greg Hendershott
;; URL: https://github.com/greghendershott/racket-mode
;; License:
;; This is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version. This is distributed in the hope that it will be
;; useful, but without any warranty; without even the implied warranty
;; of merchantability or fitness for a particular purpose. See the GNU
;; General Public License for more details. See
;; http://www.gnu.org/licenses/ for details.
(require 'racket-custom)
(defun racket--easy-keymap-define (spec)
"Make a sparse keymap with the bindings in SPEC.
This is simply a way to DRY many calls to `define-key'.
SPEC is
(list (list key-or-keys fn) ...)
where key-or-keys is either a string given to `kbd', or (for the
case where multiple keys bind to the same command) a list of such
strings."
(let ((m (make-sparse-keymap)))
(mapc (lambda (x)
(let ((keys (if (listp (car x))
(car x)
(list (car x))))
(fn (cadr x)))
(mapc (lambda (key)
(define-key m (kbd key) fn))
keys)))
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--save-if-changed ()
(unless (eq major-mode 'racket-mode)
(user-error "Current buffer is not a racket-mode buffer"))
(when (or (buffer-modified-p)
(and (racket--buffer-file-name)
(not (file-exists-p (racket--buffer-file-name)))))
(save-buffer)))
(add-hook 'racket--repl-before-run-hook #'racket--save-if-changed)
(defun racket--mode-edits-racket-p ()
"Return non-nil if the current major mode is one that edits Racket code.
This is intended to be used with commands that customize their
behavior based on whether they are editing Racket, such as
Paredit bindings, without each of those commands needing to have
a list of all modes in which Racket is edited."
(memq major-mode '(racket-mode racket-repl-mode)))
(defun racket--take-while (xs pred)
(pcase xs
(`() `())
(`(,x . ,xs) (if (funcall pred x)
(cons x (racket--take-while xs pred))
`()))))
(defun racket--thing-at-point (thing &optional no-properties)
"Like `thing-at-point' in Emacs 25+: Optional arg NO-PROPERTIES.
Someday when we no longer support Emacs 24, we could delete this
and callers just use `thing-at-point'."
(pcase (thing-at-point thing)
((and (guard no-properties)
(pred stringp)
str)
(substring-no-properties str))
(v v)))
(defconst racket--el-source-dir
(file-name-directory (or load-file-name (racket--buffer-file-name)))
"Path to dir of our Emacs Lisp source files.
When installed as a package, this can be found from the variable
`load-file-name'. When developing interactively, get it from the
.el buffer file name.")
(defconst racket--rkt-source-dir
(expand-file-name "./racket/" racket--el-source-dir)
"Path to dir of our Racket source files. ")
;;; trace
(defvar racket--trace-enable nil)
(defun racket--trace (p &optional s retval)
(when racket--trace-enable
(let ((b (get-buffer-create "*Racket Trace*"))
(deactivate-mark deactivate-mark))
(save-excursion
(save-restriction
(with-current-buffer b
(insert p ": " (if (stringp s) s (format "%S" s)) "\n"))))))
retval)
(defun racket--toggle-trace (arg)
(interactive "P")
(setq racket--trace-enable (or arg (not racket--trace-enable)))
(if racket--trace-enable
(message "Racket trace on")
(message "Racket trace off"))
(let ((b (get-buffer-create "*Racket Trace*")))
(pop-to-buffer b t t)
(setq truncate-lines t)))
(provide 'racket-util)
;; racket-util.el ends here

View File

@@ -0,0 +1,72 @@
#lang racket/base
(require racket/contract
racket/match
racket/set
"mod.rkt")
(provide message-to-main-thread-channel
(struct-out message-to-main-thread)
(struct-out load-gui)
(struct-out rerun)
rerun-default
context-level?
instrument-level?
profile/coverage-level?
debug-level?)
;;; Definitions for the context-level member of rerun
(define profile/coverage-levels
;; "sibling" levels that need instrument plus...
'(profile ;profiling-enabled
coverage)) ;execute-counts-enabled
(define instrument-levels
`(high ;compile-context-preservation-enabled #t + instrument
,@profile/coverage-levels))
(define context-levels
`(low ;compile-context-preservation-enabled #f
medium ;compile-context-preservation-enabled #t
,@instrument-levels
debug))
(define-syntax-rule (memq? x xs)
(and (memq x xs) #t))
(define (context-level? v) (memq? v context-levels))
(define (instrument-level? v) (memq? v instrument-levels))
(define (profile/coverage-level? v) (memq? v profile/coverage-levels))
(define (debug-level? v) (eq? v 'debug))
;;; Messages to the main thread via a channel
(define message-to-main-thread-channel (make-channel))
(define-struct/contract message-to-main-thread ())
(define-struct/contract (load-gui message-to-main-thread)
([in-repl? boolean?]))
(define-struct/contract (rerun message-to-main-thread)
([maybe-mod (or/c #f mod?)]
[memory-limit exact-nonnegative-integer?] ;0 = no limit
[pretty-print? boolean?]
[context-level context-level?]
;; The following contract is the weaker `vector?` instead of
;; `(vectorof string?)` because latter fails under Racket 6.0 and
;; 6.1 when the value is accessed from the struct and passed to
;; `current-command-line-arguments`. WAT.
[cmd-line-args vector?]
[debug-files (set/c path?)]
[ready-thunk (-> any/c)]))
(define rerun-default (rerun #f
0
#f
'low
#()
(set)
void))

View File

@@ -0,0 +1,232 @@
#lang racket/base
(require racket/contract
racket/format
racket/function
racket/lazy-require
racket/match
racket/set
racket/tcp
"channel.rkt"
"debug.rkt"
"elisp.rkt"
"interactions.rkt"
"md5.rkt"
"mod.rkt"
"util.rkt")
(lazy-require
["commands/check-syntax.rkt" (check-syntax)]
["commands/coverage.rkt" (get-uncovered)]
["commands/describe.rkt" (describe type)]
["commands/find-module.rkt" (find-module)]
["commands/help.rkt" (doc)]
["commands/macro.rkt" (macro-stepper macro-stepper/next)]
["commands/profile.rkt" (get-profile)]
["commands/requires.rkt" (requires/tidy requires/trim requires/base)]
["find.rkt" (find-definition)])
(provide start-command-server
attach-command-server
make-prompt-read)
(define drracket:submit-predicate/c (-> input-port? boolean? boolean?))
(define-struct/contract context
([ns namespace?]
[maybe-mod (or/c #f mod?)]
[md5 string?]
[submit-pred (or/c #f drracket:submit-predicate/c)]))
(define command-server-context (context (make-base-namespace) #f "" #f))
(define/contract (attach-command-server ns maybe-mod)
(-> namespace? (or/c #f mod?) any)
(set-debug-repl-namespace! ns)
(set! command-server-context
(context ns
maybe-mod
(maybe-mod->md5 maybe-mod)
(get-repl-submit-predicate maybe-mod))))
(define (maybe-mod->md5 m)
(define-values (dir file _) (maybe-mod->dir/file/rmp m))
(if (and dir file)
(file->md5 (build-path dir file))
""))
;; <https://docs.racket-lang.org/tools/lang-languages-customization.html#(part._.R.E.P.L_.Submit_.Predicate)>
(define/contract (get-repl-submit-predicate m)
(-> (or/c #f mod?) (or/c #f drracket:submit-predicate/c))
(define-values (dir file rmp) (maybe-mod->dir/file/rmp m))
(define path (and dir file (build-path dir file)))
(and path rmp
(or (with-handlers ([exn:fail? (λ _ #f)])
(match (with-input-from-file (build-path dir file) read-language)
[(? procedure? get-info)
(match (get-info 'drracket:submit-predicate #f)
[#f #f]
[v v])]
[_ #f]))
(with-handlers ([exn:fail? (λ _ #f)])
(match (module->language-info rmp #t)
[(vector mp name val)
(define get-info ((dynamic-require mp name) val))
(get-info 'drracket:submit-predicate #f)]
[_ #f])))))
;; The command server accepts a single TCP connection at a time.
;;
;; Immediately after connecting, the client must send us exactly the
;; same '(accept ,random-value) value that it gave us as a command
;; line argument when it started us. Else we exit. See issue #327.
;;
;; Normally Emacs will make only one connection to us, ever. If the
;; user exits the REPL, then our entire Racket process exits. (Just in
;; case, we have an accept-a-connection loop below. It handles any
;; exns -- like exn:network -- not handled during command processing.
;; It uses a custodian to clean up.)
;;
;; Command requests and responses "on the wire" are a subset of valid
;; Emacs Lisp s-expressions: See elisp-read and elisp-write.
;;
;; Command requests are (nonce command param ...).
;;
;; A thread is spun off to handle each request, so that a long-running
;; command won't block others. The nonce supplied with the request is
;; returned with the response, so that the client can match the
;; response with the request. The nonce needn't be random, just
;; unique; an increasing integer is fine.
;;
;; Command responses are either (nonce 'ok sexp ...+) or (nonce 'error
;; "message"). The latter normally can and should be displayed to the
;; user in Emacs via error or message. We handle exn:fail? up here;
;; generally we're fine letting Racket exceptions percolate up and be
;; shown to the user
(define (start-command-server port launch-token)
(thread
(thunk
(define listener (tcp-listen port 4 #t "127.0.0.1"))
(let accept-a-connection ()
(define custodian (make-custodian))
(parameterize ([current-custodian custodian])
(with-handlers ([exn:fail? void]) ;just disconnect; see #327
(define-values (in out) (tcp-accept listener))
(unless (or (not launch-token)
(equal? launch-token (elisp-read in)))
(display-commented "Authorization failed; exiting")
(exit 1)) ;see #327
(define response-channel (make-channel))
(define ((do-command/put-response nonce sexp))
(channel-put
response-channel
(cons
nonce
(with-handlers ([exn:fail? (λ (e) `(error ,(exn-message e)))])
(parameterize ([current-namespace
(context-ns command-server-context)])
`(ok ,(command sexp command-server-context)))))))
(define (get/write-response)
(elisp-writeln (sync response-channel
debug-notify-channel)
out)
(flush-output out)
(get/write-response))
;; With all the pieces defined, let's go:
(thread get/write-response)
(let read-a-command ()
(match (elisp-read in)
[(cons nonce sexp) (thread (do-command/put-response nonce sexp))
(read-a-command)]
[(? eof-object?) (void)])))
(custodian-shutdown-all custodian))
(accept-a-connection))))
(void))
(define/contract ((make-prompt-read m))
(-> (or/c #f mod?) (-> any))
(begin0 (get-interaction (maybe-mod->prompt-string m))
(next-break 'all))) ;let debug-instrumented code break again
(define/contract (command sexpr the-context)
(-> pair? context? any/c)
(match-define (context _ns maybe-mod md5 submit-pred) the-context)
(define-values (dir file mod-path) (maybe-mod->dir/file/rmp maybe-mod))
(define path (and dir file (build-path dir file)))
;; Note: Intentionally no "else" match clause -- let caller handle
;; exn and supply a consistent exn response format.
(match sexpr
[`(run ,what ,mem ,pp? ,ctx ,args ,dbg) (run what mem pp? ctx args dbg)]
[`(path+md5) (cons (or path 'top) md5)]
[`(syms) (syms)]
[`(def ,str) (find-definition str)]
[`(mod ,sym) (find-module sym maybe-mod)]
[`(describe ,str) (describe str)]
[`(doc ,str) (doc str)]
[`(type ,v) (type v)]
[`(macro-stepper ,str ,into-base?) (macro-stepper str into-base?)]
[`(macro-stepper/next) (macro-stepper/next)]
[`(requires/tidy ,reqs) (requires/tidy reqs)]
[`(requires/trim ,path-str ,reqs) (requires/trim path-str reqs)]
[`(requires/base ,path-str ,reqs) (requires/base path-str reqs)]
[`(find-collection ,str) (find-collection str)]
[`(get-profile) (get-profile)]
[`(get-uncovered) (get-uncovered path)]
[`(check-syntax ,path-str) (check-syntax path-str)]
[`(eval ,v) (eval-command v)]
[`(repl-submit? ,str ,eos?) (repl-submit? submit-pred str eos?)]
[`(debug-eval ,src ,l ,c ,p ,code) (debug-eval src l c p code)]
[`(debug-resume ,v) (debug-resume v)]
[`(debug-disable) (debug-disable)]
[`(exit) (exit)]))
;;; A few commands defined here
(define/contract (run what mem pp ctx args dbgs)
(-> list? number? elisp-bool/c context-level? list? (listof path-string?)
list?)
(define ready-channel (make-channel))
(channel-put message-to-main-thread-channel
(rerun (->mod/existing what)
mem
(as-racket-bool pp)
ctx
(list->vector args)
(list->set (map string->path dbgs))
(λ () (channel-put ready-channel what))))
;; Waiting for this allows the command response to be used as the
;; all-clear for additional commands that need the module load to be
;; done and entering a REPL for that module. For example, to compose
;; run with get-profile or get-uncovered.
(sync ready-channel))
(define/contract (repl-submit? submit-pred text eos)
(-> (or/c #f drracket:submit-predicate/c) string? elisp-bool/c (or/c 'default #t #f))
(if submit-pred
(submit-pred (open-input-string text) (as-racket-bool eos))
'default))
(define (syms)
(sort (map symbol->string (namespace-mapped-symbols))
string<?))
;;; eval-commmand
(define/contract (eval-command str)
(-> string? string?)
(define results
(call-with-values (λ ()
((current-eval) (string->namespace-syntax str)))
list))
(~a (map ~v results) "\n"))
;;; find-collection
(define/contract (find-collection str)
(-> path-string? (or/c 'find-collection-not-installed #f (listof string?)))
(define fcd (with-handlers ([exn:fail:filesystem:missing-module?
(λ _ (error 'find-collection
"For this to work, you need to `raco pkg install raco-find-collection`."))])
(dynamic-require 'find-collection/find-collection
'find-collection-dir)))
(map path->string (fcd str)))

View File

@@ -0,0 +1,61 @@
#lang racket/base
(require racket/list
racket/match
racket/path
racket/set)
(provide check-syntax)
(define check-syntax
(let ([show-content
(with-handlers ([exn:fail? (λ _ 'not-supported)])
(let ([f (dynamic-require 'drracket/check-syntax 'show-content)])
;; Ensure correct position info for Unicode like λ.
;; show-content probably ought to do this itself, but
;; work around that.
(λ (path)
(parameterize ([port-count-lines-enabled #t])
(f path)))))])
;; Note: Adjust all positions to 1-based Emacs `point' values.
(λ (path-str)
(define path (string->path path-str))
(parameterize ([current-load-relative-directory (path-only path)])
;; Get all the data.
(define xs (remove-duplicates (show-content path)))
;; Extract the add-mouse-over-status items into a list.
(define infos
(remove-duplicates
(filter values
(for/list ([x (in-list xs)])
(match x
[(vector 'syncheck:add-mouse-over-status beg end str)
(list 'info (add1 beg) (add1 end) str)]
[_ #f])))))
;; Consolidate the add-arrow/name-dup items into a hash table
;; with one item per definition. The key is the definition
;; position. The value is the set of its uses.
(define ht-defs/uses (make-hash))
(for ([x (in-list xs)])
(match x
[(or (vector 'syncheck:add-arrow/name-dup
def-beg def-end
use-beg use-end
_ _ _ _)
(vector 'syncheck:add-arrow/name-dup/pxpy
def-beg def-end _ _
use-beg use-end _ _
_ _ _ _))
(hash-update! ht-defs/uses
(list (add1 def-beg) (add1 def-end))
(λ (v) (set-add v (list (add1 use-beg) (add1 use-end))))
(set))]
[_ #f]))
;; Convert the hash table into a list, sorting the usage positions.
(define defs/uses
(for/list ([(def uses) (in-hash ht-defs/uses)])
(match-define (list def-beg def-end) def)
(define tweaked-uses (sort (set->list uses) < #:key car))
(list 'def/uses def-beg def-end tweaked-uses)))
;; Append both lists and print as Elisp values.
(append infos defs/uses)))))

View File

@@ -0,0 +1,50 @@
#lang racket/base
(require racket/list
racket/match
(only-in "../instrument.rkt" get-test-coverage-info))
(provide get-uncovered)
(define (get-uncovered file)
(consolidate-coverage-ranges
(for*/list ([x (in-list (get-test-coverage-info))]
[covered? (in-value (first x))]
#:when (not covered?)
[src (in-value (second x))]
#:when (equal? file src)
[pos (in-value (third x))]
[span (in-value (fourth x))])
(cons pos (+ pos span)))))
(define (consolidate-coverage-ranges xs)
(remove-duplicates (sort xs < #:key car)
same?))
(define (same? x y)
;; Is x a subset of y or vice versa?
(match-define (cons x/beg x/end) x)
(match-define (cons y/beg y/end) y)
(or (and (<= x/beg y/beg) (<= y/end x/end))
(and (<= y/beg x/beg) (<= x/end y/end))))
(module+ test
(require rackunit)
(check-true (same? '(0 . 9) '(0 . 9)))
(check-true (same? '(0 . 9) '(4 . 5)))
(check-true (same? '(4 . 5) '(0 . 9)))
(check-false (same? '(0 . 1) '(1 . 2)))
(check-equal? (consolidate-coverage-ranges
'((10 . 20) (10 . 11) (19 . 20) (10 . 20)
(20 . 30) (20 . 21) (29 . 30) (20 . 30)))
'((10 . 20)
(20 . 30)))
;; This is a test of actual coverage data I got from one example,
;; where the maximal subsets were (164 . 197) and (214. 247).
(check-equal?
(consolidate-coverage-ranges
'((164 . 197) (164 . 197) (164 . 197)
(173 . 180) (173 . 180) (173 . 180) (173 . 180) (173 . 180) (187 . 196)
(214 . 247) (214 . 247) (214 . 247)
(223 . 230) (223 . 230) (223 . 230) (223 . 230) (223 . 230) (237 . 246)))
'((164 . 197) (214 . 247))))

View File

@@ -0,0 +1,73 @@
#lang racket/base
(require racket/contract
racket/format
racket/match
racket/port
(only-in xml xexpr->string)
(only-in "../find.rkt" find-signature)
"../scribble.rkt")
(provide type
describe)
(define (type v)
(type-or-sig v))
(define (type-or-sig v)
(or (type-or-contract v)
(sig v)
""))
(define (sig v) ;any/c -> (or/c #f string?)
(and (symbol? v)
(match (find-signature (symbol->string v))
[#f #f]
[x (~a x)])))
(define (type-or-contract v) ;any/c -> (or/c #f string?)
(or
;; 1. Try using Typed Racket's REPL simplified type.
(with-handlers ([exn:fail? (λ _ #f)])
(match (with-output-to-string
(λ ()
((current-eval)
(cons '#%top-interaction v))))
[(pregexp "^- : (.*) \\.\\.\\..*\n" (list _ t)) t]
[(pregexp "^- : (.*)\n$" (list _ t)) t]))
;; 2. Try to find a contract.
(with-handlers ([exn:fail? (λ _ #f)])
(parameterize ([error-display-handler (λ _ (void))])
((current-eval)
(cons '#%top-interaction
`(if (has-contract? ,v)
(~a (contract-name (value-contract ,v)))
(error ""))))))))
(define (sig-and/or-type stx)
(define dat (syntax->datum stx))
(define s (sig dat))
(define t (type-or-contract stx))
(xexpr->string
`(div ()
(h1 () ,(or s (~a dat)))
,(cond [(not (or s t))
`(p ()
(em () "(Found no documentation, signature, type, or contract.)"))]
[t `(pre () ,t)]
[else ""])
(br ()))))
;;; describe
;; If a symbol has installed documentation, display it.
;;
;; Otherwise, walk the source to find the signature of its definition
;; (because the argument names have explanatory value), and also look
;; for Typed Racket type or a contract, if any.
(define/contract (describe str)
(-> string? string?)
(define stx (namespace-symbol->identifier (string->symbol str)))
(or (scribble-doc/html stx)
(sig-and/or-type stx)))

View File

@@ -0,0 +1,46 @@
#lang racket/base
(require racket/contract
racket/match
syntax/modresolve
"../mod.rkt")
(provide find-module)
(define/contract (find-module str maybe-mod)
(-> string? (or/c #f mod?)
(or/c #f (list/c path-string? number? number?)))
(define-values (dir _file maybe-rmp) (maybe-mod->dir/file/rmp maybe-mod))
(parameterize ([current-load-relative-directory dir])
(or (mod-loc str maybe-rmp)
(mod-loc (string->symbol str) maybe-rmp))))
(define (mod-loc v maybe-rmp)
(match (with-handlers ([exn:fail? (λ _ #f)])
(resolve-module-path v maybe-rmp))
[(? path-string? path)
#:when (file-exists? path)
(list (path->string path) 1 0)]
[_ #f]))
(module+ test
(require rackunit
racket/runtime-path)
(define-runtime-path here ".")
(let* ([here (simplify-path here)] ;nuke trailing dot
;; Examples of finding relative and absolute:
[requires.rkt (path->string (build-path here "requires.rkt"))]
[pe-racket/string (pregexp "collects/racket/string.rkt$")])
;; Examples of having no current module (i.e. plain racket/base
;; REPL) and having one ("coverage.rkt").
(let ([mod #f])
(parameterize ([current-directory here])
(check-match (find-module "requires.rkt" mod)
(list (== requires.rkt) 1 0))
(check-match (find-module "racket/string" mod)
(list pe-racket/string 1 0))))
(let ([mod (->mod/existing (build-path here "coverage.rkt"))])
(check-match (find-module "requires.rkt" mod)
(list (== requires.rkt) 1 0))
(check-match (find-module "racket/string" mod)
(list pe-racket/string 1 0)))))

View File

@@ -0,0 +1,120 @@
#lang at-exp racket/base
(require (only-in help/help-utils find-help)
(only-in help/search perform-search)
net/url
racket/contract
racket/match
racket/port
(only-in "../scribble.rkt" binding->path+anchor))
(provide doc)
(define/contract (doc str)
(-> string? any)
(or (identifier-help (namespace-symbol->identifier (string->symbol str)))
(perform-search str)))
;; It is 2017 therefore it is hard to activate a web browser and show
;; an anchor link within a local HTML file.
;;
;; 1. On macOS `find-help` suffers from the fact that `send-url/file`
;; doesn't supply a `browser` arg to `send-url/mac`. This causes it
;; to give an "open location" command to osascript. This causes
;; macOS to ignore #anchor fragments in the URL. Although the
;; correct page will open, it won't be scrolled to the item of
;; interest.
;;
;; 2. Furthermore, `send-url/mac` doesn't use an "activate" command to
;; show the browser window (it might be hidden behind Emacs).
;;
;; Let's pretend it's 2020. If we we're on mac and can determine the
;; default browser (from plist files^1), do the equivalent of
;; `send-url/mac` but with both desired behaviors.
;;
;; ^1: This is kludgy because the plist has "bundle IDs" like
;; "com.google.chrome" but osascript wants strings like "chrome".
(module mac-default-browser racket/base
(require json
racket/match
racket/file
racket/system)
(provide mac-default-browser)
(define launch-plists
'("Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"
"Library/Preferences/com.apple.LaunchServices.plist"))
(define (mac-default-browser)
(and (equal? (system-type) 'macosx)
(for/or ([plist launch-plists])
(match (mac-http-handler (build-path (find-system-path 'home-dir) plist))
[#f #f]
[(pregexp "^.+\\.(.+?)$" ;after final dot
(list _ s)) s]))))
(define (mac-http-handler plist-path) ;; path? -> (or/c string? #f)
(for/or ([h (in-list (hash-ref (read-bplist plist-path) 'LSHandlers '()))])
(and (equal? (hash-ref h 'LSHandlerURLScheme #f) "http")
(hash-ref h 'LSHandlerRoleAll #f))))
(define plutil (find-executable-path "plutil" #f))
(define (read-bplist plist-path) ;path? -> json?
(define out-path (make-temporary-file))
(begin0
(if (system* plutil
"-convert" "json"
"-o" out-path
plist-path)
(with-input-from-file out-path read-json)
(make-hash))
(delete-file out-path))))
(module browse-file-url/mac racket/base
(provide browse-file-url/mac)
(require racket/format
racket/system)
(define osascript (find-executable-path "osascript" #f))
(define (browse-file-url/mac file-url browser)
;; Note: Unlike `send-url/mac`, we also do an "activate" to show
;; the browser window.
(system*
osascript
"-e"
@~a{tell application "@browser" to open location "@file-url" activate})))
(require 'mac-default-browser
'browse-file-url/mac)
(define/contract (identifier-help stx)
(-> identifier? boolean?)
((if (mac-default-browser)
identifier-help/mac
identifier-help/other)
stx))
(define/contract (identifier-help/other stx)
(-> identifier? boolean?)
;; Like `find-help` but returns whether help was found and shown.
;; That way, if this returns #f caller knows it could next call
;; `perform-search` as Plan B.
(with-handlers ([exn:fail? (λ _ #f)])
(match (with-output-to-string (λ () (find-help stx)))
[(pregexp "Sending to web browser") #t]
[_ #f])))
(define/contract (identifier-help/mac stx)
(-> identifier? boolean?)
(define-values (path anchor) (binding->path+anchor stx))
(and path
anchor
(let ([path-url (path->url (path->complete-path path))])
(browse-file-url/mac
(url->string (struct-copy url path-url [fragment anchor]))
(mac-default-browser)))
#t))

View File

@@ -0,0 +1,125 @@
#lang racket/base
(require racket/contract
racket/file
racket/format
racket/match
racket/pretty
racket/system
"../elisp.rkt"
"../syntax.rkt"
"../util.rkt")
(provide macro-stepper
macro-stepper/next)
(define step-thunk/c (-> (cons/c (or/c 'original string? 'final) string?)))
(define step-thunk #f)
(define/contract (make-expr-stepper str)
(-> string? step-thunk/c)
(define step-num #f)
(define last-stx (string->namespace-syntax str))
(define (step)
(cond [(not step-num)
(set! step-num 0)
(cons 'original (pretty-format-syntax last-stx))]
[else
(define this-stx (expand-once last-stx))
(cond [(not (equal? (syntax->datum last-stx)
(syntax->datum this-stx)))
(begin0
(cons (~a step-num ": expand-once")
(diff-text (pretty-format-syntax last-stx)
(pretty-format-syntax this-stx)
#:unified 3))
(set! last-stx this-stx))]
[else
(cons 'final (pretty-format-syntax this-stx))])]))
step)
(define/contract (make-file-stepper path into-base?)
(-> (and/c path-string? absolute-path?) boolean? step-thunk/c)
;; If the dynamic-require fails, just let it bubble up.
(define stepper-text (dynamic-require 'macro-debugger/stepper-text 'stepper-text))
(define stx (file->syntax path))
(define-values (dir _name _dir) (split-path path))
(define raw-step (parameterize ([current-load-relative-directory dir])
(stepper-text stx
(if into-base? (λ _ #t) (not-in-base)))))
(define step-num #f)
(define step-last-after "")
(define/contract (step) step-thunk/c
(cond [(not step-num)
(set! step-num 0)
(cons 'original
(pretty-format-syntax stx))]
[else
(define out (open-output-string))
(parameterize ([current-output-port out])
(cond [(raw-step 'next)
(set! step-num (add1 step-num))
(match-define (list title before after)
(step-parts (get-output-string out)))
(set! step-last-after after)
(cons (~a step-num ": " title)
(diff-text before after #:unified 3))]
[else
(cons 'final step-last-after)]))]))
step)
(define/contract (macro-stepper what into-base?)
(-> (or/c (cons/c 'expr string?) (cons/c 'file path-string?)) elisp-bool/c
(cons/c 'original string?))
(set! step-thunk
(match what
[(cons 'expr str) (make-expr-stepper str)]
[(cons 'file path) (make-file-stepper path (as-racket-bool into-base?))]))
(macro-stepper/next))
(define/contract (macro-stepper/next)
(-> (cons/c (or/c 'original 'final string?) string?))
(unless step-thunk
(error 'macro-stepper "Nothing to expand"))
(define v (step-thunk))
(when (eq? 'final (car v))
(set! step-thunk #f))
v)
;; Borrowed from xrepl.
(define not-in-base
(λ () (let ([base-stxs #f])
(unless base-stxs
(set! base-stxs ; all ids that are bound to a syntax in racket/base
(parameterize ([current-namespace (make-base-namespace)])
(let-values ([(vals stxs) (module->exports 'racket/base)])
(map (λ (s) (namespace-symbol->identifier (car s)))
(cdr (assq 0 stxs)))))))
(λ (id) (not (ormap (λ (s) (free-identifier=? id s)) base-stxs))))))
(define (step-parts str)
(match str
[(pregexp "^(.+?)\n(.+?)\n +==>\n(.+?)\n+$"
(list _ title before after))
(list title before after)]))
(define (diff-text before-text after-text #:unified [-U 3])
(define template "racket-mode-syntax-diff-~a")
(define (make-temporary-file-with-text str)
(define file (make-temporary-file template))
(with-output-to-file file #:mode 'text #:exists 'replace
(λ () (displayln str)))
file)
(define before-file (make-temporary-file-with-text before-text))
(define after-file (make-temporary-file-with-text after-text))
(define out (open-output-string))
(begin0 (parameterize ([current-output-port out])
(system (format "diff -U ~a ~a ~a" -U before-file after-file))
(match (get-output-string out)
["" " <empty diff>\n"]
[(pregexp "\n(@@.+@@\n.+)$" (list _ v)) v]))
(delete-file before-file)
(delete-file after-file)))
(define (pretty-format-syntax stx)
(pretty-format #:mode 'write (syntax->datum stx)))

View File

@@ -0,0 +1,19 @@
#lang racket/base
(require racket/match
(only-in "../instrument.rkt" get-profile-info))
(provide get-profile)
(define (get-profile)
;; TODO: Filter files from racket-mode itself, b/c just noise?
(for/list ([x (in-list (get-profile-info))])
(match-define (list count msec name stx _ ...) x)
(list count
msec
(and name (symbol->string name))
(and (syntax-source stx) (path? (syntax-source stx))
(path->string (syntax-source stx)))
(syntax-position stx)
(and (syntax-position stx) (syntax-span stx)
(+ (syntax-position stx) (syntax-span stx))))))

View File

@@ -0,0 +1,276 @@
#lang at-exp racket/base
(require (only-in macro-debugger/analysis/check-requires show-requires)
racket/format
racket/function
racket/list
racket/match
racket/set)
(provide requires/tidy
requires/trim
requires/base)
(module+ test
(require rackunit))
;; requires/tidy : (listof require-sexpr) -> require-sexpr
(define (requires/tidy reqs)
(let* ([reqs (combine-requires reqs)]
[reqs (group-requires reqs)])
(require-pretty-format reqs)))
;; requires/trim : path-string? (listof require-sexpr) -> require-sexpr
;;
;; Note: Why pass in a list of the existing require forms -- why not
;; just use the "keep" list from show-requires? Because the keep list
;; only states the module name, not the original form. Therefore if
;; the original require has a subform like `(only-in mod f)` (or
;; rename-in, except-in, &c), we won't know how to preserve that
;; unless we're given it. That's why our strategy must be to look for
;; things to drop, as opposed to things to keep.
(define (requires/trim path-str reqs)
(let* ([reqs (combine-requires reqs)]
[sr (show-requires* path-str)]
[drops (filter-map (λ (x)
(match x
[(list 'drop mod lvl) (list mod lvl)]
[_ #f]))
sr)]
[reqs (filter-map (λ (req)
(cond [(member req drops) #f]
[else req]))
reqs)]
[reqs (group-requires reqs)])
(require-pretty-format reqs)))
;; Use `bypass` to help convert from `#lang racket` to `#lang
;; racket/base` plus explicit requires.
;;
;; Note: Currently this is hardcoded to `#lang racket`, only.
(define (requires/base path-str reqs)
(let* ([reqs (combine-requires reqs)]
[sr (show-requires* path-str)]
[drops (filter-map (λ (x)
(match x
[(list 'drop mod lvl) (list mod lvl)]
[_ #f]))
sr)]
[adds (append*
(filter-map (λ (x)
(match x
[(list 'bypass 'racket 0
(list (list mod lvl _) ...))
(filter (λ (x)
(match x
[(list 'racket/base 0) #f]
[_ #t]))
(map list mod lvl))]
[_ #f]))
sr))]
[reqs (filter-map (λ (req)
(cond [(member req drops) #f]
[else req]))
reqs)]
[reqs (append reqs adds)]
[reqs (group-requires reqs)])
(require-pretty-format reqs)))
;; show-requires* : Like show-requires but accepts a path-string? that
;; need not already be a module path.
(define (show-requires* path-str)
(define-values (base name _) (split-path (string->path path-str)))
(parameterize ([current-load-relative-directory base]
[current-directory base])
(show-requires name)))
(define (combine-requires reqs)
(remove-duplicates
(append* (for/list ([req reqs])
(match req
[(list* 'require vs)
(append*
(for/list ([v vs])
;; Use (list mod level), like `show-requires` uses.
(match v
[(list* 'for-meta level vs) (map (curryr list level) vs)]
[(list* 'for-syntax vs) (map (curryr list 1) vs)]
[(list* 'for-template vs) (map (curryr list -1) vs)]
[(list* 'for-label vs) (map (curryr list #f) vs)]
[v (list (list v 0))])))])))))
(module+ test
(check-equal?
(combine-requires '((require a b c)
(require d e)
(require a f)
(require (for-syntax s t u) (for-label l0 l1 l2))
(require (for-meta 1 m1a m1b)
(for-meta 2 m2a m2b))))
'((a 0) (b 0) (c 0) (d 0) (e 0) (f 0)
(s 1) (t 1) (u 1)
(l0 #f) (l1 #f) (l2 #f)
(m1a 1) (m1b 1) (m2a 2) (m2b 2))))
;; Given a list of requires -- each in the (list module level) form
;; used by `show-requires` -- group them by level and convert them to
;; a Racket `require` form. Also, sort the subforms by phase level:
;; for-syntax, for-template, for-label, for-meta, and plain (0).
;; Within each such group, sort them first by module paths then
;; relative requires. Within each such group, sort alphabetically.
(define (group-requires reqs)
;; Put the requires into a hash of sets.
(define ht (make-hasheq)) ;(hash/c <level> (set <mod>))
(for ([req reqs]) (match req
[(list mod lvl) (hash-update! ht lvl
(lambda (s) (set-add s mod))
(set mod))]))
(define (mod-set->mod-list mod-set)
(sort (set->list mod-set) mod<?))
(define (for-level level k)
(define mods (hash-ref ht level #f))
(cond [mods (k (mod-set->mod-list mods))]
[else '()]))
(define (preface . pres)
(λ (mods) `((,@pres ,@mods))))
(define (meta-levels)
(sort (for/list ([x (hash-keys ht)] #:when (not (member x '(-1 0 1 #f)))) x)
<))
`(require
,@(for-level 1 (preface 'for-syntax))
,@(for-level -1 (preface 'for-template))
,@(for-level #f (preface 'for-label))
,@(append* (for/list ([level (in-list (meta-levels))])
(for-level level (preface 'for-meta level))))
,@(for-level 0 values)))
(module+ test
(check-equal? (group-requires
(combine-requires
'((require z c b a)
(require (for-meta 4 m41 m40))
(require (for-meta -4 m-41 m-40))
(require (for-label l1 l0))
(require (for-template t1 t0))
(require (for-syntax s1 s0))
(require "a.rkt" "b.rkt" "c.rkt" "z.rkt"
(only-in "mod.rkt" oi)
(only-in mod oi)))))
'(require
(for-syntax s0 s1)
(for-template t0 t1)
(for-label l0 l1)
(for-meta -4 m-40 m-41)
(for-meta 4 m40 m41)
a b c (only-in mod oi) z
"a.rkt" "b.rkt" "c.rkt" (only-in "mod.rkt" oi) "z.rkt")))
(define (mod<? a b)
(define (key x)
(match x
[(list 'only-in m _ ...) (key m)]
[(list 'except-in m _ ...) (key m)]
[(list 'prefix-in _ m) (key m)]
[(list 'relative-in _ m _ ...) (key m)]
[m m]))
(let ([a (key a)]
[b (key b)])
(or (and (symbol? a) (not (symbol? b)))
(and (list? a) (not (list? b)))
(and (not (string? a)) (string? a))
(and (string? a) (string? b)
(string<? a b))
(and (symbol? a) (symbol? b)
(string<? (symbol->string a) (symbol->string b))))))
(module+ test
(check-true (mod<? 'a 'b))
(check-false (mod<? 'b 'a))
(check-true (mod<? 'a '(only-in b)))
(check-true (mod<? '(only-in a) 'b))
(check-true (mod<? 'a '(except-in b)))
(check-true (mod<? '(except-in a) 'b))
(check-true (mod<? 'a '(prefix-in p 'b)))
(check-true (mod<? '(prefix-in p 'a) 'b))
(check-true (mod<? 'a '(relative-in p 'b)))
(check-true (mod<? '(relative-in p 'a) 'b))
(check-true (mod<? 'a '(prefix-in p (only-in b))))
(check-true (mod<? '(prefix-in p (only-in a)) 'b)))
;; require-pretty-format : list? -> string?
(define (require-pretty-format x)
(define out (open-output-string))
(parameterize ([current-output-port out])
(require-pretty-print x))
(get-output-string out))
(module+ test
(check-equal? (require-pretty-format
'(require a))
@~a{(require a)
})
(check-equal? (require-pretty-format
'(require a b))
@~a{(require a
b)
})
(check-equal? (require-pretty-format
'(require (for-syntax a b) (for-meta 2 c d) e f))
@~a{(require (for-syntax a
b)
(for-meta 2 c
d)
e
f)
})
(check-equal? (require-pretty-format
`(require (only-in m a b) (except-in m a b)))
@~a{(require (only-in m
a
b)
(except-in m
a
b))
}))
;; Pretty print a require form with one module per line and with
;; indentation for the `for-X` subforms. Example:
;;
;; (require (for-syntax racket/base
;; syntax/parse)
;; (for-meta 3 racket/a
;; racket/b)
;; racket/format
;; racket/string
;; "a.rkt"
;; "b.rkt")
(define (require-pretty-print x)
(define (prn x first? indent)
(define (indent-string)
(if first? "" (make-string indent #\space)))
(define (prn-form pre this more)
(define new-indent (+ indent (+ 2 (string-length pre))))
(printf "~a(~a " (indent-string) pre)
(prn this #t new-indent)
(for ([x more])
(newline)
(prn x #f new-indent))
(display ")"))
(match x
[(list 'require)
(void)]
[(list* (and pre (or 'require 'for-syntax 'for-template 'for-label
'only-in 'except-in))
this more)
(prn-form (format "~s" pre) this more)
(when (eq? pre 'require)
(newline))]
[(list* 'for-meta level this more)
(prn-form (format "for-meta ~a" level) this more)]
[this
(printf "~a~s" (indent-string) this)]))
(prn x #t 0))

View File

@@ -0,0 +1,381 @@
#lang racket/base
(require (for-syntax racket/base)
gui-debugger/marks
(only-in mzscheme [apply plain-apply])
(prefix-in kernel: syntax/kerncase))
;; This is like gui-debugger/annotate except:
;;
;; 0. Our annotate-stx does NOT add breaks to syntax sources not
;; matching the syntax it is given. See
;; https://github.com/racket/drracket/issues/230 and below.
;;
;; 1. Our module-annotate disarms/rearms module level expressions. See
;; https://github.com/racket/drracket/issues/231 and below.
;;
;; 2. "Modernize": Use racket/base not racket/scheme. Don't need
;; opt-lambda.
;;
;; 3. We remove the record-bound-id and record-top-level-id callbacks
;; that we don't use, from annotate-for-single-stepping (but leave
;; them for now in annotate-stx).
;;
;; 4. We remove the source arg that is completely unused (I'm guessing
;; historical).
(provide annotate-for-single-stepping)
(define (annotate-for-single-stepping stx break? break-before break-after)
(define (break-wrap debug-info annotated raw is-tail?)
(let* ([start (syntax-position raw)]
[end (+ start (syntax-span raw) -1)]
[break? (break? (syntax-source raw))])
(if is-tail?
#`(let-values ([(value-list) #f])
(if (#%plain-app #,break? #,start)
(set! value-list (#%plain-app
#,break-before
#,debug-info
(#%plain-app current-continuation-marks)))
(#%plain-app void))
(if (#%plain-app not value-list)
#,annotated
(#%plain-app plain-apply values value-list)))
#`(let-values ([(value-list) #f])
(if (#%plain-app #,break? #,start)
(set! value-list (#%plain-app
#,break-before
#,debug-info
(#%plain-app current-continuation-marks)))
(#%plain-app void))
(if (#%plain-app not value-list)
(#%plain-app
call-with-values
(#%plain-lambda () #,annotated)
(case-lambda
[(val) (if (#%plain-app #,break? #,end)
(#%plain-app
#,break-after
#,debug-info
(#%plain-app current-continuation-marks)
val)
val)]
[vals (if (#%plain-app
#,break? #,end)
(#%plain-app
plain-apply
#,break-after
#,debug-info
(#%plain-app current-continuation-marks)
vals)
(#%plain-app plain-apply values vals))]))
(if (#%plain-app #,break? #,end)
(#%plain-app
plain-apply #,break-after
#,debug-info
(#%plain-app current-continuation-marks)
value-list)
(#%plain-app plain-apply values value-list)))))))
(annotate-stx stx break-wrap))
(define (annotate-stx stx break-wrap [record-bound-id void] [record-top-level-id void])
(define breakpoints (make-hasheq))
(define (previous-bindings bound-vars)
(if (null? bound-vars)
#'null
#'(#%plain-app debugger-local-bindings)))
(define (top-level-annotate stx)
(kernel:kernel-syntax-case/phase
stx (namespace-base-phase)
[(module identifier name mb)
(module-annotate stx)]
[else-stx
(general-top-level-expr-iterator stx #f)]))
(define (module-annotate stx)
(syntax-case stx ()
[(_ identifier name mb)
(syntax-case (disarm #'mb) ()
[(plain-module-begin . module-level-exprs)
(with-syntax ([(module . _) stx])
(quasisyntax/loc stx
(module identifier name
#,(rearm
#'mb
#`(plain-module-begin
#,@(map (lambda (e)
;; https://github.com/racket/drracket/issues/231
(rearm
e
(module-level-expr-iterator
(disarm e)
(list (syntax-e #'identifier)
(syntax-source #'identifier)))))
(syntax->list #'module-level-exprs)))))))])]))
(define (module-level-expr-iterator stx module-name)
(kernel:kernel-syntax-case
stx #f
[(#%provide . provide-specs)
stx]
[(#%declare . declare-specs)
stx]
[else-stx
(general-top-level-expr-iterator stx module-name)]))
(define (general-top-level-expr-iterator stx module-name)
(kernel:kernel-syntax-case
stx #f
[(define-values (var ...) expr)
(begin
(for-each (lambda (v) (record-bound-id 'bind v v))
(syntax->list #'(var ...)))
(quasisyntax/loc stx
(begin
(define-values (var ...) #,(annotate #`expr '() #t module-name))
#,(if (syntax-source stx)
#`(begin (#%plain-app
#,record-top-level-id '#,module-name #'var
(case-lambda
[() var]
[(v) (set! var v)])) ...)
#'(#%plain-app void))
(#%plain-app void))))]
[(define-syntaxes (var ...) expr)
stx]
[(begin-for-syntax . exprs)
;; compile time, so treat it like define-syntaxes
stx]
[(begin . top-level-exprs)
(quasisyntax/loc stx
(begin #,@(map (lambda (expr)
(module-level-expr-iterator expr module-name))
(syntax->list #'top-level-exprs))))]
[(#%require . require-specs)
stx]
[(module . _)
;; a submodule:
(module-annotate stx)]
[(module* . _)
;; a submodule:
(module-annotate stx)]
[else
(annotate stx '() #f module-name)]))
(define (annotate expr bound-vars is-tail? module-name)
(define annotate-break?
(let ([pos (syntax-position expr)]
[src (syntax-source expr)])
(and src pos
;; https://github.com/racket/drracket/issues/230
(equal? src (syntax-source stx))
(hash-ref breakpoints pos (lambda () #t))
(kernel:kernel-syntax-case
expr #f
[(if test then else) #t]
[(begin . bodies) #t]
[(begin0 . bodies) #t]
[(let-values . clause) #t]
[(letrec-values . clause) #t]
[(set! var val) #t]
[(with-continuation-mark key mark body) #t]
[(#%plain-app . exprs) #t]
[_ #f])
(begin
(hash-set! breakpoints pos #f)
(when (not is-tail?)
(hash-set! breakpoints (+ pos (syntax-span expr) -1) #f))
#t))))
(define (let/rec-values-annotator letrec?)
(kernel:kernel-syntax-case
(disarm expr) #f
[(label (((var ...) rhs) ...) . bodies)
(let* ([new-bindings (apply append
(map syntax->list
(syntax->list #`((var ...) ...))))]
[all-bindings (append new-bindings bound-vars)]
[new-rhs (map (lambda (expr)
(annotate expr
(if letrec? all-bindings bound-vars)
#f module-name))
(syntax->list #'(rhs ...)))]
[last-body (car (reverse (syntax->list #'bodies)))]
[all-but-last-body (reverse (cdr (reverse (syntax->list #'bodies))))]
[bodies (append (map (lambda (expr)
(annotate expr all-bindings #f module-name))
all-but-last-body)
(list (annotate
last-body
all-bindings
is-tail? module-name)))]
[local-debug-info (assemble-debug-info new-bindings new-bindings 'normal #f)]
[previous-bindings (previous-bindings bound-vars)])
(for-each (lambda (id) (record-bound-id 'bind id id)) new-bindings)
(with-syntax ([(new-rhs/trans ...) new-rhs]
[previous-bindings previous-bindings])
(if letrec?
(quasisyntax/loc expr
(let ([old-bindings previous-bindings])
(label (((debugger-local-bindings)
(#%plain-lambda ()
(#%plain-app
list*
#,@local-debug-info
old-bindings)))
((var ...) new-rhs/trans) ...)
#,@bodies)))
(quasisyntax/loc expr
(label (((var ...) new-rhs/trans) ...)
(let ([debugger-local-bindings
(#%plain-lambda ()
(#%plain-app
list*
#,@local-debug-info
previous-bindings))])
#,@bodies))))))]))
(define (lambda-clause-annotator clause)
(kernel:kernel-syntax-case
clause #f
[(arg-list . bodies)
(let* ([new-bound-vars (arglist-bindings #'arg-list)]
[all-bound-vars (append new-bound-vars bound-vars)]
[new-bodies (let loop ([bodies (syntax->list #'bodies)])
(if (equal? '() (cdr bodies))
(list (annotate (car bodies) all-bound-vars #t module-name))
(cons (annotate (car bodies) all-bound-vars #f module-name)
(loop (cdr bodies)))))])
(for-each (lambda (id) (record-bound-id 'bind id id)) new-bound-vars)
(quasisyntax/loc clause
(arg-list
(let ([debugger-local-bindings
(#%plain-lambda ()
(#%plain-app
list*
#,@(assemble-debug-info new-bound-vars new-bound-vars 'normal #f)
#,(previous-bindings bound-vars)))])
#,@new-bodies))))]))
(define annotated
(rearm
expr
(kernel:kernel-syntax-case
(disarm expr) #f
[var-stx (identifier? (syntax var-stx))
(let ([binder (and (syntax-original? expr)
(member expr bound-vars free-identifier=?))])
(if binder
(record-bound-id 'ref expr (car binder))
(record-bound-id 'top-level expr expr))
expr)]
[(#%plain-lambda . clause)
(quasisyntax/loc expr
(#%plain-lambda #,@(lambda-clause-annotator #'clause)))]
[(case-lambda . clauses)
(quasisyntax/loc expr
(case-lambda #,@(map lambda-clause-annotator (syntax->list #'clauses))))]
[(if test then else)
(quasisyntax/loc expr
(if #,(annotate #'test bound-vars #f module-name)
#,(annotate #'then bound-vars is-tail? module-name)
#,(annotate #'else bound-vars is-tail? module-name)))]
[(begin . bodies)
(letrec ([traverse
(lambda (lst)
(if (and (pair? lst) (equal? '() (cdr lst)))
`(,(annotate (car lst) bound-vars is-tail? module-name))
(cons (annotate (car lst) bound-vars #f module-name)
(traverse (cdr lst)))))])
(quasisyntax/loc expr
(begin #,@(traverse (syntax->list #'bodies)))))]
[(begin0 body)
(quasisyntax/loc expr
(begin0 #,(annotate #'body bound-vars #t module-name)))]
[(begin0 . bodies)
(quasisyntax/loc expr
(begin0 #,@(map (lambda (expr)
(annotate expr bound-vars #f module-name))
(syntax->list #'bodies))))]
[(let-values . clause)
(let/rec-values-annotator #f)]
[(letrec-values . clause)
(let/rec-values-annotator #t)]
[(set! var val)
(let ([binder (and (syntax-original? #'var)
(member #'var bound-vars free-identifier=?))])
(when binder
(record-bound-id 'set expr (car binder)))
(quasisyntax/loc expr
(set! var #,(annotate #`val bound-vars #f module-name))))]
[(quote _) expr]
[(quote-syntax _) expr]
[(quote-syntax _ #:local) expr]
[(with-continuation-mark key mark body)
(quasisyntax/loc expr
(with-continuation-mark key
#,(annotate #'mark bound-vars #f module-name)
#,(annotate #'body bound-vars is-tail? module-name)))]
[(#%plain-app . exprs)
(let ([subexprs (map (lambda (expr)
(annotate expr bound-vars #f module-name))
(syntax->list #'exprs))])
(if (or is-tail? (not (syntax-source expr)))
(quasisyntax/loc expr (#%plain-app . #,subexprs))
(wcm-wrap (make-debug-info module-name expr
bound-vars bound-vars
'normal #f (previous-bindings bound-vars))
(quasisyntax/loc expr
(#%plain-app . #,subexprs)))))]
[(#%top . var) expr]
[(#%variable-reference . _) expr]
[else (error 'expr-syntax-object-iterator "unknown expr: ~a"
(syntax->datum expr))])))
(if annotate-break?
(break-wrap
(make-debug-info module-name expr bound-vars bound-vars
'at-break #f (previous-bindings bound-vars))
annotated
expr
is-tail?)
annotated))
(values (top-level-annotate stx) (hash-map breakpoints (lambda (k v) k))))
(define (arglist-bindings arglist-stx)
(syntax-case arglist-stx ()
[var
(identifier? arglist-stx)
(list arglist-stx)]
[(var ...)
(syntax->list arglist-stx)]
[(var . others)
(cons #'var (arglist-bindings #'others))]))
(define (disarm stx) (syntax-disarm stx code-insp))
(define (rearm old new) (syntax-rearm new old))
(define code-insp (variable-reference->module-declaration-inspector
(#%variable-reference)))

View File

@@ -0,0 +1,309 @@
#lang racket/base
(require (for-syntax racket/base)
gui-debugger/marks
racket/contract
racket/format
racket/lazy-require
racket/list
racket/match
racket/set
racket/string
syntax/modread
"interactions.rkt"
"util.rkt")
(lazy-require ["debug-annotator.rkt" (annotate-for-single-stepping)])
(provide (rename-out [on-break-channel debug-notify-channel])
debug-eval
debug-resume
debug-disable
make-debug-eval-handler
next-break
set-debug-repl-namespace!)
(define debug-repl-ns (make-base-namespace))
(define (set-debug-repl-namespace! ns)
(set! debug-repl-ns ns))
;; A gui-debugger/marks "mark" is a thunk that returns a
;; full-mark-struct -- although gui-debugger/marks doesn't provide
;; that struct. Instead the thunk can be passed to various accessor
;; functions.
(define mark/c (-> any/c))
;; A "mark-binding" is a list whose first element is syntax of the
;; identifier, and whose second element is a get/set! procedure.
(define get/set!/c (case-> (-> any/c)
(-> any/c void)))
(define breakable-positions/c (hash/c path? (set/c #:cmp 'eq pos/c)))
(define/contract breakable-positions breakable-positions/c (make-hash))
(define/contract (breakable-position? src pos)
(-> path? pos/c boolean?)
(set-member? (hash-ref breakable-positions src (seteq)) pos))
(define/contract (annotate stx)
(-> syntax? syntax?)
(define source (syntax-source stx))
(display-commented (format "Debug annotate ~v" source))
(define-values (annotated breakables)
(annotate-for-single-stepping stx break? break-before break-after))
(hash-update! breakable-positions
source
(λ (s) (set-union s (list->seteq breakables)))
(seteq))
annotated)
(define break-when/c (or/c 'all 'none (cons/c path-string? pos/c)))
(define/contract next-break
(case-> (-> break-when/c)
(-> break-when/c void))
(let ([v 'none])
(case-lambda [() v]
[(v!) (set! v v!)])))
;; If this returns #t, either break-before or break-after will be
;; called next.
(define ((break? src) pos)
(match (next-break)
['none #f]
['all #t]
[(cons (== src) (== pos)) #t]
[_ #f]))
(define/contract (break-before top-mark ccm)
(-> mark/c continuation-mark-set? (or/c #f (listof any/c)))
(break 'before top-mark ccm #f))
(define/contract (break-after top-mark ccm . vals)
(->* (mark/c continuation-mark-set?) #:rest (listof any/c)
any)
(apply values (break 'after top-mark ccm vals)))
(define/contract (break before/after top-mark ccm vals)
(-> (or/c 'before 'after) mark/c continuation-mark-set? (or/c #f (listof any/c))
(or/c #f (listof any/c)))
(define stx (mark-source top-mark))
(define src (syntax-source stx))
(define pos (case before/after
[(before) (syntax-position stx)]
[(after) (+ (syntax-position stx) (syntax-span stx) -1)]))
(define locals
(for*/list ([binding (in-list (mark-bindings top-mark))]
[stx (in-value (first binding))]
[get/set! (in-value (second binding))]
#:when (and (syntax-original? stx) (syntax-source stx)))
(list (syntax-source stx)
(syntax-position stx)
(syntax-span stx)
(syntax->datum stx)
(~v (get/set!)))))
;; Start a debug repl on its own thread, because below we're going to
;; block indefinitely with (channel-get on-resume-channel), waiting for
;; the Emacs front end to issue a debug-resume command.
(define repl-thread (parameterize ([current-namespace debug-repl-ns])
(thread (repl src pos top-mark))))
;; The on-break-channel is how we notify the Emacs front-end. This
;; is a synchronous channel-put but it should return fairly quickly,
;; as soon as the TCP command server gets and writes it. In other
;; words, this is sent as a notification, unlike a command response
;; as a result of a request.
(define this-break-id (new-break-id))
(channel-put on-break-channel
(list 'debug-break
(cons src pos)
breakable-positions
locals
(cons this-break-id
(case before/after
[(before) (list 'before)]
[(after) (list 'after (~s vals))]))))
;; Wait for debug-resume command to put to on-resume-channel. If
;; wrong break ID, ignore and wait again. Note that some Racket
;; values are non-serializable -- e.g. #<output-port> -- in which
;; case just eat the exn:fail:read and use the original `vals`.
(let wait ()
(begin0
(match (channel-get on-resume-channel)
[(list break-when (list (== this-break-id) 'before))
(next-break (calc-next-break before/after break-when top-mark ccm))
#f]
[(list break-when (list (== this-break-id) (or 'before 'after) vals-str))
(next-break (calc-next-break before/after break-when top-mark ccm))
(with-handlers ([exn:fail:read? (λ _ vals)])
(read (open-input-string vals-str)))]
[_ (wait)])
(kill-thread repl-thread)
(newline))))
(define/contract (calc-next-break before/after break-when top-mark ccm)
(-> (or/c 'before 'after) (or/c break-when/c 'over 'out) mark/c continuation-mark-set?
any)
(define (big-step frames)
(define num-marks (length (debug-marks (current-continuation-marks))))
(or (for/or ([frame (in-list frames)]
[depth (in-range (length frames) -1 -1)]
#:when (<= num-marks depth))
(let* ([stx (mark-source frame)]
[src (syntax-source stx)]
[left (syntax-position stx)]
[right (and left (+ left (syntax-span stx) -1))])
(and right
(breakable-position? src right)
(cons src right))))
'all))
(match* [break-when before/after]
[['out _] (big-step (debug-marks ccm))]
[['over 'before] (big-step (cons top-mark (debug-marks ccm)))]
[['over 'after] 'all]
[[v _] v]))
(define break-id/c nat/c)
(define/contract new-break-id
(-> break-id/c)
(let ([n 0]) (λ () (begin0 n (set! n (add1 n))))))
(define/contract (debug-marks ccm)
(-> continuation-mark-set? (listof mark/c))
(continuation-mark-set->list ccm debug-key))
;;; Debug REPL
(define ((repl src pos top-mark))
(parameterize ([current-prompt-read (make-prompt-read src pos top-mark)])
(read-eval-print-loop)))
(define ((make-prompt-read src pos top-mark))
(define-values (_base name _dir) (split-path src))
(define stx (get-interaction (format "[~a:~a]" name pos)))
(with-locals stx (mark-bindings top-mark)))
(define (with-locals stx bindings)
;; Note that mark-bindings is ordered from inner to outer scopes --
;; and can include outer variables shadowed by inner ones. So use
;; only the first occurence of each identifier symbol we encounter.
;; e.g. in (let ([x _]) (let ([x _]) ___)) we want only the inner x.
(define ht (make-hasheq))
(for* ([binding (in-list bindings)]
[sym (in-value (syntax->datum (first binding)))]
#:unless (hash-has-key? ht sym)
[get/set! (in-value (second binding))])
(hash-set! ht sym get/set!))
(syntax-case stx ()
;; I couldn't figure out how to get a set! transformer to work for
;; Typed Racket -- how to annotate or cast a get/set! as (-> Any
;; Void). So instead, just intercept (set! id e) as a datum and
;; effectively (get/set! (eval e debug-repl-ns)) here. In other
;; words treat the stx like a REPL "command". Of course this
;; totally bypasses type-checking, but this is a debugger. YOLO!
[(set! id e)
(and (module-declared? 'typed/racket/base)
(eq? 'set! (syntax->datum #'set!))
(identifier? #'id)
(hash-has-key? ht (syntax->datum #'id)))
(let ([set (hash-ref ht (syntax->datum #'id))]
[v (eval #'e debug-repl-ns)])
(set v)
#`(void))]
;; Wrap stx in a let-syntax form with a make-set!-transformer for
;; every local variable in the mark-bindings results.
[_
(let ([syntax-bindings
(for/list ([(sym get/set!) (in-hash ht)])
(define id (datum->syntax #f sym))
(define xform
(make-set!-transformer
(λ (stx)
(syntax-case stx (set!)
[(set! id v) (identifier? #'id) #`(#%plain-app #,get/set! v)]
[id (identifier? #'id) #`'#,(get/set!)]))))
#`(#,id #,xform))])
#`(let-syntax #,syntax-bindings
#,stx))]))
;;; Command interface
;; Intended use is for `code` to be a function definition form. It
;; will be re-defined annotated for single stepping: When executed it
;; will call our break?, break-before, and break-after functions.
(define/contract (debug-eval source-str line col pos code)
(-> path-string? pos/c nat/c pos/c string? #t)
(define source (string->path source-str))
(define in (open-input-string code))
(port-count-lines! in)
(set-port-next-location! in line col pos)
(eval (annotate (expand (read-syntax source in))))
(next-break 'all)
#t)
(define locals/c (listof (list/c path-string? pos/c pos/c symbol? string?)))
(define break-vals/c (cons/c break-id/c
(or/c (list/c 'before)
(list/c 'after string?))))
(define on-break/c (list/c 'debug-break
break-when/c
breakable-positions/c
locals/c
break-vals/c))
(define/contract on-break-channel (channel/c on-break/c) (make-channel))
(define resume-vals/c (cons/c break-id/c
(or/c (list/c 'before)
(list/c 'before string?)
(list/c 'after string?))))
(define on-resume/c (list/c (or/c break-when/c 'out 'over) resume-vals/c))
(define/contract on-resume-channel (channel/c on-resume/c) (make-channel))
(define/contract (debug-resume resume-info)
(-> on-resume/c #t)
(channel-put on-resume-channel resume-info)
#t)
(define (debug-disable)
(next-break 'none)
(for ([k (in-hash-keys breakable-positions)])
(hash-remove! breakable-positions k)))
;;; Make eval handler to instrument entire files
(define eval-handler/c (-> any/c any))
(define/contract ((make-debug-eval-handler files [orig-eval (current-eval)]) v)
(->* ((set/c path?)) (eval-handler/c) eval-handler/c)
(cond [(compiled-expression? (syntax-or-sexpr->sexpr v))
(orig-eval v)]
[else
(define stx (syntax-or-sexpr->syntax v))
(define top-stx (expand-syntax-to-top-form stx))
(cond [(set-member? files (syntax-source stx))
(next-break 'all)
(parameterize* ([current-eval orig-eval]
[current-load/use-compiled
(let ([orig (current-load/use-compiled)])
(λ (file mod)
(cond [(set-member? files file)
(load-module/annotate file mod)]
[else
(orig file mod)])))])
(eval-syntax (annotate (expand-syntax top-stx))))]
[else (orig-eval top-stx)])]))
;; This never seems to be called ???
(define (load-module/annotate file m)
(display-commented (format "~v" `(load-module/annotate ,file ,m)))
(define-values (base _ __) (split-path file))
(call-with-input-file* file
(λ (in)
(port-count-lines! in)
(parameterize ([read-accept-compiled #f]
[current-load-relative-directory base])
(with-module-reading-parameterization
(λ ()
(define e (parameterize ([current-namespace (make-base-namespace)])
(expand (read-syntax file in))))
(eval (annotate (check-module-form e m file)))))))))

View File

@@ -0,0 +1,57 @@
#lang racket/base
(require racket/contract
racket/match
racket/port
racket/set)
(provide elisp-read
elisp-writeln
elisp-bool/c
as-racket-bool)
;;; read/write Emacs Lisp values
(define (elisp-read in)
(elisp->racket (read in)))
(define (elisp-writeln v out)
(elisp-write v out)
(newline out))
(define (elisp-write v out)
(write (racket->elisp v) out))
(define elisp-bool/c (or/c #t '()))
(define (as-racket-bool v)
;; elisp->racket "de-puns" 'nil as '() -- not #f. Use this helper to
;; treat as a boolean.
(and v (not (null? v))))
(define (elisp->racket v)
(match v
['nil '()] ;not #f -- see as-racket-bool
['t #t]
[(? list? xs) (map elisp->racket xs)]
[(cons x y) (cons (elisp->racket x) (elisp->racket y))]
[(vector s _ ...) s] ;Emacs strings can be #("string" . properties)
[v v]))
(define (racket->elisp v)
(match v
[(or #f (list)) 'nil]
[#t 't]
[(? list? xs) (map racket->elisp xs)]
[(cons x y) (cons (racket->elisp x) (racket->elisp y))]
[(? path? v) (path->string v)]
[(? hash? v) (for/list ([(k v) (in-hash v)])
(cons (racket->elisp k) (racket->elisp v)))]
[(? set? v) (map racket->elisp (set->list v))]
[v v]))
(module+ test
(require rackunit)
(check-equal? (with-output-to-string
(λ () (elisp-write '(1 #t nil () (a . b) #hash((1 . 2) (3 . 4)))
(current-output-port))))
"(1 t nil nil (a . b) ((1 . 2) (3 . 4)))"))

View File

@@ -0,0 +1,197 @@
#lang at-exp racket/base
(require racket/format
racket/match
(only-in racket/path path-only)
racket/runtime-path
racket/string
setup/collects
setup/dirs
"fresh-line.rkt"
"instrument.rkt"
"util.rkt")
(provide display-exn
our-error-display-handler
show-full-path-in-errors)
(module+ test
(require rackunit))
(define (display-exn exn)
(our-error-display-handler (exn-message exn) exn))
(define (our-error-display-handler str v)
(cond [(exn? v)
(unless (equal? "Check failure" (exn-message v)) ;rackunit check fails
(fresh-line)
(display-commented (fully-qualify-error-path str))
(display-srclocs v)
(unless (exn:fail:user? v)
(display-context v))
(maybe-suggest-packages v))]
[else
(fresh-line)
(display-commented str)]))
(define (display-srclocs exn)
(when (exn:srclocs? exn)
(define srclocs
(match ((exn:srclocs-accessor exn) exn)
;; Some exceptions like exn:fail:read? include the first
;; srcloc in exn-message -- don't show it again.
[(cons _ xs)
#:when (or (exn:fail:read? exn)
(exn:fail:contract:variable? exn))
xs]
;; Some exceptions like exn:fail:syntax? with Typed Racket
;; include _all_ in exn-message -- don't show _any_.
[_
#:when (exn:fail:syntax? exn)
'()]
[xs xs]))
(for ([s (in-list srclocs)])
(display-commented (source-location->string s)))))
(define (display-context exn)
(cond [(instrumenting-enabled)
(define p (open-output-string))
(print-error-trace p exn)
(match (get-output-string p)
["" (void)]
[s (display-commented (string-append "Context (errortrace):"
;; et prepends a \n
s))])]
[else
(match (context->string
(continuation-mark-set->context (exn-continuation-marks exn)))
["" (void)]
[s (display-commented (string-append "Context:\n"
s))])]))
(define (context->string xs)
;; Limit the context in two ways:
;; 1. Don't go beyond error-print-context-length
;; 2. Don't go into "system" context that's just noisy.
(string-join (for/list ([x xs]
[_ (error-print-context-length)]
#:unless (system-context? x))
(context-item->string x))
"\n"))
(define-runtime-path here "error.rkt")
(define (system-context? ci)
(match-define (cons id src) ci)
(or (not src)
(let ([src (srcloc-source src)])
(and (path? src)
(or (equal? (path-only src) (path-only here))
(under-system-path? src))))))
(define (under-system-path? path)
(match (path->collects-relative path)
[`(collects #"mred" . ,_) #t]
[`(collects #"racket" #"contract" . ,_) #t]
[`(collects #"racket" #"private" . ,_) #t]
[`(collects #"typed-racket" . ,_) #t]
[_ #f]))
(define (context-item->string ci)
(match-define (cons id src) ci)
(string-append (if (or src id) " " "")
(if src (source-location->string src) "")
(if (and src id) " " "")
(if id (format "~a" id) "")))
;; Don't use source-location->string from syntax/srcloc. Don't want
;; the setup/path-to-relative behavior that replaces full pathnames
;; with <collects>, <pkgs> etc. Instead want full pathnames for Emacs'
;; compilation-mode. HOWEVER note that <collects> or <pkgs> might be
;; baked into exn-message string already; we handle that in
;; `fully-qualify-error-path`. Here we handle only strings we create
;; ourselves, such as for the Context "stack trace".
(define (source-location->string x)
(match-define (srcloc src line col pos span) x)
(format "~a:~a:~a" src (or line "1") (or col "1")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Fully qualified pathnames in error messages, so that Emacs
;; compilation-mode can do its stuff.
;; srcloc->string uses current-directory-for-user to shorten error
;; messages. But we always want full pathnames. Setting it to
;; 'pref-dir -- very unlikely user .rkt file will be there -- is
;; least-worst way AFAIK.
(define (show-full-path-in-errors)
(current-directory-for-user (find-system-path 'pref-dir)))
;; If this looks like a Racket error message, but the filename is
;; not fully-qualified, prepend curdir to the filename.
;;
;; This covers Racket 5.3.6 and earlier. In fact, this might be
;; sufficient for _all_ versions of Racket and we don't need the
;; `show-full-path-in-errors` thing above, at all. Not yet sure.
(define (fully-qualify-error-path s)
(match s
[(pregexp "^([^/.]+)\\.([^.]+):(\\d+)[:.](\\d+):(.*)$"
(list _ base ext line col more))
(define curdir (path->string (current-directory)))
(string-append curdir base "." ext ":" line ":" col ":" more)]
[s (regexp-replace* #rx"<collects>"
s
(path->string (find-collects-dir)))]))
(module+ test
(require rackunit)
(check-equal?
(parameterize ([current-directory "/tmp/"])
(fully-qualify-error-path "foo.rkt:3:0: f: unbound identifier\n in: f"))
"/tmp/foo.rkt:3:0: f: unbound identifier\n in: f")
(check-equal?
(fully-qualify-error-path "/tmp/foo.rkt:3:0: f: unbound identifier\n in: f")
"/tmp/foo.rkt:3:0: f: unbound identifier\n in: f")
(let ([o (open-output-string)])
(parameterize ([current-error-port o])
(display-srclocs (make-exn:fail:read "..."
(current-continuation-marks)
'())))
(check-equal? (get-output-string o) "")))
(define maybe-suggest-packages
(with-handlers ([exn:fail? (λ _ void)])
(with-dynamic-requires ([racket/base exn:missing-module?]
[racket/base exn:missing-module-accessor]
[pkg/db get-catalogs]
[pkg/lib pkg-catalog-suggestions-for-module])
(λ (exn)
(when (exn:missing-module? exn)
(match (get-catalogs)
[(list)
(display-commented
@~a{-----
Can't suggest packages to install, because pkg/db get-catalogs is '().
To configure:
1. Start DrRacket.
2. Choose "File | Package Mananger".
3. Click "Available from Catalog".
4. When prompted, click "Update".
-----})]
[_
(define mod ((exn:missing-module-accessor exn) exn))
(match (pkg-catalog-suggestions-for-module mod)
[(list) void]
[(list p)
(display-commented
@~a{Try "raco pkg install @|p|" ?})]
[(? list? ps)
(display-commented
@~a{Try "raco pkg install" one of @(string-join ps ", ") ?})]
[_ void])]))))))
(module+ test
;; Point of this test is older Rackets where the with-handlers
;; clause is exercised.
(check-not-exn
(λ ()
(maybe-suggest-packages (exn:fail "" (current-continuation-marks))))))

View File

@@ -0,0 +1,302 @@
;; -*- racket-indent-sequence-depth: 100; racket-indent-curly-as-sequence: t; -*-
;;; NOTE: After changing this file you will need to M-x faceup-write-file
;;; to regenerate the .faceup test comparison file.
;;;
;;; NOTE: You may need to disable certain features -- for example
;;; global-paren-face-mode -- during the M-x faceup-write-file.
#lang racket
(require xml)
(provide valid-bucket-name?)
;; Various def* forms are font-locked:
(define (function foo)
#t)
(define ((curried-function x) y)
(list x y))
(define a-var 10)
(define/contract (f2 x)
(any/c . -> . any)
#t)
(define-values (1st-var 2nd-var) (values 1 2))
(define-thing foo) ;bug 276
;; let: font-lock identifiers
(let ([foo 10]
[bar 20])
foo)
(let loop ([x 10])
(unless (zero? x)
(loop (sub1 x))))
(let* ([foo 10]
[bar 20])
foo)
(let-values ([(a b) (values 1 2)])
(values a b))
(let*-values ([(a b) (values 1 2)])
(values a b))
(letrec-values ([(a b) (values 1 2)])
(values a b))
(let-syntax ([foo #'foo])
foo)
(letrec-syntax ([foo #'foo])
foo)
(let-syntaxes ([(foo) #'foo])
foo)
(letrec-syntaxes ([(foo) #'foo])
foo)
(letrec-syntaxes+values ([(foo) #'foo])
([(a b) (values 1 2)])
foo)
;; for/fold is indented correctly:
(for/fold ([str ""])
([ss '("a" "b" "c")])
(string-append str ss))
;; Auto-converts word `lambda` to `λ`:
(lambda (x) #t)
;; Or use M-C-y to insert to insert `λ` char.
;; Smart indentation for quoted lists:
'(1 2
3 4)
;; Smart indentation for vector literals:
#(1 2
3 4)
;; Smart indentation for Rackjure dict literals:
(module x rackjure
{'a 0
'b 2})
;; Silly test submodule example.
;; Try using C-c C-f to Fold (hide) it, and C-c C-u to Unfold it.
(module+ test
(require rackunit)
(check-true #t))
;; Single line comment
#|
Multi-line
comment
|#
#;(sexpr comment)
;; Nested sexpr comments
(list 2
#;2)
(list 1
#;4
#;(3))
(let (#;[x #;1]
[y 2])
y)
(define x #<<FOO
asdfasdf
asdfasdf
asdfasdf
FOO
)
#;(define x #<<BAR
asdfasdf
asdfasdf
asdfasdf
BAR
)
|identifier with spaces|
|;no comment|
| #|no comment|# |
(define (a-function x #:keyword [y 0])
(and (append (car '(1 2 3))))
(regexp-match? #rx"foobar" "foobar")
(regexp-match? #px"foobar" "foobar")
(define a 1)
(let ([a "foo"]
[b "bar"])
(displayln b))
(let* ([a "foo"]
[b "bar"])
(displayln b))
(let-values ([(a b) (values 1 2)])
#t)
(for/list ([x (in-list (list 1 2 (list 3 4)))])
(cond [(pair? x) (car x)]
[else x])))
;; Issue 261
"@|widget-id|" @|foo|
;; Issue 298
(define x (begin "|" '\|))
(define (foo)
(let ([x 10])
#t)
(let ([x 1]
[y 2])
#t)
(define 1/2-the-way 0)
(define less-than-1/2 0)
;; Self-eval examples
(values
1/2-the-way ;should NOT be self-eval
less-than-1/2 ;should NOT be self-eval
+inf.0
-inf.0
+nan.0
#t
#f
1
1.0
1/2
-1/2
#b100
#o123
#d123
#x7f7f
'symbol
'|symbol with spaces|
'|;no comment|
'| #|no comment|# |
'symbol-with-no-alpha/numeric-chars
#\c
#\space
#\newline
;; Literal number examples
;; #b
#b1.1
#b-1.1
#b1e1
#b0/1
#b1/1
#b1e-1
#b101
;; #d
#d-1.23
#d1.123
#d1e3
#d1e-22
#d1/2
#d-1/2
#d1
#d-1
;; No # reader prefix -- same as #d
-1.23
1.123
1e3
1e-22
1/2
-1/2
1
-1
;; #e
#e-1.23
#e1.123
#e1e3
#e1e-22
#e1
#e-1
#e1/2
#e-1/2
;; #i always float
#i-1.23
#i1.123
#i1e3
#i1e-22
#i1/2
#i-1/2
#i1
#i-1
;; #o
#o777.777
#o-777.777
#o777e777
#o777e-777
#o3/7
#o-3/7
#o777
#o-777
;; #x
#x-f.f
#xf.f
#x-f
#xf
))
(define/contract (valid-bucket-name? s #:keyword [dns-compliant? #t])
((string?) (#:keyword boolean?) . ->* . boolean?)
(cond
[dns-compliant?
(and (<= 3 (string-length s)) (<= (string-length s) 63)
(not (regexp-match #px"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" s))
(for/and ([s (regexp-split #rx"\\." s)])
(define (valid-first-or-last? c)
(or (char-lower-case? (string-ref s 0))
(char-numeric? (string-ref s 0))))
(define (valid-mid? c)
(or (valid-first-or-last? c)
(equal? c #\-)))
(define len (string-length s))
(and (< 0 len)
(valid-first-or-last? (string-ref s 0))
(valid-first-or-last? (string-ref s (sub1 len)))
(or (<= len 2)
(for/and ([c (substring s 1 (sub1 len))])
(valid-mid? c))))))]
[else
(and (<= (string-length s) 255)
(for/and ([c s])
(or (char-numeric? c)
(char-lower-case? c)
(char-upper-case? c)
(equal? c #\.)
(equal? c #\-)
(equal? c #\_))))]))
(displayln "I'm running!")

View File

@@ -0,0 +1,302 @@
«m:;; »«x:-*- racket-indent-sequence-depth: 100; racket-indent-curly-as-sequence: t; -*-
»
«m:;;; »«x:NOTE: After changing this file you will need to M-x faceup-write-file
»«m:;;; »«x:to regenerate the .faceup test comparison file.
»«m:;;;»«x:
»«m:;;; »«x:NOTE: You may need to disable certain features -- for example
»«m:;;; »«x:global-paren-face-mode -- during the M-x faceup-write-file.
»
«k:#lang» «v:racket»
(«k:require» xml)
(«k:provide» valid-bucket-name?)
«m:;; »«x:Various def* forms are font-locked:
»
(«k:define» («f:function» foo)
«:racket-selfeval-face:#t»)
(«k:define» ((«f:curried-function» x) y)
(«b:list» x y))
(«k:define» «v:a-var» «:racket-selfeval-face:10»)
(«b:define/contract» («f:f2» x)
(«b:any/c» . «b:->» . «b:any»)
«:racket-selfeval-face:#t»)
(«k:define-values» («v:1st-var 2nd-var») («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»))
(define-thing «v:foo») «m:;»«x:bug 276
»
«m:;; »«x:let: font-lock identifiers
»
(«k:let» ([«v:foo» «:racket-selfeval-face:10»]
[«v:bar» «:racket-selfeval-face:20»])
foo)
(«k:let» «f:loop» ([«v:x» «:racket-selfeval-face:10»])
(«k:unless» («b:zero?» x)
(loop («b:sub1» x))))
(«k:let*» ([«v:foo» «:racket-selfeval-face:10»]
[«v:bar» «:racket-selfeval-face:20»])
foo)
(«k:let-values» ([(«v:a» «v:b») («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)])
(«b:values» a b))
(«k:let*-values» ([(«v:a» «v:b») («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)])
(«b:values» a b))
(«k:letrec-values» ([(«v:a» «v:b») («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)])
(«b:values» a b))
(«k:let-syntax» ([«v:foo» #«:racket-selfeval-face:'foo»])
foo)
(«k:letrec-syntax» ([«v:foo» #«:racket-selfeval-face:'foo»])
foo)
(«k:let-syntaxes» ([(«v:foo») #«:racket-selfeval-face:'foo»])
foo)
(«k:letrec-syntaxes» ([(«v:foo») #«:racket-selfeval-face:'foo»])
foo)
(«k:letrec-syntaxes+values» ([(«v:foo») #«:racket-selfeval-face:'foo»])
([(a b) («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)])
foo)
«m:;; »«x:for/fold is indented correctly:
»(«k:for/fold» ([str «s:""»])
([ss '(«s:"a"» «s:"b"» «s:"c"»)])
(«b:string-append» str ss))
«m:;; »«x:Auto-converts word `lambda` to `λ`:
»(«k:lambda» (x) «:racket-selfeval-face:#t»)
«m:;; »«x:Or use M-C-y to insert to insert `λ` char.
»
«m:;; »«x:Smart indentation for quoted lists:
»'(«:racket-selfeval-face:1» «:racket-selfeval-face:2»
«:racket-selfeval-face:3» «:racket-selfeval-face:4»)
«m:;; »«x:Smart indentation for vector literals:
»#(«:racket-selfeval-face:1» «:racket-selfeval-face:2»
«:racket-selfeval-face:3» «:racket-selfeval-face:4»)
«m:;; »«x:Smart indentation for Rackjure dict literals:
»(«k:module» «f:x» «v:rackjure»
{«:racket-selfeval-face:'a» «:racket-selfeval-face:0»
«:racket-selfeval-face:'b» «:racket-selfeval-face:2»})
«m:;; »«x:Silly test submodule example.
»«m:;; »«x:Try using C-c C-f to Fold (hide) it, and C-c C-u to Unfold it.
»(«k:module+» «f:test»
(«k:require» rackunit)
(check-true «:racket-selfeval-face:#t»))
«m:;; »«x:Single line comment
»
«x:#|
Multi-line
comment
|#»
«m:#;»«x:(sexpr comment)»
«m:;; »«x:Nested sexpr comments
»
(«b:list» «:racket-selfeval-face:2»
«m:#;»«x:2»)
(«b:list» «:racket-selfeval-face:1»
«m:#;»«x:4»
«m:#;»«x:(3)»)
(«k:let» («m:#;»«x:[x #;1]»
[«v:y» «:racket-selfeval-face:2»])
y)
(«k:define» «v:x» «:racket-here-string-face:#<<FOO
asdfasdf
asdfasdf
asdfasdf
FOO
» )
«m:#;»«x:(define x #<<BAR
asdfasdf
asdfasdf
asdfasdf
BAR
|identifier with spaces|
|;no comment|
| #|no comment|# |
(«k:define» («f:a-function» x «:racket-keyword-argument-face:#:keyword» [y «:racket-selfeval-face:0»])
(«k:and» («b:append» («b:car» '(«:racket-selfeval-face:1» «:racket-selfeval-face:2» «:racket-selfeval-face:3»))))
(«b:regexp-match?» «:racket-selfeval-face:#rx»«s:"foobar"» «s:"foobar"»)
(«b:regexp-match?» «:racket-selfeval-face:#px»«s:"foobar"» «s:"foobar"»)
(«k:define» «v:a» «:racket-selfeval-face:1»)
(«k:let» ([«v:a» «s:"foo"»]
[«v:b» «s:"bar"»])
(«b:displayln» b))
(«k:let*» ([«v:a» «s:"foo"»]
[«v:b» «s:"bar"»])
(«b:displayln» b))
(«k:let-values» ([(«v:a» «v:b») («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)])
«:racket-selfeval-face:#t»)
(«k:for/list» ([x («k:in-list» («b:list» «:racket-selfeval-face:1» «:racket-selfeval-face:2» («b:list» «:racket-selfeval-face:3» «:racket-selfeval-face:4»)))])
(«k:cond» [(«b:pair?» x) («b:car» x)]
[«k:else» x])))
«m:;; »«x:Issue 261
»«s:"@|widget-id|"» @|foo|
«m:;; »«x:Issue 298
»(«k:define» «v:x» («k:begin» «s:"|"» '\|))
(«k:define» («f:foo»)
(«k:let» ([«v:x» «:racket-selfeval-face:10»])
«:racket-selfeval-face:#t»)
(«k:let» ([«v:x» «:racket-selfeval-face:1»]
[«v:y» «:racket-selfeval-face:2»])
«:racket-selfeval-face:#t»)
(«k:define» «v:1/2-the-way» «:racket-selfeval-face:0»)
(«k:define» «v:less-than-1/2» «:racket-selfeval-face:0»)
«m:;; »«x:Self-eval examples
» («b:values»
1/2-the-way «m:;»«x:should NOT be self-eval
» less-than-1/2 «m:;»«x:should NOT be self-eval
» «:racket-selfeval-face:+inf.0»
«:racket-selfeval-face:-inf.0»
«:racket-selfeval-face:+nan.0»
«:racket-selfeval-face:#t»
«:racket-selfeval-face:#f»
«:racket-selfeval-face:1»
«:racket-selfeval-face:1.0»
«:racket-selfeval-face:1/2»
«:racket-selfeval-face:-1/2»
«:racket-selfeval-face:#b100»
«:racket-selfeval-face:#o123»
«:racket-selfeval-face:#d123»
«:racket-selfeval-face:#x7f7f»
«:racket-selfeval-face:'symbol»
«:racket-selfeval-face:'|symbol with spaces|»
«:racket-selfeval-face:'|;no comment|»
«:racket-selfeval-face:'| #|no comment|# |»
«:racket-selfeval-face:'symbol-with-no-alpha/numeric-chars»
«:racket-selfeval-face:#\c»
«:racket-selfeval-face:#\space»
«:racket-selfeval-face:#\newline»
«m:;; »«x:Literal number examples
»
«m:;; »«x:#b
» «:racket-selfeval-face:#b1.1»
«:racket-selfeval-face:#b-1.1»
«:racket-selfeval-face:#b1e1»
«:racket-selfeval-face:#b0/1»
«:racket-selfeval-face:#b1/1»
«:racket-selfeval-face:#b1e-1»
«:racket-selfeval-face:#b101»
«m:;; »«x:#d
» «:racket-selfeval-face:#d-1.23»
«:racket-selfeval-face:#d1.123»
«:racket-selfeval-face:#d1e3»
«:racket-selfeval-face:#d1e-22»
«:racket-selfeval-face:#d1/2»
«:racket-selfeval-face:#d-1/2»
«:racket-selfeval-face:#d1»
«:racket-selfeval-face:#d-1»
«m:;; »«x:No # reader prefix -- same as #d
» «:racket-selfeval-face:-1.23»
«:racket-selfeval-face:1.123»
«:racket-selfeval-face:1e3»
«:racket-selfeval-face:1e-22»
«:racket-selfeval-face:1/2»
«:racket-selfeval-face:-1/2»
«:racket-selfeval-face:1»
«:racket-selfeval-face:-1»
«m:;; »«x:#e
» «:racket-selfeval-face:#e-1.23»
«:racket-selfeval-face:#e1.123»
«:racket-selfeval-face:#e1e3»
«:racket-selfeval-face:#e1e-22»
«:racket-selfeval-face:#e1»
«:racket-selfeval-face:#e-1»
«:racket-selfeval-face:#e1/2»
«:racket-selfeval-face:#e-1/2»
«m:;; »«x:#i always float
» «:racket-selfeval-face:#i-1.23»
«:racket-selfeval-face:#i1.123»
«:racket-selfeval-face:#i1e3»
«:racket-selfeval-face:#i1e-22»
«:racket-selfeval-face:#i1/2»
«:racket-selfeval-face:#i-1/2»
«:racket-selfeval-face:#i1»
«:racket-selfeval-face:#i-1»
«m:;; »«x:#o
» «:racket-selfeval-face:#o777.777»
«:racket-selfeval-face:#o-777.777»
«:racket-selfeval-face:#o777e777»
«:racket-selfeval-face:#o777e-777»
«:racket-selfeval-face:#o3/7»
«:racket-selfeval-face:#o-3/7»
«:racket-selfeval-face:#o777»
«:racket-selfeval-face:#o-777»
«m:;; »«x:#x
» «:racket-selfeval-face:#x-f.f»
«:racket-selfeval-face:#xf.f»
«:racket-selfeval-face:#x-f»
«:racket-selfeval-face:#xf»
))
(«b:define/contract» («f:valid-bucket-name?» s «:racket-keyword-argument-face:#:keyword» [dns-compliant? «:racket-selfeval-face:#t»])
((«b:string?») («:racket-keyword-argument-face:#:keyword» «b:boolean?») . «b:->*» . «b:boolean?»)
(«k:cond»
[dns-compliant?
(«k:and» («b:<=» «:racket-selfeval-face:3» («b:string-length» s)) («b:<=» («b:string-length» s) «:racket-selfeval-face:63»)
(«b:not» («b:regexp-match» «:racket-selfeval-face:#px»«s:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"» s))
(«k:for/and» ([s («b:regexp-split» «:racket-selfeval-face:#rx»«s:"\\."» s)])
(«k:define» («f:valid-first-or-last?» c)
(«k:or» («b:char-lower-case?» («b:string-ref» s «:racket-selfeval-face:0»))
(«b:char-numeric?» («b:string-ref» s «:racket-selfeval-face:0»))))
(«k:define» («f:valid-mid?» c)
(«k:or» (valid-first-or-last? c)
(«b:equal?» c «:racket-selfeval-face:#\-»)))
(«k:define» «v:len» («b:string-length» s))
(«k:and» («b:<» «:racket-selfeval-face:0» len)
(valid-first-or-last? («b:string-ref» s «:racket-selfeval-face:0»))
(valid-first-or-last? («b:string-ref» s («b:sub1» len)))
(«k:or» («b:<=» len «:racket-selfeval-face:2»)
(«k:for/and» ([c («b:substring» s «:racket-selfeval-face:1» («b:sub1» len))])
(valid-mid? c))))))]
[«k:else»
(«k:and» («b:<=» («b:string-length» s) «:racket-selfeval-face:255»)
(«k:for/and» ([c s])
(«k:or» («b:char-numeric?» c)
(«b:char-lower-case?» c)
(«b:char-upper-case?» c)
(«b:equal?» c «:racket-selfeval-face:#\.»)
(«b:equal?» c «:racket-selfeval-face:#\-»)
(«b:equal?» c «:racket-selfeval-face:#\_»))))]))
(«b:displayln» «s:"I'm running!"»)

View File

@@ -0,0 +1,325 @@
;; -*- racket-indent-sequence-depth: 100; racket-indent-curly-as-sequence: t; -*-
;;; NOTE: After changing this file you will need to M-x faceup-write-file
;;; to regenerate the .faceup test comparison file.
;;;
;;; NOTE: You may need to disable certain features -- for example
;;; global-paren-face-mode -- during the M-x faceup-write-file.
;;; Quoted list
'(a b
(a b
c))
'((1) 2 3
(3)
4 5)
;;; Quasiquoted list (align with head) and unquote or unquote-splicing
;;; (use normal indent rules for the form).
`(Part ()
(PartNumber ()
,part)
(ETag ()
,etag))
`((,(x)
,y))
`(Delete
,@(for/list ([p (in-list paths)])
`(Object ()
(Key () ,p))))
;;; Syntax
#'(for/list ([x xs])
x)
#`(for/list ([x xs])
x)
#'(#%app (#%app hasheq (quote a) (quote 42))
(quote a))
(#%app (#%app hasheq (quote a) (quote 42))
(quote a))
#'(foo (#%app hasheq (quote a) (quote 42))
(quote a))
;;; Rackjure style dictionary (when racket-indent-curly-as-sequence is t).
{a b
c d}
{a b
c d
b '(a x
s (x y
x v))}
;;; Vector
#(a b
c d)
;;; List with a keyword as first member (e.g. in many contracts)
(#:x y
#:y x)
;;; Normal function application.
(foobar x
y
z)
(foobar
x
y
z)
(dict-set a
b
c)
(dict-set
a
b
c)
(call-with-values (lambda () (values 1 2))
+)
(call-with-values
(lambda () (values 1 2))
+)
;;; Forms with special indentation
(let ([x 0])
x)
;; indent 2
(syntax-case stx ()
[(_ x) #'#f]
[(_ x y) #'#t])
;; indent 3
(syntax-case* stx () x
[(_ x) #'#f]
[(_ x y) #'#t])
(syntax-case*
stx
(#%module-begin
module
define-values
define-syntaxes
define
define/contract
define-syntax
struct
define-struct)
x
[(_ x) #'#f]
[(_ x y) #'#t])
;; begin and cond have 0 style
(begin
0
0)
(begin 0
0)
(cond [1 2]
[3 4])
(cond
[1 2]
[3 4])
(if a
x
x)
;; begin*
(begin-for-foo 0
0)
(begin-for-foo
0
0)
(with-handlers ([x y])
a b c)
;; def, with-, call-with- and other 'defun style
(define (x) x x
x)
(struct x x
())
(match-define (list x y)
(list 1 2))
(with-output-to-file path #:mode 'text #:exists 'replace
(λ () (display "Hello, world.")))
(call-with-output-file path #:mode 'text #:exists 'replace
(λ (out) (display "Hello, world." out)))
;;; Special forms: When the first non-distinguished form is on the
;;; same line as distinguished forms, disregard it for indent.
;; module has indent 2
(module 1
2
3
4
5)
;; Normal case
(module 1 2
3
4
5)
;; Weird case -- but this is how scheme-mode indents it.
(module 1 2 3
4
5)
;; Weird case -- but this is how scheme-mode indents it.
(module 1 2 3 4
5)
;;; for/fold
;; for/fold untyped, accum on same line
(for/fold ([a 0]
[b 0])
([x 0]
[y 0])
#t)
;; for/fold untyped, accum on different line
(for/fold
([a 0]
[b 0])
([x 0]
[y 0])
#t)
;; for/fold typed, type on same line
(for/fold : T
([a 0]
[b 0])
([x 0]
[y 0])
#t)
;; for/fold typed, type on different line
(for/fold
: T
([a 0]
[b 0])
([x 0]
[y 0])
#t)
;;; Bug #50
'((x
y) A
z
(x
y) A
z)
(match args
[(list x) (x
y)] ...
[(list x) (x y)] ...
[(list x) (x y)] ...)
(define-syntax (fstruct stx)
(syntax-parse stx
[(_ id:id (field:id ...))
(with-syntax ([(accessor ...)
(for/list ([fld (in-list (syntax->list #'(field ...)))])
(format-id stx "~a-~a" (syntax->datum #'id) fld))])
#'(serializable-struct
id (field ...) #:transparent
#:property prop:procedure
(lambda (self . args)
(match args
[(list 'field) (accessor self)] ...
[(list (list 'field)) (accessor self)] ...
[(list (list-rest 'field fields)) ((accessor self) fields)] ...
[(list-rest 'field f args)
(struct-copy id self
[field (apply f (accessor self) args)])] ...
[(list-rest (list 'field) f args) ;<-- THIS SEXPR IS INDENTED TOO FAR
(struct-copy id self
[field (apply f (accessor self) args)])] ...
[(list-rest (list-rest 'field fields) args)
(struct-copy id self
[field (apply (accessor self) fields args)])] ...))))]))
;; Bug #123
#hash([a . (#hash()
0)]
[b . (#hasheq()
0)]
[c . (#fx(0 1 2)
0)]
[d . (#fx3(0 1 2)
0)]
[e . (#fl(0.0 1.0 2.0)
0)]
[f . (#fl3(0.0 1.0 2.0)
0)]
[g . (#s(foo x)
0)]
[h . (#3(0 1 2)
0)])
;; Bug #136
#;(list 1
#;2
3)
(list 1
#;(list 1
(let ([x 2]
#;[y 3])
x)
3)
2
3)
;; Bug #243
(cond [x y
z]
[(= a x) y
z])
;; Bug #262
(define-metafunction λL
: (x ...) ... -> (x ...)
[( any_ls ...)
,(apply append (term (any_ls ...)))])

View File

@@ -0,0 +1,325 @@
«m:;; »«x:-*- racket-indent-sequence-depth: 100; racket-indent-curly-as-sequence: t; -*-
»
«m:;;; »«x:NOTE: After changing this file you will need to M-x faceup-write-file
»«m:;;; »«x:to regenerate the .faceup test comparison file.
»«m:;;;»«x:
»«m:;;; »«x:NOTE: You may need to disable certain features -- for example
»«m:;;; »«x:global-paren-face-mode -- during the M-x faceup-write-file.
»
«m:;;; »«x:Quoted list
»
'(a b
(a b
c))
'((«:racket-selfeval-face:1») «:racket-selfeval-face:2» «:racket-selfeval-face:3»
(«:racket-selfeval-face:3»)
«:racket-selfeval-face:4» «:racket-selfeval-face:5»)
«m:;;; »«x:Quasiquoted list (align with head) and unquote or unquote-splicing
»«m:;;; »«x:(use normal indent rules for the form).
»
`(Part ()
(PartNumber ()
,part)
(ETag ()
,etag))
`((,(x)
,y))
`(Delete
,@(«k:for/list» ([p («k:in-list» paths)])
`(«t:Object» ()
(Key () ,p))))
«m:;;; »«x:Syntax
»
#'(«k:for/list» ([x xs])
x)
#`(«k:for/list» ([x xs])
x)
#'(«k:#%app» («k:#%app» «b:hasheq» («k:quote» a) («k:quote» «:racket-selfeval-face:42»))
(«k:quote» a))
(«k:#%app» («k:#%app» «b:hasheq» («k:quote» a) («k:quote» «:racket-selfeval-face:42»))
(«k:quote» a))
#'(foo («k:#%app» «b:hasheq» («k:quote» a) («k:quote» «:racket-selfeval-face:42»))
(«k:quote» a))
«m:;;; »«x:Rackjure style dictionary (when racket-indent-curly-as-sequence is t).
»
{a b
c d}
{a b
c d
b '(a x
s (x y
x v))}
«m:;;; »«x:Vector
»
#(a b
c d)
«m:;;; »«x:List with a keyword as first member (e.g. in many contracts)
»
(«:racket-keyword-argument-face:#:x» y
«:racket-keyword-argument-face:#:y» x)
«m:;;; »«x:Normal function application.
»
(foobar x
y
z)
(foobar
x
y
z)
(«b:dict-set» a
b
c)
(«b:dict-set»
a
b
c)
(«b:call-with-values» («k:lambda» () («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»))
«b:+»)
(«b:call-with-values»
(«k:lambda» () («b:values» «:racket-selfeval-face:1» «:racket-selfeval-face:2»))
«b:+»)
«m:;;; »«x:Forms with special indentation
»
(«k:let» ([«v:x» «:racket-selfeval-face:0»])
x)
«m:;; »«x:indent 2
»
(«k:syntax-case» stx ()
[(«k:_» x) #«:racket-selfeval-face:'#f»]
[(«k:_» x y) #«:racket-selfeval-face:'#t»])
«m:;; »«x:indent 3
»
(«k:syntax-case*» stx () x
[(«k:_» x) #«:racket-selfeval-face:'#f»]
[(«k:_» x y) #«:racket-selfeval-face:'#t»])
(«k:syntax-case*»
stx
(«k:#%module-begin»
«k:module»
«k:define-values»
«k:define-syntaxes»
«k:define»
«b:define/contract»
«k:define-syntax»
«k:struct»
«k:define-struct»)
x
[(«k:_» x) #«:racket-selfeval-face:'#f»]
[(«k:_» x y) #«:racket-selfeval-face:'#t»])
«m:;; »«x:begin and cond have 0 style
»(«k:begin»
«:racket-selfeval-face:0»
«:racket-selfeval-face:0»)
(«k:begin» «:racket-selfeval-face:0»
«:racket-selfeval-face:0»)
(«k:cond» [«:racket-selfeval-face:1» «:racket-selfeval-face:2»]
[«:racket-selfeval-face:3» «:racket-selfeval-face:4»])
(«k:cond»
[«:racket-selfeval-face:1» «:racket-selfeval-face:2»]
[«:racket-selfeval-face:3» «:racket-selfeval-face:4»])
(«k:if» a
x
x)
«m:;; »«x:begin*
»
(begin-for-foo «:racket-selfeval-face:0»
«:racket-selfeval-face:0»)
(begin-for-foo
«:racket-selfeval-face:0»
«:racket-selfeval-face:0»)
(«k:with-handlers» ([x y])
a b c)
«m:;; »«x:def, with-, call-with- and other 'defun style
»
(«k:define» («f:x») x x
x)
(«k:struct» x x
())
(«b:match-define» («b:list» x y)
(«b:list» «:racket-selfeval-face:1» «:racket-selfeval-face:2»))
(«k:with-output-to-file» path «:racket-keyword-argument-face:#:mode» «:racket-selfeval-face:'text» «:racket-keyword-argument-face:#:exists» «:racket-selfeval-face:'replace»
(«k:λ» () («b:display» «s:"Hello, world."»)))
(«k:call-with-output-file» path «:racket-keyword-argument-face:#:mode» «:racket-selfeval-face:'text» «:racket-keyword-argument-face:#:exists» «:racket-selfeval-face:'replace»
(«k:λ» (out) («b:display» «s:"Hello, world."» out)))
«m:;;; »«x:Special forms: When the first non-distinguished form is on the
»«m:;;; »«x:same line as distinguished forms, disregard it for indent.
»
«m:;; »«x:module has indent 2
»
(«k:module» «:racket-selfeval-face:1»
«:racket-selfeval-face:2»
«:racket-selfeval-face:3»
«:racket-selfeval-face:4»
«:racket-selfeval-face:5»)
«m:;; »«x:Normal case
»(«k:module» «:racket-selfeval-face:1» «:racket-selfeval-face:2»
«:racket-selfeval-face:3»
«:racket-selfeval-face:4»
«:racket-selfeval-face:5»)
«m:;; »«x:Weird case -- but this is how scheme-mode indents it.
»(«k:module» «:racket-selfeval-face:1» «:racket-selfeval-face:2» «:racket-selfeval-face:3»
«:racket-selfeval-face:4»
«:racket-selfeval-face:5»)
«m:;; »«x:Weird case -- but this is how scheme-mode indents it.
»(«k:module» «:racket-selfeval-face:1» «:racket-selfeval-face:2» «:racket-selfeval-face:3» «:racket-selfeval-face:4»
«:racket-selfeval-face:5»)
«m:;;; »«x:for/fold
»
«m:;; »«x:for/fold untyped, accum on same line
»(«k:for/fold» ([a «:racket-selfeval-face:0»]
[b «:racket-selfeval-face:0»])
([x «:racket-selfeval-face:0»]
[y «:racket-selfeval-face:0»])
«:racket-selfeval-face:#t»)
«m:;; »«x:for/fold untyped, accum on different line
»(«k:for/fold»
([a «:racket-selfeval-face:0»]
[b «:racket-selfeval-face:0»])
([x «:racket-selfeval-face:0»]
[y «:racket-selfeval-face:0»])
«:racket-selfeval-face:#t»)
«m:;; »«x:for/fold typed, type on same line
»(«k:for/fold» «b::» T
([a «:racket-selfeval-face:0»]
[b «:racket-selfeval-face:0»])
([x «:racket-selfeval-face:0»]
[y «:racket-selfeval-face:0»])
«:racket-selfeval-face:#t»)
«m:;; »«x:for/fold typed, type on different line
»(«k:for/fold»
«b::» T
([a «:racket-selfeval-face:0»]
[b «:racket-selfeval-face:0»])
([x «:racket-selfeval-face:0»]
[y «:racket-selfeval-face:0»])
«:racket-selfeval-face:#t»)
«m:;;; »«x:Bug #50
»
'((x
y) A
z
(x
y) A
z)
(«b:match» args
[(«b:list» x) (x
y)] «k:...»
[(«b:list» x) (x y)] «k:...»
[(«b:list» x) (x y)] «k:...»)
(«k:define-syntax» («f:fstruct» stx)
(«b:syntax-parse» stx
[(«k:_» id:id (field:id «k:...»))
(«k:with-syntax» ([(accessor «k:...»)
(«k:for/list» ([fld («k:in-list» («b:syntax->list» #'(«b:field» «k:...»)))])
(«b:format-id» stx «s:"~a-~a"» («b:syntax->datum» #«:racket-selfeval-face:'id») fld))])
#'(serializable-struct
id («b:field» «k:...») «:racket-keyword-argument-face:#:transparent»
«:racket-keyword-argument-face:#:property» «b:prop:procedure»
(«k:lambda» (self . args)
(«b:match» args
[(«b:list» «:racket-selfeval-face:'field») (accessor self)] «k:...»
[(«b:list» («b:list» «:racket-selfeval-face:'field»)) (accessor self)] «k:...»
[(«b:list» (list-rest «:racket-selfeval-face:'field» fields)) ((accessor self) fields)] «k:...»
[(list-rest «:racket-selfeval-face:'field» f args)
(«k:struct-copy» id self
[«b:field» («k:apply» f (accessor self) args)])] «k:...»
[(list-rest («b:list» «:racket-selfeval-face:'field») f args) «m:;»«x:<-- THIS SEXPR IS INDENTED TOO FAR
» («k:struct-copy» id self
[«b:field» («k:apply» f (accessor self) args)])] «k:...»
[(list-rest (list-rest «:racket-selfeval-face:'field» fields) args)
(«k:struct-copy» id self
[«b:field» («k:apply» (accessor self) fields args)])] «k:...»))))]))
«m:;; »«x:Bug #123
»
#hash([a . (#hash()
«:racket-selfeval-face:0»)]
[b . (#hasheq()
«:racket-selfeval-face:0»)]
[c . (#fx(«:racket-selfeval-face:0» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)
«:racket-selfeval-face:0»)]
[d . (#fx3(«:racket-selfeval-face:0» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)
«:racket-selfeval-face:0»)]
[e . (#fl(«:racket-selfeval-face:0.0» «:racket-selfeval-face:1.0» «:racket-selfeval-face:2.0»)
«:racket-selfeval-face:0»)]
[f . (#fl3(«:racket-selfeval-face:0.0» «:racket-selfeval-face:1.0» «:racket-selfeval-face:2.0»)
«:racket-selfeval-face:0»)]
[g . (#s(foo x)
«:racket-selfeval-face:0»)]
[h . (#3(«:racket-selfeval-face:0» «:racket-selfeval-face:1» «:racket-selfeval-face:2»)
«:racket-selfeval-face:0»)])
«m:;; »«x:Bug #136
»
«m:#;»«x:(list 1
#;2
3)»
(«b:list» «:racket-selfeval-face:1»
«m:#;»«x:(list 1
(let ([x 2]
#;[y 3])
x)
3)»
«:racket-selfeval-face:2»
«:racket-selfeval-face:3»)
«m:;; »«x:Bug #243
»(«k:cond» [x y
z]
[(«b:=» a x) y
z])
«m:;; »«x:Bug #262
»(define-metafunction «v:λL»
«b::» (x «k:...») «k:...» «b:->» (x «k:...»)
[( any_ls «k:...»)
,(«k:apply» «b:append» (term (any_ls «k:...»)))])

View File

@@ -0,0 +1,45 @@
#lang racket/base
;;; `racket-open-require-path' uses `tq' to run us. We repeatedly
;;; read-line a query and display the answer as lines terminated by a
;;; blank line.
;;;
;;; This was created because the original attempt, using
;;; `racket--eval/sexpr', couldn't keep up with fast typing. This new
;;; approach is more direct (e.g. no converting to/from sexprs) and
;;; fast enough. Using `tq' provides a "type-ahead buffer" (in lieu of
;;; the old approach's use of `run-with-timer') even though in my
;;; testing so far it's rarely needed.
;;;
;;; The case where `find-module-path-completions' isn't available: We
;;; don't error, we simply always return empty matches. (This might
;;; not be ideal but I initially had trouble making `tq' recognize
;;; e.g. an (exit 1) here and handle it smoothly. Maybe it would work
;;; to change our "protocol" to have an initial question and answer
;;; devoted to this. For example "HELLO?\n" => "OK\n\n" / "ERROR\n\n".
;;; Thereafter the status quo loop.)
(require racket/match)
(module+ main
(define dir (current-directory)) ;FIXME: Get from command-line
(define display-choices (init dir))
(let loop ()
(define str (read-line))
(unless (string=? "" str)
(display-choices str)
(displayln "") ;; terminating blank line
(flush-output)
(loop)))
(exit 0))
(define (init dir)
(with-handlers ([exn:fail? (λ _ (λ _ (void)))])
;; (error 'test-error) ;<- un-comment this to exercise failure path
(define fmpc (dynamic-require 'drracket/find-module-path-completions
'find-module-path-completions))
(define get (fmpc dir))
(λ (str)
(for ([x (in-list (get str))])
(displayln (path->string (cadr x)))))))

View File

@@ -0,0 +1,235 @@
#lang racket/base
(require racket/contract
(only-in racket/format ~a)
racket/list
racket/match
"syntax.rkt")
(provide find-definition
find-signature)
(define location/c (list/c path-string? natural-number/c natural-number/c))
;; Try to find the definition of `str`, returning a list with the file
;; name, line and column, 'kernel, or #f if not found.
(define/contract (find-definition str)
(-> string? (or/c #f 'kernel location/c))
(match (find-definition/stx str)
[(list* stx file submods)
(list (path->string (or (syntax-source stx) file))
(or (syntax-line stx) 1)
(or (syntax-column stx) 0))]
[v v]))
;; Try to find the definition of `str`, returning its signature or #f.
;; When defined in 'kernel, returns a form saying so, not #f.
(define/contract (find-signature str)
(-> string? (or/c #f pair?))
(match (find-definition/stx str)
['kernel '("defined in #%kernel, signature unavailable")]
[(list* id-stx file submods)
(define file-stx (file->syntax file))
(define sub-stx (submodule file submods file-stx))
(match ($signature (syntax-e id-stx) sub-stx)
[(? syntax? stx) (syntax->datum stx)]
[_ #f])]
[v v]))
(define/contract (find-definition/stx str)
(-> string?
(or/c #f 'kernel (cons/c syntax? (cons/c path? (listof symbol?)))))
(match (identifier-binding* str)
[(? list? xs)
(define ht (make-hash)) ;cache in case source repeated
(for/or ([x (in-list (remove-duplicates xs))])
(match x
[(cons id 'kernel) 'kernel]
[(list* id file submods)
(define (sub-stx file->stx)
(hash-ref! ht (cons file file->stx)
(λ () (submodule file submods (file->stx file)))))
(match (or ($definition id (sub-stx file->expanded-syntax))
(match ($renaming-provide id (sub-stx file->syntax))
[(? syntax? s)
($definition (syntax-e s) (sub-stx file->expanded-syntax))]
[_ #f]))
[#f #f]
[stx (list* stx file submods)])]))]
[_ #f]))
;; Distill identifier-binding to what we need. Unfortunately it can't
;; report the definition id in the case of a contract-out and a
;; rename-out, both. For `(provide (contract-out [rename orig new
;; contract]))` it reports (1) the contract-wrapper as the id, and (2)
;; `new` as the nominal-id -- but NOT (3) `orig`. Instead the caller
;; will need try using `renaming-provide`.
(define/contract (identifier-binding* v)
(-> (or/c string? symbol? identifier?)
(or/c #f
(listof (cons/c symbol?
(or/c 'kernel
(cons/c path-string? (listof symbol?)))))))
(define sym->id namespace-symbol->identifier)
(define id (cond [(string? v) (sym->id (string->symbol v))]
[(symbol? v) (sym->id v)]
[(identifier? v) v]))
(match (identifier-binding id)
[(list source-mpi source-id
nominal-source-mpi nominal-source-id
source-phase import-phase nominal-export-phase)
(list (cons source-id (mpi->path source-mpi))
(cons nominal-source-id (mpi->path nominal-source-mpi)))]
[_ #f]))
(define/contract (mpi->path mpi)
(-> module-path-index?
(or/c 'kernel
(cons/c path-string? (listof symbol?))))
(define (hash-bang-symbol? v)
(and (symbol? v)
(regexp-match? #px"^#%" (symbol->string v))))
(match (resolved-module-path-name (module-path-index-resolve mpi))
[(? hash-bang-symbol?) 'kernel]
[(? path-string? path) (list path)]
[(? symbol? sym) (list (build-path (current-load-relative-directory)
(~a sym ".rkt")))]
[(list (? path-string? path) (? symbol? subs) ...)
(list* path subs)]))
;; For use with syntax-case*. When we use syntax-case for syntax-e equality.
(define (syntax-e-eq? a b)
(eq? (syntax-e a) (syntax-e b)))
(define ((make-eq-sym? sym) stx)
(and (eq? sym (syntax-e stx)) stx))
(define (file-module file)
(match (path->string (last (explode-path file)))
[(pregexp "(.+?)\\.rkt$" (list _ v)) (string->symbol v)]))
;; Return bodies (wrapped in begin) of the module indicated by
;; file and sub-mod-syms.
(define (submodule file sub-mod-syms stx)
(submodule* (cons (file-module file) sub-mod-syms) stx))
(define (submodule* mods stx)
(match-define (cons this more) mods)
(define (subs stxs)
(if (empty? more)
#`(begin . #,stxs)
(ormap (λ (stx) (submodule* more stx))
(syntax->list stxs))))
(syntax-case* stx (module #%module-begin) syntax-e-eq?
[(module name _ (#%module-begin . stxs))
(eq? this (syntax-e #'name))
(subs #'stxs)]
[(module name _ . stxs)
(eq? this (syntax-e #'name))
(subs #'stxs)]
[_ #f]))
(module+ test
(require rackunit)
(check-equal? (syntax->datum
(submodule "/path/to/file.rkt" '(a b c)
#'(module file racket
(module a racket
(module not-b racket #f)
(module b racket
(module not-c racket #f)
(module c racket "bingo")
(module not-c racket #f))
(module not-b racket #f)))))
'(begin "bingo")))
;; Given a symbol and syntax, return syntax corresponding to the
;; definition. Intentionally does NOT walk into module forms, so, give
;; us the module bodies wrapped in begin.
;;
;; If `stx` is expanded we can find things defined via definer
;; macros.
;;
;; If `stx` is not expanded, we will miss some things, however the
;; syntax will be closer to what a human expects -- e.g. `(define (f
;; x) x)` instead of `(define-values (f) (lambda (x) x))`.
(define ($definition sym stx) ;;symbol? syntax? -> syntax?
(define eq-sym? (make-eq-sym? sym))
;; This is a hack to handle definer macros that neglect to set
;; srcloc properly using syntax/loc or (format-id ___ #:source __):
;; If the stx lacks srcloc and its parent stx has srcloc, return the
;; parent stx instead. Caveats: 1. Assumes caller only cares about
;; the srcloc. 2. We only check immediate parent. 3. We only use
;; this for define-values and define-syntaxes, below, on the
;; assumption that this only matters for fully-expanded syntax.
(define (loc s)
(if (and (not (syntax-line s))
(syntax-line stx))
stx
s))
(syntax-case* stx
(begin define-values define-syntaxes
define define/contract
define-syntax struct define-struct)
syntax-e-eq?
[(begin . stxs) (ormap (λ (stx) ($definition sym stx))
(syntax->list #'stxs))]
[(define (s . _) . _) (eq-sym? #'s) stx]
[(define/contract (s . _) . _) (eq-sym? #'s) stx]
[(define s . _) (eq-sym? #'s) stx]
[(define-values (ss ...) . _) (ormap eq-sym? (syntax->list #'(ss ...)))
(loc (ormap eq-sym? (syntax->list #'(ss ...))))]
[(define-syntax (s . _) . _) (eq-sym? #'s) stx]
[(define-syntax s . _) (eq-sym? #'s) stx]
[(define-syntaxes (ss ...) . _) (ormap eq-sym? (syntax->list #'(ss ...)))
(loc (ormap eq-sym? (syntax->list #'(ss ...))))]
[(define-struct s . _) (eq-sym? #'s) stx]
[(define-struct (s _) . _) (eq-sym? #'s) stx]
[(struct s . _) (eq-sym? #'s) stx]
[(struct (s _) . _) (eq-sym? #'s) stx]
[_ #f]))
;; Given a symbol and syntax, return syntax corresponding to the
;; function definition signature. The input syntax should NOT be
;; `expand`ed. This intentionally does NOT walk into module forms, so,
;; give us the module bodies wrapped in begin.
(define ($signature sym stx) ;;symbol? syntax? -> (or/c #f list?)
(define eq-sym? (make-eq-sym? sym))
(syntax-case* stx (begin define define/contract case-lambda) syntax-e-eq?
[(begin . stxs) (ormap (λ (stx) ($signature sym stx))
(syntax->list #'stxs))]
[(define (s . as) . _) (eq-sym? #'s) #'(s . as)]
[(define/contract (s . as) . _) (eq-sym? #'s) #'(s . as)]
[(define s (case-lambda [(ass ...) . _] ...)) (eq-sym? #'s) #'((s ass ...) ...)]
[_ #f]))
;; Find sym in a contracting and/or renaming provide, and return the
;; syntax for the ORIGINAL identifier (before being contracted and/or
;; renamed). The input syntax should NOT be expanded.
(define ($renaming-provide sym stx) ;;symbol? syntax? -> syntax?
(define eq-sym? (make-eq-sym? sym))
(syntax-case* stx (begin provide provide/contract) syntax-e-eq?
[(begin . stxs)
(ormap (λ (stx) ($renaming-provide sym stx))
(syntax->list #'stxs))]
[(provide/contract . stxs)
(for/or ([stx (syntax->list #'stxs)])
(syntax-case stx ()
[(s _) (eq-sym? #'s)]
[_ #f]))]
[(provide . stxs)
(for/or ([stx (syntax->list #'stxs)])
(syntax-case* stx (contract-out rename-out) syntax-e-eq?
[(contract-out . stxs)
(for/or ([stx (syntax->list #'stxs)])
(syntax-case* stx (rename) syntax-e-eq?
[(rename orig s _) (eq-sym? #'s) #'orig]
[(s _) (eq-sym? #'s) #'s]
[_ #f]))]
[(rename-out . stxs)
(for/or ([stx (syntax->list #'stxs)])
(syntax-case* stx () syntax-e-eq?
[(orig s) (eq-sym? #'s) #'orig]
[_ #f]))]
[_ #f]))]
[_ #f]))

View File

@@ -0,0 +1,37 @@
#lang racket/base
(provide fresh-line
zero-column!)
;; Borrowed from xrepl
(define last-output-port #f)
(define last-error-port #f)
(define (maybe-new-output-ports)
(define-syntax-rule (maybe last cur)
(unless (eq? last cur)
(when (and last
(not (port-closed? last)))
(flush-output last)) ;just in case
(set! last cur)
(flush-output last)
(port-count-lines! last)))
(maybe last-output-port (current-output-port))
(maybe last-error-port (current-error-port)))
(define (fresh-line [stderr? #f])
(maybe-new-output-ports)
(define port (if stderr? last-error-port last-output-port))
(flush-output port)
(define-values [line col pos] (port-next-location port))
(unless (eq? col 0) (newline)))
(define (zero-column!)
;; there's a problem whenever there's some printout followed by a
;; read: the cursor will be at column zero, but the port counting
;; will think that it's still right after the printout; call this
;; function in such cases to adjust the column to 0.
(maybe-new-output-ports)
(define-values [line col pos] (port-next-location last-output-port))
(set-port-next-location! last-output-port line 0 pos))

View File

@@ -0,0 +1,42 @@
#lang at-exp racket/base
(require (only-in racket/format ~a)
"util.rkt")
(provide gui-required?
require-gui
txt/gui)
(define root-eventspace #f) ;#f until racket/gui/base required first time
(define (gui-required?)
(and root-eventspace #t))
;; Extra explanation for situations like issue 93, entering `(require
;; redex)` in the REPL, as opposed to having it in a .rkt file.
(define more-explanation
@~a{The namespace was reset. Any `require`s you entered in the REPL were "undone".
This includes the `require` you just entered. You may want to enter it again.})
;; This must be called from the main thread, under the main custodian!
(define (require-gui in-repl?)
(when (gui-required?)
(error 'require-gui "Already required"))
(display-commented "On-demand, one-time instantiation of racket/gui/base.")
(when in-repl?
(display-commented more-explanation))
(define current-eventspace (gui-dyn-req 'current-eventspace))
(define make-eventspace (gui-dyn-req 'make-eventspace))
(set! root-eventspace (make-eventspace))
(current-eventspace root-eventspace))
;; Like mz/mr from racket/sandbox.
(define-syntax txt/gui
(syntax-rules ()
[(_ txtval guisym)
(if (gui-required?)
(gui-dyn-req 'guisym)
txtval)]))
(define (gui-dyn-req sym)
(dynamic-require 'racket/gui/base sym))

View File

@@ -0,0 +1,26 @@
#lang racket/base
;;; Portions Copyright (C) 2012 Jose Antonio Ortega Ruiz.
(require file/convertible
racket/file
racket/vector)
(provide convert-image?
convert-image)
;; save-temporary-image : bytes? -> string?
;;
;; Write bytes to a temporary file and return "#<Image: filename>".
(define (save-temporary-image png-bytes)
(define filename (make-temporary-file "racket-image-~a.png"))
(with-output-to-file filename #:exists 'truncate
(λ () (display png-bytes)))
(format "#<Image: ~a>" filename))
(define (convert-image? v)
(convertible? v))
(define (convert-image v)
(cond [(and (convertible? v) (convert v 'png-bytes)) => save-temporary-image]
[else v]))

View File

@@ -0,0 +1,225 @@
#lang at-exp racket/base
(require (only-in errortrace/errortrace-key
errortrace-key)
(only-in errortrace/errortrace-lib
print-error-trace
error-context-display-depth)
(only-in errortrace/stacktrace
stacktrace^
stacktrace@
stacktrace-imports^)
racket/format
racket/match
racket/unit
syntax/parse
"util.rkt")
(provide make-instrumented-eval-handler
error-context-display-depth
print-error-trace
instrumenting-enabled
test-coverage-enabled
clear-test-coverage-info!
get-test-coverage-info
profiling-enabled
clear-profile-info!
get-profile-info)
;;; Core instrumenting
(define instrumenting-enabled (make-parameter #f))
;; These two parameters added to errortrace/stacktrace circa 6.0. They
;; help make-st-mark capture the original, unexpanded syntax, which is
;; nicer to report in a stack trace. Lacking that in older Rackets,
;; the srcloc is still correct and Emacs next-error will work.
(define original-stx (with-handlers ([exn:fail? (λ _ (make-parameter #f))])
(dynamic-require 'errortrace/stacktrace 'original-stx)))
(define expanded-stx (with-handlers ([exn:fail? (λ _ (make-parameter #f))])
(dynamic-require 'errortrace/stacktrace 'expanded-stx)))
(define ((make-instrumented-eval-handler [orig-eval (current-eval)]) orig-exp)
;; This is modeled after the one in DrRacket.
(cond
[(or (not (instrumenting-enabled))
(compiled-expression? (syntax-or-sexpr->sexpr orig-exp)))
(orig-eval orig-exp)]
[else
(let loop ([exp (syntax-or-sexpr->syntax orig-exp)])
(let ([top-e (expand-syntax-to-top-form exp)])
(syntax-case top-e (begin)
[(begin expr ...)
;; Found a `begin', so expand/eval each contained
;; expression one at a time
(let i-loop ([exprs (syntax->list #'(expr ...))]
[last-one (list (void))])
(cond
[(null? exprs)
(apply values last-one)]
[else
(i-loop (cdr exprs)
(call-with-values
(λ ()
(call-with-continuation-prompt
(λ () (loop (car exprs)))
(default-continuation-prompt-tag)
(λ args
(apply
abort-current-continuation
(default-continuation-prompt-tag)
args))))
list))]))]
[_else
;; Not `begin', so proceed with normal expand and eval
(let* ([expanded-e (expand-syntax top-e)]
;; For make-st-mark to work correctly we need to
;; parameterize original-stx and expanded-stx.
[annotated (parameterize ([original-stx top-e]
[expanded-stx expanded-e])
(annotate-top expanded-e
(namespace-base-phase)))])
(warn-about-time-apply expanded-e)
(orig-eval annotated))])))]))
(define (warn-about-time-apply stx)
(syntax-parse stx
#:datum-literals (#%app time-apply)
[(#%app time-apply . _)
(display-commented
@~a{Warning: time or time-apply used in errortrace annotated code.
For meaningful timings, use command-line racket instead!})
#t]
[(ss ...) (for/or ([stx (in-list (syntax->list #'(ss ...)))])
(warn-about-time-apply stx))]
[_ #f]))
;;; Better stack traces ("basic errortrace")
(define base-phase
(variable-reference->module-base-phase (#%variable-reference)))
(define (with-mark mark expr phase)
;; This is modeled after the one in errortrace-lib. Specifically,
;; use `make-st-mark' for its capture of the original syntax to show
;; in the stack trace error message.
(match (make-st-mark mark phase)
[#f expr]
[loc (define phase-shift (- phase base-phase))
(with-syntax ([expr expr]
[loc loc]
[errortrace-key errortrace-key]
[qte (syntax-shift-phase-level #'quote phase-shift)]
[wcm (syntax-shift-phase-level #'with-continuation-mark
phase-shift)])
(syntax (wcm (qte errortrace-key)
loc
expr)))]))
;; print-error-trace
;;
;; Just re-provide the one from errortrace-lib because (a) it works
;; and (b) the `make-st-mark' representation is intentionally not
;; documented.
;;; Test coverage
(define test-coverage-enabled (make-parameter #f)) ;stacktrace-imports^
(define test-coverage-info (make-hasheq)) ;(hash/c syntax? mpair?).
;; This approach taken from DrR. Presumably set-mcar! is faster than a
;; box, which in turn is faster than hash-set!. The cdr cell is
;; ignored.
(define (clear-test-coverage-info!)
(hash-clear! test-coverage-info))
(define (initialize-test-coverage-point expr) ;stacktrace-imports^
(hash-set! test-coverage-info expr (mcons #f #f)))
(define (test-covered expr) ;stacktrace-imports^
(define v (hash-ref test-coverage-info expr #f))
(and v (with-syntax ([v v])
#'(#%plain-app set-mcar! v #t))))
(define (get-test-coverage-info)
;; Due to macro expansion (e.g. to an `if` form), there may be
;; multiple data points for the exact same source location. We want
;; to logically OR them: If any are true, the source location is
;; covered.
(define ht (make-hash)) ;; (list src pos span) => cover?
(for* ([(stx v) (in-hash test-coverage-info)]
[cover? (in-value (mcar v))]
[loc (in-value (list (syntax-source stx)
(syntax-position stx)
(syntax-span stx)))])
(match (hash-ref ht loc 'none)
['none (hash-set! ht loc cover?)]
[#f (when cover? (hash-set! ht loc #t))]
[#t (void)]))
(for/list ([(loc cover?) (in-hash ht)])
(cons cover? loc)))
;;; Profiling
(define profile-key (gensym)) ;stacktrace-imports^
(define profiling-enabled (make-parameter #f)) ;stacktrace-imports^
(define profile-info (make-hasheq)) ;(hash/c any/c prof?)
(define (clear-profile-info!)
(hash-clear! profile-info))
(struct prof
(nest? ;guard nested calls
num ;exact-nonnegative-integer?
time ;exact-nonnegative-integer?
name ;(or/c #f symbol?)
expr) ;syntax?
#:mutable
#:transparent)
(define (initialize-profile-point key name expr) ;stacktrace-imports^
(hash-set! profile-info
key
(prof #f 0 0 (and (syntax? name) (syntax-e name)) expr)))
(define (register-profile-start key) ;stacktrace-imports^
(define p (hash-ref profile-info key))
(set-prof-num! p (add1 (prof-num p)))
(cond [(prof-nest? p) #f]
[else (set-prof-nest?! p #t)
(current-process-milliseconds)]))
(define (register-profile-done key start) ;stacktrace-imports^
(void
(when start
(define p (hash-ref profile-info key))
(set-prof-nest?! p #f)
(set-prof-time! p (+ (- (current-process-milliseconds) start)
(prof-time p))))))
(define (get-profile-info)
(for/list ([x (in-list (hash-values profile-info))])
(match-define (prof nest? count msec name stx) x)
(list count msec name stx)))
;;; Finally, invoke the unit
(define-values/invoke-unit/infer stacktrace@)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; example
;; (parameterize ([instrumenting-enabled #t]
;; [test-coverage-enabled #t]
;; [profiling-enabled #f]
;; [current-eval (make-instrumented-eval-handler (current-eval))])
;; (namespace-require (string->path "/tmp/simple.rkt")))
;; (get-test-coverage-info)
;; (get-profile-info)

View File

@@ -0,0 +1,84 @@
#lang racket/base
(require racket/match
"fresh-line.rkt")
(provide current-sync/yield
get-interaction)
;; A channel to which a thread puts interactions that it reads using
;; the current-read-interaction handler (which can be set by a lang
;; from its configure-runtime, so, this should be compatible with
;; any lang, even non-sexpr langs).
;;
;; This is its own thread and channel for a couple reasons:
;;
;; - Issue #311. A consumer can use sync/timeout to avoid displaying a
;; prompt when multiple interactions are waiting.
;;
;; - Debugging. We can switch from the normal REPL to a debugger REPL,
;; without input being stuck inside a read call for the former.
;;
;; One wrinkle is we need to be careful about calling yield instead of
;; sync when the gui is active. See issue #326.
;; FIXME??: This used to be under the REPL custodian. Is it OK for it
;; _not_ to be, now? For instance what if user runs another file, but
;; this is still using the previous current-read-interaction value?
(define chan (make-channel))
(define (read-interaction/put-channel)
(define in ((current-get-interaction-input-port)))
(define (read-interaction)
(with-handlers ([exn:fail? values])
((current-read-interaction) (object-name in) in))) ;[^1]
(match (read-interaction)
[(? eof-object?) (sync in)] ;[^2]
[(? exn:fail? e) (channel-put chan e)] ;raise in other thread
[v (channel-put chan v)])
(read-interaction/put-channel))
(void (thread read-interaction/put-channel))
(define current-sync/yield (make-parameter sync)) ;see issue #326
(define (get-interaction prompt)
(match (or (sync/timeout 0.01 chan) ;see issue #311
(begin (display-prompt prompt)
((current-sync/yield) chan)))
[(? exn:fail? exn) (raise exn)]
[v v]))
(define (display-prompt str)
(flush-output (current-error-port))
(fresh-line)
(display str)
(display "> ")
(flush-output)
(zero-column!))
;; "Footnote" comments about make-prompt-read and many attempts to fix
;; issue #305.
;;
;; [^1]: datalog/lang expects each interaction to be EOF terminated.
;; This seems to be a DrRacket convention (?). We could make
;; that work here if we composed open-input-string with
;; read-line. But that would fail for valid multi-line
;; expressions in langs like racket/base e.g. "(+ 1\n2)". We
;; could have Emacs racket-repl-submit append some marker that
;; lets us know to combine multiple lines here -- but we'd have
;; to be careful to eat the marker and avoid combining lines
;; when the user is entering input for their own program that
;; uses `read-line` etc. Trying to be clever here is maybe not
;; smart. I _think_ the safest thing is for each lang like
;; datalog to implement current-read-interaction like it says on
;; the tin -- it can parse just one expression/statement from a
;; normal, "infinite" input port; if that means the lang parser
;; has to be tweaked for a single-expression/statement mode of
;; usage, so be it.
;;
;; [^2]: The eof-object? clause is here only for datalog/lang
;; configure-runtime.rkt. Its `the-read` returns eof if
;; char-ready? is false. WAT. Why doesn't it just block like a
;; normal read-interaction handler? Catch this and wait for more
;; input to be available before calling it again.

View File

@@ -0,0 +1,98 @@
#lang typed/racket/no-check
(require racket/syntax)
;; Generate lists for Racket keywords, builtins, and types.
;;
;; The question of what is a "keyword" and a "builtin" is not so
;; simple in Racket:
;;
;; 1. The distinction between the two is squishy, and from one point
;; of view Racket has 1400+ "primitives" (!).
;;
;; 2. As for "builtins", there are many, many "batteries included"
;; libraries in the main distribution. Where to draw the line?
;;
;; 3. More fundamentally, Racket is a language for making languages.
;; Ultimately the only way to be 100% correct is to do something
;; "live" with namespace-mapped-symbols. But I don't see that as
;; performant for Emacs font-lock.
;;
;; Here I'm saying that:
;;
;; (a) "keywords" are syntax (only) from racket/base
;;
;; (b) "builtins" are everything else provided by #lang racket and
;; #lang typed/racket (except the capitalized Types from typed/racket
;; go into their own list). Plus for modern macros, racket/syntax and
;; a few items from syntax/parse (but not its the syntax classes,
;; because `id` and `str` are too "generic" and too likely to be user
;; program identifiers).
;;
;; Is that somewhat arbitrary? Hell yes. It's my least-worst,
;; practical idea for now. Also, IMHO it's an improvement over getting
;; pull requests to add people's favorites, a few at a time. At least
;; this way is consistent, and can be regenerated programatically as
;; Racket evolves.
(define (symbol<=? a b)
(string<=? (symbol->string a) (symbol->string b)))
(define (exports mod #:only-stx? [only-stx? #f])
(define (ids phases)
(for*/list ([phase phases]
[item (cdr phase)])
(car item)))
(define-values (vars stxs) (module->exports mod))
(sort (remove-duplicates (append (ids stxs)
(if only-stx? '() (ids vars)))
eq?)
symbol<=?))
(define (subtract xs ys)
(for*/list ([x xs] #:when (not (memq x ys))) x))
(define base-stx (exports 'racket/base #:only-stx? #t))
(define rkt (append (exports 'racket)
(exports 'racket/syntax)
'(syntax-parse syntax-parser define-simple-macro)))
(define rkt+ (subtract rkt base-stx))
(define tr (exports 'typed/racket))
(define tr+ (subtract tr rkt)) ;This includes Types, too
(define Types (for/list ([x tr+]
#:when (char-upper-case? (string-ref (symbol->string x) 0)))
x))
;;; The final lists
(define keywords base-stx)
(define builtins
(sort (subtract (remove-duplicates (append rkt+
(subtract tr+ Types))
eq?)
base-stx)
symbol<=?))
;; So many builtins, Emacs gives "regexp too long" error, so split into two:
(define-values (builtins1 builtins2)
(let ([mid (/ (length builtins) 2)])
(for/fold ([xs '()]
[ys '()])
([x builtins]
[i (in-naturals)])
(cond [(< i mid) (values (cons x xs) ys)]
[else (values xs (cons x ys))]))))
(define types Types)
(define (prn xs)
(pretty-print (map symbol->string (sort xs symbol<=?))))
;; Run these to print, copy and paste into racket-keywords-and-builtins.el
;; (prn types)
;; (prn keywords)
;; (prn builtins1)
;; (prn builtins2)

View File

@@ -0,0 +1,93 @@
#lang at-exp racket/base
(require racket/match
racket/format
racket/tcp
"elisp.rkt"
"util.rkt")
(provide start-logger-server)
;; "On start-up, Racket creates an initial logger that is used to
;; record events from the core run-time system. For example, an 'debug
;; event is reported for each garbage collection (see Garbage
;; Collection)." Use that; don't create new one. See issue #325.
(define global-logger (current-logger))
(define (start-logger-server port launch-token)
(void (thread (logger-thread port launch-token))))
(define ((logger-thread port launch-token))
(define listener (tcp-listen port 4 #t "127.0.0.1"))
(let accept ()
(define-values (in out) (tcp-accept listener))
(unless (or (not launch-token)
(equal? launch-token (elisp-read in)))
(display-commented "Authorization failed; exiting")
(exit 1))
;; Assumption: Any network fail means the client has disconnected,
;; therefore we should go back to waiting to accept a connection.
(with-handlers ([exn:fail:network? void])
(let wait ([receiver never-evt])
;; Assumption: Our Emacs code will write complete sexprs,
;; therefore when `in` becomes ready `read` will return
;; without blocking.
(match (sync in receiver)
[(? input-port? in) (match (read in)
[(? eof-object?) (void)]
[v (wait (make-receiver v))])]
[(vector level message _v topic)
(parameterize ([current-output-port out])
(display-log level topic message)
(flush-output))
(wait receiver)])))
(close-input-port in)
(close-output-port out)
(accept)))
(define (display-log level topic message)
(display (label level))
(display " ")
(display (ensure-topic-in-message topic message))
(newline))
(define (ensure-topic-in-message topic message)
(match message
[(pregexp (format "^~a: " (regexp-quote (~a topic))))
message]
[message-without-topic
(format "~a: ~a" (or topic "*") message-without-topic)]))
(module+ test
(require rackunit)
(check-equal? (ensure-topic-in-message 'topic "topic: message")
"topic: message")
(check-equal? (ensure-topic-in-message 'topic "message")
"topic: message")
(check-equal? (ensure-topic-in-message #f "message")
"*: message"))
(define (label level)
;; justify
(case level
[(debug) "[ debug]"]
[(info) "[ info]"]
[(warning) "[warning]"]
[(error) "[ error]"]
[(fatal) "[ fatal]"]
[else @~a{[level]}]))
(define (make-receiver alist)
(apply make-log-receiver (list* global-logger
(alist->spec alist))))
;; Convert from ([logger . level] ...) alist to the format used by
;; make-log-receiver: (level logger ... ... default-level). In the
;; alist, treat the logger '* as the default level.
(define (alist->spec xs) ;(Listof (Pairof Symbol Symbol)) -> (Listof Symbol)
(for/fold ([spec '()])
([x (in-list xs)])
(append spec
(match x
[(cons '* level) (list level)]
[(cons logger level) (list level logger)]))))

View File

@@ -0,0 +1,8 @@
#lang racket/base
(require file/md5)
(provide file->md5)
(define (file->md5 file)
(call-with-input-file* file (compose bytes->string/utf-8 md5)))

View File

@@ -0,0 +1,151 @@
#lang at-exp racket/base
(require (for-syntax racket/base
syntax/parse)
racket/contract/base
racket/contract/region
racket/format
racket/match
racket/string
syntax/location
"util.rkt")
(provide relative-module-path?
(struct-out mod)
->mod/existing
maybe-mod->dir/file/rmp
maybe-mod->prompt-string
maybe-warn-about-submodules)
(module+ test
(require rackunit))
;; The subset of module-path? with a relative filename
(define (relative-module-path? v)
(define (rel-path? v) ;real predicate taking any/c, unlike relative-path?
(and (path-string? v) (relative-path? v)))
(and (module-path? v)
(match v
[(? rel-path?) #t]
[(list 'submod (? rel-path?) (? symbol?) ..1) #t]
[_ #f])))
(module+ test
(check-true (relative-module-path? "f.rkt"))
(check-true (relative-module-path? '(submod "f.rkt" a b)))
(check-false (relative-module-path? "/path/to/f.rkt"))
(check-false (relative-module-path? '(submod "/path/to/f.rkt" a b)))
(check-false (relative-module-path? 'racket/base))
(check-false (relative-module-path? '(submod 'racket/base a b))))
(define-struct/contract mod
([dir absolute-path?] ;#<path:/path/to/>
[file relative-path?] ;#<path:foo.rkt>
[rmp relative-module-path?]) ;#<path:f.rkt> or '(submod <path:f.rkt> bar)
#:transparent)
(define/contract (->mod/simple v)
(-> any/c (or/c #f mod?))
(match v
[(? symbol? s) (->mod/simple (~a s))] ;treat 'file.rkt as "file.rkt"
[(or (? path? ap) (? path-string? ap))
(let*-values ([(dir file _) (split-path (simplify-path ap))]
[(dir) (match dir ['relative (current-directory)][dir dir])])
(mod dir file file))]
[_ #f]))
(define/contract (->mod v)
(-> any/c (or/c #f mod?))
(define-match-expander mm
(syntax-parser
[(_ dir:id file:id rmp:id)
#'(app ->mod/simple (mod dir file rmp))]))
(match v
[(list 'submod
(mm d f _) (? symbol? ss) ..1) (mod d f (list* 'submod f ss))]
[(list (mm d f _) (? symbol? ss) ..1) (mod d f (list* 'submod f ss))]
[(list (mm d f mp)) (mod d f mp)]
[(mm d f mp) (mod d f mp)]
[_ #f]))
(module+ test
(define-syntax-rule (= x y) (check-equal? x y))
(define f.rkt (string->path "f.rkt"))
;; rel path
(let ([dir (current-directory)])
(= (->mod "f.rkt") (mod dir f.rkt f.rkt))
(= (->mod 'f.rkt) (mod dir f.rkt f.rkt))
(= (->mod '(submod "f.rkt" a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '(submod f.rkt a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '("f.rkt" a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '(f.rkt a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '("f.rkt")) (mod dir f.rkt f.rkt))
(= (->mod '(f.rkt)) (mod dir f.rkt f.rkt)))
;; abs path
(let ([dir (string->path "/p/t/")])
(= (->mod "/p/t/f.rkt") (mod dir f.rkt f.rkt))
(= (->mod '/p/t/f.rkt) (mod dir f.rkt f.rkt))
(= (->mod '(submod "/p/t/f.rkt" a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '(submod /p/t/f.rkt a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '("/p/t/f.rkt" a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '(/p/t/f.rkt a b)) (mod dir f.rkt `(submod ,f.rkt a b)))
(= (->mod '("/p/t/f.rkt")) (mod dir f.rkt f.rkt))
(= (->mod '(/p/t/f.rkt)) (mod dir f.rkt f.rkt)))
;; nonsense input => #f
(= (->mod 42) #f)
(= (->mod '(42 'bar)) #f)
(= (->mod '(submod 42 'bar)) #f)
(= (->mod '(submod (submod "f.rkt" foo) bar)) #f))
(define/contract (->mod/existing v)
(-> any/c (or/c #f mod?))
(match (->mod v)
[(and v (mod dir file mp))
(define path (build-path dir file))
(cond [(file-exists? path) v]
[else (display-commented (format "~a does not exist" path))
#f])]
[_ #f]))
(define/contract (maybe-mod->dir/file/rmp maybe-mod)
(-> (or/c #f mod?) (values absolute-path?
(or/c #f relative-path?)
(or/c #f relative-module-path?)))
(match maybe-mod
[(mod d f mp) (values d f mp)]
[#f (values (current-directory) #f #f)]))
(define/contract (maybe-mod->prompt-string m)
(-> (or/c #f mod?) string?)
(match m
[(mod _ _ (? path? file)) (~a file)]
[(mod _ _ (list* 'submod xs)) (string-join (map ~a xs) "/")]
[#f ""]))
;; Check whether Racket is new enough (newer than 6.2.1) that
;; module->namespace works with module+ and (module* _ #f __)
;; forms when errortrace is enabled.
(module+ check
(define x 42))
(define (can-enter-module+-namespace?)
(define mp (quote-module-path check))
(dynamic-require mp #f)
(with-handlers ([exn:fail? (λ _ #f)])
(eval 'x (module->namespace mp))
#t))
(define warned? #f)
(define/contract (maybe-warn-about-submodules mp context)
(-> (or/c #f module-path?) symbol? any)
(unless (or warned?
(not (pair? mp)) ;not submodule
(memq context '(low medium))
(can-enter-module+-namespace?))
(set! warned? #t)
(display-commented
@~a{Note: @~v[@mp] will be evaluated.
However your Racket version is old. You will be unable to
use the REPL to examine definitions in the body of a module+
or (module* _ #f ___) form when errortrace is enabled. Either
upgrade Racket, or, set the Emacs variable racket-error-context
to 'low or 'medium.})))

View File

@@ -0,0 +1,269 @@
#lang at-exp racket/base
(require racket/contract
racket/file
racket/format
racket/function
racket/list
racket/match
syntax/modread
racket/path
syntax/parse
syntax/strip-context
syntax/stx
(only-in "error.rkt" display-exn)
"mod.rkt"
(only-in "util.rkt" display-commented))
(provide dynamic-require/some-namespace)
;; A composition of dynamic-require and module->namespace that tries
;; to tolerate syntax errors. It tries to return a namespace with at
;; least some identifiers from the file -- such as from module
;; languages, requires, and definitions.
;;
;; Motivation:
;;
;; https://github.com/greghendershott/racket-mode/issues/272
;;
;; You're working in #lang racket/base. You're partway through writing
;; a some expression, and realize you need to add (say)
;; with-module-reading-parameterization. You add syntax/modread to
;; your require.
;;
;; Now, you want to type with-m and hit TAB to complete. Plus after
;; that, you might want to C-. a.k.a. M-x racket-describe to read
;; docs.
;;
;; But you need to re-run, first, for the new require to take effect
;; and make the syntax/modread exports available.
;;
;; But if you re-run, your half-written expression results in a syntax
;; or runtime error. Now your REPL is just an empty racket/base.
;;
;; Annoying!
;;
;; Strategy: When dynamic-require fails, try again using a custom load
;; handler that rewrites the file -- "distill" it to a skeleton of
;; module forms, requires, and define-values. Try again using that.
;;
;; Note that it's important for the skeleton to include submodules,
;; because racket-mode lets you "enter" a submodule and work with
;; identifiers inside it (and only inside it).
(define is-skeleton
"[Due to errors, REPL is just module language, requires, and stub definitions]")
(define is-base
"[Due to errors, REPL is just racket/base]")
;; A composition of dynamic-require and module->namespace, but which
;; tries to tolerate errors in the source file and return _some_
;; namespace more useful than racket/base (if possible).
(define/contract (dynamic-require/some-namespace mod)
(-> mod? namespace?)
(parameterize ([current-load-relative-directory (mod-dir mod)]
[current-directory (mod-dir mod)])
(cond [(normal mod) => values]
[(skeletal mod) => (λ (ns)
(display-commented is-skeleton)
ns)]
[else (display-commented is-base)
(make-base-namespace)])))
(define/contract (normal mod)
(-> mod? (or/c #f namespace?))
(with-handlers ([exn:fail? (λ (e) (display-exn e) #f)])
(dynamic-require (mod-rmp mod) #f)
(module->namespace (mod-rmp mod))))
(define/contract (skeletal mod)
(-> mod? (or/c #f namespace?))
(with-handlers ([exn:fail? (const #f)]) ;don't show errors again
(parameterize ([current-load (make-load mod)]
;; Module is cached in old namespace, so for `load`
;; to be called, we need a fresh namespace.
[current-namespace (make-base-namespace)])
(dynamic-require (mod-rmp mod) #f)
(module->namespace (mod-rmp mod)))))
(define/contract (make-load mod)
(-> mod? any)
(define original-load (current-load))
(define special-path (build-path (mod-dir mod) (mod-file mod)))
(λ (path module-name)
(if (equal? path special-path)
(eval (skeleton (read-module-file path)))
(original-load path module-name))))
(define (read-module-file file) ;Path-String -> Syntax
(with-module-reading-parameterization
(λ ()
(parameterize ([read-accept-compiled #f])
(with-input-from-file file read-syntax)))))
(define no-op-expr #'(void))
(define no-op-def-val #''|Due to errors in source file, this value is from a "stub" define-values|)
(define (skeleton stx) ;Syntax -> Syntax
;; We got here because `stx` has either a syntax error or a runtime
;; error. If it has a syntax error, we can't `expand` it as whole.
;; Let's try to distill it to a skeleton of things that create
;; runtime, module-level bidings: requires and defines.
;;
;; To get #%require and define-values, we want to work with
;; fully-expanded syntax as much as possible. But we have to catch
;; syntax errors and replace each with #'(void). Also we want to
;; walk submodule forms for their bindings, but we can't expand a
;; submodule forms in isolation (that's a syntax error).
;;
;; So, the idea is to preserve the nested modules skeleton, and only
;; try to expand each of their module-level expressions to discover
;; bindings.
;;
;; Our final result should, as a whole, work with (eval (expand)).
(strip-context
;; Unlike expand-syntax-to-top-form, expand-to-top-form does
;; namespace-syntax-introduce before expanding to top form.
(let recur ([stx (expand-to-top-form stx)])
(syntax-parse stx
#:literal-sets (kernel-literals)
#:datum-literals (#%module-begin module+)
;; Note: A #lang file has #%module-begin even on initial read
;; and without calling `expand`. However, a (module) expression
;; file -- even when using with-module-reading-parameterization
;; -- doesn't. That only gets added by `expand`. But we can't
;; use `expand`. Anyway, it hardly matters as we're going to
;; remove everything interesting that a #%module-begin might
;; transform (IIUC). Just treat #%module-begin as begin.
[((~and mod (~or module module*)) name:id lang:expr . es)
#`(mod name lang . #,(stx-map recur #'es))]
[(#%module-begin . es)
#`(begin . #,(stx-map recur #'es))]
[(module+ name:id . es)
#`(module+ name . #,(stx-map recur #'es))]
[_
(let ([stx (with-handlers ([exn:fail:syntax? (const no-op-expr)])
(expand stx))])
(syntax-parse stx
#:literal-sets (kernel-literals)
[(begin . es) #`(begin . #,(stx-map recur #'es))]
[(#%require . _) stx]
[(define-values (id ...) . _) #`(define-values (id ...)
(values
#,@(stx-map (const no-op-def-val)
#'(id ...))))]
[_ no-op-expr]))]))))
(module+ test
(require rackunit
racket/set
version/utils)
;; A example of the transformation we do.
;;
;; Note: Prior to Racket 6.3, expansion of `require` with
;; non-existent modules seems to be a syntax error. So in this test,
;; use modules that actually exist in minimal Racket.
(check-equal? (syntax->datum
(skeleton
#'(module m racket/base
(#%module-begin
(require racket/pretty
racket/list)
(if) ;stx err
(/ 1 0) ;runtime err
(define foo 42)
(define-values (bar baz) (values 43 44))
(define (f x) (+ x 1))
(module* m #f
(require net/url)
(if) ;stx err
(/ 1 0)) ;runtime err
(module+ test
(require rackunit)
(if)) ;stx err
(module m typed/racket/base
(#%module-begin
(require racket/function)
(define id 42)
(if))))))) ;stx err
(let ([no-op-expr (syntax->datum no-op-expr)]
[no-op-def-val (syntax->datum no-op-def-val)])
`(module m racket/base
(begin
(begin (#%require racket/pretty) (#%require racket/list))
,no-op-expr
,no-op-expr
(define-values (foo) (values ,no-op-def-val))
(define-values (bar baz) (values ,no-op-def-val ,no-op-def-val))
(define-values (f) (values ,no-op-def-val))
(module* m #f
(#%require net/url)
(void)
(void))
(module+ test
(#%require rackunit)
,no-op-expr)
(module m typed/racket/base
(begin
(#%require racket/function)
(define-values (id) (values ,no-op-def-val))
,no-op-expr))))))
;; Helpers to write text or sexpr to a tempory .rkt file, then run
;; through dynamic-require/some-namespace and get the
;; namespace-mapped-symbols.
(define/contract (call-with-temporary-file v proc)
(-> any/c (-> mod? any/c) any/c)
(define file #f)
(dynamic-wind
(λ ()
(set! file (make-temporary-file "call-with-temporary-file-~a.rkt"))
(call-with-output-file file #:exists 'replace
(λ (out)
(cond [(string? v) (display v out)]
[else (write v out)]))))
(λ () (proc (->mod/existing file)))
(λ () (delete-file file))))
(define/contract (syms mod)
(-> mod? (listof symbol?))
(namespace-mapped-symbols
(parameterize ([current-namespace (make-base-empty-namespace)])
(dynamic-require/some-namespace mod))))
(define (do v)
(define op (open-output-string))
(define result (parameterize ([current-error-port op])
(call-with-temporary-file v syms)))
(check-match (get-output-string op)
(regexp (string-append (regexp-quote is-skeleton) "\n$")))
result)
;; Despite a syntax error and a runtime error, a binding provided by
;; a require is available in the namespace in both:
;; (a) A #lang file:
(check-not-false
(memq 'pretty-print (do @~a{#lang racket/base
(if)
(require racket/pretty)})))
;; (b) A module expression file:
(check-not-false
(memq 'pretty-print (do `(module m racket/base
(if)
(require racket/pretty)))))
;; Requiring exactly 1 binding adds exactly that symbol to the
;; namespace:
(check-equal? (set-subtract
(list->set
(do `(module m racket/base
(/ 1 0)
(require (only-in racket/pretty pretty-print)))))
(list->set
(do `(module n racket/base
(/ 1 0)))))
(set 'pretty-print)))

View File

@@ -0,0 +1,273 @@
#lang racket/base
(require racket/cmdline
racket/contract/base
racket/contract/region
racket/format
racket/match
racket/pretty
racket/runtime-path
racket/set
racket/string
"channel.rkt"
"command-server.rkt"
(only-in "debug.rkt" make-debug-eval-handler)
"elisp.rkt"
"error.rkt"
"gui.rkt"
"instrument.rkt"
"interactions.rkt"
"logger.rkt"
"mod.rkt"
"namespace.rkt"
(prefix-in stx-cache: "syntax.rkt")
"util.rkt")
;; Main moving parts:
;;
;; 1. This main thread, which receives a couple messages on a channel
;; (see channel.rkt). One message is a `rerun` struct with info
;; about a new file/module to run. The main thread loops forever
;; (the `rerun` function tail calls itself forever). The special
;; case of racket/gui/base is handled with a custom module names
;; resolver and another message.
;;
;; 2. A thread created for each run; loads a module and goes into
;; a read-eval-print-loop.
;;
;; 3. A thread for a command server that listens on a TCP port (see
;; command-server.rkt). One of the commands is a `run` command.
(module+ main
(define-values (command-port launch-token run-info)
(match (current-command-line-arguments)
[(vector port)
(values (string->number port)
#f
rerun-default)]
[(vector port launch-token run-command)
(values (string->number port)
(elisp-read (open-input-string launch-token))
(match (elisp-read (open-input-string run-command))
[(list 'run what mem pp ctx args dbgs)
(rerun (->mod/existing what)
mem
(as-racket-bool pp)
ctx
(list->vector args)
(list->set (map string->path dbgs))
void)]
[v (eprintf "Bad arguments: ~v => ~v\n" run-command v)
(exit)]))]
[v
(eprintf "Bad arguments: ~v\n" v)
(exit)]))
(start-command-server command-port launch-token)
(start-logger-server (add1 command-port) launch-token)
;; Emacs on Windows comint-mode needs buffering disabled.
(when (eq? (system-type 'os) 'windows)
(file-stream-buffer-mode (current-output-port) 'none))
(display (banner))
(flush-output)
(parameterize ([error-display-handler our-error-display-handler])
(run run-info)))
(define (run rr) ;rerun? -> void?
(match-define (rerun maybe-mod
mem-limit
pretty-print?
context-level
cmd-line-args
debug-files
ready-thunk) rr)
(define-values (dir file mod-path) (maybe-mod->dir/file/rmp maybe-mod))
;; Always set current-directory and current-load-relative-directory
;; to match the source file.
(current-directory dir)
(current-load-relative-directory dir)
;; Make src-loc->string provide full pathnames
(show-full-path-in-errors)
;; Custodian for the REPL.
(define repl-cust (make-custodian))
(when (< 0 mem-limit)
(custodian-limit-memory repl-cust
(inexact->exact (round (* 1024 1024 mem-limit)))
repl-cust))
;; If racket/gui/base isn't loaded, the current-eventspace parameter
;; doesn't exist, so make a "dummy" parameter of that name.
(define current-eventspace (txt/gui (make-parameter #f) current-eventspace))
;; Create REPL thread
(define repl-thread
(parameterize* ;; Use `parameterize*` because the order matters.
(;; FIRST: current-custodian and current-namespace, so in
;; effect for later parameterizations.
[current-custodian repl-cust]
[current-namespace (if mod-path
((txt/gui make-base-empty-namespace
make-gui-empty-namespace))
((txt/gui make-base-namespace
make-gui-namespace)))]
;; OTHERS:
[compile-enforce-module-constants #f]
[compile-context-preservation-enabled (not (eq? context-level 'low))]
[current-eval
(cond [(debug-level? context-level) (make-debug-eval-handler debug-files)]
[(instrument-level? context-level)(make-instrumented-eval-handler)]
[else (current-eval)])]
[instrumenting-enabled (instrument-level? context-level)]
[profiling-enabled (eq? context-level 'profile)]
[test-coverage-enabled (eq? context-level 'coverage)]
[current-sync/yield (txt/gui sync yield)]
;; LAST: `current-eventspace` because `make-eventspace`
;; creates an event handler thread -- now. We want that
;; thread to inherit the parameterizations above. (Otherwise
;; in the non-gui case, we call `thread` below in the body of
;; the parameterize* form, so that's fine.)
[current-eventspace ((txt/gui void make-eventspace))])
;; repl-thunk will be called from another thread -- either a plain
;; thread when racket/gui/base is not (yet) instantiated, or, from
;; (eventspace-handler-thread (current-eventspace)).
(define (repl-thunk)
;; 0. Command line arguments
(current-command-line-arguments cmd-line-args)
;; 1. Set current-print and pretty-print hooks.
(current-print (make-print-handler pretty-print?))
(pretty-print-print-hook (make-pretty-print-print-hook))
(pretty-print-size-hook (make-pretty-print-size-hook))
(print-syntax-width +inf.0)
;; 2. If module, require and enter its namespace, etc.
(stx-cache:before-run maybe-mod)
(when (and maybe-mod mod-path)
(parameterize ([current-module-name-resolver module-name-resolver-for-run]
[current-eval (stx-cache:make-eval-handler maybe-mod)])
;; When exn:fail? during module load, re-run with "empty"
;; module. Note: Unlikely now that we're using
;; dynamic-require/some-namespace.
(define (load-exn-handler exn)
(display-exn exn)
(channel-put message-to-main-thread-channel
(struct-copy rerun rr [maybe-mod #f]))
(sync never-evt))
(with-handlers ([exn? load-exn-handler])
(maybe-configure-runtime mod-path) ;FIRST: see #281
(current-namespace (dynamic-require/some-namespace maybe-mod))
(maybe-warn-about-submodules mod-path context-level)
(check-top-interaction))))
(stx-cache:after-run maybe-mod)
;; 3. Tell command server to use our namespace and module.
(attach-command-server (current-namespace) maybe-mod)
;; 3b. And call the ready-thunk command-server gave us from a
;; run command, so that it can send a response for the run
;; command. Because the command server runs on a different
;; thread, it is probably waiting with (sync some-channel) and
;; the thunk will simply channel-put.
(ready-thunk)
;; 4. read-eval-print-loop
(parameterize ([current-prompt-read (make-prompt-read maybe-mod)]
[current-module-name-resolver module-name-resolver-for-repl])
;; Note that read-eval-print-loop catches all non-break
;; exceptions.
(read-eval-print-loop)))
;; Main thread: Run repl-thunk on a plain thread, or, on the
;; eventspace thread via queue-callback. Return the thread.
(define t/v ((txt/gui thread queue-callback ) repl-thunk))
(define thd ((txt/gui (λ _ t/v) eventspace-handler-thread) (current-eventspace)))
thd))
;; Main thread: Wait for message from REPL thread on channel. Also
;; catch breaks, in which case we (a) break the REPL thread so
;; display-exn runs there, and (b) continue from the break instead
;; of re-running so that the REPL environment is maintained.
(define message
(call-with-exception-handler
(match-lambda
[(and (or (? exn:break:terminate?) (? exn:break:hang-up?)) e) e]
[(exn:break msg marks continue) (break-thread repl-thread) (continue)]
[e e])
(λ () (sync message-to-main-thread-channel))))
(match context-level
['profile (clear-profile-info!)]
['coverage (clear-test-coverage-info!)]
[_ (void)])
(custodian-shutdown-all repl-cust)
(newline) ;; FIXME: Move this to racket-mode.el instead?
(match message
[(? rerun? new-rr) (run new-rr)]
[(load-gui repl?) (require-gui repl?) (run rr)]))
(define (maybe-configure-runtime mod-path)
;; Do configure-runtime when available.
;; Important for langs like Typed Racket.
(with-handlers ([exn:fail? void])
(match (module->language-info mod-path #t)
[(vector mp name val)
(define get-info ((dynamic-require mp name) val))
(define configs (get-info 'configure-runtime '()))
(for ([config (in-list configs)])
(match-let ([(vector mp name val) config])
((dynamic-require mp name) val)))]
[_ (void)])
(define cr-submod `(submod
,@(match mod-path
[(list 'submod sub-paths ...) sub-paths]
[_ (list mod-path)])
configure-runtime))
(when (module-declared? cr-submod)
(dynamic-require cr-submod #f))))
(define (check-top-interaction)
;; Check that the lang defines #%top-interaction
(unless (memq '#%top-interaction (namespace-mapped-symbols))
(display-commented
"Because the language used by this module provides no #%top-interaction\n you will be unable to evaluate expressions here in the REPL.")))
;; Catch attempt to load racket/gui/base for the first time.
(define (make-module-name-resolver repl?)
(let ([orig-resolver (current-module-name-resolver)])
(define (resolve mp rmp stx load?)
(when (and load? (memq mp '(racket/gui/base
racket/gui/dynamic
scheme/gui/base)))
(unless (gui-required?)
(channel-put message-to-main-thread-channel
(load-gui repl?))
(sync never-evt)))
(orig-resolver mp rmp stx load?))
(case-lambda
[(rmp ns) (orig-resolver rmp ns)]
[(mp rmp stx) (resolve mp rmp stx #t)]
[(mp rmp stx load?) (resolve mp rmp stx load?)])))
(define module-name-resolver-for-run (make-module-name-resolver #f))
(define module-name-resolver-for-repl (make-module-name-resolver #t))
(define (make-print-handler pretty-print?)
(cond [pretty-print? pretty-print-handler]
[else (make-plain-print-handler)]))
;; Note: The `dynamic-require`s seem to be necessary otherwise
;; file/convertible's convertible? always returns #f. Which seeems to
;; be a namespace issue that I don't understand.
(define-runtime-path image.rkt "image.rkt")
(define (make-plain-print-handler)
(let ([convert (dynamic-require image.rkt 'convert-image)])
(λ (v)
(void (unless (void? v)
(print (convert v))
(newline))))))
(define (make-pretty-print-size-hook [orig (pretty-print-size-hook)])
(let ([convert? (dynamic-require image.rkt 'convert-image?)]
[width (floor (/ (pretty-print-columns) 4))]) ;magic number? yep.
(λ (value display? port)
(cond [(convert? value) width]
[else (orig value display? port)]))))
(define (make-pretty-print-print-hook [orig (pretty-print-print-hook)])
(let ([convert? (dynamic-require image.rkt 'convert-image?)]
[convert (dynamic-require image.rkt 'convert-image)])
(λ (value display? port)
(cond [(convert? value) (print (convert value) port)]
[else (orig value display? port)]))))

View File

@@ -0,0 +1,176 @@
#lang racket/base
(require (only-in html
read-html-as-xml)
racket/file
racket/function
racket/match
scribble/xref
setup/xref
(only-in xml
xml->xexpr
element
xexpr->string))
(provide scribble-doc/html
binding->path+anchor)
;;; Extract Scribble documentation as modified HTML suitable for
;;; Emacs' shr renderer.
(define (scribble-doc/html stx)
(define xexpr (scribble-doc/xexpr stx))
(and xexpr (xexpr->string xexpr)))
(define (scribble-doc/xexpr stx)
(define xexpr (scribble-doc/xexpr-raw stx))
(and xexpr (massage-xexpr xexpr)))
(define (scribble-doc/xexpr-raw stx)
(define-values (path anchor) (binding->path+anchor stx))
(and path anchor (scribble-get-xexpr path anchor)))
(define (binding->path+anchor stx)
(define xref (load-collections-xref))
(define tag (and (identifier? stx)
(xref-binding->definition-tag xref stx 0)))
(cond [tag (xref-tag->path+anchor xref tag)]
[else (values #f #f)]))
(define (scribble-get-xexpr path anchor)
(match (let loop ([es (main-elements (html-file->xexpr path))])
(match es
[(list) (list)]
[(cons (? (curryr anchored-element anchor) this) more)
;; Accumulate until another intrapara with an anchor
(cons this
(let get ([es more])
(match es
[(list) (list)]
[(cons (? anchored-element) _) (list)] ;stop
[(cons this more) (cons this (get more))])))]
[(cons _ more) (loop more)]))
[(list) #f]
[xs `(div () ,@xs)]))
(module+ test
(require rackunit)
(test-case "procedure"
(check-not-false (scribble-doc/xexpr #'print)))
(test-case "syntax"
(check-not-false (scribble-doc/xexpr #'match)))
(test-case "parameter"
(check-not-false (scribble-doc/xexpr #'current-eval)))
(test-case "indented sub-item"
(check-not-false (scribble-doc/xexpr #'struct-out)))
(test-case "deftogether"
(test-case "1 of 2"
(check-not-false (scribble-doc/xexpr #'lambda)))
(test-case "2 of 2"
(check-not-false (scribble-doc/xexpr #'λ))))
(check-not-false (scribble-doc/xexpr #'xref-binding->definition-tag)))
(define (main-elements x)
(match x
[`(x () "\n"
(html ()
(head ,_ . ,_)
(body ,_
(div ([class "tocset"]) . ,_)
(div ([class "maincolumn"])
(div ([class "main"]) . ,es))
. ,_)))
es]
[_ '()]))
;; anchored-element : xexpr? (or/c #f string?) -> (or/c #f string?)
;; When `name` is #f, return the first anchor having any name.
;; Otherwise, return the first anchor having `name`.
(define (anchored-element x [name #f])
(define (anchor xs)
(for/or ([x (in-list xs)])
(match x
[`(a ((name ,a)) . ,_) (or (not name) (equal? name a))]
[`(,tag ,attrs . ,es) (anchor es)]
[_ #f])))
(match x
[`(div ((class "SIntrapara"))
(blockquote ((class "SVInsetFlow"))
(table ,(list-no-order `(class "boxed RBoxed") _ ...)
. ,es)))
;; That's likely sufficient to say we're in HTML resulting from a
;; Scribble defXXX form. From here on out, there can be some
;; variation, so just look recursively for anchors within `es'.
(anchor es)]
[`(blockquote ((class "leftindent"))
(p ())
(div ((class "SIntrapara"))
(blockquote ((class "SVInsetFlow"))
(table ,(list-no-order `(class "boxed RBoxed") _ ...)
. ,es)))
,_ ...)
(anchor es)]
[_ #f]))
(define (html-file->xexpr pathstr)
(xml->xexpr
(element #f #f 'x '()
(read-html-as-xml (open-input-string (file->string pathstr))))))
;; This is a big ole pile of poo, attempting to simplify and massage
;; the HTML so that Emacs shr renders it in the least-worst way.
;;
;; Note: Emacs shr renderer removes leading spaces and nbsp from <td>
;; elements -- which messes up the alignment of s-expressions
;; including contracts. But actually, the best place to address that
;; is up in Elisp, not here -- replace &nbsp; in the HTML with some
;; temporary character, then replace that character in the shr output.
(define (massage-xexpr x)
(define kind-xexprs '())
(define provide-xexprs '())
(define (walk x)
(match x
;; The "Provided" title/tooltip. Set aside for later.
[`(span ([title ,(and s (pregexp "^Provided from:"))]) . ,xs)
(set! provide-xexprs (list s))
`(span () ,@(map walk xs))]
;; The HTML for the "kind" (e.g. procedure or syntax or
;; parameter) comes before the rest of the bluebox. Simple HTML
;; renderers like shr don't handle this well. Set aside for
;; later.
[`(div ([class "RBackgroundLabel SIEHidden"])
(div ([class "RBackgroundLabelInner"]) (p () . ,xs)))
(set! kind-xexprs xs)
""]
;; Bold RktValDef, which is the name of the thing.
[`(a ([class ,(pregexp "RktValDef|RktStxDef")] . ,_) . ,xs)
`(b () ,@(map walk xs))]
;; Kill links. (Often these won't work anyway -- e.g. due to
;; problems with "open" and file: links on macOS.)
[`(a ,_ . ,xs)
`(span () ,@(map walk xs))]
;; Kill "see also" notes, since they're N/A w/o links.
[`(div ([class "SIntrapara"])
(blockquote ([class "refpara"]) . ,_))
`(span ())]
;; Delete some things that produce unwanted blank lines and/or
;; indents in simple rendering engines like Emacs' shr.
[`(blockquote ([class ,(or "SVInsetFlow" "SubFlow")]) . ,xs)
`(span () ,@(map walk xs))]
[`(p ([class "RForeground"]) . ,xs)
`(div () ,@(map walk xs))]
;; Let's italicize all RktXXX classes except RktPn.
[`(span ([class ,(pregexp "^Rkt(?!Pn)")]) . ,xs)
`(i () ,@(map walk xs))]
;; Misc element: Just walk kids.
[`(,tag ,attrs . ,xs)
`(,tag ,attrs ,@(map walk xs))]
[x x]))
(match (walk x)
[`(div () . ,xs)
`(div ()
(span ([style "color: #C0C0C0"])
(i () ,@kind-xexprs)
'nbsp
,@provide-xexprs)
,@xs)]))

View File

@@ -0,0 +1,100 @@
#lang racket/base
(require (only-in compiler/cm [get-file-sha1 file->digest])
racket/contract
racket/match
racket/promise
syntax/modread
"mod.rkt")
(provide file->syntax
file->expanded-syntax
before-run
make-eval-handler
after-run)
;; Return a syntax object or #f for the contents of `file`. The
;; resulting syntax is applied to `k` while the parameters
;; current-load-relative-directory and current-namespace are still set
;; appropriately.
(define/contract (file->syntax file [k values])
(->* (path-string?)
((-> syntax? syntax?))
(or/c #f syntax?))
(define-values (base _ __) (split-path file))
(parameterize ([current-load-relative-directory base]
[current-namespace (make-base-namespace)])
(with-handlers ([exn:fail? (λ _ #f)])
(k
(with-module-reading-parameterization
(λ ()
(with-input-from-file file read-syntax/count-lines)))))))
(define (read-syntax/count-lines)
(port-count-lines! (current-input-port))
(read-syntax))
;;; expanded syntax caching
;; cache : (hash/c file (cons/c digest-string? (or/c promise? syntax?)))
(define cache (make-hash))
(define last-mod #f)
;; Call this early in a file run, _before_ any evaluation. If it's not
;; the same file as before, we empty the cache -- to free up memory.
;; If it's the same file, we keep the cache.
(define (before-run maybe-mod)
(unless (equal? last-mod maybe-mod)
(hash-clear! cache)
(set! last-mod maybe-mod)))
(define ((make-eval-handler maybe-mod [orig-eval (current-eval)]) e)
(cond [(and (syntax? e)
(syntax-source e)
(path-string? (syntax-source e))
(not (compiled-expression? (syntax-e e))))
(define expanded-stx (expand e))
(cache-set! (syntax-source e) (λ () expanded-stx))
(orig-eval expanded-stx)]
[else (orig-eval e)]))
(define (after-run maybe-mod)
;; When the rkt file being run has a compiled zo that was used, then
;; our eval-hander above won't expand and cache any syntax. That
;; means when the user does a command that needs expanded syntax
;; (e.g. find-completion), they will need to wait for expansion. But
;; if you call this _after_ the file was run, it will cache-set! the
;; expansion using `delay/thread` -- i.e. the work will happen "in
;; the background". (Furthermore, when we already have a cache entry
;; for the file and digest, from a previous run, we'll just use
;; that.) As a result, it's likely to be mostly or entirely ready
;; when the user does a command.
(define-values (dir base _) (maybe-mod->dir/file/rmp maybe-mod))
(when (and dir base)
(define path (build-path dir base))
(cache-set! path (λ () (delay/thread (file->syntax path expand))))))
;; cache-set! takes a thunk so that, if the cache already has an entry
;; for the file and digest, it can avoid doing any work. Furthermore,
;; if you already have a digest for file, supply it to avoid redoing
;; that work, too.
(define/contract (cache-set! file thk [digest #f])
(->* (path-string? (-> (or/c promise? syntax?)))
((or/c #f string?))
any)
(let ([digest (or digest (file->digest file))])
(match (hash-ref cache file #f)
[(cons (== digest) _)
(void)]
[_
(hash-set! cache file (cons digest (thk)))])))
(define (file->expanded-syntax file)
(define digest (file->digest file))
(match (hash-ref cache file #f)
[(cons (== digest) promise)
(force promise)]
[_
(define stx (file->syntax file expand))
(cache-set! file (λ () stx) digest)
stx]))

View File

@@ -0,0 +1,55 @@
#lang racket/base
(require racket/contract)
;; Examples for test/defn.rkt.
(define (plain x) x)
(provide plain)
(provide (rename-out [plain renamed]))
(define (contracted1 x) x)
(provide (contract-out [contracted1 (-> any/c any)]))
(define (contracted2 x) x)
(provide/contract [contracted2 (-> any/c any)])
(define (c/r x) x)
(provide (contract-out [rename c/r contracted/renamed (-> any/c any)]))
(define-syntax-rule (plain-definer name)
(begin
(define (name x) x)
(provide name)))
(plain-definer plain-by-macro)
(define-syntax-rule (contracted-definer name)
(begin
(define (name x) x)
(provide (contract-out [name (-> any/c any)]))))
(contracted-definer contracted-by-macro)
;; This is here to try to trip naive matching, by having a definition
;; of `sub` that is not actually provided, unlike the one in the `sub`
;; module just below.
(module red-herring racket/base
(define (sub) #f))
(module sub racket/base
(define (sub x) x)
(provide sub
(rename-out [sub sub/renamed])))
(require 'sub)
(provide sub sub/renamed)
;; Likewise, another case of naive matching:
(module red-herring-2 racket/base
(define (foo) #f))
(define (foo x) x)
(provide foo)
;; Issue 317
(define a-number 42)
(provide a-number)
(define a-parameter (make-parameter #f))
(provide a-parameter)

View File

@@ -0,0 +1,121 @@
#lang at-exp racket/base
(require racket/format
racket/match
racket/runtime-path
rackunit
syntax/modread
"../find.rkt"
"find-examples.rkt")
(define-runtime-path dot-dot "..")
(define-namespace-anchor nsa)
(parameterize ([current-namespace (namespace-anchor->namespace nsa)])
(define (not-0 v) (not (= 0 v)))
(define (not-1 v) (not (= 1 v)))
(check-equal? (find-definition "display")
'kernel)
(check-equal? (find-signature "display")
'("defined in #%kernel, signature unavailable"))
(check-match (find-definition "displayln")
(list (pregexp "/racket/private/misc\\.rkt$")
(? not-1)
(? not-0)))
(check-equal? (find-signature "displayln")
'((displayln v) (displayln v p))) ;case-lambda defn
;; Test a definer macro that (as of Racket 6.7) does not properly
;; set srcloc: Can we at least return a specfic location for its
;; parent syntax (as opposed to line 1 column 0)?
(check-match (find-definition "in-hash")
(list (pregexp "/racket/private/for.rkt$")
(? not-1)
(? not-0)))
;; Tests for specific locations in find-examples.rkt
(check-match (find-definition "plain")
(list (pregexp "find-examples.rkt$") 7 9))
(check-equal? (find-signature "plain")
'(plain x))
(check-match (find-definition "renamed")
(list (pregexp "find-examples.rkt$") 7 9))
(check-equal? (find-signature "renamed")
'(plain x))
(check-match (find-definition "contracted1")
(list (pregexp "find-examples.rkt$") 11 9))
(check-equal? (find-signature "contracted1")
'(contracted1 x))
(check-match (find-definition "contracted2")
(list (pregexp "find-examples.rkt$") 13 9))
(check-equal? (find-signature "contracted2")
'(contracted2 x))
(check-match (find-definition "contracted/renamed")
(list (pregexp "find-examples.rkt$") 16 9))
(check-equal? (find-signature "contracted/renamed")
'(c/r x))
(check-match (find-definition "plain-by-macro")
(list (pregexp "find-examples.rkt$") 23 15))
(check-false (find-signature "plain-by-macro"))
(check-match (find-definition "contracted-by-macro")
(list (pregexp "find-examples.rkt$") 29 20))
(check-false (find-signature "contracted-by-macro"))
(check-match (find-definition "sub")
(list (pregexp "find-examples.rkt$") 38 11))
(check-equal? (find-signature "sub")
'(sub x))
(check-match (find-definition "sub/renamed")
(list (pregexp "find-examples.rkt$") 38 11))
(check-equal? (find-signature "sub/renamed")
'(sub x))
(check-match (find-definition "foo")
(list (pregexp "find-examples.rkt$") 48 9))
(check-equal? (find-signature "foo")
'(foo x))
(check-match (find-definition "a-number")
(list (pregexp "find-examples.rkt$") 52 8))
(check-match (find-definition "a-parameter")
(list (pregexp "find-examples.rkt$") 54 8))
;; This is (roughly) a test of opening a Racket source file and
;; doing M-. on every non-list sexpr: Call find-definition on each
;; sexpr. Not-found (#f) is fine. But fail test for (list _ 1 0) --
;; i.e. the source file was found, but not the location within.
(define (check-non-bof-location file)
(define ht (make-hash))
(define (find k) ;memoized find-definition
(hash-ref ht k
(λ ()
(define v (find-definition (format "~a" k)))
(hash-set! ht k v)
v)))
(define (walk v)
(if (list? v)
(for-each walk v)
(match (find v)
[(list where 1 0)
(fail @~a{can't find definition of `@|v|` in @where})]
[_ (void)])))
(walk
(with-module-reading-parameterization
;; Why read not read-syntax? Because we only care about the
;; sexprs as text: `find-definition` takes a string, because
;; `racket-visit-definition` takes text from an Emacs buffer.
(λ () (with-input-from-file file read)))))
(for ([file '("commands/requires.rkt"
"run.rkt")])
(check-non-bof-location (build-path dot-dot file))))

View File

@@ -0,0 +1,39 @@
#lang racket/base
(require (for-syntax racket/base
syntax/parse))
(provide display-commented
with-dynamic-requires
string->namespace-syntax
syntax-or-sexpr->syntax
syntax-or-sexpr->sexpr
nat/c
pos/c)
(define (display-commented str)
(eprintf "; ~a\n"
(regexp-replace* "\n" str "\n; ")))
(define-syntax (with-dynamic-requires stx)
(syntax-parse stx
[(_ ([lib:id id:id] ...+) body:expr ...+)
#'(let ([id (dynamic-require 'lib 'id)] ...)
body ...)]))
(define (string->namespace-syntax str)
(namespace-syntax-introduce
(read-syntax #f (open-input-string str))))
(define (syntax-or-sexpr->syntax v)
(if (syntax? v)
v
(namespace-syntax-introduce (datum->syntax #f v))))
(define (syntax-or-sexpr->sexpr v)
(if (syntax? v)
(syntax-e v)
v))
(define nat/c exact-nonnegative-integer?)
(define pos/c exact-positive-integer?)