Hi all,
Emacs user here, but there's a feature I'd like to have for DrRacket users and could use some help with it: I'd like to be able to evaluate an expression on demand.
This feature would be generally useful, but I am looking for it in particular because I'm writing an interactive style of tutorial for the Qi language, and this relies on the ability to run source expressions one at a time. In Emacs, you can typically do this using the keybinding C-x C-e
.
More in detail: DrRacket already highlights expressions based on the location of the cursor. These highlighted expressions are precisely the ones I'd like to be able to evaluate on demand. That is, probably, the expression should be pasted into and executed in the interactions window to produce the result, and it shouldn't require the user to select the expression (as it's already highlighted) or visit the interactions window. I asked @spdegabrielle about this and he was of the opinion that it should be possible to do using a quickscript.
So my question is: is this straightforward to do, and would you be willing to write / maintain this script? A DrRacket user maintaining it seems ideal, and they would have a better idea of conventions and possible improvements that could be made. Otherwise, even just pointers on how to do it would be appreciated and I don't mind writing/maintaining it either.
Any help appreciated!
-Sid
1 Like
The Racket docs actually have exactly what you want in the form of keybindings.
This can also be done easily as a quickscript with added menu items (and also key shortcuts)---let me know if you'd prefer this option and I'll be happy to help.
2 Likes
@Laurent.O That docs solution does almost what I want! But not quite: it sends either (a) the entire toplevel form, or (b) the selection if you have selected some text. I'm ideally looking for a way to just send the inferred expression based on the cursor position, which it highlights automatically if it is a form (but not if it's an identifier or literal), so that seems to indicate that DrRacket "knows" about the expression already. Not sure if there's a way to get that from an API?
In either case (even as is) I do think this would be nicer as a quickscript to avoid the manual steps (and also gain the other conveniences) - and sounds like it's a commonly requested feature already!
Alright, a quickscript it is then
When you say "the inferred expression", should it be a) the enclosing sexp, b) the backward sexp, c) the forward sexp, d) something else?
All of these are easy to obtain with the find-up-sexp
methods and friends.
3 Likes
Nice! Any of those would probably have been fine, except that it would be confusing for the user if it didn't match the highlighted expression. Playing around with it a bit, I think this is the logic:
In order of priority:
If before an opening delimiter, use the next expression.
If after a closing delimiter, use the previous expression.
If after an opening delimiter, use the next expression.
If before a closing delimiter, use the previous expression.
If before and after whitespace (newline/space/tab/eof), use the enclosing expression.
If before whitespace (newline/space/tab/eof), use the previous expression.
If after whitespace (newline/space/tab), use the next expression.
Otherwise the enclosing expression.
That seems intuitive / consistent with the idea behind the highlighting as far as I was able to see.
WDYT?
1 Like
Here's a first attempt, to be refined.
Notes:
-
Currently it only selects the sexp rather than sending it to the interactions, just to make it easy to test
-
By default it binds Control+Enter. Not sure if this is a smart choice. I wanted Shift-Enter, but it's bound to do-return
and I couldn't rebind it with Quickscript apparently.
-
Rules in order of priority:
- If a range is already selected, don't try to select anything else
- If DrRacket highlights a sexp, use that (ensures consistency)
- if whitespace left and right, try in order to select the sexp: up, right, and finally left.
(That is, we first try to select the sexp up, and if it fails (no sexp there) then select right, and if fails select left.)
- if whitespace left, try: right, up, or left
- if whitespace right: left, up, or right
- otherwise, try: up, right, or left
How does this sound?
1 Like
That looks and sounds great! I noticed one thing:
With (|(abc) (def))
it uses (abc)
, but with (|abc def)
it uses the entire expression.
And likewise,
With ((abc) (def)|)
it uses (def)
, but with (abc def|)
it uses the entire expression.
To handle these in the same way I added:
(cond
[left-opening? (try-right)]
[right-closing? (try-left)]
...
to the conditions (although I think it should be try-right, try-up, try-left and try-left, try-up, try-right to match the failsafes you have in the other conditions), with these supporting expressions:
(define (opening-delimiter? c)
(member c (list #\( #\[)))
(define (closing-delimiter? c)
(member c (list #\) #\])))
(define left-opening? (opening-delimiter? char-left))
(define right-closing? (closing-delimiter? char-right))
Gist updated.
New:
- your improvements
- added #{ #} to the delimiters
- 2 scripts functions: one to select the sexp (because I think that can be useful too) on ctl+return, one to send it to the interactions and give it the focus, on ctl-shift-return
- added to the wiki list
Last remaining case:
(+ a b)|(+ b c)
DrRacket actually highlights both expressions. Currently the r.h.s. expression (+ b c )
is selected by the algorithm. Probably a good default?
Let me know if it needs more tweaking.
Almost there! I'd like to be able to send the expression and have it be evaluated while still retaining focus in the Definitions window -- the idea is that the user can evaluate many source expressions/subexpressions at a stretch to check their understanding of different components. Is there a way to do that without manually hitting Enter in the Interactions window? Simply adding a "\n" or "\r" didn't seem to work...
It should work now. The magic method is do-submission
, which I only found out about because of the example in the docs referenced at the top.
One caveat is that when an exception is raised, I can't give the focus back to the definitions window.
2 Likes
This is perfect, thank you @Laurent.O ! re: exception behavior, yeah I'm not too worried about that.
2 Likes
I've added it to the README for the tutorial. Looks like GitHub is having an outage at the moment so not sure if you'll be able to see it. Let me know if it's missing anything
1 Like
Nice!
FWIW, Quickscript is already installed with DrRacket nowadays (but not quickscript-extra).
Also, I encourage you to use an actual license (such as MIT, Apache2, or dual, which is what Racket uses), as it actually increases reusability, because the terms are clear and known. As an example, some big companies will not use code that isn't properly licensed (and you can assume they don't take the matter lightly).
1 Like
Ah, good to know! Re: license, whenever there is a query on any of my repos I use a COPYING file indicating that the repo is public domain and copyright-free, which is a convention used by the GNU community for public domain work, for which licenses are not applicable. I haven't adopted that convention widely but since it has come up a few times maybe I should.
1 Like