Skip to content

Commit 38d6286

Browse files
committed
Merge branch 'dev'
2 parents 132097a + ac2d9bc commit 38d6286

File tree

10 files changed

+157
-38
lines changed

10 files changed

+157
-38
lines changed

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Change Log
22
Changes to Calva.
33

4-
## When time allows, this will be worked on
5-
- [Support for custom project/workflow commands](https://github.com/BetterThanTomorrow/calva/issues/281)
6-
74
## [Unreleased]
85

6+
## [2.0.44] - 05.10.2019
7+
- [Support for custom project/workflow commands](https://github.com/BetterThanTomorrow/calva/issues/281)
8+
99
## [2.0.43] - 03.10.2019
1010
- [Insourcing @tonsky's Clojue Warrior, now named Calva Highlight](https://github.com/BetterThanTomorrow/calva/pull/362)
1111
- [Update status bar when configuration changed](https://github.com/BetterThanTomorrow/calva/issues/358)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Demo: switch between `clj` and `cljs` repl sessions for `cljc` files:
9797
- Evaluate code from the editor to the REPL window: `ctrl+alt+c ctrl+alt+e` (`ctrl+alt+c ctrl+alt+v` on Windows)
9898
- When editing `cljc` files, easily choose if REPL commands should go to the `clj` or `cljs` REPL by clicking the `cljc/clj[s]` indicator in the status bar.
9999
- Selection of current form: `ctrl+alt+c s`. Auto-detected the same way as for evaluation. Will select the form preceding or following the cursor first, otherwise the form the cursor is inside. (Only when the cursor is directly adjacent to any bracket so far.)
100+
- Configure and run custom commands, i.e. code snippets, at will: `ctrl+alt+c .`
100101

101102
Demo: Peek at definitions, etcetera:
102103

docs/integration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ Smaller changes:
3535

3636
When a VSIX is good enough for release, and someone authorized to commit to the `master` branch has _at least half an hour of spare time_, the following will bring it to the Marketplace:
3737

38-
1. With `dev` checked out: `git checkout -B master`. (This ”moves” `master` to where `dev`'s `HEAD` is pointing.)
38+
1. With `dev` checked out: `git checkout master`.
39+
1. `git merge dev`
3940
1. Tag with `v<VERSION>`
4041
1. Push `master` (Using `--follow-tags`).
4142
* This will build the release VSIX, push a relase to GitHub, and publish it on the extension Marketplace.

package.json

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Calva: Clojure & Clojurescript Interactive Programming",
44
"description": "Integrated REPL, formatter, Paredit, and more. Powered by nREPL.",
55
"icon": "assets/calva.png",
6-
"version": "2.0.43",
6+
"version": "2.0.44",
77
"publisher": "betterthantomorrow",
88
"author": {
99
"name": "Better Than Tomorrow",
@@ -215,6 +215,46 @@
215215
"default": true,
216216
"description": "Should Calva open the Figwheel app for you when Figwheel has been started?"
217217
},
218+
"calva.customREPLCommandSnippets": {
219+
"type": "array",
220+
"default": [],
221+
"description": "Configuration for the command **Run Custom REPL Command**",
222+
"$schema": "http://json-schema.org/draft-06/schema#",
223+
"items": {
224+
"title": "replCommand",
225+
"type": "object",
226+
"properties": {
227+
"name": {
228+
"type": "string",
229+
"description": "Name this command so that it is easy to pick from the menu."
230+
},
231+
"snippet": {
232+
"type": [
233+
"string",
234+
"array"
235+
],
236+
"description": "Command to send to the REPL"
237+
},
238+
"ns": {
239+
"type": "string",
240+
"description": "(optional) Namespace to evaluate the command in. If ommitted the command will be executed in whatever namespace the REPL window has at the moment, which probably is mostly useful for running code using definitions from the `user` namespace."
241+
},
242+
"repl": {
243+
"type": "string",
244+
"description": "Choose which REPL should the code should be evaluated in.",
245+
"enum": [
246+
"clj",
247+
"cljs"
248+
]
249+
}
250+
},
251+
"required": [
252+
"name",
253+
"snippet",
254+
"repl"
255+
]
256+
}
257+
},
218258
"calva.myLeinProfiles": {
219259
"type": "array",
220260
"description": "At Jack in, any profiles listed here will be added to the profiles found in the `project.clj` file.",
@@ -671,10 +711,16 @@
671711
},
672712
{
673713
"command": "calva.evalCurrentTopLevelFormInREPLWindow",
674-
"title": "Evaluate Top Level Form in REPL Window (defun)",
714+
"title": "Evaluate Top Level Form (defun) in REPL Window",
675715
"enablement": "calva:connected",
676716
"category": "Calva"
677717
},
718+
{
719+
"command": "calva.runCustomREPLCommand",
720+
"title": "Run Custom REPL Command",
721+
"category": "Calva",
722+
"enablement": "calva:connected"
723+
},
678724
{
679725
"command": "calva.switchCljsBuild",
680726
"title": "Select CLJS Build Connection",
@@ -1022,6 +1068,10 @@
10221068
"command": "calva.evalCurrentTopLevelFormInREPLWindow",
10231069
"key": "ctrl+alt+c ctrl+alt+space"
10241070
},
1071+
{
1072+
"command": "calva.runCustomREPLCommand",
1073+
"key": "ctrl+alt+c ."
1074+
},
10251075
{
10261076
"command": "paredit.forwardSexp",
10271077
"key": "ctrl+alt+right",

src/calva-fmt/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ Some examples of what it can be like to use Calva Formatter:
2525

2626
### Format Current Form
2727

28-
![Format Current Form](/assets/format-current-form.gif)
28+
![Format Current Form](/src/calva-fmt/assets/format-current-form.gif)
2929

3030
### Align Current Form
3131

32-
![Align Current Form](/assets/align-items.gif)
32+
![Align Current Form](/src/calva-fmt/assets/align-items.gif)
3333

3434
### Parinfer
3535

36-
![Infer parens](/assets/parinfer.gif)
36+
![Infer parens](/src/calva-fmt/assets/parinfer.gif)
3737

3838
## How to use
3939

src/connector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc
5353

5454
if (connectSequence.afterCLJReplJackInCode) {
5555
state.outputChannel().appendLine("Evaluating `afterCLJReplJackInCode` in CLJ REPL Window");
56-
await sendTextToREPLWindow(connectSequence.afterCLJReplJackInCode, null, false);
56+
await sendTextToREPLWindow("clj", connectSequence.afterCLJReplJackInCode, null, false);
5757
}
5858

5959
//cljsSession = nClient.session;

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ function activate(context: vscode.ExtensionContext) {
227227

228228
for (const config of ["enableBracketColors", "bracketColors", "cycleBracketColors", "misplacedBracketStyle", "matchedBracketStyle", "commentFormStyle", "ignoredFormStyle"]) {
229229
if (cwConfig.get(config) !== undefined) {
230-
vscode.window.showWarningMessage("Legacy Clojure Warrior settings detected. These settings have changed prefix/namespace to from `clojureWarrior´ to `calva.highlight`. You should update `settings.json`.", ...["Roger that!"]);
230+
vscode.window.showWarningMessage("Legacy Clojure Warrior settings detected. These settings have changed prefix/namespace from `clojureWarrior´ to `calva.highlight`. You should update `settings.json`.", ...["Roger that!"]);
231231
break;
232232
}
233233
}

src/nrepl/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export class NReplSession {
165165
const msgType: string = msgData.out? "stdout" : "stderr";
166166

167167
if (msgValue && this.replType) {
168-
const window = await replWindow.openReplWindow(this.replType, true);
168+
const window = replWindow.getReplWindow(this.replType);
169169
const windowMsg = {
170170
type: msgType,
171171
value: msgValue

src/repl-window.ts

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as path from "path";
44
import * as state from "./state";
55
import status from "./status"
66
import * as fs from "fs";
7+
import * as _ from "lodash";
78
import { NReplEvaluation, NReplSession } from "./nrepl";
89

910
import annotations from './providers/annotations';
@@ -25,9 +26,9 @@ export function activeReplWindow() {
2526
export function isReplWindowOpen(mode: "clj" | "cljs" = "clj") {
2627
// If we find `mode` in ythe `replWindows` dictionary, then it is open.
2728
if (!replWindows[mode]) {
28-
return(false);
29+
return (false);
2930
}
30-
return(true);
31+
return (true);
3132
}
3233

3334
class REPLWindow {
@@ -174,7 +175,7 @@ class REPLWindow {
174175
})
175176
try {
176177
this.postMessage({ type: "repl-response", value: await this.evaluation.value, ns: this.ns = ns || this.evaluation.ns || this.ns });
177-
if(this.evaluation.ns && this.ns != this.evaluation.ns) {
178+
if (this.evaluation.ns && this.ns != this.evaluation.ns) {
178179
// the evaluation changed the namespace so set the new namespace.
179180
this.setNamespace(this.evaluation.ns);
180181
}
@@ -198,17 +199,25 @@ class REPLWindow {
198199
let ctx: vscode.ExtensionContext
199200

200201
let replWindows: { [id: string]: REPLWindow } = {};
201-
let replViewColum: { [id: string]: vscode.ViewColumn } = {"clj": vscode.ViewColumn.Two,
202-
"cljs": vscode.ViewColumn.Two};
202+
let replViewColum: { [id: string]: vscode.ViewColumn } = {
203+
"clj": vscode.ViewColumn.Two,
204+
"cljs": vscode.ViewColumn.Two
205+
};
206+
207+
export function getReplWindow(mode: "clj" | "cljs") {
208+
return replWindows[mode];
209+
}
203210

204211
function getImageUrl(name: string) {
205212
let imagepath = "";
206-
if (!name)
207-
imagepath = path.join(ctx.extensionPath, "assets/images/empty.svg");
208-
else
209-
imagepath = path.join(ctx.extensionPath, "assets/images/", name);
213+
if (!name) {
214+
imagepath = path.join(ctx.extensionPath, "assets/images/empty.svg");
215+
}
216+
else {
217+
imagepath = path.join(ctx.extensionPath, "assets/images/", name);
218+
}
210219

211-
if(!fs.existsSync(imagepath)) {
220+
if (!fs.existsSync(imagepath)) {
212221
imagepath = path.join(ctx.extensionPath, "assets/images/empty.svg");
213222
}
214223
return vscode.Uri.file(imagepath).with({ scheme: 'vscode-resource' }).toString()
@@ -223,7 +232,7 @@ export async function reconnectReplWindow(mode: "clj" | "cljs") {
223232

224233
export async function openClojureReplWindows() {
225234
if (state.deref().get('connected')) {
226-
if(util.getSession("clj")) {
235+
if (util.getSession("clj")) {
227236
openReplWindow("clj", true);
228237
return;
229238
}
@@ -233,7 +242,7 @@ export async function openClojureReplWindows() {
233242

234243
export async function openClojureScriptReplWindows() {
235244
if (state.deref().get('connected')) {
236-
if(util.getSession("cljs")) {
245+
if (util.getSession("cljs")) {
237246
openReplWindow("cljs", true);
238247
return;
239248
}
@@ -247,7 +256,7 @@ export async function openReplWindow(mode: "clj" | "cljs" = "clj", preserveFocus
247256

248257
if (!replWindows[mode]) {
249258
await createReplWindow(session, mode);
250-
} else if (!nreplClient.sessions[replWindows[mode].session.sessionId]) {
259+
} else if (!nreplClient.sessions[replWindows[mode].session.sessionId]) {
251260
replWindows[mode].session = await session.clone();
252261
}
253262

@@ -295,20 +304,31 @@ async function setREPLNamespaceCommand() {
295304
await setREPLNamespace(util.getDocumentNamespace(), false).catch(r => { console.error(r) });
296305
}
297306

298-
export async function sendTextToREPLWindow(text, ns: string, pprint: boolean) {
299-
let wnd = await openReplWindow(util.getREPLSessionType(), true);
307+
export async function sendTextToREPLWindow(sessionType: "clj" | "cljs", text: string, ns: string, pprint: boolean) {
308+
const chan = state.outputChannel(),
309+
wnd = await openReplWindow(sessionType, true);
300310
if (wnd) {
301-
let oldNs = wnd.ns;
302-
if (ns && ns != oldNs)
303-
await wnd.session.eval("(in-ns '" + ns + ")").value;
304-
try {
305-
wnd.evaluate(ns || oldNs, text);
306-
await wnd.replEval(text, oldNs, pprint);
307-
} finally {
308-
if (ns && ns != oldNs) {
309-
await wnd.session.eval("(in-ns '" + oldNs + ")").value;
311+
const inNs = ns ? ns : wnd.ns;
312+
if (ns && ns !== wnd.ns) {
313+
try {
314+
const requireEvaluation = wnd.session.eval(`(require '${ns})`)
315+
await requireEvaluation.value;
316+
const inNSEvaluation = wnd.session.eval(`(in-ns '${ns})`)
317+
await inNSEvaluation.value;
318+
if (inNSEvaluation) {
319+
wnd.setNamespace(inNSEvaluation.ns);
320+
}
321+
} catch (e) {
322+
vscode.window.showErrorMessage(`Error loading namespcace "${ns}" for command snippet: ${e}`, ...["OK"]);
323+
return;
310324
}
311325
}
326+
try {
327+
wnd.evaluate(inNs, text);
328+
await wnd.replEval(text, inNs, pprint);
329+
} catch (e) {
330+
vscode.window.showErrorMessage("Error running command snippet: " + e, ...["OK"]);
331+
}
312332
}
313333
}
314334

@@ -341,7 +361,7 @@ function evalCurrentFormInREPLWindow(topLevel: boolean, pprint: boolean) {
341361
code = doc.getText(selection);
342362
}
343363
if (code !== "") {
344-
sendTextToREPLWindow(code, util.getNamespace(doc), pprint)
364+
sendTextToREPLWindow(util.getREPLSessionType(), code, util.getNamespace(doc), pprint)
345365
}
346366
}
347367

@@ -353,6 +373,50 @@ function evalCurrentTopLevelFormInREPLWindowCommand() {
353373
evalCurrentFormInREPLWindow(true, state.config().pprint);
354374
}
355375

376+
export type customREPLCommandSnippet = { name: string, snippet: string, repl: string, ns?: string };
377+
378+
function sendCustomCommandSnippetToREPLCommand() {
379+
let pickCounter = 1,
380+
configErrors: {"name": string, "keys": string[]}[] = [];
381+
const snippets = state.config().customREPLCommandSnippets as customREPLCommandSnippet[],
382+
snippetPicks = _.map(snippets, (c: customREPLCommandSnippet) => {
383+
const undefs = ["name", "snippet", "repl"].filter(k => {
384+
return !c[k];
385+
})
386+
if (undefs.length > 0) {
387+
configErrors.push({"name": c.name, "keys": undefs});
388+
}
389+
return `${pickCounter++}: ${c.name} (${c.repl})`;
390+
}),
391+
snippetsDict = {};
392+
pickCounter = 1;
393+
394+
if (configErrors.length > 0) {
395+
vscode.window.showErrorMessage("Errors found in the `calva.customREPLCommandSnippets` setting. Values missing for: " + JSON.stringify(configErrors), "OK");
396+
return;
397+
}
398+
snippets.forEach((c: customREPLCommandSnippet) => {
399+
snippetsDict[`${pickCounter++}: ${c.name} (${c.repl})`] = c;
400+
});
401+
402+
if (snippets && snippets.length > 0) {
403+
util.quickPickSingle({
404+
values: snippetPicks,
405+
placeHolder: "Choose a command to run at the REPL",
406+
saveAs: "runCustomREPLCommand"
407+
}).then(async (pick) => {
408+
if (pick && snippetsDict[pick] && snippetsDict[pick].snippet) {
409+
const command = snippetsDict[pick].snippet,
410+
ns = snippetsDict[pick].ns ? snippetsDict[pick].ns : "user",
411+
repl = snippetsDict[pick].repl ? snippetsDict[pick].repl : "clj";
412+
sendTextToREPLWindow(repl ? repl : "clj", command, ns, false);
413+
}
414+
});
415+
} else {
416+
vscode.window.showInformationMessage("No snippets configured. Configure snippets in `calva.customREPLCommandSnippets`.", ...["OK"]);
417+
}
418+
}
419+
356420
export function activate(context: vscode.ExtensionContext) {
357421
ctx = context;
358422
context.subscriptions.push(vscode.commands.registerCommand('calva.openCljReplWindow', openClojureReplWindows));
@@ -361,6 +425,7 @@ export function activate(context: vscode.ExtensionContext) {
361425
context.subscriptions.push(vscode.commands.registerCommand('calva.setREPLNamespace', setREPLNamespaceCommand));
362426
context.subscriptions.push(vscode.commands.registerCommand('calva.evalCurrentFormInREPLWindow', evalCurrentFormInREPLWindowCommand));
363427
context.subscriptions.push(vscode.commands.registerCommand('calva.evalCurrentTopLevelFormInREPLWindow', evalCurrentTopLevelFormInREPLWindowCommand));
428+
context.subscriptions.push(vscode.commands.registerCommand('calva.runCustomREPLCommand', sendCustomCommandSnippetToREPLCommand));
364429
}
365430

366431
export function clearHistory() {

src/state.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ReplConnectSequence } from './nrepl/connectSequence';
66
import * as util from './utilities';
77
import * as path from 'path';
88
import * as fs from 'fs';
9+
import { customREPLCommandSnippet } from './repl-window';
910

1011
let extensionContext: vscode.ExtensionContext;
1112
export function setExtensionContext(context: vscode.ExtensionContext) {
@@ -106,7 +107,8 @@ function config() {
106107
myLeinProfiles: configOptions.get("myLeinProfiles", []).map(_trimAliasName) as string[],
107108
myCljAliases: configOptions.get("myCljAliases", []).map(_trimAliasName) as string[],
108109
pprint: configOptions.get("prettyPrint") as boolean,
109-
asyncOutputDestination: configOptions.get("sendAsyncOutputTo") as string
110+
asyncOutputDestination: configOptions.get("sendAsyncOutputTo") as string,
111+
customREPLCommandSnippets: configOptions.get("customREPLCommandSnippets", []) as customREPLCommandSnippet[]
110112
};
111113
}
112114

0 commit comments

Comments
 (0)