Skip to content

Commit

Permalink
Merge pull request #1053 from BetterThanTomorrow/use_cljs_for_state
Browse files Browse the repository at this point in the history
Use cljs for state
  • Loading branch information
bpringe authored Mar 10, 2021
2 parents 2c67681 + 2a20e8e commit d364d43
Show file tree
Hide file tree
Showing 36 changed files with 18,364 additions and 1,314 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Changes to Calva.

## [Unreleased]
- Implementation detail: [Use cljs for state](https://github.com/BetterThanTomorrow/calva/pull/1053)

## [2.0.178] - 2021-03-09
- [Add command for evaluating from start of list to cursor](https://github.com/BetterThanTomorrow/calva/issues/1057)
Expand Down
18,907 changes: 17,952 additions & 955 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion shadow-cljs.edn
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
:deactivateLsp calva.lsp/deactivate
:getReferences calva.lsp/get-references
:getDocumentSymbols calva.lsp/get-document-symbols
:LSP_CLIENT_KEY calva.lsp/client-key}
:LSP_CLIENT_KEY calva.lsp/client-key
:setStateValue calva.state/set-state-value!
:getStateValue calva.state/get-state-value
:getState calva.state/get-state
:removeStateValue calva.state/remove-state-value!}
:output-to "out/cljs-lib/cljs-lib.js"}
:test
{:target :node-test
Expand Down
9 changes: 4 additions & 5 deletions src/calva-fmt/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as vscode from 'vscode';
import * as state from '../../state';
import { FormatOnTypeEditProvider } from './providers/ontype_formatter';
import { RangeEditProvider } from './providers/range_formatter';
import * as formatter from './format';
import * as inferer from './infer';
import * as docmirror from "../../doc-mirror/index"
import * as config from './config'
import * as calvaConfig from '../../config';

function getLanguageConfiguration(autoIndentOn: boolean): vscode.LanguageConfiguration {
return {
onEnterRules: autoIndentOn && state.config().format ? [
onEnterRules: autoIndentOn && calvaConfig.getConfig().format ? [
// When Calva is the formatter disable all vscode default indentation
// (By outdenting a lot, which is the only way I have found that works)
// TODO: Make it actually consider whether Calva is the formatter or not
Expand All @@ -24,7 +24,6 @@ function getLanguageConfiguration(autoIndentOn: boolean): vscode.LanguageConfigu
}
}


export function activate(context: vscode.ExtensionContext) {
docmirror.activate();
vscode.languages.setLanguageConfiguration("clojure", getLanguageConfiguration(config.getConfig()["format-as-you-type"]));
Expand All @@ -33,8 +32,8 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.inferParens', inferer.inferParensCommand));
context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.tabIndent', (e) => { inferer.indentCommand(e, " ", true) }));
context.subscriptions.push(vscode.commands.registerTextEditorCommand('calva-fmt.tabDedent', (e) => { inferer.indentCommand(e, " ", false) }));
context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(state.documentSelector, new FormatOnTypeEditProvider, "\r", "\n"));
context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider(state.documentSelector, new RangeEditProvider));
context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(calvaConfig.documentSelector, new FormatOnTypeEditProvider, "\r", "\n"));
context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider(calvaConfig.documentSelector, new RangeEditProvider));
vscode.window.onDidChangeActiveTextEditor(inferer.updateState);
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration("calva.fmt.formatAsYouType")) {
Expand Down
20 changes: 20 additions & 0 deletions src/cljs-lib/src/calva/state.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
(ns calva.state)

(defonce ^:private state (atom {}))

(defn set-state-value! [key value]
(swap! state assoc key value))

(defn remove-state-value! [key]
(swap! state dissoc key))

(defn get-state-value [key]
(get @state key))

(defn get-state []
@state)

(comment
(set-state-value! "hello" "world")
(get-state)
(remove-state-value! "hello"))
31 changes: 31 additions & 0 deletions src/cljs-lib/test/calva/state_test.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
(ns calva.state-test
(:require [cljs.test :refer [testing deftest is use-fixtures]]
[calva.state :as state]))

(use-fixtures :each
{:before (fn [] (reset! state/state {}))})

(deftest set-state-value!-test
(testing "Should write value to state, given key"
(state/set-state-value! "hello" "world")
(is (= {"hello" "world"} @state/state))))

(deftest remove-state-value!-test
(testing "Should remove value from state, given key"
(reset! state/state {"hello" "world"})
(state/remove-state-value! "hello")
(is (= {} @state/state))))

(deftest get-state-value-test
(testing "Should get value from state, given key"
(reset! state/state {"hello" "world"})
(is (= "world" (state/get-state-value "hello")))))

(deftest get-state-test
(testing "Should return all state"
(let [all-state {"hello" "world" "fizz" "buzz"}]
(reset! state/state all-state)
(is (= all-state (state/get-state))))))

(comment
(state/get-state))
71 changes: 63 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,68 @@
const config = {
REPL_FILE_EXT: 'calva-repl',
KEYBINDINGS_ENABLED_CONFIG_KEY: 'calva.keybindingsEnabled',
KEYBINDINGS_ENABLED_CONTEXT_KEY: 'calva:keybindingsEnabled'
};
import * as vscode from 'vscode';
import { customREPLCommandSnippet } from './evaluate';
import { ReplConnectSequence } from './nrepl/connectSequence';
import { PrettyPrintingOptions } from './printer';

const REPL_FILE_EXT = 'calva-repl';
const KEYBINDINGS_ENABLED_CONFIG_KEY = 'calva.keybindingsEnabled';
const KEYBINDINGS_ENABLED_CONTEXT_KEY = 'calva:keybindingsEnabled';

type ReplSessionType = 'clj' | 'cljs';

export {
ReplSessionType
// include the 'file' and 'untitled' to the
// document selector. All other schemes are
// not known and therefore not supported.
const documentSelector = [
{ scheme: 'file', language: 'clojure' },
{ scheme: 'jar', language: 'clojure' },
{ scheme: 'untitled', language: 'clojure' }
];

/**
* 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,:]*$/, "")
}

export default config;
// TODO find a way to validate the configs
function getConfig() {
const configOptions = vscode.workspace.getConfiguration('calva');
return {
format: configOptions.get("formatOnSave"),
evaluate: configOptions.get("evalOnSave"),
test: configOptions.get("testOnSave"),
showDocstringInParameterHelp: configOptions.get("showDocstringInParameterHelp") as boolean,
jackInEnv: configOptions.get("jackInEnv"),
jackInDependencyVersions: configOptions.get("jackInDependencyVersions") as { JackInDependency: string },
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[],
asyncOutputDestination: configOptions.get("sendAsyncOutputTo") as string,
customREPLCommandSnippets: configOptions.get("customREPLCommandSnippets", []),
customREPLCommandSnippetsGlobal: configOptions.inspect("customREPLCommandSnippets").globalValue as customREPLCommandSnippet[],
customREPLCommandSnippetsWorkspace: configOptions.inspect("customREPLCommandSnippets").workspaceValue as customREPLCommandSnippet[],
customREPLCommandSnippetsWorkspaceFolder: configOptions.inspect("customREPLCommandSnippets").workspaceFolderValue as customREPLCommandSnippet[],
prettyPrintingOptions: configOptions.get("prettyPrintingOptions") as PrettyPrintingOptions,
enableJSCompletions: configOptions.get("enableJSCompletions") as boolean,
autoOpenREPLWindow: configOptions.get("autoOpenREPLWindow") as boolean,
autoOpenJackInTerminal: configOptions.get("autoOpenJackInTerminal") as boolean,
referencesCodeLensEnabled: configOptions.get('referencesCodeLens.enabled') as boolean,
displayDiagnostics: configOptions.get('displayDiagnostics') as boolean,
hideReplUi: configOptions.get('hideReplUi') as boolean
};
}

export {
REPL_FILE_EXT,
KEYBINDINGS_ENABLED_CONFIG_KEY,
KEYBINDINGS_ENABLED_CONTEXT_KEY,
documentSelector,
ReplSessionType,
getConfig
}
61 changes: 31 additions & 30 deletions src/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import * as open from 'open';
import status from './status';
import * as projectTypes from './nrepl/project-types';
import { NReplClient, NReplSession } from "./nrepl";
import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType, CljsTypes, askForConnectSequence } from './nrepl/connectSequence';
import { CljsTypeConfig, ReplConnectSequence, getDefaultCljsType, askForConnectSequence } from './nrepl/connectSequence';
import { disabledPrettyPrinter } from './printer';
import { keywordize } from './util/string';
import { REQUESTS, initializeDebugger } from './debugger/calva-debug';
import { initializeDebugger } from './debugger/calva-debug';
import * as outputWindow from './results-output/results-doc';
import evaluate from './evaluate';
import * as namespace from './namespace';
import * as liveShareSupport from './liveShareSupport';
import * as calvaDebug from './debugger/calva-debug';
import { setStateValue, getStateValue } from '../out/cljs-lib/cljs-lib';
import * as replSession from './nrepl/repl-session';

async function connectToHost(hostname: string, port: number, connectSequence: ReplConnectSequence) {
state.analytics().logEvent("REPL", "Connecting").send();
Expand Down Expand Up @@ -51,11 +52,11 @@ async function connectToHost(hostname: string, port: number, connectSequence: Re
util.setConnectingState(false);
util.setConnectedState(true);
state.analytics().logEvent("REPL", "ConnectedCLJ").send();
state.cursor.set('clj', cljSession);
state.cursor.set('cljc', cljSession);
setStateValue('clj', cljSession);
setStateValue('cljc', cljSession);
status.update();
outputWindow.append(`; Connected session: clj\n${outputWindow.CLJ_CONNECT_GREETINGS}`);
namespace.updateREPLSessionType();
replSession.updateReplSessionType();

await initializeDebugger(cljSession);

Expand Down Expand Up @@ -91,6 +92,7 @@ async function connectToHost(hostname: string, port: number, connectSequence: Re
util.setConnectedState(false);
outputWindow.append("; Failed connecting.");
state.analytics().logEvent("REPL", "FailedConnectingCLJ").send();
console.error('Failed connecting:', e);
return false;
}

Expand All @@ -100,11 +102,11 @@ async function connectToHost(hostname: string, port: number, connectSequence: Re
}

async function setUpCljsRepl(session, build) {
state.cursor.set("cljs", session);
setStateValue("cljs", session);
status.update();
outputWindow.append(`; Connected session: cljs${(build ? ", repl: " + build : "")}\n${outputWindow.CLJS_CONNECT_GREETINGS}`);
outputWindow.setSession(session, 'cljs.user');
namespace.updateREPLSessionType();
replSession.updateReplSessionType();
}

async function getFigwheelMainBuilds() {
Expand Down Expand Up @@ -156,7 +158,7 @@ async function evalConnectCode(newCljsSession: NReplSession, code: string, name:
});
if (checkSuccess(valueResult, out, err)) {
state.analytics().logEvent("REPL", "ConnectedCLJS", name).send();
state.cursor.set('cljs', cljsSession = newCljsSession)
setStateValue('cljs', cljsSession = newCljsSession)
return true
} else {
return false;
Expand Down Expand Up @@ -291,7 +293,7 @@ function createCLJSReplType(cljsType: CljsTypeConfig, cljsTypeName: string, conn
return;
}

state.cursor.set('cljsBuild', build);
setStateValue('cljsBuild', build);

return evalConnectCode(session, initCode, name, checkFn, [startAppNowProcessor, printThisPrinter], [allPrinter]);
},
Expand Down Expand Up @@ -379,20 +381,20 @@ async function makeCljsSessionClone(session, repl: ReplType, projectTypeName: st
} else {
state.analytics().logEvent("REPL", "FailedStartingCLJS", repl.name).send();
outputWindow.append("; Failed starting cljs repl");
state.cursor.set('cljsBuild', null);
setStateValue('cljsBuild', null);
return [null, null];
}
}
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, state.deref().get('cljsBuild')];
setStateValue('cljs', cljsSession = newCljsSession);
return [cljsSession, getStateValue('cljsBuild')];
} else {
let build = state.deref().get('cljsBuild')
let build = getStateValue('cljsBuild')
state.analytics().logEvent("REPL", "FailedConnectingCLJS", repl.name).send();
let failed = "Failed starting cljs repl" + (build != null ? ` for build: ${build}. Is the build running and connected?\n See the Output channel "Calva Connection Log" for any hints on what went wrong.` : "");
outputWindow.append(`; ${failed}`);
state.cursor.set('cljsBuild', null);
setStateValue('cljsBuild', null);
vscode.window.showInformationMessage(
failed,
{ modal: true },
Expand All @@ -419,8 +421,8 @@ async function promptForNreplUrlAndConnect(port, connectSequence: ReplConnectSeq
let [hostname, port] = url.split(':'),
parsedPort = parseFloat(port);
if (parsedPort && parsedPort > 0 && parsedPort < 65536) {
state.cursor.set("hostname", hostname);
state.cursor.set("port", parsedPort);
setStateValue("hostname", hostname);
setStateValue("port", parsedPort);
await connectToHost(hostname, parsedPort, connectSequence);
} else {
outputWindow.append("; Bad url: " + url);
Expand Down Expand Up @@ -463,8 +465,8 @@ export async function connect(connectSequence: ReplConnectSequence,
if (port) {
hostname = hostname !== undefined ? hostname : "localhost";
if (isAutoConnect) {
state.cursor.set("hostname", hostname);
state.cursor.set("port", port);
setStateValue("hostname", hostname);
setStateValue("port", port);
await connectToHost(hostname, parseInt(port), connectSequence);
} else {
await promptForNreplUrlAndConnect(port, connectSequence);
Expand Down Expand Up @@ -526,10 +528,10 @@ export default {
disconnect: (options = null, callback = () => { }) => {
status.updateNeedReplUi(false);
['clj', 'cljs'].forEach(sessionType => {
state.cursor.set(sessionType, null);
setStateValue(sessionType, null);
});
util.setConnectedState(false);
state.cursor.set('cljc', null);
setStateValue('cljc', null);
status.update();

if (nClient) {
Expand All @@ -547,26 +549,25 @@ export default {
callback();
},
toggleCLJCSession: () => {
let current = state.deref();
let newSession: NReplSession;

if (current.get('connected')) {
if (namespace.getSession('cljc') == namespace.getSession('cljs')) {
newSession = namespace.getSession('clj');
} else if (namespace.getSession('cljc') == namespace.getSession('clj')) {
newSession = namespace.getSession('cljs');
if (getStateValue('connected')) {
if (replSession.getSession('cljc') == replSession.getSession('cljs')) {
newSession = replSession.getSession('clj');
} else if (replSession.getSession('cljc') == replSession.getSession('clj')) {
newSession = replSession.getSession('cljs');
}
state.cursor.set('cljc', newSession);
setStateValue('cljc', newSession);
if (outputWindow.isResultsDoc(vscode.window.activeTextEditor.document)) {
outputWindow.setSession(newSession, undefined);
namespace.updateREPLSessionType();
replSession.updateReplSessionType();
outputWindow.appendPrompt();
}
status.update();
}
},
switchCljsBuild: async () => {
let cljSession = namespace.getSession('clj');
let cljSession = replSession.getSession('clj');
const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'),
cljTypeName: string = state.extensionContext.workspaceState.get('selectedCljTypeName');
state.analytics().logEvent("REPL", "switchCljsBuild", cljsTypeName).send();
Expand Down
Loading

0 comments on commit d364d43

Please sign in to comment.