From 0e8d21fcf64b4ec79e925e8faac34227a9d13e6a Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 15 Aug 2019 16:57:36 +0200 Subject: [PATCH 001/128] Add first Version for default start sequences --- calva/nrepl/connectSequence.ts | 75 ++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 calva/nrepl/connectSequence.ts diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts new file mode 100644 index 000000000..999ff60e1 --- /dev/null +++ b/calva/nrepl/connectSequence.ts @@ -0,0 +1,75 @@ +enum ProjectTypes { + "Leiningen", + "Clojure-CLI", + "shadow-cljs" +} + +enum CljsTypes { + "Figwheel Main", + "lein-figwheel", + "shadow-cljs" +} + +interface CustomCljsType { + startCode?: string, + isStartedRegExp?: string, + connectCode: string, + isConnectedRegExp?: string, + printThisLineRegExp?: string +} + +interface CLJJackInCode { + code: string, + continueStdOutRegExp?: string +} + +interface ReplConnectSequence { + name: string, + projectType: ProjectTypes, + afterCLJReplJackInCode?: CLJJackInCode, + cljsType?: CljsTypes | CustomCljsType +} + +const leiningenDefaults: ReplConnectSequence[] = + [{ + name: "Leiningen", + projectType: ProjectTypes.Leiningen + }, + { + name: "Leiningen + Figwheel", + projectType: ProjectTypes.Leiningen, + cljsType: CljsTypes["lein-figwheel"] + }, + { + name: "Leiningen + Figwheel Main", + projectType: ProjectTypes.Leiningen, + cljsType: CljsTypes["Figwheel Main"] + }]; + +const cljDefaults: ReplConnectSequence[] = + [{ + name: "Clojure-CLI", + projectType: ProjectTypes["Clojure-CLI"] + }, + { + name: "Clojure-CLI + Figwheel", + projectType: ProjectTypes["Clojure-CLI"], + cljsType: CljsTypes["lein-figwheel"] + }, + { + name: "Clujure-CLI + Figwheel Main", + projectType: ProjectTypes["Clojure-CLI"], + cljsType: CljsTypes["Figwheel Main"] + }]; + +const shadowCljsDefaults: ReplConnectSequence[] = [{ + name: "shadow-cljs", + projectType: ProjectTypes["shadow-cljs"], + cljsType: CljsTypes["shadow-cljs"] +}] + +const defaultSequences = { + "leiningen": leiningenDefaults, + "clj": cljDefaults, + "shadow-cljs": shadowCljsDefaults +}; \ No newline at end of file From a3c6047b6146bf4a6347db63e4a15c0d08fff469 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 15 Aug 2019 17:39:07 +0200 Subject: [PATCH 002/128] Add replConnectSequences config definition --- package.json | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e2b1ec842..a258e4c92 100644 --- a/package.json +++ b/package.json @@ -207,6 +207,83 @@ "type": "boolean", "default": true, "description": "Should Calva open the Figwheel app for you when Figwheel has been started?" + }, + "calva.replConnectSequences": { + "type": "array", + "description": "Custom Repl connect sequences that calva should use.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name shown in the selection" + }, + "projectType": { + "type": "string", + "description": "Define the type of your project.", + "enum": [ + "Leiningen", + "Clojure-CLI", + "shadow-cljs" + ] + }, + "afterCLJReplJackInCode": { + "type": "object", + "description": "Define what should happen after the Jack-in connect.", + "required": false, + "properties": { + "code": { + "type": "string" + }, + "continueStdOutRegExp": { + "type": "string", + "required": false + } + } + }, + "cljsType": { + "description": "Choose a cljs Type or define your own.", + "anyOf": [ + { + "type": "string", + "enum": [ + "Figwheel Main", + "lein-figwheel", + "shadow-cljs" + ] + }, + { + "type": "object", + "properties": { + "startCode": { + "type": "string", + "required": false + }, + "isStartedRegExp": { + "type": "string", + "required": false + }, + "connectCode": { + "type": "string" + }, + "isConnectedRegExp": { + "type": "string", + "required": false + }, + "printThisLineRegExp": { + "type": "string", + "required": false + } + } + } + ] + } + } + }, + "required": [ + "name", + "projectType" + ] } } }, @@ -1044,4 +1121,4 @@ "webpack": "^4.33.0", "webpack-cli": "^3.3.3" } -} +} \ No newline at end of file From 1133dc3ac45bd37c4205a791587b7a65b36650ac Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 15 Aug 2019 19:36:36 +0200 Subject: [PATCH 003/128] Moving required block to right position --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a258e4c92..a53cb66e4 100644 --- a/package.json +++ b/package.json @@ -213,6 +213,10 @@ "description": "Custom Repl connect sequences that calva should use.", "items": { "type": "object", + "required": [ + "name", + "projectType" + ], "properties": { "name": { "type": "string", @@ -279,11 +283,7 @@ ] } } - }, - "required": [ - "name", - "projectType" - ] + } } } }, From 09865bbfb1ce8c02273d309942d59c0a1d99fc30 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 15 Aug 2019 19:54:09 +0200 Subject: [PATCH 004/128] Add more and better description based on the issue description --- package.json | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index a53cb66e4..559f11e0b 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,7 @@ "properties": { "name": { "type": "string", - "description": "Name shown in the selection" + "description": "This will show up in the Jack-in quick-pick menu when you start Jack-in if you have more than one sequence configured." }, "projectType": { "type": "string", @@ -233,20 +233,22 @@ }, "afterCLJReplJackInCode": { "type": "object", - "description": "Define what should happen after the Jack-in connect.", + "description": "Here you can give Calva some Clojure code to evaluate in the CLJ REPL, once it has been created.", "required": false, "properties": { "code": { - "type": "string" + "type": "string", + "description": "The Clojure code to evaluate." }, "continueStdOutRegExp": { "type": "string", + "description": "A regular expression which, when matched in the stdout from the evaluation, will tell Calva that the CLJ REPL is ready. If this field is omitted the sequence will continue when the code is evaluated.", "required": false } } }, "cljsType": { - "description": "Choose a cljs Type or define your own.", + "description": "If omitted Calva will show a menu with the built-in types.", "anyOf": [ { "type": "string", @@ -258,25 +260,28 @@ }, { "type": "object", + "required": ["connectCode"], "properties": { "startCode": { "type": "string", - "required": false + "description": "Clojure code to be evaluated to create and/or start your custom CLJS REPL." }, "isStartedRegExp": { "type": "string", - "required": false + "description": "A regular experession which, when matched in the stdout from the startCode evaluation, will make Calva continue with connecting the REPL" }, "connectCode": { - "type": "string" + "type": "string", + "description": ": Clojure code to be evaluated to convert the REPL to a CLJS REPL that Calva can use to connect to the application." }, "isConnectedRegExp": { "type": "string", - "required": false + "description": "A regular experession which, when matched in the stdout from the connectCode evaluation, will tell Calva that the application is connected.", + "default": "To quit, type: :cljs/quit" }, "printThisLineRegExp": { "type": "string", - "required": false + "description": "regular experession which, when matched in the stdout from any code evaluations in the cljsType, will make the matched text be printed to the Calva says Output channel." } } } From fb79404efb8fe98f32109848e9004bddcf4a47b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 15 Aug 2019 20:57:31 +0200 Subject: [PATCH 005/128] How much description can the user handle? --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 559f11e0b..03f7cfd83 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ }, "calva.replConnectSequences": { "type": "array", - "description": "Custom Repl connect sequences that calva should use.", + "description": "For when your project needs a custom REPL connect sequence.", "items": { "type": "object", "required": [ @@ -248,7 +248,7 @@ } }, "cljsType": { - "description": "If omitted Calva will show a menu with the built-in types.", + "description": "Either a built in type, or an object configuring a custom type. If omitted Calva will show a menu with the built-in types.", "anyOf": [ { "type": "string", @@ -268,11 +268,11 @@ }, "isStartedRegExp": { "type": "string", - "description": "A regular experession which, when matched in the stdout from the startCode evaluation, will make Calva continue with connecting the REPL" + "description": "A regular experession which, when matched in the stdout from the startCode evaluation, will make Calva continue with connecting the REPL, and to prompt the user to start the application. If omitted and there is startCode Calva will continue when that code is evaluated. Special case: _If the regexp has a group, it will be assumed to match the URL where the application is started and Calva will automatically open it in order to connect to it._" }, "connectCode": { "type": "string", - "description": ": Clojure code to be evaluated to convert the REPL to a CLJS REPL that Calva can use to connect to the application." + "description": "Clojure code to be evaluated to convert the REPL to a CLJS REPL that Calva can use to connect to the application." }, "isConnectedRegExp": { "type": "string", @@ -281,7 +281,7 @@ }, "printThisLineRegExp": { "type": "string", - "description": "regular experession which, when matched in the stdout from any code evaluations in the cljsType, will make the matched text be printed to the Calva says Output channel." + "description": "A regular experession which, when matched in the stdout from any code evaluations in the cljsType, will make the matched text be printed to the Calva says Output channel." } } } From 4ce03aa12faf79e8a89d60110784f6c09b991ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 15 Aug 2019 20:59:53 +0200 Subject: [PATCH 006/128] Less duh!, I think :smile: --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03f7cfd83..9c2695c0d 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,7 @@ }, "projectType": { "type": "string", - "description": "Define the type of your project.", + "description": "Select one of the project types supported by Calva.", "enum": [ "Leiningen", "Clojure-CLI", From 3aded20a64dab832bcabaa25a7ad0a74bdec8b2f Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 16 Aug 2019 10:54:43 +0200 Subject: [PATCH 007/128] Add function to get connectSequences --- calva/nrepl/connectSequence.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 999ff60e1..3c52c0232 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -1,3 +1,5 @@ +import { workspace } from "vscode"; + enum ProjectTypes { "Leiningen", "Clojure-CLI", @@ -72,4 +74,27 @@ const defaultSequences = { "leiningen": leiningenDefaults, "clj": cljDefaults, "shadow-cljs": shadowCljsDefaults -}; \ No newline at end of file +}; + +/** Retrieve the replConnectSequences from the config */ +function getConfigcustomConnectSequences(): ReplConnectSequence[] { + return workspace.getConfiguration('calva') + .get("replConnectSequences", []); +} + +/** + * Retrieve the replConnectSequences and returns only that if only one was defined. + * Otherwise the user defined will be combined with the defaults one to be returned. + * @param projectType what default Sequences would be used (leiningen, clj, shadow-cljs) + */ +function getConnectSequences(projectType: string) { + let customSequences = getConfigcustomConnectSequences(); + if (customSequences.length == 1) { + return customSequences; + } + return defaultSequences[projectType].slice().push(customSequences.push()); +} + +export { + getConnectSequences +} \ No newline at end of file From b90d162fcf73d5cb54875e01e8edc97a29b56231 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 16 Aug 2019 12:28:47 +0200 Subject: [PATCH 008/128] Add missing type return for getConnectSequences --- calva/nrepl/connectSequence.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 3c52c0232..c07d89e79 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -87,7 +87,7 @@ function getConfigcustomConnectSequences(): ReplConnectSequence[] { * Otherwise the user defined will be combined with the defaults one to be returned. * @param projectType what default Sequences would be used (leiningen, clj, shadow-cljs) */ -function getConnectSequences(projectType: string) { +function getConnectSequences(projectType: string): ReplConnectSequence[] { let customSequences = getConfigcustomConnectSequences(); if (customSequences.length == 1) { return customSequences; From 0b000e6eaf75464a485d6609cbf915dd6e3cb5ac Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Sun, 18 Aug 2019 10:59:58 +0200 Subject: [PATCH 009/128] ConnectSequences for multiple projectTypes, fixing enum values --- calva/nrepl/connectSequence.ts | 25 ++++++++++++++++--------- calva/nrepl/jack-in.ts | 1 + 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index c07d89e79..369ff8901 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -1,15 +1,15 @@ import { workspace } from "vscode"; enum ProjectTypes { - "Leiningen", - "Clojure-CLI", - "shadow-cljs" + "Leiningen" = "Leiningen", + "Clojure-CLI" = "Clojure-CLI", + "shadow-cljs" = "shadow-cljs" } enum CljsTypes { - "Figwheel Main", - "lein-figwheel", - "shadow-cljs" + "Figwheel Main" = "Figwheel Main", + "lein-figwheel" = "lein-figwheel", + "shadow-cljs" = "shadow-cljs" } interface CustomCljsType { @@ -71,7 +71,7 @@ const shadowCljsDefaults: ReplConnectSequence[] = [{ }] const defaultSequences = { - "leiningen": leiningenDefaults, + "lein": leiningenDefaults, "clj": cljDefaults, "shadow-cljs": shadowCljsDefaults }; @@ -87,12 +87,19 @@ function getConfigcustomConnectSequences(): ReplConnectSequence[] { * Otherwise the user defined will be combined with the defaults one to be returned. * @param projectType what default Sequences would be used (leiningen, clj, shadow-cljs) */ -function getConnectSequences(projectType: string): ReplConnectSequence[] { +function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { let customSequences = getConfigcustomConnectSequences(); if (customSequences.length == 1) { return customSequences; } - return defaultSequences[projectType].slice().push(customSequences.push()); + console.log("defaultSeq", defaultSequences); + let result = []; + for (let pType of projectTypes) { + console.log("pType", pType); + console.log("pSeq", defaultSequences[pType]); + result = result.concat(defaultSequences[pType]); + } + return result.concat(customSequences); } export { diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 87d461eb8..a7db11134 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -6,6 +6,7 @@ import * as state from "../state" import * as connector from "../connector"; import statusbar from "../statusbar"; import { parseEdn, parseForms } from "../../cljs-out/cljs-lib"; +import { getConnectSequences } from "./connectSequence"; const isWin = /^win/.test(process.platform); From d3e016c42ea26bc23a1cd6f681afb29f14e1f7d9 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Sun, 18 Aug 2019 13:30:14 +0200 Subject: [PATCH 010/128] Use ConnectSequences for user selection, need to use it and pass it through --- calva/nrepl/connectSequence.ts | 5 +++-- calva/nrepl/jack-in.ts | 29 +++++++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 369ff8901..b4e521517 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -2,7 +2,7 @@ import { workspace } from "vscode"; enum ProjectTypes { "Leiningen" = "Leiningen", - "Clojure-CLI" = "Clojure-CLI", + "Clojure CLI" = "Clojure CLI", "shadow-cljs" = "shadow-cljs" } @@ -103,5 +103,6 @@ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { } export { - getConnectSequences + getConnectSequences, + ReplConnectSequence } \ No newline at end of file diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index a7db11134..c348a3e9f 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -6,7 +6,7 @@ import * as state from "../state" import * as connector from "../connector"; import statusbar from "../statusbar"; import { parseEdn, parseForms } from "../../cljs-out/cljs-lib"; -import { getConnectSequences } from "./connectSequence"; +import { getConnectSequences, ReplConnectSequence } from "./connectSequence"; const isWin = /^win/.test(process.platform); @@ -332,9 +332,11 @@ export async function calvaJackIn() { return; } + let sequences = getConnectSequences(cljTypes); + // Show a prompt to pick one if there are multiple let menu: string[] = []; - for (const clj of cljTypes) { + /** for (const clj of cljTypes) { menu.push(projectTypes[clj].name); const customCljsRepl = connector.getCustomCLJSRepl(); const cljsTypes = projectTypes[clj].cljsTypes.slice(); @@ -344,29 +346,32 @@ export async function calvaJackIn() { for (const cljs of cljsTypes) { menu.push(`${projectTypes[clj].name} + ${cljs}`); } + }*/ + for (const seq of sequences) { + menu.push(seq.name); } - let projectTypeSelection = await utilities.quickPickSingle({ values: menu, placeHolder: "Please select a project type", saveAs: `${connector.getProjectRoot()}/jack-in-type`, autoSelect: true }); - if (!projectTypeSelection) { + let projectConnectSequenceName = await utilities.quickPickSingle({ values: menu, placeHolder: "Please select a project type", saveAs: `${connector.getProjectRoot()}/jack-in-type`, autoSelect: true }); + if (!projectConnectSequenceName) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypePicked").send(); return; } // Resolve the selection to an entry in projectTypes - const projectTypeName: string = projectTypeSelection.replace(/ \+ .*$/, ""); - let projectType = getProjectTypeForName(projectTypeName); + const projectTypeName: string = projectConnectSequenceName.replace(/ \+ .*$/, ""); + + let projectConnectSequence: ReplConnectSequence = sequences.find(seq => seq.name === projectConnectSequenceName); state.extensionContext.workspaceState.update('selectedCljTypeName', projectTypeName); - let matched = projectTypeSelection.match(/ \+ (.*)$/); - const selectedCljsType = projectType.name == "shadow-cljs" ? "shadow-cljs" : matched != null ? matched[1] : ""; + let matched = projectConnectSequenceName.match(/ \+ (.*)$/); + const selectedCljsType = projectConnectSequence.cljsType == "shadow-cljs" ? "shadow-cljs" : matched != null ? matched[1] : ""; state.extensionContext.workspaceState.update('selectedCljsTypeName', selectedCljsType); - if (!projectType) { + if (!projectConnectSequence) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypeForBuildName").send(); return; } - + let projectType = getProjectTypeForName(projectTypeName); let executable = isWin ? projectType.winCmd : projectType.cmd; - // Ask the project type to build up the command line. This may prompt for further information. let args = await projectType.commandLine(selectedCljsType != ""); - executeJackInTask(projectType, projectTypeSelection, executable, args, cljTypes, outputChannel); + executeJackInTask(projectType, projectConnectSequenceName, executable, args, cljTypes, outputChannel); } From 71cd68540eec827af8954ef0a7dd3f833c76407a Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Sun, 18 Aug 2019 14:20:03 +0200 Subject: [PATCH 011/128] Passing ConnectSequence to executeJackIn + some formating --- calva/nrepl/jack-in.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index c348a3e9f..c9be4bf0e 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -115,8 +115,8 @@ const projectTypes: { [id: string]: connector.ProjectType } = { out.push("/d", "/c", "lein"); } const q = isWin ? '' : "'", - dQ = '"', - s = isWin ? "^ " : " "; + dQ = '"', + s = isWin ? "^ " : " "; for (let i = 0; i < keys.length; i++) { let dep = keys[i]; @@ -143,7 +143,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { } else { out.push("repl", ":headless"); } - + return out; } }, @@ -188,7 +188,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { vscode.window.showErrorMessage("Could not parse deps.edn"); throw e; } - let aliases:string[] = []; + let aliases: string[] = []; if (parsed.aliases != undefined) { aliases = await utilities.quickPickMulti({ values: Object.keys(parsed.aliases).map(x => ":" + x), saveAs: `${connector.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); } @@ -196,10 +196,10 @@ const projectTypes: { [id: string]: connector.ProjectType } = { const dependencies = includeCljs ? { ...cliDependencies, ...figwheelDependencies } : cliDependencies, useMiddleware = includeCljs ? [...middleware, ...cljsMiddleware] : middleware; const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; - let aliasHasMain:boolean = false; + let aliasHasMain: boolean = false; for (let ali in aliases) { let aliasKey = aliases[ali].substr(1); - let alias = parsed.aliases[aliasKey]; + let alias = parsed.aliases[aliasKey]; aliasHasMain = (alias["main-opts"] != undefined); if (aliasHasMain) break; @@ -210,7 +210,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { out.push(dep + ` {:mvn/version ${dQ}${dependencies[dep]}${dQ}}`) let args = ["-Sdeps", `'${"{:deps {" + out.join(' ') + "}}"}'`]; - + if (aliasHasMain) { args.push(aliasesOption); } else { @@ -259,7 +259,7 @@ function getProjectTypeForName(name: string) { let watcher: fs.FSWatcher; const TASK_NAME = "Calva Jack-in"; -async function executeJackInTask(projectType: connector.ProjectType, projectTypeSelection: any, executable: string, args: any, cljTypes: string[], outputChannel: vscode.OutputChannel) { +async function executeJackInTask(projectType: connector.ProjectType, projectTypeSelection: any, executable: string, args: any, cljTypes: string[], outputChannel: vscode.OutputChannel, connectSequence : ReplConnectSequence) { state.cursor.set("launching", projectTypeSelection); statusbar.update(); const nReplPortFile = projectType.nReplPortFile(); @@ -290,7 +290,7 @@ async function executeJackInTask(projectType: connector.ProjectType, projectType if (watcher != undefined) { watcher.removeAllListeners(); } - + watcher = fs.watch(portFileDir, async (eventType, fileName) => { if (fileName == portFileBase) { if (!fs.existsSync(nReplPortFile)) { @@ -359,7 +359,7 @@ export async function calvaJackIn() { // Resolve the selection to an entry in projectTypes const projectTypeName: string = projectConnectSequenceName.replace(/ \+ .*$/, ""); - let projectConnectSequence: ReplConnectSequence = sequences.find(seq => seq.name === projectConnectSequenceName); + let projectConnectSequence: ReplConnectSequence = sequences.find(seq => seq.name === projectConnectSequenceName); state.extensionContext.workspaceState.update('selectedCljTypeName', projectTypeName); let matched = projectConnectSequenceName.match(/ \+ (.*)$/); const selectedCljsType = projectConnectSequence.cljsType == "shadow-cljs" ? "shadow-cljs" : matched != null ? matched[1] : ""; @@ -373,5 +373,6 @@ export async function calvaJackIn() { // Ask the project type to build up the command line. This may prompt for further information. let args = await projectType.commandLine(selectedCljsType != ""); - executeJackInTask(projectType, projectConnectSequenceName, executable, args, cljTypes, outputChannel); + executeJackInTask(projectType, projectConnectSequenceName, executable, args, cljTypes, outputChannel, projectConnectSequence) + .then(() => { }, () => { }); } From 8370a168acec868a0a1af32350a33309474f3fa0 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Mon, 19 Aug 2019 16:23:22 +0200 Subject: [PATCH 012/128] Build-in cljs-types translated --- calva/nrepl/connectSequence.ts | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index b4e521517..cd589c591 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -14,8 +14,9 @@ enum CljsTypes { interface CustomCljsType { startCode?: string, + builds?: string[], isStartedRegExp?: string, - connectCode: string, + connectCode: string | Object, isConnectedRegExp?: string, printThisLineRegExp?: string } @@ -76,6 +77,28 @@ const defaultSequences = { "shadow-cljs": shadowCljsDefaults }; +const defaultCljsTypes = { + "Figwheel Main": { + startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, + builds: [], + isStartedRegExp: "Prompt will show", + connectCode: `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl %BUILD%))`, + isConnectedRegExp: "To quit, type: :cljs/quit" + }, + "lein-figwheel": { + connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", + isConnectedRegExp: "Prompt will show" + }, + "shadow-cljs": { + connectCode: { + build: `(shadow.cljs.devtools.api/nrepl-select %BUILD%)`, + repl: `(shadow.cljs.devtools.api/%REPL%)` + }, + builds: [], + isConnectedRegExp: /:selected/ + } +}; + /** Retrieve the replConnectSequences from the config */ function getConfigcustomConnectSequences(): ReplConnectSequence[] { return workspace.getConfiguration('calva') @@ -102,7 +125,17 @@ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { return result.concat(customSequences); } +/** + * Returns the CLJS-Type description of one of the build-in. + * @param cljsType Build-in cljsType + */ +function getDefaultCljsType(cljsType: string): CustomCljsType { + return defaultCljsTypes[cljsType]; +} + export { getConnectSequences, - ReplConnectSequence + getDefaultCljsType, + ReplConnectSequence, + CustomCljsType } \ No newline at end of file From 952f89cc89ea207d777ce04a3ecf71f52bf2bef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 19 Aug 2019 16:27:55 +0200 Subject: [PATCH 013/128] Add myLeinProfiles settings for jack-in --- calva/nrepl/jack-in.ts | 7 ++++--- calva/state.ts | 6 +++++- package.json | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 87d461eb8..f896ab3eb 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -95,11 +95,12 @@ const projectTypes: { [id: string]: connector.ProjectType } = { } if (defproject != undefined) { - let profilesIndex = defproject.indexOf("profiles"); + const profilesIndex = defproject.indexOf("profiles"), + myProfiles = state.config().myLeinProfiles; if (profilesIndex > -1) { try { - const profilesMap = defproject[profilesIndex + 1]; - profiles = [...profiles, ...Object.keys(profilesMap).map((v, k) => { return ":" + v })]; + const profilesList = [...Object.keys(defproject[profilesIndex + 1]), ...myProfiles]; + profiles = [...profiles, ...profilesList.map(v => { return ":" + v })]; if (profiles.length) { profiles = await utilities.quickPickMulti({ values: profiles, saveAs: `${connector.getProjectRoot()}/lein-cli-profiles`, placeHolder: "Pick any profiles to launch with" }); } diff --git a/calva/state.ts b/calva/state.ts index 158de8f50..3032d16a1 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -86,7 +86,11 @@ function config() { syncReplNamespaceToCurrentFile: configOptions.get("syncReplNamespaceToCurrentFile"), jackInEnv: configOptions.get("jackInEnv"), openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted"), - customCljsRepl: configOptions.get("customCljsRepl", null) + customCljsRepl: configOptions.get("customCljsRepl", null), + myLeinProfiles: configOptions.get("myLeinProfiles", []).map(v => { + return v.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") + }), + myCljAliases: configOptions.get("myCljAliases", []), }; } diff --git a/package.json b/package.json index 40f422c7b..eaa760b09 100644 --- a/package.json +++ b/package.json @@ -207,6 +207,20 @@ "type": "boolean", "default": true, "description": "Should Calva open the Figwheel app for you when Figwheel has been started?" + }, + "calva.myLeinProfiles": { + "type": "array", + "description": "At Jack in, any profiles listed here will be added to the profiles found in the `project.clj` file.", + "items": { + "type": "string" + } + }, + "calva.myCljAliases": { + "type": "array", + "description": "At Jack in, any aliases listed here will be added to the aliases found in the projects's `deps.edn` file.", + "items": { + "type": "string" + } } } }, From 510bd25c44b1e57413e195c5407e0303097569ce Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Mon, 19 Aug 2019 17:42:19 +0200 Subject: [PATCH 014/128] First version for translating CustomCljsType to ReplType description --- calva/connector.ts | 119 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/calva/connector.ts b/calva/connector.ts index f3da49809..856475ec1 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -10,6 +10,7 @@ import status from './status'; const { parseEdn } = require('../cljs-out/cljs-lib'); import { NReplClient, NReplSession } from "./nrepl"; import { reconnectReplWindow, openReplWindow } from './repl-window'; +import { CustomCljsType } from './nrepl/connectSequence'; const PROJECT_DIR_KEY = "connect.projectDir"; const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; @@ -115,6 +116,9 @@ async function connectToHost(hostname, port, cljsTypeName: string, replTypes: Re state.cursor.set('cljc', cljSession) status.update(); + //TODO Execute here cljJackInCode + //TODO Check continueStdOutRegExp when present, if so wait for it in stdout, otherwise only wait for eval + //! connection stdout as option in eval //cljsSession = nClient.session; //terminal.createREPLTerminal('clj', null, chan); let cljsSession = null, @@ -156,7 +160,7 @@ export function shadowConfigFile() { export function shadowBuilds() { let parsed = parseEdn(fs.readFileSync(shadowConfigFile(), 'utf8').toString()), - builds = _.map(parsed.builds, (_v, key) => { return ":" + key }); + builds: string[] = _.map(parsed.builds, (_v, key) => { return ":" + key }); builds.push("node-repl"); builds.push("browser-repl") return builds; @@ -189,6 +193,13 @@ function getFigwheelMainProjects() { return projects; } +/** + * ! DO it later + */ +function getFigwheelBuilds() { + +} + type checkConnectedFn = (value: string, out: any[], err: any[]) => boolean; type processOutputFn = (output: string) => void; type connectFn = (session: NReplSession, name: string, checkSuccess: checkConnectedFn) => Promise; @@ -341,6 +352,112 @@ type customCLJSREPLType = { connectedRegExp: string, }; +function figwheelOrShadowBuilds(cljsTypeName: string): string[] { + if (cljsTypeName.includes("Figwheel Main")) { + return getFigwheelMainProjects(); + } else if (cljsTypeName.includes("shadow-cljs")) { + return shadowBuilds(); + } +} + +function updateInitCode(build: string, initCode): string { + if (build && typeof initCode === 'object') { + if (build.charAt(0) == ":") { + return initCode.build.replace("%BUILD%", build); + } else { + return initCode.repl.replace("%REPL%", build); + } + } else if (build && typeof initCode === 'string') { + return initCode.replace("%BUILD%", build); + } + return null; +} + +function createCLJSReplType(cljsTypeName: string, desc: CustomCljsType): ReplType { + let result: ReplType = { + name: cljsTypeName, + connect: async (session, name, checkFn) => { + const chan = state.outputChannel(); + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', !(desc.builds === undefined)); + let initCode = desc.connectCode; + let build: string = null; + + if (desc.builds === [] && (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { + let projects = await figwheelOrShadowBuilds(cljsTypeName); + build = await util.quickPickSingle({ + values: projects, + placeHolder: "Select which build to connect to", + saveAs: `${getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` + }); + + initCode = updateInitCode(build, initCode); + + if (!initCode) { + //TODO error message + return; + } + } + + if (!(typeof initCode == 'string')) { + //TODO error message + return; + } + + state.cursor.set('cljsBuild', null); + + return evalConnectCode(session, initCode, name, checkFn); + }, + connected: (result, out, _err) => { + if (desc.isConnectedRegExp) { + return (result.search(desc.isConnectedRegExp) >= 0 || + out.find((x: string) => { return x.search(desc.isConnectedRegExp) >= 0 }) != undefined); + } + return true; + } + }; + + if (desc.startCode) { + result.start = async (session, name, checkFn) => { + let startCode = desc.startCode; + + let builds: string[]; + + if (startCode.includes("%BUILDS")) { + let projects = await figwheelOrShadowBuilds(cljsTypeName); + builds = projects.length <= 1 ? projects : await util.quickPickMulti({ + values: projects, + placeHolder: "Please select which builds to start", + saveAs: `${getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-projects` + }); + + if (builds) { + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); + state.cursor.set('cljsBuild', builds[0]); + startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); + return evalConnectCode(session, startCode, name, checkFn); + } else { + let chan = state.outputChannel(); + chan.appendLine("Starting REPL for " + cljsTypeName + " aborted."); + throw "Aborted"; + } + } + + return evalConnectCode(session, startCode, name, checkFn); + }; + } + + if (desc.isStartedRegExp) { + result.started = (result, out, err) => { + return out.find((x: string) => { return x.search(desc.isStartedRegExp) >= 0 }) != undefined || + err != undefined && err.find((x: string) => { + return x.search("already running") >= 0 + }); + } + } + + return result; +} + function createCustomCLJSReplType(custom: customCLJSREPLType): ReplType { return { name: custom.name, From 49fd7bf0cc5082e45f2f966ffb60eb84e66ea869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 19 Aug 2019 22:48:03 +0200 Subject: [PATCH 015/128] Add support for myCljAliases --- calva/nrepl/jack-in.ts | 50 +++++++++++++++++++++++------------------- calva/state.ts | 10 +++++---- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index f896ab3eb..ff458d7ed 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -96,17 +96,17 @@ const projectTypes: { [id: string]: connector.ProjectType } = { if (defproject != undefined) { const profilesIndex = defproject.indexOf("profiles"), + projectProfiles = profilesIndex > -1 ? Object.keys(defproject[profilesIndex + 1]) : [], myProfiles = state.config().myLeinProfiles; - if (profilesIndex > -1) { - try { - const profilesList = [...Object.keys(defproject[profilesIndex + 1]), ...myProfiles]; - profiles = [...profiles, ...profilesList.map(v => { return ":" + v })]; - if (profiles.length) { - profiles = await utilities.quickPickMulti({ values: profiles, saveAs: `${connector.getProjectRoot()}/lein-cli-profiles`, placeHolder: "Pick any profiles to launch with" }); - } - } catch (error) { - vscode.window.showErrorMessage("The project.clj file is not sane. " + error.message); - console.log(error); + if (projectProfiles.length + myProfiles.length > 0) { + const profilesList = [...projectProfiles, ...myProfiles]; + profiles = [...profiles, ...profilesList.map(v => { return ":" + v })]; + if (profiles.length) { + profiles = await utilities.quickPickMulti({ + values: profiles, + saveAs: `${connector.getProjectRoot()}/lein-cli-profiles`, + placeHolder: "Pick any profiles to launch with" + }); } } } @@ -115,8 +115,8 @@ const projectTypes: { [id: string]: connector.ProjectType } = { out.push("/d", "/c", "lein"); } const q = isWin ? '' : "'", - dQ = '"', - s = isWin ? "^ " : " "; + dQ = '"', + s = isWin ? "^ " : " "; for (let i = 0; i < keys.length; i++) { let dep = keys[i]; @@ -143,7 +143,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { } else { out.push("repl", ":headless"); } - + return out; } }, @@ -182,25 +182,31 @@ const projectTypes: { [id: string]: connector.ProjectType } = { let out: string[] = []; let data = fs.readFileSync(path.join(connector.getProjectRoot(), "deps.edn"), 'utf8').toString(); let parsed; + try { parsed = parseEdn(data); } catch (e) { vscode.window.showErrorMessage("Could not parse deps.edn"); throw e; } - let aliases:string[] = []; - if (parsed.aliases != undefined) { - aliases = await utilities.quickPickMulti({ values: Object.keys(parsed.aliases).map(x => ":" + x), saveAs: `${connector.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); + + const projectAliases = parsed.aliases != undefined ? Object.keys(parsed.aliases) : [], + myAliases = state.config().myCljAliases; + let aliases: string[] = []; + if (projectAliases.length + myAliases.length > 0) { + aliases = await utilities.quickPickMulti({ values: [...projectAliases, ...myAliases].map(x => ":" + x), saveAs: `${connector.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); } const dependencies = includeCljs ? { ...cliDependencies, ...figwheelDependencies } : cliDependencies, useMiddleware = includeCljs ? [...middleware, ...cljsMiddleware] : middleware; const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; - let aliasHasMain:boolean = false; + let aliasHasMain: boolean = false; for (let ali in aliases) { - let aliasKey = aliases[ali].substr(1); - let alias = parsed.aliases[aliasKey]; - aliasHasMain = (alias["main-opts"] != undefined); + const aliasKey = aliases[ali].substr(1); + if (parsed.aliases) { + let alias = parsed.aliases[aliasKey]; + aliasHasMain = alias && alias["main-opts"] != undefined; + } if (aliasHasMain) break; } @@ -210,7 +216,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { out.push(dep + ` {:mvn/version ${dQ}${dependencies[dep]}${dQ}}`) let args = ["-Sdeps", `'${"{:deps {" + out.join(' ') + "}}"}'`]; - + if (aliasHasMain) { args.push(aliasesOption); } else { @@ -290,7 +296,7 @@ async function executeJackInTask(projectType: connector.ProjectType, projectType if (watcher != undefined) { watcher.removeAllListeners(); } - + watcher = fs.watch(portFileDir, async (eventType, fileName) => { if (fileName == portFileBase) { if (!fs.existsSync(nReplPortFile)) { diff --git a/calva/state.ts b/calva/state.ts index 3032d16a1..2116a9590 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -74,6 +74,10 @@ function reset() { data = Immutable.fromJS(initialData); } +function _trimAliasName(name: string): string { + return name.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") +} + function config() { let configOptions = vscode.workspace.getConfiguration('calva'); return { @@ -87,10 +91,8 @@ function config() { jackInEnv: configOptions.get("jackInEnv"), openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted"), customCljsRepl: configOptions.get("customCljsRepl", null), - myLeinProfiles: configOptions.get("myLeinProfiles", []).map(v => { - return v.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") - }), - myCljAliases: configOptions.get("myCljAliases", []), + myLeinProfiles: configOptions.get("myLeinProfiles", []).map(_trimAliasName), + myCljAliases: configOptions.get("myCljAliases", []).map(_trimAliasName), }; } From fc66ed962517b7a1313825df8606833be43abff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 20 Aug 2019 09:43:23 +0200 Subject: [PATCH 016/128] npm i --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9c2695c0d..eba43b8fc 100644 --- a/package.json +++ b/package.json @@ -260,7 +260,9 @@ }, { "type": "object", - "required": ["connectCode"], + "required": [ + "connectCode" + ], "properties": { "startCode": { "type": "string", @@ -1126,4 +1128,4 @@ "webpack": "^4.33.0", "webpack-cli": "^3.3.3" } -} \ No newline at end of file +} From d35b099de07c9b91aba682072bad3eb9c4313840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 20 Aug 2019 09:43:59 +0200 Subject: [PATCH 017/128] camelCasing it properly --- calva/nrepl/connectSequence.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index cd589c591..30eeb09bd 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -100,7 +100,7 @@ const defaultCljsTypes = { }; /** Retrieve the replConnectSequences from the config */ -function getConfigcustomConnectSequences(): ReplConnectSequence[] { +function getConfigCustomConnectSequences(): ReplConnectSequence[] { return workspace.getConfiguration('calva') .get("replConnectSequences", []); } @@ -111,7 +111,7 @@ function getConfigcustomConnectSequences(): ReplConnectSequence[] { * @param projectType what default Sequences would be used (leiningen, clj, shadow-cljs) */ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { - let customSequences = getConfigcustomConnectSequences(); + let customSequences = getConfigCustomConnectSequences(); if (customSequences.length == 1) { return customSequences; } From 309d1b034ea355b8ee72c43802f2c32921ff41ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 20 Aug 2019 09:45:07 +0200 Subject: [PATCH 018/128] Simplify menu creation some --- calva/nrepl/jack-in.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index c9be4bf0e..8f1bc6d6b 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -259,7 +259,7 @@ function getProjectTypeForName(name: string) { let watcher: fs.FSWatcher; const TASK_NAME = "Calva Jack-in"; -async function executeJackInTask(projectType: connector.ProjectType, projectTypeSelection: any, executable: string, args: any, cljTypes: string[], outputChannel: vscode.OutputChannel, connectSequence : ReplConnectSequence) { +async function executeJackInTask(projectType: connector.ProjectType, projectTypeSelection: any, executable: string, args: any, cljTypes: string[], outputChannel: vscode.OutputChannel, connectSequence: ReplConnectSequence) { state.cursor.set("launching", projectTypeSelection); statusbar.update(); const nReplPortFile = projectType.nReplPortFile(); @@ -335,7 +335,7 @@ export async function calvaJackIn() { let sequences = getConnectSequences(cljTypes); // Show a prompt to pick one if there are multiple - let menu: string[] = []; + //let menu: string[] = []; /** for (const clj of cljTypes) { menu.push(projectTypes[clj].name); const customCljsRepl = connector.getCustomCLJSRepl(); @@ -347,10 +347,15 @@ export async function calvaJackIn() { menu.push(`${projectTypes[clj].name} + ${cljs}`); } }*/ - for (const seq of sequences) { - menu.push(seq.name); - } - let projectConnectSequenceName = await utilities.quickPickSingle({ values: menu, placeHolder: "Please select a project type", saveAs: `${connector.getProjectRoot()}/jack-in-type`, autoSelect: true }); + // for (const seq of sequences) { + // menu.push(seq.name); + // } + const projectConnectSequenceName = await utilities.quickPickSingle({ + values: sequences.map(s => { return s.name }), + placeHolder: "Please select a project type", + saveAs: `${connector.getProjectRoot()}/jack-in-type`, + autoSelect: true + }); if (!projectConnectSequenceName) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypePicked").send(); return; From d2ada699df69092bb8b27b74dccf58c48bd7c49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 17 Aug 2019 16:05:34 +0200 Subject: [PATCH 019/128] v2.0.32 support launching with aliases --- CHANGELOG.md | 8 ++++++-- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f13c699..06dbdd841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Change Log Changes to Calva. (We really will try to keep this updated now.) -## [2.0.32] - Unreleased -- Better alias support for leiningen and clj Projects +## Working on +- [Customizing the REPL connect sequence](https://github.com/BetterThanTomorrow/calva/issues/282) +- [Support for custom project/workflow commands](https://github.com/BetterThanTomorrow/calva/issues/281) + +## [2.0.32] - 17.08.2019 +- Support for starting leiningen and clj projects with aliases ## [2.0.31] - 13.08.2019 - Support Jack-in and Connect in multi-project workspaces. diff --git a/package-lock.json b/package-lock.json index 3a4331250..ed1981619 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "calva", - "version": "2.0.31", + "version": "2.0.32", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eba43b8fc..ab12ce886 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Calva: Clojure & Clojurescript Interactive Programming", "description": "Integrated REPL, formatter, Paredit, and more. Powered by nREPL.", "icon": "assets/calva.png", - "version": "2.0.31", + "version": "2.0.32", "publisher": "betterthantomorrow", "author": { "name": "Better Than Tomorrow", From 39e47e017e47fcbbe545d2c1333f7db592ed5d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 16 Aug 2019 12:55:18 +0200 Subject: [PATCH 020/128] Use `cider/cider-nrepl` `0.22.0-beta10` --- calva/nrepl/jack-in.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 8f1bc6d6b..c97d11f83 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -24,17 +24,17 @@ export async function detectProjectType(): Promise { const cliDependencies = { "nrepl": "0.6.0", - "cider/cider-nrepl": "0.21.1", + "cider/cider-nrepl": "0.22.0-beta10", } const figwheelDependencies = { "cider/piggieback": "0.4.1", "figwheel-sidecar": "0.5.18" } const shadowDependencies = { - "cider/cider-nrepl": "0.21.1", + "cider/cider-nrepl": "0.22.0-beta10", } const leinPluginDependencies = { - "cider/cider-nrepl": "0.21.1" + "cider/cider-nrepl": "0.22.0-beta10" } const leinDependencies = { "nrepl": "0.6.0", From f2dbf4550edb67e3663eecbd1d29acd7ee418a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 16 Aug 2019 22:04:50 +0200 Subject: [PATCH 021/128] Make let readable --- calva/utilities.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/calva/utilities.ts b/calva/utilities.ts index 56bb41b0f..b5d85c6ab 100644 --- a/calva/utilities.ts +++ b/calva/utilities.ts @@ -184,7 +184,9 @@ function getActualWord(document, position, selected, word) { } function getWordAtPosition(document, position) { - let selected = document.getWordRangeAtPosition(position), selectedText = selected !== undefined ? document.getText(new vscode.Range(selected.start, selected.end)) : "", text = getActualWord(document, position, selected, selectedText); + let selected = document.getWordRangeAtPosition(position), + selectedText = selected !== undefined ? document.getText(new vscode.Range(selected.start, selected.end)) : "", + text = getActualWord(document, position, selected, selectedText); return text; } From 42d3726561f935a2d0808e8dd505b4227677f787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 16 Aug 2019 22:05:52 +0200 Subject: [PATCH 022/128] Prepare to figure out __prefix__ --- calva/providers/completion.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/calva/providers/completion.ts b/calva/providers/completion.ts index 87f43ae9b..aee137535 100644 --- a/calva/providers/completion.ts +++ b/calva/providers/completion.ts @@ -1,6 +1,7 @@ import { TextDocument, Position, CancellationToken, CompletionContext, Hover, CompletionItemKind, window, CompletionList, CompletionItemProvider, CompletionItem } from 'vscode'; import * as state from '../state'; import * as util from '../utilities'; +import select from '../select'; export default class CalvaCompletionItemProvider implements CompletionItemProvider { state: any; @@ -24,9 +25,11 @@ export default class CalvaCompletionItemProvider implements CompletionItemProvid let text = util.getWordAtPosition(document, position); if (this.state.deref().get("connected")) { - let client = util.getSession(util.getFileType(document)); - let res = await client.complete(util.getNamespace(document), text); - let results = res.completions || []; + const formSelection = select.getFormSelection(document, position, true), + currentWordRange = document.getWordRangeAtPosition(position), + client = util.getSession(util.getFileType(document)), + res = await client.complete(util.getNamespace(document), text), + results = res.completions || []; return new CompletionList( results.map(item => ({ label: item.candidate, From 7515918928c1d28c665aec6c57609a24db61e0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 17 Aug 2019 09:54:29 +0200 Subject: [PATCH 023/128] Extract `context` for cider-nrepl `complete` --- calva/providers/completion.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/calva/providers/completion.ts b/calva/providers/completion.ts index aee137535..b1581602d 100644 --- a/calva/providers/completion.ts +++ b/calva/providers/completion.ts @@ -25,10 +25,17 @@ export default class CalvaCompletionItemProvider implements CompletionItemProvid let text = util.getWordAtPosition(document, position); if (this.state.deref().get("connected")) { - const formSelection = select.getFormSelection(document, position, true), - currentWordRange = document.getWordRangeAtPosition(position), + const toplevelSelection = select.getFormSelection(document, position, true), + toplevel = document.getText(toplevelSelection), + toplevelStartOffset = document.offsetAt(toplevelSelection.start), + wordRange = document.getWordRangeAtPosition(position), + wordStartLocalOffset = document.offsetAt(wordRange.start) - toplevelStartOffset, + wordEndLocalOffset = document.offsetAt(wordRange.end) - toplevelStartOffset, + contextStart = toplevel.substring(0, wordStartLocalOffset), + contextEnd = toplevel.substring(wordEndLocalOffset), + context = `${contextStart}__prefix__${contextEnd}`, client = util.getSession(util.getFileType(document)), - res = await client.complete(util.getNamespace(document), text), + res = await client.complete(util.getNamespace(document), text, context), results = res.completions || []; return new CompletionList( results.map(item => ({ From 199f8ded40d2d7e72ece66fa5fd04b92c5cdf5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 17 Aug 2019 15:01:35 +0200 Subject: [PATCH 024/128] Handle non-closed top level forms --- calva/providers/completion.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/calva/providers/completion.ts b/calva/providers/completion.ts index b1581602d..71fb4e7a8 100644 --- a/calva/providers/completion.ts +++ b/calva/providers/completion.ts @@ -2,6 +2,7 @@ import { TextDocument, Position, CancellationToken, CompletionContext, Hover, Co import * as state from '../state'; import * as util from '../utilities'; import select from '../select'; +import * as docMirror from '../calva-fmt/ts/docmirror'; export default class CalvaCompletionItemProvider implements CompletionItemProvider { state: any; @@ -28,6 +29,8 @@ export default class CalvaCompletionItemProvider implements CompletionItemProvid const toplevelSelection = select.getFormSelection(document, position, true), toplevel = document.getText(toplevelSelection), toplevelStartOffset = document.offsetAt(toplevelSelection.start), + toplevelStartCursor = docMirror.getDocument(document).getTokenCursor(toplevelStartOffset + 1), + toplevelIsValidForm = toplevelStartCursor.forwardList(), wordRange = document.getWordRangeAtPosition(position), wordStartLocalOffset = document.offsetAt(wordRange.start) - toplevelStartOffset, wordEndLocalOffset = document.offsetAt(wordRange.end) - toplevelStartOffset, @@ -35,7 +38,7 @@ export default class CalvaCompletionItemProvider implements CompletionItemProvid contextEnd = toplevel.substring(wordEndLocalOffset), context = `${contextStart}__prefix__${contextEnd}`, client = util.getSession(util.getFileType(document)), - res = await client.complete(util.getNamespace(document), text, context), + res = await client.complete(util.getNamespace(document), text, toplevelIsValidForm ? context : null), results = res.completions || []; return new CompletionList( results.map(item => ({ From 45bb21370263c67bacf589dda53f2f1c5640a5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 17 Aug 2019 15:53:00 +0200 Subject: [PATCH 025/128] Don't pass any context when we don't have it --- calva/providers/completion.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/providers/completion.ts b/calva/providers/completion.ts index 71fb4e7a8..eedd0b419 100644 --- a/calva/providers/completion.ts +++ b/calva/providers/completion.ts @@ -30,13 +30,13 @@ export default class CalvaCompletionItemProvider implements CompletionItemProvid toplevel = document.getText(toplevelSelection), toplevelStartOffset = document.offsetAt(toplevelSelection.start), toplevelStartCursor = docMirror.getDocument(document).getTokenCursor(toplevelStartOffset + 1), - toplevelIsValidForm = toplevelStartCursor.forwardList(), wordRange = document.getWordRangeAtPosition(position), wordStartLocalOffset = document.offsetAt(wordRange.start) - toplevelStartOffset, wordEndLocalOffset = document.offsetAt(wordRange.end) - toplevelStartOffset, contextStart = toplevel.substring(0, wordStartLocalOffset), contextEnd = toplevel.substring(wordEndLocalOffset), context = `${contextStart}__prefix__${contextEnd}`, + toplevelIsValidForm = toplevelStartCursor.forwardList() && context != '__prefix__', client = util.getSession(util.getFileType(document)), res = await client.complete(util.getNamespace(document), text, toplevelIsValidForm ? context : null), results = res.completions || []; From 64a4afa2f6355f35e0219ab066979fa9a7efcd5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 17 Aug 2019 16:36:51 +0200 Subject: [PATCH 026/128] Revert back to stable cider-nrepl --- calva/nrepl/jack-in.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index c97d11f83..8f1bc6d6b 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -24,17 +24,17 @@ export async function detectProjectType(): Promise { const cliDependencies = { "nrepl": "0.6.0", - "cider/cider-nrepl": "0.22.0-beta10", + "cider/cider-nrepl": "0.21.1", } const figwheelDependencies = { "cider/piggieback": "0.4.1", "figwheel-sidecar": "0.5.18" } const shadowDependencies = { - "cider/cider-nrepl": "0.22.0-beta10", + "cider/cider-nrepl": "0.21.1", } const leinPluginDependencies = { - "cider/cider-nrepl": "0.22.0-beta10" + "cider/cider-nrepl": "0.21.1" } const leinDependencies = { "nrepl": "0.6.0", From fe4f744dcfba74d1a4a95182bbea0c86308f52d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 18 Aug 2019 11:07:30 +0200 Subject: [PATCH 027/128] Explain a bit what `forwardList` is used for --- calva/providers/completion.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/calva/providers/completion.ts b/calva/providers/completion.ts index eedd0b419..a1865cebf 100644 --- a/calva/providers/completion.ts +++ b/calva/providers/completion.ts @@ -36,6 +36,7 @@ export default class CalvaCompletionItemProvider implements CompletionItemProvid contextStart = toplevel.substring(0, wordStartLocalOffset), contextEnd = toplevel.substring(wordEndLocalOffset), context = `${contextStart}__prefix__${contextEnd}`, + // using forwardList() here to see that the toplevel form is balanced toplevelIsValidForm = toplevelStartCursor.forwardList() && context != '__prefix__', client = util.getSession(util.getFileType(document)), res = await client.complete(util.getNamespace(document), text, toplevelIsValidForm ? context : null), From c1e0d45fe92c2cd565bdd30cee54a6677b439719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 18 Aug 2019 11:06:31 +0200 Subject: [PATCH 028/128] Add nodemon and webpack-dev-server --- package-lock.json | 1317 ++++++++++++++++++++++++++++++++++++- package.json | 8 +- webpack.config.js | 9 + webview-src/tsconfig.json | 45 +- 4 files changed, 1349 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index ed1981619..4f090ccde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "calva", - "version": "2.0.32", + "version": "2.0.33", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -42,6 +42,29 @@ "ts-node": "^7.0.1" } }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/mocha": { "version": "2.2.48", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", @@ -349,12 +372,33 @@ "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", "dev": true }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -467,6 +511,21 @@ "es-abstract": "^1.7.0" } }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -702,6 +761,12 @@ "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", "dev": true }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -771,6 +836,51 @@ } } }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + }, + "dependencies": { + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + } + } + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -909,6 +1019,12 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -1009,6 +1125,12 @@ "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", "dev": true }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1077,6 +1199,12 @@ "tslib": "^1.9.0" } }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -1116,6 +1244,12 @@ } } }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -1307,6 +1441,37 @@ "yargs": "^12.0.1" } }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -1397,6 +1562,15 @@ "elliptic": "^6.0.0" } }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -1454,6 +1628,12 @@ "randomfill": "^1.0.3" } }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, "cson-parser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.2.tgz", @@ -1607,12 +1787,73 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -1663,6 +1904,29 @@ } } }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1700,6 +1964,12 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, "diagnostic-channel": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz", @@ -1729,6 +1999,31 @@ "randombytes": "^2.0.0" } }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -1744,6 +2039,21 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -2131,12 +2441,27 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true + }, "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", "dev": true }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -2147,6 +2472,21 @@ "safe-buffer": "^5.1.1" } }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -2359,6 +2699,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -2547,6 +2896,32 @@ } } }, + "follow-redirects": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", + "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "dev": true, + "requires": { + "debug": "^3.2.6" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -3252,6 +3627,12 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -3301,6 +3682,15 @@ } } }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -3331,6 +3721,46 @@ "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==", "dev": true }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -3343,6 +3773,12 @@ "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", "dev": true }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3508,10 +3944,34 @@ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -3520,6 +3980,23 @@ "toidentifier": "1.0.0" } }, + "http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, "http-proxy-agent": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", @@ -3541,6 +4018,18 @@ } } }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -3626,6 +4115,12 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -3645,6 +4140,12 @@ "immutable": "^3.7.6" } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -3727,6 +4228,16 @@ "through": "^2.3.6" } }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, "interpret": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", @@ -3739,11 +4250,29 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, + "is-absolute-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.0.tgz", + "integrity": "sha512-3OkP8XrM2Xq4/IxsJnClfMp3OaM3TAatLPLKPeWcxLBTrpe6hihwtX+XZfJTcXg/FTRi4qjy0y/C5qiyNxY24g==", + "dev": true + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -3800,6 +4329,15 @@ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -3872,6 +4410,22 @@ "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -3892,6 +4446,47 @@ } } }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + }, + "dependencies": { + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + } + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3907,6 +4502,12 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -3922,6 +4523,12 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -4024,6 +4631,12 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -4056,12 +4669,27 @@ "readable-stream": "~2.0.6" } }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, "lazy-cache": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", @@ -4162,6 +4790,18 @@ "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", "dev": true }, + "loglevel": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz", + "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==", + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", @@ -4541,6 +5181,22 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -4601,6 +5257,12 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-forge": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", + "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "dev": true + }, "node-gyp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.0.0.tgz", @@ -4708,6 +5370,50 @@ } } }, + "nodemon": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.1.tgz", + "integrity": "sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==", + "dev": true, + "requires": { + "chokidar": "^2.1.5", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.6", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -7914,6 +8620,12 @@ "isobject": "^3.0.1" } }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -7953,6 +8665,15 @@ "is-wsl": "^1.1.0" } }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -7967,6 +8688,15 @@ "wordwrap": "~1.0.0" } }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -8065,12 +8795,39 @@ "p-limit": "^2.0.0" } }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } + }, "p-try": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, "pako": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", @@ -8261,6 +9018,21 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -8321,6 +9093,25 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, + "portfinder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.22.tgz", + "integrity": "sha512-aZuwaz9ujJsyE8C5kurXAD8UmRxsJr+RtZWyQRvRk19Z2ri5uuHw5YS4tDBZrJlOS9Zw96uAbBuPb6W4wgvV5A==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -8412,6 +9203,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, "prettier": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", @@ -8467,6 +9264,12 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -8592,6 +9395,18 @@ } } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "read-pkg": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", @@ -8715,6 +9530,25 @@ "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", "dev": true }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -8852,6 +9686,12 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -8981,11 +9821,35 @@ } } }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", + "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "dev": true, + "requires": { + "node-forge": "0.7.5" + } + }, "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -9019,6 +9883,41 @@ "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", "dev": true }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -9261,6 +10160,56 @@ } } }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -9338,6 +10287,93 @@ "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, + "spdy": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", + "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -9587,6 +10623,15 @@ } } }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, "terser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.0.0.tgz", @@ -9670,6 +10715,18 @@ } } }, + "thunky": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", + "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -9741,6 +10798,26 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -9902,6 +10979,15 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -9938,6 +11024,15 @@ "imurmurhash": "^0.1.4" } }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, "universal-analytics": { "version": "0.4.20", "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", @@ -10008,12 +11103,36 @@ } } }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, "upath": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", "dev": true }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -10075,6 +11194,15 @@ "requires-port": "^1.0.0" } }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -10189,6 +11317,15 @@ "neo-async": "^2.5.0" } }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, "webpack": { "version": "4.33.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.33.0.tgz", @@ -10313,6 +11450,133 @@ } } }, + "webpack-dev-middleware": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", + "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.2", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.8.0.tgz", + "integrity": "sha512-Hs8K9yI6pyMvGkaPTeTonhD6JXVsigXDApYk9JLW4M7viVBspQvb1WdAcWxqtmttxNW4zf2UFLsLNe0y87pIGQ==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.6", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.2.1", + "http-proxy-middleware": "^0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.0", + "killable": "^1.0.1", + "loglevel": "^1.6.3", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.21", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.4", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.1", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.0", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "12.0.5" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, "webpack-sources": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", @@ -10323,6 +11587,23 @@ "source-map": "~0.6.1" } }, + "websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -10347,6 +11628,15 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -10424,6 +11714,17 @@ "mkdirp": "^0.5.1" } }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", @@ -10440,6 +11741,12 @@ "resolved": "https://registry.npmjs.org/wsl-path/-/wsl-path-1.1.0.tgz", "integrity": "sha512-+BSk4znlhB1Gr8ENwKUut6bb1MXBPYjE5zaKpVL2TRTU7BaOIQbGVKc4Yr34b7OcLtB/Kx0e9VLf1MDp6vRxKA==" }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index ab12ce886..af51522f1 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Calva: Clojure & Clojurescript Interactive Programming", "description": "Integrated REPL, formatter, Paredit, and more. Powered by nREPL.", "icon": "assets/calva.png", - "version": "2.0.32", + "version": "2.0.33", "publisher": "betterthantomorrow", "author": { "name": "Better Than Tomorrow", @@ -1071,6 +1071,8 @@ "watch-webpack": "webpack --mode development --watch", "watch-ts": "rm -rf ./out/* ./tsconfig.tsbuildinfo && tsc -watch -p ./tsconfig.json", "release-cljs": "npx shadow-cljs release :calva-lib", + "start": "ts-node --inspect=5858 webview-src/main.ts", + "repl-window-dev-server": "concurrently \"node ./node_modules/nodemon/bin/nodemon.js webview-src/main.ts\" \"npx webpack-dev-server\"", "webpack-release": "webpack --mode production", "compile-cljs": "npx shadow-cljs compile :calva-lib", "compile": "npm run compile-cljs && npm run webpack", @@ -1116,6 +1118,7 @@ "eslint-plugin-standard": "^3.0.1", "file-loader": "^3.0.1", "node-gyp": "^5.0.0", + "nodemon": "^1.19.1", "sass": "^1.0.0-beta.3", "shadow-cljs": "^2.8.39", "style-loader": "^0.23.1", @@ -1126,6 +1129,7 @@ "url-loader": "^1.1.2", "vscode": "^1.1.34", "webpack": "^4.33.0", - "webpack-cli": "^3.3.3" + "webpack-cli": "^3.3.3", + "webpack-dev-server": "^3.8.0" } } diff --git a/webpack.config.js b/webpack.config.js index f660ded0f..2f9c700d3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -82,6 +82,15 @@ const REPL_WINDOW = { filename: 'main.js', path: path.resolve(__dirname, 'html') }, + devServer: { + historyApiFallback: true, + host: "0.0.0.0", + compress: true, + contentBase: path.join(__dirname, 'www'), + proxy: { + '/api': 'http://localhost:3000', + } + }, devtool: 'source-map' } diff --git a/webview-src/tsconfig.json b/webview-src/tsconfig.json index aabd94c5b..4ccf97b7a 100644 --- a/webview-src/tsconfig.json +++ b/webview-src/tsconfig.json @@ -1,24 +1,23 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "lib": [ - "es6", - "dom" - ], - "sourceMap": true, - "rootDir": ".", - /* Strict Type-Checking Option */ - //"strict": true, /* enable all strict type-checking options */ - /* Additional Checks */ - "noUnusedLocals": true /* Report errors on unused locals. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - }, - "exclude": [ - "node_modules", - ".vscode-test" - ] -} + "compilerOptions": { + "moduleResolution": "node", + "target": "es5", + "lib": [ + "es2016", + "dom" + ], + "jsx": "react", + "downlevelIteration": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "declaration": false, + "noImplicitAny": false, + "noImplicitUseStrict": false, + "removeComments": true, + "noLib": false, + "preserveConstEnums": true, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "buildOnSave": false, +} \ No newline at end of file From 3b2eef42a0476ebe4ed8a13d14cc2e9afc081b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 18 Aug 2019 12:52:28 +0200 Subject: [PATCH 029/128] Move the rest of the repl-interactor code in here --- package.json | 4 +- webpack.config.js | 4 +- webview-src/client/clojure-lexer.ts | 109 +++++ webview-src/client/hotkeys.ts | 99 ++++ webview-src/client/indent.ts | 211 ++++++++ webview-src/client/index.ts | 3 + webview-src/client/lexer.ts | 89 ++++ webview-src/client/main.ts | 10 + webview-src/client/model.ts | 370 ++++++++++++++ webview-src/client/paredit.ts | 358 ++++++++++++++ webview-src/client/readline.ts | 717 ++++++++++++++++++++++++++++ webview-src/client/repl-console.ts | 598 +++++++++++++++++++++++ webview-src/client/token-cursor.ts | 370 ++++++++++++++ webview-src/client/undo.ts | 123 +++++ webview-src/{ => server}/main.ts | 6 +- 15 files changed, 3064 insertions(+), 7 deletions(-) create mode 100644 webview-src/client/clojure-lexer.ts create mode 100644 webview-src/client/hotkeys.ts create mode 100644 webview-src/client/indent.ts create mode 100644 webview-src/client/index.ts create mode 100644 webview-src/client/lexer.ts create mode 100644 webview-src/client/main.ts create mode 100644 webview-src/client/model.ts create mode 100644 webview-src/client/paredit.ts create mode 100644 webview-src/client/readline.ts create mode 100644 webview-src/client/repl-console.ts create mode 100644 webview-src/client/token-cursor.ts create mode 100644 webview-src/client/undo.ts rename webview-src/{ => server}/main.ts (99%) diff --git a/package.json b/package.json index af51522f1..c18741b4a 100644 --- a/package.json +++ b/package.json @@ -1071,8 +1071,8 @@ "watch-webpack": "webpack --mode development --watch", "watch-ts": "rm -rf ./out/* ./tsconfig.tsbuildinfo && tsc -watch -p ./tsconfig.json", "release-cljs": "npx shadow-cljs release :calva-lib", - "start": "ts-node --inspect=5858 webview-src/main.ts", - "repl-window-dev-server": "concurrently \"node ./node_modules/nodemon/bin/nodemon.js webview-src/main.ts\" \"npx webpack-dev-server\"", + "NOT-USED-start": "ts-node --inspect=5858 webview-src/server/main.ts", + "NOT-USED-repl-window-dev-server": "concurrently \"npx nodemon -- ./webview-src/client/index.js\" \"npx webpack-dev-server\"", "webpack-release": "webpack --mode production", "compile-cljs": "npx shadow-cljs compile :calva-lib", "compile": "npm run compile-cljs && npm run webpack", diff --git a/webpack.config.js b/webpack.config.js index 2f9c700d3..648164dcb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -34,7 +34,7 @@ const CALVA_MAIN = { } const REPL_WINDOW = { - entry: './webview-src/main.ts', + entry: './webview-src/server/main.ts', performance: { maxEntrypointSize: 1024000, maxAssetSize: 1024000, @@ -86,7 +86,7 @@ const REPL_WINDOW = { historyApiFallback: true, host: "0.0.0.0", compress: true, - contentBase: path.join(__dirname, 'www'), + contentBase: path.join(__dirname, 'html'), proxy: { '/api': 'http://localhost:3000', } diff --git a/webview-src/client/clojure-lexer.ts b/webview-src/client/clojure-lexer.ts new file mode 100644 index 000000000..613393669 --- /dev/null +++ b/webview-src/client/clojure-lexer.ts @@ -0,0 +1,109 @@ +import { LexicalGrammar, Token as LexerToken } from "./lexer" + +/** The 'toplevel' lexical grammar. This grammar contains all normal tokens. Multi-line strings are identified as + * "str-start", which trigger the lexer to switch to the 'multstring' lexical grammar. + */ +let toplevel = new LexicalGrammar() + + +/** Maps open and close parentheses to their class. */ +export const canonicalParens = { + '#?(': '()', + '#?@(': '()', + '#(': '()', + '(': '()', + ')': '()', + '#{': '{}', + '{': '{}', + '}': '{}', + '[': '[]', + ']': '[]' +} + + +/** Returns true if open and close are compatible parentheses */ +export function validPair(open: string, close: string) { + return canonicalParens[open] == canonicalParens[close]; +} + +export interface Token extends LexerToken { + state: ScannerState; +} + +// whitespace +toplevel.terminal(/[\s,]+/, (l, m) => ({ type: "ws" })) +// comments +toplevel.terminal(/;.*/, (l, m) => ({ type: "comment" })) +// open parens +toplevel.terminal(/\(|\[|\{|#\(|#\?\(|#\{|#\?@\(/, (l, m) => ({ type: "open" })) +// close parens +toplevel.terminal(/\)|\]|\}/, (l, m) => ({ type: "close" })) + +// punctuators +toplevel.terminal(/~@|~|'|#'|#:|#_|\^|`|#|\^:/, (l, m) => ({ type: "punc" })) + +toplevel.terminal(/true|false|nil/, (l, m) => ({ type: "lit" })) +toplevel.terminal(/[0-9]+[rR][0-9a-zA-Z]+/, (l, m) => ({ type: "lit" })) +toplevel.terminal(/[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?/, (l, m) => ({ type: "lit" })) + +toplevel.terminal(/:[^()[\]\{\}#,~@'`^\"\s;]*/, (l, m) => ({ type: "kw" })) +// this is a REALLY lose symbol definition, but similar to how clojure really collects it. numbers/true/nil are all +toplevel.terminal(/[^()[\]\{\}#,~@'`^\"\s:;][^()[\]\{\}#,~@'`^\"\s;]*/, (l, m) => ({ type: "id" })) + +// complete string on a single line +toplevel.terminal(/"([^"\\]|\\.)*"/, (l, m) => ({ type: "str" })) +toplevel.terminal(/"([^"\\]|\\.)*/, (l, m) => ({ type: "str-start" })) +toplevel.terminal(/./, (l, m) => ({ type: "junk" })) + +/** This is the multi-line string grammar. It spits out 'str-end' once it is time to switch back to the 'toplevel' grammar, and 'str-inside' if the string continues. */ +let multstring = new LexicalGrammar() +// end a multiline string +multstring.terminal(/([^"\\]|\\.)*"/, (l, m) => ({ type: "str-end" })) +// still within a multiline string +multstring.terminal(/([^"\\]|\\.)*/, (l, m) => ({ type: "str-inside" })) + +/** + * The state of the scanner. + * We only really need to know if we're inside a string or not. + */ +export interface ScannerState { + /** Are we scanning inside a string? If so use multstring grammar, otherwise use toplevel. */ + inString: boolean +} + +/** + * A Clojure(Script) lexical analyser. + * Takes a line of text and a start state, and returns an array of Token, updating its internal state. + */ +export class Scanner { + state: ScannerState = { inString: false }; + + processLine(line: string, state: ScannerState = this.state) { + let tks: Token[] = []; + this.state = state; + let lex = (this.state.inString ? multstring : toplevel).lex(line); + let tk: LexerToken; + do { + tk = lex.scan(); + if (tk) { + let oldpos = lex.position; + switch (tk.type) { + case "str-end": // multiline string ended, switch back to toplevel + this.state = { ...this.state, inString: false }; + lex = toplevel.lex(line); + lex.position = oldpos; + break; + case "str-start": // multiline string started, switch to multstring. + this.state = { ...this.state, inString: true }; + lex = multstring.lex(line); + lex.position = oldpos; + break; + } + tks.push({ ...tk, state: this.state }); + } + } while (tk); + // insert a sentinel EOL value, this allows us to simplify TokenCaret's implementation. + tks.push({ type: "eol", raw: "\n", offset: line.length, state: this.state }) + return tks; + } +} \ No newline at end of file diff --git a/webview-src/client/hotkeys.ts b/webview-src/client/hotkeys.ts new file mode 100644 index 000000000..0b6ec9e30 --- /dev/null +++ b/webview-src/client/hotkeys.ts @@ -0,0 +1,99 @@ +export const ALT = 1; +export const CTRL = 2; +export const SHIFT = 4; +export const META = 8; + +const isMac = navigator.platform.match(/Mac(Intel|PPC|68k)/i); // somewhat optimistic this would run on MacOS8 but hey ;) + +let keyToId: {[id: string]: number} = {} +let idToKey: {[id: string]: string} = {} + +interface CommandWidget { + commands: {[id: string]: () => void}; +} + +function key(name: string, id: number) { + keyToId[name.toLowerCase()] = id; + idToKey[id] = name; +} + +key("Backspace", 8) +key("Space", 0x20) +key("Tab", 9) +key("Return", 13) +key("End", 35) +key("/", 191) +key("[", 219) + +key("Home", 36) +key("LeftArrow", 37) +key("UpArrow", 38) +key("RightArrow", 39) +key("DownArrow", 40) +key("Delete", 46) + +export function parseHotKey(key: string, command: any) { + let parts = key.split("+").map(x => x.trim().toLowerCase()); + let i=0; + let modifiers = 0; + outer: for(; i { + table: HotKey[] = []; + constructor(keys: {[id: string]: keyof T["commands"]}) { + for(let key in keys) + this.table.push(parseHotKey(key, keys[key])); + } + + execute(obj: T, e: KeyboardEvent) { + for(let key of this.table) { + if(key.match(e)) { + obj.commands[key.command]() + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/webview-src/client/indent.ts b/webview-src/client/indent.ts new file mode 100644 index 000000000..50267d931 --- /dev/null +++ b/webview-src/client/indent.ts @@ -0,0 +1,211 @@ +import { LineInputModel } from "./model"; + +const whitespace = new Set(["ws", "comment", "eol"]) + +type IndentRule = ["block", number] | ["inner", number] | ["inner", number, number]; + +/** Rules shamelessly copied from cljfmt. */ +let indentRules: { [id: string]: IndentRule[]} = { + "alt!": [["block", 0]], + "alt!!": [["block", 0]], + "are": [["block", 2]] , + "as->": [["block", 2]], + "binding": [["block", 1]], + "bound-fn": [["inner", 1]], + "case": [["block", 1]], + "catch": [["block", 2]], + "comment": [["block", 0]], + "cond": [["block", 0]], + "condp": [["block", 2]], + "cond->": [["block", 1]], + "cond->>": [["block", 1]], + "def": [["inner", 0]], + "defmacro": [["inner", 0]], + "defmethod": [["inner", 0]], + "defmulti": [["inner", 0]], + "defn": [["inner", 0]], + "defn-": [["inner", 0]], + "defonce": [["inner", 0]], + "defprotocol": [["block", 1], ["inner", 1]], + "defrecord": [["block", 2], ["inner", 1]], + "defstruct": [["block", 1]], + "deftest": [["inner", 0]], + "deftype": [["block", 2], ["inner", 1]], + "do": [["block", 0]], + "doseq": [["block", 1]], + "dotimes": [["block", 1]], + "doto": [["block", 1]], + "extend": [["block", 1]], + "extend-protocol": [["block", 1], ["inner", 1]], + "extend-type": [["block", 1], ["inner", 1]], + "fdef": [["inner", 0]], + "finally": [["block", 0]], + "fn": [["inner", 0]], + "for": [["block", 1]], + "future": [["block", 0]], + "go": [["block", 0]], + "go-loop": [["block", 1]], + "if": [["block", 1]], + "if-let": [["block", 1]], + "if-not": [["block", 1]], + "if-some": [["block", 1]], + "let": [["block", 1]], + "letfn": [["block", 1], ["inner", 2, 0]], + "locking": [["block", 1]], + "loop": [["block", 1]], + "match": [["block", 1]], + "ns": [["block", 1]], + "proxy": [["block", 2], ["inner", 1]], + "reify": [["inner", 0], ["inner", 1]], + "struct-map": [["block", 1]], + "testing": [["block", 1]], + "thread": [["block", 0]], + "try": [["block", 0]], + "use-fixtures": [["inner", 0]], + "when": [["block", 1]], + "when-first": [["block", 1]], + "when-let": [["block", 1]], + "when-not": [["block", 1]], + "when-some": [["block", 1]], + "while": [["block", 1]], + "with-local-vars": [["block", 1]], + "with-open": [["block", 1]], + "with-out-str": [["block", 0]], + "with-precision": [["block", 1]], + "with-redefs": [["block", 1]], +} + +/** + * The information about an enclosing s-expr, returned by collectIndents + */ +interface IndentInformation { + /** The first token in the expression (after the open paren/bracket etc.), as a raw string */ + first: string; + + /** The indent immediately after the open paren/bracket etc */ + startIndent: number; + + /** If there is a second token on the same line as the first token, the indent for that token */ + firstItemIdent: number; + + /** The applicable indent rules for this IndentInformation, local only. */ + rules: IndentRule[]; + + /** The index at which the cursor (or the sexpr containing the cursor at this level) is in the expression. */ + argPos: number; + + /** The number of expressions on the first line of this expression. */ + exprsOnLine: number; +} + +/** + * If a token's raw string is in this set, then it counts as an 'open list'. An open list that starts with a symbol + * is something that could be + * considered code, so special formatting rules apply. + */ +const OPEN_LIST = new Set(["#(", "#?(", "(", "#?@("]) + +/** + * Analyses the text before position in the document, and returns a list of enclosing expression information with + * various indent information, for use with getIndent() + * + * @param document The document to analyse + * @param position The position (as [row, col] into the document to analyse from) + * @param maxDepth The maximum depth upwards from the expression to search. + * @param maxLines The maximum number of lines above the position to search until we bail with an imprecise answer. + */ +export function collectIndents(document: LineInputModel, offset: number, maxDepth: number = 3, maxLines: number = 20): IndentInformation[] { + let cursor = document.getTokenCursor(offset); + cursor.backwardWhitespace(); + let argPos = 0; + let startLine = cursor.line; + let exprsOnLine = 0; + let lastLine = cursor.line; + let lastIndent = 0; + let indents: IndentInformation[] = []; + do { + if(!cursor.backwardSexp()) { + // this needs some work.. + let prevToken = cursor.getPrevToken(); + if(prevToken.type == 'open' && prevToken.offset <= 1) { + maxDepth = 0; // treat an sexpr starting on line 0 sensibly. + } + // skip past the first item and record the indent of the first item on the same line if there is one. + let nextCursor = cursor.clone(); + nextCursor.forwardSexp() + nextCursor.forwardWhitespace(); + + // iff the first item of this list is a an identifier, and the second item is on the same line, indent to that second item. otherwise indent to the open paren. + let firstItemIdent = cursor.getToken().type == "id" && nextCursor.line == cursor.line && !nextCursor.atEnd() && OPEN_LIST.has(prevToken.raw) ? nextCursor.rowCol[1] : cursor.rowCol[1]; + + + let token = cursor.getToken().raw; + let startIndent = cursor.rowCol[1]; + if(!cursor.backwardUpList()) + break; + let indentRule = indentRules[token] || []; + indents.unshift({ first: token, rules: indentRule, argPos, exprsOnLine, startIndent, firstItemIdent }); + argPos = 0; + exprsOnLine = 1; + } + + if(cursor.line != lastLine) { + let head = cursor.clone(); + head.forwardSexp(); + head.forwardWhitespace(); + if(!head.atEnd()) { + lastIndent = head.rowCol[1]; + exprsOnLine = 0; + lastLine = cursor.line; + } + } + + if(whitespace.has(cursor.getPrevToken().type)) { + argPos++; + exprsOnLine++; + } + } while(!cursor.atStart() && Math.abs(startLine-cursor.line) < maxLines && indents.length < maxDepth); + if(!indents.length) + indents.push({argPos: 0, first: null, rules: [], exprsOnLine: 0, startIndent: lastIndent >= 0 ? lastIndent : 0, firstItemIdent: lastIndent >= 0 ? lastIndent : 0}) + return indents; +} + +/** Returns the expected newline indent for the given position, in characters. */ +export function getIndent(document: LineInputModel, offset: number): number { + let state = collectIndents(document, offset); + // now find applicable indent rules + let indent = -1; + let thisBlock = state[state.length-1]; + if(!state.length) + return 0; + + for(let pos = state.length-1; pos >= 0; pos--) { + for(let rule of state[pos].rules) { + if(rule[0] == "inner") { + if(pos + rule[1] == state.length-1) { + if(rule.length == 3) { + if(rule[2] > thisBlock.argPos) + indent = thisBlock.startIndent + 1; + } else + indent = thisBlock.startIndent + 1; + } + } else if(rule[0] == "block" && pos == state.length-1) { + if(thisBlock.exprsOnLine <= rule[1]) { + if(thisBlock.argPos >= rule[1]) + indent = thisBlock.startIndent + 1 + } else { + indent = thisBlock.firstItemIdent; + } + } + } + } + + if(indent == -1) { + // no indentation styles applied, so use default style. + if(thisBlock.exprsOnLine > 0) + indent = thisBlock.firstItemIdent; + else + indent = thisBlock.startIndent + } + return indent; +} \ No newline at end of file diff --git a/webview-src/client/index.ts b/webview-src/client/index.ts new file mode 100644 index 000000000..74770ddc1 --- /dev/null +++ b/webview-src/client/index.ts @@ -0,0 +1,3 @@ +export * from "./repl-console" +export * from "./hotkeys" +export * from "./readline" \ No newline at end of file diff --git a/webview-src/client/lexer.ts b/webview-src/client/lexer.ts new file mode 100644 index 000000000..798add3b2 --- /dev/null +++ b/webview-src/client/lexer.ts @@ -0,0 +1,89 @@ +/** + * A Lexical analyser + * @module lexer + */ + +/** + * The base Token class. Contains the token type, + * the raw string of the token, and the offset into the input line. + */ +export interface Token { + type: string; + raw: string; + offset: number; +} + +/** + * A Lexical rule for a terminal. Consists of a RegExp and an action. + */ +export interface Rule { + r: RegExp; + fn: (Lexer, RegExpExecArray) => any +} + +/** + * A Lexer instance, parsing a given file. Usually you should use a LexicalGrammar to + * create one of these. + * + * @class + * @param {string} source the source code to parse + * @param rules the rules of this lexer. + */ +export class Lexer { + position: number = 0; + constructor(public source: string, public rules: Rule[]) { + } + + /** Returns the next token in this lexer, or null if at the end. If the match fails, throws an Error. */ + scan(): Token { + var token = null; + var length = 0; + this.rules.forEach(rule => { + rule.r.lastIndex = this.position; + var x = rule.r.exec(this.source); + if (x && x[0].length > length && this.position + x[0].length == rule.r.lastIndex) { + token = rule.fn(this, x); + token.offset = this.position; + token.raw = x[0]; + length = x[0].length; + } + }) + this.position += length; + if (token == null) { + if (this.position == this.source.length) + return null; + throw new Error("Unexpected character at " + this.position + ": " + JSON.stringify(this.source)); + } + return token; + } +} + +/** + * A lexical grammar- factory for lexer instances. + * @class + */ +export class LexicalGrammar { + rules: Rule[] = []; + + /** + * Defines a terminal with the given pattern and constructor. + * @param {string | RegExp} pattern the pattern this terminal must match. + * @param {function(Array): Object} fn returns a lexical token representing + * this terminal. An additional "offset" property containing the token source position + * will also be added, as well as a "raw" property, containing the raw string match. + */ + terminal(pattern: string | RegExp, fn: (T, RegExpExecArray) => any): void { + this.rules.push({ + // This is b/c the RegExp constructor seems to not like our union type (unknown reasons why) + r: pattern instanceof RegExp ? new RegExp(pattern, "g") : new RegExp(pattern, "g"), + fn: fn + }); + } + + /** + * Create a Lexer for the given input. + */ + lex(source: string): Lexer { + return new Lexer(source, this.rules) + } +} \ No newline at end of file diff --git a/webview-src/client/main.ts b/webview-src/client/main.ts new file mode 100644 index 000000000..605a059e3 --- /dev/null +++ b/webview-src/client/main.ts @@ -0,0 +1,10 @@ +import { ReplConsole } from "./repl-console"; + +let console = new ReplConsole(document.querySelector(".repl"), (line) => { + console.requestPrompt("user=> "); +}); + +console.requestPrompt("user=> ") +document.addEventListener("DOMContentLoaded", () => { + console.input.focus(); +}) \ No newline at end of file diff --git a/webview-src/client/model.ts b/webview-src/client/model.ts new file mode 100644 index 000000000..1d7d0e958 --- /dev/null +++ b/webview-src/client/model.ts @@ -0,0 +1,370 @@ +import { Scanner, Token, ScannerState } from "./clojure-lexer"; +import { UndoManager, UndoStep } from "./undo"; +import { ReplReadline } from "./readline"; +import { LispTokenCursor } from "./token-cursor"; + +const scanner = new Scanner(); + +/** A cheesy deep-equal function for matching scanner states. Good enough to compare plain old js objects. */ +function equal(x: any, y: any): boolean { + if(x==y) return true; + if(x instanceof Array && y instanceof Array) { + if(x.length == y.length) { + for(let i = 0; i = new Set(); + + /** Lines which must be inserted. */ + insertedLines: Set<[number, number]> = new Set(); + + /** Lines which must be deleted. */ + deletedLines: Set<[number, number]> = new Set(); + + /** Handles undo/redo support */ + undoManager = new UndoManager(); + + /** When set, insertString and deleteRange will be added to the undo history. */ + recordingUndo: boolean = false; + + /** Lines which must be re-lexed. */ + dirtyLines: number[] = []; + + private updateLines(start: number, deleted: number, inserted: number) { + let delta = inserted-deleted; + + this.dirtyLines = this.dirtyLines.filter(x => x < start || x >= start+deleted) + .map(x => x >= start ? x + delta : x); + + + this.changedLines = new Set(Array.from(this.changedLines).map(x => { + if(x > start && x < start + deleted) + return null; + if(x >= start) + return x+delta; + return x; + }).filter(x => x !== null)) + + this.insertedLines = new Set(Array.from(this.insertedLines).map((x):[number, number] => { + let [a, b] = x; + if(a > start && a < start + deleted) + return null; + if(a >= start) + return [a+delta, b]; + return [a, b] + }).filter(x => x !== null)) + + this.deletedLines = new Set(Array.from(this.deletedLines).map((x):[number, number] => { + let [a, b] = x; + if(a > start && a < start + deleted) + return null; + if(a >= start) + return [a+delta, b]; + return [a, b] + }).filter(x => x !== null)) + } + + private deleteLines(start: number, count: number) { + if(count == 0) + return; + this.updateLines(start, count, 0); + this.deletedLines.add([start, count]) + } + + private insertLines(start: number, count: number) { + this.updateLines(start, 0, count); + this.insertedLines.add([start, count]) + } + + /** + * Mark a line as needing to be re-lexed. + * + * @param idx the index of the line which needs re-lexing (0-based) + */ + private markDirty(idx: number) { + if(idx >= 0 && idx < this.lines.length && this.dirtyLines.indexOf(idx) == -1) + this.dirtyLines.push(idx); + } + + /** + * Re-lexes all lines marked dirty, cascading onto the lines below if the end state for this line has + * changed. + */ + flushChanges() { + if(!this.dirtyLines.length) + return; + let seen = new Set(); + this.dirtyLines.sort(); + while(this.dirtyLines.length) { + let nextIdx = this.dirtyLines.shift(); + if(seen.has(nextIdx)) + continue; // already processed. + let prevState = this.getStateForLine(nextIdx); + do { + seen.add(nextIdx); + this.changedLines.add(nextIdx); + this.lines[nextIdx].processLine(prevState); + prevState = this.lines[nextIdx].endState; + + } while(this.lines[++nextIdx] && !(equal(this.lines[nextIdx].startState, prevState))) + } + } + + /** + * Returns the character offset in the model to the start of a given line. + * + * @param line the line who's offset will be returned. + */ + getOffsetForLine(line: number) { + let max = 0; + for(let i=0; i this.maxOffset)) + return ""; + let st = this.getRowCol(Math.min(start, end)); + let en = this.getRowCol(Math.max(start, end)); + + let lines = []; + if(st[0] == en[0]) + lines[0] = this.lines[st[0]].text.substring(st[1], en[1]) + else + lines[0] = this.lines[st[0]].text.substring(st[1]) + for(let i=st[0]+1; i this.lines[i].text.length) + offset -= this.lines[i].text.length+1; + else + return [i, offset]; + } + return [this.lines.length-1, this.lines[this.lines.length-1].text.length] + } + + /** + * Returns the initial lexer state for a given line. + * Line 0 is always { inString: false }, all lines below are equivalent to their previous line's startState. + * + * @param line the line to retrieve the lexer state. + */ + private getStateForLine(line: number): ScannerState { + return line == 0 ? { inString: false, } : { ... this.lines[line-1].endState }; + } + + /** + * Changes the model. Deletes any text between `start` and `end`, and the inserts `text`. + * + * If provided, `oldSelection` and `newSelection` are used to manage the cursor positioning for undo support. + * + * @param start the start offset in the range to delete + * @param end the end offset in the range to delete + * @param text the new text to insert + * @param oldSelection the old selection + * @param newSelection the new selection + */ + changeRange(start: number, end: number, text: string, oldSelection?: [number, number], newSelection?: [number, number]) { + let deletedText = this.recordingUndo ? this.getText(start, end) : ""; + let [startLine, startCol] = this.getRowCol(start); + let [endLine, endCol] = this.getRowCol(end); + // extract the lines we will replace + let replaceLines = text.split(/\r\n|\n/); + + // the left side of the line unaffected by the edit. + let left = this.lines[startLine].text.substr(0, startCol); + + // the right side of the line unaffected by the edit. + let right = this.lines[endLine].text.substr(endCol); + + let items: TextLine[] = []; + + // initialize the lexer state - the first line is definitely not in a string, otherwise copy the + // end state of the previous line before the edit + let state = this.getStateForLine(startLine) + + if(startLine != endLine) + this.deleteLines(startLine+1, endLine-startLine - (replaceLines.length-1)); + + if(replaceLines.length == 1) { + // trivial single line edit + items.push(new TextLine(left + replaceLines[0] + right, state)); + this.changedLines.add(startLine); + } else { + // multi line edit. + items.push(new TextLine(left + replaceLines[0], state)); + for(let i=1; i col : tk.offset > col) + return new LispTokenCursor(this, row, previous ? Math.max(0, lastIndex-1) : lastIndex); + lastIndex = i; + } + return new LispTokenCursor(this, row, line.tokens.length-1); + } + } +} + +/** + * An Editor UndoStep. + * + * All Editor Undo steps contain the position of the cursor before and after the edit. + */ +class EditorUndoStep extends UndoStep { + constructor(public name: string, public start: number, public insertedText: string, public deletedText: string, public oldSelection?: [number, number], public newSelection?: [number, number]) { + super(); + } + + undo(c: ReplReadline) { + c.model.changeRange(this.start, this.start+this.insertedText.length, this.deletedText); + if(this.oldSelection) + [c.selectionStart, c.selectionEnd] = this.oldSelection; + } + + redo(c: ReplReadline) { + c.model.changeRange(this.start, this.start+this.deletedText.length, this.insertedText); + if(this.newSelection) + [c.selectionStart, c.selectionEnd] = this.newSelection; + } + + coalesce(step: EditorUndoStep) { + if(this.deletedText === "" && step.deletedText === "" && this.insertedText !== "" && step.insertedText !== "") { + if(this.start + this.insertedText.length == step.start) { + this.insertedText += step.insertedText; + this.newSelection = step.newSelection; + return true; + } + } else if(this.deletedText !== "" && step.deletedText !== "" && this.insertedText === "" && step.insertedText === "") { + // repeated delete key + if(this.start == step.start) { + this.deletedText += step.deletedText; + this.newSelection = step.newSelection; + return true; + } + + // repeated backspace key + if(this.start - step.deletedText.length == step.start) { + this.start = step.start; + this.deletedText = step.deletedText + this.deletedText; + this.newSelection = step.newSelection; + return true; + } + } + return false; + } +} diff --git a/webview-src/client/paredit.ts b/webview-src/client/paredit.ts new file mode 100644 index 000000000..6269f0445 --- /dev/null +++ b/webview-src/client/paredit.ts @@ -0,0 +1,358 @@ +import { ReplReadline } from "./readline"; +import { validPair } from "./clojure-lexer"; + +export function wrapSexpr(doc: ReplReadline, open: string, close: string, start: number = doc.selectionStart, end: number = doc.selectionEnd) { + let st = Math.min(start, end); + let en = Math.max(start, end); + let cursor = doc.getTokenCursor(en); + if(cursor.withinString()) + throw new Error("Invalid context for paredit.wrapSexp"); + if(st == end) { + cursor.forwardSexp() + en = cursor.offsetStart; + // NOTE: emacs leaves the selection as is, but it has no relation to what was selected after the transform. + // I have opted to clear it here. + doc.selectionStart = doc.selectionEnd = en; + } + doc.model.insertString(en, close); + doc.model.insertString(st, open); +} + +export function splitSexp(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + if(cursor.withinString()) { + if(doc.model.getText(start-1, start+1, true) == '\\"') { + doc.model.changeRange(start+1, start+1, "\" \"") + doc.selectionStart = doc.selectionEnd = start+2; + } else { + doc.model.changeRange(start, start, "\" \"") + doc.selectionStart = doc.selectionEnd = start+1; + } + return; + } + cursor.backwardWhitespace(); + start = cursor.offsetStart; + let ws = cursor.clone(); + ws.forwardWhitespace() + if(cursor.backwardList()) { + let open = cursor.getPrevToken().raw; + + if(cursor.forwardList()) { + let close = cursor.getToken().raw; + doc.model.changeRange(start, ws.offsetStart, close+" "+open); + doc.selectionStart = doc.selectionEnd = start + 1; + } + } +} + +export function joinSexp(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + cursor.backwardWhitespace(); + let open = cursor.getPrevToken(); + let beginning = cursor.offsetStart; + if(cursor.withinString()) + throw new Error("Invalid context for paredit.joinSexp"); + if(open.type == "str-end" || open.type == "str") { + cursor.forwardWhitespace(); + let close = cursor.getToken(); + let end = cursor.offsetStart; + if((close.type == "str" || close.type == "str-start")) { + doc.model.changeRange(beginning-1, end+1, ""); + doc.selectionStart = doc.selectionEnd = beginning-1; + } + + } else if(open.type == "close") { + cursor.forwardWhitespace(); + let close = cursor.getToken(); + let end = cursor.offsetStart; + if(close.type == "open" && validPair(open.raw, close.raw)) { + doc.model.changeRange(beginning-1, end+1, " "); + doc.selectionStart = doc.selectionEnd = beginning; + } + } +} + +export function spliceSexp(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + // NOTE: this should unwrap the string, not throw. + if(cursor.withinString()) + throw new Error("Invalid context for paredit.spliceSexp"); + + cursor.backwardList() + let open = cursor.getPrevToken(); + let beginning = cursor.offsetStart; + if(open.type == "open") { + cursor.forwardList(); + let close = cursor.getToken(); + let end = cursor.offsetStart; + if(close.type == "close" && validPair(open.raw, close.raw)) { + doc.model.changeRange(end, end+1, ""); + doc.model.changeRange(beginning-1, beginning, ""); + doc.selectionStart = doc.selectionEnd = start-1; + } + } +} + +export function killBackwardList(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + // NOTE: this should unwrap the string, not throw. + if(cursor.withinString()) + throw new Error("Invalid context for paredit.killBackwardList"); + cursor.backwardList(); + doc.model.changeRange(cursor.offsetStart, start, ""); + return doc.selectionStart = doc.selectionEnd = cursor.offsetStart; +} + +export function killForwardList(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + let inComment = (cursor.getToken().type == "comment" && start > cursor.offsetStart) || cursor.getPrevToken().type == "comment"; + // NOTE: this should unwrap the string, not throw. + if(cursor.withinString()) + throw new Error("Invalid context for paredit.killForwardList"); + cursor.forwardList(); + doc.model.changeRange(start, cursor.offsetStart, inComment ? "\n" : ""); + return doc.selectionStart = doc.selectionEnd = start; +} + +export function spliceSexpKillingBackward(doc: ReplReadline, start: number = doc.selectionEnd) { + spliceSexp(doc, killBackwardList(doc, start)); +} + +export function spliceSexpKillingForward(doc: ReplReadline, start: number = doc.selectionEnd) { + spliceSexp(doc, killForwardList(doc, start)); +} + +export function forwardSlurpSexp(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + cursor.forwardList(); + if(cursor.getToken().type == "close") { + let offset = cursor.offsetStart; + let close = cursor.getToken().raw; + cursor.next(); + cursor.forwardSexp(true); + cursor.backwardWhitespace(false); + doc.model.changeRange(cursor.offsetStart, cursor.offsetStart, close); + doc.model.changeRange(offset, offset+1, ""); + } +} + +export function backwardSlurpSexp(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + cursor.backwardList(); + let tk = cursor.getPrevToken(); + if(tk.type == "open") { + let offset = cursor.clone().previous().offsetStart; + let close = cursor.getPrevToken().raw; + cursor.previous(); + cursor.backwardSexp(true); + cursor.forwardWhitespace(false); + doc.model.changeRange(offset, offset+tk.raw.length, ""); + doc.model.changeRange(cursor.offsetStart, cursor.offsetStart, close); + } +} + +export function forwardBarfSexp(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + cursor.forwardList(); + if(cursor.getToken().type == "close") { + let offset = cursor.offsetStart; + let close = cursor.getToken().raw; + cursor.backwardSexp(true); + cursor.backwardWhitespace(); + doc.model.changeRange(offset, offset+1, ""); + doc.model.changeRange(cursor.offsetStart, cursor.offsetStart, close); + } +} + +export function backwardBarfSexp(doc: ReplReadline, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(start); + cursor.backwardList(); + let tk = cursor.getPrevToken(); + if(tk.type == "open") { + cursor.previous(); + let offset = cursor.offsetStart; + let close = cursor.getToken().raw; + cursor.next(); + cursor.forwardSexp(true); + cursor.forwardWhitespace(false); + doc.model.changeRange(cursor.offsetStart, cursor.offsetStart, close); + doc.model.changeRange(offset, offset+tk.raw.length, ""); + } +} + +export function open(doc: ReplReadline, pair: string, start: number = doc.selectionEnd) { + doc.insertString(pair); + doc.selectionStart = doc.selectionEnd = start+1; +} + +export function close(doc: ReplReadline, close: string, start: number = doc.selectionEnd) { + let cursor = doc.getTokenCursor(); + cursor.forwardWhitespace(false); + if(cursor.getToken().raw == close) { + doc.model.changeRange(start, cursor.offsetStart, ""); + doc.selectionStart = doc.selectionEnd = start+1; + } else { + // one of two things are possible: + if(cursor.forwardList()) { + // we are in a matched list, just jump to the end of it. + doc.selectionStart = doc.selectionEnd = cursor.offsetEnd; + } else { + while(cursor.forwardSexp()) {} + doc.model.changeRange(cursor.offsetEnd, cursor.offsetEnd, close) + doc.selectionStart = doc.selectionEnd = cursor.offsetEnd+1; + } + } +} + +const parenPair = new Set(["()", "[]", "{}", '""', '\\"']) +const openParen = new Set(["(", "[", "{", '"']) +const closeParen = new Set([")", "]", "}", '"']) + +export function backspace(doc: ReplReadline, start: number = doc.selectionStart, end: number = doc.selectionEnd) { + if(start != end) { + doc.backspace(); + } else { + if(doc.model.getText(start-3, start, true) == '\\""') { + doc.selectionStart = doc.selectionEnd = start-1; + } else if(doc.model.getText(start-2, start-1, true) == '\\') { + doc.model.deleteRange(start-2, 2); + doc.selectionStart = doc.selectionEnd = start-2; + } else if(parenPair.has(doc.model.getText(start-1, start+1, true))) { + doc.model.deleteRange(start-1, 2); + doc.selectionStart = doc.selectionEnd = start-1; + } else if(closeParen.has(doc.model.getText(start-1, start, true)) || openParen.has(doc.model.getText(start-1, start, true))) { + doc.selectionStart = doc.selectionEnd = start-1; + } else if(openParen.has(doc.model.getText(start-1, start+1, true)) || closeParen.has(doc.model.getText(start-1, start, true))) { + doc.model.deleteRange(start-1, 2); + doc.selectionStart = doc.selectionEnd = start-1; + } else + doc.backspace(); + } +} + +export function deleteForward(doc: ReplReadline, start: number = doc.selectionStart, end: number = doc.selectionEnd) { + if(start != end) { + doc.delete(); + } else { + if(parenPair.has(doc.model.getText(start, start+2, true))) { + doc.model.deleteRange(start, 2); + } else if(parenPair.has(doc.model.getText(start-1, start+1, true))) { + doc.model.deleteRange(start-1, 2); + doc.selectionStart = doc.selectionEnd = start-1; + } else if(openParen.has(doc.model.getText(start, start+1, true)) || closeParen.has(doc.model.getText(start, start+1, true))) { + doc.selectionStart = doc.selectionEnd = start+1; + } else + doc.delete(); + } +} + +export function stringQuote(doc: ReplReadline, start: number = doc.selectionStart, end: number = doc.selectionEnd) { + if(start != end) { + doc.insertString('"'); + } else { + let cursor = doc.getTokenCursor(start); + if(cursor.withinString()) { + // inside a string, let's be clever + if(cursor.offsetEnd-1 == start && cursor.getToken().type == "str" || cursor.getToken().type == "str-end") { + doc.selectionStart = doc.selectionEnd = start + 1; + } else { + doc.model.changeRange(start, start, '"'); + doc.selectionStart = doc.selectionEnd = start + 1; + } + } else { + doc.model.changeRange(start, start, '""'); + doc.selectionStart = doc.selectionEnd = start + 1; + } + } +} + +export function growSelection(doc: ReplReadline, start: number = doc.selectionStart, end: number = doc.selectionEnd) { + let startC = doc.getTokenCursor(start); + let endC = doc.getTokenCursor(end); + if(startC.equals(endC) && !startC.withinWhitespace()) { + if(startC.getToken().type == "close") { + if(startC.getPrevToken().type == "close") { + startC.backwardList(); + doc.growSelectionStack.push([doc.selectionStart = startC.offsetStart, doc.selectionEnd = endC.offsetStart]); + } else { + endC = startC.previous(); + doc.growSelectionStack.push([doc.selectionStart = startC.offsetStart, doc.selectionEnd = endC.offsetEnd]); + } + } else if(startC.getToken().type == "open") { + endC.forwardList(); + doc.growSelectionStack.push([doc.selectionStart = startC.offsetStart, doc.selectionEnd = endC.offsetStart]); + } else { + doc.growSelectionStack.push([doc.selectionStart = startC.offsetStart, doc.selectionEnd = startC.offsetEnd]); + } + } else { + if(startC.getPrevToken().type == "open" && endC.getToken().type == "close") { + startC.backwardList(); + startC.backwardUpList(); + endC.forwardList(); + doc.growSelectionStack.push([doc.selectionStart = startC.offsetStart, doc.selectionEnd = endC.offsetEnd]); + } else { + startC.backwardList(); + endC.forwardList(); + endC.previous(); + doc.growSelectionStack.push([doc.selectionStart = startC.offsetStart, doc.selectionEnd = endC.offsetEnd]); + } + } +} + +export function shrinkSelection(doc: ReplReadline) { + if(doc.growSelectionStack.length) { + let [start, end] = doc.growSelectionStack.pop(); + if(start == doc.selectionStart && end == doc.selectionEnd && doc.growSelectionStack.length) { + [doc.selectionStart, doc.selectionEnd] = doc.growSelectionStack[doc.growSelectionStack.length-1]; + } else { + doc.growSelectionStack = []; + } + } +} + +export function raiseSexp(doc: ReplReadline, start = doc.selectionStart, end = doc.selectionEnd) { + if(start == end) { + let cursor = doc.getTokenCursor(end); + cursor.forwardWhitespace(); + let endCursor = cursor.clone(); + if(endCursor.forwardSexp()) { + let raised = doc.model.getText(cursor.offsetStart, endCursor.offsetStart); + cursor.backwardList(); + endCursor.forwardList(); + if(cursor.getPrevToken().type == "open") { + cursor.previous(); + if(endCursor.getToken().type == "close") { + doc.model.changeRange(cursor.offsetStart, endCursor.offsetEnd, raised); + doc.selectionStart = doc.selectionEnd = cursor.offsetStart; + } + } + } + } +} + +export function convolute(doc: ReplReadline, start = doc.selectionStart, end = doc.selectionEnd) { + if(start == end) { + let cursorStart = doc.getTokenCursor(end); + let cursorEnd = cursorStart.clone(); + + if(cursorStart.backwardList()) { + if(cursorEnd.forwardList()) { + let head = doc.model.getText(cursorStart.offsetStart, end); + if(cursorStart.getPrevToken().type == "open") { + cursorStart.previous(); + let headStart = cursorStart.clone(); + + if(headStart.backwardList() && headStart.backwardUpList()) { + let headEnd = cursorStart.clone(); + if(headEnd.forwardList() && cursorEnd.getToken().type == "close") { + doc.model.changeRange(headEnd.offsetEnd, headEnd.offsetEnd, ")"); + doc.model.changeRange(cursorEnd.offsetStart, cursorEnd.offsetEnd, ""); + doc.model.changeRange(cursorStart.offsetStart, end, ""); + doc.model.changeRange(headStart.offsetStart, headStart.offsetStart, "("+head); + } + } + } + } + } + } +} diff --git a/webview-src/client/readline.ts b/webview-src/client/readline.ts new file mode 100644 index 000000000..92fc3d6a8 --- /dev/null +++ b/webview-src/client/readline.ts @@ -0,0 +1,717 @@ +import { LineInputModel } from "./model"; +import { Token, validPair } from "./clojure-lexer"; +import { TokenCursor, LispTokenCursor } from "./token-cursor"; + +/** A cheesy utility canvas, used to measure the length of text. */ +const canvas = document.createElement("canvas"); +let ctx = canvas.getContext("2d") as CanvasRenderingContext2D; + +/** Returns the length of the string. */ +function measureText(str: string) { + return ctx.measureText(str).width; +} + +export type CompletionEvent = ClearCompletion | ShowCompletion; + +interface ClearCompletion { + type: "clear"; +} + +interface ShowCompletion { + type: "show" + position: number; + toplevel: string; +} + +export type CompletionListener = (c: CompletionEvent) => void; + +/** + * A syntax-highlighting text editor. + */ +export class ReplReadline { + /** Event listeners for completion */ + private _completionListeners: CompletionListener[] = []; + + addCompletionListener(c: CompletionListener) { + if(this._completionListeners.indexOf(c) == -1) + this._completionListeners.push(c); + } + + removeCompletionListener(c: CompletionListener) { + let idx = this._completionListeners.indexOf(c); + if(idx != -1) + this._completionListeners.splice(idx, 1); + } + + /** The offset of the start of the selection into the document. */ + private _selectionStart: number = 0; + + /** Returns the offset of the start of the selection. */ + get selectionStart() { + return this._selectionStart + }; + + /** Sets the start of the selection. */ + set selectionStart(val: number) { + this._selectionStart = Math.min(this.model.maxOffset, Math.max(val, 0)); + } + + /** The offset of the end of the selection into the document. */ + private _selectionEnd: number = 0; + + /** Returns the offset of the end of the selection. */ + get selectionEnd() { + return this._selectionEnd + }; + + /** Sets the end of the selection. */ + set selectionEnd(val: number) { + this._selectionEnd = Math.min(this.model.maxOffset, Math.max(val, 0)); + } + + /** The underlying tokenized source. */ + model = new LineInputModel(); + + /** The HTMLDivElements in the rendered view for each line. */ + inputLines: HTMLDivElement[] = []; + + /** The element representing the caret. */ + caret: HTMLDivElement; + + /** The target column of the caret, for up/down movement. */ + caretX: number = 0; + + /** The start of the selection when we last updated the component's DOM. */ + private lastSelectionStart: number = 0; + + /** The end of the selection when we last updated the component's DOM. */ + private lastSelectionEnd: number = 0; + + /** + * Returns a TokenCursor into the document. + * + * @param row the line to position the cursor at. + * @param col the column to position the cursor at. + * @param previous if true, position the cursor at the previous token. + */ + public getTokenCursor(offset: number = this.selectionEnd, previous: boolean = false) { + let [row, col] = this.model.getRowCol(offset); + let line = this.model.lines[row] + let lastIndex = 0; + if(line) { + for(let i=0; i col : tk.offset > col) + return new LispTokenCursor(this.model, row, previous ? Math.max(0, lastIndex-1) : lastIndex); + lastIndex = i; + } + return new LispTokenCursor(this.model, row, line.tokens.length-1); + } + } + + /** + * Executes a block of code, during which any edits that are performed on the document will be created with Undo support. + * This should happen almost all of the time- in fact the only time it shouldn't is when replaying undo/redo operations. + * + * FIXME: Perhaps this should be "withoutUndo"? + * + * @param body the code to execute. + */ + withUndo(body: () => void) { + let oldUndo = this.model.recordingUndo; + try { + this.model.recordingUndo = true; + this.model.undoManager.withUndo(body) + } finally { + this.model.recordingUndo = oldUndo; + } + } + + /** + * Inserts a string at the current cursor location. + * + * FIXME: this should just be `changeRange`. + * @param text the text to insert + */ + insertString(text: string) { + this.withUndo(() => { + if(this.selectionStart != this.selectionEnd) { + this.deleteSelection(); + } + let [cs, ce] = [this.selectionStart, this.selectionEnd] + this.selectionEnd += this.model.insertString(this.selectionEnd, text, [cs, ce], [cs+text.length, cs+text.length]); + this.selectionStart = this.selectionEnd; + + this.repaint(); + + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + }); + } + + clearCompletion() { + let evt: CompletionEvent = { type: "clear" } + this._completionListeners.forEach(x => x(evt)); + } + + maybeShowCompletion() { + if(this.getTokenCursor().offsetStart == this.selectionEnd && !this.getTokenCursor().previous().withinWhitespace()) { + let evt: CompletionEvent = { type: "show", position: this.selectionEnd, toplevel: this.model.getText(0, this.model.maxOffset) } + this._completionListeners.forEach(x => x(evt)); + } else + this.clearCompletion(); + } + + /** + * Moves the caret left one character, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretLeft(clear: boolean = true) { + this.clearCompletion(); + if(clear && this.selectionStart != this.selectionEnd) { + if(this.selectionStart < this.selectionEnd) + this.selectionEnd = this.selectionStart; + else + this.selectionStart = this.selectionEnd; + } else { + this.selectionEnd--; + if(clear) + this.selectionStart = this.selectionEnd; + } + this.repaint(); + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + } + + /** + * Moves the caret right one character, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretRight(clear: boolean = true) { + this.clearCompletion(); + if(clear && this.selectionStart != this.selectionEnd) { + if(this.selectionStart > this.selectionEnd) + this.selectionEnd = this.selectionStart; + else + this.selectionStart = this.selectionEnd; + } else { + this.selectionEnd++ + if(clear) + this.selectionStart = this.selectionEnd; + } + this.repaint(); + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + } + + /** + * Moves the caret to the beginning of the document, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretHomeAll(clear: boolean = true) { + this.clearCompletion(); + this.selectionEnd = 0; + if(clear) + this.selectionStart = this.selectionEnd; + this.repaint(); + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + } + + /** + * Moves the caret to the end of the document, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretEndAll(clear: boolean = true) { + this.clearCompletion(); + this.selectionEnd = this.model.maxOffset; + if(clear) + this.selectionStart = this.selectionEnd; + this.repaint(); + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + } + + /** + * Moves the caret to the beginning of the line, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretHome(clear: boolean = true) { + this.clearCompletion(); + let [row, col] = this.model.getRowCol(this.selectionEnd); + this.selectionEnd = this.selectionEnd-col; + if(clear) + this.selectionStart = this.selectionEnd; + this.repaint(); + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + } + + /** + * Moves the caret to the end of the line, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretEnd(clear: boolean = true) { + this.clearCompletion(); + let [row, col] = this.model.getRowCol(this.selectionEnd); + this.selectionEnd = this.selectionEnd-col + this.model.lines[row].text.length; + if(clear) + this.selectionStart = this.selectionEnd; + this.repaint(); + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + } + + /** + * Moves the caret to the previous line, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretUp(clear: boolean = true) { + this.clearCompletion(); + let [row, col] = this.model.getRowCol(this.selectionEnd); + if(row > 0) { + let len = this.model.lines[row-1].text.length; + this.selectionEnd = this.model.getOffsetForLine(row-1)+Math.min(this.caretX, len); + } else { + this.selectionEnd = 0; + } + if(clear) + this.selectionStart = this.selectionEnd; + this.repaint(); + } + + /** + * Moves the caret to the next line, using text editor semantics. + * + * @param clear if true, clears the current selection, if any, otherwise moves `cursorEnd` only. + */ + caretDown(clear: boolean = true) { + this.clearCompletion(); + let [row, col] = this.model.getRowCol(this.selectionEnd); + if(row < this.model.lines.length-1) { + let len = this.model.lines[row+1].text.length; + this.selectionEnd = this.model.getOffsetForLine(row+1)+Math.min(this.caretX, len); + } else { + this.selectionEnd = this.model.maxOffset; + } + if(clear) + this.selectionStart = this.selectionEnd; + this.repaint(); + } + + /** + * Deletes the current selection. + * + * FIXME: this should just be `changeRange` + */ + private deleteSelection() { + this.withUndo(() => { + if(this.selectionStart != this.selectionEnd) { + this.model.deleteRange(Math.min(this.selectionStart, this.selectionEnd), Math.max(this.selectionStart, this.selectionEnd)-Math.min(this.selectionStart, this.selectionEnd)); + this.selectionStart = this.selectionEnd = Math.min(this.selectionStart, this.selectionEnd); + } + }) + } + + /** + * If there is no selection- deletes the character to the left of the cursor and moves it back one character. + * + * If there is a selection, deletes the selection. + */ + backspace() { + this.withUndo(() => { + if(this.selectionStart != this.selectionEnd) { + this.deleteSelection(); + } else { + if(this.selectionEnd > 0) { + this.model.deleteRange(this.selectionEnd-1, 1, [this.selectionStart, this.selectionEnd], [this.selectionEnd-1, this.selectionEnd-1]); + this.selectionEnd--; + } + this.selectionStart = this.selectionEnd; + } + this.repaint() + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + }); + } + + /** + * If there is no selection- deletes the character to the right of the cursor. + * + * If there is a selection, deletes the selection. + */ + delete() { + this.withUndo(() => { + if(this.selectionStart != this.selectionEnd) { + this.deleteSelection(); + } else { + this.model.deleteRange(this.selectionEnd, 1); + this.selectionStart = this.selectionEnd; + } + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + this.repaint() + }); + } + + /** + * Construct a selection marker div. + * @param start the left hand side start position in pixels. + * @param width the width of the marker, in pixels. + */ + private makeSelection(start: number, width: number) { + let div = document.createElement("div") + div.className = "sel-marker"; + let left = start; + div.style.left = left + "px"; + div.style.width = width + "px"; + return div; + } + + /** + * If we are rendering a matched parenthesis, a cursor pointing at the close parenthesis. + */ + closeParen: TokenCursor; + + /** + * If we are rendering a matched parenthesis, a cursor pointing at the open parenthesis. + */ + openParen: TokenCursor; + + /** + * True if we are rendering a matched parenthesis. + */ + matchingParen = false; + + /** + * Clears the rendering for matching parenthesis. + */ + private clearParenMatches() { + let cp = this.getElementForToken(this.closeParen); + if(cp) { + cp.classList.remove("match"); + cp.classList.remove("match-fail"); + } + + let op = this.getElementForToken(this.openParen); + if(op) { + op.classList.remove("match"); + op.classList.remove("match-fail"); + } + this.closeParen = null; + this.openParen = null; + } + + /** + * Sets the rendering for matching parenthesis. + */ + updateParenMatches() { + let cursor = this.getTokenCursor(); + + if(cursor.getToken().type == "close") { + this.closeParen = cursor.clone() + while(cursor.backwardSexp()); + if(cursor.getPrevToken().type == "open") { + this.openParen = cursor.previous(); + } + if(this.closeParen && this.openParen) + this.matchingParen = validPair(this.openParen.getToken().raw, this.closeParen.getToken().raw); + else + this.matchingParen = false; + } else if(cursor.getToken().type == "open") { + this.openParen = cursor.clone(); + cursor.next(); + while(cursor.forwardSexp()); + if(cursor.getToken().type == "close") { + this.closeParen = cursor; + } + if(this.closeParen && this.openParen) + this.matchingParen = validPair(this.openParen.getToken().raw, this.closeParen.getToken().raw); + else + this.matchingParen = false; + } + + let cp = this.getElementForToken(this.closeParen); + if(cp) { + if(this.matchingParen) + cp.classList.add("match"); + else + cp.classList.add("fail-match") + } + + let op = this.getElementForToken(this.openParen); + if(op) { + if(this.matchingParen) + op.classList.add("match"); + else + op.classList.add("fail-match") + } + } + + /** + * Given a TokenCursor, returns the HTMLElement that is rendered for this token. + * @param cursor + */ + private getElementForToken(cursor: TokenCursor) { + if(cursor && this.inputLines[cursor.line]) + return this.inputLines[cursor.line].querySelector(".content").children.item(cursor.token) as HTMLElement + } + + private _repaintListeners = []; + addOnRepaintListener(fn: () => void) { + this._repaintListeners.push(fn); + } + + /** + * Update the DOM for the editor. After a change in the model or local editor information (e.g. cursor position), we apply the changes, + * attempting to minimize the work. + */ + repaint() { + this.clearParenMatches(); + this.model.flushChanges() + // remove any deleted lines + for(let [start, count] of this.model.deletedLines) { + for(let j=0; j ce[0]) { + // definitely outside the selection, nuke all the selectiond divs. + while(ln.firstChild) + ln.removeChild(ln.firstChild); + } else if(line == cs[0] && line == ce[0]) { + // this selection is exactly 1 line, and we're at it. + while(ln.firstChild) + ln.removeChild(ln.firstChild); + let left = measureText("M")*cs[1]; + ln.appendChild(this.makeSelection(left, measureText("M")*ce[1]-left)); + } else if(line == cs[0]) { + // this is the first line of the selection + while(ln.firstChild) + ln.removeChild(ln.firstChild); + let left = measureText("M")*cs[1]; + ln.appendChild(this.makeSelection(left, measureText("M")*this.model.lines[line].text.length - left)); + } else if(line == ce[0]) { + // this is the last line of the selection + while(ln.firstChild) + ln.removeChild(ln.firstChild); + ln.appendChild(this.makeSelection(0, measureText("M")*ce[1])); + } else if(line > cs[0] && line < ce[0]) { + // this line is within the selection, but is not the first or last. + if(line > lcs[0] && line < lce[0]) { + // this line was within the selection previously, it is already highlighted, + // nothing to do. + } else if(line >= cs[0] && line <= ce[0]) { + // this line is newly within the selection + while(ln.firstChild) + ln.removeChild(ln.firstChild); + ln.appendChild(this.makeSelection(0, Math.max(measureText("M"), measureText("M")*this.model.lines[line].text.length))); + } else { + // this line is no longer within the selection + while(ln.firstChild) + ln.removeChild(ln.firstChild); + } + } + } + + this.lastSelectionStart = this.selectionStart; + this.lastSelectionEnd = this.selectionEnd; + + this.updateParenMatches() + this._repaintListeners.forEach(x => x()); + } + + getCaretOnScreen() { + let rect = this.caret.getBoundingClientRect(); + return { x: rect.left, y: rect.top+window.scrollY, width: rect.width, height: rect.height}; + } + + /** Given a (pageX, pageY) pixel coordinate, returns the character offset into this document. */ + pageToOffset(pageX: number, pageY: number) { + let rect = this.mainElem.getBoundingClientRect(); + let y = pageY-(rect.top + window.scrollY); + let i: number; + + // NOTE: assuming every line is a fixed size, this could be O(1). + // on the other hand, this seems quite fast for now. + for(i=0; i { + this.selectionEnd = this.pageToOffset(e.pageX, e.pageY) + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + this.repaint(); + } + + private mouseUp = (e: MouseEvent) => { + window.removeEventListener("mousemove", this.mouseDrag) + window.removeEventListener("mouseup", this.mouseUp) + } + + private mouseDown = (e: MouseEvent) => { + e.preventDefault(); + + this.selectionStart = this.selectionEnd = this.pageToOffset(e.pageX, e.pageY) + this.caretX = this.model.getRowCol(this.selectionEnd)[1]; + this.repaint(); + + window.addEventListener("mousemove", this.mouseDrag) + window.addEventListener("mouseup", this.mouseUp) + } + + focus = (e: Event) => { e.preventDefault(); this.input.focus() }; + + public mainElem: HTMLElement; + public promptElem: HTMLElement; + elem: HTMLElement; + wrap: HTMLElement; + + constructor(public parent: HTMLElement, prompt: string, public input: HTMLInputElement) { + this.wrap = this.elem = document.createElement("div"); + this.wrap.className = "prompt-wrap" + this.wrap.addEventListener("mousedown", this.focus); + this.wrap.addEventListener("touchstart", this.focus); + this.promptElem = document.createElement("div"); + this.promptElem.className = "prompt" + this.promptElem.textContent = prompt; + + this.mainElem = document.createElement("div"); + this.wrap.appendChild(this.promptElem); + this.wrap.appendChild(this.mainElem); + + parent.appendChild(this.wrap); + this.mainElem.addEventListener("mousedown", this.mouseDown) + + this.caret = document.createElement("div"); + this.caret.className = "caret"; + let line = this.makeLine(); + this.inputLines.push(line) + this.mainElem.appendChild(line); + ctx.font = getComputedStyle(line).font+""; + this.caret.style.width = measureText("M")+"px"; + line.appendChild(this.caret); + } + + private makeLine() { + let line = document.createElement("div"); + line.className = "line"; + + let content = document.createElement("div"); + content.className = "content"; + line.append(content); + + let selection = document.createElement("div"); + selection.className = "selection"; + line.append(selection); + return line; + } + + public canReturn() { + return this.selectionEnd == this.selectionStart && this.selectionEnd == this.model.maxOffset; + } + + public freeze() { + this.mainElem.removeEventListener("mousedown", this.mouseDown) + window.removeEventListener("mouseup", this.mouseUp) + window.removeEventListener("mousemove", this.mouseDrag); + this.wrap.removeEventListener("mousedown", this.focus); + this.wrap.removeEventListener("touchstart", this.focus); + this.input.disabled = true; + + this.selectionStart = this.selectionEnd = this.model.maxOffset; + this.repaint(); + this.caret.parentElement.removeChild(this.caret); + } + + public doReturn() { + this.freeze(); + } + + growSelectionStack: [number, number][] = []; +} + +/** + * A set of tokens which should be highlighted as macros. + * this is, of course, a really stupid way of doing it. + */ +const macros = new Set(["if", "let", "do", "while", "cond", "case"]); + +/** + * Constructs an HTMLElement to represent a token with the correct syntax highlighting. + * @param tk the token to construct. + */ +function makeToken(tk: Token) { + let span = document.createElement("span"); + let className = tk.type; + if(tk.type == "id") { + if(tk.raw.startsWith("def")) + className = "decl"; + else if(macros.has(tk.raw)) + className = "macro"; + } + + span.textContent = tk.raw; + span.className = className; + return span; +} \ No newline at end of file diff --git a/webview-src/client/repl-console.ts b/webview-src/client/repl-console.ts new file mode 100644 index 000000000..fffb6e617 --- /dev/null +++ b/webview-src/client/repl-console.ts @@ -0,0 +1,598 @@ +import { ReplReadline, CompletionListener } from "./readline"; +import * as paredit from "./paredit"; +import { getIndent } from "./indent"; +import { HotKeyTable } from "./hotkeys"; + +const defaultHotkeys = new HotKeyTable({ + "Alt+R": "raise-sexp", + "Alt+Shift+/": "convolute-sexp", + "Alt+Backspace": "force-backspace", + "Ctrl+Shift+Space": "grow-selection", + "Ctrl+Alt+Shift+Space": "shrink-selection", + "Alt+Delete": "force-delete", + "Alt+LeftArrow": "backward-sexp", + "Alt+RightArrow": "forward-sexp", + "Ctrl+DownArrow": "down-list", + "Ctrl+Shift+UpArrow": "up-list", + "Ctrl+UpArrow": "backward-up-list", + "Cmd+A": "select-all", + "Cmd+Z": "undo", + "Cmd+Shift+Z": "redo", + "Alt+Shift+J": "join-sexp", + "Alt+Shift+Cmd+LeftArrow": "backward-slurp-sexp", + "Alt+Cmd+LeftArrow": "forward-barf-sexp", + "LeftArrow": "cursor-left", + "Shift+LeftArrow": "cursor-select-left", + "Alt+Shift+Cmd+RightArrow": "forward-slurp-sexp", + "Alt+Cmd+RightArrow": "backward-barf-sexp", + "RightArrow": "cursor-right", + "Shift+RightArrow": "cursor-select-right", + "Alt+Ctrl+Backspace": "splice-sexp-killing-backwards", + "UpArrow": "cursor-up", + "Shift+UpArrow": "cursor-select-up", + "Alt+Ctrl+Delete": "splice-sexp-killing-forwards", + "DownArrow": "cursor-down", + "Shift+DownArrow": "cursor-select-down", + "Backspace": "backspace", + "Home": "cursor-home", + "Shift+Home": "cursor-select-home", + "Ctrl+Home": "cursor-home-all", + "Shift+Ctrl+Home": "cursor-select-home-all", + "End": "cursor-end", + "Shift+End": "cursor-select-end", + "Ctrl+End": "cursor-end-all", + "Shift+Ctrl+End": "cursor-select-end-all", + "Delete": "delete", + "Alt+Shift+9": "wrap-round", + "Alt+[": "wrap-square", + "Alt+Shift+[": "wrap-curly", + "Alt+Shift+S": "split-sexp", + "Alt+S": "splice-sexp", + "Alt+UpArrow": "history-up", + "Alt+DownArrow": "history-down", + "Alt+Return": "submit", + "Ctrl+Return": "submit-pprint", + "Ctrl+L": "clear-window" +}) + + + +export class ReplConsole { + readline: ReplReadline; + input: HTMLInputElement; + hotkeys: HotKeyTable; + + historyIndex = -1; + history: string[] = []; + + /** Event listeners for history */ + private _historyListeners: ((line: string) => void)[] = []; + + private isElementInViewport(el) { + var rect = el.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ + rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ + ); + } + + private ensureCaretInView() { + const el = this.readline.caret; + if (!this.isElementInViewport(el)) { + el.scrollIntoView({ block: "nearest" }); + } + } + + addHistoryListener(c: (line: string) => void) { + if (this._historyListeners.indexOf(c) == -1) + this._historyListeners.push(c); + } + + removeHistoryListener(c: (line: string) => void) { + let idx = this._historyListeners.indexOf(c); + if (idx != -1) + this._historyListeners.splice(idx, 1); + } + + /** Event listeners for completion */ + private _completionListeners: CompletionListener[] = []; + + addCompletionListener(c: CompletionListener) { + if (this._completionListeners.indexOf(c) == -1) + this._completionListeners.push(c); + } + + removeCompletionListener(c: CompletionListener) { + let idx = this._completionListeners.indexOf(c); + if (idx != -1) + this._completionListeners.splice(idx, 1); + } + + constructor(public elem: HTMLElement, public onReadLine: (x: string, pprint: boolean) => void = () => { }) { + this.hotkeys = defaultHotkeys; + this.input = document.createElement("input"); + this.input.style.width = "0px"; + this.input.style.height = "0px"; + this.input.style.position = "fixed"; + this.input.style.opacity = "0"; + + this.input.addEventListener("focus", () => { + this.readline.mainElem.classList.add("is-focused") + }) + + this.input.addEventListener("blur", () => { + this.readline.clearCompletion(); + this.readline.mainElem.classList.remove("is-focused") + }) + + document.addEventListener("cut", e => { + if (document.activeElement == this.input) { + e.clipboardData.setData("text/plain", this.readline.model.getText(this.readline.selectionStart, this.readline.selectionEnd)); + this.readline.delete(); + e.preventDefault(); + this.ensureCaretInView(); + } + }) + + document.addEventListener("copy", e => { + if (document.activeElement == this.input) { + e.clipboardData.setData("text/plain", this.readline.model.getText(this.readline.selectionStart, this.readline.selectionEnd)); + e.preventDefault(); + } + }) + + document.addEventListener("paste", e => { + if (document.activeElement == this.input) { + this.readline.clearCompletion(); + this.readline.model.undoManager.insertUndoStop() + this.readline.insertString(e.clipboardData.getData("text/plain")); + e.preventDefault(); + this.ensureCaretInView(); + } + }) + + this.input.addEventListener("keydown", e => { + if (this.hotkeys.execute(this, e)) { + e.preventDefault(); + this.ensureCaretInView(); + return; + } + if (e.key.length == 1 && !e.metaKey && !e.ctrlKey) { + if (e.key == " ") + this.readline.model.undoManager.insertUndoStop(); + } else { + switch (e.keyCode) { + case 9: // Tab + e.preventDefault(); + break; + case 13: + if (this.readline.canReturn()) { + this.submitLine(); + this.readline.clearCompletion(); + window.scrollTo({ left: 0 }); + } else { + this.readline.model.undoManager.insertUndoStop(); + let indent = getIndent(this.readline.model, this.readline.selectionEnd); + let istr = "" + for (let i = 0; i < indent; i++) + istr += " " + this.readline.insertString("\n" + istr); + } + break; + } + } + }, { capture: true }) + + this.input.addEventListener("input", e => { + this.readline.mainElem.scrollIntoView({ block: "end" }) + + if (this.input.value == '"') { + this.readline.withUndo(() => { + paredit.stringQuote(this.readline) + this.readline.repaint() + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == "(") { + this.readline.withUndo(() => { + paredit.open(this.readline, "()"); + this.readline.repaint(); + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == "[") { + this.readline.withUndo(() => { + paredit.open(this.readline, "[]"); + this.readline.repaint(); + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == "{") { + this.readline.withUndo(() => { + paredit.open(this.readline, "{}"); + this.readline.repaint(); + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == "{") { + this.readline.withUndo(() => { + paredit.open(this.readline, "{}"); + this.readline.repaint(); + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == ")") { + this.readline.withUndo(() => { + paredit.close(this.readline, ")"); + this.readline.repaint(); + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == "]") { + this.readline.withUndo(() => { + paredit.close(this.readline, "]"); + this.readline.repaint(); + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == "}") { + this.readline.withUndo(() => { + paredit.close(this.readline, "}"); + this.readline.repaint(); + }) + this.readline.clearCompletion(); + e.preventDefault(); + } else if (this.input.value == "\n") { + if (this.readline.canReturn()) { + this.submitLine(); + this.readline.mainElem.scrollIntoView({ block: "end" }); + } else { + this.readline.model.undoManager.insertUndoStop(); + let indent = getIndent(this.readline.model, this.readline.selectionEnd); + let istr = "" + for (let i = 0; i < indent; i++) + istr += " " + this.readline.insertString("\n" + istr); + this.readline.clearCompletion(); + } + } else { + this.readline.insertString(this.input.value) + this.readline.maybeShowCompletion(); + } + this.input.value = "" + e.preventDefault(); + this.ensureCaretInView(); + }) + } + + printElement(element: HTMLElement) { + if (!this.readline || this.input.disabled) { + this.elem.appendChild(element); + } else { + this.elem.insertBefore(element, this.readline.elem); + } + this.elem.lastElementChild.scrollIntoView({ block: "end" }) + } + + print(text: string) { + let el = document.createElement("div"); + el.textContent = text; + el.className = "output"; + this.printElement(el); + } + + setText(text: string) { + this.readline.model.changeRange(0, this.readline.model.maxOffset, text) + this.readline.repaint(); + } + + setHistory(history: string[]) { + this.history = history; + this.historyIndex = -1; + } + + submitLine(trigger = true, pprint = false) { + let line = this.readline.model.getText(0, this.readline.model.maxOffset); + if (line.trim() == "") { + this.readline.freeze(); + this.requestPrompt(this.readline.promptElem.textContent); + return; + } + this.history.push(line); + this._historyListeners.forEach(x => x(line)); + this.historyIndex = -1; + this.readline.freeze(); + if (trigger) + this.onReadLine(line, pprint); + } + + requestPrompt(prompt: string) { + if (this.readline && !this.input.disabled) + return; + this.readline = new ReplReadline(this.elem, prompt, this.input); + this.readline.addCompletionListener(e => this._completionListeners.forEach(listener => listener(e))); + this.elem.appendChild(this.input); + this.input.disabled = false; + this.input.focus(); + this.readline.mainElem.scrollIntoView({ block: "end" }) + } + + onRepaint = () => { }; + + commands = { + "raise-sexp": () => { + this.readline.withUndo(() => { + paredit.raiseSexp(this.readline); + this.readline.repaint(); + }); + }, + "convolute-sexp": () => { + this.readline.withUndo(() => { + paredit.convolute(this.readline); + this.readline.repaint(); + }); + }, + "force-backspace": () => { + this.readline.withUndo(() => { + this.readline.backspace(); + this.readline.repaint(); + }); + }, + "force-delete": () => { + this.readline.withUndo(() => { + this.readline.delete(); + this.readline.repaint(); + }); + }, + "grow-selection": () => { + this.readline.withUndo(() => { + paredit.growSelection(this.readline) + this.readline.repaint(); + }) + }, + "shrink-selection": () => { + this.readline.withUndo(() => { + paredit.shrinkSelection(this.readline) + this.readline.repaint(); + }) + }, + "backward-sexp": () => { + let cursor = this.readline.getTokenCursor(); + cursor.backwardSexp(true); + this.readline.selectionStart = this.readline.selectionEnd = cursor.offsetStart; + this.readline.repaint(); + }, + "forward-sexp": () => { + let cursor = this.readline.getTokenCursor(); + cursor.forwardSexp(true); + this.readline.selectionStart = this.readline.selectionEnd = cursor.offsetStart; + this.readline.repaint(); + }, + "down-list": () => { + let cursor = this.readline.getTokenCursor(); + do { + cursor.forwardWhitespace() + } while (cursor.getToken().type != "open" && cursor.forwardSexp()) { } + cursor.downList(); + this.readline.selectionStart = this.readline.selectionEnd = cursor.offsetStart; + this.readline.repaint(); + }, + "up-list": () => { + let cursor = this.readline.getTokenCursor(); + cursor.forwardList(); + cursor.upList(); + this.readline.selectionStart = this.readline.selectionEnd = cursor.offsetStart; + this.readline.repaint(); + }, + "backward-up-list": () => { + let cursor = this.readline.getTokenCursor(); + cursor.backwardList(); + cursor.backwardUpList(); + this.readline.selectionStart = this.readline.selectionEnd = cursor.offsetStart; + this.readline.repaint(); + }, + "select-all": () => { + this.readline.selectionStart = 0; + this.readline.selectionEnd = this.readline.model.maxOffset; + this.readline.repaint(); + }, + "undo": () => { + this.readline.model.undoManager.undo(this.readline) + this.readline.repaint(); + }, + "redo": () => { + this.readline.model.undoManager.redo(this.readline) + this.readline.repaint(); + }, + "join-sexp": () => { + this.readline.withUndo(() => { + paredit.joinSexp(this.readline); + this.readline.repaint(); + }) + }, + "backward-slurp-sexp": () => { + this.readline.withUndo(() => { + paredit.backwardSlurpSexp(this.readline); + this.readline.repaint(); + }) + }, + "forward-barf-sexp": () => { + this.readline.withUndo(() => { + paredit.forwardBarfSexp(this.readline); + this.readline.repaint(); + }) + }, + "cursor-left": () => { + this.readline.caretLeft(true); + this.readline.repaint(); + }, + "cursor-select-left": () => { + this.readline.caretLeft(false); + this.readline.repaint(); + }, + "forward-slurp-sexp": () => { + this.readline.withUndo(() => { + paredit.forwardSlurpSexp(this.readline); + this.readline.repaint(); + }) + }, + "backward-barf-sexp": () => { + this.readline.withUndo(() => { + paredit.backwardBarfSexp(this.readline); + this.readline.repaint(); + }) + }, + "cursor-right": () => { + this.readline.caretRight(true) + this.readline.repaint(); + }, + "cursor-select-right": () => { + this.readline.caretRight(false) + this.readline.repaint(); + }, + "splice-sexp-killing-backwards": () => { + this.readline.withUndo(() => { + paredit.spliceSexpKillingBackward(this.readline) + this.readline.repaint(); + }); + }, + "cursor-up": () => { + this.readline.caretUp(true); + this.readline.repaint(); + }, + "cursor-select-up": () => { + this.readline.caretUp(false); + this.readline.repaint(); + }, + "splice-sexp-killing-forwards": () => { + this.readline.withUndo(() => { + paredit.spliceSexpKillingForward(this.readline) + this.readline.repaint(); + }); + }, + "cursor-down": () => { + this.readline.caretDown(true); + this.readline.repaint(); + }, + "cursor-select-down": () => { + this.readline.caretDown(false); + this.readline.repaint(); + }, + "backspace": () => { + this.readline.withUndo(() => { + paredit.backspace(this.readline); + this.readline.repaint() + }) + }, + "cursor-home": () => { + this.readline.caretHome(true); + this.readline.repaint(); + }, + "cursor-select-home": () => { + this.readline.caretHome(false); + this.readline.repaint(); + }, + "cursor-home-all": () => { + this.readline.caretHomeAll(true); + this.readline.repaint(); + }, + "cursor-select-home-all": () => { + this.readline.caretHomeAll(false); + this.readline.repaint(); + }, + "cursor-end": () => { + this.readline.caretEnd(true); + this.readline.repaint(); + }, + "cursor-select-end": () => { + this.readline.caretEnd(false); + this.readline.repaint(); + }, + "cursor-end-all": () => { + this.readline.caretEndAll(true); + this.readline.repaint(); + }, + "cursor-select-end-all": () => { + this.readline.caretEndAll(false); + this.readline.repaint(); + }, + "delete": () => { + this.readline.withUndo(() => { + paredit.deleteForward(this.readline); + this.readline.repaint() + }) + }, + "wrap-round": () => { + this.readline.withUndo(() => { + paredit.wrapSexpr(this.readline, "(", ")"); + this.readline.repaint(); + }) + }, + "wrap-square": () => { + this.readline.withUndo(() => { + paredit.wrapSexpr(this.readline, "[", "]"); + this.readline.repaint(); + }) + }, + "wrap-curly": () => { + this.readline.withUndo(() => { + paredit.wrapSexpr(this.readline, "{", "}"); + this.readline.repaint(); + }) + }, + "split-sexp": () => { + this.readline.withUndo(() => { + paredit.splitSexp(this.readline); + this.readline.repaint(); + }) + }, + "splice-sexp": () => { + this.readline.withUndo(() => { + paredit.spliceSexp(this.readline); + this.readline.repaint(); + }) + }, + "history-up": () => { + if (this.historyIndex == 0) + return; + if (this.historyIndex == -1) + this.historyIndex = this.history.length; + this.historyIndex--; + let line = this.history[this.historyIndex] || ""; + this.readline.withUndo(() => { + this.readline.model.changeRange(0, this.readline.model.maxOffset, line); + this.readline.selectionStart = this.readline.selectionEnd = line.length; + }) + this.readline.repaint(); + }, + "history-down": () => { + if (this.historyIndex == this.history.length || this.historyIndex == -1) + return; + this.historyIndex++; + let line = this.history[this.historyIndex] || ""; + this.readline.withUndo(() => { + this.readline.model.changeRange(0, this.readline.model.maxOffset, line); + this.readline.selectionStart = this.readline.selectionEnd = line.length; + }) + this.readline.repaint(); + }, + "submit": () => { + this.submitLine(true, false) + this.readline.clearCompletion(); + }, + "submit-pprint": () => { + this.submitLine(true, true) + this.readline.clearCompletion(); + }, + "clear-window": () => { + // TODO: How to add undo to this? + const prompt = this.readline.promptElem.textContent; + const replElement = window.document.getElementById('repl'); + replElement.textContent = ""; + this.readline = null; + this.requestPrompt(prompt); + } + } +} \ No newline at end of file diff --git a/webview-src/client/token-cursor.ts b/webview-src/client/token-cursor.ts new file mode 100644 index 000000000..ef12d4d5c --- /dev/null +++ b/webview-src/client/token-cursor.ts @@ -0,0 +1,370 @@ +import { LineInputModel } from "./model"; +import { Token } from "./clojure-lexer"; + +/** + * A mutable cursor into the token stream. + */ +export class TokenCursor { + constructor(public doc: LineInputModel, public line: number, public token: number) { + } + + /** Create a copy of this cursor. */ + clone() { + return new TokenCursor(this.doc, this.line, this.token); + } + + /** + * Sets this TokenCursor state to the same as another. + * @param cursor the cursor to copy state from. + */ + set(cursor: TokenCursor) { + this.doc = cursor.doc; + this.line = cursor.line; + this.token = cursor.token; + } + + /** Return the position */ + get rowCol() { + return [this.line, this.getToken().offset]; + } + + /** Return the offset at the start of the token */ + get offsetStart() { + return this.doc.getOffsetForLine(this.line) + this.getToken().offset; + } + + /** Return the offset at the end of the token */ + get offsetEnd() { + return Math.min(this.doc.maxOffset, this.doc.getOffsetForLine(this.line) + this.getToken().offset + this.getToken().raw.length); + } + + + /** True if we are at the start of the document */ + atStart() { + return this.token == 0 && this.line == 0; + } + + /** True if we are at the end of the document */ + atEnd() { + return this.line == this.doc.lines.length-1 && this.token == this.doc.lines[this.line].tokens.length-1; + } + + /** Move this cursor backwards one token */ + previous() { + if(this.token > 0) { + this.token--; + } else { + if(this.line == 0) return; + this.line--; + this.token = this.doc.lines[this.line].tokens.length-1; + } + return this; + } + + /** Move this cursor forwards one token */ + next() { + if(this.token < this.doc.lines[this.line].tokens.length-1) { + this.token++; + } else { + if(this.line == this.doc.lines.length-1) return; + this.line++; + this.token = 0; + } + return this; + } + + /** + * Return the token immediately preceding this cursor. At the start of the file, a token of type "eol" is returned. + */ + getPrevToken(): Token { + if(this.line == 0 && this.token == 0) + return { type: "eol", raw: "\n", offset: 0, state: null }; + let cursor = this.clone(); + cursor.previous(); + return cursor.getToken(); + } + + /** + * Returns the token at this cursor position. + */ + getToken() { + return this.doc.lines[this.line].tokens[this.token]; + } + + equals(cursor: TokenCursor) { + return this.line == cursor.line && this.token == cursor.token && this.doc == cursor.doc; + } +} + +export class LispTokenCursor extends TokenCursor { + constructor(public doc: LineInputModel, public line: number, public token: number) { + super(doc, line, token); + } + + /** Create a copy of this cursor. */ + clone() { + return new LispTokenCursor(this.doc, this.line, this.token); + } + + /** + * Moves this token past the inside of a multiline string + */ + fowardString() { + while(!this.atEnd()) { + switch(this.getToken().type) { + case "eol": + case "str-inside": + case "str-start": + this.next(); + continue; + default: + return; + } + } + } + + /** + * Moves this token past any whitespace or comment. + */ + forwardWhitespace(includeComments = true) { + while(!this.atEnd()) { + switch(this.getToken().type) { + case "comment": + if(!includeComments) + return; + case "eol": + case "ws": + this.next(); + continue; + default: + return; + } + } + } + + /** + * Moves this token back past any whitespace or comment. + */ + backwardWhitespace(includeComments = true) { + while(!this.atStart()) { + switch(this.getPrevToken().type) { + case "comment": + if(!includeComments) + return; + case "eol": + this.previous(); + if(this.getPrevToken().type == "comment") { + this.next(); + return; + } + continue; + case "ws": + this.previous(); + continue; + default: + return; + } + } + } + + // Lisp navigation commands begin here. + + /** + * Moves this token forward one s-expression at this level. + * If the next non whitespace token is an open paren, skips past it's matching + * close paren. + * + * If the next token is a form of closing paren, does not move. + * + * @returns true if the cursor was moved, false otherwise. + */ + forwardSexp(skipComments = false): boolean { + let delta = 0; + this.forwardWhitespace(!skipComments); + if(this.getToken().type == "close") { + return false; + } + while(!this.atEnd()) { + this.forwardWhitespace(!skipComments); + let tk = this.getToken(); + switch(tk.type) { + case 'comment': + this.next(); // skip past comment + this.next(); // skip past EOL. + return true; + case 'id': + case 'lit': + case 'kw': + case 'str': + case 'str-end': + this.next(); + if(delta <= 0) + return true; + break; + case 'str-inside': + case 'str-start': + do { + this.next(); + tk = this.getToken(); + } while(!this.atEnd() && (tk.type == "str-inside" || tk.type == "eol")) + continue; + case 'close': + delta--; + this.next(); + if(delta <= 0) + return true; + break; + case 'open': + delta++; + this.next(); + break; + default: + this.next(); + break; + } + } + } + + /** + * Moves this token backward one s-expression at this level. + * If the previous non whitespace token is an close paren, skips past it's matching + * open paren. + * + * If the previous token is a form of open paren, does not move. + * + * @returns true if the cursor was moved, false otherwise. + */ + backwardSexp(skipComments = true) { + let delta = 0; + this.backwardWhitespace(!skipComments); + switch(this.getPrevToken().type) { + case "open": + return false; + } + while(!this.atStart()) { + this.backwardWhitespace(!skipComments); + let tk = this.getPrevToken(); + switch(tk.type) { + case 'id': + case 'lit': + case 'kw': + case 'comment': + case 'str': + case 'str-start': + this.previous(); + if(delta <= 0) + return true; + break; + case 'str-inside': + case 'str-end': + do { + this.previous(); + tk = this.getPrevToken(); + } while(!this.atStart() && tk.type == "str-inside") + continue; + case 'close': + delta++; + this.previous(); + break; + case 'open': + delta--; + this.previous(); + if(delta <= 0) + return true; + break; + default: + this.previous(); + } + } + } + + /** + * Moves this cursor to the close paren of the containing sexpr, or until the end of the document. + */ + forwardList(): boolean { + let cursor = this.clone(); + while(cursor.forwardSexp()) { } + if(cursor.getToken().type == "close") { + this.set(cursor); + return true; + } + return false; + } + + /** + * Moves this cursor backwards to the open paren of the containing sexpr, or until the start of the document. + */ + backwardList(): boolean { + let cursor = this.clone(); + while(cursor.backwardSexp()) { } + if(cursor.getPrevToken().type == "open") { + this.set(cursor); + return true; + } + return false; + } + + /** + * If possible, moves this cursor forwards past any whitespace, and then past the immediately following open-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + downList(): boolean { + let cursor = this.clone(); + cursor.forwardWhitespace(); + if(cursor.getToken().type == "open") { + cursor.next(); + this.set(cursor); + return true; + } + return false; + } + + /** + * If possible, moves this cursor forwards past any whitespace, and then past the immediately following close-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + upList(): boolean { + let cursor = this.clone(); + cursor.forwardWhitespace(); + if(cursor.getToken().type == "close") { + cursor.next(); + this.set(cursor); + return true; + } + return false; + } + + /** + * If possible, moves this cursor backwards past any whitespace, and then backwards past the immediately following open-paren and returns true. + * If the source does not match this, returns false and does not move the cursor. + */ + backwardUpList(): boolean { + let cursor = this.clone(); + cursor.backwardWhitespace(); + if(cursor.getPrevToken().type == "open") { + cursor.previous(); + this.set(cursor); + return true; + } + return false; + } + + withinWhitespace() { + let tk = this.getToken().type; + if(tk == "eol" || tk == "ws") { + return true; + } + } + withinString() { + let tk = this.getToken().type; + if(tk == "str" || tk == "str-start" || tk == "str-end" || tk == "str-inside") { + return true; + } + if(tk == "eol") { + tk = this.getPrevToken().type; + if(tk == "str-inside" || tk == "str-start") + return true; + } + return false; + } +} diff --git a/webview-src/client/undo.ts b/webview-src/client/undo.ts new file mode 100644 index 000000000..633e2413f --- /dev/null +++ b/webview-src/client/undo.ts @@ -0,0 +1,123 @@ +/** + * A reversable operation to a document of type T. + */ +export abstract class UndoStep { + /** The name of this undo operation. */ + name: string; + /** If true, the UndoManager will not attempt to coalesce events onto this step. */ + undoStop: boolean; + + /** Given the document, undos the effect of this step */ + abstract undo(c: T): void; + + /** Given the document, redoes the effect of this step */ + abstract redo(c: T): void; + + /** + * Given another UndoStep, attempts to modify this undo-step to include the subsequent one. + * If successful, returns true, if unsuccessful, returns false, and the step must be added to the + * UndoManager, too. + */ + coalesce(c: UndoStep): boolean { + return false; + } +} + +export class UndoStepGroup extends UndoStep { + steps: UndoStep[] = []; + + addUndoStep(step: UndoStep) { + let prevStep = this.steps.length && this.steps[this.steps.length-1]; + + if(prevStep && !prevStep.undoStop && prevStep.coalesce(step)) + return; + this.steps.push(step); + } + + undo(c: T): void { + for(let i=this.steps.length-1; i>=0; i--) + this.steps[i].undo(c); + } + + redo(c: T): void { + for(let i=0; i { + private undos: UndoStep[] = []; + private redos: UndoStep[] = []; + + private groupedUndo: UndoStepGroup | null; + + /** + * Adds the step to the undo stack, and clears the redo stack. + * If possible, coalesces it into the previous undo. + * + * @param step the UndoStep to add. + */ + addUndoStep(step: UndoStep) { + if(this.groupedUndo) { + this.groupedUndo.addUndoStep(step); + } else if(this.undos.length) { + let prevUndo = this.undos[this.undos.length-1]; + if(prevUndo.undoStop) { + this.undos.push(step); + } else if(!prevUndo.coalesce(step)) { + this.undos.push(step); + } + } else { + this.undos.push(step); + } + this.redos = []; + } + + withUndo(f: () => void) { + if(!this.groupedUndo) { + try { + this.groupedUndo = new UndoStepGroup(); + f(); + let undo = this.groupedUndo; + this.groupedUndo = null; + switch(undo.steps.length) { + case 0: break; + case 1: this.addUndoStep(undo.steps[0]); break; + default: + this.addUndoStep(undo); + } + } finally { + this.groupedUndo = null; + } + } else { + f(); + } + } + + /** Prevents this undo from becoming coalesced with future undos */ + insertUndoStop() { + if(this.undos.length) + this.undos[this.undos.length-1].undoStop = true; + } + + /** Performs the top undo operation on the document (if it exists), moving it to the redo stack. */ + undo(c: T) { + if(this.undos.length) { + const step = this.undos.pop(); + step.undo(c); + this.redos.push(step); + } + } + + /** Performs the top redo operation on the document (if it exists), moving it back onto the undo stack. */ + redo(c: T) { + if(this.redos.length) { + const step = this.redos.pop(); + step.redo(c); + this.undos.push(step); + } + } +} diff --git a/webview-src/main.ts b/webview-src/server/main.ts similarity index 99% rename from webview-src/main.ts rename to webview-src/server/main.ts index 045486ee8..011d5bccb 100644 --- a/webview-src/main.ts +++ b/webview-src/server/main.ts @@ -1,7 +1,7 @@ -import { ReplConsole } from "@calva/repl-interactor"; -import * as lexer from "@calva/repl-interactor/js/clojure-lexer"; +import { ReplConsole } from "../client/repl-console"; +import * as lexer from "../client/clojure-lexer"; var Ansi = require('ansi-to-html'); -import "../html/styles.scss"; +import "../../html/styles.scss"; declare function acquireVsCodeApi(): { postMessage: (object: any) => void } const message = acquireVsCodeApi(); From de66f0cedf0d030817578cc6b292787128c45816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 18 Aug 2019 21:16:52 +0200 Subject: [PATCH 030/128] Move webview-src insidce calva rootDir --- calva/calva-fmt/ts/docmirror/index.ts | 4 +- calva/utilities.ts | 4 +- .../webview-src}/client/clojure-lexer.ts | 0 .../webview-src}/client/hotkeys.ts | 0 .../webview-src}/client/indent.ts | 0 .../webview-src}/client/index.ts | 0 .../webview-src}/client/lexer.ts | 0 .../webview-src}/client/main.ts | 0 .../webview-src}/client/model.ts | 0 .../webview-src}/client/paredit.ts | 0 .../webview-src}/client/readline.ts | 0 .../webview-src}/client/repl-console.ts | 0 .../webview-src}/client/token-cursor.ts | 0 .../webview-src}/client/undo.ts | 0 .../webview-src}/server/main.ts | 2 +- .../webview-src}/tsconfig.json | 0 package-lock.json | 190 ++++++++++-------- package.json | 1 - tsconfig.json | 2 +- webpack.config.js | 4 +- 20 files changed, 115 insertions(+), 92 deletions(-) rename {webview-src => calva/webview-src}/client/clojure-lexer.ts (100%) rename {webview-src => calva/webview-src}/client/hotkeys.ts (100%) rename {webview-src => calva/webview-src}/client/indent.ts (100%) rename {webview-src => calva/webview-src}/client/index.ts (100%) rename {webview-src => calva/webview-src}/client/lexer.ts (100%) rename {webview-src => calva/webview-src}/client/main.ts (100%) rename {webview-src => calva/webview-src}/client/model.ts (100%) rename {webview-src => calva/webview-src}/client/paredit.ts (100%) rename {webview-src => calva/webview-src}/client/readline.ts (100%) rename {webview-src => calva/webview-src}/client/repl-console.ts (100%) rename {webview-src => calva/webview-src}/client/token-cursor.ts (100%) rename {webview-src => calva/webview-src}/client/undo.ts (100%) rename {webview-src => calva/webview-src}/server/main.ts (99%) rename {webview-src => calva/webview-src}/tsconfig.json (100%) diff --git a/calva/calva-fmt/ts/docmirror/index.ts b/calva/calva-fmt/ts/docmirror/index.ts index f2c5e0041..0bfa69883 100644 --- a/calva/calva-fmt/ts/docmirror/index.ts +++ b/calva/calva-fmt/ts/docmirror/index.ts @@ -1,5 +1,5 @@ -import * as model from "@calva/repl-interactor/js/model" -export { getIndent } from "@calva/repl-interactor/js/indent" +import * as model from "../../../webview-src/client/model" +export { getIndent } from "../../../webview-src/client/indent" import * as vscode from "vscode" import * as utilities from '../../../utilities'; diff --git a/calva/utilities.ts b/calva/utilities.ts index b5d85c6ab..955b5cdcc 100644 --- a/calva/utilities.ts +++ b/calva/utilities.ts @@ -9,8 +9,8 @@ import { activeReplWindow } from './repl-window'; const syntaxQuoteSymbol = "`"; const { parseForms } = require('../cljs-out/cljs-lib'); import * as docMirror from './calva-fmt/ts/docmirror'; -import { TokenCursor, LispTokenCursor } from '@calva/repl-interactor/js/token-cursor'; -import { Token } from '@calva/repl-interactor/js/clojure-lexer'; +import { TokenCursor, LispTokenCursor } from './webview-src/client/token-cursor'; +import { Token } from './webview-src/client/clojure-lexer'; import select from './select'; diff --git a/webview-src/client/clojure-lexer.ts b/calva/webview-src/client/clojure-lexer.ts similarity index 100% rename from webview-src/client/clojure-lexer.ts rename to calva/webview-src/client/clojure-lexer.ts diff --git a/webview-src/client/hotkeys.ts b/calva/webview-src/client/hotkeys.ts similarity index 100% rename from webview-src/client/hotkeys.ts rename to calva/webview-src/client/hotkeys.ts diff --git a/webview-src/client/indent.ts b/calva/webview-src/client/indent.ts similarity index 100% rename from webview-src/client/indent.ts rename to calva/webview-src/client/indent.ts diff --git a/webview-src/client/index.ts b/calva/webview-src/client/index.ts similarity index 100% rename from webview-src/client/index.ts rename to calva/webview-src/client/index.ts diff --git a/webview-src/client/lexer.ts b/calva/webview-src/client/lexer.ts similarity index 100% rename from webview-src/client/lexer.ts rename to calva/webview-src/client/lexer.ts diff --git a/webview-src/client/main.ts b/calva/webview-src/client/main.ts similarity index 100% rename from webview-src/client/main.ts rename to calva/webview-src/client/main.ts diff --git a/webview-src/client/model.ts b/calva/webview-src/client/model.ts similarity index 100% rename from webview-src/client/model.ts rename to calva/webview-src/client/model.ts diff --git a/webview-src/client/paredit.ts b/calva/webview-src/client/paredit.ts similarity index 100% rename from webview-src/client/paredit.ts rename to calva/webview-src/client/paredit.ts diff --git a/webview-src/client/readline.ts b/calva/webview-src/client/readline.ts similarity index 100% rename from webview-src/client/readline.ts rename to calva/webview-src/client/readline.ts diff --git a/webview-src/client/repl-console.ts b/calva/webview-src/client/repl-console.ts similarity index 100% rename from webview-src/client/repl-console.ts rename to calva/webview-src/client/repl-console.ts diff --git a/webview-src/client/token-cursor.ts b/calva/webview-src/client/token-cursor.ts similarity index 100% rename from webview-src/client/token-cursor.ts rename to calva/webview-src/client/token-cursor.ts diff --git a/webview-src/client/undo.ts b/calva/webview-src/client/undo.ts similarity index 100% rename from webview-src/client/undo.ts rename to calva/webview-src/client/undo.ts diff --git a/webview-src/server/main.ts b/calva/webview-src/server/main.ts similarity index 99% rename from webview-src/server/main.ts rename to calva/webview-src/server/main.ts index 011d5bccb..425891460 100644 --- a/webview-src/server/main.ts +++ b/calva/webview-src/server/main.ts @@ -1,7 +1,7 @@ import { ReplConsole } from "../client/repl-console"; import * as lexer from "../client/clojure-lexer"; var Ansi = require('ansi-to-html'); -import "../../html/styles.scss"; +import "../../../html/styles.scss"; declare function acquireVsCodeApi(): { postMessage: (object: any) => void } const message = acquireVsCodeApi(); diff --git a/webview-src/tsconfig.json b/calva/webview-src/tsconfig.json similarity index 100% rename from webview-src/tsconfig.json rename to calva/webview-src/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 4f090ccde..987f7e75d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,16 +32,6 @@ } } }, - "@calva/repl-interactor": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@calva/repl-interactor/-/repl-interactor-0.0.20.tgz", - "integrity": "sha512-LUnfD5gLgXgKUhjov1fMlx8fa3au5P/RBSFcPlPERoR+qvNAJG+x+lNTeRe27iUy69HTCqmSA9ZJLm4QussLmw==", - "requires": { - "compression": "^1.7.3", - "express": "^4.16.4", - "ts-node": "^7.0.1" - } - }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -286,6 +276,7 @@ "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, "requires": { "mime-types": "~2.1.24", "negotiator": "0.6.2" @@ -294,12 +285,14 @@ "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, "requires": { "mime-db": "1.40.0" } @@ -499,7 +492,8 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true }, "array-includes": { "version": "3.0.3", @@ -532,11 +526,6 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -803,6 +792,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, "requires": { "bytes": "3.1.0", "content-type": "~1.0.4", @@ -819,12 +809,14 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -832,7 +824,8 @@ "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true } } }, @@ -1017,7 +1010,8 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "buffer-indexof": { "version": "1.1.1", @@ -1046,7 +1040,8 @@ "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true }, "cacache": { "version": "11.3.2", @@ -1349,6 +1344,7 @@ "version": "2.0.17", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dev": true, "requires": { "mime-db": ">= 1.40.0 < 2" }, @@ -1356,7 +1352,8 @@ "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true } } }, @@ -1364,6 +1361,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, "requires": { "accepts": "~1.3.5", "bytes": "3.0.0", @@ -1503,6 +1501,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, "requires": { "safe-buffer": "5.1.2" } @@ -1510,17 +1509,20 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true }, "cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true }, "copy-concurrently": { "version": "1.0.5", @@ -1771,6 +1773,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -1941,7 +1944,8 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true }, "des.js": { "version": "1.0.0", @@ -1956,7 +1960,8 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true }, "detect-file": { "version": "1.0.0", @@ -1986,7 +1991,8 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true }, "diffie-hellman": { "version": "5.0.3", @@ -2078,7 +2084,8 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true }, "elliptic": { "version": "6.4.1", @@ -2104,7 +2111,8 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true }, "end-of-stream": { "version": "1.4.1", @@ -2205,7 +2213,8 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", @@ -2439,7 +2448,8 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true }, "eventemitter3": { "version": "3.1.2", @@ -2535,6 +2545,7 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, "requires": { "accepts": "~1.3.7", "array-flatten": "1.1.1", @@ -2571,7 +2582,8 @@ "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true } } }, @@ -2770,6 +2782,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -2955,7 +2968,8 @@ "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true }, "fragment-cache": { "version": "0.2.1", @@ -2969,7 +2983,8 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true }, "from2": { "version": "2.3.0", @@ -3972,6 +3987,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -4265,7 +4281,8 @@ "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true }, "is-absolute-url": { "version": "3.0.0", @@ -4830,11 +4847,6 @@ } } }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" - }, "mamacro": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", @@ -4879,7 +4891,8 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true }, "mem": { "version": "4.0.0", @@ -4905,12 +4918,14 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true }, "micromatch": { "version": "3.1.10", @@ -4946,7 +4961,8 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true }, "mime-db": { "version": "1.37.0", @@ -4991,7 +5007,8 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "minipass": { "version": "2.3.5", @@ -5081,6 +5098,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" }, @@ -5088,7 +5106,8 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true } } }, @@ -5179,7 +5198,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "multicast-dns": { "version": "6.2.3", @@ -5238,7 +5258,8 @@ "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true }, "neo-async": { "version": "2.6.1", @@ -8630,6 +8651,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, "requires": { "ee-first": "1.1.1" } @@ -8637,7 +8659,8 @@ "on-headers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true }, "once": { "version": "1.4.0", @@ -8922,7 +8945,8 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true }, "pascalcase": { "version": "0.1.1", @@ -8975,7 +8999,8 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true }, "path-type": { "version": "2.0.0", @@ -9242,6 +9267,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, "requires": { "forwarded": "~0.1.2", "ipaddr.js": "1.9.0" @@ -9367,12 +9393,14 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, "requires": { "bytes": "3.1.0", "http-errors": "1.7.2", @@ -9383,12 +9411,14 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -9854,6 +9884,7 @@ "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -9873,7 +9904,8 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -9922,6 +9954,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -9967,7 +10000,8 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true }, "sha.js": { "version": "2.4.11", @@ -10219,7 +10253,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-resolve": { "version": "0.5.2", @@ -10238,6 +10273,7 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -10438,7 +10474,8 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true }, "stream-browserify": { "version": "2.0.2", @@ -10796,7 +10833,8 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true }, "touch": { "version": "3.1.0", @@ -10858,21 +10896,6 @@ "semver": "^5.0.1" } }, - "ts-node": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", - "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", - "requires": { - "arrify": "^1.0.0", - "buffer-from": "^1.1.0", - "diff": "^3.1.0", - "make-error": "^1.1.1", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "source-map-support": "^0.5.6", - "yn": "^2.0.0" - } - }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -10941,6 +10964,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -10949,12 +10973,14 @@ "mime-db": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true }, "mime-types": { "version": "2.1.24", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, "requires": { "mime-db": "1.40.0" } @@ -11061,7 +11087,8 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true }, "unset-value": { "version": "1.0.0", @@ -11226,7 +11253,8 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true }, "uuid": { "version": "3.3.2", @@ -11252,7 +11280,8 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true }, "verror": { "version": "1.10.0", @@ -11795,11 +11824,6 @@ "decamelize": "^1.2.0" } }, - "yn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", - "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=" - }, "zone.js": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.7.6.tgz", diff --git a/package.json b/package.json index c18741b4a..77bed51be 100644 --- a/package.json +++ b/package.json @@ -1084,7 +1084,6 @@ "postinstall": "node ./node_modules/vscode/bin/install" }, "dependencies": { - "@calva/repl-interactor": "0.0.20", "@types/mocha": "^2.2.42", "@types/node": "^7.10.6", "@types/universal-analytics": "^0.4.2", diff --git a/tsconfig.json b/tsconfig.json index 4e4f07b97..4a250621d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,7 @@ }, "exclude": [ "node_modules", - "webview-src", + "calva/webview-src", ".vscode-test" ] } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 648164dcb..55fd00321 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -34,7 +34,7 @@ const CALVA_MAIN = { } const REPL_WINDOW = { - entry: './webview-src/server/main.ts', + entry: './calva/webview-src/server/main.ts', performance: { maxEntrypointSize: 1024000, maxAssetSize: 1024000, @@ -53,7 +53,7 @@ const REPL_WINDOW = { options: { transpileOnly: true, experimentalWatchApi: true, - configFile: 'webview-src/tsconfig.json' + configFile: 'calva/webview-src/tsconfig.json' }, exclude: /node_modules/ }, From 7e1ee94723bf3c48142efdbc03e60b413cde4a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 18 Aug 2019 21:28:27 +0200 Subject: [PATCH 031/128] Add withinValidList() to TokenCursor --- calva/providers/completion.ts | 3 +-- calva/webview-src/client/token-cursor.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/calva/providers/completion.ts b/calva/providers/completion.ts index a1865cebf..736f07019 100644 --- a/calva/providers/completion.ts +++ b/calva/providers/completion.ts @@ -36,8 +36,7 @@ export default class CalvaCompletionItemProvider implements CompletionItemProvid contextStart = toplevel.substring(0, wordStartLocalOffset), contextEnd = toplevel.substring(wordEndLocalOffset), context = `${contextStart}__prefix__${contextEnd}`, - // using forwardList() here to see that the toplevel form is balanced - toplevelIsValidForm = toplevelStartCursor.forwardList() && context != '__prefix__', + toplevelIsValidForm = toplevelStartCursor.withinValidList() && context != '__prefix__', client = util.getSession(util.getFileType(document)), res = await client.complete(util.getNamespace(document), text, toplevelIsValidForm ? context : null), results = res.completions || []; diff --git a/calva/webview-src/client/token-cursor.ts b/calva/webview-src/client/token-cursor.ts index ef12d4d5c..073f8fd19 100644 --- a/calva/webview-src/client/token-cursor.ts +++ b/calva/webview-src/client/token-cursor.ts @@ -367,4 +367,13 @@ export class LispTokenCursor extends TokenCursor { } return false; } + + /** + * Tells if the cursor is inside a properly closed list. + */ + withinValidList(): boolean { + let cursor = this.clone(); + while(cursor.forwardSexp()) { } + return cursor.getToken().type == "close"; + } } From 5f7403fdaa7d766471f67a38e3659c6081b9e5ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 18 Aug 2019 22:39:41 +0200 Subject: [PATCH 032/128] Disable sync diagnostics --- calva/calva-fmt/ts/docmirror/index.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/calva/calva-fmt/ts/docmirror/index.ts b/calva/calva-fmt/ts/docmirror/index.ts index 0bfa69883..5a2e97f18 100644 --- a/calva/calva-fmt/ts/docmirror/index.ts +++ b/calva/calva-fmt/ts/docmirror/index.ts @@ -22,17 +22,15 @@ function processChanges(document: vscode.TextDocument, contentChanges: vscode.Te model.insertedLines.clear() model.deletedLines.clear(); - // FIXME: // this is an important diagnostic check to ensure the models haven't de-synced, but it MUST be removed before release. - - let mtext = model.getText(0, model.maxOffset) - let dtext = document.getText().replace(/\r\n/g,"\n"); - if(mtext != dtext) { - vscode.window.showErrorMessage("document hozed") - console.error(mtext) - console.error("vs") - console.error(dtext) - } + // let mtext = model.getText(0, model.maxOffset) + // let dtext = document.getText().replace(/\r\n/g,"\n"); + // if(mtext != dtext) { + // vscode.window.showErrorMessage("document hozed") + // console.error(mtext) + // console.error("vs") + // console.error(dtext) + // } } export function getDocument(doc: vscode.TextDocument) { From f13939b6334c2246b0b69358a334f5b17ca80c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 20 Aug 2019 12:13:01 +0200 Subject: [PATCH 033/128] Clarify the trimming and keywordizing of aliases --- calva/nrepl/jack-in.ts | 28 ++++++++++++++++++++++++---- calva/state.ts | 6 ++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index ff458d7ed..a3c78c6b7 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -100,7 +100,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { myProfiles = state.config().myLeinProfiles; if (projectProfiles.length + myProfiles.length > 0) { const profilesList = [...projectProfiles, ...myProfiles]; - profiles = [...profiles, ...profilesList.map(v => { return ":" + v })]; + profiles = [...profiles, ...profilesList.map(_keywordize)]; if (profiles.length) { profiles = await utilities.quickPickMulti({ values: profiles, @@ -135,7 +135,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { } if (profiles.length) { - out.push("with-profile", profiles.map(x => `+${x.substr(1)}`).join(',')); + out.push("with-profile", profiles.map(x => `+${_unKeywordize(x)}`).join(',')); } if (alias) { @@ -194,7 +194,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { myAliases = state.config().myCljAliases; let aliases: string[] = []; if (projectAliases.length + myAliases.length > 0) { - aliases = await utilities.quickPickMulti({ values: [...projectAliases, ...myAliases].map(x => ":" + x), saveAs: `${connector.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); + aliases = await utilities.quickPickMulti({ values: [...projectAliases, ...myAliases].map(_keywordize), saveAs: `${connector.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); } const dependencies = includeCljs ? { ...cliDependencies, ...figwheelDependencies } : cliDependencies, @@ -202,7 +202,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; let aliasHasMain: boolean = false; for (let ali in aliases) { - const aliasKey = aliases[ali].substr(1); + const aliasKey = _unKeywordize(aliases[ali]); if (parsed.aliases) { let alias = parsed.aliases[aliasKey]; aliasHasMain = alias && alias["main-opts"] != undefined; @@ -255,6 +255,26 @@ const projectTypes: { [id: string]: connector.ProjectType } = { } } +/** + * Prepends a `:` to a string, so it can be used as an EDN keyword. + * (Or at least made to look like one). + * @param {string} s the string to be keywordized + * @return {string} keywordized string + */ +function _keywordize(s: string): string { + return `:${s}`; +} + +/** + * Remove the leading `:` from strings (EDN keywords)' + * NB: Does not check if the leading character is really a `:`. + * @param {string} kw + * @return {string} kw without the first character + */ +function _unKeywordize(kw: string) { + return kw.substr(1); +} + /** Given the name of a project in project types, find that project. */ function getProjectTypeForName(name: string) { for (let id in projectTypes) diff --git a/calva/state.ts b/calva/state.ts index 2116a9590..a94ccb594 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -74,6 +74,12 @@ function reset() { data = Immutable.fromJS(initialData); } +/** + * Trims EDN alias and profile names from any surrounding whitespace or `:` characters. + * This in order to free the user from having to figure out how the name should be entered. + * @param {string} name + * @return {string} The trimmed name + */ function _trimAliasName(name: string): string { return name.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") } From 3dae919383b69de24d8b2e0d9e6e589ce85ea13e Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Tue, 20 Aug 2019 16:07:42 +0200 Subject: [PATCH 034/128] ConnectSequence are used now to start everything, adding name to customCljsType --- calva/connector.ts | 271 ++++++--------------------------- calva/nrepl/connectSequence.ts | 4 + calva/nrepl/jack-in.ts | 2 +- package.json | 11 +- 4 files changed, 63 insertions(+), 225 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 856475ec1..b8f56f6eb 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -10,7 +10,7 @@ import status from './status'; const { parseEdn } = require('../cljs-out/cljs-lib'); import { NReplClient, NReplSession } from "./nrepl"; import { reconnectReplWindow, openReplWindow } from './repl-window'; -import { CustomCljsType } from './nrepl/connectSequence'; +import { CustomCljsType, ReplConnectSequence, getDefaultCljsType } from './nrepl/connectSequence'; const PROJECT_DIR_KEY = "connect.projectDir"; const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; @@ -80,7 +80,7 @@ export type ProjectType = { nReplPortFile: () => string; }; -async function connectToHost(hostname, port, cljsTypeName: string, replTypes: ReplType[]) { +async function connectToHost(hostname, port, cljsTypeName: string, connectSequence: ReplConnectSequence) { state.analytics().logEvent("REPL", "Connecting").send(); let chan = state.outputChannel(); @@ -116,15 +116,37 @@ async function connectToHost(hostname, port, cljsTypeName: string, replTypes: Re state.cursor.set('cljc', cljSession) status.update(); - //TODO Execute here cljJackInCode - //TODO Check continueStdOutRegExp when present, if so wait for it in stdout, otherwise only wait for eval - //! connection stdout as option in eval + if (connectSequence.afterCLJReplJackInCode) { + let afterCljRepl = connectSequence.afterCLJReplJackInCode; + let done = true; + let stdout; + let stderr; + if (afterCljRepl.continueStdOutRegExp) { + done = false; + stdout = (msg) => { + done = (msg.search(afterCljRepl.continueStdOutRegExp) >= 0 || + msg.find((x: string) => { return x.search(afterCljRepl.continueStdOutRegExp) >= 0 }) != undefined); + }; + + stderr = (msg) => { + done = true; + } + } + + await cljSession.eval(afterCljRepl.code, {stdout, stderr}); + + while (! done) {}; //TODO Find better way to wait? + } + //cljsSession = nClient.session; //terminal.createREPLTerminal('clj', null, chan); let cljsSession = null, shadowBuild = null; try { - [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, replTypes) : [null, null]; + let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string"? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + let translatedReplType = createCLJSReplType(cljsType); + + [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType) : [null, null]; } catch (e) { chan.appendLine("Error while connecting cljs REPL: " + e); } @@ -170,16 +192,6 @@ export function shadowBuild() { return state.deref().get('cljsBuild'); } - -function shadowCljsReplStart(buildOrRepl: string) { - if (!buildOrRepl) - return null; - if (buildOrRepl.charAt(0) == ":") - return `(shadow.cljs.devtools.api/nrepl-select ${buildOrRepl})` - else - return `(shadow.cljs.devtools.api/${buildOrRepl})` -} - function getFigwheelMainProjects() { let chan = state.outputChannel(); let res = fs.readdirSync(getProjectRoot()); @@ -242,116 +254,6 @@ export interface ReplType { connected: (valueResult: string, out: any[], err: any[]) => boolean; } -export let cljsReplTypes: ReplType[] = [ - { - name: "Figwheel Main", - start: async (session, name, checkFn) => { - let projects = await getFigwheelMainProjects(); - let builds = projects.length <= 1 ? projects : await util.quickPickMulti({ - values: projects, - placeHolder: "Please select which builds to start", - saveAs: `${getProjectRoot()}/figwheel-main-projects` - }) - if (builds) { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - state.cursor.set('cljsBuild', builds[0]); - const initCode = `(do (require 'figwheel.main.api) (figwheel.main.api/start ${builds.map(x => { return `"${x}"` }).join(" ")}))`; - return evalConnectCode(session, initCode, name, checkFn); - } - else { - let chan = state.outputChannel(); - chan.appendLine("Connection to Figwheel Main aborted."); - throw "Aborted"; - } - }, - started: (result, out, err) => { - return out.find((x: string) => { return x.search("Prompt will show") >= 0 }) != undefined || - err != undefined && err.find((x: string) => { - return x.search("already running") >= 0 - }); - }, - connect: async (session, name, checkFn) => { - let build = await util.quickPickSingle({ - values: await getFigwheelMainProjects(), - placeHolder: "Select which build to connect to", - saveAs: `${getProjectRoot()}/figwheel-main-build` - }); - if (build) { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - state.cursor.set('cljsBuild', build); - const initCode = `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl "${build}"))`; - return evalConnectCode(session, initCode, name, checkFn); - } else { - let chan = state.outputChannel(); - chan.appendLine("Connection aborted."); - throw "Aborted"; - } - }, - connected: (_result, out, _err) => { - return out.find((x: string) => { return x.search("To quit, type: :cljs/quit") >= 0 }) != undefined - } - }, - { - name: "Figwheel", - connect: async (session, name, checkFn) => { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', false); - state.cursor.set('cljsBuild', null); - const initCode = "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))"; - return evalConnectCode(session, initCode, name, checkFn, - [(output) => { - let matched = output.match(/Figwheel: Starting server at (.*)/); - if (matched && matched.length > 1) { - let chan = state.outputChannel(); - chan.appendLine(matched[0]); - if (state.config().openBrowserWhenFigwheelStarted) { - chan.appendLine("Opening Figwheel app in the browser (this automatic behaviour can be disabled using Settings) ..."); - open(matched[1]).catch(reason => { - console.error("Error opening Figwheel app in browser: ", reason); - }); - } else { - chan.appendLine("Not automaticaly opening Figwheel app in the browser (this can be disabled using Settings)."); - } - chan.appendLine("The CLJS REPL session will be connected when the Figwheel app has been started in the browser."); - } - }]); - }, - connected: (_result, out, _err) => { - return out.find((x: string) => { return x.search("Prompt will show") >= 0 }) != undefined - } - }, - { - name: "shadow-cljs", - connect: async (session, name, checkFn) => { - let build = await util.quickPickSingle({ - values: await shadowBuilds(), - placeHolder: "Select which build to connect to", - saveAs: `${getProjectRoot()}/shadow-cljs-build` - }); - if (build) { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - state.cursor.set('cljsBuild', build); - const initCode = shadowCljsReplStart(build); - return evalConnectCode(session, initCode, name, checkFn); - } else { - let chan = state.outputChannel(); - chan.appendLine("Connection aborted."); - throw "Aborted"; - } - }, - connected: (result, _out, _err) => { - return result.search(/:selected/) >= 0; - } - } -]; - -type customCLJSREPLType = { - name: string, - startCode: string, - tellUserToStartRegExp?: string, - printThisLineRegExp?: string, - connectedRegExp: string, -}; - function figwheelOrShadowBuilds(cljsTypeName: string): string[] { if (cljsTypeName.includes("Figwheel Main")) { return getFigwheelMainProjects(); @@ -373,7 +275,8 @@ function updateInitCode(build: string, initCode): string { return null; } -function createCLJSReplType(cljsTypeName: string, desc: CustomCljsType): ReplType { +function createCLJSReplType(desc: CustomCljsType): ReplType { + const cljsTypeName = desc.name; let result: ReplType = { name: cljsTypeName, connect: async (session, name, checkFn) => { @@ -458,65 +361,8 @@ function createCLJSReplType(cljsTypeName: string, desc: CustomCljsType): ReplTyp return result; } -function createCustomCLJSReplType(custom: customCLJSREPLType): ReplType { - return { - name: custom.name, - connect: (session, name, checkFn) => { - const chan = state.outputChannel(); - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', false); - state.cursor.set('cljsBuild', null); - const initCode = custom.startCode; - let outputProcessors: processOutputFn[] = []; - if (custom.tellUserToStartRegExp) { - outputProcessors.push((output) => { - if (custom.tellUserToStartRegExp) { - const matched = output.match(custom.tellUserToStartRegExp); - if (matched && matched.length > 0) { - chan.appendLine("CLJS REPL ready to connect. Please, start your ClojureScript app."); - chan.appendLine(" The CLJS REPL will connect when your app is running."); - } - } - }); - } - if (custom.printThisLineRegExp) { - outputProcessors.push((output) => { - if (custom.printThisLineRegExp) { - const matched = output.match(`.*(${custom.printThisLineRegExp}).*`); - if (matched && matched.length > 0) { - chan.appendLine(util.stripAnsi(matched[0])); - } - } - }); - } - return evalConnectCode(session, initCode, name, checkFn, outputProcessors); - }, - connected: (_result, out, err) => { - return out.find((x: string) => { return x.search(custom.connectedRegExp) >= 0 }) != undefined - } - } -} - -export function getCustomCLJSRepl(): ReplType { - const replConfig = state.config().customCljsRepl; - if (replConfig) { - return createCustomCLJSReplType(replConfig as customCLJSREPLType); - } else { - return undefined; - } -} - -function getCLJSReplTypes() { - let types = cljsReplTypes.slice(); - const customType = getCustomCLJSRepl(); - if (customType) { - types.push(customType); - } - return types; -} - -async function makeCljsSessionClone(session, replType, replTypes: ReplType[]) { +async function makeCljsSessionClone(session, replType, repl: ReplType) { let chan = state.outputChannel(); - let repl: ReplType; chan.appendLine("Creating cljs repl session..."); let newCljsSession = await session.clone(); @@ -524,7 +370,6 @@ async function makeCljsSessionClone(session, replType, replTypes: ReplType[]) { chan.show(true); state.extensionContext.workspaceState.update('cljsReplType', replType); state.analytics().logEvent("REPL", "ConnectingCLJS", replType).send(); - repl = replTypes.find(x => x.name == replType); if (repl.start != undefined) { chan.appendLine("Starting repl for: " + repl.name + "..."); if (await repl.start(newCljsSession, repl.name, repl.started)) { @@ -556,7 +401,7 @@ async function makeCljsSessionClone(session, replType, replTypes: ReplType[]) { return [null, null]; } -async function promptForNreplUrlAndConnect(port, cljsTypeName, replTypes: ReplType[]) { +async function promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence: ReplConnectSequence) { let current = state.deref(), chan = state.outputChannel(); @@ -573,7 +418,7 @@ async function promptForNreplUrlAndConnect(port, cljsTypeName, replTypes: ReplTy if (parsedPort && parsedPort > 0 && parsedPort < 65536) { state.cursor.set("hostname", hostname); state.cursor.set("port", parsedPort); - await connectToHost(hostname, parsedPort, cljsTypeName, replTypes); + await connectToHost(hostname, parsedPort, cljsTypeName, connectSequence); } else { chan.appendLine("Bad url: " + url); state.cursor.set('connecting', false); @@ -587,7 +432,7 @@ async function promptForNreplUrlAndConnect(port, cljsTypeName, replTypes: ReplTy } export let nClient: NReplClient; -export let cljSession: NReplSession; +export let cljSession: NReplSession; export let cljsSession: NReplSession; export function nreplPortFile(subPath: string): string { @@ -599,43 +444,21 @@ export function nreplPortFile(subPath: string): string { return subPath; } -export async function connect(isAutoConnect = false, isJackIn = false) { +export async function connect(connectSequence: ReplConnectSequence, isAutoConnect = false, isJackIn = false) { let chan = state.outputChannel(); let cljsTypeName: string; state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); - const types = getCLJSReplTypes(); - const CLJS_PROJECT_TYPE_NONE = "Don't load any cljs support, thanks" - if (isJackIn) { - cljsTypeName = state.extensionContext.workspaceState.get('selectedCljsTypeName'); - } else { - try { - await initProjectDir(); - } catch { - return; - } - let typeNames = types.map(x => x.name); - typeNames.splice(0, 0, CLJS_PROJECT_TYPE_NONE) - cljsTypeName = await util.quickPickSingle({ - values: typeNames, - placeHolder: "If you want ClojureScript support, please select a cljs project type", saveAs: `${getProjectRoot()}/connect-cljs-type`, autoSelect: true - }); - if (!cljsTypeName) { - state.analytics().logEvent("REPL", "ConnectInterrupted", "NoCljsProjectPicked").send(); - return; - } - } - + cljsTypeName = typeof connectSequence.cljsType == "string"? connectSequence.cljsType : connectSequence.cljsType.name; state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); - if (cljsTypeName == CLJS_PROJECT_TYPE_NONE) { - cljsTypeName = ""; - } + console.log("connect", {connectSequence, cljsTypeName}); const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? nreplPortFile(".shadow-cljs/nrepl.port") : nreplPortFile(".nrepl-port")); state.extensionContext.workspaceState.update('selectedCljsTypeName', cljsTypeName); + state.extensionContext.workspaceState.update('selectedConnectSequence', connectSequence); if (fs.existsSync(portFile)) { let port = fs.readFileSync(portFile, 'utf8'); @@ -643,16 +466,16 @@ export async function connect(isAutoConnect = false, isJackIn = false) { if (isAutoConnect) { state.cursor.set("hostname", "localhost"); state.cursor.set("port", port); - await connectToHost("localhost", port, cljsTypeName, types); + await connectToHost("localhost", port, cljsTypeName, connectSequence); } else { - await promptForNreplUrlAndConnect(port, cljsTypeName, types); + await promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence); } } else { chan.appendLine('No nrepl port file found. (Calva does not start the nrepl for you, yet.)'); - await promptForNreplUrlAndConnect(port, cljsTypeName, types); + await promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence); } } else { - await promptForNreplUrlAndConnect(null, cljsTypeName, types); + await promptForNreplUrlAndConnect(null, cljsTypeName, connectSequence); } return true; } @@ -687,12 +510,14 @@ export default { let current = state.deref(), cljSession = util.getSession('clj'), chan = state.outputChannel(); - const cljsTypeName = state.extensionContext.workspaceState.get('selectedCljsTypeName'); + const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'); + const connectSequence: ReplConnectSequence = state.extensionContext.workspaceState.get('selectedConnectSequence'); + let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string"? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + let translatedReplType = createCLJSReplType(cljsType); - let [session, shadowBuild] = await makeCljsSessionClone(cljSession, cljsTypeName, getCLJSReplTypes()); + let [session, shadowBuild] = await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType); if (session) await setUpCljsRepl(session, chan, shadowBuild); status.update(); - }, - getCustomCLJSRepl: getCustomCLJSRepl + } }; diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 30eeb09bd..f88b90a91 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -13,6 +13,7 @@ enum CljsTypes { } interface CustomCljsType { + name: string, startCode?: string, builds?: string[], isStartedRegExp?: string, @@ -79,6 +80,7 @@ const defaultSequences = { const defaultCljsTypes = { "Figwheel Main": { + name: "Figwheel Main", startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, builds: [], isStartedRegExp: "Prompt will show", @@ -86,10 +88,12 @@ const defaultCljsTypes = { isConnectedRegExp: "To quit, type: :cljs/quit" }, "lein-figwheel": { + name: "lein-figwheel", connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", isConnectedRegExp: "Prompt will show" }, "shadow-cljs": { + name: "shadow-cljs", connectCode: { build: `(shadow.cljs.devtools.api/nrepl-select %BUILD%)`, repl: `(shadow.cljs.devtools.api/%REPL%)` diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 8f1bc6d6b..1f8eb9fab 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -304,7 +304,7 @@ async function executeJackInTask(projectType: connector.ProjectType, projectType setTimeout(() => { chan.show() }, 1000); state.cursor.set("launching", null); watcher.removeAllListeners(); - await connector.connect(true, true); + await connector.connect(connectSequence, true, true); chan.appendLine("Jack-in done.\nUse the VS Code task management UI to control the life cycle of the Jack-in task."); } }); diff --git a/package.json b/package.json index 77bed51be..3164f2e93 100644 --- a/package.json +++ b/package.json @@ -261,13 +261,22 @@ { "type": "object", "required": [ - "connectCode" + "connectCode", + "name" ], "properties": { + "name": { + "type": "string", + "description": "Used internally to save selection and states." + }, "startCode": { "type": "string", "description": "Clojure code to be evaluated to create and/or start your custom CLJS REPL." }, + "builds": { + "type": "array", + "description": "List of all builds that should be started." + }, "isStartedRegExp": { "type": "string", "description": "A regular experession which, when matched in the stdout from the startCode evaluation, will make Calva continue with connecting the REPL, and to prompt the user to start the application. If omitted and there is startCode Calva will continue when that code is evaluated. Special case: _If the regexp has a group, it will be assumed to match the URL where the application is started and Calva will automatically open it in order to connect to it._" From 1702b0e7dd4ff859b0f4533b985914557129fdbc Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Wed, 21 Aug 2019 15:34:16 +0200 Subject: [PATCH 035/128] CHeck if result and out are not undefined before using --- calva/connector.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index b8f56f6eb..0c233c482 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -312,8 +312,8 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { }, connected: (result, out, _err) => { if (desc.isConnectedRegExp) { - return (result.search(desc.isConnectedRegExp) >= 0 || - out.find((x: string) => { return x.search(desc.isConnectedRegExp) >= 0 }) != undefined); + return (result != undefined && (result.search(desc.isConnectedRegExp) >= 0)) || + (out != undefined && out.find((x: string) => { return x.search(desc.isConnectedRegExp) >= 0 }) != undefined); } return true; } @@ -351,7 +351,7 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { if (desc.isStartedRegExp) { result.started = (result, out, err) => { - return out.find((x: string) => { return x.search(desc.isStartedRegExp) >= 0 }) != undefined || + return (out != undefined && out.find((x: string) => { return x.search(desc.isStartedRegExp) >= 0 }) != undefined) || err != undefined && err.find((x: string) => { return x.search("already running") >= 0 }); From 559c4424961300bba59c1c2c09decb6132022f6a Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Wed, 21 Aug 2019 16:49:41 +0200 Subject: [PATCH 036/128] Fixing: ProjectType for cli --- calva/nrepl/connectSequence.ts | 12 ++++++------ calva/nrepl/jack-in.ts | 9 +++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index f88b90a91..4b2e71a34 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -52,17 +52,17 @@ const leiningenDefaults: ReplConnectSequence[] = const cljDefaults: ReplConnectSequence[] = [{ - name: "Clojure-CLI", - projectType: ProjectTypes["Clojure-CLI"] + name: "Clojure CLI", + projectType: ProjectTypes["Clojure CLI"] }, { - name: "Clojure-CLI + Figwheel", - projectType: ProjectTypes["Clojure-CLI"], + name: "Clojure CLI + Figwheel", + projectType: ProjectTypes["Clojure CLI"], cljsType: CljsTypes["lein-figwheel"] }, { - name: "Clujure-CLI + Figwheel Main", - projectType: ProjectTypes["Clojure-CLI"], + name: "Clojure CLI + Figwheel Main", + projectType: ProjectTypes["Clojure CLI"], cljsType: CljsTypes["Figwheel Main"] }]; diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 1f8eb9fab..e3b32217c 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -361,13 +361,10 @@ export async function calvaJackIn() { return; } - // Resolve the selection to an entry in projectTypes - const projectTypeName: string = projectConnectSequenceName.replace(/ \+ .*$/, ""); - let projectConnectSequence: ReplConnectSequence = sequences.find(seq => seq.name === projectConnectSequenceName); - state.extensionContext.workspaceState.update('selectedCljTypeName', projectTypeName); - let matched = projectConnectSequenceName.match(/ \+ (.*)$/); - const selectedCljsType = projectConnectSequence.cljsType == "shadow-cljs" ? "shadow-cljs" : matched != null ? matched[1] : ""; + const projectTypeName: string = projectConnectSequence.projectType; + state.extensionContext.workspaceState.update('selectedCljTypeName', projectConnectSequence.projectType); + const selectedCljsType = typeof projectConnectSequence.cljsType == "string" ? projectConnectSequence.cljsType : projectConnectSequence.cljsType.name; state.extensionContext.workspaceState.update('selectedCljsTypeName', selectedCljsType); if (!projectConnectSequence) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypeForBuildName").send(); From cf7bdf02cf7bbc5d508526057a157434fee33fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 22 Aug 2019 21:49:30 +0200 Subject: [PATCH 037/128] Fix empty builds check and build quoting --- calva/connector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 0c233c482..d0cde9abe 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -270,7 +270,7 @@ function updateInitCode(build: string, initCode): string { return initCode.repl.replace("%REPL%", build); } } else if (build && typeof initCode === 'string') { - return initCode.replace("%BUILD%", build); + return initCode.replace("%BUILD%", `"${build}"`); } return null; } @@ -285,7 +285,7 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { let initCode = desc.connectCode; let build: string = null; - if (desc.builds === [] && (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { + if (desc.builds.length === 0 && (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { let projects = await figwheelOrShadowBuilds(cljsTypeName); build = await util.quickPickSingle({ values: projects, From 02ed62c5cd9fcb62324316dd9c38b54f1be9d539 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 23 Aug 2019 10:15:36 +0200 Subject: [PATCH 038/128] Check if cljsType is set before using it, always add build-in to custom connectSequence --- calva/connector.ts | 37 +++++++++++++++++++++------------- calva/nrepl/connectSequence.ts | 5 +---- calva/nrepl/jack-in.ts | 20 ++++++++++++++---- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index d0cde9abe..6b6fc8906 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -125,7 +125,7 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen done = false; stdout = (msg) => { done = (msg.search(afterCljRepl.continueStdOutRegExp) >= 0 || - msg.find((x: string) => { return x.search(afterCljRepl.continueStdOutRegExp) >= 0 }) != undefined); + msg.find((x: string) => { return x.search(afterCljRepl.continueStdOutRegExp) >= 0 }) != undefined); }; stderr = (msg) => { @@ -133,9 +133,9 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen } } - await cljSession.eval(afterCljRepl.code, {stdout, stderr}); + await cljSession.eval(afterCljRepl.code, { stdout, stderr }); - while (! done) {}; //TODO Find better way to wait? + while (!done) { }; //TODO Find better way to wait? } //cljsSession = nClient.session; @@ -143,10 +143,12 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen let cljsSession = null, shadowBuild = null; try { - let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string"? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; - let translatedReplType = createCLJSReplType(cljsType); + if (connectSequence.cljsType != undefined) { + let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + let translatedReplType = createCLJSReplType(cljsType); - [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType) : [null, null]; + [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType) : [null, null]; + } } catch (e) { chan.appendLine("Error while connecting cljs REPL: " + e); } @@ -336,7 +338,7 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { if (builds) { state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); state.cursor.set('cljsBuild', builds[0]); - startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); + startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); return evalConnectCode(session, startCode, name, checkFn); } else { let chan = state.outputChannel(); @@ -352,9 +354,9 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { if (desc.isStartedRegExp) { result.started = (result, out, err) => { return (out != undefined && out.find((x: string) => { return x.search(desc.isStartedRegExp) >= 0 }) != undefined) || - err != undefined && err.find((x: string) => { - return x.search("already running") >= 0 - }); + err != undefined && err.find((x: string) => { + return x.search("already running") >= 0 + }); } } @@ -432,7 +434,7 @@ async function promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence: } export let nClient: NReplClient; -export let cljSession: NReplSession; +export let cljSession: NReplSession; export let cljsSession: NReplSession; export function nreplPortFile(subPath: string): string { @@ -450,10 +452,17 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); - cljsTypeName = typeof connectSequence.cljsType == "string"? connectSequence.cljsType : connectSequence.cljsType.name; + if (connectSequence.cljsType == undefined) { + cljsTypeName = ""; + } else if (typeof connectSequence.cljsType == "string") { + cljsTypeName = connectSequence.cljsType; + } else { + cljsTypeName = connectSequence.cljsType.name; + } + state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); - console.log("connect", {connectSequence, cljsTypeName}); + console.log("connect", { connectSequence, cljsTypeName }); const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? nreplPortFile(".shadow-cljs/nrepl.port") : nreplPortFile(".nrepl-port")); @@ -512,7 +521,7 @@ export default { chan = state.outputChannel(); const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'); const connectSequence: ReplConnectSequence = state.extensionContext.workspaceState.get('selectedConnectSequence'); - let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string"? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; let translatedReplType = createCLJSReplType(cljsType); let [session, shadowBuild] = await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 4b2e71a34..3f95b6a9b 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -116,10 +116,7 @@ function getConfigCustomConnectSequences(): ReplConnectSequence[] { */ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { let customSequences = getConfigCustomConnectSequences(); - if (customSequences.length == 1) { - return customSequences; - } - console.log("defaultSeq", defaultSequences); + let result = []; for (let pType of projectTypes) { console.log("pType", pType); diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index e3b32217c..cea3a5c1e 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -362,14 +362,26 @@ export async function calvaJackIn() { } let projectConnectSequence: ReplConnectSequence = sequences.find(seq => seq.name === projectConnectSequenceName); - const projectTypeName: string = projectConnectSequence.projectType; - state.extensionContext.workspaceState.update('selectedCljTypeName', projectConnectSequence.projectType); - const selectedCljsType = typeof projectConnectSequence.cljsType == "string" ? projectConnectSequence.cljsType : projectConnectSequence.cljsType.name; - state.extensionContext.workspaceState.update('selectedCljsTypeName', selectedCljsType); + if (!projectConnectSequence) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypeForBuildName").send(); return; } + + const projectTypeName: string = projectConnectSequence.projectType; + state.extensionContext.workspaceState.update('selectedCljTypeName', projectConnectSequence.projectType); + let selectedCljsType: string; + + if (projectConnectSequence.cljsType == undefined) { + selectedCljsType = ""; + } else if (typeof projectConnectSequence.cljsType == "string") { + selectedCljsType = projectConnectSequence.cljsType; + } else { + selectedCljsType = projectConnectSequence.cljsType.name; + } + + state.extensionContext.workspaceState.update('selectedCljsTypeName', selectedCljsType); + let projectType = getProjectTypeForName(projectTypeName); let executable = isWin ? projectType.winCmd : projectType.cmd; // Ask the project type to build up the command line. This may prompt for further information. From 489d370efc1aafaa9fba85319c99f52de3597d24 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 23 Aug 2019 10:43:08 +0200 Subject: [PATCH 039/128] Check if name and projectType is set in customSequences, if not inform user about it --- calva/nrepl/connectSequence.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 3f95b6a9b..f21ab3198 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -1,4 +1,4 @@ -import { workspace } from "vscode"; +import { workspace , window } from "vscode"; enum ProjectTypes { "Leiningen" = "Leiningen", @@ -105,8 +105,22 @@ const defaultCljsTypes = { /** Retrieve the replConnectSequences from the config */ function getConfigCustomConnectSequences(): ReplConnectSequence[] { - return workspace.getConfiguration('calva') - .get("replConnectSequences", []); + let result: ReplConnectSequence[] = workspace.getConfiguration('calva') + .get("replConnectSequences", []); + + for (let conSeq of result) { + if (conSeq.name == undefined || + conSeq.projectType == undefined) { + + window.showWarningMessage("Check your calva.replConnectSequences. "+ + "You need to supply name and projectType for every sequence. " + + "After fixing the customSequences can be used."); + + return []; + } + } + + return result; } /** From 9cd7905a29d33a797ad90b5ad8497ba97027a70a Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 23 Aug 2019 12:23:46 +0200 Subject: [PATCH 040/128] Removed while loop to wait, need to find a way --- calva/connector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/connector.ts b/calva/connector.ts index 6b6fc8906..8777b4e2d 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -135,7 +135,7 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen await cljSession.eval(afterCljRepl.code, { stdout, stderr }); - while (!done) { }; //TODO Find better way to wait? + //TODO Find way to wait } //cljsSession = nClient.session; From 7c4ddd0f4e42f5098caf424bdba8f27ec9c849c4 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 23 Aug 2019 12:58:02 +0200 Subject: [PATCH 041/128] Show all output/error message and result from the jackInCode in calva says --- calva/connector.ts | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 8777b4e2d..23001a7cc 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -118,24 +118,17 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen if (connectSequence.afterCLJReplJackInCode) { let afterCljRepl = connectSequence.afterCLJReplJackInCode; - let done = true; - let stdout; - let stderr; - if (afterCljRepl.continueStdOutRegExp) { - done = false; - stdout = (msg) => { - done = (msg.search(afterCljRepl.continueStdOutRegExp) >= 0 || - msg.find((x: string) => { return x.search(afterCljRepl.continueStdOutRegExp) >= 0 }) != undefined); - }; - - stderr = (msg) => { - done = true; - } - } + let stdout = (msg) => { + state.outputChannel().appendLine(msg); + }; - await cljSession.eval(afterCljRepl.code, { stdout, stderr }); + let stderr = (msg) => { + state.outputChannel().appendLine(msg); + }; + + let result = await cljSession.eval(afterCljRepl.code, { stdout, stderr }).value; - //TODO Find way to wait + state.outputChannel().appendLine(result); } //cljsSession = nClient.session; From 99a23fbc2cc21ac209a1924da02c87656608f730 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 23 Aug 2019 13:00:20 +0200 Subject: [PATCH 042/128] Made afterCLJReplJackInCode to string onld --- calva/connector.ts | 2 +- calva/nrepl/connectSequence.ts | 7 +------ package.json | 15 ++------------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 23001a7cc..02809b14a 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -126,7 +126,7 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen state.outputChannel().appendLine(msg); }; - let result = await cljSession.eval(afterCljRepl.code, { stdout, stderr }).value; + let result = await cljSession.eval(afterCljRepl, { stdout, stderr }).value; state.outputChannel().appendLine(result); } diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index f21ab3198..e3ad778b5 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -22,15 +22,10 @@ interface CustomCljsType { printThisLineRegExp?: string } -interface CLJJackInCode { - code: string, - continueStdOutRegExp?: string -} - interface ReplConnectSequence { name: string, projectType: ProjectTypes, - afterCLJReplJackInCode?: CLJJackInCode, + afterCLJReplJackInCode?: string, cljsType?: CljsTypes | CustomCljsType } diff --git a/package.json b/package.json index 3164f2e93..78d0da5eb 100644 --- a/package.json +++ b/package.json @@ -232,20 +232,9 @@ ] }, "afterCLJReplJackInCode": { - "type": "object", + "type": "string", "description": "Here you can give Calva some Clojure code to evaluate in the CLJ REPL, once it has been created.", - "required": false, - "properties": { - "code": { - "type": "string", - "description": "The Clojure code to evaluate." - }, - "continueStdOutRegExp": { - "type": "string", - "description": "A regular expression which, when matched in the stdout from the evaluation, will tell Calva that the CLJ REPL is ready. If this field is omitted the sequence will continue when the code is evaluated.", - "required": false - } - } + "required": false }, "cljsType": { "description": "Either a built in type, or an object configuring a custom type. If omitted Calva will show a menu with the built-in types.", From 93f6dfd40e6ca715a547981f0be1de6d0ae4113b Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Fri, 23 Aug 2019 13:18:06 +0200 Subject: [PATCH 043/128] StripAnsi from afterCLJReplJackInCode + better output, fixing lein-figwheel jack-in --- calva/connector.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 02809b14a..54c345b1a 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -119,16 +119,17 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen if (connectSequence.afterCLJReplJackInCode) { let afterCljRepl = connectSequence.afterCLJReplJackInCode; let stdout = (msg) => { - state.outputChannel().appendLine(msg); + state.outputChannel().appendLine(util.stripAnsi(msg.trim())); }; let stderr = (msg) => { - state.outputChannel().appendLine(msg); + state.outputChannel().appendLine("ERR: " + util.stripAnsi(msg.trim())); }; + state.outputChannel().appendLine("Executing afterCLJReplJackInCode: " + afterCljRepl); let result = await cljSession.eval(afterCljRepl, { stdout, stderr }).value; - state.outputChannel().appendLine(result); + state.outputChannel().appendLine("=> " + result); } //cljsSession = nClient.session; @@ -280,7 +281,9 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { let initCode = desc.connectCode; let build: string = null; - if (desc.builds.length === 0 && (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { + if (desc.builds != undefined && + desc.builds.length === 0 && + (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { let projects = await figwheelOrShadowBuilds(cljsTypeName); build = await util.quickPickSingle({ values: projects, From ec0117bcddde85b833d4a161a95c030fea6f095d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 23 Aug 2019 13:40:42 +0200 Subject: [PATCH 044/128] Evaluate afterCLJJackIn in REPL window --- calva/connector.ts | 17 +++-------------- calva/repl-window.ts | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 54c345b1a..20acee48c 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -9,7 +9,7 @@ import status from './status'; const { parseEdn } = require('../cljs-out/cljs-lib'); import { NReplClient, NReplSession } from "./nrepl"; -import { reconnectReplWindow, openReplWindow } from './repl-window'; +import { reconnectReplWindow, openReplWindow, sendTextToREPLWindow } from './repl-window'; import { CustomCljsType, ReplConnectSequence, getDefaultCljsType } from './nrepl/connectSequence'; const PROJECT_DIR_KEY = "connect.projectDir"; @@ -117,19 +117,8 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen status.update(); if (connectSequence.afterCLJReplJackInCode) { - let afterCljRepl = connectSequence.afterCLJReplJackInCode; - let stdout = (msg) => { - state.outputChannel().appendLine(util.stripAnsi(msg.trim())); - }; - - let stderr = (msg) => { - state.outputChannel().appendLine("ERR: " + util.stripAnsi(msg.trim())); - }; - - state.outputChannel().appendLine("Executing afterCLJReplJackInCode: " + afterCljRepl); - let result = await cljSession.eval(afterCljRepl, { stdout, stderr }).value; - - state.outputChannel().appendLine("=> " + result); + state.outputChannel().appendLine("Evaluating `afterCLJReplJackInCode` in CLJ REPL Window"); + await sendTextToREPLWindow(connectSequence.afterCLJReplJackInCode, null, false); } //cljsSession = nClient.session; diff --git a/calva/repl-window.ts b/calva/repl-window.ts index 71a1c739f..006b16ef8 100644 --- a/calva/repl-window.ts +++ b/calva/repl-window.ts @@ -245,7 +245,7 @@ function setREPLNamespaceCommand() { setREPLNamespace(util.getDocumentNamespace(), false).catch(r => { console.error(r) }); } -async function sendTextToREPLWindow(text, ns: string, pprint: boolean) { +export async function sendTextToREPLWindow(text, ns: string, pprint: boolean) { let wnd = await openReplWindow(util.getREPLSessionType()); if (wnd) { let oldNs = wnd.ns; From 1b9d99aa9b3b2095e647a6f3e4cceb4b9ea8a9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 23 Aug 2019 14:44:18 +0200 Subject: [PATCH 045/128] Add deprectationMessage to customCLjsRepl --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 78d0da5eb..f07d93a2b 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,7 @@ } }, "calva.customCljsRepl": { + "deprecationMessage": "This settings is deprecated. Use `cljsType` in a `calva.replConnectSequences` item instead.", "type": "object", "default": null, "description": "Configuration for custom any CLJS REPL type your project may use", From 7f8755eaec951826e8d13be01d41f87d3fa6be54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 23 Aug 2019 18:38:03 +0200 Subject: [PATCH 046/128] Fix schema specifying the wrong CLI key --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f07d93a2b..d5864bbb5 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "description": "Select one of the project types supported by Calva.", "enum": [ "Leiningen", - "Clojure-CLI", + "Clojure CLI", "shadow-cljs" ] }, From 71345cccccc1e759a80fcae8e46e7d16d1181f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 23 Aug 2019 18:57:06 +0200 Subject: [PATCH 047/128] Trying to better understand what's going on =) --- calva/connector.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 20acee48c..502b9e207 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -260,18 +260,18 @@ function updateInitCode(build: string, initCode): string { return null; } -function createCLJSReplType(desc: CustomCljsType): ReplType { - const cljsTypeName = desc.name; - let result: ReplType = { +function createCLJSReplType(cljsType: CustomCljsType): ReplType { + const cljsTypeName = cljsType.name; + let replType: ReplType = { name: cljsTypeName, connect: async (session, name, checkFn) => { const chan = state.outputChannel(); - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', !(desc.builds === undefined)); - let initCode = desc.connectCode; + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', !(cljsType.builds === undefined)); + let initCode = cljsType.connectCode; let build: string = null; - if (desc.builds != undefined && - desc.builds.length === 0 && + if (cljsType.builds != undefined && + cljsType.builds.length === 0 && (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { let projects = await figwheelOrShadowBuilds(cljsTypeName); build = await util.quickPickSingle({ @@ -297,18 +297,18 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { return evalConnectCode(session, initCode, name, checkFn); }, - connected: (result, out, _err) => { - if (desc.isConnectedRegExp) { - return (result != undefined && (result.search(desc.isConnectedRegExp) >= 0)) || - (out != undefined && out.find((x: string) => { return x.search(desc.isConnectedRegExp) >= 0 }) != undefined); + connected: (replType, out, _err) => { + if (cljsType.isConnectedRegExp) { + return (replType != undefined && (replType.search(cljsType.isConnectedRegExp) >= 0)) || + (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); } return true; } }; - if (desc.startCode) { - result.start = async (session, name, checkFn) => { - let startCode = desc.startCode; + if (cljsType.startCode) { + replType.start = async (session, name, checkFn) => { + let startCode = cljsType.startCode; let builds: string[]; @@ -336,16 +336,16 @@ function createCLJSReplType(desc: CustomCljsType): ReplType { }; } - if (desc.isStartedRegExp) { - result.started = (result, out, err) => { - return (out != undefined && out.find((x: string) => { return x.search(desc.isStartedRegExp) >= 0 }) != undefined) || + if (cljsType.isStartedRegExp) { + replType.started = (result, out, err) => { + return (out != undefined && out.find((x: string) => { return x.search(cljsType.isStartedRegExp) >= 0 }) != undefined) || err != undefined && err.find((x: string) => { return x.search("already running") >= 0 }); } } - return result; + return replType; } async function makeCljsSessionClone(session, replType, repl: ReplType) { From be934d5d4d8f587e8c5ebff03b228d2336c06fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 24 Aug 2019 11:39:54 +0200 Subject: [PATCH 048/128] Use literal regexps instead of strings --- calva/nrepl/connectSequence.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index e3ad778b5..14629961c 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -78,14 +78,14 @@ const defaultCljsTypes = { name: "Figwheel Main", startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, builds: [], - isStartedRegExp: "Prompt will show", + isStartedRegExp: /Prompt will show|already running/, connectCode: `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl %BUILD%))`, - isConnectedRegExp: "To quit, type: :cljs/quit" + isConnectedRegExp: /To quit, type: :cljs\/quit/ }, "lein-figwheel": { name: "lein-figwheel", connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", - isConnectedRegExp: "Prompt will show" + isConnectedRegExp: /Prompt will show/ }, "shadow-cljs": { name: "shadow-cljs", From 629a3f79cf7f0f36c09c9389ef2aea6d80a8b049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 24 Aug 2019 11:47:47 +0200 Subject: [PATCH 049/128] Use same regexp for searching out and err --- calva/connector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/connector.ts b/calva/connector.ts index 502b9e207..77cd0b284 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -340,7 +340,7 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { replType.started = (result, out, err) => { return (out != undefined && out.find((x: string) => { return x.search(cljsType.isStartedRegExp) >= 0 }) != undefined) || err != undefined && err.find((x: string) => { - return x.search("already running") >= 0 + return x.search(cljsType.isStartedRegExp) >= 0 }); } } From 27132f26bb4c46be771c1c2b64f408bcd2c753bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 24 Aug 2019 13:07:16 +0200 Subject: [PATCH 050/128] Add printer for `printThisLineRegExp` --- calva/connector.ts | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 77cd0b284..8a2503672 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -234,9 +234,9 @@ async function evalConnectCode(newCljsSession: NReplSession, code: string, export interface ReplType { name: string, start?: connectFn; - started?: (valueResult: string, out: any[], err: any[]) => boolean; + started?: (valueResult: string, out: string[], err: string[]) => boolean; connect?: connectFn; - connected: (valueResult: string, out: any[], err: any[]) => boolean; + connected: (valueResult: string, out: string[], err: string[]) => boolean; } function figwheelOrShadowBuilds(cljsTypeName: string): string[] { @@ -261,11 +261,12 @@ function updateInitCode(build: string, initCode): string { } function createCLJSReplType(cljsType: CustomCljsType): ReplType { - const cljsTypeName = cljsType.name; + const cljsTypeName = cljsType.name, + chan = state.outputChannel(); + let replType: ReplType = { name: cljsTypeName, connect: async (session, name, checkFn) => { - const chan = state.outputChannel(); state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', !(cljsType.builds === undefined)); let initCode = cljsType.connectCode; let build: string = null; @@ -297,10 +298,19 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { return evalConnectCode(session, initCode, name, checkFn); }, - connected: (replType, out, _err) => { + connected: (replType, out, err) => { + if (cljsType.printThisLineRegExp) { + [...out, ...err].forEach(x => { + if (x.search(cljsType.printThisLineRegExp) >= 0) { + chan.appendLine(x); + } + }); + } + if (cljsType.isConnectedRegExp) { - return (replType != undefined && (replType.search(cljsType.isConnectedRegExp) >= 0)) || - (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); + return out.find(x => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined; + // return (replType != undefined && (replType.search(cljsType.isConnectedRegExp) >= 0)) || + // (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); } return true; } @@ -326,7 +336,6 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); return evalConnectCode(session, startCode, name, checkFn); } else { - let chan = state.outputChannel(); chan.appendLine("Starting REPL for " + cljsTypeName + " aborted."); throw "Aborted"; } @@ -338,10 +347,14 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { if (cljsType.isStartedRegExp) { replType.started = (result, out, err) => { - return (out != undefined && out.find((x: string) => { return x.search(cljsType.isStartedRegExp) >= 0 }) != undefined) || - err != undefined && err.find((x: string) => { - return x.search(cljsType.isStartedRegExp) >= 0 + if (cljsType.printThisLineRegExp) { + [...out, ...err].forEach(x => { + if (x.search(cljsType.printThisLineRegExp) >= 0) { + chan.appendLine(x); + } }); + } + return [...out, ...err].find(x => { return x.search(cljsType.isStartedRegExp) >= 0 }) != undefined; } } @@ -357,6 +370,8 @@ async function makeCljsSessionClone(session, replType, repl: ReplType) { chan.show(true); state.extensionContext.workspaceState.update('cljsReplType', replType); state.analytics().logEvent("REPL", "ConnectingCLJS", replType).send(); + chan.appendLine("Connecting CLJS repl: " + repl.name + "..."); + chan.appendLine("See Calva Connection Log for detailed progress updates."); if (repl.start != undefined) { chan.appendLine("Starting repl for: " + repl.name + "..."); if (await repl.start(newCljsSession, repl.name, repl.started)) { @@ -370,9 +385,6 @@ async function makeCljsSessionClone(session, replType, repl: ReplType) { return [null, null]; } } - chan.appendLine("Connecting CLJS repl: " + repl.name + "..."); - chan.appendLine(" Compiling and stuff. This can take a minute or two."); - chan.appendLine(" See Calva Connection Log for detailed progress updates."); if (await repl.connect(newCljsSession, repl.name, repl.connected)) { state.analytics().logEvent("REPL", "ConnectedCLJS", repl.name).send(); state.cursor.set('cljs', cljsSession = newCljsSession); From 3c63fcf71208de76ead176736591f26c05ae9a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 25 Aug 2019 12:19:17 +0200 Subject: [PATCH 051/128] Print matching lines as they come in Also, always print stuff on stderr --- calva/connector.ts | 51 ++-- calva/master-connector.ts | 581 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 606 insertions(+), 26 deletions(-) create mode 100644 calva/master-connector.ts diff --git a/calva/connector.ts b/calva/connector.ts index 8a2503672..dc9de3af6 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -202,18 +202,19 @@ type processOutputFn = (output: string) => void; type connectFn = (session: NReplSession, name: string, checkSuccess: checkConnectedFn) => Promise; async function evalConnectCode(newCljsSession: NReplSession, code: string, - name: string, checkSuccess: checkConnectedFn, outputProcessors?: processOutputFn[]): Promise { + name: string, checkSuccess: checkConnectedFn, outputProcessors: processOutputFn[] = [], errorProcessors: processOutputFn[] = []): Promise { let chan = state.connectionLogChannel(); let err = [], out = [], result = await newCljsSession.eval(code, { stdout: x => { - if (outputProcessors) { - for (const p of outputProcessors) { - p(x); - } + for (const p of outputProcessors) { + p(x); } out.push(util.stripAnsi(x)); chan.append(util.stripAnsi(x)); }, stderr: x => { + for (const p of errorProcessors) { + p(x); + } err.push(util.stripAnsi(x)); chan.append(util.stripAnsi(x)); } @@ -262,7 +263,17 @@ function updateInitCode(build: string, initCode): string { function createCLJSReplType(cljsType: CustomCljsType): ReplType { const cljsTypeName = cljsType.name, - chan = state.outputChannel(); + chan = state.outputChannel(), + printThisPrinter: processOutputFn = x => { + if (cljsType.printThisLineRegExp) { + if (x.search(cljsType.printThisLineRegExp) >= 0) { + chan.appendLine(util.stripAnsi(x).replace(/\s*$/, "")); + } + } + }, + allPrinter: processOutputFn = x => { + chan.appendLine(util.stripAnsi(x).replace(/\s*$/, "")); + } let replType: ReplType = { name: cljsTypeName, @@ -296,23 +307,16 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { state.cursor.set('cljsBuild', null); - return evalConnectCode(session, initCode, name, checkFn); + return evalConnectCode(session, initCode, name, checkFn, [printThisPrinter], [allPrinter]); }, - connected: (replType, out, err) => { - if (cljsType.printThisLineRegExp) { - [...out, ...err].forEach(x => { - if (x.search(cljsType.printThisLineRegExp) >= 0) { - chan.appendLine(x); - } - }); - } - + connected: (replType, out, err) => { if (cljsType.isConnectedRegExp) { return out.find(x => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined; // return (replType != undefined && (replType.search(cljsType.isConnectedRegExp) >= 0)) || // (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); + } else { + return true; } - return true; } }; @@ -322,6 +326,8 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { let builds: string[]; + let outputProcessors: processOutputFn[]; + if (startCode.includes("%BUILDS")) { let projects = await figwheelOrShadowBuilds(cljsTypeName); builds = projects.length <= 1 ? projects : await util.quickPickMulti({ @@ -339,21 +345,14 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { chan.appendLine("Starting REPL for " + cljsTypeName + " aborted."); throw "Aborted"; } + } else { + return evalConnectCode(session, startCode, name, checkFn, [printThisPrinter], [allPrinter]); } - - return evalConnectCode(session, startCode, name, checkFn); }; } if (cljsType.isStartedRegExp) { replType.started = (result, out, err) => { - if (cljsType.printThisLineRegExp) { - [...out, ...err].forEach(x => { - if (x.search(cljsType.printThisLineRegExp) >= 0) { - chan.appendLine(x); - } - }); - } return [...out, ...err].find(x => { return x.search(cljsType.isStartedRegExp) >= 0 }) != undefined; } } diff --git a/calva/master-connector.ts b/calva/master-connector.ts new file mode 100644 index 000000000..f3da49809 --- /dev/null +++ b/calva/master-connector.ts @@ -0,0 +1,581 @@ +import * as vscode from 'vscode'; +import * as _ from 'lodash'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as state from './state'; +import * as util from './utilities'; +import * as open from 'open'; +import status from './status'; + +const { parseEdn } = require('../cljs-out/cljs-lib'); +import { NReplClient, NReplSession } from "./nrepl"; +import { reconnectReplWindow, openReplWindow } from './repl-window'; + +const PROJECT_DIR_KEY = "connect.projectDir"; +const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; + +export function getProjectRoot(): string { + return state.deref().get(PROJECT_DIR_KEY); +} + +export function getProjectWsFolder(): vscode.WorkspaceFolder { + return state.deref().get(PROJECT_WS_FOLDER_KEY); +} + +/** + * Figures out, and stores, the current clojure project root + * Also stores the WorkSpace folder for the project to be used + * when executing the Task and get proper vscode reporting. + * + * 1. If there is no file open. Stop and complain. + * 2. If there is a file open, use it to determine the project root + * by looking for project files from the file's directory and up to + * the window root (for plain folder windows) or the file's + * workspace folder root (for workspaces) to find the project root. + * + * If there is no project file found, then store either of these + * 1. the window root for plain folders + * 2. first workspace root for workspaces. + * (This situation will be detected later by the connect process.) + */ +export async function initProjectDir(): Promise { + const projectFileNames: string[] = ["project.clj", "shadow-cljs.edn", "deps.edn"], + doc = util.getDocument({}), + workspaceFolder = doc ? vscode.workspace.getWorkspaceFolder(doc.uri) : null; + if (!workspaceFolder) { + vscode.window.showErrorMessage("There is no document opened in the workspace. Aborting. Please open a file in your Clojure project and try again."); + state.analytics().logEvent("REPL", "JackinOrConnectInterrupted", "NoCurrentDocument").send(); + throw "There is no document opened in the workspace. Aborting."; + } else { + state.cursor.set(PROJECT_WS_FOLDER_KEY, workspaceFolder); + let rootPath: string = path.resolve(workspaceFolder.uri.fsPath); + let d = path.dirname(doc.uri.fsPath); + let prev = null; + while (d != prev) { + for (let projectFile in projectFileNames) { + const p = path.resolve(d, projectFileNames[projectFile]); + if (fs.existsSync(p)) { + rootPath = d; + break; + } + } + if (d == rootPath) { + break; + } + prev = d; + d = path.resolve(d, ".."); + } + state.cursor.set(PROJECT_DIR_KEY, rootPath); + } +} + +export type ProjectType = { + name: string; + cljsTypes: string[]; + cmd: string; + winCmd: string; + commandLine: (includeCljs: boolean) => any; + useWhenExists: string; + nReplPortFile: () => string; +}; + +async function connectToHost(hostname, port, cljsTypeName: string, replTypes: ReplType[]) { + state.analytics().logEvent("REPL", "Connecting").send(); + + let chan = state.outputChannel(); + if (nClient) { + nClient["silent"] = true; + nClient.close(); + } + cljsSession = cljSession = null; + state.cursor.set('connecting', true); + status.update(); + try { + chan.appendLine("Hooking up nREPL sessions..."); + // Create an nREPL client. waiting for the connection to be established. + nClient = await NReplClient.create({ host: hostname, port: +port }) + nClient.addOnCloseHandler(c => { + state.cursor.set("connected", false); + state.cursor.set("connecting", false); + if (!c["silent"]) // we didn't deliberately close this session, mention this fact. + chan.appendLine("nREPL Connection was closed"); + status.update(); + }) + cljSession = nClient.session; + chan.appendLine("Connected session: clj"); + await openReplWindow("clj", true); + await reconnectReplWindow("clj").catch(reason => { + console.error("Failed reconnecting REPL window: ", reason); + }); + + state.cursor.set("connected", true); + state.analytics().logEvent("REPL", "ConnectedCLJ").send(); + state.cursor.set("connecting", false); + state.cursor.set('clj', cljSession) + state.cursor.set('cljc', cljSession) + status.update(); + + //cljsSession = nClient.session; + //terminal.createREPLTerminal('clj', null, chan); + let cljsSession = null, + shadowBuild = null; + try { + [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, replTypes) : [null, null]; + } catch (e) { + chan.appendLine("Error while connecting cljs REPL: " + e); + } + if (cljsSession) + await setUpCljsRepl(cljsSession, chan, shadowBuild); + chan.appendLine('cljc files will use the clj REPL.' + (cljsSession ? ' (You can toggle this at will.)' : '')); + //evaluate.loadFile(); + status.update(); + + } catch (e) { + state.cursor.set("connected", false); + state.cursor.set("connecting", false); + chan.appendLine("Failed connecting."); + state.analytics().logEvent("REPL", "FailedConnectingCLJ").send(); + return false; + } + + return true; +} + +async function setUpCljsRepl(cljsSession, chan, shadowBuild) { + state.cursor.set("cljs", cljsSession); + chan.appendLine("Connected session: cljs"); + await openReplWindow("cljs", true); + await reconnectReplWindow("cljs"); + //terminal.createREPLTerminal('cljs', shadowBuild, chan); + status.update(); +} + +export function shadowConfigFile() { + return getProjectRoot() + '/shadow-cljs.edn'; +} + +export function shadowBuilds() { + let parsed = parseEdn(fs.readFileSync(shadowConfigFile(), 'utf8').toString()), + builds = _.map(parsed.builds, (_v, key) => { return ":" + key }); + builds.push("node-repl"); + builds.push("browser-repl") + return builds; +} + +export function shadowBuild() { + return state.deref().get('cljsBuild'); +} + + +function shadowCljsReplStart(buildOrRepl: string) { + if (!buildOrRepl) + return null; + if (buildOrRepl.charAt(0) == ":") + return `(shadow.cljs.devtools.api/nrepl-select ${buildOrRepl})` + else + return `(shadow.cljs.devtools.api/${buildOrRepl})` +} + +function getFigwheelMainProjects() { + let chan = state.outputChannel(); + let res = fs.readdirSync(getProjectRoot()); + let projects = res.filter(x => x.match(/\.cljs\.edn/)).map(x => x.replace(/\.cljs\.edn$/, "")); + if (projects.length == 0) { + vscode.window.showErrorMessage("There are no figwheel project files (.cljs.edn) in the project directory."); + chan.appendLine("There are no figwheel project files (.cljs.edn) in the project directory."); + chan.appendLine("Connection to Figwheel Main aborted."); + throw "Aborted"; + } + return projects; +} + +type checkConnectedFn = (value: string, out: any[], err: any[]) => boolean; +type processOutputFn = (output: string) => void; +type connectFn = (session: NReplSession, name: string, checkSuccess: checkConnectedFn) => Promise; + +async function evalConnectCode(newCljsSession: NReplSession, code: string, + name: string, checkSuccess: checkConnectedFn, outputProcessors?: processOutputFn[]): Promise { + let chan = state.connectionLogChannel(); + let err = [], out = [], result = await newCljsSession.eval(code, { + stdout: x => { + if (outputProcessors) { + for (const p of outputProcessors) { + p(x); + } + } + out.push(util.stripAnsi(x)); + chan.append(util.stripAnsi(x)); + }, stderr: x => { + err.push(util.stripAnsi(x)); + chan.append(util.stripAnsi(x)); + } + }); + let valueResult = await result.value + .catch(reason => { + console.error("Error evaluating connect form: ", reason); + }); + if (checkSuccess(valueResult, out, err)) { + state.analytics().logEvent("REPL", "ConnectedCLJS", name).send(); + state.cursor.set('cljs', cljsSession = newCljsSession) + return true + } else { + return false; + } +} + +export interface ReplType { + name: string, + start?: connectFn; + started?: (valueResult: string, out: any[], err: any[]) => boolean; + connect?: connectFn; + connected: (valueResult: string, out: any[], err: any[]) => boolean; +} + +export let cljsReplTypes: ReplType[] = [ + { + name: "Figwheel Main", + start: async (session, name, checkFn) => { + let projects = await getFigwheelMainProjects(); + let builds = projects.length <= 1 ? projects : await util.quickPickMulti({ + values: projects, + placeHolder: "Please select which builds to start", + saveAs: `${getProjectRoot()}/figwheel-main-projects` + }) + if (builds) { + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); + state.cursor.set('cljsBuild', builds[0]); + const initCode = `(do (require 'figwheel.main.api) (figwheel.main.api/start ${builds.map(x => { return `"${x}"` }).join(" ")}))`; + return evalConnectCode(session, initCode, name, checkFn); + } + else { + let chan = state.outputChannel(); + chan.appendLine("Connection to Figwheel Main aborted."); + throw "Aborted"; + } + }, + started: (result, out, err) => { + return out.find((x: string) => { return x.search("Prompt will show") >= 0 }) != undefined || + err != undefined && err.find((x: string) => { + return x.search("already running") >= 0 + }); + }, + connect: async (session, name, checkFn) => { + let build = await util.quickPickSingle({ + values: await getFigwheelMainProjects(), + placeHolder: "Select which build to connect to", + saveAs: `${getProjectRoot()}/figwheel-main-build` + }); + if (build) { + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); + state.cursor.set('cljsBuild', build); + const initCode = `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl "${build}"))`; + return evalConnectCode(session, initCode, name, checkFn); + } else { + let chan = state.outputChannel(); + chan.appendLine("Connection aborted."); + throw "Aborted"; + } + }, + connected: (_result, out, _err) => { + return out.find((x: string) => { return x.search("To quit, type: :cljs/quit") >= 0 }) != undefined + } + }, + { + name: "Figwheel", + connect: async (session, name, checkFn) => { + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', false); + state.cursor.set('cljsBuild', null); + const initCode = "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))"; + return evalConnectCode(session, initCode, name, checkFn, + [(output) => { + let matched = output.match(/Figwheel: Starting server at (.*)/); + if (matched && matched.length > 1) { + let chan = state.outputChannel(); + chan.appendLine(matched[0]); + if (state.config().openBrowserWhenFigwheelStarted) { + chan.appendLine("Opening Figwheel app in the browser (this automatic behaviour can be disabled using Settings) ..."); + open(matched[1]).catch(reason => { + console.error("Error opening Figwheel app in browser: ", reason); + }); + } else { + chan.appendLine("Not automaticaly opening Figwheel app in the browser (this can be disabled using Settings)."); + } + chan.appendLine("The CLJS REPL session will be connected when the Figwheel app has been started in the browser."); + } + }]); + }, + connected: (_result, out, _err) => { + return out.find((x: string) => { return x.search("Prompt will show") >= 0 }) != undefined + } + }, + { + name: "shadow-cljs", + connect: async (session, name, checkFn) => { + let build = await util.quickPickSingle({ + values: await shadowBuilds(), + placeHolder: "Select which build to connect to", + saveAs: `${getProjectRoot()}/shadow-cljs-build` + }); + if (build) { + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); + state.cursor.set('cljsBuild', build); + const initCode = shadowCljsReplStart(build); + return evalConnectCode(session, initCode, name, checkFn); + } else { + let chan = state.outputChannel(); + chan.appendLine("Connection aborted."); + throw "Aborted"; + } + }, + connected: (result, _out, _err) => { + return result.search(/:selected/) >= 0; + } + } +]; + +type customCLJSREPLType = { + name: string, + startCode: string, + tellUserToStartRegExp?: string, + printThisLineRegExp?: string, + connectedRegExp: string, +}; + +function createCustomCLJSReplType(custom: customCLJSREPLType): ReplType { + return { + name: custom.name, + connect: (session, name, checkFn) => { + const chan = state.outputChannel(); + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', false); + state.cursor.set('cljsBuild', null); + const initCode = custom.startCode; + let outputProcessors: processOutputFn[] = []; + if (custom.tellUserToStartRegExp) { + outputProcessors.push((output) => { + if (custom.tellUserToStartRegExp) { + const matched = output.match(custom.tellUserToStartRegExp); + if (matched && matched.length > 0) { + chan.appendLine("CLJS REPL ready to connect. Please, start your ClojureScript app."); + chan.appendLine(" The CLJS REPL will connect when your app is running."); + } + } + }); + } + if (custom.printThisLineRegExp) { + outputProcessors.push((output) => { + if (custom.printThisLineRegExp) { + const matched = output.match(`.*(${custom.printThisLineRegExp}).*`); + if (matched && matched.length > 0) { + chan.appendLine(util.stripAnsi(matched[0])); + } + } + }); + } + return evalConnectCode(session, initCode, name, checkFn, outputProcessors); + }, + connected: (_result, out, err) => { + return out.find((x: string) => { return x.search(custom.connectedRegExp) >= 0 }) != undefined + } + } +} + +export function getCustomCLJSRepl(): ReplType { + const replConfig = state.config().customCljsRepl; + if (replConfig) { + return createCustomCLJSReplType(replConfig as customCLJSREPLType); + } else { + return undefined; + } +} + +function getCLJSReplTypes() { + let types = cljsReplTypes.slice(); + const customType = getCustomCLJSRepl(); + if (customType) { + types.push(customType); + } + return types; +} + +async function makeCljsSessionClone(session, replType, replTypes: ReplType[]) { + let chan = state.outputChannel(); + let repl: ReplType; + + chan.appendLine("Creating cljs repl session..."); + let newCljsSession = await session.clone(); + if (newCljsSession) { + chan.show(true); + state.extensionContext.workspaceState.update('cljsReplType', replType); + state.analytics().logEvent("REPL", "ConnectingCLJS", replType).send(); + repl = replTypes.find(x => x.name == replType); + if (repl.start != undefined) { + chan.appendLine("Starting repl for: " + repl.name + "..."); + if (await repl.start(newCljsSession, repl.name, repl.started)) { + state.analytics().logEvent("REPL", "StartedCLJS", repl.name).send(); + chan.appendLine("Started cljs builds"); + newCljsSession = await session.clone(); + } else { + state.analytics().logEvent("REPL", "FailedStartingCLJS", repl.name).send(); + chan.appendLine("Failed starting cljs repl"); + state.cursor.set('cljsBuild', null); + return [null, null]; + } + } + chan.appendLine("Connecting CLJS repl: " + repl.name + "..."); + chan.appendLine(" Compiling and stuff. This can take a minute or two."); + chan.appendLine(" See Calva Connection Log for detailed progress updates."); + if (await repl.connect(newCljsSession, repl.name, repl.connected)) { + state.analytics().logEvent("REPL", "ConnectedCLJS", repl.name).send(); + state.cursor.set('cljs', cljsSession = newCljsSession); + return [cljsSession, null]; + } else { + let build = state.deref().get('cljsBuild') + state.analytics().logEvent("REPL", "FailedConnectingCLJS", repl.name).send(); + let failed = "Failed starting cljs repl" + (build != null ? ` for build: ${build}` : ""); + chan.appendLine(`${failed}. Is the build running and connected?\n See the Output channel "Calva Connection Log" for any hints on what went wrong.`); + state.cursor.set('cljsBuild', null); + } + } + return [null, null]; +} + +async function promptForNreplUrlAndConnect(port, cljsTypeName, replTypes: ReplType[]) { + let current = state.deref(), + chan = state.outputChannel(); + + let url = await vscode.window.showInputBox({ + placeHolder: "Enter existing nREPL hostname:port here...", + prompt: "Add port to nREPL if localhost, otherwise 'hostname:port'", + value: "localhost:" + (port ? port : ""), + ignoreFocusOut: true + }) + // state.reset(); TODO see if this should be done + if (url !== undefined) { + let [hostname, port] = url.split(':'), + parsedPort = parseFloat(port); + if (parsedPort && parsedPort > 0 && parsedPort < 65536) { + state.cursor.set("hostname", hostname); + state.cursor.set("port", parsedPort); + await connectToHost(hostname, parsedPort, cljsTypeName, replTypes); + } else { + chan.appendLine("Bad url: " + url); + state.cursor.set('connecting', false); + status.update(); + } + } else { + state.cursor.set('connecting', false); + status.update(); + } + return true; +} + +export let nClient: NReplClient; +export let cljSession: NReplSession; +export let cljsSession: NReplSession; + +export function nreplPortFile(subPath: string): string { + try { + return path.resolve(getProjectRoot(), subPath); + } catch (e) { + console.log(e); + } + return subPath; +} + +export async function connect(isAutoConnect = false, isJackIn = false) { + let chan = state.outputChannel(); + let cljsTypeName: string; + + state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); + + const types = getCLJSReplTypes(); + const CLJS_PROJECT_TYPE_NONE = "Don't load any cljs support, thanks" + if (isJackIn) { + cljsTypeName = state.extensionContext.workspaceState.get('selectedCljsTypeName'); + } else { + try { + await initProjectDir(); + } catch { + return; + } + let typeNames = types.map(x => x.name); + typeNames.splice(0, 0, CLJS_PROJECT_TYPE_NONE) + cljsTypeName = await util.quickPickSingle({ + values: typeNames, + placeHolder: "If you want ClojureScript support, please select a cljs project type", saveAs: `${getProjectRoot()}/connect-cljs-type`, autoSelect: true + }); + if (!cljsTypeName) { + state.analytics().logEvent("REPL", "ConnectInterrupted", "NoCljsProjectPicked").send(); + return; + } + } + + state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); + + if (cljsTypeName == CLJS_PROJECT_TYPE_NONE) { + cljsTypeName = ""; + } + + const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? nreplPortFile(".shadow-cljs/nrepl.port") : nreplPortFile(".nrepl-port")); + + state.extensionContext.workspaceState.update('selectedCljsTypeName', cljsTypeName); + + if (fs.existsSync(portFile)) { + let port = fs.readFileSync(portFile, 'utf8'); + if (port) { + if (isAutoConnect) { + state.cursor.set("hostname", "localhost"); + state.cursor.set("port", port); + await connectToHost("localhost", port, cljsTypeName, types); + } else { + await promptForNreplUrlAndConnect(port, cljsTypeName, types); + } + } else { + chan.appendLine('No nrepl port file found. (Calva does not start the nrepl for you, yet.)'); + await promptForNreplUrlAndConnect(port, cljsTypeName, types); + } + } else { + await promptForNreplUrlAndConnect(null, cljsTypeName, types); + } + return true; +} + +export default { + connect: connect, + disconnect: function (options = null, callback = () => { }) { + ['clj', 'cljs'].forEach(sessionType => { + state.cursor.set(sessionType, null); + }); + state.cursor.set("connected", false); + state.cursor.set('cljc', null); + status.update(); + + nClient.close(); + callback(); + }, + nreplPortFile: nreplPortFile, + toggleCLJCSession: function () { + let current = state.deref(); + + if (current.get('connected')) { + if (util.getSession('cljc') == util.getSession('cljs')) { + state.cursor.set('cljc', util.getSession('clj')); + } else if (util.getSession('cljc') == util.getSession('clj')) { + state.cursor.set('cljc', util.getSession('cljs')); + } + status.update(); + } + }, + recreateCljsRepl: async function () { + let current = state.deref(), + cljSession = util.getSession('clj'), + chan = state.outputChannel(); + const cljsTypeName = state.extensionContext.workspaceState.get('selectedCljsTypeName'); + + let [session, shadowBuild] = await makeCljsSessionClone(cljSession, cljsTypeName, getCLJSReplTypes()); + if (session) + await setUpCljsRepl(session, chan, shadowBuild); + status.update(); + }, + getCustomCLJSRepl: getCustomCLJSRepl +}; From bdea802c04358662008378e9f7f57afbba04d561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 25 Aug 2019 12:33:09 +0200 Subject: [PATCH 052/128] This file shouldn't have been commited! --- calva/master-connector.ts | 581 -------------------------------------- 1 file changed, 581 deletions(-) delete mode 100644 calva/master-connector.ts diff --git a/calva/master-connector.ts b/calva/master-connector.ts deleted file mode 100644 index f3da49809..000000000 --- a/calva/master-connector.ts +++ /dev/null @@ -1,581 +0,0 @@ -import * as vscode from 'vscode'; -import * as _ from 'lodash'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as state from './state'; -import * as util from './utilities'; -import * as open from 'open'; -import status from './status'; - -const { parseEdn } = require('../cljs-out/cljs-lib'); -import { NReplClient, NReplSession } from "./nrepl"; -import { reconnectReplWindow, openReplWindow } from './repl-window'; - -const PROJECT_DIR_KEY = "connect.projectDir"; -const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; - -export function getProjectRoot(): string { - return state.deref().get(PROJECT_DIR_KEY); -} - -export function getProjectWsFolder(): vscode.WorkspaceFolder { - return state.deref().get(PROJECT_WS_FOLDER_KEY); -} - -/** - * Figures out, and stores, the current clojure project root - * Also stores the WorkSpace folder for the project to be used - * when executing the Task and get proper vscode reporting. - * - * 1. If there is no file open. Stop and complain. - * 2. If there is a file open, use it to determine the project root - * by looking for project files from the file's directory and up to - * the window root (for plain folder windows) or the file's - * workspace folder root (for workspaces) to find the project root. - * - * If there is no project file found, then store either of these - * 1. the window root for plain folders - * 2. first workspace root for workspaces. - * (This situation will be detected later by the connect process.) - */ -export async function initProjectDir(): Promise { - const projectFileNames: string[] = ["project.clj", "shadow-cljs.edn", "deps.edn"], - doc = util.getDocument({}), - workspaceFolder = doc ? vscode.workspace.getWorkspaceFolder(doc.uri) : null; - if (!workspaceFolder) { - vscode.window.showErrorMessage("There is no document opened in the workspace. Aborting. Please open a file in your Clojure project and try again."); - state.analytics().logEvent("REPL", "JackinOrConnectInterrupted", "NoCurrentDocument").send(); - throw "There is no document opened in the workspace. Aborting."; - } else { - state.cursor.set(PROJECT_WS_FOLDER_KEY, workspaceFolder); - let rootPath: string = path.resolve(workspaceFolder.uri.fsPath); - let d = path.dirname(doc.uri.fsPath); - let prev = null; - while (d != prev) { - for (let projectFile in projectFileNames) { - const p = path.resolve(d, projectFileNames[projectFile]); - if (fs.existsSync(p)) { - rootPath = d; - break; - } - } - if (d == rootPath) { - break; - } - prev = d; - d = path.resolve(d, ".."); - } - state.cursor.set(PROJECT_DIR_KEY, rootPath); - } -} - -export type ProjectType = { - name: string; - cljsTypes: string[]; - cmd: string; - winCmd: string; - commandLine: (includeCljs: boolean) => any; - useWhenExists: string; - nReplPortFile: () => string; -}; - -async function connectToHost(hostname, port, cljsTypeName: string, replTypes: ReplType[]) { - state.analytics().logEvent("REPL", "Connecting").send(); - - let chan = state.outputChannel(); - if (nClient) { - nClient["silent"] = true; - nClient.close(); - } - cljsSession = cljSession = null; - state.cursor.set('connecting', true); - status.update(); - try { - chan.appendLine("Hooking up nREPL sessions..."); - // Create an nREPL client. waiting for the connection to be established. - nClient = await NReplClient.create({ host: hostname, port: +port }) - nClient.addOnCloseHandler(c => { - state.cursor.set("connected", false); - state.cursor.set("connecting", false); - if (!c["silent"]) // we didn't deliberately close this session, mention this fact. - chan.appendLine("nREPL Connection was closed"); - status.update(); - }) - cljSession = nClient.session; - chan.appendLine("Connected session: clj"); - await openReplWindow("clj", true); - await reconnectReplWindow("clj").catch(reason => { - console.error("Failed reconnecting REPL window: ", reason); - }); - - state.cursor.set("connected", true); - state.analytics().logEvent("REPL", "ConnectedCLJ").send(); - state.cursor.set("connecting", false); - state.cursor.set('clj', cljSession) - state.cursor.set('cljc', cljSession) - status.update(); - - //cljsSession = nClient.session; - //terminal.createREPLTerminal('clj', null, chan); - let cljsSession = null, - shadowBuild = null; - try { - [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, replTypes) : [null, null]; - } catch (e) { - chan.appendLine("Error while connecting cljs REPL: " + e); - } - if (cljsSession) - await setUpCljsRepl(cljsSession, chan, shadowBuild); - chan.appendLine('cljc files will use the clj REPL.' + (cljsSession ? ' (You can toggle this at will.)' : '')); - //evaluate.loadFile(); - status.update(); - - } catch (e) { - state.cursor.set("connected", false); - state.cursor.set("connecting", false); - chan.appendLine("Failed connecting."); - state.analytics().logEvent("REPL", "FailedConnectingCLJ").send(); - return false; - } - - return true; -} - -async function setUpCljsRepl(cljsSession, chan, shadowBuild) { - state.cursor.set("cljs", cljsSession); - chan.appendLine("Connected session: cljs"); - await openReplWindow("cljs", true); - await reconnectReplWindow("cljs"); - //terminal.createREPLTerminal('cljs', shadowBuild, chan); - status.update(); -} - -export function shadowConfigFile() { - return getProjectRoot() + '/shadow-cljs.edn'; -} - -export function shadowBuilds() { - let parsed = parseEdn(fs.readFileSync(shadowConfigFile(), 'utf8').toString()), - builds = _.map(parsed.builds, (_v, key) => { return ":" + key }); - builds.push("node-repl"); - builds.push("browser-repl") - return builds; -} - -export function shadowBuild() { - return state.deref().get('cljsBuild'); -} - - -function shadowCljsReplStart(buildOrRepl: string) { - if (!buildOrRepl) - return null; - if (buildOrRepl.charAt(0) == ":") - return `(shadow.cljs.devtools.api/nrepl-select ${buildOrRepl})` - else - return `(shadow.cljs.devtools.api/${buildOrRepl})` -} - -function getFigwheelMainProjects() { - let chan = state.outputChannel(); - let res = fs.readdirSync(getProjectRoot()); - let projects = res.filter(x => x.match(/\.cljs\.edn/)).map(x => x.replace(/\.cljs\.edn$/, "")); - if (projects.length == 0) { - vscode.window.showErrorMessage("There are no figwheel project files (.cljs.edn) in the project directory."); - chan.appendLine("There are no figwheel project files (.cljs.edn) in the project directory."); - chan.appendLine("Connection to Figwheel Main aborted."); - throw "Aborted"; - } - return projects; -} - -type checkConnectedFn = (value: string, out: any[], err: any[]) => boolean; -type processOutputFn = (output: string) => void; -type connectFn = (session: NReplSession, name: string, checkSuccess: checkConnectedFn) => Promise; - -async function evalConnectCode(newCljsSession: NReplSession, code: string, - name: string, checkSuccess: checkConnectedFn, outputProcessors?: processOutputFn[]): Promise { - let chan = state.connectionLogChannel(); - let err = [], out = [], result = await newCljsSession.eval(code, { - stdout: x => { - if (outputProcessors) { - for (const p of outputProcessors) { - p(x); - } - } - out.push(util.stripAnsi(x)); - chan.append(util.stripAnsi(x)); - }, stderr: x => { - err.push(util.stripAnsi(x)); - chan.append(util.stripAnsi(x)); - } - }); - let valueResult = await result.value - .catch(reason => { - console.error("Error evaluating connect form: ", reason); - }); - if (checkSuccess(valueResult, out, err)) { - state.analytics().logEvent("REPL", "ConnectedCLJS", name).send(); - state.cursor.set('cljs', cljsSession = newCljsSession) - return true - } else { - return false; - } -} - -export interface ReplType { - name: string, - start?: connectFn; - started?: (valueResult: string, out: any[], err: any[]) => boolean; - connect?: connectFn; - connected: (valueResult: string, out: any[], err: any[]) => boolean; -} - -export let cljsReplTypes: ReplType[] = [ - { - name: "Figwheel Main", - start: async (session, name, checkFn) => { - let projects = await getFigwheelMainProjects(); - let builds = projects.length <= 1 ? projects : await util.quickPickMulti({ - values: projects, - placeHolder: "Please select which builds to start", - saveAs: `${getProjectRoot()}/figwheel-main-projects` - }) - if (builds) { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - state.cursor.set('cljsBuild', builds[0]); - const initCode = `(do (require 'figwheel.main.api) (figwheel.main.api/start ${builds.map(x => { return `"${x}"` }).join(" ")}))`; - return evalConnectCode(session, initCode, name, checkFn); - } - else { - let chan = state.outputChannel(); - chan.appendLine("Connection to Figwheel Main aborted."); - throw "Aborted"; - } - }, - started: (result, out, err) => { - return out.find((x: string) => { return x.search("Prompt will show") >= 0 }) != undefined || - err != undefined && err.find((x: string) => { - return x.search("already running") >= 0 - }); - }, - connect: async (session, name, checkFn) => { - let build = await util.quickPickSingle({ - values: await getFigwheelMainProjects(), - placeHolder: "Select which build to connect to", - saveAs: `${getProjectRoot()}/figwheel-main-build` - }); - if (build) { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - state.cursor.set('cljsBuild', build); - const initCode = `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl "${build}"))`; - return evalConnectCode(session, initCode, name, checkFn); - } else { - let chan = state.outputChannel(); - chan.appendLine("Connection aborted."); - throw "Aborted"; - } - }, - connected: (_result, out, _err) => { - return out.find((x: string) => { return x.search("To quit, type: :cljs/quit") >= 0 }) != undefined - } - }, - { - name: "Figwheel", - connect: async (session, name, checkFn) => { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', false); - state.cursor.set('cljsBuild', null); - const initCode = "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))"; - return evalConnectCode(session, initCode, name, checkFn, - [(output) => { - let matched = output.match(/Figwheel: Starting server at (.*)/); - if (matched && matched.length > 1) { - let chan = state.outputChannel(); - chan.appendLine(matched[0]); - if (state.config().openBrowserWhenFigwheelStarted) { - chan.appendLine("Opening Figwheel app in the browser (this automatic behaviour can be disabled using Settings) ..."); - open(matched[1]).catch(reason => { - console.error("Error opening Figwheel app in browser: ", reason); - }); - } else { - chan.appendLine("Not automaticaly opening Figwheel app in the browser (this can be disabled using Settings)."); - } - chan.appendLine("The CLJS REPL session will be connected when the Figwheel app has been started in the browser."); - } - }]); - }, - connected: (_result, out, _err) => { - return out.find((x: string) => { return x.search("Prompt will show") >= 0 }) != undefined - } - }, - { - name: "shadow-cljs", - connect: async (session, name, checkFn) => { - let build = await util.quickPickSingle({ - values: await shadowBuilds(), - placeHolder: "Select which build to connect to", - saveAs: `${getProjectRoot()}/shadow-cljs-build` - }); - if (build) { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - state.cursor.set('cljsBuild', build); - const initCode = shadowCljsReplStart(build); - return evalConnectCode(session, initCode, name, checkFn); - } else { - let chan = state.outputChannel(); - chan.appendLine("Connection aborted."); - throw "Aborted"; - } - }, - connected: (result, _out, _err) => { - return result.search(/:selected/) >= 0; - } - } -]; - -type customCLJSREPLType = { - name: string, - startCode: string, - tellUserToStartRegExp?: string, - printThisLineRegExp?: string, - connectedRegExp: string, -}; - -function createCustomCLJSReplType(custom: customCLJSREPLType): ReplType { - return { - name: custom.name, - connect: (session, name, checkFn) => { - const chan = state.outputChannel(); - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', false); - state.cursor.set('cljsBuild', null); - const initCode = custom.startCode; - let outputProcessors: processOutputFn[] = []; - if (custom.tellUserToStartRegExp) { - outputProcessors.push((output) => { - if (custom.tellUserToStartRegExp) { - const matched = output.match(custom.tellUserToStartRegExp); - if (matched && matched.length > 0) { - chan.appendLine("CLJS REPL ready to connect. Please, start your ClojureScript app."); - chan.appendLine(" The CLJS REPL will connect when your app is running."); - } - } - }); - } - if (custom.printThisLineRegExp) { - outputProcessors.push((output) => { - if (custom.printThisLineRegExp) { - const matched = output.match(`.*(${custom.printThisLineRegExp}).*`); - if (matched && matched.length > 0) { - chan.appendLine(util.stripAnsi(matched[0])); - } - } - }); - } - return evalConnectCode(session, initCode, name, checkFn, outputProcessors); - }, - connected: (_result, out, err) => { - return out.find((x: string) => { return x.search(custom.connectedRegExp) >= 0 }) != undefined - } - } -} - -export function getCustomCLJSRepl(): ReplType { - const replConfig = state.config().customCljsRepl; - if (replConfig) { - return createCustomCLJSReplType(replConfig as customCLJSREPLType); - } else { - return undefined; - } -} - -function getCLJSReplTypes() { - let types = cljsReplTypes.slice(); - const customType = getCustomCLJSRepl(); - if (customType) { - types.push(customType); - } - return types; -} - -async function makeCljsSessionClone(session, replType, replTypes: ReplType[]) { - let chan = state.outputChannel(); - let repl: ReplType; - - chan.appendLine("Creating cljs repl session..."); - let newCljsSession = await session.clone(); - if (newCljsSession) { - chan.show(true); - state.extensionContext.workspaceState.update('cljsReplType', replType); - state.analytics().logEvent("REPL", "ConnectingCLJS", replType).send(); - repl = replTypes.find(x => x.name == replType); - if (repl.start != undefined) { - chan.appendLine("Starting repl for: " + repl.name + "..."); - if (await repl.start(newCljsSession, repl.name, repl.started)) { - state.analytics().logEvent("REPL", "StartedCLJS", repl.name).send(); - chan.appendLine("Started cljs builds"); - newCljsSession = await session.clone(); - } else { - state.analytics().logEvent("REPL", "FailedStartingCLJS", repl.name).send(); - chan.appendLine("Failed starting cljs repl"); - state.cursor.set('cljsBuild', null); - return [null, null]; - } - } - chan.appendLine("Connecting CLJS repl: " + repl.name + "..."); - chan.appendLine(" Compiling and stuff. This can take a minute or two."); - chan.appendLine(" See Calva Connection Log for detailed progress updates."); - if (await repl.connect(newCljsSession, repl.name, repl.connected)) { - state.analytics().logEvent("REPL", "ConnectedCLJS", repl.name).send(); - state.cursor.set('cljs', cljsSession = newCljsSession); - return [cljsSession, null]; - } else { - let build = state.deref().get('cljsBuild') - state.analytics().logEvent("REPL", "FailedConnectingCLJS", repl.name).send(); - let failed = "Failed starting cljs repl" + (build != null ? ` for build: ${build}` : ""); - chan.appendLine(`${failed}. Is the build running and connected?\n See the Output channel "Calva Connection Log" for any hints on what went wrong.`); - state.cursor.set('cljsBuild', null); - } - } - return [null, null]; -} - -async function promptForNreplUrlAndConnect(port, cljsTypeName, replTypes: ReplType[]) { - let current = state.deref(), - chan = state.outputChannel(); - - let url = await vscode.window.showInputBox({ - placeHolder: "Enter existing nREPL hostname:port here...", - prompt: "Add port to nREPL if localhost, otherwise 'hostname:port'", - value: "localhost:" + (port ? port : ""), - ignoreFocusOut: true - }) - // state.reset(); TODO see if this should be done - if (url !== undefined) { - let [hostname, port] = url.split(':'), - parsedPort = parseFloat(port); - if (parsedPort && parsedPort > 0 && parsedPort < 65536) { - state.cursor.set("hostname", hostname); - state.cursor.set("port", parsedPort); - await connectToHost(hostname, parsedPort, cljsTypeName, replTypes); - } else { - chan.appendLine("Bad url: " + url); - state.cursor.set('connecting', false); - status.update(); - } - } else { - state.cursor.set('connecting', false); - status.update(); - } - return true; -} - -export let nClient: NReplClient; -export let cljSession: NReplSession; -export let cljsSession: NReplSession; - -export function nreplPortFile(subPath: string): string { - try { - return path.resolve(getProjectRoot(), subPath); - } catch (e) { - console.log(e); - } - return subPath; -} - -export async function connect(isAutoConnect = false, isJackIn = false) { - let chan = state.outputChannel(); - let cljsTypeName: string; - - state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); - - const types = getCLJSReplTypes(); - const CLJS_PROJECT_TYPE_NONE = "Don't load any cljs support, thanks" - if (isJackIn) { - cljsTypeName = state.extensionContext.workspaceState.get('selectedCljsTypeName'); - } else { - try { - await initProjectDir(); - } catch { - return; - } - let typeNames = types.map(x => x.name); - typeNames.splice(0, 0, CLJS_PROJECT_TYPE_NONE) - cljsTypeName = await util.quickPickSingle({ - values: typeNames, - placeHolder: "If you want ClojureScript support, please select a cljs project type", saveAs: `${getProjectRoot()}/connect-cljs-type`, autoSelect: true - }); - if (!cljsTypeName) { - state.analytics().logEvent("REPL", "ConnectInterrupted", "NoCljsProjectPicked").send(); - return; - } - } - - state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); - - if (cljsTypeName == CLJS_PROJECT_TYPE_NONE) { - cljsTypeName = ""; - } - - const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? nreplPortFile(".shadow-cljs/nrepl.port") : nreplPortFile(".nrepl-port")); - - state.extensionContext.workspaceState.update('selectedCljsTypeName', cljsTypeName); - - if (fs.existsSync(portFile)) { - let port = fs.readFileSync(portFile, 'utf8'); - if (port) { - if (isAutoConnect) { - state.cursor.set("hostname", "localhost"); - state.cursor.set("port", port); - await connectToHost("localhost", port, cljsTypeName, types); - } else { - await promptForNreplUrlAndConnect(port, cljsTypeName, types); - } - } else { - chan.appendLine('No nrepl port file found. (Calva does not start the nrepl for you, yet.)'); - await promptForNreplUrlAndConnect(port, cljsTypeName, types); - } - } else { - await promptForNreplUrlAndConnect(null, cljsTypeName, types); - } - return true; -} - -export default { - connect: connect, - disconnect: function (options = null, callback = () => { }) { - ['clj', 'cljs'].forEach(sessionType => { - state.cursor.set(sessionType, null); - }); - state.cursor.set("connected", false); - state.cursor.set('cljc', null); - status.update(); - - nClient.close(); - callback(); - }, - nreplPortFile: nreplPortFile, - toggleCLJCSession: function () { - let current = state.deref(); - - if (current.get('connected')) { - if (util.getSession('cljc') == util.getSession('cljs')) { - state.cursor.set('cljc', util.getSession('clj')); - } else if (util.getSession('cljc') == util.getSession('clj')) { - state.cursor.set('cljc', util.getSession('cljs')); - } - status.update(); - } - }, - recreateCljsRepl: async function () { - let current = state.deref(), - cljSession = util.getSession('clj'), - chan = state.outputChannel(); - const cljsTypeName = state.extensionContext.workspaceState.get('selectedCljsTypeName'); - - let [session, shadowBuild] = await makeCljsSessionClone(cljSession, cljsTypeName, getCLJSReplTypes()); - if (session) - await setUpCljsRepl(session, chan, shadowBuild); - status.update(); - }, - getCustomCLJSRepl: getCustomCLJSRepl -}; From 6d45763c20937f2369c6150b34a6fea2096b8a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 25 Aug 2019 12:43:25 +0200 Subject: [PATCH 053/128] When ready to connect, instruct user to start app --- calva/connector.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index dc9de3af6..c59eabf31 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -271,6 +271,14 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { } } }, + startAppNowProcessor: processOutputFn = x => { + if (cljsType.isStartedRegExp) { + if (x.search(cljsType.isStartedRegExp) >= 0) { + chan.appendLine("CLJS REPL ready to connect. Please, start your ClojureScript app."); + chan.appendLine(" The CLJS REPL will connect when your app is running."); + } + } + }, allPrinter: processOutputFn = x => { chan.appendLine(util.stripAnsi(x).replace(/\s*$/, "")); } @@ -307,7 +315,7 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { state.cursor.set('cljsBuild', null); - return evalConnectCode(session, initCode, name, checkFn, [printThisPrinter], [allPrinter]); + return evalConnectCode(session, initCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); }, connected: (replType, out, err) => { if (cljsType.isConnectedRegExp) { @@ -346,7 +354,7 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { throw "Aborted"; } } else { - return evalConnectCode(session, startCode, name, checkFn, [printThisPrinter], [allPrinter]); + return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); } }; } From 5f0eec5ebb874db86c3b4978e6a93c124c84d69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 25 Aug 2019 18:55:07 +0200 Subject: [PATCH 054/128] Add help for starting CLJS app --- calva/connector.ts | 41 ++++++++++++++++++++++++---------- calva/nrepl/connectSequence.ts | 21 ++++++++++++----- calva/state.ts | 2 +- package.json | 14 +++++++++--- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index c59eabf31..4629572a7 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -206,17 +206,17 @@ async function evalConnectCode(newCljsSession: NReplSession, code: string, let chan = state.connectionLogChannel(); let err = [], out = [], result = await newCljsSession.eval(code, { stdout: x => { - for (const p of outputProcessors) { - p(x); - } out.push(util.stripAnsi(x)); chan.append(util.stripAnsi(x)); - }, stderr: x => { - for (const p of errorProcessors) { - p(x); + for (const p of outputProcessors) { + p(util.stripAnsi(x)); } + }, stderr: x => { err.push(util.stripAnsi(x)); chan.append(util.stripAnsi(x)); + for (const p of errorProcessors) { + p(util.stripAnsi(x)); + } } }); let valueResult = await result.value @@ -262,19 +262,36 @@ function updateInitCode(build: string, initCode): string { } function createCLJSReplType(cljsType: CustomCljsType): ReplType { + let appURL: string, const cljsTypeName = cljsType.name, chan = state.outputChannel(), printThisPrinter: processOutputFn = x => { if (cljsType.printThisLineRegExp) { if (x.search(cljsType.printThisLineRegExp) >= 0) { - chan.appendLine(util.stripAnsi(x).replace(/\s*$/, "")); + chan.appendLine(x.replace(/\s*$/, "")); } } }, startAppNowProcessor: processOutputFn = x => { - if (cljsType.isStartedRegExp) { - if (x.search(cljsType.isStartedRegExp) >= 0) { + if (cljsType.openUrlRegExp) { + let matched = util.stripAnsi(x).match(cljsType.openUrlRegExp); + if (matched && matched.length > 1) { + appURL = matched[1]; + } + } + if (cljsType.isReadyToStartRegExp) { + if (x.search(cljsType.isReadyToStartRegExp) >= 0) { chan.appendLine("CLJS REPL ready to connect. Please, start your ClojureScript app."); + if (appURL) { + if (cljsType.shouldOpenURL) { + chan.appendLine(` Opening ClojureScript app in the browser at: ${appURL} ...`); + open(appURL).catch(reason => { + chan.appendLine("Error opening ClojureScript app in the browser: " + reason); + }); + } else { + chan.appendLine(" Open the app on this URL: " + appURL); + } + } chan.appendLine(" The CLJS REPL will connect when your app is running."); } } @@ -321,7 +338,7 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { if (cljsType.isConnectedRegExp) { return out.find(x => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined; // return (replType != undefined && (replType.search(cljsType.isConnectedRegExp) >= 0)) || - // (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); + // (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); } else { return true; } @@ -359,9 +376,9 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { }; } - if (cljsType.isStartedRegExp) { + if (cljsType.isReadyToStartRegExp) { replType.started = (result, out, err) => { - return [...out, ...err].find(x => { return x.search(cljsType.isStartedRegExp) >= 0 }) != undefined; + return [...out, ...err].find(x => { return x.search(cljsType.isReadyToStartRegExp) >= 0 }) != undefined; } } diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 14629961c..c3c028380 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -1,4 +1,5 @@ import { workspace , window } from "vscode"; +import { config } from "../state"; enum ProjectTypes { "Leiningen" = "Leiningen", @@ -16,10 +17,12 @@ interface CustomCljsType { name: string, startCode?: string, builds?: string[], - isStartedRegExp?: string, + isReadyToStartRegExp?: string | RegExp, + openUrlRegExp?: string | RegExp, + shouldOpenURL?: boolean, connectCode: string | Object, - isConnectedRegExp?: string, - printThisLineRegExp?: string + isConnectedRegExp?: string | RegExp, + printThisLineRegExp?: string | RegExp } interface ReplConnectSequence { @@ -73,19 +76,24 @@ const defaultSequences = { "shadow-cljs": shadowCljsDefaults }; -const defaultCljsTypes = { +const defaultCljsTypes: { [id: string]: CustomCljsType } = { "Figwheel Main": { name: "Figwheel Main", startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, builds: [], - isStartedRegExp: /Prompt will show|already running/, + isReadyToStartRegExp: /Starting Server at|already running/, + openUrlRegExp: /Starting Server at (\S+)/, + shouldOpenURL: false, connectCode: `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl %BUILD%))`, isConnectedRegExp: /To quit, type: :cljs\/quit/ }, "lein-figwheel": { name: "lein-figwheel", + isReadyToStartRegExp: /Launching ClojureScript REPL for build/, + openUrlRegExp: /Figwheel: Starting server at (\S+)/, + shouldOpenURL: config().openBrowserWhenFigwheelStarted, connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", - isConnectedRegExp: /Prompt will show/ + isConnectedRegExp: /To quit, type: :cljs\/quit/ }, "shadow-cljs": { name: "shadow-cljs", @@ -93,6 +101,7 @@ const defaultCljsTypes = { build: `(shadow.cljs.devtools.api/nrepl-select %BUILD%)`, repl: `(shadow.cljs.devtools.api/%REPL%)` }, + shouldOpenURL: false, builds: [], isConnectedRegExp: /:selected/ } diff --git a/calva/state.ts b/calva/state.ts index 158de8f50..a0c3814f0 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -85,7 +85,7 @@ function config() { useWSL: configOptions.get("useWSL"), syncReplNamespaceToCurrentFile: configOptions.get("syncReplNamespaceToCurrentFile"), jackInEnv: configOptions.get("jackInEnv"), - openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted"), + openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted") as boolean, customCljsRepl: configOptions.get("customCljsRepl", null) }; } diff --git a/package.json b/package.json index d5864bbb5..1816d72bb 100644 --- a/package.json +++ b/package.json @@ -267,9 +267,17 @@ "type": "array", "description": "List of all builds that should be started." }, - "isStartedRegExp": { + "isReadyToStartRegExp": { "type": "string", - "description": "A regular experession which, when matched in the stdout from the startCode evaluation, will make Calva continue with connecting the REPL, and to prompt the user to start the application. If omitted and there is startCode Calva will continue when that code is evaluated. Special case: _If the regexp has a group, it will be assumed to match the URL where the application is started and Calva will automatically open it in order to connect to it._" + "description": "A regular experession which, when matched in the stdout from the startCode evaluation, will make Calva continue with connecting the REPL, and to prompt the user to start the application. If omitted and there is startCode Calva will continue when that code is evaluated." + }, + "openUrlRegExp": { + "type": "string", + "description": "A regular expression, matched against the stdout of cljsType evaluations, for extracting the URL with which the app can be started. The expression should have one, and only one, group for extracting the URL. E.g. \\”Open URL: (.+)$\\”" + }, + "shouldOpenUrl": { + "type": "boolean", + "description": "Choose if Calva should automatically open the URL for you or not." }, "connectCode": { "type": "string", @@ -277,7 +285,7 @@ }, "isConnectedRegExp": { "type": "string", - "description": "A regular experession which, when matched in the stdout from the connectCode evaluation, will tell Calva that the application is connected.", + "description": "A regular experession which, when matched in the stdout of the connectCode evaluation, will tell Calva that the application is connected.", "default": "To quit, type: :cljs/quit" }, "printThisLineRegExp": { From 69f62dbbebc573b7e0453e0564828634aeecd83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 25 Aug 2019 19:38:57 +0200 Subject: [PATCH 055/128] Search for connectedRegExp in result as well Fixes shadow-cljs connection failure --- calva/connector.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 4629572a7..5ff136f10 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -334,9 +334,9 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { return evalConnectCode(session, initCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); }, - connected: (replType, out, err) => { + connected: (result, out, err) => { if (cljsType.isConnectedRegExp) { - return out.find(x => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined; + return [...out, result].find(x => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined; // return (replType != undefined && (replType.search(cljsType.isConnectedRegExp) >= 0)) || // (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); } else { From ef2a0bcd7e2097370470d641d1116fac74232c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 25 Aug 2019 22:07:26 +0200 Subject: [PATCH 056/128] When no is-started-regexp return true after eval --- calva/connector.ts | 10 +++++----- calva/nrepl/connectSequence.ts | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 5ff136f10..19ff3d7c9 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -262,7 +262,7 @@ function updateInitCode(build: string, initCode): string { } function createCLJSReplType(cljsType: CustomCljsType): ReplType { - let appURL: string, + let appURL: string; const cljsTypeName = cljsType.name, chan = state.outputChannel(), printThisPrinter: processOutputFn = x => { @@ -337,8 +337,6 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { connected: (result, out, err) => { if (cljsType.isConnectedRegExp) { return [...out, result].find(x => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined; - // return (replType != undefined && (replType.search(cljsType.isConnectedRegExp) >= 0)) || - // (out != undefined && out.find((x: string) => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined); } else { return true; } @@ -376,9 +374,11 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { }; } - if (cljsType.isReadyToStartRegExp) { - replType.started = (result, out, err) => { + replType.started = (result, out, err) => { + if (cljsType.isReadyToStartRegExp) { return [...out, ...err].find(x => { return x.search(cljsType.isReadyToStartRegExp) >= 0 }) != undefined; + } else { + return true; } } diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index c3c028380..8430ab857 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -97,6 +97,7 @@ const defaultCljsTypes: { [id: string]: CustomCljsType } = { }, "shadow-cljs": { name: "shadow-cljs", + isReadyToStartRegExp: /To quit, type: :cljs\/quit/, connectCode: { build: `(shadow.cljs.devtools.api/nrepl-select %BUILD%)`, repl: `(shadow.cljs.devtools.api/%REPL%)` From 2c0c5767c24b15435d438d682da7a70ac86d2432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 25 Aug 2019 23:01:00 +0200 Subject: [PATCH 057/128] Use outputProcessors also for BUILDS case --- calva/connector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/connector.ts b/calva/connector.ts index 19ff3d7c9..c543949d4 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -363,7 +363,7 @@ function createCLJSReplType(cljsType: CustomCljsType): ReplType { state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); state.cursor.set('cljsBuild', builds[0]); startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); - return evalConnectCode(session, startCode, name, checkFn); + return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); } else { chan.appendLine("Starting REPL for " + cljsTypeName + " aborted."); throw "Aborted"; From af0daf20c928bd1b10ea3c437c9b4bf5c29b4705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 26 Aug 2019 08:11:26 +0200 Subject: [PATCH 058/128] Rename some symbols and read config from `state` --- calva/connector.ts | 8 ++++---- calva/nrepl/connectSequence.ts | 25 ++++++++++++------------- calva/state.ts | 4 +++- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index c543949d4..837130f5d 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -10,7 +10,7 @@ import status from './status'; const { parseEdn } = require('../cljs-out/cljs-lib'); import { NReplClient, NReplSession } from "./nrepl"; import { reconnectReplWindow, openReplWindow, sendTextToREPLWindow } from './repl-window'; -import { CustomCljsType, ReplConnectSequence, getDefaultCljsType } from './nrepl/connectSequence'; +import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType } from './nrepl/connectSequence'; const PROJECT_DIR_KEY = "connect.projectDir"; const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; @@ -127,7 +127,7 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen shadowBuild = null; try { if (connectSequence.cljsType != undefined) { - let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + let cljsType: CljsTypeConfig = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; let translatedReplType = createCLJSReplType(cljsType); [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType) : [null, null]; @@ -261,7 +261,7 @@ function updateInitCode(build: string, initCode): string { return null; } -function createCLJSReplType(cljsType: CustomCljsType): ReplType { +function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { let appURL: string; const cljsTypeName = cljsType.name, chan = state.outputChannel(), @@ -542,7 +542,7 @@ export default { chan = state.outputChannel(); const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'); const connectSequence: ReplConnectSequence = state.extensionContext.workspaceState.get('selectedConnectSequence'); - let cljsType: CustomCljsType = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + let cljsType: CljsTypeConfig = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; let translatedReplType = createCLJSReplType(cljsType); let [session, shadowBuild] = await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 8430ab857..70e2e9eb8 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -13,7 +13,7 @@ enum CljsTypes { "shadow-cljs" = "shadow-cljs" } -interface CustomCljsType { +interface CljsTypeConfig { name: string, startCode?: string, builds?: string[], @@ -29,7 +29,7 @@ interface ReplConnectSequence { name: string, projectType: ProjectTypes, afterCLJReplJackInCode?: string, - cljsType?: CljsTypes | CustomCljsType + cljsType?: CljsTypes | CljsTypeConfig } const leiningenDefaults: ReplConnectSequence[] = @@ -76,7 +76,7 @@ const defaultSequences = { "shadow-cljs": shadowCljsDefaults }; -const defaultCljsTypes: { [id: string]: CustomCljsType } = { +const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { "Figwheel Main": { name: "Figwheel Main", startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, @@ -109,13 +109,12 @@ const defaultCljsTypes: { [id: string]: CustomCljsType } = { }; /** Retrieve the replConnectSequences from the config */ -function getConfigCustomConnectSequences(): ReplConnectSequence[] { - let result: ReplConnectSequence[] = workspace.getConfiguration('calva') - .get("replConnectSequences", []); +function getCustomConnectSequences(): ReplConnectSequence[] { + let sequences = config().replConnectSequences; - for (let conSeq of result) { - if (conSeq.name == undefined || - conSeq.projectType == undefined) { + for (let sequence of sequences) { + if (sequence.name == undefined || + sequence.projectType == undefined) { window.showWarningMessage("Check your calva.replConnectSequences. "+ "You need to supply name and projectType for every sequence. " + @@ -125,7 +124,7 @@ function getConfigCustomConnectSequences(): ReplConnectSequence[] { } } - return result; + return sequences; } /** @@ -134,7 +133,7 @@ function getConfigCustomConnectSequences(): ReplConnectSequence[] { * @param projectType what default Sequences would be used (leiningen, clj, shadow-cljs) */ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { - let customSequences = getConfigCustomConnectSequences(); + let customSequences = getCustomConnectSequences(); let result = []; for (let pType of projectTypes) { @@ -149,7 +148,7 @@ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { * Returns the CLJS-Type description of one of the build-in. * @param cljsType Build-in cljsType */ -function getDefaultCljsType(cljsType: string): CustomCljsType { +function getDefaultCljsType(cljsType: string): CljsTypeConfig { return defaultCljsTypes[cljsType]; } @@ -157,5 +156,5 @@ export { getConnectSequences, getDefaultCljsType, ReplConnectSequence, - CustomCljsType + CljsTypeConfig } \ No newline at end of file diff --git a/calva/state.ts b/calva/state.ts index a0c3814f0..8c4edaa2c 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import * as Immutable from 'immutable'; import * as ImmutableCursor from 'immutable-cursor'; import Analytics from './analytics'; +import { ReplConnectSequence } from './nrepl/connectSequence' let extensionContext: vscode.ExtensionContext; export function setExtensionContext(context: vscode.ExtensionContext) { @@ -86,7 +87,8 @@ function config() { syncReplNamespaceToCurrentFile: configOptions.get("syncReplNamespaceToCurrentFile"), jackInEnv: configOptions.get("jackInEnv"), openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted") as boolean, - customCljsRepl: configOptions.get("customCljsRepl", null) + customCljsRepl: configOptions.get("customCljsRepl", null), + replConnectSequences: configOptions.get("replConnectSequences") as ReplConnectSequence[] }; } From 99e3a6206ae739f7b68df1c42a9f8268d3be7b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 26 Aug 2019 08:22:57 +0200 Subject: [PATCH 059/128] Read figwheel openURL config at connect time --- calva/nrepl/connectSequence.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 70e2e9eb8..e8aeda4e9 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -149,6 +149,8 @@ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { * @param cljsType Build-in cljsType */ function getDefaultCljsType(cljsType: string): CljsTypeConfig { + // TODO: Find a less hacky way to get dynamic config for lein-figwheel + defaultCljsTypes["lein-figwheel"].shouldOpenURL = config().openBrowserWhenFigwheelStarted; return defaultCljsTypes[cljsType]; } From b15ee7775370e65ed012f0f9f5e0b4783c0f3ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 26 Aug 2019 09:06:07 +0200 Subject: [PATCH 060/128] Make room for breakpoints :smile: --- calva/connector.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 837130f5d..477d33e39 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -336,7 +336,9 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { }, connected: (result, out, err) => { if (cljsType.isConnectedRegExp) { - return [...out, result].find(x => { return x.search(cljsType.isConnectedRegExp) >= 0 }) != undefined; + return [...out, result].find(x => { + return x.search(cljsType.isConnectedRegExp) >= 0 + }) != undefined; } else { return true; } @@ -376,7 +378,9 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { replType.started = (result, out, err) => { if (cljsType.isReadyToStartRegExp) { - return [...out, ...err].find(x => { return x.search(cljsType.isReadyToStartRegExp) >= 0 }) != undefined; + return [...out, ...err].find(x => { + return x.search(cljsType.isReadyToStartRegExp) >= 0 + }) != undefined; } else { return true; } From 2ee901cc07c36edf32258a5e11c41fb6361de209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 26 Aug 2019 11:09:30 +0200 Subject: [PATCH 061/128] Changed openURL extraction to named group --- calva/connector.ts | 10 ++++++---- calva/nrepl/connectSequence.ts | 6 +++--- package.json | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 477d33e39..8fb12a6a6 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -262,7 +262,8 @@ function updateInitCode(build: string, initCode): string { } function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { - let appURL: string; + let appURL: string, + hasShownStartMessage = false; const cljsTypeName = cljsType.name, chan = state.outputChannel(), printThisPrinter: processOutputFn = x => { @@ -275,13 +276,14 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { startAppNowProcessor: processOutputFn = x => { if (cljsType.openUrlRegExp) { let matched = util.stripAnsi(x).match(cljsType.openUrlRegExp); - if (matched && matched.length > 1) { - appURL = matched[1]; + if (matched && matched["groups"] && matched["groups"].url != undefined) { + appURL = matched["groups"].url; } } - if (cljsType.isReadyToStartRegExp) { + if (!hasShownStartMessage && cljsType.isReadyToStartRegExp) { if (x.search(cljsType.isReadyToStartRegExp) >= 0) { chan.appendLine("CLJS REPL ready to connect. Please, start your ClojureScript app."); + hasShownStartMessage = true; if (appURL) { if (cljsType.shouldOpenURL) { chan.appendLine(` Opening ClojureScript app in the browser at: ${appURL} ...`); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index e8aeda4e9..501194bc7 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -81,8 +81,8 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { name: "Figwheel Main", startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, builds: [], - isReadyToStartRegExp: /Starting Server at|already running/, - openUrlRegExp: /Starting Server at (\S+)/, + isReadyToStartRegExp: /Open(ing)? URL|already running/, + openUrlRegExp: /(Starting Server at|Open(ing)? URL) (?\S+)/, shouldOpenURL: false, connectCode: `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl %BUILD%))`, isConnectedRegExp: /To quit, type: :cljs\/quit/ @@ -90,7 +90,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { "lein-figwheel": { name: "lein-figwheel", isReadyToStartRegExp: /Launching ClojureScript REPL for build/, - openUrlRegExp: /Figwheel: Starting server at (\S+)/, + openUrlRegExp: /Figwheel: Starting server at (?\S+)/, shouldOpenURL: config().openBrowserWhenFigwheelStarted, connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", isConnectedRegExp: /To quit, type: :cljs\/quit/ diff --git a/package.json b/package.json index 1816d72bb..535283ce4 100644 --- a/package.json +++ b/package.json @@ -273,7 +273,8 @@ }, "openUrlRegExp": { "type": "string", - "description": "A regular expression, matched against the stdout of cljsType evaluations, for extracting the URL with which the app can be started. The expression should have one, and only one, group for extracting the URL. E.g. \\”Open URL: (.+)$\\”" + "description": "A regular expression, matched against the stdout of cljsType evaluations, for extracting the URL with which the app can be started. The expression should have a capturing group named 'url'. E.g. \\”Open URL: (?\\S+)\\”", + "default": "Open(ing)? URL (?\\S+)" }, "shouldOpenUrl": { "type": "boolean", From 25db46e766f03b4eb7517a78eee8a9e7033e5824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 26 Aug 2019 13:49:08 +0200 Subject: [PATCH 062/128] Covering more Figwheel Main output scenarios --- calva/nrepl/connectSequence.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 501194bc7..2d0918726 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -81,7 +81,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { name: "Figwheel Main", startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, builds: [], - isReadyToStartRegExp: /Open(ing)? URL|already running/, + isReadyToStartRegExp: /Prompt will show|Open(ing)? URL|already running/, openUrlRegExp: /(Starting Server at|Open(ing)? URL) (?\S+)/, shouldOpenURL: false, connectCode: `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl %BUILD%))`, From 384bffcf768773ce99bfff5e23d867ed66e066e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 26 Aug 2019 14:42:51 +0200 Subject: [PATCH 063/128] Document the outputprocessors some --- calva/connector.ts | 47 +++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 8fb12a6a6..fafa7f2f0 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -263,9 +263,14 @@ function updateInitCode(build: string, initCode): string { function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { let appURL: string, - hasShownStartMessage = false; + haveShownStartMessage = false, + haveShownAppURL = false, + haveShownStartSuffix = false; const cljsTypeName = cljsType.name, chan = state.outputChannel(), + // The output processors are used to keep the user informed about the connection process + // The output from Figwheel is meant for printing to the REPL prompt, + // and since we print to Calva says we, only print some of the messages. printThisPrinter: processOutputFn = x => { if (cljsType.printThisLineRegExp) { if (x.search(cljsType.printThisLineRegExp) >= 0) { @@ -273,31 +278,43 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { } } }, + // Having and app to connect to is crucial so we do what we can to help the user + // start the app at the right time in the process. startAppNowProcessor: processOutputFn = x => { - if (cljsType.openUrlRegExp) { + // Extract the appURL if we have the regexp for it configured. + if (cljsType.openUrlRegExp && !appURL) { let matched = util.stripAnsi(x).match(cljsType.openUrlRegExp); if (matched && matched["groups"] && matched["groups"].url != undefined) { appURL = matched["groups"].url; } } - if (!hasShownStartMessage && cljsType.isReadyToStartRegExp) { + // When the app is ready to start, say so. + if (!haveShownStartMessage && cljsType.isReadyToStartRegExp) { if (x.search(cljsType.isReadyToStartRegExp) >= 0) { chan.appendLine("CLJS REPL ready to connect. Please, start your ClojureScript app."); - hasShownStartMessage = true; - if (appURL) { - if (cljsType.shouldOpenURL) { - chan.appendLine(` Opening ClojureScript app in the browser at: ${appURL} ...`); - open(appURL).catch(reason => { - chan.appendLine("Error opening ClojureScript app in the browser: " + reason); - }); - } else { - chan.appendLine(" Open the app on this URL: " + appURL); - } - } - chan.appendLine(" The CLJS REPL will connect when your app is running."); + haveShownStartMessage = true; } } + // If we have an appURL to go with the ”start now” message, say so + if (appURL && haveShownStartMessage && !haveShownAppURL) { + if (cljsType.shouldOpenURL) { + chan.appendLine(` Opening ClojureScript app in the browser at: ${appURL} ...`); + open(appURL).catch(reason => { + chan.appendLine("Error opening ClojureScript app in the browser: " + reason); + }); + } else { + chan.appendLine(" Open the app on this URL: " + appURL); + } + haveShownAppURL = true; + } + // Wait for any appURL to be printed before we round of the ”start now” message. + // (If we do not have the regexp for extracting the appURL, do not wait for appURL.) + if (!haveShownStartSuffix && (haveShownAppURL || (haveShownStartMessage && !cljsType.openUrlRegExp))) { + chan.appendLine(" The CLJS REPL will connect when your app is running."); + haveShownStartSuffix = true; + } }, + // This processor prints everything. We use it for stderr below. allPrinter: processOutputFn = x => { chan.appendLine(util.stripAnsi(x).replace(/\s*$/, "")); } From 2ca3418d86800f22231f2e91293e0388270130b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 27 Aug 2019 07:57:04 +0200 Subject: [PATCH 064/128] Clarify that figwheel openURL config is deferred --- calva/nrepl/connectSequence.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 2d0918726..96f77556d 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -91,7 +91,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { name: "lein-figwheel", isReadyToStartRegExp: /Launching ClojureScript REPL for build/, openUrlRegExp: /Figwheel: Starting server at (?\S+)/, - shouldOpenURL: config().openBrowserWhenFigwheelStarted, + // shouldOpenURL: will be added later, at use-time of this config, connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", isConnectedRegExp: /To quit, type: :cljs\/quit/ }, From 42833f2c284a5e7ac9b774296678df9fb994ee4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Wed, 28 Aug 2019 07:44:46 +0200 Subject: [PATCH 065/128] Jack-in start and done messaging --- calva/nrepl/jack-in.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index cea3a5c1e..70ad55096 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -305,7 +305,7 @@ async function executeJackInTask(projectType: connector.ProjectType, projectType state.cursor.set("launching", null); watcher.removeAllListeners(); await connector.connect(connectSequence, true, true); - chan.appendLine("Jack-in done.\nUse the VS Code task management UI to control the life cycle of the Jack-in task."); + chan.appendLine("Jack-in done. Use the VS Code task management UI to control the life cycle of the Jack-in task."); } }); }, (reason) => { @@ -322,7 +322,6 @@ export async function calvaJackIn() { return; } state.analytics().logEvent("REPL", "JackInInitiated").send(); - outputChannel.appendLine("Jacking in..."); // figure out what possible kinds of project we're in let cljTypes = await detectProjectType(); @@ -361,6 +360,8 @@ export async function calvaJackIn() { return; } + outputChannel.appendLine("Jacking in..."); + let projectConnectSequence: ReplConnectSequence = sequences.find(seq => seq.name === projectConnectSequenceName); if (!projectConnectSequence) { From c2d8d9380d1b3f9f0a07b36cf5d2f38307df8317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Wed, 28 Aug 2019 13:30:54 +0200 Subject: [PATCH 066/128] Separate dependencies for legacy figwheel and main * Fix #239 * Fix #291 * Also: Use cljsType.baseType instead of `name` --- calva/connector.ts | 9 ++++--- calva/nrepl/connectSequence.ts | 5 +++- calva/nrepl/jack-in.ts | 49 ++++++++++++++++++++-------------- package.json | 12 ++++++--- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index fafa7f2f0..0574de732 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -10,7 +10,7 @@ import status from './status'; const { parseEdn } = require('../cljs-out/cljs-lib'); import { NReplClient, NReplSession } from "./nrepl"; import { reconnectReplWindow, openReplWindow, sendTextToREPLWindow } from './repl-window'; -import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType } from './nrepl/connectSequence'; +import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType, CljsTypes } from './nrepl/connectSequence'; const PROJECT_DIR_KEY = "connect.projectDir"; const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; @@ -75,7 +75,7 @@ export type ProjectType = { cljsTypes: string[]; cmd: string; winCmd: string; - commandLine: (includeCljs: boolean) => any; + commandLine: (cljsType: CljsTypes) => any; useWhenExists: string; nReplPortFile: () => string; }; @@ -266,7 +266,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { haveShownStartMessage = false, haveShownAppURL = false, haveShownStartSuffix = false; - const cljsTypeName = cljsType.name, + const cljsTypeName = cljsType.baseType, chan = state.outputChannel(), // The output processors are used to keep the user informed about the connection process // The output from Figwheel is meant for printing to the REPL prompt, @@ -496,12 +496,13 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); + // TODO: MUST DO: REALLY BAD IF WE DO NOT DO: distinguish between baseType and cljsType. if (connectSequence.cljsType == undefined) { cljsTypeName = ""; } else if (typeof connectSequence.cljsType == "string") { cljsTypeName = connectSequence.cljsType; } else { - cljsTypeName = connectSequence.cljsType.name; + cljsTypeName = connectSequence.cljsType.baseType; } state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 96f77556d..a1fa9831e 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -10,11 +10,13 @@ enum ProjectTypes { enum CljsTypes { "Figwheel Main" = "Figwheel Main", "lein-figwheel" = "lein-figwheel", - "shadow-cljs" = "shadow-cljs" + "shadow-cljs" = "shadow-cljs", + "Other" = "Other" } interface CljsTypeConfig { name: string, + baseType?: CljsTypes, startCode?: string, builds?: string[], isReadyToStartRegExp?: string | RegExp, @@ -157,6 +159,7 @@ function getDefaultCljsType(cljsType: string): CljsTypeConfig { export { getConnectSequences, getDefaultCljsType, + CljsTypes, ReplConnectSequence, CljsTypeConfig } \ No newline at end of file diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 70ad55096..4deedd6b0 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -6,7 +6,7 @@ import * as state from "../state" import * as connector from "../connector"; import statusbar from "../statusbar"; import { parseEdn, parseForms } from "../../cljs-out/cljs-lib"; -import { getConnectSequences, ReplConnectSequence } from "./connectSequence"; +import { getConnectSequences, ReplConnectSequence, CljsTypes } from "./connectSequence"; const isWin = /^win/.test(process.platform); @@ -26,13 +26,22 @@ const cliDependencies = { "nrepl": "0.6.0", "cider/cider-nrepl": "0.21.1", } -const figwheelDependencies = { - "cider/piggieback": "0.4.1", - "figwheel-sidecar": "0.5.18" -} -const shadowDependencies = { - "cider/cider-nrepl": "0.21.1", + +const cljsDependencies: { [id: string]: Object } = { + "lein-figwheel": { + "cider/piggieback": "0.4.1", + "figwheel-sidecar": "0.5.18" + }, + "Figwheel Main": { + "cider/piggieback": "0.4.1", + "com.bhauman/figwheel-main": "0.2.3" + }, + "shadow-cljs": { + "cider/cider-nrepl": "0.21.1", + }, + "Other": {} } + const leinPluginDependencies = { "cider/cider-nrepl": "0.21.1" } @@ -60,9 +69,9 @@ const projectTypes: { [id: string]: connector.ProjectType } = { * 5. Add all profiles choosed by the user * 6. Use alias if selected otherwise repl :headless */ - commandLine: async (includeCljs) => { + commandLine: async (cljsType: CljsTypes) => { let out: string[] = []; - let dependencies = includeCljs ? { ...leinDependencies, ...figwheelDependencies } : leinDependencies; + let dependencies = { ...leinDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }; let keys = Object.keys(dependencies); let data = fs.readFileSync(path.resolve(connector.getProjectRoot(), "project.clj"), 'utf8').toString(); let parsed; @@ -129,7 +138,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { out.push("update-in", ":plugins", "conj", `${q + "[" + dep + dQ + leinPluginDependencies[dep] + dQ + "]" + q}`, '--'); } - const useMiddleware = includeCljs ? [...middleware, ...cljsMiddleware] : middleware; + const useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; for (let mw of useMiddleware) { out.push("update-in", `${q + '[:repl-options' + s + ':nrepl-middleware]' + q}`, "conj", `'["${mw}"]'`, '--'); } @@ -178,7 +187,7 @@ const projectTypes: { [id: string]: connector.ProjectType } = { * 5. If main-opts in alias => just use aliases * 6. if no main-opts => supply our own main to run nrepl with middlewares */ - commandLine: async (includeCljs) => { + commandLine: async (cljsType) => { let out: string[] = []; let data = fs.readFileSync(path.join(connector.getProjectRoot(), "deps.edn"), 'utf8').toString(); let parsed; @@ -193,8 +202,8 @@ const projectTypes: { [id: string]: connector.ProjectType } = { aliases = await utilities.quickPickMulti({ values: Object.keys(parsed.aliases).map(x => ":" + x), saveAs: `${connector.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); } - const dependencies = includeCljs ? { ...cliDependencies, ...figwheelDependencies } : cliDependencies, - useMiddleware = includeCljs ? [...middleware, ...cljsMiddleware] : middleware; + const dependencies = { ...cliDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }, + useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; let aliasHasMain: boolean = false; for (let ali in aliases) { @@ -235,10 +244,10 @@ const projectTypes: { [id: string]: connector.ProjectType } = { /** * Build the Commandline args for a shadow-project. */ - commandLine: async (_includeCljs) => { + commandLine: async (cljsType) => { let args: string[] = []; - for (let dep in shadowDependencies) - args.push("-d", dep + ":" + shadowDependencies[dep]); + for (let dep in { ...(cljsType ? cljsDependencies[cljsType] : {})}) + args.push("-d", dep + ":" + cljsDependencies[cljsType][dep]); const shadowBuilds = await connector.shadowBuilds(); let builds = await utilities.quickPickMulti({ values: shadowBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${connector.getProjectRoot()}/shadowcljs-jack-in` }) @@ -371,14 +380,14 @@ export async function calvaJackIn() { const projectTypeName: string = projectConnectSequence.projectType; state.extensionContext.workspaceState.update('selectedCljTypeName', projectConnectSequence.projectType); - let selectedCljsType: string; + let selectedCljsType: CljsTypes; if (projectConnectSequence.cljsType == undefined) { - selectedCljsType = ""; + selectedCljsType = CljsTypes["Figwheel Main"]; } else if (typeof projectConnectSequence.cljsType == "string") { selectedCljsType = projectConnectSequence.cljsType; } else { - selectedCljsType = projectConnectSequence.cljsType.name; + selectedCljsType = projectConnectSequence.cljsType.baseType; } state.extensionContext.workspaceState.update('selectedCljsTypeName', selectedCljsType); @@ -386,7 +395,7 @@ export async function calvaJackIn() { let projectType = getProjectTypeForName(projectTypeName); let executable = isWin ? projectType.winCmd : projectType.cmd; // Ask the project type to build up the command line. This may prompt for further information. - let args = await projectType.commandLine(selectedCljsType != ""); + let args = await projectType.commandLine(selectedCljsType); executeJackInTask(projectType, projectConnectSequenceName, executable, args, cljTypes, outputChannel, projectConnectSequence) .then(() => { }, () => { }); diff --git a/package.json b/package.json index 535283ce4..a65ae19f6 100644 --- a/package.json +++ b/package.json @@ -252,12 +252,18 @@ "type": "object", "required": [ "connectCode", - "name" + "baseType" ], "properties": { - "name": { + "baseType": { "type": "string", - "description": "Used internally to save selection and states." + "enum": [ + "Figwheel Main", + "lein-figwheel", + "shadow-cljs", + "Other" + ], + "description": "The kind of CLojureScript REPL this customization is based on. NB: If it is `Other`, then you need to launch with an alias (deps.edn), profile (Leiningen), or build (shadow-cljs) that specifies the dependencies needed." }, "startCode": { "type": "string", From 5be340b0c46f30ded66779afab0e7725f2009475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 29 Aug 2019 20:14:06 +0200 Subject: [PATCH 067/128] baseType -> dependsOn And shouldOpenUrl always camelCased --- calva/connector.ts | 8 ++++---- calva/nrepl/connectSequence.ts | 14 +++++++------- calva/nrepl/jack-in.ts | 4 ++-- package.json | 8 ++++---- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 0574de732..a23020abf 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -266,7 +266,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { haveShownStartMessage = false, haveShownAppURL = false, haveShownStartSuffix = false; - const cljsTypeName = cljsType.baseType, + const cljsTypeName = cljsType.dependsOn, chan = state.outputChannel(), // The output processors are used to keep the user informed about the connection process // The output from Figwheel is meant for printing to the REPL prompt, @@ -297,7 +297,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { } // If we have an appURL to go with the ”start now” message, say so if (appURL && haveShownStartMessage && !haveShownAppURL) { - if (cljsType.shouldOpenURL) { + if (cljsType.shouldOpenUrl) { chan.appendLine(` Opening ClojureScript app in the browser at: ${appURL} ...`); open(appURL).catch(reason => { chan.appendLine("Error opening ClojureScript app in the browser: " + reason); @@ -496,13 +496,13 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); - // TODO: MUST DO: REALLY BAD IF WE DO NOT DO: distinguish between baseType and cljsType. + // TODO: MUST DO: REALLY BAD IF WE DO NOT DO: distinguish between dependsOn and cljsType. if (connectSequence.cljsType == undefined) { cljsTypeName = ""; } else if (typeof connectSequence.cljsType == "string") { cljsTypeName = connectSequence.cljsType; } else { - cljsTypeName = connectSequence.cljsType.baseType; + cljsTypeName = connectSequence.cljsType.dependsOn; } state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index a1fa9831e..cd8b5e4e0 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -11,17 +11,17 @@ enum CljsTypes { "Figwheel Main" = "Figwheel Main", "lein-figwheel" = "lein-figwheel", "shadow-cljs" = "shadow-cljs", - "Other" = "Other" + "User provided" = "User provided" } interface CljsTypeConfig { name: string, - baseType?: CljsTypes, + dependsOn?: CljsTypes, startCode?: string, builds?: string[], isReadyToStartRegExp?: string | RegExp, openUrlRegExp?: string | RegExp, - shouldOpenURL?: boolean, + shouldOpenUrl?: boolean, connectCode: string | Object, isConnectedRegExp?: string | RegExp, printThisLineRegExp?: string | RegExp @@ -85,7 +85,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { builds: [], isReadyToStartRegExp: /Prompt will show|Open(ing)? URL|already running/, openUrlRegExp: /(Starting Server at|Open(ing)? URL) (?\S+)/, - shouldOpenURL: false, + shouldOpenUrl: false, connectCode: `(do (use 'figwheel.main.api) (figwheel.main.api/cljs-repl %BUILD%))`, isConnectedRegExp: /To quit, type: :cljs\/quit/ }, @@ -93,7 +93,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { name: "lein-figwheel", isReadyToStartRegExp: /Launching ClojureScript REPL for build/, openUrlRegExp: /Figwheel: Starting server at (?\S+)/, - // shouldOpenURL: will be added later, at use-time of this config, + // shouldOpenUrl: will be added later, at use-time of this config, connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", isConnectedRegExp: /To quit, type: :cljs\/quit/ }, @@ -104,7 +104,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { build: `(shadow.cljs.devtools.api/nrepl-select %BUILD%)`, repl: `(shadow.cljs.devtools.api/%REPL%)` }, - shouldOpenURL: false, + shouldOpenUrl: false, builds: [], isConnectedRegExp: /:selected/ } @@ -152,7 +152,7 @@ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { */ function getDefaultCljsType(cljsType: string): CljsTypeConfig { // TODO: Find a less hacky way to get dynamic config for lein-figwheel - defaultCljsTypes["lein-figwheel"].shouldOpenURL = config().openBrowserWhenFigwheelStarted; + defaultCljsTypes["lein-figwheel"].shouldOpenUrl = config().openBrowserWhenFigwheelStarted; return defaultCljsTypes[cljsType]; } diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 4deedd6b0..588ee8f6a 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -39,7 +39,7 @@ const cljsDependencies: { [id: string]: Object } = { "shadow-cljs": { "cider/cider-nrepl": "0.21.1", }, - "Other": {} + "User provided": {} } const leinPluginDependencies = { @@ -387,7 +387,7 @@ export async function calvaJackIn() { } else if (typeof projectConnectSequence.cljsType == "string") { selectedCljsType = projectConnectSequence.cljsType; } else { - selectedCljsType = projectConnectSequence.cljsType.baseType; + selectedCljsType = projectConnectSequence.cljsType.dependsOn; } state.extensionContext.workspaceState.update('selectedCljsTypeName', selectedCljsType); diff --git a/package.json b/package.json index a65ae19f6..db4306728 100644 --- a/package.json +++ b/package.json @@ -252,18 +252,18 @@ "type": "object", "required": [ "connectCode", - "baseType" + "dependsOn" ], "properties": { - "baseType": { + "dependsOn": { "type": "string", "enum": [ "Figwheel Main", "lein-figwheel", "shadow-cljs", - "Other" + "User provided" ], - "description": "The kind of CLojureScript REPL this customization is based on. NB: If it is `Other`, then you need to launch with an alias (deps.edn), profile (Leiningen), or build (shadow-cljs) that specifies the dependencies needed." + "description": "The CLojureScript REPL dependencies this customization needs. NB: If it is `User provided`, then you need to provide the dependencies in the project, or launch with an alias (deps.edn), profile (Leiningen), or build (shadow-cljs) that privides the dependencies needed." }, "startCode": { "type": "string", From 49bb1c2382049826bf180212f91a9dfc21f1ea17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 09:50:33 +0200 Subject: [PATCH 068/128] Use cljsTypeName much sparser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introducing type ”custom” for logging and artwork --- calva/connector.ts | 35 +++++++++++++++++++++-------------- calva/nrepl/jack-in.ts | 2 -- calva/repl-window.ts | 1 + 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index a23020abf..af277b9aa 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -80,7 +80,7 @@ export type ProjectType = { nReplPortFile: () => string; }; -async function connectToHost(hostname, port, cljsTypeName: string, connectSequence: ReplConnectSequence) { +async function connectToHost(hostname, port, connectSequence: ReplConnectSequence) { state.analytics().logEvent("REPL", "Connecting").send(); let chan = state.outputChannel(); @@ -127,10 +127,12 @@ async function connectToHost(hostname, port, cljsTypeName: string, connectSequen shadowBuild = null; try { if (connectSequence.cljsType != undefined) { - let cljsType: CljsTypeConfig = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + const isBuiltinType: boolean = typeof connectSequence.cljsType == "string"; + let cljsType: CljsTypeConfig = isBuiltinType ? getDefaultCljsType(connectSequence.cljsType as string) : connectSequence.cljsType as CljsTypeConfig; let translatedReplType = createCLJSReplType(cljsType); - [cljsSession, shadowBuild] = cljsTypeName != "" ? await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType) : [null, null]; + [cljsSession, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); + state.analytics().logEvent("REPL", "ConnectCljsRepl", isBuiltinType ? connectSequence.cljsType as string: "Custom").send(); } } catch (e) { chan.appendLine("Error while connecting cljs REPL: " + e); @@ -408,15 +410,13 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { return replType; } -async function makeCljsSessionClone(session, replType, repl: ReplType) { +async function makeCljsSessionClone(session, repl: ReplType) { let chan = state.outputChannel(); chan.appendLine("Creating cljs repl session..."); let newCljsSession = await session.clone(); if (newCljsSession) { chan.show(true); - state.extensionContext.workspaceState.update('cljsReplType', replType); - state.analytics().logEvent("REPL", "ConnectingCLJS", replType).send(); chan.appendLine("Connecting CLJS repl: " + repl.name + "..."); chan.appendLine("See Calva Connection Log for detailed progress updates."); if (repl.start != undefined) { @@ -447,7 +447,7 @@ async function makeCljsSessionClone(session, replType, repl: ReplType) { return [null, null]; } -async function promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence: ReplConnectSequence) { +async function promptForNreplUrlAndConnect(port, connectSequence: ReplConnectSequence) { let current = state.deref(), chan = state.outputChannel(); @@ -464,7 +464,7 @@ async function promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence: if (parsedPort && parsedPort > 0 && parsedPort < 65536) { state.cursor.set("hostname", hostname); state.cursor.set("port", parsedPort); - await connectToHost(hostname, parsedPort, cljsTypeName, connectSequence); + await connectToHost(hostname, parsedPort, connectSequence); } else { chan.appendLine("Bad url: " + url); state.cursor.set('connecting', false); @@ -502,13 +502,14 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec } else if (typeof connectSequence.cljsType == "string") { cljsTypeName = connectSequence.cljsType; } else { - cljsTypeName = connectSequence.cljsType.dependsOn; + cljsTypeName = "custom"; } state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); console.log("connect", { connectSequence, cljsTypeName }); + // TODO: move nrepl-port file to configuration const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? nreplPortFile(".shadow-cljs/nrepl.port") : nreplPortFile(".nrepl-port")); state.extensionContext.workspaceState.update('selectedCljsTypeName', cljsTypeName); @@ -520,20 +521,25 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec if (isAutoConnect) { state.cursor.set("hostname", "localhost"); state.cursor.set("port", port); - await connectToHost("localhost", port, cljsTypeName, connectSequence); + await connectToHost("localhost", port, connectSequence); } else { - await promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence); + await promptForNreplUrlAndConnect(port, connectSequence); } } else { chan.appendLine('No nrepl port file found. (Calva does not start the nrepl for you, yet.)'); - await promptForNreplUrlAndConnect(port, cljsTypeName, connectSequence); + await promptForNreplUrlAndConnect(port, connectSequence); } } else { - await promptForNreplUrlAndConnect(null, cljsTypeName, connectSequence); + await promptForNreplUrlAndConnect(null, connectSequence); } return true; } +export async function conenctCommand() { + // Get the connect sequence from the user + // Call connect() +} + export default { connect: connect, disconnect: function (options = null, callback = () => { }) { @@ -567,9 +573,10 @@ export default { const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'); const connectSequence: ReplConnectSequence = state.extensionContext.workspaceState.get('selectedConnectSequence'); let cljsType: CljsTypeConfig = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; + state.analytics().logEvent("REPL", "RecreateCljsRepl", cljsTypeName).send(); let translatedReplType = createCLJSReplType(cljsType); - let [session, shadowBuild] = await makeCljsSessionClone(cljSession, cljsTypeName, translatedReplType); + let [session, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); if (session) await setUpCljsRepl(session, chan, shadowBuild); status.update(); diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 588ee8f6a..d1d4a424f 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -390,8 +390,6 @@ export async function calvaJackIn() { selectedCljsType = projectConnectSequence.cljsType.dependsOn; } - state.extensionContext.workspaceState.update('selectedCljsTypeName', selectedCljsType); - let projectType = getProjectTypeForName(projectTypeName); let executable = isWin ? projectType.winCmd : projectType.cmd; // Ask the project type to build up the command line. This may prompt for further information. diff --git a/calva/repl-window.ts b/calva/repl-window.ts index 006b16ef8..3a72ef1b8 100644 --- a/calva/repl-window.ts +++ b/calva/repl-window.ts @@ -105,6 +105,7 @@ class REPLWindow { }) panel.iconPath = vscode.Uri.file(path.join(ctx.extensionPath, "html", "/calva-icon.png")); + // TODO: Add a custom-cljs.svg const cljTypeSlug = `clj-type-${cljType.replace(/ /, "-").toLowerCase()}`; const cljsTypeSlug = `cljs-type-${cljsType.replace(/ /, "-").toLowerCase()}`; let html = readFileSync(path.join(ctx.extensionPath, "html/index.html")).toString(); From 05a9c08d3a9e16427e4a9f36a0b7691a2e8d4fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 11:26:29 +0200 Subject: [PATCH 069/128] Moved project types to project-types.ts In preparation for implementing connectCommand() --- calva/connector.ts | 104 +---------- calva/nrepl/connectSequence.ts | 38 +++- calva/nrepl/jack-in.ts | 329 ++------------------------------- calva/nrepl/project-types.ts | 300 ++++++++++++++++++++++++++++++ calva/state.ts | 63 ++++++- 5 files changed, 423 insertions(+), 411 deletions(-) create mode 100644 calva/nrepl/project-types.ts diff --git a/calva/connector.ts b/calva/connector.ts index af277b9aa..e01f4580a 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -6,80 +6,13 @@ import * as state from './state'; import * as util from './utilities'; import * as open from 'open'; import status from './status'; +import * as projectTypes from './nrepl/project-types'; const { parseEdn } = require('../cljs-out/cljs-lib'); import { NReplClient, NReplSession } from "./nrepl"; import { reconnectReplWindow, openReplWindow, sendTextToREPLWindow } from './repl-window'; import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType, CljsTypes } from './nrepl/connectSequence'; -const PROJECT_DIR_KEY = "connect.projectDir"; -const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; - -export function getProjectRoot(): string { - return state.deref().get(PROJECT_DIR_KEY); -} - -export function getProjectWsFolder(): vscode.WorkspaceFolder { - return state.deref().get(PROJECT_WS_FOLDER_KEY); -} - -/** - * Figures out, and stores, the current clojure project root - * Also stores the WorkSpace folder for the project to be used - * when executing the Task and get proper vscode reporting. - * - * 1. If there is no file open. Stop and complain. - * 2. If there is a file open, use it to determine the project root - * by looking for project files from the file's directory and up to - * the window root (for plain folder windows) or the file's - * workspace folder root (for workspaces) to find the project root. - * - * If there is no project file found, then store either of these - * 1. the window root for plain folders - * 2. first workspace root for workspaces. - * (This situation will be detected later by the connect process.) - */ -export async function initProjectDir(): Promise { - const projectFileNames: string[] = ["project.clj", "shadow-cljs.edn", "deps.edn"], - doc = util.getDocument({}), - workspaceFolder = doc ? vscode.workspace.getWorkspaceFolder(doc.uri) : null; - if (!workspaceFolder) { - vscode.window.showErrorMessage("There is no document opened in the workspace. Aborting. Please open a file in your Clojure project and try again."); - state.analytics().logEvent("REPL", "JackinOrConnectInterrupted", "NoCurrentDocument").send(); - throw "There is no document opened in the workspace. Aborting."; - } else { - state.cursor.set(PROJECT_WS_FOLDER_KEY, workspaceFolder); - let rootPath: string = path.resolve(workspaceFolder.uri.fsPath); - let d = path.dirname(doc.uri.fsPath); - let prev = null; - while (d != prev) { - for (let projectFile in projectFileNames) { - const p = path.resolve(d, projectFileNames[projectFile]); - if (fs.existsSync(p)) { - rootPath = d; - break; - } - } - if (d == rootPath) { - break; - } - prev = d; - d = path.resolve(d, ".."); - } - state.cursor.set(PROJECT_DIR_KEY, rootPath); - } -} - -export type ProjectType = { - name: string; - cljsTypes: string[]; - cmd: string; - winCmd: string; - commandLine: (cljsType: CljsTypes) => any; - useWhenExists: string; - nReplPortFile: () => string; -}; - async function connectToHost(hostname, port, connectSequence: ReplConnectSequence) { state.analytics().logEvent("REPL", "Connecting").send(); @@ -163,25 +96,13 @@ async function setUpCljsRepl(cljsSession, chan, shadowBuild) { status.update(); } -export function shadowConfigFile() { - return getProjectRoot() + '/shadow-cljs.edn'; -} - -export function shadowBuilds() { - let parsed = parseEdn(fs.readFileSync(shadowConfigFile(), 'utf8').toString()), - builds: string[] = _.map(parsed.builds, (_v, key) => { return ":" + key }); - builds.push("node-repl"); - builds.push("browser-repl") - return builds; -} - export function shadowBuild() { return state.deref().get('cljsBuild'); } function getFigwheelMainProjects() { let chan = state.outputChannel(); - let res = fs.readdirSync(getProjectRoot()); + let res = fs.readdirSync(state.getProjectRoot()); let projects = res.filter(x => x.match(/\.cljs\.edn/)).map(x => x.replace(/\.cljs\.edn$/, "")); if (projects.length == 0) { vscode.window.showErrorMessage("There are no figwheel project files (.cljs.edn) in the project directory."); @@ -246,7 +167,7 @@ function figwheelOrShadowBuilds(cljsTypeName: string): string[] { if (cljsTypeName.includes("Figwheel Main")) { return getFigwheelMainProjects(); } else if (cljsTypeName.includes("shadow-cljs")) { - return shadowBuilds(); + return projectTypes.shadowBuilds(); } } @@ -335,7 +256,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { build = await util.quickPickSingle({ values: projects, placeHolder: "Select which build to connect to", - saveAs: `${getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` + saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` }); initCode = updateInitCode(build, initCode); @@ -379,7 +300,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { builds = projects.length <= 1 ? projects : await util.quickPickMulti({ values: projects, placeHolder: "Please select which builds to start", - saveAs: `${getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-projects` + saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-projects` }); if (builds) { @@ -481,15 +402,6 @@ export let nClient: NReplClient; export let cljSession: NReplSession; export let cljsSession: NReplSession; -export function nreplPortFile(subPath: string): string { - try { - return path.resolve(getProjectRoot(), subPath); - } catch (e) { - console.log(e); - } - return subPath; -} - export async function connect(connectSequence: ReplConnectSequence, isAutoConnect = false, isJackIn = false) { let chan = state.outputChannel(); let cljsTypeName: string; @@ -510,7 +422,7 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec console.log("connect", { connectSequence, cljsTypeName }); // TODO: move nrepl-port file to configuration - const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? nreplPortFile(".shadow-cljs/nrepl.port") : nreplPortFile(".nrepl-port")); + const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? projectTypes.nreplPortFile(".shadow-cljs/nrepl.port") : projectTypes.nreplPortFile(".nrepl-port")); state.extensionContext.workspaceState.update('selectedCljsTypeName', cljsTypeName); state.extensionContext.workspaceState.update('selectedConnectSequence', connectSequence); @@ -535,7 +447,7 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec return true; } -export async function conenctCommand() { +export async function connectCommand() { // Get the connect sequence from the user // Call connect() } @@ -553,7 +465,7 @@ export default { nClient.close(); callback(); }, - nreplPortFile: nreplPortFile, + nreplPortFile: projectTypes.nreplPortFile, toggleCLJCSession: function () { let current = state.deref(); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index cd8b5e4e0..31e93526f 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -1,5 +1,7 @@ -import { workspace , window } from "vscode"; -import { config } from "../state"; +import * as vscode from "vscode"; +import * as state from "../state"; +import * as projectTypes from './project-types'; +import * as utilities from '../utilities'; enum ProjectTypes { "Leiningen" = "Leiningen", @@ -112,13 +114,13 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { /** Retrieve the replConnectSequences from the config */ function getCustomConnectSequences(): ReplConnectSequence[] { - let sequences = config().replConnectSequences; + let sequences = state.config().replConnectSequences; for (let sequence of sequences) { if (sequence.name == undefined || sequence.projectType == undefined) { - window.showWarningMessage("Check your calva.replConnectSequences. "+ + vscode.window.showWarningMessage("Check your calva.replConnectSequences. "+ "You need to supply name and projectType for every sequence. " + "After fixing the customSequences can be used."); @@ -152,11 +154,37 @@ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { */ function getDefaultCljsType(cljsType: string): CljsTypeConfig { // TODO: Find a less hacky way to get dynamic config for lein-figwheel - defaultCljsTypes["lein-figwheel"].shouldOpenUrl = config().openBrowserWhenFigwheelStarted; + defaultCljsTypes["lein-figwheel"].shouldOpenUrl = state.config().openBrowserWhenFigwheelStarted; return defaultCljsTypes[cljsType]; } +async function askForConnectSequence(cljTypes: string[], saveAs: string, logLabel: string): Promise { + // figure out what possible kinds of project we're in + if (cljTypes.length == 0) { + vscode.window.showErrorMessage("Cannot find project, no project.clj, deps.edn or shadow-cljs.edn."); + state.analytics().logEvent("REPL", logLabel, "FailedFindingProjectType").send(); + return; + } + + const sequences = getConnectSequences(cljTypes); + + const projectConnectSequenceName = await utilities.quickPickSingle({ + values: sequences.map(s => { return s.name }), + placeHolder: "Please select a project type", + saveAs: `${state.getProjectRoot()}/${saveAs}`, + autoSelect: true + }); + if (!projectConnectSequenceName) { + state.analytics().logEvent("REPL", logLabel, "NoProjectTypePicked").send(); + return; + } + + + return sequences.find(seq => seq.name === projectConnectSequenceName); +} + export { + askForConnectSequence, getConnectSequences, getDefaultCljsType, CljsTypes, diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index d1d4a424f..521cc8049 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -6,289 +6,34 @@ import * as state from "../state" import * as connector from "../connector"; import statusbar from "../statusbar"; import { parseEdn, parseForms } from "../../cljs-out/cljs-lib"; -import { getConnectSequences, ReplConnectSequence, CljsTypes } from "./connectSequence"; - -const isWin = /^win/.test(process.platform); - -export async function detectProjectType(): Promise { - let rootDir = connector.getProjectRoot(), - cljProjTypes = [] - for (let clj in projectTypes) { - try { - fs.accessSync(rootDir + "/" + projectTypes[clj].useWhenExists); - cljProjTypes.push(clj); - } catch (_e) { } - } - return cljProjTypes; -} - -const cliDependencies = { - "nrepl": "0.6.0", - "cider/cider-nrepl": "0.21.1", -} - -const cljsDependencies: { [id: string]: Object } = { - "lein-figwheel": { - "cider/piggieback": "0.4.1", - "figwheel-sidecar": "0.5.18" - }, - "Figwheel Main": { - "cider/piggieback": "0.4.1", - "com.bhauman/figwheel-main": "0.2.3" - }, - "shadow-cljs": { - "cider/cider-nrepl": "0.21.1", - }, - "User provided": {} -} - -const leinPluginDependencies = { - "cider/cider-nrepl": "0.21.1" -} -const leinDependencies = { - "nrepl": "0.6.0", -} -const middleware = ["cider.nrepl/cider-middleware"]; -const cljsMiddleware = ["cider.piggieback/wrap-cljs-repl"]; - -const projectTypes: { [id: string]: connector.ProjectType } = { - "lein": { - name: "Leiningen", - cljsTypes: ["Figwheel", "Figwheel Main"], - cmd: "lein", - winCmd: "cmd.exe", - useWhenExists: "project.clj", - nReplPortFile: () => { - return connector.nreplPortFile(".nrepl-port"); - }, - /** Build the Commandline args for a lein-project. - * 1. Parsing the project.clj - * 2. Let the user choose a alias - * 3. Let the user choose profiles to use - * 4. Add nedded middleware deps to args - * 5. Add all profiles choosed by the user - * 6. Use alias if selected otherwise repl :headless - */ - commandLine: async (cljsType: CljsTypes) => { - let out: string[] = []; - let dependencies = { ...leinDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }; - let keys = Object.keys(dependencies); - let data = fs.readFileSync(path.resolve(connector.getProjectRoot(), "project.clj"), 'utf8').toString(); - let parsed; - try { - parsed = parseForms(data); - } catch (e) { - vscode.window.showErrorMessage("Could not parse project.clj"); - throw e; - } - let profiles: string[] = []; - let alias: string; - const defproject = parsed.find(x => x[0] == "defproject"); - - if (defproject) { - let aliasesIndex = defproject.indexOf("aliases"); - if (aliasesIndex > -1) { - try { - let aliases: string[] = []; - const aliasesMap = defproject[aliasesIndex + 1]; - aliases = [...profiles, ...Object.keys(aliasesMap).map((v, k) => { return v })]; - if (aliases.length) { - aliases.unshift("No alias"); - alias = await utilities.quickPickSingle({ values: aliases, saveAs: `${connector.getProjectRoot()}/lein-cli-alias`, placeHolder: "Choose alias to run" }); - alias = (alias == "No alias") ? undefined : alias; - } - } catch (error) { - vscode.window.showErrorMessage("The project.clj file is not sane. " + error.message); - console.log(error); - } - } - } - - if (defproject != undefined) { - let profilesIndex = defproject.indexOf("profiles"); - if (profilesIndex > -1) { - try { - const profilesMap = defproject[profilesIndex + 1]; - profiles = [...profiles, ...Object.keys(profilesMap).map((v, k) => { return ":" + v })]; - if (profiles.length) { - profiles = await utilities.quickPickMulti({ values: profiles, saveAs: `${connector.getProjectRoot()}/lein-cli-profiles`, placeHolder: "Pick any profiles to launch with" }); - } - } catch (error) { - vscode.window.showErrorMessage("The project.clj file is not sane. " + error.message); - console.log(error); - } - } - } - - if (isWin) { - out.push("/d", "/c", "lein"); - } - const q = isWin ? '' : "'", - dQ = '"', - s = isWin ? "^ " : " "; - - for (let i = 0; i < keys.length; i++) { - let dep = keys[i]; - out.push("update-in", ":dependencies", "conj", `${q + "[" + dep + dQ + dependencies[dep] + dQ + "]" + q}`, '--'); - } - - keys = Object.keys(leinPluginDependencies); - for (let i = 0; i < keys.length; i++) { - let dep = keys[i]; - out.push("update-in", ":plugins", "conj", `${q + "[" + dep + dQ + leinPluginDependencies[dep] + dQ + "]" + q}`, '--'); - } - - const useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; - for (let mw of useMiddleware) { - out.push("update-in", `${q + '[:repl-options' + s + ':nrepl-middleware]' + q}`, "conj", `'["${mw}"]'`, '--'); - } - - if (profiles.length) { - out.push("with-profile", profiles.map(x => `+${x.substr(1)}`).join(',')); - } - - if (alias) { - out.push(alias); - } else { - out.push("repl", ":headless"); - } - - return out; - } - }, - /* // Works but analysing the possible launch environment is unsatifactory for now, use the cli :) - "boot": { - name: "Boot", - cmd: "boot", - winCmd: "boot.exe", - useWhenExists: "build.boot", - commandLine: () => { - let out: string[] = []; - for(let dep in cliDependencies) - out.push("-d", dep+":"+cliDependencies[dep]); - return [...out, "-i", initEval, "repl"]; - } - }, - */ - "clj": { - name: "Clojure CLI", - cljsTypes: ["Figwheel", "Figwheel Main"], - cmd: "clojure", - winCmd: "powershell.exe", - useWhenExists: "deps.edn", - nReplPortFile: () => { - return connector.nreplPortFile(".nrepl-port"); - }, - /** Build the Commandline args for a clj-project. - * 1. Read the deps.edn and parsed it - * 2. Present the user all found aliases - * 3. Define needed dependencies and middlewares used by calva - * 4. Check if the selected aliases have main-opts - * 5. If main-opts in alias => just use aliases - * 6. if no main-opts => supply our own main to run nrepl with middlewares - */ - commandLine: async (cljsType) => { - let out: string[] = []; - let data = fs.readFileSync(path.join(connector.getProjectRoot(), "deps.edn"), 'utf8').toString(); - let parsed; - try { - parsed = parseEdn(data); - } catch (e) { - vscode.window.showErrorMessage("Could not parse deps.edn"); - throw e; - } - let aliases: string[] = []; - if (parsed.aliases != undefined) { - aliases = await utilities.quickPickMulti({ values: Object.keys(parsed.aliases).map(x => ":" + x), saveAs: `${connector.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); - } - - const dependencies = { ...cliDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }, - useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; - const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; - let aliasHasMain: boolean = false; - for (let ali in aliases) { - let aliasKey = aliases[ali].substr(1); - let alias = parsed.aliases[aliasKey]; - aliasHasMain = (alias["main-opts"] != undefined); - if (aliasHasMain) - break; - } - - const dQ = isWin ? '""' : '"'; - for (let dep in dependencies) - out.push(dep + ` {:mvn/version ${dQ}${dependencies[dep]}${dQ}}`) - - let args = ["-Sdeps", `'${"{:deps {" + out.join(' ') + "}}"}'`]; - - if (aliasHasMain) { - args.push(aliasesOption); - } else { - args.push(aliasesOption, "-m", "nrepl.cmdline", "--middleware", `"[${useMiddleware.join(' ')}]"`); - } - - if (isWin) { - args.unshift("clojure"); - } - return args; - } - }, - "shadow-cljs": { - name: "shadow-cljs", - cljsTypes: [], - cmd: "npx", - winCmd: "npx.cmd", - useWhenExists: "shadow-cljs.edn", - nReplPortFile: () => { - return connector.nreplPortFile(path.join(".shadow-cljs", "nrepl.port")); - }, - /** - * Build the Commandline args for a shadow-project. - */ - commandLine: async (cljsType) => { - let args: string[] = []; - for (let dep in { ...(cljsType ? cljsDependencies[cljsType] : {})}) - args.push("-d", dep + ":" + cljsDependencies[cljsType][dep]); - - const shadowBuilds = await connector.shadowBuilds(); - let builds = await utilities.quickPickMulti({ values: shadowBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${connector.getProjectRoot()}/shadowcljs-jack-in` }) - if (!builds || !builds.length) - return; - return ["shadow-cljs", ...args, "watch", ...builds]; - } - } -} - -/** Given the name of a project in project types, find that project. */ -function getProjectTypeForName(name: string) { - for (let id in projectTypes) - if (projectTypes[id].name == name) - return projectTypes[id]; -} +import { askForConnectSequence, ReplConnectSequence, CljsTypes } from "./connectSequence"; +import { stringify } from "querystring"; +import * as projectTypes from './project-types'; let watcher: fs.FSWatcher; const TASK_NAME = "Calva Jack-in"; -async function executeJackInTask(projectType: connector.ProjectType, projectTypeSelection: any, executable: string, args: any, cljTypes: string[], outputChannel: vscode.OutputChannel, connectSequence: ReplConnectSequence) { +async function executeJackInTask(projectType: projectTypes.ProjectType, projectTypeSelection: any, executable: string, args: any, cljTypes: string[], outputChannel: vscode.OutputChannel, connectSequence: ReplConnectSequence) { state.cursor.set("launching", projectTypeSelection); statusbar.update(); const nReplPortFile = projectType.nReplPortFile(); const env = { ...process.env, ...state.config().jackInEnv } as { [key: string]: string; }; - const execution = isWin ? + const execution = projectTypes.isWin ? new vscode.ProcessExecution(executable, args, { - cwd: connector.getProjectRoot(), + cwd: state.getProjectRoot(), env: env, }) : new vscode.ShellExecution(executable, args, { - cwd: connector.getProjectRoot(), + cwd: state.getProjectRoot(), env: env, }); const taskDefinition: vscode.TaskDefinition = { - type: isWin ? "process" : "shell", + type: projectTypes.isWin ? "process" : "shell", label: "Calva: Jack-in" }; - const task = new vscode.Task(taskDefinition, connector.getProjectWsFolder(), TASK_NAME, "Calva", execution); + const task = new vscode.Task(taskDefinition, state.getProjectWsFolder(), TASK_NAME, "Calva", execution); state.analytics().logEvent("REPL", "JackInExecuting", JSON.stringify(cljTypes)).send(); @@ -326,57 +71,23 @@ async function executeJackInTask(projectType: connector.ProjectType, projectType export async function calvaJackIn() { const outputChannel = state.outputChannel(); try { - await connector.initProjectDir(); + await state.initProjectDir(); } catch { return; } state.analytics().logEvent("REPL", "JackInInitiated").send(); - // figure out what possible kinds of project we're in - let cljTypes = await detectProjectType(); - if (cljTypes.length == 0) { - vscode.window.showErrorMessage("Cannot find project, no project.clj, deps.edn or shadow-cljs.edn."); - state.analytics().logEvent("REPL", "JackInInterrupted", "FailedFindingProjectType").send(); - return; - } - - let sequences = getConnectSequences(cljTypes); - - // Show a prompt to pick one if there are multiple - //let menu: string[] = []; - /** for (const clj of cljTypes) { - menu.push(projectTypes[clj].name); - const customCljsRepl = connector.getCustomCLJSRepl(); - const cljsTypes = projectTypes[clj].cljsTypes.slice(); - if (customCljsRepl) { - cljsTypes.push(customCljsRepl.name); - } - for (const cljs of cljsTypes) { - menu.push(`${projectTypes[clj].name} + ${cljs}`); - } - }*/ - // for (const seq of sequences) { - // menu.push(seq.name); - // } - const projectConnectSequenceName = await utilities.quickPickSingle({ - values: sequences.map(s => { return s.name }), - placeHolder: "Please select a project type", - saveAs: `${connector.getProjectRoot()}/jack-in-type`, - autoSelect: true - }); - if (!projectConnectSequenceName) { - state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypePicked").send(); - return; - } - - outputChannel.appendLine("Jacking in..."); - - let projectConnectSequence: ReplConnectSequence = sequences.find(seq => seq.name === projectConnectSequenceName); - + const cljTypes = await projectTypes.detectProjectTypes(); + const projectConnectSequence: ReplConnectSequence = await askForConnectSequence(cljTypes,'jack-in-type', "JackInInterrupted") + if (!projectConnectSequence) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypeForBuildName").send(); + // TODO: Say something helpful + outputChannel.appendLine("Aborting Jack-in, because reasons"); return; } + + outputChannel.appendLine("Jacking in..."); const projectTypeName: string = projectConnectSequence.projectType; state.extensionContext.workspaceState.update('selectedCljTypeName', projectConnectSequence.projectType); @@ -390,11 +101,11 @@ export async function calvaJackIn() { selectedCljsType = projectConnectSequence.cljsType.dependsOn; } - let projectType = getProjectTypeForName(projectTypeName); - let executable = isWin ? projectType.winCmd : projectType.cmd; + let projectType = projectTypes.getProjectTypeForName(projectTypeName); + let executable = projectTypes.isWin ? projectType.winCmd : projectType.cmd; // Ask the project type to build up the command line. This may prompt for further information. let args = await projectType.commandLine(selectedCljsType); - executeJackInTask(projectType, projectConnectSequenceName, executable, args, cljTypes, outputChannel, projectConnectSequence) + executeJackInTask(projectType, projectConnectSequence.name, executable, args, cljTypes, outputChannel, projectConnectSequence) .then(() => { }, () => { }); } diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts new file mode 100644 index 000000000..f1c6bd96d --- /dev/null +++ b/calva/nrepl/project-types.ts @@ -0,0 +1,300 @@ +import * as state from '../state'; +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as utilities from '../utilities'; +import _ from 'lodash'; + +import { CljsTypes } from './connectSequence'; +const { parseForms, parseEdn } = require('../cljs-out/cljs-lib'); + +export const isWin = /^win/.test(process.platform); + +export type ProjectType = { + name: string; + cljsTypes: string[]; + cmd: string; + winCmd: string; + commandLine: (cljsType: CljsTypes) => any; + useWhenExists: string; + nReplPortFile: () => string; +}; + +export function nreplPortFile(subPath: string): string { + try { + return path.resolve(state.getProjectRoot(), subPath); + } catch (e) { + console.log(e); + } + return subPath; +} + +export function shadowConfigFile() { + return state.getProjectRoot() + '/shadow-cljs.edn'; +} + +export function shadowBuilds() { + let parsed = parseEdn(fs.readFileSync(shadowConfigFile(), 'utf8').toString()), + builds: string[] = _.map(parsed.builds, (_v, key) => { return ":" + key }); + builds.push("node-repl"); + builds.push("browser-repl") + return builds; +} + + +const cliDependencies = { + "nrepl": "0.6.0", + "cider/cider-nrepl": "0.21.1", +} + +const cljsDependencies: { [id: string]: Object } = { + "lein-figwheel": { + "cider/piggieback": "0.4.1", + "figwheel-sidecar": "0.5.18" + }, + "Figwheel Main": { + "cider/piggieback": "0.4.1", + "com.bhauman/figwheel-main": "0.2.3" + }, + "shadow-cljs": { + "cider/cider-nrepl": "0.21.1", + }, + "User provided": {} +} + +const leinPluginDependencies = { + "cider/cider-nrepl": "0.21.1" +} +const leinDependencies = { + "nrepl": "0.6.0", +} +const middleware = ["cider.nrepl/cider-middleware"]; +const cljsMiddleware = ["cider.piggieback/wrap-cljs-repl"]; + +const projectTypes: { [id: string]: ProjectType } = { + "lein": { + name: "Leiningen", + cljsTypes: ["Figwheel", "Figwheel Main"], + cmd: "lein", + winCmd: "cmd.exe", + useWhenExists: "project.clj", + nReplPortFile: () => { + return nreplPortFile(".nrepl-port"); + }, + /** Build the Commandline args for a lein-project. + * 1. Parsing the project.clj + * 2. Let the user choose a alias + * 3. Let the user choose profiles to use + * 4. Add nedded middleware deps to args + * 5. Add all profiles choosed by the user + * 6. Use alias if selected otherwise repl :headless + */ + commandLine: async (cljsType: CljsTypes) => { + let out: string[] = []; + let dependencies = { ...leinDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }; + let keys = Object.keys(dependencies); + let data = fs.readFileSync(path.resolve(state.getProjectRoot(), "project.clj"), 'utf8').toString(); + let parsed; + try { + parsed = parseForms(data); + } catch (e) { + vscode.window.showErrorMessage("Could not parse project.clj"); + throw e; + } + let profiles: string[] = []; + let alias: string; + const defproject = parsed.find(x => x[0] == "defproject"); + + if (defproject) { + let aliasesIndex = defproject.indexOf("aliases"); + if (aliasesIndex > -1) { + try { + let aliases: string[] = []; + const aliasesMap = defproject[aliasesIndex + 1]; + aliases = [...profiles, ...Object.keys(aliasesMap).map((v, k) => { return v })]; + if (aliases.length) { + aliases.unshift("No alias"); + alias = await utilities.quickPickSingle({ values: aliases, saveAs: `${state.getProjectRoot()}/lein-cli-alias`, placeHolder: "Choose alias to run" }); + alias = (alias == "No alias") ? undefined : alias; + } + } catch (error) { + vscode.window.showErrorMessage("The project.clj file is not sane. " + error.message); + console.log(error); + } + } + } + + if (defproject != undefined) { + let profilesIndex = defproject.indexOf("profiles"); + if (profilesIndex > -1) { + try { + const profilesMap = defproject[profilesIndex + 1]; + profiles = [...profiles, ...Object.keys(profilesMap).map((v, k) => { return ":" + v })]; + if (profiles.length) { + profiles = await utilities.quickPickMulti({ values: profiles, saveAs: `${state.getProjectRoot()}/lein-cli-profiles`, placeHolder: "Pick any profiles to launch with" }); + } + } catch (error) { + vscode.window.showErrorMessage("The project.clj file is not sane. " + error.message); + console.log(error); + } + } + } + + if (isWin) { + out.push("/d", "/c", "lein"); + } + const q = isWin ? '' : "'", + dQ = '"', + s = isWin ? "^ " : " "; + + for (let i = 0; i < keys.length; i++) { + let dep = keys[i]; + out.push("update-in", ":dependencies", "conj", `${q + "[" + dep + dQ + dependencies[dep] + dQ + "]" + q}`, '--'); + } + + keys = Object.keys(leinPluginDependencies); + for (let i = 0; i < keys.length; i++) { + let dep = keys[i]; + out.push("update-in", ":plugins", "conj", `${q + "[" + dep + dQ + leinPluginDependencies[dep] + dQ + "]" + q}`, '--'); + } + + const useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; + for (let mw of useMiddleware) { + out.push("update-in", `${q + '[:repl-options' + s + ':nrepl-middleware]' + q}`, "conj", `'["${mw}"]'`, '--'); + } + + if (profiles.length) { + out.push("with-profile", profiles.map(x => `+${x.substr(1)}`).join(',')); + } + + if (alias) { + out.push(alias); + } else { + out.push("repl", ":headless"); + } + + return out; + } + }, + /* // Works but analysing the possible launch environment is unsatifactory for now, use the cli :) + "boot": { + name: "Boot", + cmd: "boot", + winCmd: "boot.exe", + useWhenExists: "build.boot", + commandLine: () => { + let out: string[] = []; + for(let dep in cliDependencies) + out.push("-d", dep+":"+cliDependencies[dep]); + return [...out, "-i", initEval, "repl"]; + } + }, + */ + "clj": { + name: "Clojure CLI", + cljsTypes: ["Figwheel", "Figwheel Main"], + cmd: "clojure", + winCmd: "powershell.exe", + useWhenExists: "deps.edn", + nReplPortFile: () => { + return nreplPortFile(".nrepl-port"); + }, + /** Build the Commandline args for a clj-project. + * 1. Read the deps.edn and parsed it + * 2. Present the user all found aliases + * 3. Define needed dependencies and middlewares used by calva + * 4. Check if the selected aliases have main-opts + * 5. If main-opts in alias => just use aliases + * 6. if no main-opts => supply our own main to run nrepl with middlewares + */ + commandLine: async (cljsType) => { + let out: string[] = []; + let data = fs.readFileSync(path.join(state.getProjectRoot(), "deps.edn"), 'utf8').toString(); + let parsed; + try { + parsed = parseEdn(data); + } catch (e) { + vscode.window.showErrorMessage("Could not parse deps.edn"); + throw e; + } + let aliases: string[] = []; + if (parsed.aliases != undefined) { + aliases = await utilities.quickPickMulti({ values: Object.keys(parsed.aliases).map(x => ":" + x), saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); + } + + const dependencies = { ...cliDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }, + useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; + const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; + let aliasHasMain: boolean = false; + for (let ali in aliases) { + let aliasKey = aliases[ali].substr(1); + let alias = parsed.aliases[aliasKey]; + aliasHasMain = (alias["main-opts"] != undefined); + if (aliasHasMain) + break; + } + + const dQ = isWin ? '""' : '"'; + for (let dep in dependencies) + out.push(dep + ` {:mvn/version ${dQ}${dependencies[dep]}${dQ}}`) + + let args = ["-Sdeps", `'${"{:deps {" + out.join(' ') + "}}"}'`]; + + if (aliasHasMain) { + args.push(aliasesOption); + } else { + args.push(aliasesOption, "-m", "nrepl.cmdline", "--middleware", `"[${useMiddleware.join(' ')}]"`); + } + + if (isWin) { + args.unshift("clojure"); + } + return args; + } + }, + "shadow-cljs": { + name: "shadow-cljs", + cljsTypes: [], + cmd: "npx", + winCmd: "npx.cmd", + useWhenExists: "shadow-cljs.edn", + nReplPortFile: () => { + return nreplPortFile(path.join(".shadow-cljs", "nrepl.port")); + }, + /** + * Build the Commandline args for a shadow-project. + */ + commandLine: async (cljsType) => { + let args: string[] = []; + for (let dep in { ...(cljsType ? cljsDependencies[cljsType] : {})}) + args.push("-d", dep + ":" + cljsDependencies[cljsType][dep]); + + const foundBuilds = await shadowBuilds(); + let selectedBuilds = await utilities.quickPickMulti({ values: foundBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${state.getProjectRoot()}/shadowcljs-jack-in` }) + if (!selectedBuilds || !selectedBuilds.length) + return; + return ["shadow-cljs", ...args, "watch", ...selectedBuilds]; + } + } +} + +/** Given the name of a project in project types, find that project. */ +export function getProjectTypeForName(name: string) { + for (let id in projectTypes) + if (projectTypes[id].name == name) + return projectTypes[id]; +} + +export async function detectProjectTypes(): Promise { + let rootDir = state.getProjectRoot(), + cljProjTypes = [] + for (let clj in projectTypes) { + try { + fs.accessSync(rootDir + "/" + projectTypes[clj].useWhenExists); + cljProjTypes.push(clj); + } catch (_e) { } + } + return cljProjTypes; +} + + diff --git a/calva/state.ts b/calva/state.ts index 8c4edaa2c..c50f2d25f 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -2,7 +2,10 @@ import * as vscode from 'vscode'; import * as Immutable from 'immutable'; import * as ImmutableCursor from 'immutable-cursor'; import Analytics from './analytics'; -import { ReplConnectSequence } from './nrepl/connectSequence' +import { ReplConnectSequence } from './nrepl/connectSequence'; +import * as util from './utilities'; +import * as path from 'path'; +import * as fs from 'fs'; let extensionContext: vscode.ExtensionContext; export function setExtensionContext(context: vscode.ExtensionContext) { @@ -92,6 +95,64 @@ function config() { }; } +const PROJECT_DIR_KEY = "connect.projectDir"; +const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; + +export function getProjectRoot(): string { + return deref().get(PROJECT_DIR_KEY); +} + +export function getProjectWsFolder(): vscode.WorkspaceFolder { + return deref().get(PROJECT_WS_FOLDER_KEY); +} + +/** + * Figures out, and stores, the current clojure project root + * Also stores the WorkSpace folder for the project to be used + * when executing the Task and get proper vscode reporting. + * + * 1. If there is no file open. Stop and complain. + * 2. If there is a file open, use it to determine the project root + * by looking for project files from the file's directory and up to + * the window root (for plain folder windows) or the file's + * workspace folder root (for workspaces) to find the project root. + * + * If there is no project file found, then store either of these + * 1. the window root for plain folders + * 2. first workspace root for workspaces. + * (This situation will be detected later by the connect process.) + */ +export async function initProjectDir(): Promise { + const projectFileNames: string[] = ["project.clj", "shadow-cljs.edn", "deps.edn"], + doc = util.getDocument({}), + workspaceFolder = doc ? vscode.workspace.getWorkspaceFolder(doc.uri) : null; + if (!workspaceFolder) { + vscode.window.showErrorMessage("There is no document opened in the workspace. Aborting. Please open a file in your Clojure project and try again."); + analytics().logEvent("REPL", "JackinOrConnectInterrupted", "NoCurrentDocument").send(); + throw "There is no document opened in the workspace. Aborting."; + } else { + cursor.set(PROJECT_WS_FOLDER_KEY, workspaceFolder); + let rootPath: string = path.resolve(workspaceFolder.uri.fsPath); + let d = path.dirname(doc.uri.fsPath); + let prev = null; + while (d != prev) { + for (let projectFile in projectFileNames) { + const p = path.resolve(d, projectFileNames[projectFile]); + if (fs.existsSync(p)) { + rootPath = d; + break; + } + } + if (d == rootPath) { + break; + } + prev = d; + d = path.resolve(d, ".."); + } + cursor.set(PROJECT_DIR_KEY, rootPath); + } +} + export { cursor, mode, From 706c0fb07e838c88b7e4e25f34cb37a0a924ab22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 13:19:12 +0200 Subject: [PATCH 070/128] Re-enabled standalone connect --- calva/connector.ts | 16 +++++++++++----- calva/extension.ts | 2 +- calva/nrepl/jack-in.ts | 2 +- calva/nrepl/project-types.ts | 2 +- calva/state.ts | 6 ++++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index e01f4580a..8cd4c116a 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -11,7 +11,7 @@ import * as projectTypes from './nrepl/project-types'; const { parseEdn } = require('../cljs-out/cljs-lib'); import { NReplClient, NReplSession } from "./nrepl"; import { reconnectReplWindow, openReplWindow, sendTextToREPLWindow } from './repl-window'; -import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType, CljsTypes } from './nrepl/connectSequence'; +import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType, CljsTypes, askForConnectSequence } from './nrepl/connectSequence'; async function connectToHost(hostname, port, connectSequence: ReplConnectSequence) { state.analytics().logEvent("REPL", "Connecting").send(); @@ -408,7 +408,6 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); - // TODO: MUST DO: REALLY BAD IF WE DO NOT DO: distinguish between dependsOn and cljsType. if (connectSequence.cljsType == undefined) { cljsTypeName = ""; } else if (typeof connectSequence.cljsType == "string") { @@ -448,12 +447,19 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec } export async function connectCommand() { - // Get the connect sequence from the user - // Call connect() + // TODO: Figure out a better way to have an initializwd project directory. + try { + await state.initProjectDir(); + } catch { + return; + } + const cljTypes = await projectTypes.detectProjectTypes(), + connectSequence = await askForConnectSequence(cljTypes, 'connect-type', "ConnectInterrupted"); + connect(connectSequence, false, false); } export default { - connect: connect, + connectCommand: connectCommand, disconnect: function (options = null, callback = () => { }) { ['clj', 'cljs'].forEach(sessionType => { state.cursor.set(sessionType, null); diff --git a/calva/extension.ts b/calva/extension.ts index 5b21303cc..cc4822b88 100644 --- a/calva/extension.ts +++ b/calva/extension.ts @@ -109,7 +109,7 @@ function activate(context: vscode.ExtensionContext) { }) })); context.subscriptions.push(vscode.commands.registerCommand('calva.jackIn', jackIn.calvaJackIn)) - context.subscriptions.push(vscode.commands.registerCommand('calva.connect', connector.connect)); + context.subscriptions.push(vscode.commands.registerCommand('calva.connect', connector.connectCommand)); context.subscriptions.push(vscode.commands.registerCommand('calva.toggleCLJCSession', connector.toggleCLJCSession)); context.subscriptions.push(vscode.commands.registerCommand('calva.recreateCljsRepl', connector.recreateCljsRepl)); context.subscriptions.push(vscode.commands.registerCommand('calva.selectCurrentForm', select.selectCurrentForm)); diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 521cc8049..b58e0c686 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -78,7 +78,7 @@ export async function calvaJackIn() { state.analytics().logEvent("REPL", "JackInInitiated").send(); const cljTypes = await projectTypes.detectProjectTypes(); - const projectConnectSequence: ReplConnectSequence = await askForConnectSequence(cljTypes,'jack-in-type', "JackInInterrupted") + const projectConnectSequence: ReplConnectSequence = await askForConnectSequence(cljTypes,'jack-in-type', "JackInInterrupted"); if (!projectConnectSequence) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypeForBuildName").send(); diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index f1c6bd96d..fb037be43 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -6,7 +6,7 @@ import * as utilities from '../utilities'; import _ from 'lodash'; import { CljsTypes } from './connectSequence'; -const { parseForms, parseEdn } = require('../cljs-out/cljs-lib'); +const { parseForms, parseEdn } = require('../../cljs-out/cljs-lib'); export const isWin = /^win/.test(process.platform); diff --git a/calva/state.ts b/calva/state.ts index c50f2d25f..816b323a3 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -98,8 +98,10 @@ function config() { const PROJECT_DIR_KEY = "connect.projectDir"; const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; -export function getProjectRoot(): string { - return deref().get(PROJECT_DIR_KEY); +export function getProjectRoot(useCache = true): string { + if (useCache) { + return deref().get(PROJECT_DIR_KEY); + } } export function getProjectWsFolder(): vscode.WorkspaceFolder { From 06a98d6b844062f47b7f9e18f6e5572f26859f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 13:44:47 +0200 Subject: [PATCH 071/128] Guard against no sane sequence selected --- calva/connector.ts | 23 +++++++++++------------ calva/nrepl/project-types.ts | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 8cd4c116a..57877667e 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -403,19 +403,10 @@ export let cljSession: NReplSession; export let cljsSession: NReplSession; export async function connect(connectSequence: ReplConnectSequence, isAutoConnect = false, isJackIn = false) { - let chan = state.outputChannel(); - let cljsTypeName: string; + const chan = state.outputChannel(), + cljsTypeName = projectTypes.getCljsTypeName(connectSequence); state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); - - if (connectSequence.cljsType == undefined) { - cljsTypeName = ""; - } else if (typeof connectSequence.cljsType == "string") { - cljsTypeName = connectSequence.cljsType; - } else { - cljsTypeName = "custom"; - } - state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); console.log("connect", { connectSequence, cljsTypeName }); @@ -447,6 +438,7 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec } export async function connectCommand() { + const chan = state.outputChannel(); // TODO: Figure out a better way to have an initializwd project directory. try { await state.initProjectDir(); @@ -455,7 +447,14 @@ export async function connectCommand() { } const cljTypes = await projectTypes.detectProjectTypes(), connectSequence = await askForConnectSequence(cljTypes, 'connect-type', "ConnectInterrupted"); - connect(connectSequence, false, false); + if (connectSequence) { + const cljsTypeName = projectTypes.getCljsTypeName(connectSequence); + chan.appendLine(`Connecting ...`); + state.analytics().logEvent("REPL", "StandaloneConnect", `${connectSequence.name} + ${cljsTypeName}`).send(); + connect(connectSequence, false, false); + } else { + chan.appendLine("Aborting connect, error determining connect sequence.") + } } export default { diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index fb037be43..749bcf28a 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import * as utilities from '../utilities'; import _ from 'lodash'; -import { CljsTypes } from './connectSequence'; +import { CljsTypes, ReplConnectSequence } from './connectSequence'; const { parseForms, parseEdn } = require('../../cljs-out/cljs-lib'); export const isWin = /^win/.test(process.platform); @@ -297,4 +297,16 @@ export async function detectProjectTypes(): Promise { return cljProjTypes; } - +export function getCljsTypeName(connectSequence: ReplConnectSequence) { + let cljsTypeName; + if (connectSequence.cljsType == undefined) { + cljsTypeName = ""; + } + else if (typeof connectSequence.cljsType == "string") { + cljsTypeName = connectSequence.cljsType; + } + else { + cljsTypeName = "custom"; + } + return cljsTypeName; +} \ No newline at end of file From 7db5c5c028d9e38972d052de6cf9dc02e36ca4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 14:18:49 +0200 Subject: [PATCH 072/128] Stop using init-the-get for ws folder --- calva/state.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/calva/state.ts b/calva/state.ts index 816b323a3..11c6865d0 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -96,7 +96,6 @@ function config() { } const PROJECT_DIR_KEY = "connect.projectDir"; -const PROJECT_WS_FOLDER_KEY = "connect.projecWsFolder"; export function getProjectRoot(useCache = true): string { if (useCache) { @@ -105,7 +104,8 @@ export function getProjectRoot(useCache = true): string { } export function getProjectWsFolder(): vscode.WorkspaceFolder { - return deref().get(PROJECT_WS_FOLDER_KEY); + const doc = util.getDocument({}); + return doc ? vscode.workspace.getWorkspaceFolder(doc.uri) : null; } /** @@ -133,7 +133,6 @@ export async function initProjectDir(): Promise { analytics().logEvent("REPL", "JackinOrConnectInterrupted", "NoCurrentDocument").send(); throw "There is no document opened in the workspace. Aborting."; } else { - cursor.set(PROJECT_WS_FOLDER_KEY, workspaceFolder); let rootPath: string = path.resolve(workspaceFolder.uri.fsPath); let d = path.dirname(doc.uri.fsPath); let prev = null; From ad0c3d8212a54eeb0ad265bc90f1e75de4070466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 15:18:20 +0200 Subject: [PATCH 073/128] Get the cljs name a bit more robustly --- calva/connector.ts | 9 ++++----- calva/nrepl/project-types.ts | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 57877667e..b9451140a 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -62,7 +62,7 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc if (connectSequence.cljsType != undefined) { const isBuiltinType: boolean = typeof connectSequence.cljsType == "string"; let cljsType: CljsTypeConfig = isBuiltinType ? getDefaultCljsType(connectSequence.cljsType as string) : connectSequence.cljsType as CljsTypeConfig; - let translatedReplType = createCLJSReplType(cljsType); + let translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence)); [cljsSession, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); state.analytics().logEvent("REPL", "ConnectCljsRepl", isBuiltinType ? connectSequence.cljsType as string: "Custom").send(); @@ -184,13 +184,12 @@ function updateInitCode(build: string, initCode): string { return null; } -function createCLJSReplType(cljsType: CljsTypeConfig): ReplType { +function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): ReplType { let appURL: string, haveShownStartMessage = false, haveShownAppURL = false, haveShownStartSuffix = false; - const cljsTypeName = cljsType.dependsOn, - chan = state.outputChannel(), + const chan = state.outputChannel(), // The output processors are used to keep the user informed about the connection process // The output from Figwheel is meant for printing to the REPL prompt, // and since we print to Calva says we, only print some of the messages. @@ -491,7 +490,7 @@ export default { const connectSequence: ReplConnectSequence = state.extensionContext.workspaceState.get('selectedConnectSequence'); let cljsType: CljsTypeConfig = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; state.analytics().logEvent("REPL", "RecreateCljsRepl", cljsTypeName).send(); - let translatedReplType = createCLJSReplType(cljsType); + let translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence)); let [session, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); if (session) diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 749bcf28a..ad07d80cc 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -301,11 +301,11 @@ export function getCljsTypeName(connectSequence: ReplConnectSequence) { let cljsTypeName; if (connectSequence.cljsType == undefined) { cljsTypeName = ""; - } - else if (typeof connectSequence.cljsType == "string") { + } else if (typeof connectSequence.cljsType == "string") { cljsTypeName = connectSequence.cljsType; - } - else { + } else if (connectSequence.cljsType.dependsOn != undefined) { + cljsTypeName = connectSequence.cljsType.dependsOn; + } else { cljsTypeName = "custom"; } return cljsTypeName; From bbeac2798d99ab00ceaede1792683c8587344bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 18:59:44 +0200 Subject: [PATCH 074/128] Import lodash using `* as` (or bust) --- calva/nrepl/project-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index ad07d80cc..35b50762b 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -3,7 +3,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import * as utilities from '../utilities'; -import _ from 'lodash'; +import * as _ from 'lodash'; import { CljsTypes, ReplConnectSequence } from './connectSequence'; const { parseForms, parseEdn } = require('../../cljs-out/cljs-lib'); From 805d969f6fe4aa042d2812109d4d99746cc43cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 19:12:32 +0200 Subject: [PATCH 075/128] Don't use lodash for mapping over shadow builds --- calva/nrepl/project-types.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 35b50762b..e65361b24 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -3,7 +3,6 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import * as utilities from '../utilities'; -import * as _ from 'lodash'; import { CljsTypes, ReplConnectSequence } from './connectSequence'; const { parseForms, parseEdn } = require('../../cljs-out/cljs-lib'); @@ -33,12 +32,9 @@ export function shadowConfigFile() { return state.getProjectRoot() + '/shadow-cljs.edn'; } -export function shadowBuilds() { - let parsed = parseEdn(fs.readFileSync(shadowConfigFile(), 'utf8').toString()), - builds: string[] = _.map(parsed.builds, (_v, key) => { return ":" + key }); - builds.push("node-repl"); - builds.push("browser-repl") - return builds; +export function shadowBuilds(): string[] { + const parsed = parseEdn(fs.readFileSync(shadowConfigFile(), 'utf8').toString()); + return [...Object.keys(parsed.builds).map((key: string) => { return ":" + key }), ...["node-repl", "browser-repl"]]; } From 7ad477bbac811ef158a4862df04ffe828d3797eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 30 Aug 2019 22:15:29 +0200 Subject: [PATCH 076/128] Abort shadow-cljs jack-in if no build selected --- calva/nrepl/project-types.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index e65361b24..93c13178c 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -261,15 +261,20 @@ const projectTypes: { [id: string]: ProjectType } = { * Build the Commandline args for a shadow-project. */ commandLine: async (cljsType) => { + const chan = state.outputChannel(); let args: string[] = []; for (let dep in { ...(cljsType ? cljsDependencies[cljsType] : {})}) args.push("-d", dep + ":" + cljsDependencies[cljsType][dep]); - const foundBuilds = await shadowBuilds(); - let selectedBuilds = await utilities.quickPickMulti({ values: foundBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${state.getProjectRoot()}/shadowcljs-jack-in` }) - if (!selectedBuilds || !selectedBuilds.length) - return; - return ["shadow-cljs", ...args, "watch", ...selectedBuilds]; + const foundBuilds = await shadowBuilds(), + selectedBuilds = await utilities.quickPickMulti({ values: foundBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${state.getProjectRoot()}/shadowcljs-jack-in` }); + if (selectedBuilds && selectedBuilds.length) { + return ["shadow-cljs", ...args, "watch", ...selectedBuilds]; + } else { + chan.show(); + chan.appendLine("Aborting. No valid shadow-cljs build selected."); + throw "No shadow-cljs build selected" + } } } } From 108cb89085d8420cb27d7b61bb9a4c6a2a33e1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 31 Aug 2019 12:16:20 +0200 Subject: [PATCH 077/128] Re-enable build-switching functionality --- calva/connector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calva/connector.ts b/calva/connector.ts index b9451140a..7b4423d4a 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -271,7 +271,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): Rep return; } - state.cursor.set('cljsBuild', null); + state.cursor.set('cljsBuild', build); return evalConnectCode(session, initCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); }, From a7b0757aa3e5b84fd3723a5f09882cbc77aefd90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 31 Aug 2019 13:09:01 +0200 Subject: [PATCH 078/128] Use => syntax in main connector.ts export --- calva/connector.ts | 50 ++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 7b4423d4a..2d147b0f5 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -436,29 +436,27 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec return true; } -export async function connectCommand() { - const chan = state.outputChannel(); - // TODO: Figure out a better way to have an initializwd project directory. - try { - await state.initProjectDir(); - } catch { - return; - } - const cljTypes = await projectTypes.detectProjectTypes(), - connectSequence = await askForConnectSequence(cljTypes, 'connect-type', "ConnectInterrupted"); - if (connectSequence) { - const cljsTypeName = projectTypes.getCljsTypeName(connectSequence); - chan.appendLine(`Connecting ...`); - state.analytics().logEvent("REPL", "StandaloneConnect", `${connectSequence.name} + ${cljsTypeName}`).send(); - connect(connectSequence, false, false); - } else { - chan.appendLine("Aborting connect, error determining connect sequence.") - } -} - export default { - connectCommand: connectCommand, - disconnect: function (options = null, callback = () => { }) { + connectCommand: async () => { + const chan = state.outputChannel(); + // TODO: Figure out a better way to have an initializwd project directory. + try { + await state.initProjectDir(); + } catch { + return; + } + const cljTypes = await projectTypes.detectProjectTypes(), + connectSequence = await askForConnectSequence(cljTypes, 'connect-type', "ConnectInterrupted"); + if (connectSequence) { + const cljsTypeName = projectTypes.getCljsTypeName(connectSequence); + chan.appendLine(`Connecting ...`); + state.analytics().logEvent("REPL", "StandaloneConnect", `${connectSequence.name} + ${cljsTypeName}`).send(); + connect(connectSequence, false, false); + } else { + chan.appendLine("Aborting connect, error determining connect sequence.") + } + }, + disconnect: (options = null, callback = () => { }) => { ['clj', 'cljs'].forEach(sessionType => { state.cursor.set(sessionType, null); }); @@ -469,8 +467,7 @@ export default { nClient.close(); callback(); }, - nreplPortFile: projectTypes.nreplPortFile, - toggleCLJCSession: function () { + toggleCLJCSession: () => { let current = state.deref(); if (current.get('connected')) { @@ -482,7 +479,7 @@ export default { status.update(); } }, - recreateCljsRepl: async function () { + recreateCljsRepl: async () => { let current = state.deref(), cljSession = util.getSession('clj'), chan = state.outputChannel(); @@ -493,8 +490,9 @@ export default { let translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence)); let [session, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); - if (session) + if (session) { await setUpCljsRepl(session, chan, shadowBuild); + } status.update(); } }; From ac65db4548baae65f2c01a262096578ea9d6bf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 31 Aug 2019 16:18:35 +0200 Subject: [PATCH 079/128] Make repl window follow when nrepl session changes Fixes bug with REPL window not connected to the right session after a build switch. --- calva/repl-window.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/calva/repl-window.ts b/calva/repl-window.ts index 3a72ef1b8..fdd97bb83 100644 --- a/calva/repl-window.ts +++ b/calva/repl-window.ts @@ -208,7 +208,8 @@ export async function openReplWindow(mode: "clj" | "cljs" = "clj", preserveFocus nreplClient = session.client; if (replWindows[mode]) { - if (!nreplClient.sessions[replWindows[mode].session.sessionId]) { + const modeSession = nreplClient.sessions[replWindows[mode].session.sessionId]; + if (!modeSession || modeSession !== session) { replWindows[mode].session = await session.clone(); } replWindows[mode].panel.reveal(vscode.ViewColumn.Two, preserveFocus); From c41cd9200b05d74bc4c146845473c23c0e2e1238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sat, 31 Aug 2019 16:46:32 +0200 Subject: [PATCH 080/128] Don't recreate the repl type on build switch --- calva/connector.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 2d147b0f5..640f8d322 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -62,7 +62,7 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc if (connectSequence.cljsType != undefined) { const isBuiltinType: boolean = typeof connectSequence.cljsType == "string"; let cljsType: CljsTypeConfig = isBuiltinType ? getDefaultCljsType(connectSequence.cljsType as string) : connectSequence.cljsType as CljsTypeConfig; - let translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence)); + translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence)); [cljsSession, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); state.analytics().logEvent("REPL", "ConnectCljsRepl", isBuiltinType ? connectSequence.cljsType as string: "Custom").send(); @@ -163,6 +163,8 @@ export interface ReplType { connected: (valueResult: string, out: string[], err: string[]) => boolean; } +let translatedReplType: ReplType; + function figwheelOrShadowBuilds(cljsTypeName: string): string[] { if (cljsTypeName.includes("Figwheel Main")) { return getFigwheelMainProjects(); @@ -480,14 +482,10 @@ export default { } }, recreateCljsRepl: async () => { - let current = state.deref(), - cljSession = util.getSession('clj'), + let cljSession = util.getSession('clj'), chan = state.outputChannel(); const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'); - const connectSequence: ReplConnectSequence = state.extensionContext.workspaceState.get('selectedConnectSequence'); - let cljsType: CljsTypeConfig = typeof connectSequence.cljsType == "string" ? getDefaultCljsType(cljsTypeName) : connectSequence.cljsType; state.analytics().logEvent("REPL", "RecreateCljsRepl", cljsTypeName).send(); - let translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence)); let [session, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); if (session) { From b81886af50b97793121ea699b58dd26b346f2f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 1 Sep 2019 13:36:59 +0200 Subject: [PATCH 081/128] Rename recreate cljs repl to Switch build --- calva/connector.ts | 4 ++-- calva/extension.ts | 2 +- calva/statusbar.ts | 2 +- package.json | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 640f8d322..4efc74a31 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -481,11 +481,11 @@ export default { status.update(); } }, - recreateCljsRepl: async () => { + switchCljsBuild: async () => { let cljSession = util.getSession('clj'), chan = state.outputChannel(); const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'); - state.analytics().logEvent("REPL", "RecreateCljsRepl", cljsTypeName).send(); + state.analytics().logEvent("REPL", "switchCljsBuild", cljsTypeName).send(); let [session, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); if (session) { diff --git a/calva/extension.ts b/calva/extension.ts index cc4822b88..dfdc80a42 100644 --- a/calva/extension.ts +++ b/calva/extension.ts @@ -111,7 +111,7 @@ function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('calva.jackIn', jackIn.calvaJackIn)) context.subscriptions.push(vscode.commands.registerCommand('calva.connect', connector.connectCommand)); context.subscriptions.push(vscode.commands.registerCommand('calva.toggleCLJCSession', connector.toggleCLJCSession)); - context.subscriptions.push(vscode.commands.registerCommand('calva.recreateCljsRepl', connector.recreateCljsRepl)); + context.subscriptions.push(vscode.commands.registerCommand('calva.switchCljsBuild', connector.switchCljsBuild)); context.subscriptions.push(vscode.commands.registerCommand('calva.selectCurrentForm', select.selectCurrentForm)); context.subscriptions.push(vscode.commands.registerCommand('calva.loadFile', () => { EvaluateMiddleWare.loadFile(); diff --git a/calva/statusbar.ts b/calva/statusbar.ts index 673ecab48..cbf90d950 100644 --- a/calva/statusbar.ts +++ b/calva/statusbar.ts @@ -33,7 +33,7 @@ function update() { connectionStatus.tooltip = "REPL connection status"; cljsBuildStatus.text = null; - cljsBuildStatus.command = "calva.recreateCljsRepl"; + cljsBuildStatus.command = "calva.switchCljsBuild"; cljsBuildStatus.tooltip = null; if (current.get('connected')) { diff --git a/package.json b/package.json index db4306728..34898ec2a 100644 --- a/package.json +++ b/package.json @@ -509,8 +509,8 @@ "category": "Calva" }, { - "command": "calva.recreateCljsRepl", - "title": "Attach (or re-attach) a Clojurescript session", + "command": "calva.switchCljsBuild", + "title": "Switch CLJS build", "category": "Calva" }, { @@ -763,8 +763,8 @@ "key": "ctrl+alt+c ctrl+alt+s" }, { - "command": "calva.recreateCljsRepl", - "key": "ctrl+alt+c ctrl+alt+r" + "command": "calva.switchCljsBuild", + "key": "ctrl+alt+c ctrl+alt+b" }, { "command": "calva.selectCurrentForm", From 5025348c0561f6425443957e97a02f49dbf5d831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 1 Sep 2019 13:51:17 +0200 Subject: [PATCH 082/128] Always inject piggieback on cljs jack-in --- calva/nrepl/project-types.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 93c13178c..91b8ab8b4 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -43,19 +43,22 @@ const cliDependencies = { "cider/cider-nrepl": "0.21.1", } +const cljsCommonDependencies = { + "cider/piggieback": "0.4.1" +} + const cljsDependencies: { [id: string]: Object } = { "lein-figwheel": { - "cider/piggieback": "0.4.1", "figwheel-sidecar": "0.5.18" }, "Figwheel Main": { - "cider/piggieback": "0.4.1", "com.bhauman/figwheel-main": "0.2.3" }, "shadow-cljs": { "cider/cider-nrepl": "0.21.1", }, - "User provided": {} + "User provided": { + } } const leinPluginDependencies = { @@ -87,7 +90,7 @@ const projectTypes: { [id: string]: ProjectType } = { */ commandLine: async (cljsType: CljsTypes) => { let out: string[] = []; - let dependencies = { ...leinDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }; + let dependencies = { ...leinDependencies, ...(cljsType ? {...cljsCommonDependencies, ...cljsDependencies[cljsType]} : {}) }; let keys = Object.keys(dependencies); let data = fs.readFileSync(path.resolve(state.getProjectRoot(), "project.clj"), 'utf8').toString(); let parsed; @@ -218,7 +221,7 @@ const projectTypes: { [id: string]: ProjectType } = { aliases = await utilities.quickPickMulti({ values: Object.keys(parsed.aliases).map(x => ":" + x), saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); } - const dependencies = { ...cliDependencies, ...(cljsType ? cljsDependencies[cljsType] : {}) }, + const dependencies = { ...cliDependencies, ...(cljsType ? {...cljsCommonDependencies, ...cljsDependencies[cljsType]} : {}) }, useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; let aliasHasMain: boolean = false; @@ -261,10 +264,11 @@ const projectTypes: { [id: string]: ProjectType } = { * Build the Commandline args for a shadow-project. */ commandLine: async (cljsType) => { - const chan = state.outputChannel(); + const chan = state.outputChannel(), + dependencies = { ...(cljsType ? {...cljsCommonDependencies, ...cljsDependencies[cljsType]} : {}) }; let args: string[] = []; - for (let dep in { ...(cljsType ? cljsDependencies[cljsType] : {})}) - args.push("-d", dep + ":" + cljsDependencies[cljsType][dep]); + for (let dep in dependencies) + args.push("-d", dep + ":" + dependencies[dep]); const foundBuilds = await shadowBuilds(), selectedBuilds = await utilities.quickPickMulti({ values: foundBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${state.getProjectRoot()}/shadowcljs-jack-in` }); From 3340a78cabc3992c2cd3455cd4f1f84db95601d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 1 Sep 2019 14:14:11 +0200 Subject: [PATCH 083/128] Add Nashorn built-in cjls type --- calva/nrepl/connectSequence.ts | 16 ++++++++++++++++ calva/nrepl/project-types.ts | 2 ++ package.json | 4 +++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 31e93526f..0db5d1032 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -13,6 +13,7 @@ enum CljsTypes { "Figwheel Main" = "Figwheel Main", "lein-figwheel" = "lein-figwheel", "shadow-cljs" = "shadow-cljs", + "Nashorn" = "Nashorn", "User provided" = "User provided" } @@ -50,6 +51,11 @@ const leiningenDefaults: ReplConnectSequence[] = name: "Leiningen + Figwheel Main", projectType: ProjectTypes.Leiningen, cljsType: CljsTypes["Figwheel Main"] + }, + { + name: "Leiningen + Nashorn", + projectType: ProjectTypes.Leiningen, + cljsType: CljsTypes["Nashorn"] }]; const cljDefaults: ReplConnectSequence[] = @@ -66,6 +72,11 @@ const cljDefaults: ReplConnectSequence[] = name: "Clojure CLI + Figwheel Main", projectType: ProjectTypes["Clojure CLI"], cljsType: CljsTypes["Figwheel Main"] + }, + { + name: "Clojure CLI + Nashorn", + projectType: ProjectTypes["Clojure CLI"], + cljsType: CljsTypes["Nashorn"] }]; const shadowCljsDefaults: ReplConnectSequence[] = [{ @@ -109,6 +120,11 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { shouldOpenUrl: false, builds: [], isConnectedRegExp: /:selected/ + }, + "Nashorn": { + name: "Nashorn", + connectCode: "(do (require 'cljs.repl.nashorn) (cider.piggieback/cljs-repl (cljs.repl.nashorn/repl-env)))", + isConnectedRegExp: "To quit, type: :cljs/quit" } }; diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 91b8ab8b4..1f49a464f 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -57,6 +57,8 @@ const cljsDependencies: { [id: string]: Object } = { "shadow-cljs": { "cider/cider-nrepl": "0.21.1", }, + "Nashorn": { + }, "User provided": { } } diff --git a/package.json b/package.json index 34898ec2a..f8fe8ea9e 100644 --- a/package.json +++ b/package.json @@ -245,7 +245,8 @@ "enum": [ "Figwheel Main", "lein-figwheel", - "shadow-cljs" + "shadow-cljs", + "Nashorn" ] }, { @@ -261,6 +262,7 @@ "Figwheel Main", "lein-figwheel", "shadow-cljs", + "Nashorn", "User provided" ], "description": "The CLojureScript REPL dependencies this customization needs. NB: If it is `User provided`, then you need to provide the dependencies in the project, or launch with an alias (deps.edn), profile (Leiningen), or build (shadow-cljs) that privides the dependencies needed." From dc16ffb623b7f27f2ff68f7cc7cfb979be0c47b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 1 Sep 2019 14:40:09 +0200 Subject: [PATCH 084/128] Clarify error message some --- calva/nrepl/jack-in.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index b58e0c686..383101f6d 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -82,8 +82,7 @@ export async function calvaJackIn() { if (!projectConnectSequence) { state.analytics().logEvent("REPL", "JackInInterrupted", "NoProjectTypeForBuildName").send(); - // TODO: Say something helpful - outputChannel.appendLine("Aborting Jack-in, because reasons"); + outputChannel.appendLine("Aborting Jack-in, since no project typee was selected."); return; } From ddab0a589cfefe0c5b0b4f5fae35c6818d40d16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 1 Sep 2019 16:25:16 +0200 Subject: [PATCH 085/128] Deal with nreplPortFile in project-types.ts --- calva/connector.ts | 3 +-- calva/nrepl/jack-in.ts | 2 +- calva/nrepl/project-types.ts | 17 ++++++----------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 4efc74a31..8b30f6dcf 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -412,8 +412,7 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec console.log("connect", { connectSequence, cljsTypeName }); - // TODO: move nrepl-port file to configuration - const portFile: string = await Promise.resolve(cljsTypeName === "shadow-cljs" ? projectTypes.nreplPortFile(".shadow-cljs/nrepl.port") : projectTypes.nreplPortFile(".nrepl-port")); + const portFile = projectTypes.nreplPortFile(connectSequence.projectType); state.extensionContext.workspaceState.update('selectedCljsTypeName', cljsTypeName); state.extensionContext.workspaceState.update('selectedConnectSequence', connectSequence); diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index 383101f6d..f0ee2eea9 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -16,7 +16,7 @@ const TASK_NAME = "Calva Jack-in"; async function executeJackInTask(projectType: projectTypes.ProjectType, projectTypeSelection: any, executable: string, args: any, cljTypes: string[], outputChannel: vscode.OutputChannel, connectSequence: ReplConnectSequence) { state.cursor.set("launching", projectTypeSelection); statusbar.update(); - const nReplPortFile = projectType.nReplPortFile(); + const nReplPortFile = projectTypes.nreplPortFile(projectType); const env = { ...process.env, ...state.config().jackInEnv } as { [key: string]: string; }; diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 1f49a464f..2e4fc981a 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -16,10 +16,11 @@ export type ProjectType = { winCmd: string; commandLine: (cljsType: CljsTypes) => any; useWhenExists: string; - nReplPortFile: () => string; + nReplPortFile: string; }; -export function nreplPortFile(subPath: string): string { +export function nreplPortFile(projectType: ProjectType | string): string { + const subPath: string = typeof projectType == "string" ? getProjectTypeForName(projectType).nReplPortFile : projectType.nReplPortFile; try { return path.resolve(state.getProjectRoot(), subPath); } catch (e) { @@ -79,9 +80,7 @@ const projectTypes: { [id: string]: ProjectType } = { cmd: "lein", winCmd: "cmd.exe", useWhenExists: "project.clj", - nReplPortFile: () => { - return nreplPortFile(".nrepl-port"); - }, + nReplPortFile: ".nrepl-port", /** Build the Commandline args for a lein-project. * 1. Parsing the project.clj * 2. Let the user choose a alias @@ -197,9 +196,7 @@ const projectTypes: { [id: string]: ProjectType } = { cmd: "clojure", winCmd: "powershell.exe", useWhenExists: "deps.edn", - nReplPortFile: () => { - return nreplPortFile(".nrepl-port"); - }, + nReplPortFile: ".nrepl-port", /** Build the Commandline args for a clj-project. * 1. Read the deps.edn and parsed it * 2. Present the user all found aliases @@ -259,9 +256,7 @@ const projectTypes: { [id: string]: ProjectType } = { cmd: "npx", winCmd: "npx.cmd", useWhenExists: "shadow-cljs.edn", - nReplPortFile: () => { - return nreplPortFile(path.join(".shadow-cljs", "nrepl.port")); - }, + nReplPortFile: path.join(".shadow-cljs", "nrepl.port"), /** * Build the Commandline args for a shadow-project. */ From c86a910954e86ab251082eb60a1f67171a9bbbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Sun, 1 Sep 2019 18:06:29 +0200 Subject: [PATCH 086/128] Rename go use builds for FM as well --- calva/connector.ts | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 8b30f6dcf..41b4f0750 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -100,17 +100,17 @@ export function shadowBuild() { return state.deref().get('cljsBuild'); } -function getFigwheelMainProjects() { +function getFigwheelMainBuilds() { let chan = state.outputChannel(); let res = fs.readdirSync(state.getProjectRoot()); - let projects = res.filter(x => x.match(/\.cljs\.edn/)).map(x => x.replace(/\.cljs\.edn$/, "")); - if (projects.length == 0) { - vscode.window.showErrorMessage("There are no figwheel project files (.cljs.edn) in the project directory."); - chan.appendLine("There are no figwheel project files (.cljs.edn) in the project directory."); + let builds = res.filter(x => x.match(/\.cljs\.edn/)).map(x => x.replace(/\.cljs\.edn$/, "")); + if (builds.length == 0) { + vscode.window.showErrorMessage("There are no figwheel build files (.cljs.edn) in the project directory."); + chan.appendLine("There are no figwheel build files (.cljs.edn) in the project directory."); chan.appendLine("Connection to Figwheel Main aborted."); throw "Aborted"; } - return projects; + return builds; } /** @@ -167,7 +167,7 @@ let translatedReplType: ReplType; function figwheelOrShadowBuilds(cljsTypeName: string): string[] { if (cljsTypeName.includes("Figwheel Main")) { - return getFigwheelMainProjects(); + return getFigwheelMainBuilds(); } else if (cljsTypeName.includes("shadow-cljs")) { return projectTypes.shadowBuilds(); } @@ -253,9 +253,9 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): Rep if (cljsType.builds != undefined && cljsType.builds.length === 0 && (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { - let projects = await figwheelOrShadowBuilds(cljsTypeName); + let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); build = await util.quickPickSingle({ - values: projects, + values: allBuilds, placeHolder: "Select which build to connect to", saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` }); @@ -292,21 +292,16 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): Rep replType.start = async (session, name, checkFn) => { let startCode = cljsType.startCode; - let builds: string[]; - - let outputProcessors: processOutputFn[]; - if (startCode.includes("%BUILDS")) { - let projects = await figwheelOrShadowBuilds(cljsTypeName); - builds = projects.length <= 1 ? projects : await util.quickPickMulti({ - values: projects, + let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); + const builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ + values: allBuilds, placeHolder: "Please select which builds to start", - saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-projects` + saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-builds` }); if (builds) { state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - state.cursor.set('cljsBuild', builds[0]); startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); } else { From 8d137a2676b8d5dae3bbc62ffcb62da141cf185b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 2 Sep 2019 09:46:10 +0200 Subject: [PATCH 087/128] More to the point connection messaging --- calva/connector.ts | 93 +++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 41b4f0750..ea5fa597b 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -57,21 +57,21 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc //cljsSession = nClient.session; //terminal.createREPLTerminal('clj', null, chan); let cljsSession = null, - shadowBuild = null; + cljsBuild = null; try { if (connectSequence.cljsType != undefined) { const isBuiltinType: boolean = typeof connectSequence.cljsType == "string"; let cljsType: CljsTypeConfig = isBuiltinType ? getDefaultCljsType(connectSequence.cljsType as string) : connectSequence.cljsType as CljsTypeConfig; - translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence)); + translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence), connectSequence.name); - [cljsSession, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); + [cljsSession, cljsBuild] = await makeCljsSessionClone(cljSession, translatedReplType, connectSequence.name); state.analytics().logEvent("REPL", "ConnectCljsRepl", isBuiltinType ? connectSequence.cljsType as string: "Custom").send(); } } catch (e) { chan.appendLine("Error while connecting cljs REPL: " + e); } if (cljsSession) - await setUpCljsRepl(cljsSession, chan, shadowBuild); + await setUpCljsRepl(cljsSession, chan, cljsBuild); chan.appendLine('cljc files will use the clj REPL.' + (cljsSession ? ' (You can toggle this at will.)' : '')); //evaluate.loadFile(); status.update(); @@ -87,19 +87,14 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc return true; } -async function setUpCljsRepl(cljsSession, chan, shadowBuild) { +async function setUpCljsRepl(cljsSession, chan, build) { state.cursor.set("cljs", cljsSession); - chan.appendLine("Connected session: cljs"); + chan.appendLine("Connected session: cljs, build: " + build); await openReplWindow("cljs", true); await reconnectReplWindow("cljs"); - //terminal.createREPLTerminal('cljs', shadowBuild, chan); status.update(); } -export function shadowBuild() { - return state.deref().get('cljsBuild'); -} - function getFigwheelMainBuilds() { let chan = state.outputChannel(); let res = fs.readdirSync(state.getProjectRoot()); @@ -186,7 +181,7 @@ function updateInitCode(build: string, initCode): string { return null; } -function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): ReplType { +function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, projectTypeName: string): ReplType { let appURL: string, haveShownStartMessage = false, haveShownAppURL = false, @@ -206,10 +201,13 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): Rep // start the app at the right time in the process. startAppNowProcessor: processOutputFn = x => { // Extract the appURL if we have the regexp for it configured. - if (cljsType.openUrlRegExp && !appURL) { - let matched = util.stripAnsi(x).match(cljsType.openUrlRegExp); + if (cljsType.openUrlRegExp) { + const matched = util.stripAnsi(x).match(cljsType.openUrlRegExp); if (matched && matched["groups"] && matched["groups"].url != undefined) { - appURL = matched["groups"].url; + if (matched["groups"].url != appURL) { + appURL = matched["groups"].url; + haveShownAppURL = false; + } } } // When the app is ready to start, say so. @@ -288,38 +286,49 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): Rep } }; + let hasStarted = false; if (cljsType.startCode) { replType.start = async (session, name, checkFn) => { let startCode = cljsType.startCode; - - if (startCode.includes("%BUILDS")) { - let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); - const builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ - values: allBuilds, - placeHolder: "Please select which builds to start", - saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-builds` - }); - - if (builds) { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); - startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); - return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); + if (!hasStarted) { + if (startCode.includes("%BUILDS")) { + let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); + const builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ + values: allBuilds, + placeHolder: "Please select which builds to start", + saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-builds` + }); + + if (builds) { + chan.appendLine("Starting cljs repl for: " + projectTypeName + "..."); + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); + startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); + return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); + } else { + chan.appendLine("Aborted starting cljs repl."); + throw "Aborted"; + } } else { - chan.appendLine("Starting REPL for " + cljsTypeName + " aborted."); - throw "Aborted"; + chan.appendLine("Starting cljs repl for: " + projectTypeName + "..."); + return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); } } else { - return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); + return true; } }; } replType.started = (result, out, err) => { - if (cljsType.isReadyToStartRegExp) { - return [...out, ...err].find(x => { + if (cljsType.isReadyToStartRegExp && !hasStarted) { + const started = [...out, ...err].find(x => { return x.search(cljsType.isReadyToStartRegExp) >= 0 }) != undefined; + if (started) { + hasStarted = true; + } + return started; } else { + hasStarted = true; return true; } } @@ -327,20 +336,19 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string): Rep return replType; } -async function makeCljsSessionClone(session, repl: ReplType) { +async function makeCljsSessionClone(session, repl: ReplType, projectTypeName: string) { let chan = state.outputChannel(); chan.appendLine("Creating cljs repl session..."); let newCljsSession = await session.clone(); if (newCljsSession) { chan.show(true); - chan.appendLine("Connecting CLJS repl: " + repl.name + "..."); - chan.appendLine("See Calva Connection Log for detailed progress updates."); + chan.appendLine("Connecting cljs repl: " + projectTypeName + "..."); + chan.appendLine("THe Calva Connection Log might have more connection progress information."); if (repl.start != undefined) { - chan.appendLine("Starting repl for: " + repl.name + "..."); if (await repl.start(newCljsSession, repl.name, repl.started)) { state.analytics().logEvent("REPL", "StartedCLJS", repl.name).send(); - chan.appendLine("Started cljs builds"); + chan.appendLine("Cljs builds started"); newCljsSession = await session.clone(); } else { state.analytics().logEvent("REPL", "FailedStartingCLJS", repl.name).send(); @@ -352,7 +360,7 @@ async function makeCljsSessionClone(session, repl: ReplType) { if (await repl.connect(newCljsSession, repl.name, repl.connected)) { state.analytics().logEvent("REPL", "ConnectedCLJS", repl.name).send(); state.cursor.set('cljs', cljsSession = newCljsSession); - return [cljsSession, null]; + return [cljsSession, state.deref().get('cljsBuild')]; } else { let build = state.deref().get('cljsBuild') state.analytics().logEvent("REPL", "FailedConnectingCLJS", repl.name).send(); @@ -478,12 +486,13 @@ export default { switchCljsBuild: async () => { let cljSession = util.getSession('clj'), chan = state.outputChannel(); - const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'); + const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'), + cljTypeName: string = state.extensionContext.workspaceState.get('selectedCljTypeName'); state.analytics().logEvent("REPL", "switchCljsBuild", cljsTypeName).send(); - let [session, shadowBuild] = await makeCljsSessionClone(cljSession, translatedReplType); + let [session, build] = await makeCljsSessionClone(cljSession, translatedReplType, cljTypeName); if (session) { - await setUpCljsRepl(session, chan, shadowBuild); + await setUpCljsRepl(session, chan, build); } status.update(); } From cd67fe095fe1609acb372189e2067128b2b77669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 2 Sep 2019 10:17:58 +0200 Subject: [PATCH 088/128] Introduced isStarted for cljsTypes For types where the repl is already started, like with shadow-cljs --- calva/connector.ts | 13 ++++++------- calva/nrepl/connectSequence.ts | 9 +++++++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index ea5fa597b..d1bdec012 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -89,7 +89,7 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc async function setUpCljsRepl(cljsSession, chan, build) { state.cursor.set("cljs", cljsSession); - chan.appendLine("Connected session: cljs, build: " + build); + chan.appendLine("Connected session: cljs" + (build ? ", repl: " + build : "")); await openReplWindow("cljs", true); await reconnectReplWindow("cljs"); status.update(); @@ -286,11 +286,10 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, proj } }; - let hasStarted = false; if (cljsType.startCode) { replType.start = async (session, name, checkFn) => { let startCode = cljsType.startCode; - if (!hasStarted) { + if (!cljsType.isStarted) { if (startCode.includes("%BUILDS")) { let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); const builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ @@ -319,16 +318,16 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, proj } replType.started = (result, out, err) => { - if (cljsType.isReadyToStartRegExp && !hasStarted) { + if (cljsType.isReadyToStartRegExp && !cljsType.isStarted) { const started = [...out, ...err].find(x => { return x.search(cljsType.isReadyToStartRegExp) >= 0 }) != undefined; if (started) { - hasStarted = true; + cljsType.isStarted = true; } return started; } else { - hasStarted = true; + cljsType.isStarted = true; return true; } } @@ -344,7 +343,7 @@ async function makeCljsSessionClone(session, repl: ReplType, projectTypeName: st if (newCljsSession) { chan.show(true); chan.appendLine("Connecting cljs repl: " + projectTypeName + "..."); - chan.appendLine("THe Calva Connection Log might have more connection progress information."); + chan.appendLine("The Calva Connection Log might have more connection progress information."); if (repl.start != undefined) { if (await repl.start(newCljsSession, repl.name, repl.started)) { state.analytics().logEvent("REPL", "StartedCLJS", repl.name).send(); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 0db5d1032..0988221ea 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -20,6 +20,7 @@ enum CljsTypes { interface CljsTypeConfig { name: string, dependsOn?: CljsTypes, + isStarted: boolean, startCode?: string, builds?: string[], isReadyToStartRegExp?: string | RegExp, @@ -94,6 +95,7 @@ const defaultSequences = { const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { "Figwheel Main": { name: "Figwheel Main", + isStarted: false, startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, builds: [], isReadyToStartRegExp: /Prompt will show|Open(ing)? URL|already running/, @@ -104,6 +106,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { }, "lein-figwheel": { name: "lein-figwheel", + isStarted: false, isReadyToStartRegExp: /Launching ClojureScript REPL for build/, openUrlRegExp: /Figwheel: Starting server at (?\S+)/, // shouldOpenUrl: will be added later, at use-time of this config, @@ -112,7 +115,8 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { }, "shadow-cljs": { name: "shadow-cljs", - isReadyToStartRegExp: /To quit, type: :cljs\/quit/, + isStarted: true, + // isReadyToStartRegExp: /To quit, type: :cljs\/quit/, connectCode: { build: `(shadow.cljs.devtools.api/nrepl-select %BUILD%)`, repl: `(shadow.cljs.devtools.api/%REPL%)` @@ -123,6 +127,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { }, "Nashorn": { name: "Nashorn", + isStarted: true, connectCode: "(do (require 'cljs.repl.nashorn) (cider.piggieback/cljs-repl (cljs.repl.nashorn/repl-env)))", isConnectedRegExp: "To quit, type: :cljs/quit" } @@ -130,7 +135,7 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { /** Retrieve the replConnectSequences from the config */ function getCustomConnectSequences(): ReplConnectSequence[] { - let sequences = state.config().replConnectSequences; + let sequences: ReplConnectSequence[] = state.config().replConnectSequences; for (let sequence of sequences) { if (sequence.name == undefined || From 964c01dfe814f3a412af125248727aa5c1dc94d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 2 Sep 2019 10:38:02 +0200 Subject: [PATCH 089/128] Add isStarted to custom sequence config --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index f8fe8ea9e..2773c20ab 100644 --- a/package.json +++ b/package.json @@ -267,6 +267,10 @@ ], "description": "The CLojureScript REPL dependencies this customization needs. NB: If it is `User provided`, then you need to provide the dependencies in the project, or launch with an alias (deps.edn), profile (Leiningen), or build (shadow-cljs) that privides the dependencies needed." }, + "isStarted": { + "type": "boolean", + "description": "For cljs repls that Calva does not need to start, set this to true. (If you base your custom cljs repl on shadow-cljs workflow, for instance.)" + }, "startCode": { "type": "string", "description": "Clojure code to be evaluated to create and/or start your custom CLJS REPL." From 3d2ae3e181787b6ffbb26ecbca6efe61c1835a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 2 Sep 2019 15:48:52 +0200 Subject: [PATCH 090/128] Use closure local for managing started state --- calva/connector.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index d1bdec012..5768acea1 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -185,7 +185,8 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, proj let appURL: string, haveShownStartMessage = false, haveShownAppURL = false, - haveShownStartSuffix = false; + haveShownStartSuffix = false, + hasStarted = cljsType.isStarted; const chan = state.outputChannel(), // The output processors are used to keep the user informed about the connection process // The output from Figwheel is meant for printing to the REPL prompt, @@ -289,7 +290,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, proj if (cljsType.startCode) { replType.start = async (session, name, checkFn) => { let startCode = cljsType.startCode; - if (!cljsType.isStarted) { + if (!hasStarted) { if (startCode.includes("%BUILDS")) { let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); const builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ @@ -318,16 +319,16 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, proj } replType.started = (result, out, err) => { - if (cljsType.isReadyToStartRegExp && !cljsType.isStarted) { + if (cljsType.isReadyToStartRegExp && !hasStarted) { const started = [...out, ...err].find(x => { return x.search(cljsType.isReadyToStartRegExp) >= 0 }) != undefined; if (started) { - cljsType.isStarted = true; + hasStarted = true; } return started; } else { - cljsType.isStarted = true; + hasStarted = true; return true; } } From c1e0dd2c328258fe19199d4f9afb7490c1cbf402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Mon, 2 Sep 2019 21:36:03 +0200 Subject: [PATCH 091/128] Move myAliases/-Profiles into the connect sequence --- calva/nrepl/connectSequence.ts | 28 +++++++++++++++++++--------- calva/nrepl/jack-in.ts | 2 +- calva/nrepl/project-types.ts | 12 ++++++------ calva/state.ts | 4 ++-- package.json | 28 ++++++++++++++-------------- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 0988221ea..649f1002c 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -35,49 +35,59 @@ interface ReplConnectSequence { name: string, projectType: ProjectTypes, afterCLJReplJackInCode?: string, - cljsType?: CljsTypes | CljsTypeConfig + cljsType?: CljsTypes | CljsTypeConfig, + myLeinProfiles?: string[], + myCljAliases?: string[] } const leiningenDefaults: ReplConnectSequence[] = [{ name: "Leiningen", - projectType: ProjectTypes.Leiningen + projectType: ProjectTypes.Leiningen, + myLeinProfiles: [] }, { name: "Leiningen + Figwheel", projectType: ProjectTypes.Leiningen, - cljsType: CljsTypes["lein-figwheel"] + cljsType: CljsTypes["lein-figwheel"], + myLeinProfiles: [] }, { name: "Leiningen + Figwheel Main", projectType: ProjectTypes.Leiningen, - cljsType: CljsTypes["Figwheel Main"] + cljsType: CljsTypes["Figwheel Main"], + myLeinProfiles: [] }, { name: "Leiningen + Nashorn", projectType: ProjectTypes.Leiningen, - cljsType: CljsTypes["Nashorn"] + cljsType: CljsTypes["Nashorn"], + myLeinProfiles: [] }]; const cljDefaults: ReplConnectSequence[] = [{ name: "Clojure CLI", - projectType: ProjectTypes["Clojure CLI"] + projectType: ProjectTypes["Clojure CLI"], + myCljAliases: [] }, { name: "Clojure CLI + Figwheel", projectType: ProjectTypes["Clojure CLI"], - cljsType: CljsTypes["lein-figwheel"] + cljsType: CljsTypes["lein-figwheel"], + myCljAliases: [] }, { name: "Clojure CLI + Figwheel Main", projectType: ProjectTypes["Clojure CLI"], - cljsType: CljsTypes["Figwheel Main"] + cljsType: CljsTypes["Figwheel Main"], + myCljAliases: [] }, { name: "Clojure CLI + Nashorn", projectType: ProjectTypes["Clojure CLI"], - cljsType: CljsTypes["Nashorn"] + cljsType: CljsTypes["Nashorn"], + myCljAliases: [] }]; const shadowCljsDefaults: ReplConnectSequence[] = [{ diff --git a/calva/nrepl/jack-in.ts b/calva/nrepl/jack-in.ts index f0ee2eea9..a275b942c 100644 --- a/calva/nrepl/jack-in.ts +++ b/calva/nrepl/jack-in.ts @@ -103,7 +103,7 @@ export async function calvaJackIn() { let projectType = projectTypes.getProjectTypeForName(projectTypeName); let executable = projectTypes.isWin ? projectType.winCmd : projectType.cmd; // Ask the project type to build up the command line. This may prompt for further information. - let args = await projectType.commandLine(selectedCljsType); + let args = await projectType.commandLine(projectConnectSequence, selectedCljsType); executeJackInTask(projectType, projectConnectSequence.name, executable, args, cljTypes, outputChannel, projectConnectSequence) .then(() => { }, () => { }); diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 54d511baa..0abe1003b 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -14,7 +14,7 @@ export type ProjectType = { cljsTypes: string[]; cmd: string; winCmd: string; - commandLine: (cljsType: CljsTypes) => any; + commandLine: (connectSequence: ReplConnectSequence, cljsType: CljsTypes) => any; useWhenExists: string; nReplPortFile: string; }; @@ -89,7 +89,7 @@ const projectTypes: { [id: string]: ProjectType } = { * 5. Add all profiles choosed by the user * 6. Use alias if selected otherwise repl :headless */ - commandLine: async (cljsType: CljsTypes) => { + commandLine: async (connectSequence: ReplConnectSequence, cljsType: CljsTypes) => { let out: string[] = []; let dependencies = { ...leinDependencies, ...(cljsType ? { ...cljsCommonDependencies, ...cljsDependencies[cljsType] } : {}) }; let keys = Object.keys(dependencies); @@ -127,7 +127,7 @@ const projectTypes: { [id: string]: ProjectType } = { if (defproject != undefined) { const profilesIndex = defproject.indexOf("profiles"), projectProfiles = profilesIndex > -1 ? Object.keys(defproject[profilesIndex + 1]) : [], - myProfiles = state.config().myLeinProfiles; + myProfiles = connectSequence.myLeinProfiles; if (projectProfiles.length + myProfiles.length > 0) { const profilesList = [...projectProfiles, ...myProfiles]; profiles = [...profiles, ...profilesList.map(_keywordize)]; @@ -206,7 +206,7 @@ const projectTypes: { [id: string]: ProjectType } = { * 5. If main-opts in alias => just use aliases * 6. if no main-opts => supply our own main to run nrepl with middlewares */ - commandLine: async (cljsType) => { + commandLine: async (connectSequence, cljsType) => { let out: string[] = []; let data = fs.readFileSync(path.join(state.getProjectRoot(), "deps.edn"), 'utf8').toString(); let parsed; @@ -217,7 +217,7 @@ const projectTypes: { [id: string]: ProjectType } = { throw e; } const projectAliases = parsed.aliases != undefined ? Object.keys(parsed.aliases) : [], - myAliases = state.config().myCljAliases; + myAliases = connectSequence.myCljAliases; let aliases: string[] = []; if (projectAliases.length + myAliases.length > 0) { aliases = await utilities.quickPickMulti({ values: [...projectAliases, ...myAliases].map(_keywordize), saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); @@ -263,7 +263,7 @@ const projectTypes: { [id: string]: ProjectType } = { /** * Build the Commandline args for a shadow-project. */ - commandLine: async (cljsType) => { + commandLine: async (connectSequence, cljsType) => { const chan = state.outputChannel(), dependencies = { ...(cljsType ? { ...cljsCommonDependencies, ...cljsDependencies[cljsType] } : {}) }; let args: string[] = []; diff --git a/calva/state.ts b/calva/state.ts index 1b515a142..20ec0d0cb 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -102,8 +102,8 @@ function config() { openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted") as boolean, customCljsRepl: configOptions.get("customCljsRepl", null), replConnectSequences: configOptions.get("replConnectSequences") as ReplConnectSequence[], - myLeinProfiles: configOptions.get("myLeinProfiles", []).map(_trimAliasName), - myCljAliases: configOptions.get("myCljAliases", []).map(_trimAliasName), + // myLeinProfiles: configOptions.get("myLeinProfiles", []).map(_trimAliasName), + // myCljAliases: configOptions.get("myCljAliases", []).map(_trimAliasName), }; } diff --git a/package.json b/package.json index 30eda61b2..4202a7e38 100644 --- a/package.json +++ b/package.json @@ -237,6 +237,20 @@ "description": "Here you can give Calva some Clojure code to evaluate in the CLJ REPL, once it has been created.", "required": false }, + "myLeinProfiles": { + "type": "array", + "description": "At Jack in, any profiles listed here will be added to the profiles found in the `project.clj` file.", + "items": { + "type": "string" + } + }, + "myCljAliases": { + "type": "array", + "description": "At Jack in, any aliases listed here will be added to the aliases found in the projects's `deps.edn` file.", + "items": { + "type": "string" + } + }, "cljsType": { "description": "Either a built in type, or an object configuring a custom type. If omitted Calva will show a menu with the built-in types.", "anyOf": [ @@ -311,20 +325,6 @@ } } } - }, - "calva.myLeinProfiles": { - "type": "array", - "description": "At Jack in, any profiles listed here will be added to the profiles found in the `project.clj` file.", - "items": { - "type": "string" - } - }, - "calva.myCljAliases": { - "type": "array", - "description": "At Jack in, any aliases listed here will be added to the aliases found in the projects's `deps.edn` file.", - "items": { - "type": "string" - } } } }, From 3168b686a667ba60e7e3eb864623819fb8aeb482 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Tue, 3 Sep 2019 11:03:24 +0200 Subject: [PATCH 092/128] Introduce launchProfiles this removes the need for myLeinProfiles and myCljAliases also when set dont prompt the user to select a profile/alias related to #288 --- calva/connector.ts | 2 -- calva/nrepl/connectSequence.ts | 29 +++++++---------------- calva/nrepl/project-types.ts | 43 ++++++++++++++++++++++++++-------- calva/state.ts | 13 +--------- package.json | 13 +++------- 5 files changed, 46 insertions(+), 54 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 5768acea1..838a211d8 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -413,8 +413,6 @@ export async function connect(connectSequence: ReplConnectSequence, isAutoConnec state.analytics().logEvent("REPL", "ConnectInitiated", isAutoConnect ? "auto" : "manual"); state.analytics().logEvent("REPL", "ConnnectInitiated", cljsTypeName).send(); - console.log("connect", { connectSequence, cljsTypeName }); - const portFile = projectTypes.nreplPortFile(connectSequence.projectType); state.extensionContext.workspaceState.update('selectedCljsTypeName', cljsTypeName); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 649f1002c..b4d94e65d 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -36,58 +36,49 @@ interface ReplConnectSequence { projectType: ProjectTypes, afterCLJReplJackInCode?: string, cljsType?: CljsTypes | CljsTypeConfig, - myLeinProfiles?: string[], - myCljAliases?: string[] + launchProfiles?: string[] } const leiningenDefaults: ReplConnectSequence[] = [{ name: "Leiningen", - projectType: ProjectTypes.Leiningen, - myLeinProfiles: [] + projectType: ProjectTypes.Leiningen }, { name: "Leiningen + Figwheel", projectType: ProjectTypes.Leiningen, - cljsType: CljsTypes["lein-figwheel"], - myLeinProfiles: [] + cljsType: CljsTypes["lein-figwheel"] }, { name: "Leiningen + Figwheel Main", projectType: ProjectTypes.Leiningen, - cljsType: CljsTypes["Figwheel Main"], - myLeinProfiles: [] + cljsType: CljsTypes["Figwheel Main"] }, { name: "Leiningen + Nashorn", projectType: ProjectTypes.Leiningen, - cljsType: CljsTypes["Nashorn"], - myLeinProfiles: [] + cljsType: CljsTypes["Nashorn"] }]; const cljDefaults: ReplConnectSequence[] = [{ name: "Clojure CLI", - projectType: ProjectTypes["Clojure CLI"], - myCljAliases: [] + projectType: ProjectTypes["Clojure CLI"] }, { name: "Clojure CLI + Figwheel", projectType: ProjectTypes["Clojure CLI"], - cljsType: CljsTypes["lein-figwheel"], - myCljAliases: [] + cljsType: CljsTypes["lein-figwheel"] }, { name: "Clojure CLI + Figwheel Main", projectType: ProjectTypes["Clojure CLI"], - cljsType: CljsTypes["Figwheel Main"], - myCljAliases: [] + cljsType: CljsTypes["Figwheel Main"] }, { name: "Clojure CLI + Nashorn", projectType: ProjectTypes["Clojure CLI"], - cljsType: CljsTypes["Nashorn"], - myCljAliases: [] + cljsType: CljsTypes["Nashorn"] }]; const shadowCljsDefaults: ReplConnectSequence[] = [{ @@ -172,8 +163,6 @@ function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { let result = []; for (let pType of projectTypes) { - console.log("pType", pType); - console.log("pSeq", defaultSequences[pType]); result = result.concat(defaultSequences[pType]); } return result.concat(customSequences); diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 0abe1003b..6dea4234a 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -127,10 +127,11 @@ const projectTypes: { [id: string]: ProjectType } = { if (defproject != undefined) { const profilesIndex = defproject.indexOf("profiles"), projectProfiles = profilesIndex > -1 ? Object.keys(defproject[profilesIndex + 1]) : [], - myProfiles = connectSequence.myLeinProfiles; - if (projectProfiles.length + myProfiles.length > 0) { - const profilesList = [...projectProfiles, ...myProfiles]; - profiles = [...profiles, ...profilesList.map(_keywordize)]; + launchProfiles = connectSequence.launchProfiles; + if (launchProfiles) { + profiles = [...profiles, ...launchProfiles.map(_keywordize)]; + } else if (projectProfiles.length) { + profiles = [...profiles, ...projectProfiles.map(_keywordize)]; if (profiles.length) { profiles = await utilities.quickPickMulti({ values: profiles, @@ -217,14 +218,20 @@ const projectTypes: { [id: string]: ProjectType } = { throw e; } const projectAliases = parsed.aliases != undefined ? Object.keys(parsed.aliases) : [], - myAliases = connectSequence.myCljAliases; + launchProfiles = connectSequence.launchProfiles; let aliases: string[] = []; - if (projectAliases.length + myAliases.length > 0) { - aliases = await utilities.quickPickMulti({ values: [...projectAliases, ...myAliases].map(_keywordize), saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, placeHolder: "Pick any aliases to launch with" }); + if (launchProfiles) { + aliases = launchProfiles.map(_keywordize); + } else if (projectAliases.length) { + aliases = await utilities.quickPickMulti({ + values: projectAliases.map(_keywordize), + saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, + placeHolder: "Pick any aliases to launch with" }); } const dependencies = { ...cliDependencies, ...(cljsType ? {...cljsCommonDependencies, ...cljsDependencies[cljsType]} : {}) }, - useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; + useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; + const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; let aliasHasMain: boolean = false; for (let ali in aliases) { const aliasKey = _unKeywordize(aliases[ali]); @@ -271,7 +278,20 @@ const projectTypes: { [id: string]: ProjectType } = { args.push("-d", dep + ":" + dependencies[dep]); const foundBuilds = await shadowBuilds(), + launchProfiles = connectSequence.launchProfiles, selectedBuilds = await utilities.quickPickMulti({ values: foundBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${state.getProjectRoot()}/shadowcljs-jack-in` }); + + let aliases: string[] = []; + + if (launchProfiles) { + aliases = launchProfiles.map(_keywordize); + } // TODO do the same as clj to prompt the user with a list of aliases + + const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; + if (aliasesOption && aliasesOption.length) { + args.push(aliasesOption); + } + if (selectedBuilds && selectedBuilds.length) { return ["shadow-cljs", ...args, "watch", ...selectedBuilds]; } else { @@ -290,7 +310,10 @@ const projectTypes: { [id: string]: ProjectType } = { * @return {string} keywordized string */ function _keywordize(s: string): string { - return `:${s}`; + if (!s.match(/^[\s,:]*/)) + return `:${s}`; + else + return s; } /** @@ -300,7 +323,7 @@ function _keywordize(s: string): string { * @return {string} kw without the first character */ function _unKeywordize(kw: string) { - return kw.substr(1); + return kw.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") } diff --git a/calva/state.ts b/calva/state.ts index 20ec0d0cb..36bec5321 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -78,16 +78,7 @@ function reset() { data = Immutable.fromJS(initialData); } -/** - * Trims EDN alias and profile names from any surrounding whitespace or `:` characters. - * This in order to free the user from having to figure out how the name should be entered. - * @param {string} name - * @return {string} The trimmed name - */ -function _trimAliasName(name: string): string { - return name.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") -} - +// TODO find a way to validate the configs function config() { let configOptions = vscode.workspace.getConfiguration('calva'); return { @@ -102,8 +93,6 @@ function config() { openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted") as boolean, customCljsRepl: configOptions.get("customCljsRepl", null), replConnectSequences: configOptions.get("replConnectSequences") as ReplConnectSequence[], - // myLeinProfiles: configOptions.get("myLeinProfiles", []).map(_trimAliasName), - // myCljAliases: configOptions.get("myCljAliases", []).map(_trimAliasName), }; } diff --git a/package.json b/package.json index 4202a7e38..aa203a489 100644 --- a/package.json +++ b/package.json @@ -237,20 +237,13 @@ "description": "Here you can give Calva some Clojure code to evaluate in the CLJ REPL, once it has been created.", "required": false }, - "myLeinProfiles": { + "launchProfiles": { "type": "array", - "description": "At Jack in, any profiles listed here will be added to the profiles found in the `project.clj` file.", + "description": "At Jack-in, using these profiles or aliases to launch the repl. The user will not get prompted what to use.", "items": { "type": "string" } - }, - "myCljAliases": { - "type": "array", - "description": "At Jack in, any aliases listed here will be added to the aliases found in the projects's `deps.edn` file.", - "items": { - "type": "string" - } - }, + }, "cljsType": { "description": "Either a built in type, or an object configuring a custom type. If omitted Calva will show a menu with the built-in types.", "anyOf": [ From 3787417cc0a54ce2c8332b740e940afcd3ed968e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 3 Sep 2019 15:10:08 +0200 Subject: [PATCH 093/128] Introduce menuSelections settings for sequences --- calva/connector.ts | 60 ++++++++++++++++++++-------------- calva/nrepl/connectSequence.ts | 18 +++++++--- calva/nrepl/project-types.ts | 28 +++++++++------- package.json | 33 +++++++++++++++---- 4 files changed, 92 insertions(+), 47 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index 838a211d8..fd36ee5f4 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -62,7 +62,7 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc if (connectSequence.cljsType != undefined) { const isBuiltinType: boolean = typeof connectSequence.cljsType == "string"; let cljsType: CljsTypeConfig = isBuiltinType ? getDefaultCljsType(connectSequence.cljsType as string) : connectSequence.cljsType as CljsTypeConfig; - translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence), connectSequence.name); + translatedReplType = createCLJSReplType(cljsType, projectTypes.getCljsTypeName(connectSequence), connectSequence); [cljsSession, cljsBuild] = await makeCljsSessionClone(cljSession, translatedReplType, connectSequence.name); state.analytics().logEvent("REPL", "ConnectCljsRepl", isBuiltinType ? connectSequence.cljsType as string: "Custom").send(); @@ -181,12 +181,15 @@ function updateInitCode(build: string, initCode): string { return null; } -function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, projectTypeName: string): ReplType { +function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, connectSequence: ReplConnectSequence): ReplType { + const projectTypeName: string = connectSequence.name, + menuSelections = connectSequence.menuSelections; let appURL: string, haveShownStartMessage = false, haveShownAppURL = false, haveShownStartSuffix = false, - hasStarted = cljsType.isStarted; + hasStarted = cljsType.isStarted, + useDefaultBuild = true; const chan = state.outputChannel(), // The output processors are used to keep the user informed about the connection process // The output from Figwheel is meant for printing to the REPL prompt, @@ -245,22 +248,26 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, proj let replType: ReplType = { name: cljsTypeName, connect: async (session, name, checkFn) => { - state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', !(cljsType.builds === undefined)); - let initCode = cljsType.connectCode; - let build: string = null; - - if (cljsType.builds != undefined && - cljsType.builds.length === 0 && - (typeof initCode === 'object' || initCode.includes("%BUILD%"))) { - let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); - build = await util.quickPickSingle({ - values: allBuilds, - placeHolder: "Select which build to connect to", - saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` - }); + state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', cljsType.buildsRequired); + let initCode = cljsType.connectCode, + build: string = null; + if (menuSelections && menuSelections.cljsDefaultBuild && useDefaultBuild) { + build = menuSelections.cljsDefaultBuild; + useDefaultBuild = false; + } else { + // TODO: Only allow selecting builds that have been started + if ((typeof initCode === 'object' || initCode.includes("%BUILD%"))) { + let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); + build = await util.quickPickSingle({ + values: allBuilds, + placeHolder: "Select which build to connect to", + saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` + }); + } + } + if (build != null) { initCode = updateInitCode(build, initCode); - if (!initCode) { //TODO error message return; @@ -292,13 +299,18 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, proj let startCode = cljsType.startCode; if (!hasStarted) { if (startCode.includes("%BUILDS")) { - let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); - const builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ - values: allBuilds, - placeHolder: "Please select which builds to start", - saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-builds` - }); - + let builds: string[]; + if (menuSelections && menuSelections.cljsLaunchBuilds) { + builds = menuSelections.cljsLaunchBuilds; + } + else { + const allBuilds = await figwheelOrShadowBuilds(cljsTypeName); + builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ + values: allBuilds, + placeHolder: "Please select which builds to start", + saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-builds` + }); + } if (builds) { chan.appendLine("Starting cljs repl for: " + projectTypeName + "..."); state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index b4d94e65d..9727881d7 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -22,7 +22,7 @@ interface CljsTypeConfig { dependsOn?: CljsTypes, isStarted: boolean, startCode?: string, - builds?: string[], + buildsRequired?: boolean, isReadyToStartRegExp?: string | RegExp, openUrlRegExp?: string | RegExp, shouldOpenUrl?: boolean, @@ -31,12 +31,18 @@ interface CljsTypeConfig { printThisLineRegExp?: string | RegExp } +interface MenuSelecions { + projectLaunchProfilesOrAliases?: string[], + cljsLaunchBuilds?: string[], + cljsDefaultBuild?: string +} + interface ReplConnectSequence { name: string, projectType: ProjectTypes, afterCLJReplJackInCode?: string, cljsType?: CljsTypes | CljsTypeConfig, - launchProfiles?: string[] + menuSelections?: MenuSelecions, } const leiningenDefaults: ReplConnectSequence[] = @@ -96,9 +102,9 @@ const defaultSequences = { const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { "Figwheel Main": { name: "Figwheel Main", + buildsRequired: true, isStarted: false, startCode: `(do (require 'figwheel.main.api) (figwheel.main.api/start %BUILDS%))`, - builds: [], isReadyToStartRegExp: /Prompt will show|Open(ing)? URL|already running/, openUrlRegExp: /(Starting Server at|Open(ing)? URL) (?\S+)/, shouldOpenUrl: false, @@ -107,15 +113,17 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { }, "lein-figwheel": { name: "lein-figwheel", + buildsRequired: false, isStarted: false, isReadyToStartRegExp: /Launching ClojureScript REPL for build/, openUrlRegExp: /Figwheel: Starting server at (?\S+)/, - // shouldOpenUrl: will be added later, at use-time of this config, + // shouldOpenUrl: will be set at use-time of this config, connectCode: "(do (use 'figwheel-sidecar.repl-api) (if (not (figwheel-sidecar.repl-api/figwheel-running?)) (figwheel-sidecar.repl-api/start-figwheel!)) (figwheel-sidecar.repl-api/cljs-repl))", isConnectedRegExp: /To quit, type: :cljs\/quit/ }, "shadow-cljs": { name: "shadow-cljs", + buildsRequired: true, isStarted: true, // isReadyToStartRegExp: /To quit, type: :cljs\/quit/, connectCode: { @@ -123,11 +131,11 @@ const defaultCljsTypes: { [id: string]: CljsTypeConfig } = { repl: `(shadow.cljs.devtools.api/%REPL%)` }, shouldOpenUrl: false, - builds: [], isConnectedRegExp: /:selected/ }, "Nashorn": { name: "Nashorn", + buildsRequired: false, isStarted: true, connectCode: "(do (require 'cljs.repl.nashorn) (cider.piggieback/cljs-repl (cljs.repl.nashorn/repl-env)))", isConnectedRegExp: "To quit, type: :cljs/quit" diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 6dea4234a..0605ce924 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -127,7 +127,8 @@ const projectTypes: { [id: string]: ProjectType } = { if (defproject != undefined) { const profilesIndex = defproject.indexOf("profiles"), projectProfiles = profilesIndex > -1 ? Object.keys(defproject[profilesIndex + 1]) : [], - launchProfiles = connectSequence.launchProfiles; + menuSelections = connectSequence.menuSelections, + launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined; if (launchProfiles) { profiles = [...profiles, ...launchProfiles.map(_keywordize)]; } else if (projectProfiles.length) { @@ -218,19 +219,21 @@ const projectTypes: { [id: string]: ProjectType } = { throw e; } const projectAliases = parsed.aliases != undefined ? Object.keys(parsed.aliases) : [], - launchProfiles = connectSequence.launchProfiles; + menuSelections = connectSequence.menuSelections, + launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined; let aliases: string[] = []; if (launchProfiles) { aliases = launchProfiles.map(_keywordize); } else if (projectAliases.length) { - aliases = await utilities.quickPickMulti({ - values: projectAliases.map(_keywordize), - saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, - placeHolder: "Pick any aliases to launch with" }); + aliases = await utilities.quickPickMulti({ + values: projectAliases.map(_keywordize), + saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, + placeHolder: "Pick any aliases to launch with" + }); } - const dependencies = { ...cliDependencies, ...(cljsType ? {...cljsCommonDependencies, ...cljsDependencies[cljsType]} : {}) }, - useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; + const dependencies = { ...cliDependencies, ...(cljsType ? { ...cljsCommonDependencies, ...cljsDependencies[cljsType] } : {}) }, + useMiddleware = [...middleware, ...(cljsType ? cljsMiddleware : [])]; const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; let aliasHasMain: boolean = false; for (let ali in aliases) { @@ -278,15 +281,16 @@ const projectTypes: { [id: string]: ProjectType } = { args.push("-d", dep + ":" + dependencies[dep]); const foundBuilds = await shadowBuilds(), - launchProfiles = connectSequence.launchProfiles, + menuSelections = connectSequence.menuSelections, + launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined, selectedBuilds = await utilities.quickPickMulti({ values: foundBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${state.getProjectRoot()}/shadowcljs-jack-in` }); - + let aliases: string[] = []; if (launchProfiles) { aliases = launchProfiles.map(_keywordize); } // TODO do the same as clj to prompt the user with a list of aliases - + const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; if (aliasesOption && aliasesOption.length) { args.push(aliasesOption); @@ -312,7 +316,7 @@ const projectTypes: { [id: string]: ProjectType } = { function _keywordize(s: string): string { if (!s.match(/^[\s,:]*/)) return `:${s}`; - else + else return s; } diff --git a/package.json b/package.json index aa203a489..003a49853 100644 --- a/package.json +++ b/package.json @@ -237,13 +237,30 @@ "description": "Here you can give Calva some Clojure code to evaluate in the CLJ REPL, once it has been created.", "required": false }, - "launchProfiles": { - "type": "array", - "description": "At Jack-in, using these profiles or aliases to launch the repl. The user will not get prompted what to use.", - "items": { - "type": "string" + "menuSelections": { + "type": "object", + "description": "Pre-selected menu options. If a slection is made here. Calva won't prompt for it.", + "properties": { + "projectLaunchProfilesOrAliases": { + "type": "array", + "description": "At Jack-in, use these project profiles, or aliases, to launch the repl.", + "items": { + "type": "string" + } + }, + "cljsLaunchBuilds": { + "type": "array", + "description": "Which cljs builds to launch.", + "items": { + "type": "string" + } + }, + "cljsDefaultBuild": { + "type": "string", + "description": "Which cljs build to acttach to at the initial connect." + } } - }, + }, "cljsType": { "description": "Either a built in type, or an object configuring a custom type. If omitted Calva will show a menu with the built-in types.", "anyOf": [ @@ -274,6 +291,10 @@ ], "description": "The CLojureScript REPL dependencies this customization needs. NB: If it is `User provided`, then you need to provide the dependencies in the project, or launch with an alias (deps.edn), profile (Leiningen), or build (shadow-cljs) that privides the dependencies needed." }, + "buildsRequired": { + "type": "boolean", + "description": "If the repl type requires that builds are started in order to connect to them, set this to true." + }, "isStarted": { "type": "boolean", "description": "For cljs repls that Calva does not need to start, set this to true. (If you base your custom cljs repl on shadow-cljs workflow, for instance.)" From 70208a994def80807c27c5fd32bd34150a300a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 3 Sep 2019 16:24:24 +0200 Subject: [PATCH 094/128] Reinstall myCljAliases and myLeinProfiles As with #289, fixes #288 --- calva/nrepl/project-types.ts | 51 +++++++++++++++++++++--------------- calva/state.ts | 12 +++++++++ package.json | 14 ++++++++++ 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 0605ce924..1b05b9a63 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -126,19 +126,25 @@ const projectTypes: { [id: string]: ProjectType } = { if (defproject != undefined) { const profilesIndex = defproject.indexOf("profiles"), - projectProfiles = profilesIndex > -1 ? Object.keys(defproject[profilesIndex + 1]) : [], menuSelections = connectSequence.menuSelections, launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined; if (launchProfiles) { profiles = [...profiles, ...launchProfiles.map(_keywordize)]; - } else if (projectProfiles.length) { - profiles = [...profiles, ...projectProfiles.map(_keywordize)]; - if (profiles.length) { - profiles = await utilities.quickPickMulti({ - values: profiles, - saveAs: `${state.getProjectRoot()}/lein-cli-profiles`, - placeHolder: "Pick any profiles to launch with" - }); + } else { + let projectProfiles = profilesIndex > -1 ? Object.keys(defproject[profilesIndex + 1]) : []; + const myProfiles = state.config().myLeinProfiles; + if (myProfiles && myProfiles.length) { + projectProfiles = [...projectProfiles, ...myProfiles]; + } + if (projectProfiles.length) { + profiles = [...profiles, ...projectProfiles.map(_keywordize)]; + if (profiles.length) { + profiles = await utilities.quickPickMulti({ + values: profiles, + saveAs: `${state.getProjectRoot()}/lein-cli-profiles`, + placeHolder: "Pick any profiles to launch with" + }); + } } } } @@ -218,18 +224,24 @@ const projectTypes: { [id: string]: ProjectType } = { vscode.window.showErrorMessage("Could not parse deps.edn"); throw e; } - const projectAliases = parsed.aliases != undefined ? Object.keys(parsed.aliases) : [], - menuSelections = connectSequence.menuSelections, + const menuSelections = connectSequence.menuSelections, launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined; let aliases: string[] = []; if (launchProfiles) { aliases = launchProfiles.map(_keywordize); - } else if (projectAliases.length) { - aliases = await utilities.quickPickMulti({ - values: projectAliases.map(_keywordize), - saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, - placeHolder: "Pick any aliases to launch with" - }); + } else { + let projectAliases = parsed.aliases != undefined ? Object.keys(parsed.aliases) : []; + const myAliases = state.config().myCljAliases; + if (myAliases && myAliases.length) { + projectAliases = [...projectAliases, ...myAliases]; + } + if (projectAliases.length) { + aliases = await utilities.quickPickMulti({ + values: projectAliases.map(_keywordize), + saveAs: `${state.getProjectRoot()}/clj-cli-aliases`, + placeHolder: "Pick any aliases to launch with" + }); + } } const dependencies = { ...cliDependencies, ...(cljsType ? { ...cljsCommonDependencies, ...cljsDependencies[cljsType] } : {}) }, @@ -314,10 +326,7 @@ const projectTypes: { [id: string]: ProjectType } = { * @return {string} keywordized string */ function _keywordize(s: string): string { - if (!s.match(/^[\s,:]*/)) - return `:${s}`; - else - return s; + return s.replace(/^[\s,:]*/, ":"); } /** diff --git a/calva/state.ts b/calva/state.ts index 36bec5321..5b39289b6 100644 --- a/calva/state.ts +++ b/calva/state.ts @@ -78,6 +78,16 @@ function reset() { data = Immutable.fromJS(initialData); } +/** + * Trims EDN alias and profile names from any surrounding whitespace or `:` characters. + * This in order to free the user from having to figure out how the name should be entered. + * @param {string} name + * @return {string} The trimmed name + */ +function _trimAliasName(name: string): string { + return name.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") +} + // TODO find a way to validate the configs function config() { let configOptions = vscode.workspace.getConfiguration('calva'); @@ -93,6 +103,8 @@ function config() { openBrowserWhenFigwheelStarted: configOptions.get("openBrowserWhenFigwheelStarted") as boolean, customCljsRepl: configOptions.get("customCljsRepl", null), replConnectSequences: configOptions.get("replConnectSequences") as ReplConnectSequence[], + myLeinProfiles: configOptions.get("myLeinProfiles", []).map(_trimAliasName) as string[], + myCljAliases: configOptions.get("myCljAliases", []).map(_trimAliasName) as string[] }; } diff --git a/package.json b/package.json index 003a49853..ed27e5540 100644 --- a/package.json +++ b/package.json @@ -209,6 +209,20 @@ "default": true, "description": "Should Calva open the Figwheel app for you when Figwheel has been started?" }, + "calva.myLeinProfiles": { + "type": "array", + "description": "At Jack in, any profiles listed here will be added to the profiles found in the `project.clj` file.", + "items": { + "type": "string" + } + }, + "calva.myCljAliases": { + "type": "array", + "description": "At Jack in, any aliases listed here will be added to the aliases found in the projects's `deps.edn` file.", + "items": { + "type": "string" + } + }, "calva.replConnectSequences": { "type": "array", "description": "For when your project needs a custom REPL connect sequence.", From ddd3ddee387554c7f331286260b32513119ba19f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 3 Sep 2019 16:44:26 +0200 Subject: [PATCH 095/128] Listen to some linter suggestions --- calva/nrepl/project-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 1b05b9a63..9ddaba4d4 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -335,7 +335,7 @@ function _keywordize(s: string): string { * @param {string} kw * @return {string} kw without the first character */ -function _unKeywordize(kw: string) { +function _unKeywordize(kw: string): string { return kw.replace(/^[\s,:]*/, "").replace(/[\s,:]*$/, "") } @@ -360,7 +360,7 @@ export async function detectProjectTypes(): Promise { } export function getCljsTypeName(connectSequence: ReplConnectSequence) { - let cljsTypeName; + let cljsTypeName: string; if (connectSequence.cljsType == undefined) { cljsTypeName = ""; } else if (typeof connectSequence.cljsType == "string") { From d856212b24beaff17af661e1aecbd25d50266275 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Tue, 3 Sep 2019 17:16:51 +0200 Subject: [PATCH 096/128] When Custom Sequence is set changed behavior When only one is defined just start this directly without showing a prompt When multiple show only prompt with costum sequences --- calva/nrepl/connectSequence.ts | 54 +++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 9727881d7..7f0f1cc98 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -147,15 +147,15 @@ function getCustomConnectSequences(): ReplConnectSequence[] { let sequences: ReplConnectSequence[] = state.config().replConnectSequences; for (let sequence of sequences) { - if (sequence.name == undefined || + if (sequence.name == undefined || sequence.projectType == undefined) { - - vscode.window.showWarningMessage("Check your calva.replConnectSequences. "+ - "You need to supply name and projectType for every sequence. " + - "After fixing the customSequences can be used."); - + + vscode.window.showWarningMessage("Check your calva.replConnectSequences. " + + "You need to supply name and projectType for every sequence. " + + "After fixing the customSequences can be used."); + return []; - } + } } return sequences; @@ -169,11 +169,15 @@ function getCustomConnectSequences(): ReplConnectSequence[] { function getConnectSequences(projectTypes: string[]): ReplConnectSequence[] { let customSequences = getCustomConnectSequences(); - let result = []; - for (let pType of projectTypes) { - result = result.concat(defaultSequences[pType]); + if (customSequences.length) { + return customSequences; + } else { + let result = []; + for (let pType of projectTypes) { + result = result.concat(defaultSequences[pType]); + } + return result; } - return result.concat(customSequences); } /** @@ -195,20 +199,22 @@ async function askForConnectSequence(cljTypes: string[], saveAs: string, logLabe } const sequences = getConnectSequences(cljTypes); - - const projectConnectSequenceName = await utilities.quickPickSingle({ - values: sequences.map(s => { return s.name }), - placeHolder: "Please select a project type", - saveAs: `${state.getProjectRoot()}/${saveAs}`, - autoSelect: true - }); - if (!projectConnectSequenceName) { - state.analytics().logEvent("REPL", logLabel, "NoProjectTypePicked").send(); - return; + if (sequences.length > 1) { + const projectConnectSequenceName = await utilities.quickPickSingle({ + values: sequences.map(s => { return s.name }), + placeHolder: "Please select a project type", + saveAs: `${state.getProjectRoot()}/${saveAs}`, + autoSelect: true + }); + if (!projectConnectSequenceName) { + state.analytics().logEvent("REPL", logLabel, "NoProjectTypePicked").send(); + return; + } + + return sequences.find(seq => seq.name === projectConnectSequenceName); + } else { + return sequences[0]; } - - - return sequences.find(seq => seq.name === projectConnectSequenceName); } export { From ddcdb33e5a4decd180472a108aa3ab92c721ac25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 3 Sep 2019 21:17:20 +0200 Subject: [PATCH 097/128] Update launch builds description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed27e5540..512b8124a 100644 --- a/package.json +++ b/package.json @@ -264,7 +264,7 @@ }, "cljsLaunchBuilds": { "type": "array", - "description": "Which cljs builds to launch.", + "description": "The cljs builds to start/watch at Jack-in/comnnect.", "items": { "type": "string" } From f7a27ff2893c328acc05f4488dc4e89734dfd9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 3 Sep 2019 21:23:38 +0200 Subject: [PATCH 098/128] Remove `builds` from sequence cljs type schema --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 512b8124a..2f6a2bde4 100644 --- a/package.json +++ b/package.json @@ -317,10 +317,6 @@ "type": "string", "description": "Clojure code to be evaluated to create and/or start your custom CLJS REPL." }, - "builds": { - "type": "array", - "description": "List of all builds that should be started." - }, "isReadyToStartRegExp": { "type": "string", "description": "A regular experession which, when matched in the stdout from the startCode evaluation, will make Calva continue with connecting the REPL, and to prompt the user to start the application. If omitted and there is startCode Calva will continue when that code is evaluated." From ee1f2e2b2c7b8f40af9cba1fb0b9efcb23b31db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Wed, 4 Sep 2019 18:17:14 +0200 Subject: [PATCH 099/128] Remove CLJS task and make TS task build CLJS first --- .vscode/tasks.json | 11 ----------- package.json | 3 +-- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cec2d76ae..a0def8e76 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,17 +3,6 @@ // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ - { - "label": "Watch CLJS", - "type": "npm", - "script": "watch-cljs", - "isBackground": true, - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": [] - }, { "label": "Watch TS", "type": "npm", diff --git a/package.json b/package.json index 73f4e7c1e..f6a904bd9 100644 --- a/package.json +++ b/package.json @@ -1125,9 +1125,8 @@ } }, "scripts": { - "watch-cljs": "npx shadow-cljs watch :test :calva-lib", "watch-webpack": "webpack --mode development --watch", - "watch-ts": "rm -rf ./out/* ./tsconfig.tsbuildinfo && tsc -watch -p ./tsconfig.json", + "watch-ts": "rm -rf ./out/* ./tsconfig.tsbuildinfo && npm run compile-cljs && tsc -watch -p ./tsconfig.json", "release-cljs": "npx shadow-cljs release :calva-lib", "NOT-USED-start": "ts-node --inspect=5858 webview-src/server/main.ts", "NOT-USED-repl-window-dev-server": "concurrently \"npx nodemon -- ./webview-src/client/index.js\" \"npx webpack-dev-server\"", From 78644497269a275462311163233f424288920bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 00:14:46 +0200 Subject: [PATCH 100/128] Menu sel leinProfiles, leinAlias, cljAliases --- calva/nrepl/connectSequence.ts | 4 +++- calva/nrepl/project-types.ts | 40 ++++++++++++++++++++++------------ package.json | 22 +++++++++++++++++-- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/calva/nrepl/connectSequence.ts b/calva/nrepl/connectSequence.ts index 7f0f1cc98..68c946e11 100644 --- a/calva/nrepl/connectSequence.ts +++ b/calva/nrepl/connectSequence.ts @@ -32,7 +32,9 @@ interface CljsTypeConfig { } interface MenuSelecions { - projectLaunchProfilesOrAliases?: string[], + leinProfiles?: string[], + leinAlias?: string, + cljAliases?: string[], cljsLaunchBuilds?: string[], cljsDefaultBuild?: string } diff --git a/calva/nrepl/project-types.ts b/calva/nrepl/project-types.ts index 9ddaba4d4..f602d9291 100644 --- a/calva/nrepl/project-types.ts +++ b/calva/nrepl/project-types.ts @@ -109,13 +109,25 @@ const projectTypes: { [id: string]: ProjectType } = { let aliasesIndex = defproject.indexOf("aliases"); if (aliasesIndex > -1) { try { - let aliases: string[] = []; - const aliasesMap = defproject[aliasesIndex + 1]; - aliases = [...profiles, ...Object.keys(aliasesMap).map((v, k) => { return v })]; - if (aliases.length) { - aliases.unshift("No alias"); - alias = await utilities.quickPickSingle({ values: aliases, saveAs: `${state.getProjectRoot()}/lein-cli-alias`, placeHolder: "Choose alias to run" }); - alias = (alias == "No alias") ? undefined : alias; + const menuSelections = connectSequence.menuSelections, + leinAlias = menuSelections ? menuSelections.leinAlias : undefined; + if (leinAlias) { + alias = _unKeywordize(leinAlias); + } else if (leinAlias === null) { + alias = undefined; + } else { + let aliases: string[] = []; + const aliasesMap = defproject[aliasesIndex + 1]; + aliases = [...profiles, ...Object.keys(aliasesMap).map((v, k) => { return v })]; + if (aliases.length) { + aliases.unshift("No alias"); + alias = await utilities.quickPickSingle({ + values: aliases, + saveAs: `${state.getProjectRoot()}/lein-cli-alias`, + placeHolder: "Choose alias to launch with" + }); + alias = (alias == "No alias") ? undefined : alias; + } } } catch (error) { vscode.window.showErrorMessage("The project.clj file is not sane. " + error.message); @@ -127,7 +139,7 @@ const projectTypes: { [id: string]: ProjectType } = { if (defproject != undefined) { const profilesIndex = defproject.indexOf("profiles"), menuSelections = connectSequence.menuSelections, - launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined; + launchProfiles = menuSelections ? menuSelections.leinProfiles : undefined; if (launchProfiles) { profiles = [...profiles, ...launchProfiles.map(_keywordize)]; } else { @@ -225,10 +237,10 @@ const projectTypes: { [id: string]: ProjectType } = { throw e; } const menuSelections = connectSequence.menuSelections, - launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined; + launchAliases = menuSelections ? menuSelections.cljAliases : undefined; let aliases: string[] = []; - if (launchProfiles) { - aliases = launchProfiles.map(_keywordize); + if (launchAliases) { + aliases = launchAliases.map(_keywordize); } else { let projectAliases = parsed.aliases != undefined ? Object.keys(parsed.aliases) : []; const myAliases = state.config().myCljAliases; @@ -294,13 +306,13 @@ const projectTypes: { [id: string]: ProjectType } = { const foundBuilds = await shadowBuilds(), menuSelections = connectSequence.menuSelections, - launchProfiles = menuSelections ? menuSelections.projectLaunchProfilesOrAliases : undefined, + launchAliases = menuSelections ? menuSelections.cljAliases : undefined, selectedBuilds = await utilities.quickPickMulti({ values: foundBuilds.filter(x => x[0] == ":"), placeHolder: "Select builds to start", saveAs: `${state.getProjectRoot()}/shadowcljs-jack-in` }); let aliases: string[] = []; - if (launchProfiles) { - aliases = launchProfiles.map(_keywordize); + if (launchAliases) { + aliases = launchAliases.map(_keywordize); } // TODO do the same as clj to prompt the user with a list of aliases const aliasesOption = aliases.length > 0 ? `-A${aliases.join("")}` : ''; diff --git a/package.json b/package.json index f6a904bd9..d6d8e00bb 100644 --- a/package.json +++ b/package.json @@ -255,9 +255,27 @@ "type": "object", "description": "Pre-selected menu options. If a slection is made here. Calva won't prompt for it.", "properties": { - "projectLaunchProfilesOrAliases": { + "leinProfiles": { "type": "array", - "description": "At Jack-in, use these project profiles, or aliases, to launch the repl.", + "description": "At Jack-in to a Leiningen project, use these profiles to launch the repl.", + "items": { + "type": "string" + } + }, + "leinAlias": { + "description": "At Jack-in to a Leiningen project, launch with this alias. Set to null to launch with Calva's default task (a headless repl).", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "cljAliases": { + "type": "array", + "description": "At Jack-in to a Clojure CLI project, use these aliases to launch the repl.", "items": { "type": "string" } From 5c5109de9b6a4bb98cabe5056e42dff860026775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 13:16:02 +0200 Subject: [PATCH 101/128] Switching only to builds that have been started --- calva/connector.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index fd36ee5f4..fb8449810 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -189,7 +189,8 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, conn haveShownAppURL = false, haveShownStartSuffix = false, hasStarted = cljsType.isStarted, - useDefaultBuild = true; + useDefaultBuild = true, + startedBuilds: string[]; const chan = state.outputChannel(), // The output processors are used to keep the user informed about the connection process // The output from Figwheel is meant for printing to the REPL prompt, @@ -255,11 +256,10 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, conn build = menuSelections.cljsDefaultBuild; useDefaultBuild = false; } else { - // TODO: Only allow selecting builds that have been started if ((typeof initCode === 'object' || initCode.includes("%BUILD%"))) { - let allBuilds = await figwheelOrShadowBuilds(cljsTypeName); + const projectBuilds = await figwheelOrShadowBuilds(cljsTypeName); build = await util.quickPickSingle({ - values: allBuilds, + values: startedBuilds ? startedBuilds : projectBuilds, placeHolder: "Select which build to connect to", saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` }); @@ -304,7 +304,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, conn builds = menuSelections.cljsLaunchBuilds; } else { - const allBuilds = await figwheelOrShadowBuilds(cljsTypeName); + const allBuilds = figwheelOrShadowBuilds(cljsTypeName); builds = allBuilds.length <= 1 ? allBuilds : await util.quickPickMulti({ values: allBuilds, placeHolder: "Please select which builds to start", @@ -315,7 +315,11 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, conn chan.appendLine("Starting cljs repl for: " + projectTypeName + "..."); state.extensionContext.workspaceState.update('cljsReplTypeHasBuilds', true); startCode = startCode.replace("%BUILDS%", builds.map(x => { return `"${x}"` }).join(" ")); - return evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); + const result = evalConnectCode(session, startCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]); + if (result) { + startedBuilds = builds; + } + return result; } else { chan.appendLine("Aborted starting cljs repl."); throw "Aborted"; From 34b0e333dec2398a5a713863d6523fbe9c3bc025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 13:23:58 +0200 Subject: [PATCH 102/128] Don't show build select if only one build --- calva/connector.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/calva/connector.ts b/calva/connector.ts index fb8449810..5e3e38de6 100644 --- a/calva/connector.ts +++ b/calva/connector.ts @@ -257,11 +257,11 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, conn useDefaultBuild = false; } else { if ((typeof initCode === 'object' || initCode.includes("%BUILD%"))) { - const projectBuilds = await figwheelOrShadowBuilds(cljsTypeName); build = await util.quickPickSingle({ - values: startedBuilds ? startedBuilds : projectBuilds, + values: startedBuilds ? startedBuilds : figwheelOrShadowBuilds(cljsTypeName), placeHolder: "Select which build to connect to", - saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build` + saveAs: `${state.getProjectRoot()}/${cljsTypeName.replace(" ", "-")}-build`, + autoSelect: true }); } } From 23f8c8249ddb181966bf6408850b0b562abff6a0 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Wed, 4 Sep 2019 17:22:14 +0200 Subject: [PATCH 103/128] Init circleci config --- .circleci/config.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..a0198029d --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,38 @@ + version: 2 + jobs: + test: + docker: + - image: circleci/openjdk:11.0.4-stretch-node + working_directory: ~/calva + steps: + - checkout: + path: ~/calva + - run: + name: Install node_modules + command: npm install + - run: + name: Install vsce + command: npm install -g vsce + - run: + name: Run Tests + command: echo Add Test execution + build: + docker: + - image: circleci/openjdk:11.0.4-stretch-node + branches: + only: + - master + working_directory: ~/calva + steps: + - checkout: + path: ~/calva + - run: + name: Install node_modules + command: npm install + - run: + name: Install vsce + command: npm install -g vsce + - run: + name: Publish to marketplace + context: Calva + command: vsce publish -p $PUBLISH_TOKEN \ No newline at end of file From f185fcd94a4201f858b394665db6c077c79c46ce Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Wed, 4 Sep 2019 18:08:45 +0200 Subject: [PATCH 104/128] Add workflow and a publish job --- .circleci/config.yml | 46 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a0198029d..410f6fff1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,4 @@ - version: 2 - jobs: + jobs: test: docker: - image: circleci/openjdk:11.0.4-stretch-node @@ -33,6 +32,43 @@ name: Install vsce command: npm install -g vsce - run: - name: Publish to marketplace - context: Calva - command: vsce publish -p $PUBLISH_TOKEN \ No newline at end of file + name: package vsix + command: vsce package + - run: + name: Copy vsix + command: | + mkdir /tmp/artifacts + cp *.vsix /tmp/artifacts/ + - store_artifacts: + path: /tmp/artifacts + - save_cache: + paths: /tmp/artifacts + key: calva-{{ .BuildNum }} + publish: + docker: + - image: circleci/openjdk:11.0.4-stretch-node + branches: + only: + - master + steps: + - restore_cache: + name: restoring previous cache + key: calva-{{ .BuildNum }} + - run: + name: + command: vsce publish --packagePath /tmp/artifacts/*.vsix -p $PUBLISH_TOKEN + workflows: + version: 2 + master-build-publish: + jobs: + - test: + filters: + branches: + only: + - master + - build: + requires: test + - publish: + requires: build + type: approval + From 74dba7f836b103d271c95b074febf7adfe554f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Wed, 4 Sep 2019 18:26:53 +0200 Subject: [PATCH 105/128] Add command for running tests --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 410f6fff1..53c0935e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,7 +14,7 @@ command: npm install -g vsce - run: name: Run Tests - command: echo Add Test execution + command: node cljs-out/test/cljs-lib-tests.js build: docker: - image: circleci/openjdk:11.0.4-stretch-node From 5f5874d5beaebc25046d90e3294942cb73eb56e1 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 13:26:58 +0200 Subject: [PATCH 106/128] Extract Version to file that gets saved to the cache Using extracted version for the publishing step Workflow only publish for master --- .circleci/config.yml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 53c0935e4..374363ab6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,6 +41,9 @@ cp *.vsix /tmp/artifacts/ - store_artifacts: path: /tmp/artifacts + - run: + name: Save version to file + command: 'cat package.json grep version head -1 awk -F: \'{ print $2 }\' sed \'s/[",]//g\' > /tmp/artifacts/version' - save_cache: paths: /tmp/artifacts key: calva-{{ .BuildNum }} @@ -55,20 +58,26 @@ name: restoring previous cache key: calva-{{ .BuildNum }} - run: - name: - command: vsce publish --packagePath /tmp/artifacts/*.vsix -p $PUBLISH_TOKEN + name: Install vsce + command: npm install -g vsce + - run: + name: Set Version variable + commend: export CALVA_VERSION=$(cat /tmp/artifacts/version) + - run: + name: Publish to the marketplace + command: vsce publish --packagePath /tmp/artifacts/calva-$CALVA_VERSION.vsix -p $PUBLISH_TOKEN workflows: version: 2 - master-build-publish: + build-publish: jobs: - - test: + - test + - build: + requires: test + - publish: filters: branches: only: - master - - build: - requires: test - - publish: requires: build type: approval From b597fc81922e824c3e541bd051697cb2815323ee Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 14:12:38 +0200 Subject: [PATCH 107/128] Other version extraction and some formating issues --- .circleci/config.yml | 109 ++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 374363ab6..b9c20322a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,83 +1,88 @@ - jobs: - test: +version: 2 +jobs: + test: docker: - - image: circleci/openjdk:11.0.4-stretch-node + - image: circleci/openjdk:11.0.4-stretch-node working_directory: ~/calva steps: - checkout: - path: ~/calva + path: ~/calva - run: - name: Install node_modules - command: npm install + name: Install node_modules + command: npm install - run: - name: Install vsce - command: npm install -g vsce + name: Install vsce + command: npm install -g vsce - run: - name: Run Tests - command: node cljs-out/test/cljs-lib-tests.js - build: - docker: - - image: circleci/openjdk:11.0.4-stretch-node - branches: - only: - - master - working_directory: ~/calva - steps: - - checkout: + name: Run Tests + command: node cljs-out/test/cljs-lib-tests.js + build: + docker: + - image: circleci/openjdk:11.0.4-stretch-node + branches: + only: + - master + working_directory: ~/calva + steps: + - checkout: path: ~/calva - - run: + - run: name: Install node_modules command: npm install - - run: + - run: name: Install vsce command: npm install -g vsce - - run: + - run: name: package vsix command: vsce package - - run: + - run: name: Copy vsix command: | mkdir /tmp/artifacts cp *.vsix /tmp/artifacts/ - - store_artifacts: + - store_artifacts: path: /tmp/artifacts - - run: + - run: name: Save version to file - command: 'cat package.json grep version head -1 awk -F: \'{ print $2 }\' sed \'s/[",]//g\' > /tmp/artifacts/version' - - save_cache: + command: "node -p 'require(\"./package.json\").version' > /tmp/artifacts/version" + - save_cache: paths: /tmp/artifacts key: calva-{{ .BuildNum }} - publish: - docker: - - image: circleci/openjdk:11.0.4-stretch-node - branches: - only: - - master - steps: - - restore_cache: - name: restoring previous cache - key: calva-{{ .BuildNum }} - - run: + publish: + docker: + - image: circleci/openjdk:11.0.4-stretch-node + branches: + only: + - master + steps: + - restore_cache: + name: restoring previous cache + key: calva-{{ .BuildNum }} + - run: name: Install vsce command: npm install -g vsce - - run: - name: Set Version variable - commend: export CALVA_VERSION=$(cat /tmp/artifacts/version) - - run: - name: Publish to the marketplace - command: vsce publish --packagePath /tmp/artifacts/calva-$CALVA_VERSION.vsix -p $PUBLISH_TOKEN - workflows: + - run: + name: Set Version variable + commend: export CALVA_VERSION=$(cat /tmp/artifacts/version) + - run: + name: Publish to the marketplace + command: vsce publish --packagePath /tmp/artifacts/calva-$CALVA_VERSION.vsix -p $PUBLISH_TOKEN +workflows: version: 2 - build-publish: + build_publish: jobs: - test - build: requires: test + filters: + branches: + only: + - master + - development - publish: - filters: - branches: - only: - - master requires: build - type: approval - + filters: + branches: + only: + - master + type: approval \ No newline at end of file From 0064e24e10dc2c3d44e7c20021074c79e7301850 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 14:21:46 +0200 Subject: [PATCH 108/128] Typo fixed other errors --- .circleci/config.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9c20322a..c65608ad3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,9 +19,6 @@ jobs: build: docker: - image: circleci/openjdk:11.0.4-stretch-node - branches: - only: - - master working_directory: ~/calva steps: - checkout: @@ -46,14 +43,12 @@ jobs: name: Save version to file command: "node -p 'require(\"./package.json\").version' > /tmp/artifacts/version" - save_cache: - paths: /tmp/artifacts + paths: + - /tmp/artifacts key: calva-{{ .BuildNum }} publish: docker: - image: circleci/openjdk:11.0.4-stretch-node - branches: - only: - - master steps: - restore_cache: name: restoring previous cache @@ -63,7 +58,7 @@ jobs: command: npm install -g vsce - run: name: Set Version variable - commend: export CALVA_VERSION=$(cat /tmp/artifacts/version) + command: export CALVA_VERSION=$(cat /tmp/artifacts/version) - run: name: Publish to the marketplace command: vsce publish --packagePath /tmp/artifacts/calva-$CALVA_VERSION.vsix -p $PUBLISH_TOKEN @@ -73,14 +68,16 @@ workflows: jobs: - test - build: - requires: test + requires: + - test filters: branches: only: - master - development - publish: - requires: build + requires: + - build filters: branches: only: From cacaf9f92c5844ece32cad7254701529d431bb63 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 14:28:07 +0200 Subject: [PATCH 109/128] Remvod vsce in test job, also install vsce as sudo --- .circleci/config.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c65608ad3..c578d01bc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,9 +10,6 @@ jobs: - run: name: Install node_modules command: npm install - - run: - name: Install vsce - command: npm install -g vsce - run: name: Run Tests command: node cljs-out/test/cljs-lib-tests.js @@ -28,7 +25,7 @@ jobs: command: npm install - run: name: Install vsce - command: npm install -g vsce + command: sudo npm install -g vsce - run: name: package vsix command: vsce package @@ -55,7 +52,7 @@ jobs: key: calva-{{ .BuildNum }} - run: name: Install vsce - command: npm install -g vsce + command: sudo npm install -g vsce - run: name: Set Version variable command: export CALVA_VERSION=$(cat /tmp/artifacts/version) From 2b967917309963789016e0cd3eee9f9e4b781cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 14:38:24 +0200 Subject: [PATCH 110/128] Compile tests (which also runs them) --- .circleci/config.yml | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c578d01bc..7e99a7473 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,8 +11,8 @@ jobs: name: Install node_modules command: npm install - run: - name: Run Tests - command: node cljs-out/test/cljs-lib-tests.js + name: Compile and Run Tests + command: npm run compile-cljs build: docker: - image: circleci/openjdk:11.0.4-stretch-node diff --git a/package.json b/package.json index d6d8e00bb..2a5adc568 100644 --- a/package.json +++ b/package.json @@ -1149,7 +1149,7 @@ "NOT-USED-start": "ts-node --inspect=5858 webview-src/server/main.ts", "NOT-USED-repl-window-dev-server": "concurrently \"npx nodemon -- ./webview-src/client/index.js\" \"npx webpack-dev-server\"", "webpack-release": "webpack --mode production", - "compile-cljs": "npx shadow-cljs compile :calva-lib", + "compile-cljs": "npx shadow-cljs compile :calva-lib :test", "compile": "npm run compile-cljs && npm run webpack", "update-grammar": "node ./calva/calva-fmt/update-grammar.js ./calva/calva-fmt/atom-language-clojure/grammars/clojure.cson clojure.tmLanguage.json", "release": "npm i && npm run update-grammar && npm run release-cljs && npm run webpack-release", From 7c4c563065f7ef84b83531f8f98d783c99ec44f6 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 14:53:27 +0200 Subject: [PATCH 111/128] Add context to publish, remvo filters for build --- .circleci/config.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7e99a7473..2828bd742 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,11 +67,6 @@ workflows: - build: requires: - test - filters: - branches: - only: - - master - - development - publish: requires: - build @@ -79,4 +74,5 @@ workflows: branches: only: - master + context: Calva type: approval \ No newline at end of file From 73127bfa331c4a53da7ede10e08a78a9d039175e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 15:59:20 +0200 Subject: [PATCH 112/128] Trying w/o quotes for version extract command --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2828bd742..550969b9e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,7 +38,7 @@ jobs: path: /tmp/artifacts - run: name: Save version to file - command: "node -p 'require(\"./package.json\").version' > /tmp/artifacts/version" + command: node -p 'require("./package.json").version' > /tmp/artifacts/version - save_cache: paths: - /tmp/artifacts From aba76a119a617aa4f799e6bb8fcbcf1fe3ab7d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 17:24:59 +0200 Subject: [PATCH 113/128] Change to publishing from `release` branch --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 550969b9e..c926ef074 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -73,6 +73,6 @@ workflows: filters: branches: only: - - master + - release context: Calva type: approval \ No newline at end of file From 01552075ab69daac6ba2ac3d4d2a7e2b160137f5 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 17:55:03 +0200 Subject: [PATCH 114/128] Add approval job to workflow --- .circleci/config.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c926ef074..becd62a40 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -67,12 +67,16 @@ workflows: - build: requires: - test - - publish: + - publish-approval: requires: - build filters: branches: only: - release + type: approval + - publish: + requires: + - publish-approval context: Calva - type: approval \ No newline at end of file + \ No newline at end of file From 0ddb414a3801e7477424e57606d9a194d72f82b7 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 19:19:34 +0200 Subject: [PATCH 115/128] Using workspace instead of cache to get vsix to next job --- .circleci/config.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index becd62a40..6d099dacf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,17 +39,16 @@ jobs: - run: name: Save version to file command: node -p 'require("./package.json").version' > /tmp/artifacts/version - - save_cache: + - persist_to_workspace: + root: /tmp paths: - - /tmp/artifacts - key: calva-{{ .BuildNum }} + - artifacts publish: docker: - image: circleci/openjdk:11.0.4-stretch-node steps: - - restore_cache: - name: restoring previous cache - key: calva-{{ .BuildNum }} + - attach_workspace: + at: /tmp - run: name: Install vsce command: sudo npm install -g vsce From aa60ba060e5829e6d17fdd5c777516530b64e9c5 Mon Sep 17 00:00:00 2001 From: Kevin Stehn Date: Thu, 5 Sep 2019 19:26:25 +0200 Subject: [PATCH 116/128] Using cache for node-modules --- .circleci/config.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d099dacf..605a8fff4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,9 +7,17 @@ jobs: steps: - checkout: path: ~/calva + - restore_cache: + name: Restore node-modules + key: dependency-cache-{{ checksum "package.json" }} - run: name: Install node_modules command: npm install + - save_cache: + name: Save node-modules + key: dependency-cache-{{ checksum "package.json" }} + paths: + - ./node_modules - run: name: Compile and Run Tests command: npm run compile-cljs @@ -20,9 +28,17 @@ jobs: steps: - checkout: path: ~/calva + - restore_cache: + name: Restore node-modules + key: dependency-cache-{{ checksum "package.json" }} - run: name: Install node_modules command: npm install + - save_cache: + name: Save node-modules + key: dependency-cache-{{ checksum "package.json" }} + paths: + - ./node_modules - run: name: Install vsce command: sudo npm install -g vsce From 9aca8339d49b504c1ed0313b73584d1663c777dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 20:43:39 +0200 Subject: [PATCH 117/128] Find out version number the same way in publish --- .circleci/config.yml | 26 ++++++-------------------- package.json | 3 ++- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 605a8fff4..c2babdd45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,20 +31,9 @@ jobs: - restore_cache: name: Restore node-modules key: dependency-cache-{{ checksum "package.json" }} - - run: - name: Install node_modules - command: npm install - - save_cache: - name: Save node-modules - key: dependency-cache-{{ checksum "package.json" }} - paths: - - ./node_modules - - run: - name: Install vsce - command: sudo npm install -g vsce - run: name: package vsix - command: vsce package + command: npx vsce package - run: name: Copy vsix command: | @@ -52,9 +41,6 @@ jobs: cp *.vsix /tmp/artifacts/ - store_artifacts: path: /tmp/artifacts - - run: - name: Save version to file - command: node -p 'require("./package.json").version' > /tmp/artifacts/version - persist_to_workspace: root: /tmp paths: @@ -65,15 +51,15 @@ jobs: steps: - attach_workspace: at: /tmp - - run: - name: Install vsce - command: sudo npm install -g vsce + - restore_cache: + name: Restore node-modules + key: dependency-cache-{{ checksum "package.json" }} - run: name: Set Version variable - command: export CALVA_VERSION=$(cat /tmp/artifacts/version) + command: export CALVA_VERSION=$(node -p 'require("./package.json").version') - run: name: Publish to the marketplace - command: vsce publish --packagePath /tmp/artifacts/calva-$CALVA_VERSION.vsix -p $PUBLISH_TOKEN + command: npx vsce publish --packagePath /tmp/artifacts/calva-$CALVA_VERSION.vsix -p $PUBLISH_TOKEN workflows: version: 2 build_publish: diff --git a/package.json b/package.json index 2a5adc568..7a5fc5fa7 100644 --- a/package.json +++ b/package.json @@ -1204,6 +1204,7 @@ "vscode": "^1.1.34", "webpack": "^4.33.0", "webpack-cli": "^3.3.3", - "webpack-dev-server": "^3.8.0" + "webpack-dev-server": "^3.8.0", + "vsce": "1.66.0" } } From 6e1316491c311ba5110b53f925969882182e14b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 20:59:18 +0200 Subject: [PATCH 118/128] Checkout calva in publish step --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c2babdd45..050bc08dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: name: Save node-modules key: dependency-cache-{{ checksum "package.json" }} paths: - - ./node_modules + - ./ - run: name: Compile and Run Tests command: npm run compile-cljs @@ -51,6 +51,8 @@ jobs: steps: - attach_workspace: at: /tmp + - checkout: + path: ~/calva - restore_cache: name: Restore node-modules key: dependency-cache-{{ checksum "package.json" }} From 594380eeee9ba7175742d72202be10421f29beb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 21:08:21 +0200 Subject: [PATCH 119/128] Need working directory too --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 050bc08dc..6fbbc935a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,6 +48,7 @@ jobs: publish: docker: - image: circleci/openjdk:11.0.4-stretch-node + working_directory: ~/calva steps: - attach_workspace: at: /tmp From 291fe261af2ad86c223f8627b6d201c7a93b173b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 21:23:20 +0200 Subject: [PATCH 120/128] Pick up version JIT --- .circleci/config.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6fbbc935a..d594c7b2f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,12 +57,9 @@ jobs: - restore_cache: name: Restore node-modules key: dependency-cache-{{ checksum "package.json" }} - - run: - name: Set Version variable - command: export CALVA_VERSION=$(node -p 'require("./package.json").version') - run: name: Publish to the marketplace - command: npx vsce publish --packagePath /tmp/artifacts/calva-$CALVA_VERSION.vsix -p $PUBLISH_TOKEN + command: npx vsce publish --packagePath /tmp/artifacts/calva-$(node -p 'require("./package.json").version').vsix -p ${PUBLISH_TOKEN} workflows: version: 2 build_publish: From b592d689c415ca7fe50c21666d1d152d416860c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 21:38:51 +0200 Subject: [PATCH 121/128] Cache maven deps --- .circleci/config.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d594c7b2f..968cee073 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,10 +14,11 @@ jobs: name: Install node_modules command: npm install - save_cache: - name: Save node-modules + name: Save dependencies key: dependency-cache-{{ checksum "package.json" }} paths: - - ./ + - ./node_modules + - ~/.m2 - run: name: Compile and Run Tests command: npm run compile-cljs @@ -29,7 +30,7 @@ jobs: - checkout: path: ~/calva - restore_cache: - name: Restore node-modules + name: Restore dependencies key: dependency-cache-{{ checksum "package.json" }} - run: name: package vsix @@ -55,7 +56,7 @@ jobs: - checkout: path: ~/calva - restore_cache: - name: Restore node-modules + name: Restore dependencies key: dependency-cache-{{ checksum "package.json" }} - run: name: Publish to the marketplace From 15e505f992d2e0f1fc84cc27a940140b28561e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 22:02:18 +0200 Subject: [PATCH 122/128] Typo: Restoring more than just node-modules --- .circleci/config.yml | 2 +- package-lock.json | 338 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 968cee073..e488120de 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: - checkout: path: ~/calva - restore_cache: - name: Restore node-modules + name: Restore dependencies key: dependency-cache-{{ checksum "package.json" }} - run: name: Install node_modules diff --git a/package-lock.json b/package-lock.json index 9b3323ee3..d27543bae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -630,6 +630,18 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "azure-devops-node-api": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-7.2.0.tgz", + "integrity": "sha512-pMfGJ6gAQ7LRKTHgiRF+8iaUUeGAI0c8puLaqHLc7B8AR7W6GJLozK9RFeUHFjEGybC9/EB3r67WPd7e46zQ8w==", + "dev": true, + "requires": { + "os": "0.1.1", + "tunnel": "0.0.4", + "typed-rest-client": "1.2.0", + "underscore": "1.8.3" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -851,6 +863,12 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -1007,6 +1025,12 @@ "isarray": "^1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1159,6 +1183,20 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, "chokidar": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", @@ -1678,6 +1716,24 @@ } } }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1941,6 +1997,12 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -1988,6 +2050,12 @@ "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz", "integrity": "sha1-ji1geottef6IC1SLxYzGvrKIxPM=" }, + "didyoumean": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz", + "integrity": "sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=", + "dev": true + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2039,12 +2107,47 @@ "esutils": "^2.0.2" } }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -2720,6 +2823,15 @@ "websocket-driver": ">=0.5.1" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -3977,6 +4089,48 @@ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", "dev": true }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -4740,6 +4894,15 @@ "immediate": "~3.0.5" } }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -4877,6 +5040,19 @@ "object-visit": "^1.0.0" } }, + "markdown-it": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", + "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~1.1.1", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -4888,6 +5064,12 @@ "safe-buffer": "^5.1.2" } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8569,6 +8751,15 @@ "set-blocking": "~2.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -8720,12 +8911,24 @@ "url-parse": "^1.4.3" } }, + "os": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", + "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=", + "dev": true + }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -8782,6 +8985,16 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -8942,6 +9155,24 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=", + "dev": true, + "requires": { + "semver": "^5.1.0" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -9032,6 +9263,12 @@ "sha.js": "^2.4.8" } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -9437,6 +9674,15 @@ "strip-json-comments": "~2.0.1" } }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, "read-pkg": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", @@ -10938,6 +11184,12 @@ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, + "tunnel": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", + "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -10987,6 +11239,16 @@ } } }, + "typed-rest-client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.2.0.tgz", + "integrity": "sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw==", + "dev": true, + "requires": { + "tunnel": "0.0.4", + "underscore": "1.8.3" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -10999,6 +11261,12 @@ "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", "dev": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -11014,6 +11282,12 @@ "debug": "^2.2.0" } }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -11192,6 +11466,12 @@ } } }, + "url-join": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", + "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=", + "dev": true + }, "url-loader": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", @@ -11302,6 +11582,45 @@ "indexof": "0.0.1" } }, + "vsce": { + "version": "1.66.0", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.66.0.tgz", + "integrity": "sha512-Zf4+WD4PhEcOr7jkU08SI9lwFqDhmhk73YOCGQ/tNLaBy+PnnX4eSdqj9LdzDLuI2dsyomJLXzDSNgxuaInxCQ==", + "dev": true, + "requires": { + "azure-devops-node-api": "^7.2.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.1", + "commander": "^2.8.1", + "denodeify": "^1.2.1", + "didyoumean": "^1.2.1", + "glob": "^7.0.6", + "lodash": "^4.17.10", + "markdown-it": "^8.3.1", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "osenv": "^0.1.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^5.1.0", + "tmp": "0.0.29", + "typed-rest-client": "1.2.0", + "url-join": "^1.1.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "dependencies": { + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, "vscode": { "version": "1.1.34", "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.34.tgz", @@ -11824,6 +12143,25 @@ "decamelize": "^1.2.0" } }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3" + } + }, "zone.js": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.7.6.tgz", From e6e9cc93279f03557e4eea093601e7e8a236e8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 22:23:07 +0200 Subject: [PATCH 123/128] Save the cache after running shadow-cljs --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e488120de..b857b5f20 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,15 +13,15 @@ jobs: - run: name: Install node_modules command: npm install + - run: + name: Compile and Run Tests + command: npm run compile-cljs - save_cache: name: Save dependencies key: dependency-cache-{{ checksum "package.json" }} paths: - ./node_modules - ~/.m2 - - run: - name: Compile and Run Tests - command: npm run compile-cljs build: docker: - image: circleci/openjdk:11.0.4-stretch-node From 07632136743b8349240bfe4cd334a1205bb7da1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Thu, 5 Sep 2019 23:04:58 +0200 Subject: [PATCH 124/128] Append commit-sha to vsix version if not release --- .circleci/config.yml | 3 +++ package-lock.json | 5 +++++ package.json | 1 + 3 files changed, 9 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b857b5f20..18d90bc78 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,6 +32,9 @@ jobs: - restore_cache: name: Restore dependencies key: dependency-cache-{{ checksum "package.json" }} + - run: + name: Tamper Calva version if not release + command: test "release" = "${CIRCLE_BRANCH}" && echo 'No version tampering on release branch' || npx json -I -f package.json -e 'this.version=this.version.replace(/$/, "-'${CIRCLE_SHA1:0:8}'")' - run: name: package vsix command: npx vsce package diff --git a/package-lock.json b/package-lock.json index d27543bae..20527234e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4774,6 +4774,11 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "json": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/json/-/json-9.0.6.tgz", + "integrity": "sha1-eXLCpaSKQmeNsnMMfCxO5uTiRYU=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", diff --git a/package.json b/package.json index 7a5fc5fa7..3478061fa 100644 --- a/package.json +++ b/package.json @@ -1168,6 +1168,7 @@ "find": "0.2.9", "immutable": "3.8.1", "immutable-cursor": "2.0.1", + "json": "^9.0.6", "jszip": "3.1.3", "lodash": "^4.17.14", "net": "1.0.2", From 3e8f5e07aacc9386145acdb0c13c326e70ca4fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 6 Sep 2019 13:19:54 +0200 Subject: [PATCH 125/128] Update jack-in info in README --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 351f04460..776bd10b1 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,21 @@ Welcome to [Calva](https://marketplace.visualstudio.com/items?itemName=bettertha *Calva is short for Calvados, a liquid gifted to humanity from God. It is distilled from [Cider](https://cider.mx/).* +Latest build on `master`: + +[![CircleCI](https://circleci.com/gh/BetterThanTomorrow/calva.svg?style=svg)](https://circleci.com/gh/BetterThanTomorrow/calva) + ## Raison d´être -With Calva we hope to help lower the barriers into the Clojure world. The idea is that by leveraging the strengths of VS Code and nREPL, we can offer a turn-key, productive environment in which to learn and use Clojure and ClojureScript. +With Calva we want to help to lower the barriers into the Clojure world. By leveraging the strengths of VS Code and nREPL, we think we can offer an environment in which it is easy to get started with Clojure and ClojureScript, and wich is productive enough for seasoned Clojurians to stay with. ## How to Connect Calva to your project -Connect by letting Calva start your project (_a.k.a. **Jack-in**_). +Let Calva start your project (_a.k.a. **Jack-in**_). -1. Open your project in VS Code. -1. Issue the command **Start a REPL project and connect**: `ctrl+alt+c ctrl+alt+j`. -1. Answer the quick-pick prompts telling Calva about project types and what profiles to start. +1. Open your project in VS Code, and a file in the project. From the command line it could be something like: `code path/to/myproject README.md`. +1. Issue the command **Start a REPL project and connect (aka Jack-in)**: `ctrl+alt+c ctrl+alt+j`. +1. Answer the prompts where Calva asks you a few things about your project. When Calva has connected, it will open a REPL window giving you some getting started tips, and you can start hacking. The first thing you should always do to ”wake” Calva is to load/evaluate the current Clojure(Script) file: `ctrl+alt+c enter`. @@ -40,6 +44,7 @@ When something doesn't work and you think there might be a workaround for it, pl ## Features ### At a glance +- Quickly and easily get your REPL connected - Evaluate code inline - Run tests - Integrated REPL windows From 558602a00256337adb162af9cb995bd39e55703a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 6 Sep 2019 16:29:03 +0200 Subject: [PATCH 126/128] Bump version and add branch name and sha --- .circleci/config.yml | 9 +++- package-lock.json | 125 +++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 3 files changed, 131 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 18d90bc78..6c038ada5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,7 +34,14 @@ jobs: key: dependency-cache-{{ checksum "package.json" }} - run: name: Tamper Calva version if not release - command: test "release" = "${CIRCLE_BRANCH}" && echo 'No version tampering on release branch' || npx json -I -f package.json -e 'this.version=this.version.replace(/$/, "-'${CIRCLE_SHA1:0:8}'")' + command: | + if [ "release" = "${CIRCLE_BRANCH}" ] + then + echo 'No version tampering on release branch' + else + echo "Bump version and append some commit sha" + echo npx json -I -f package.json -e 'const semver = require("semver");this.version=semver.inc(this.version,"patch").replace(/$/,"'-${CIRCLE_BRANCH}-${CIRCLE_SHA1:0:8}'")' + fi - run: name: package vsix command: npx vsce package diff --git a/package-lock.json b/package-lock.json index 20527234e..fea9f2b44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1885,6 +1885,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "execa": { @@ -2043,6 +2051,13 @@ "integrity": "sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=", "requires": { "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } } }, "diagnostic-channel-publishers": { @@ -2385,6 +2400,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, @@ -2464,6 +2485,14 @@ "minimatch": "^3.0.4", "resolve": "^1.3.3", "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "eslint-plugin-promise": { @@ -5012,6 +5041,12 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, @@ -5611,6 +5646,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -5641,6 +5682,14 @@ "is-builtin-module": "^1.0.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "normalize-path": { @@ -8956,6 +9005,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "execa": { @@ -9067,6 +9124,14 @@ "registry-auth-token": "^3.0.1", "registry-url": "^3.0.3", "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "pako": { @@ -9167,6 +9232,14 @@ "dev": true, "requires": { "semver": "^5.1.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "parse5": { @@ -10118,9 +10191,9 @@ } }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "semver-diff": { "version": "2.1.0", @@ -10129,6 +10202,14 @@ "dev": true, "requires": { "semver": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "send": { @@ -11145,6 +11226,14 @@ "loader-utils": "^1.0.2", "micromatch": "^3.1.4", "semver": "^5.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "tslib": { @@ -11172,6 +11261,14 @@ "semver": "^5.3.0", "tslib": "^1.8.0", "tsutils": "^2.29.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "tsutils": { @@ -11615,6 +11712,12 @@ "yazl": "^2.2.2" }, "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, "tmp": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", @@ -11639,6 +11742,14 @@ "source-map-support": "^0.5.0", "url-parse": "^1.4.4", "vscode-test": "^0.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "vscode-extension-telemetry": { @@ -11790,6 +11901,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "supports-color": { diff --git a/package.json b/package.json index 3478061fa..a8e82c2d3 100644 --- a/package.json +++ b/package.json @@ -1176,6 +1176,7 @@ "open": "^6.3.0", "paredit.js": "^0.3.6", "parinfer": "^3.12.0", + "semver": "^6.3.0", "universal-analytics": "^0.4.20", "vscode-extension-telemetry": "0.0.15", "wsl-path": "^1.1.0" From b30eeb8d903c5fd59b81bf815e2609d6b52c7cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Fri, 6 Sep 2019 21:40:40 +0200 Subject: [PATCH 127/128] Use `npx version` instead of `semver` --- .circleci/config.yml | 8 +++++++- package.json | 5 ++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fb2ee7576..f05e40ec9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,7 +40,13 @@ jobs: echo 'No version tampering on release branch' else echo "Bump version and append some commit sha" - npx json -I -f package.json -e 'const semver = require("semver");this.version=semver.inc(this.version,"patch").replace(/$/,"'-${CIRCLE_BRANCH}-${CIRCLE_SHA1:0:8}'")' + npm config set git-tag-version false + npm version patch + BRANCH=${CIRCLE_BRANCH//\//-} + COMMIT=${CIRCLE_SHA1:0:8} + PRERELEASE=${BRANCH}-${COMMIT} + echo "Addding prerelease: ${PRERELEASE}" + npx json -I -f package.json -e 'this.version=this.version.replace(/$/,"-'${PRERELEASE}'")' fi - run: name: package vsix diff --git a/package.json b/package.json index a8e82c2d3..a2f73fcf1 100644 --- a/package.json +++ b/package.json @@ -1168,7 +1168,6 @@ "find": "0.2.9", "immutable": "3.8.1", "immutable-cursor": "2.0.1", - "json": "^9.0.6", "jszip": "3.1.3", "lodash": "^4.17.14", "net": "1.0.2", @@ -1176,7 +1175,6 @@ "open": "^6.3.0", "paredit.js": "^0.3.6", "parinfer": "^3.12.0", - "semver": "^6.3.0", "universal-analytics": "^0.4.20", "vscode-extension-telemetry": "0.0.15", "wsl-path": "^1.1.0" @@ -1193,6 +1191,7 @@ "eslint-plugin-promise": "^3.7.0", "eslint-plugin-standard": "^3.0.1", "file-loader": "^3.0.1", + "json": "^9.0.6", "node-gyp": "^5.0.0", "nodemon": "^1.19.1", "sass": "^1.0.0-beta.3", @@ -1207,6 +1206,6 @@ "webpack": "^4.33.0", "webpack-cli": "^3.3.3", "webpack-dev-server": "^3.8.0", - "vsce": "1.66.0" + "vsce": "^1.66.0" } } From 87924883b8a501e1f023f73d1191e44daac08a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Str=C3=B6mberg?= Date: Tue, 10 Sep 2019 13:13:48 +0200 Subject: [PATCH 128/128] Add errorMessage at start if old cljsRepl settings --- calva/extension.ts | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/calva/extension.ts b/calva/extension.ts index dfdc80a42..e4e5d22e3 100644 --- a/calva/extension.ts +++ b/calva/extension.ts @@ -21,6 +21,8 @@ import * as replWindow from "./repl-window"; import { format } from 'url'; import * as greetings from "./greet"; import Analytics from './analytics'; +import * as open from 'open'; + import { edit } from './paredit/utils'; function onDidSave(document) { @@ -63,10 +65,28 @@ function onDidOpen(document) { function activate(context: vscode.ExtensionContext) { state.cursor.set('analytics', new Analytics(context)); state.analytics().logPath("/start").logEvent("LifeCycle", "Started").send(); + + const chan = state.outputChannel(); + - let legacyExtension = vscode.extensions.getExtension('cospaia.clojure4vscode'), + const legacyExtension = vscode.extensions.getExtension('cospaia.clojure4vscode'), fmtExtension = vscode.extensions.getExtension('cospaia.calva-fmt'), - pareEditExtension = vscode.extensions.getExtension('cospaia.paredit-revived'); + pareEditExtension = vscode.extensions.getExtension('cospaia.paredit-revived'), + customCljsRepl = state.config().customCljsRepl, + replConnectSequences = state.config().replConnectSequences, + BUTTON_GOTO_WIKI = "Open the Wiki", + BUTTON_OK = "Got it", + WIKI_URL = "https://github.com/BetterThanTomorrow/calva/wiki/Custom-Connect-Sequences"; + + if (customCljsRepl && replConnectSequences.length == 0) { + chan.appendLine("Old customCljsRepl settings detected."); + vscode.window.showErrorMessage("Old customCljsRepl settings detected. You need to specifiy it using the new calva.customConnectSequence setting. See the Calva wiki for instructions.", ...[BUTTON_GOTO_WIKI, BUTTON_OK]) + .then(v => { + if (v == BUTTON_GOTO_WIKI) { + open(WIKI_URL); + } + }) + } if (legacyExtension) { vscode.window.showErrorMessage("Calva Legacy extension detected. Things will break. Please uninstall, or disable, the old Calva extension.", ...["Roger that. Right away!"]) @@ -87,7 +107,6 @@ function activate(context: vscode.ExtensionContext) { replWindow.activate(context); - let chan = state.outputChannel(); chan.appendLine("Calva activated."); let { lint, @@ -174,9 +193,12 @@ function activate(context: vscode.ExtensionContext) { vscode.commands.executeCommand("setContext", "calva:pareditValid", false); } status.update(); - if (editor && editor.document && editor.document.fileName.match(/\.clj[cs]?/).length && state.config().syncReplNamespaceToCurrentFile) { - replWindow.setREPLNamespace(util.getDocumentNamespace(editor.document)) - .catch(reasons => { console.warn(`Namespace sync failed, becauase: ${reasons}`) }); + if (editor && editor.document && editor.document.fileName) { + const fileExtIfClj = editor.document.fileName.match(/\.clj[cs]?/); + if (fileExtIfClj && fileExtIfClj.length && state.config().syncReplNamespaceToCurrentFile) { + replWindow.setREPLNamespace(util.getDocumentNamespace(editor.document)) + .catch(reasons => { console.warn(`Namespace sync failed, becauase: ${reasons}`) }); + } } })); context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(annotations.onDidChangeTextDocument))