BetterThanTomorrow / calva

Clojure & ClojureScript Interactive Programming for VS Code
https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva
Other
1.68k stars 217 forks source link

Binding keys to REPL functions, passing the namespace and cursor line (Notespace integration) #863

Closed daslu closed 3 years ago

daslu commented 3 years ago

Many thanks for Calva!

Background (you can skip reading this part)

In the last few weeks, a few of us started using Notespace for literate programming. In Emacs+Cider, it is easy to interact with Notespace, as we can comfortably bind keys to Notespace API function calls, and even provide these calls with the important information of what line the cursor is in. We want to create a more complete Notespace story for Calva as well. This brings us to the following need.

A feature request

bpringe commented 3 years ago

Interesting! A PR is welcome.

daslu commented 3 years ago

Related discussion: https://clojureverse.org/t/extending-calva-with-a-repl-function/

behrica commented 3 years ago

Maye a simple approach could be that the snippets for configuring the custom commands can have some "special character sequences" which get replaced by Calva before calling the snippet:

"calva.customREPLCommandSnippets": [
        {
            "name": "print-line-number",
            "snippet": "(println $line)",
            "repl": "clj"
        },
    ]

For the case of notespace we would only need 2: $line - current line number
$ns - current name space // true ? The notespace functions work on "*ns*"

-> a kind of editing context

https://github.com/scicloj/notespace/blob/a3758b631420d093ff2d342dd5ee3b98feba3905/emacs-config.el#L14 would become

        {
            "name": "eval-and-realize-note-at-line",
            "snippet": "(notespace.api/eval-and-realize-note-at-line $line)",
            "repl": "clj"
        },
    ]

Not sure, if something else makes sense (current column ?, current xxx ?)

Somewho information on the current "state" of calva, which a Clojure function itself cannot get from nowhere else.

behrica commented 3 years ago

Not sure, if something else makes sense (current column ?, current xxx ?)

  • current file name ?
  • cider port ?
  • cider host ? ...
behrica commented 3 years ago

It works in principle with a trivial change to Calva:

@@ -375,11 +375,13 @@ async function evaluateCustomCommandSnippetCommand(): Promise<void> {
                 saveAs: "runCustomREPLCommand"
             });
             if (pick && snippetsDict[pick] && snippetsDict[pick].snippet) {
-                const command = snippetsDict[pick].snippet;
+                var command = snippetsDict[pick].snippet;
                 const editor = vscode.window.activeTextEditor;
+                var currentLine = editor.selection.active.line
                 const editorNS = editor && editor.document && editor.document.languageId === 'clojure' ? namespace.getNamespace(editor.document) : undefined;
                 const ns = snippetsDict[pick].ns ? snippetsDict[pick].ns : editorNS;
                 const repl = snippetsDict[pick].repl ? snippetsDict[pick].repl : "clj";
+                command = command.replace("$line",currentLine);
                 await evaluateInOutputWindow(command, repl ? repl : "clj", ns);
             }
         } catch (e) {

and a custom command setting like thgis:

{
            "name": "eval-and-realize-note-at-line",
            "snippet": "(notespace.api/eval-and-realize-note-at-line $line)",
            "repl": "clj" 
        }
behrica commented 3 years ago

@daslu This change would be enough for all commands here: https://github.com/scicloj/notespace/blob/master/emacs-config.el

A namespace is not really needed. But it would be as well available in variable editorNS.

Your example Emacs configuration saves as well the file.

VS code has a built-in autosave feature instead.

Both together would bring (nearly) the same notespace experience to Calva.

The custom commands can not be bound freely to keystrokes, but are all available as 'Ctrl-Alt-c 1 " 2 / 3 /...

daslu commented 3 years ago

@behrica that looks wonderful. I will try it soon.

daslu commented 3 years ago

@behrica this works beautifully.

It seems kind of analogous to the Cursive substitutions in REPL commands: https://cursive-ide.com/userguide/repl.html

@bpringe what do you think?

behrica commented 3 years ago

@behrica this works beautifully.

It seems kind of analogous to the Cursive substitutions in REPL commands: https://cursive-ide.com/userguide/repl.html

Indeed the same idea. But Cursive does not have "line number". But we took take inspiration from there, which substitutions can be useful, more general then for notespace only

PEZ commented 3 years ago

Awesome, @behrica! Care to provide a PR with those changes? I can pick up the torch from here, but it is more fun if you PR it. 😄

I'm thinking all your suggestions are good. Maybe start with line, column, file? And ns for convenience? $1-$9 for arguments that Calva will prompt for?

PEZ commented 3 years ago

I also suspect Calva currently messes up *1-*3 for the snippets. Would be nice to be able to use those reliably too.

behrica commented 3 years ago

I will do an initial change request, as you proposed. But I am an absolute beginner in Typescript / Javascript... (and Calva ...)

Do you have a preference for the "special character" to mark the text substitutions ? "$" vs "~" ?

It should be something, which is "impossible" to be part of any normal Clojure syntax. "$" is valid in Clojure to denote inner classes, while "~" is only valid as part of macro definitions.

So maybe "~" is even less likely to be used for an other purpose in the substitution.

I will document this as well somewhere then: https://github.com/BetterThanTomorrow/calva/blob/master/docs/site/custom-commands.md

PEZ commented 3 years ago

Christmas comes early! 🎄 Awesome that you are planning to include docs. Both @bpringe and I will be ready to answer any Calva dev questions. Extra quickly if you're on the Clojurian Slack and the #calva channel there. As for TypeScript, I'm no expert, but of course ready to help with those questions as well, should they arise. Not a total beginner, at least. 😄 Same goes for vscode. Also if you find gaps in the How to Hack on Calva stuff, please let us know. We truly want Calva to be contributor friendly.

As for special character. This is always tricky with templating, right? As for those two alternatives.

Maybe go for something that is not just ”impossible”. Like 0_, even if that could look a bit weird...

What I sometimes do is work Documentation first, so I write the docs and see if they make sense to me and others. Then implement. Maybe that could help here as well.

Another thing that has been bothering me regarding the snippets is that they are pretty cumbersome to write inside JSON settings. Would make more sense with an EDN file in the .calva directory. But maybe that is stuff for a separate PR. 😄

bpringe commented 3 years ago

@daslu This looks good to me, taking into account what @PEZ said above for the special character. We definitely don't want to have to change it later because of some conflict with Clojure, because that seems like an unavoidable breaking change.

Another thing that has been bothering me regarding the snippets is that they are pretty cumbersome to write inside JSON settings. Would make more sense with an EDN file in the .calva directory. But maybe that is stuff for a separate PR.

That's a good idea, but I agree, another PR. :smile:

behrica commented 3 years ago

Christmas comes early! Awesome that you are planning to include docs. Both @bpringe and I will be ready to answer any Calva dev questions. Extra quickly if you're on the Clojurian Slack and the #calva channel there. As for TypeScript, I'm no expert, but of course ready to help with those questions as well, should they arise. Not a total beginner, at least. Same goes for vscode. Also if you find gaps in the How to Hack on Calva stuff, please let us know. We truly want Calva to be contributor friendly.

As for special character. This is always tricky with templating, right? As for those two alternatives.

  • Would inner classes be used in snippets? (I don't know enough about it to have a guess)
  • ~ can be used outside macros, right? unquote-splicing

Maybe go for something that is not just ”impossible”. Like 0_, even if that could look a bit weird...

I propose to stick to the "$line".

In any case the subsitution sequence would be "$line", so a single "$" would not be touched.

This would mean, it only fails if somebody refers to a Java inner class which is called "line...." This is pretty unlikely to exist. On top it would fail with an obvious error message

Java naming conventions dictate that class names should begin with an upper case letter

PEZ commented 3 years ago

How about people using things like $ and $something for binding in as-> threading? Not sure if anything else but $ is used. In any case, this would be documented, and to instruct people to refrain from using such binding names in their snippet code is pretty OK, I think. I now vote for $line, etcetera.

daslu commented 3 years ago

Thanks for your kind help, @PEZ @bpringe @behrica .