Skip to content

Commit

Permalink
Add arguments to custom repl commands
Browse files Browse the repository at this point in the history
Allso adds default commands for tapping form and toplevel
Fixes #1011
Fixes #1008
  • Loading branch information
PEZ committed Feb 4, 2021
1 parent c92b105 commit b10c062
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 44 deletions.
50 changes: 40 additions & 10 deletions docs/site/custom-commands.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Running Custom REPL Commands
# Custom REPL Commands

Calva supports configuration of custom command snippets that you can execute in the REPL at will. If your workflow has you repeatedly evaluate a particular piece of code, you can use the setting `calva.customREPLCommandSnippets` to configure it and then use the command **Run Custom REPL Command** to access it. The command will give you a menu with the snippets you have configured.
Calva supports configuration of custom command snippets that you can evaluate in the REPL at will. If your workflow has you repeatedly evaluate a particular piece of code, you can use the setting `calva.customREPLCommandSnippets` to configure it. Then either bind keyboard shortcuts to them or use the command **Run Custom REPL Command** to access it. The command will give you a menu with the snippets you have configured.

The `calva.customREPLCommandSnippets` is an array of objects with the following fields:
The `calva.customREPLCommandSnippets` is an array of objects with the following fields (required fields in **bold**):

* `name`: The name of the snippet as it will appear in the picker menu
* `snippet`: The code that will be evaluated
* `ns`: (optional) Namespace to evaluate the command in. If omitted the command will be executed in the namespace of the current editor.
* `repl`: Which repl session to use for the evaluation. Either `"clj"` or `"cljs"`
* **`name`**: The name of the snippet as it will appear in the picker menu
* **`snippet`**: The code that will be evaluated
* `key`: A key can be used to reference the snippet from **Run Custom REPL Command** keyboard shortcut arguments. It will also be used in the quick-pick menu.
* `ns`: A namespace to evaluate the command in. If omitted the command will be executed in the namespace of the current editor.
* `repl`: Which repl session to use for the evaluation. Either `"clj"` or `"cljs"`. Omit if you want to use the session of the current editor.

There are also substitutions available, which will take elements from the current state of Calva and splice them in to the text of your command before executing it. They are

Expand Down Expand Up @@ -38,6 +39,7 @@ Consider these settings:
},
{
"name": "Refresh",
"key": "r",
"snippet": "(refresh)",
"repl": "clj"
},
Expand All @@ -48,11 +50,15 @@ Consider these settings:
},
{
"name": "Call Current Top Level Form",
"key": "call-t",
"repl": "clj",
"snippet": "($top-level-form)"
},
{
// You don't need to configure this one,
// there is a built-in command for tapping the current form
"name": "Tap Current Form",
"key": "tap",
"repl": "clj",
"snippet": "(tap> $current-form)"
},
Expand All @@ -70,10 +76,34 @@ Consider these settings:
```


Issuing **Run Custom REPL Command** will render this VS Code menu:
Issuing **Run Custom REPL Command** will then render this VS Code menu:

![](images/custom-command-menu.png)

The items are numbered for you so that you can choose them in predictable way. The default keyboard shortcut for the command is `ctrl+alt+space`. Which means that to execute the **Tap Current Form** custom command, you could do:
The items are numbered for you so that you can choose them in predictable way. The default keyboard shortcut for the command is `ctrl+alt+space`. Which means that to execute the **Call Current Form** custom command, you could do:

`ctrl+alt+space 6 enter`.
`ctrl+alt+space 4 enter`.

## Binding keyboard shortcuts

Some custom REPL commands might be so central to your workflow that you want to bind keyboard shortcuts to them directly. There are two ways to do this:

1. Bind `calva.runCustomREPLCommand` to a shortcut with whatever code you want to evaluate in `args` key. You have access to the substitution variables here as well.
2. Bind `calva.runCustomREPLCommand` to a shortcut referencing the `key` of on of your `calva.customREPLCommandSnippets`.

Given the above example settings, here are two shortcut definitions that both `tap>` the current form:

```json
{
"key": "ctrl+alt+shift+t t",
"command": "calva.runCustomREPLCommand",
"args": "(tap> $current-form)"
},
{
"key": "ctrl+alt+shift+t t",
"command": "calva.runCustomREPLCommand",
"args": "tap"
}
```

(Again, none of them is needed, the built in command for tapping the current form is default bound to the shortcut `ctrl+shift+t t`.)
Binary file modified docs/site/images/custom-command-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 23 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,13 @@
},
{
"command": "calva.evaluateSelection",
"title": "Evaluate Current Form",
"title": "Evaluate Current Form (or selection, if any)",
"enablement": "calva:connected",
"category": "Calva"
},
{
"command": "calva.tapSelection",
"title": "Tap Current Form (or selection, if any)",
"enablement": "calva:connected",
"category": "Calva"
},
Expand All @@ -696,6 +702,12 @@
"enablement": "calva:connected",
"category": "Calva"
},
{
"command": "calva.tapCurrentTopLevelForm",
"title": "Tap Current Top Level Form",
"enablement": "calva:connected",
"category": "Calva"
},
{
"command": "calva.evaluateSelectionReplace",
"title": "Evaluate Current Form and Replace it with the Result",
Expand Down Expand Up @@ -1258,6 +1270,16 @@
"key": "ctrl+enter",
"when": "calva:keybindingsEnabled && editorTextFocus"
},
{
"command": "calva.tapSelection",
"key": "ctrl+shift+t t",
"when": "calva:keybindingsEnabled && editorTextFocus"
},
{
"command": "calva.tapCurrentTopLevelForm",
"key": "ctrl+shift+t space",
"when": "calva:keybindingsEnabled && editorTextFocus"
},
{
"command": "calva.interruptAllEvaluations",
"key": "ctrl+alt+c ctrl+alt+d",
Expand Down
78 changes: 49 additions & 29 deletions src/custom-snippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import * as _ from 'lodash';
import * as state from './state';
import * as util from './utilities';
import * as namespace from './namespace';
import * as outputWindow from './results-output/results-doc'
import { customREPLCommandSnippet, evaluateInOutputWindow } from './evaluate';
import { forEach } from 'lodash';

export async function evaluateCustomCommandSnippetCommand(): Promise<void> {
export async function evaluateCustomCodeSnippetCommand(codeOrKey?: string) {
await evaluateCustomCodeSnippet(codeOrKey);
outputWindow.appendPrompt();
};

async function evaluateCustomCodeSnippet(codeOrKey?: string): Promise<void> {
const editor = vscode.window.activeTextEditor;
const currentLine = editor.selection.active.line;
const currentColumn = editor.selection.active.character;
Expand All @@ -15,7 +20,12 @@ export async function evaluateCustomCommandSnippetCommand(): Promise<void> {
let configErrors: { "name": string; "keys": string[]; }[] = [];
const snippets = state.config().customREPLCommandSnippets as customREPLCommandSnippet[];
const snippetsDict = {};
const snippetsKeyDict = {};
const snippetsMenuItems: string[] = [];
const editorNS = editor && editor.document &&
editor.document.languageId === 'clojure' ? namespace.getNamespace(editor.document) : undefined;
const editorRepl = editor && editor.document &&
editor.document.languageId === 'clojure' ? namespace.getREPLSessionType() : "clj";
snippets.forEach((c: customREPLCommandSnippet) => {
const undefs = ["name", "snippet"].filter(k => {
return !c[k];
Expand All @@ -24,45 +34,55 @@ export async function evaluateCustomCommandSnippetCommand(): Promise<void> {
configErrors.push({ "name": c.name, "keys": undefs });
}
const entry = { ...c };
const editorNS = editor && editor.document && editor.document.languageId === 'clojure' ? namespace.getNamespace(editor.document) : undefined;
entry.ns = c.ns ? c.ns : editorNS;
const editorRepl = editor && editor.document && editor.document.languageId === 'clojure' ? namespace.getREPLSessionType() : "clj";
entry.repl = c.repl ? c.repl : editorRepl;

entry.ns = entry.ns ? entry.ns : editorNS;

entry.repl = entry.repl ? entry.repl : editorRepl;
pickCounter++;
const prefix = c.key !== undefined ? c.key : pickCounter;
const prefix = entry.key !== undefined ? entry.key : pickCounter;
const item = `${prefix}: ${entry.name} (${entry.repl})`;
snippetsMenuItems.push(item);
snippetsDict[item] = entry;
snippetsKeyDict[entry.key] = item;
});

if (configErrors.length > 0) {
vscode.window.showErrorMessage("Errors found in the `calva.customREPLCommandSnippets` setting. Values missing for: " + JSON.stringify(configErrors), "OK");
return;
}

if (snippets && snippets.length > 0) {
try {
const pick = await util.quickPickSingle({
values: snippetsMenuItems,
placeHolder: "Choose a command to run at the REPL",
saveAs: "runCustomREPLCommand"
});
if (pick && snippetsDict[pick] && snippetsDict[pick].snippet) {
const command = snippetsDict[pick].snippet.
replace("$line", currentLine).
replace("$column", currentColumn).
replace("$file", currentFilename).
replace("$ns", snippetsDict[pick].ns).
replace("$current-form", util.currentFormText(editor, false)).
replace("$top-level-form", util.currentFormText(editor, true)).
replace("$current-fn", util.currentFunction(editor)).
replace("$top-level-defined-symbol", util.currentTopLevelFunction(editor));
await evaluateInOutputWindow(command, snippetsDict[pick].repl, snippetsDict[pick].ns);
let pick: string;
if (codeOrKey === undefined) { // Without codeOrKey always show snippets menu
if (snippetsMenuItems.length > 0) {
try {
pick = await util.quickPickSingle({
values: snippetsMenuItems,
placeHolder: "Choose a command to run at the REPL",
saveAs: "runCustomREPLCommand"
});
} catch (e) {
console.error(e);
}
} catch (e) {
console.error(e);
}
} else {
vscode.window.showInformationMessage("No snippets configured. Configure snippets in `calva.customREPLCommandSnippets`.", ...["OK"]);
if (pick === undefined) {
outputWindow.append("; No snippets configured. Configure snippets in `calva.customREPLCommandSnippets`.");
return;
}
}
if (pick === undefined) { // still no pick, but codeOrKey might be one
pick = snippetsKeyDict[codeOrKey];
}
const code = pick !== undefined ? snippetsDict[pick].snippet : codeOrKey;
const ns = pick !== undefined ? snippetsDict[pick].ns : editorNS;
const repl = pick !== undefined ? snippetsDict[pick].repl : editorRepl;
const interpolatedCode = code.
replace("$line", currentLine).
replace("$column", currentColumn).
replace("$file", currentFilename).
replace("$ns", ns).
replace("$current-form", util.currentFormText(editor, false)).
replace("$top-level-form", util.currentFormText(editor, true)).
replace("$current-fn", util.currentFunction(editor)).
replace("$top-level-defined-symbol", util.currentTopLevelFunction(editor));
await evaluateInOutputWindow(interpolatedCode, repl, ns);
}
7 changes: 3 additions & 4 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,9 @@ async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('calva.refresh', refresh.refresh));
context.subscriptions.push(vscode.commands.registerCommand('calva.refreshAll', refresh.refreshAll));
context.subscriptions.push(vscode.commands.registerCommand('calva.debug.instrument', eval.instrumentTopLevelForm));
context.subscriptions.push(vscode.commands.registerCommand('calva.runCustomREPLCommand', async () => {
await snippets.evaluateCustomCommandSnippetCommand();
outputWindow.appendPrompt();
}));
context.subscriptions.push(vscode.commands.registerCommand('calva.runCustomREPLCommand', snippets.evaluateCustomCodeSnippetCommand));
context.subscriptions.push(vscode.commands.registerCommand('calva.tapSelection', () => snippets.evaluateCustomCodeSnippetCommand("(tap> $current-form)")));
context.subscriptions.push(vscode.commands.registerCommand('calva.tapCurrentTopLevelForm', () => snippets.evaluateCustomCodeSnippetCommand("(tap> $top-level-form)")));
context.subscriptions.push(vscode.commands.registerCommand('calva.showOutputWindow', () => { outputWindow.revealResultsDoc(false) }));
context.subscriptions.push(vscode.commands.registerCommand('calva.showFileForOutputWindowNS', () => { outputWindow.revealDocForCurrentNS(false) }));
context.subscriptions.push(vscode.commands.registerCommand('calva.setOutputWindowNamespace', outputWindow.setNamespaceFromCurrentFile));
Expand Down

0 comments on commit b10c062

Please sign in to comment.