Skip to content

Commit 0bcc17f

Browse files
authored
Port eval-region to squint (#44)
Also export eval-region extension as module available at ``` import * as evalRegion from '@nextjournal/clojure-mode/extensions/eval-region' ```
1 parent 1199223 commit 0bcc17f

File tree

11 files changed

+187
-264
lines changed

11 files changed

+187
-264
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ jobs:
102102
run: |
103103
yarn build
104104
105+
- name: 🧶 Squint Build
106+
run: |
107+
yarn squint compile
108+
yarn vite:build
109+
105110
- name: 📠 Copy static build to bucket under SHA
106111
run: gsutil cp -r public gs://nextjournal-snapshots/clojure-mode/build/${{ github.sha }}
107112

package.json

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
{ "name": "@nextjournal/clojure-mode",
2-
"files": ["dist"],
1+
{
2+
"name": "@nextjournal/clojure-mode",
3+
"files": [
4+
"dist"
5+
],
36
"version": "0.2.0",
47
"license": "EPL-2.0",
58
"repository": {
@@ -20,9 +23,8 @@
2023
"@lezer/highlight": "^1.0.0",
2124
"@lezer/lr": "^1.0.0",
2225
"@nextjournal/lezer-clojure": "1.0.0",
23-
"squint-cljs": "0.3.36",
24-
"w3c-keyname": "^2.2.4",
25-
"squint-macros": "https://github.com/squint-cljs/squint-macros"
26+
"squint-cljs": "0.4.58",
27+
"w3c-keyname": "^2.2.4"
2628
},
2729
"comments": {
2830
"to run squint as a local dependency:": "bb yarn-install:squint-dev"
@@ -52,9 +54,11 @@
5254
"react-dom": "^17.0.2",
5355
"rollup-plugin-analyzer": "^4.0.0",
5456
"shadow-cljs": "2.19.5",
55-
"vite": "^4.4.9"
57+
"vite": "^4.4.9",
58+
"@squint-cljs/macros": "0.1.0"
5659
},
5760
"exports": {
58-
".": "./dist/nextjournal/clojure_mode.mjs"
61+
".": "./dist/nextjournal/clojure_mode.mjs",
62+
"./extensions/eval-region": "./dist/nextjournal/clojure_mode/extensions/eval_region.mjs"
5963
}
6064
}

public/squint/index.html

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@
5858
}
5959
}
6060
</style>
61+
<script type="importmap">
62+
{
63+
"imports": {
64+
"squint-cljs/core.js": "https://unpkg.com/[email protected]/core.js"
65+
}
66+
}
67+
</script>
68+
6169
</head>
6270
<body>
6371
<div class="landing-page pt-10">
@@ -88,12 +96,12 @@ <h2 id="try-it" class="mt-0 mb-12 text-center text-3xl font-bold">
8896
</h2>
8997
<div class="flex flex-col-reverse md:flex-row">
9098
<div class="md:w-1/2 flex-shrink-0 md:px-6 mt-12 md:mt-0">
91-
<h3 class="text-center sans-serif font-bold text-lg mt-0 mb-1">Try evaluating any of these forms with <span class="kbd alt font-normal">Alt</span> <span class="font-normal">+</span> <span class="kbd font-normal"></span> !</h3>
99+
<h3 class="text-center sans-serif font-bold text-lg mt-0 mb-1">Try evaluating any of these forms with <span class="kbd mod font-normal">Mod</span> <span class="font-normal">+</span> <span class="kbd font-normal"></span> !</h3>
92100
<p class="sans-serif text-sm text-center mb-6 mt-0">
93-
In-browser eval is powered by <a href="https://github.com/borkdude/sci">Sci</a>.
101+
In-browser eval is powered by <a href="https://github.com/squint-cljs/squint">Squint</a>.
94102
</p>
95-
<div id="editor" class="rounded-md mb-0 text-sm monospace overflow-auto relative border shadow-lg bg-white">
96-
</div>
103+
<div id="editor" class="rounded-md mb-0 text-sm monospace overflow-auto relative border shadow-lg bg-white"></div>
104+
<div id="result" class="mt-3.mv-4.pl-6" style="white-space: pre-wrap; font-family: var(--code-font)"></div>
97105
</div>
98106
<div class="md:w-1/2 flex-shrink-0 md:px-6 sans-serif">
99107
<ul class="text-lg">
@@ -162,23 +170,23 @@ <h3 class="text-center sans-serif font-bold text-lg mt-0 mb-1">Try evaluating an
162170
At Cursor
163171
</td>
164172
<td class="py-1 text-right">
165-
<span class="kbd alt">Alt</span> + <span class="kbd"></span>
173+
<span class="kbd mod">Mod</span> + <span class="kbd"></span>
166174
</td>
167175
</tr>
168176
<tr class="border-t">
169177
<td class="py-1 pr-12">
170178
Top-level form
171179
</td>
172180
<td class="py-1 text-right">
173-
<span class="kbd alt">Alt</span> + <span class="kbd"></span> + <span class="kbd"></span>
181+
<span class="kbd alt">Mod</span> + <span class="kbd"></span> + <span class="kbd"></span>
174182
</td>
175183
</tr>
176184
<tr class="border-t">
177185
<td class="py-1 pr-12">
178186
Cell
179187
</td>
180188
<td class="py-1 text-right">
181-
<span class="kbd mod">Mod</span> + <span class="kbd"></span>
189+
<span class="kbd alt">Alt</span> + <span class="kbd"></span>
182190
</td>
183191
</tr>
184192
</tbody>

public/squint/js/demo.mjs

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { default_extensions, complete_keymap } from '@nextjournal/clojure-mode';
2+
import { extension as eval_ext, cursor_node_string, top_level_string } from '@nextjournal/clojure-mode/extensions/eval-region';
23
import { EditorView, drawSelection, keymap } from '@codemirror/view';
34
import { EditorState } from '@codemirror/state';
45
import { syntaxHighlighting, defaultHighlightStyle, foldGutter } from '@codemirror/language';
6+
import { compileString } from 'squint-cljs';
57

68
let theme = EditorView.theme({
79
".cm-content": {whitespace: "pre-wrap",
@@ -23,14 +25,79 @@ let theme = EditorView.theme({
2325
"&.cm-focused .cm-cursor": {visibility: "visible"}
2426
});
2527

28+
let evalCode = async function (code) {
29+
let js = compileString(`(do ${code})`, {repl: true,
30+
context: 'return',
31+
"elide-exports": true})
32+
let result;
33+
try {
34+
result = {value: await eval(`(async function() { ${js} })()`)};
35+
}
36+
catch (e) {
37+
result = {error: true, ex: e};
38+
}
39+
if (result.error) {
40+
document.getElementById("result").innerText = result.ex;
41+
} else {
42+
document.getElementById("result").innerText = '' + JSONstringify(result.value);
43+
}
44+
}
45+
46+
let evalCell = (opts) => {
47+
let code = opts.state.doc.toString();
48+
evalCode(code);
49+
return true;
50+
}
51+
52+
let evalToplevel = function (opts) {
53+
let state = opts.state;
54+
let code = top_level_string(state);
55+
evalCode(code);
56+
return true;
57+
}
58+
59+
function JSONstringify(json) {
60+
json = JSON.stringify(json, function(key, value) {
61+
if (!value) return value;
62+
if (typeof value === 'string') return value;
63+
if (Array.isArray(value) || value.constructor === Object) return value;
64+
if (value[Symbol.iterator]) {
65+
return [...value];
66+
}
67+
if (typeof value === 'object') {
68+
return `#object[${value.constructor.name}]`;
69+
} else {
70+
return value;
71+
}
72+
});
73+
return json;
74+
}
75+
76+
let evalAtCursor = function (opts) {
77+
let state = opts.state;
78+
let code = cursor_node_string(state);
79+
evalCode(code);
80+
return true;
81+
}
82+
83+
let squintExtension = ( opts ) => {
84+
return keymap.of([{key: "Alt-Enter", run: evalCell},
85+
{key: opts.modifier + "-Enter",
86+
run: evalAtCursor,
87+
shift: evalToplevel
88+
}])}
89+
90+
2691
let extensions = [ theme, foldGutter(),
2792
syntaxHighlighting(defaultHighlightStyle),
2893
drawSelection(),
2994
keymap.of(complete_keymap),
30-
...default_extensions
95+
...default_extensions,
96+
eval_ext({modifier: "Meta"}),
97+
squintExtension({modifier: "Meta"})
3198
];
3299

33-
let state = EditorState.create( {doc: `(comment
100+
let doc = `(comment
34101
(fizz-buzz 1)
35102
(fizz-buzz 3)
36103
(fizz-buzz 5)
@@ -43,9 +110,48 @@ let state = EditorState.create( {doc: `(comment
43110
15 "fizzbuzz"
44111
3 "fizz"
45112
5 "buzz"
46-
n))`,
113+
n))
114+
115+
(require '["https://esm.sh/[email protected]$default" :as confetti])
116+
117+
(do
118+
(js-await (confetti))
119+
(+ 1 2 3))
120+
` ;
121+
122+
evalCode(doc);
123+
124+
let state = EditorState.create( {doc: doc,
47125
extensions: extensions });
126+
48127
let editorElt = document.querySelector('#editor');
49128
let editor = new EditorView({state: state,
50129
parent: editorElt,
51-
extensions: extensions });
130+
extensions: extensions })
131+
132+
let keys = {"ArrowUp": "↑",
133+
"ArrowDown": "↓",
134+
"ArrowRight": "→",
135+
"ArrowLeft": "←",
136+
"Mod": "Ctrl"}
137+
138+
let macKeys = {"Alt": "⌥",
139+
"Shift": "⇧",
140+
"Enter": "⏎",
141+
"Ctrl": "⌃",
142+
"Mod": "⌘"}
143+
144+
let mac;
145+
146+
if (/^(Mac)|(iPhone)|(iPad)|(iPod)$/.test(window.navigator.platform.substring(0,3))) {
147+
mac = true;
148+
Object.assign(keys, macKeys);
149+
}
150+
151+
document.querySelectorAll(".mod,.alt,.ctrl").forEach(node => {
152+
let k = node.innerHTML;
153+
let symbol = keys[k];
154+
if (symbol) {
155+
node.innerHTML = symbol;
156+
}
157+
});

squint.edn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
{:paths ["src-shared" "src-squint" "test" "node_modules/squint-macros/src"]
1+
{:paths ["src-shared" "src-squint" "test"
2+
"node_modules/@squint-cljs/macros/src"]
23
:output-dir "dist"}

src/nextjournal/clojure_mode/extensions/eval_region.cljs renamed to src-shared/nextjournal/clojure_mode/extensions/eval_region.cljc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
["@codemirror/state" :as state :refer [StateEffect StateField]]
44
["@codemirror/view" :as view :refer [EditorView Decoration keymap]]
55
["w3c-keyname" :refer [keyName]]
6-
[applied-science.js-interop :as j]
6+
#?@(:squint [] :cljs [[applied-science.js-interop :as j]])
77
[nextjournal.clojure-mode.util :as u]
88
[nextjournal.clojure-mode.node :as n]
9-
[clojure.string :as str]))
9+
[clojure.string :as str])
10+
#?(:squint (:require-macros [applied-science.js-interop :as j])))
1011

1112
(defn uppermost-edge-here
1213
"Returns node or its highest ancestor that starts or ends at the cursor position."
@@ -19,7 +20,8 @@
1920
node))
2021

2122
(defn main-selection [state]
22-
(-> (j/call-in state [:selection :asSingle])
23+
(->
24+
(j/call-in state [:selection :asSingle])
2325
(j/get-in [:ranges 0])))
2426

2527
(defn node-at-cursor
@@ -45,6 +47,7 @@
4547

4648
;; Modifier field
4749
(defonce modifier-effect (.define StateEffect))
50+
4851
(defonce modifier-field
4952
(.define StateField
5053
(j/lit {:create (constantly {})
@@ -55,7 +58,7 @@
5558

5659
(defn get-modifier-field [^js state] (.field state modifier-field))
5760

58-
(j/defn set-modifier-field! [^:js {:as view :keys [dispatch state]} value]
61+
(j/defn set-modifier-field! [^:js {:as _view :keys [dispatch]} value]
5962
(dispatch #js{:effects (.of modifier-effect value)
6063
:userEvent "evalregion"}))
6164

@@ -118,7 +121,7 @@
118121
(when (not= prev next)
119122
(set-modifier-field! view next))
120123
false))
121-
handle-backspace (j/fn [^:js {:as view :keys [state dispatch]}]
124+
handle-backspace (j/fn [^:js {:as _view :keys [state dispatch]}]
122125
(j/let [^:js {:keys [from to]} (current-range state)]
123126
(when (not= from to)
124127
(dispatch (j/lit {:changes {:from from :to to :insert ""}

src-shared/nextjournal/clojure_mode/extensions/formatting.cljc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@
167167
(u/iter-changed-lines tr
168168
(fn [^js line ^js changes]
169169
(format-line state context (.-from line) (.-text line) (.-number line) changes true)))))))]
170-
(.. tr -startState (update (j/assoc! changes :filter false)))
170+
(do #_(js/console.log :changes changes)
171+
(.. tr -startState (update (j/assoc! changes :filter false))))
171172
tr)))
172173

173174
(defn format [state]

src-shared/nextjournal/clojure_mode/node.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
(defn ^boolean prefix? [n] (prefix-type? (type n)))
9797
(defn ^boolean prefix-edge? [n] (prefix-edge-type? (type n)))
9898
(defn ^boolean prefix-container? [n] (prefix-container-type? (type n)))
99-
(defn ^boolean same-edge? [n] (same-edge-type? (type n)))
99+
(defn ^boolean same-edge? [n ](same-edge-type? (type n)))
100100
(defn ^boolean start-edge? [n]
101101
(start-edge-type? (type n)))
102102
(defn ^boolean end-edge? [n] (end-edge-type? (type n)))

src-squint/nextjournal/clojure_mode_tests/macros.cljc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,10 @@
7474
`(do ~@processed))
7575
#?(:clj (throw (IllegalArgumentException. "The number of args doesn't match are's argv."))
7676
:cljs (throw (js/Error "The number of args doesn't match are's argv.")))))
77+
78+
(defmacro is
79+
[expr & _]
80+
(if (and (seq? expr)
81+
(= '= (first expr)))
82+
(list* 'assert.equal (rest expr))
83+
expr))

test/nextjournal/clojure_mode_tests.cljc

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
(ns nextjournal.clojure-mode-tests
22
(:require #?@(:squint []
3-
:cljs [[cljs.test :refer [are testing deftest]]])
3+
:cljs [[cljs.test :refer [are testing deftest is]]])
44
[nextjournal.clojure-mode :as cm-clojure]
5+
[nextjournal.clojure-mode.util :as util]
56
[nextjournal.clojure-mode.test-utils :as test-utils]
67
[nextjournal.clojure-mode.extensions.close-brackets :as close-brackets]
78
[nextjournal.clojure-mode.commands :as commands]
89
[nextjournal.clojure-mode.extensions.formatting :as format]
10+
[nextjournal.clojure-mode.extensions.eval-region :as eval-region]
911
#?@(:squint []
1012
:cljs [[nextjournal.livedoc :as livedoc]])
1113
#?(:squint ["assert" :as assert]))
12-
#?(:squint (:require-macros [nextjournal.clojure-mode-tests.macros :refer [deftest are testing]])))
14+
#?(:squint (:require-macros [nextjournal.clojure-mode-tests.macros :refer [deftest are testing is]])))
1315

1416
(def extensions
15-
cm-clojure/default-extensions
17+
(.concat cm-clojure/default-extensions (eval-region/extension #js {}))
1618
;; optionally test with live grammar
1719
#_
1820
#js[(cm-clojure/syntax live-grammar/parser)
@@ -322,4 +324,16 @@
322324
"(()|)" "(()\n |)"
323325
"(a |b)" "(a\n |b)"
324326
"(a b|c)" "(a b\n |c)"
325-
)))
327+
))
328+
329+
(deftest eval-region-test
330+
(are [input f expected]
331+
(= (f (test-utils/make-state extensions input)) expected)
332+
"(+ |1 2 3)" eval-region/cursor-node-string "1"
333+
"(+ |(+ 1 2) 2 3)" eval-region/cursor-node-string "(+ 1 2)"
334+
"(+ (+ 1 2)| 2 3)" eval-region/cursor-node-string "(+ 1 2)")
335+
(let [state (test-utils/make-state extensions ";; dude\n|{:a 1}")]
336+
(is (= "{:a 1}" (->> (eval-region/top-level-node state)
337+
(util/range-str state)))))))
338+
339+
#_(prn (eval-region/cursor-node-string (test-utils/make-state extensions "(+ (+ 1 2)| 2 3)")))

0 commit comments

Comments
 (0)