diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8d00908..7b9586f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Changes to Calva. ## [Unreleased] +- [Send evaluation results and output to a file](https://github.com/BetterThanTomorrow/calva/issues/681) ## [2.0.107] - 2020-06-16 - Fix [Flicker matching brackets as code is typed](https://github.com/BetterThanTomorrow/calva/issues/673) diff --git a/package.json b/package.json index 88e280f12..c25bd2c31 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,8 @@ ".clojure", ".edn", ".joke", - ".boot" + ".boot", + ".calva-out" ] } ], diff --git a/src/connector.ts b/src/connector.ts index c08ae4772..668d7569b 100644 --- a/src/connector.ts +++ b/src/connector.ts @@ -12,6 +12,7 @@ import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType, CljsTypes, ask import { disabledPrettyPrinter } from './printer'; import { keywordize } from './util/string'; import { REQUESTS, initializeDebugger } from './debugger/calva-debug'; +import * as resultsOutput from './result-output' async function createAndConnectReplWindow(session: NReplSession, mode: "clj" | "cljs", ): Promise { if (state.config().openREPLWindowOnConnect) { @@ -60,6 +61,7 @@ async function connectToHost(hostname, port, connectSequence: ReplConnectSequenc state.cursor.set('clj', cljSession); state.cursor.set('cljc', cljSession); status.update(); + resultsOutput.openResultsDoc(true); // Initialize debugger await initializeDebugger(cljSession); diff --git a/src/evaluate.ts b/src/evaluate.ts index ed31dcfda..77bc617db 100644 --- a/src/evaluate.ts +++ b/src/evaluate.ts @@ -9,6 +9,9 @@ import { activeReplWindow } from './repl-window'; import { NReplSession, NReplEvaluation } from './nrepl'; import statusbar from './statusbar'; import { PrettyPrintingOptions } from './printer'; +import * as resultsOutput from './result-output' + + function interruptAllEvaluations() { @@ -92,6 +95,7 @@ async function evaluateSelection(document, options) { stdout: (m) => { out.push(m); chan.appendLine(normalizeNewLines(m)); + resultsOutput.appendToResultsDoc(normalizeNewLines(m)); }, stderr: m => err.push(m), pprintOptions: pprintOptions @@ -115,11 +119,14 @@ async function evaluateSelection(document, options) { if (!asComment) { chan.appendLine('=>'); chan.appendLine(value); + resultsOutput.appendToResultsDoc(value); } if (err.length > 0) { chan.appendLine("Error:") chan.appendLine(normalizeNewLinesAndJoin(err)); + resultsOutput.appendToResultsDoc(";Error:") + resultsOutput.appendToResultsDoc(`;${normalizeNewLinesAndJoin(err)}`); } } catch (e) { if (!err.length) { // venantius/ultra outputs errors on stdout, it seems. @@ -128,6 +135,8 @@ async function evaluateSelection(document, options) { if (err.length > 0) { chan.appendLine("Error:") chan.appendLine(normalizeNewLinesAndJoin(err)); + resultsOutput.appendToResultsDoc(";Error:") + resultsOutput.appendToResultsDoc(`;${normalizeNewLinesAndJoin(err)}`); } const message = util.stripAnsi(err.join("\n")); @@ -192,6 +201,7 @@ async function loadFile(document, callback: () => { }, pprintOptions: PrettyPrin if (doc && doc.languageId == "clojure" && fileType != "edn" && current.get('connected')) { state.analytics().logEvent("Evaluation", "LoadFile").send(); chan.appendLine("Evaluating file: " + fileName); + resultsOutput.appendToResultsDoc(";Evaluating file: " + fileName); let res = client.loadFile(doc.getText(), { fileName: fileName, @@ -203,11 +213,14 @@ async function loadFile(document, callback: () => { }, pprintOptions: PrettyPrin await res.value.then((value) => { if (value) { chan.appendLine("=> " + value); + resultsOutput.appendToResultsDoc(value); } else { chan.appendLine("No results from file evaluation."); + resultsOutput.appendToResultsDoc(";No results from file evaluation."); } }).catch((e) => { chan.appendLine(`Evaluation of file ${fileName} failed: ${e}`); + resultsOutput.appendToResultsDoc(`;Evaluation of file ${fileName} failed: ${e}`); }); } if (callback) { diff --git a/src/result-output.ts b/src/result-output.ts new file mode 100644 index 000000000..9238bf552 --- /dev/null +++ b/src/result-output.ts @@ -0,0 +1,96 @@ +import * as os from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import * as state from './state'; + +const RESULTS_DOC_NAME = 'eval-results.calva-out'; + +function getResultsUri(untitled: boolean): vscode.Uri { + return vscode.Uri.parse((untitled ? 'untitled:' : '') + path.join(os.tmpdir(), RESULTS_DOC_NAME)); +} + +export async function openResultsDoc(clear: boolean = false): Promise { + let exists: boolean = false; + let resultsDoc: vscode.TextDocument; + try { + const stat = await vscode.workspace.fs.stat(getResultsUri(false)); + exists = true; + } catch { + exists = false; + } + await vscode.workspace.openTextDocument(getResultsUri(!exists)).then(async doc => { + resultsDoc = doc; + if (clear) { + var edit = new vscode.WorkspaceEdit(); + edit.delete(getResultsUri(false), new vscode.Range( + new vscode.Position(0, 0), + doc.positionAt(doc.getText().length) + )); + const success = await vscode.workspace.applyEdit(edit); + if (!success) { + state.deref().outputChannel().appendLine('Error clearing output document.') + } + } + vscode.window.showTextDocument(doc, 1, true); + }); + return resultsDoc; +} + +export function revealResultsDoc() { + openResultsDoc().then(doc => { + vscode.window.showTextDocument(doc); + }); +} + +let scrollToBottomSub: vscode.Disposable; + +export function appendToResultsDoc(text: string, reveal: boolean = false) { + const edit = new vscode.WorkspaceEdit(); + vscode.workspace.openTextDocument(getResultsUri(false)).then(doc => { + edit.insert(getResultsUri(false), doc.positionAt(Infinity), `${text}\n`); + if (scrollToBottomSub) { + scrollToBottomSub.dispose(); + } + scrollToBottomSub = vscode.window.onDidChangeActiveTextEditor((editor) => { + if (path.basename(editor.document.fileName) === RESULTS_DOC_NAME) { + const lastPos = editor.document.positionAt(Infinity); + editor.selection = new vscode.Selection(lastPos, lastPos); + editor.revealRange(new vscode.Range(lastPos, lastPos)); + console.log("Scrolled to bottom") + scrollToBottomSub.dispose(); + } + }); + state.extensionContext.subscriptions.push(scrollToBottomSub); + vscode.workspace.applyEdit(edit).then( + success => { + if (!success) { + console.log("Sad puppy") + } + }, + reason => { + console.error(`Error appending output to: ${getResultsUri(false).path}`); + console.error(reason) + } + ); + }) +} + +// function createFileWithContent(filename, content) { +// var newFile = vscode.Uri.parse('untitled:' + path.join(os.homedir(), filename)); +// vscode.workspace.openTextDocument(newFile).then(function (document) { +// var edit = new vscode.WorkspaceEdit(); +// edit.delete(newFile, new vscode.Range( +// document.positionAt(0), +// document.positionAt(document.getText().length - 1) +// )); +// return vscode.workspace.applyEdit(edit).then(function (success) { +// var edit = new vscode.WorkspaceEdit(); +// edit.insert(newFile, new vscode.Position(0, 0), content); +// return vscode.workspace.applyEdit(edit).then(function (success) { +// if (success) { +// vscode.window.showTextDocument(document); +// } +// }); +// }); +// }); +// } diff --git a/src/utilities.ts b/src/utilities.ts index afa807b6c..76b7f108f 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -250,7 +250,7 @@ function getSession(fileType = undefined): NReplSession { if (fileType.match(/^clj[sc]?/)) { return current.get(fileType); } else { - return current.get('clj'); + return current.get('cljc'); } } @@ -402,7 +402,7 @@ function updateREPLSessionType() { sessionType = 'cljs' else if (fileType == 'clj' && getSession('clj') !== null) sessionType = 'clj' - else if (fileType == 'cljc' && getSession('cljc') !== null) + else if (getSession('cljc') !== null) sessionType = getSession('cljc') == getSession('clj') ? 'clj' : 'cljs'; else sessionType = 'clj'