Skip to content

Port eval-region to squint #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ jobs:
run: |
yarn build

- name: 🧶 Squint Build
run: |
yarn squint compile
yarn vite:build

- name: 📠 Copy static build to bucket under SHA
run: gsutil cp -r public gs://nextjournal-snapshots/clojure-mode/build/${{ github.sha }}

Expand Down
18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{ "name": "@nextjournal/clojure-mode",
"files": ["dist"],
{
"name": "@nextjournal/clojure-mode",
"files": [
"dist"
],
"version": "0.2.0",
"license": "EPL-2.0",
"repository": {
Expand All @@ -20,9 +23,8 @@
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"@nextjournal/lezer-clojure": "1.0.0",
"squint-cljs": "0.3.36",
"w3c-keyname": "^2.2.4",
"squint-macros": "https://github.com/squint-cljs/squint-macros"
"squint-cljs": "0.4.58",
"w3c-keyname": "^2.2.4"
},
"comments": {
"to run squint as a local dependency:": "bb yarn-install:squint-dev"
Expand Down Expand Up @@ -52,9 +54,11 @@
"react-dom": "^17.0.2",
"rollup-plugin-analyzer": "^4.0.0",
"shadow-cljs": "2.19.5",
"vite": "^4.4.9"
"vite": "^4.4.9",
"@squint-cljs/macros": "0.1.0"
},
"exports": {
".": "./dist/nextjournal/clojure_mode.mjs"
".": "./dist/nextjournal/clojure_mode.mjs",
"./extensions/eval-region": "./dist/nextjournal/clojure_mode/extensions/eval_region.mjs"
}
}
22 changes: 15 additions & 7 deletions public/squint/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
}
}
</style>
<script type="importmap">
{
"imports": {
"squint-cljs/core.js": "https://unpkg.com/[email protected]/core.js"
}
}
</script>

</head>
<body>
<div class="landing-page pt-10">
Expand Down Expand Up @@ -88,12 +96,12 @@ <h2 id="try-it" class="mt-0 mb-12 text-center text-3xl font-bold">
</h2>
<div class="flex flex-col-reverse md:flex-row">
<div class="md:w-1/2 flex-shrink-0 md:px-6 mt-12 md:mt-0">
<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>
<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>
<p class="sans-serif text-sm text-center mb-6 mt-0">
In-browser eval is powered by <a href="https://github.com/borkdude/sci">Sci</a>.
In-browser eval is powered by <a href="https://github.com/squint-cljs/squint">Squint</a>.
</p>
<div id="editor" class="rounded-md mb-0 text-sm monospace overflow-auto relative border shadow-lg bg-white">
</div>
<div id="editor" class="rounded-md mb-0 text-sm monospace overflow-auto relative border shadow-lg bg-white"></div>
<div id="result" class="mt-3.mv-4.pl-6" style="white-space: pre-wrap; font-family: var(--code-font)"></div>
</div>
<div class="md:w-1/2 flex-shrink-0 md:px-6 sans-serif">
<ul class="text-lg">
Expand Down Expand Up @@ -162,23 +170,23 @@ <h3 class="text-center sans-serif font-bold text-lg mt-0 mb-1">Try evaluating an
At Cursor
</td>
<td class="py-1 text-right">
<span class="kbd alt">Alt</span> + <span class="kbd">⏎</span>
<span class="kbd mod">Mod</span> + <span class="kbd">⏎</span>
</td>
</tr>
<tr class="border-t">
<td class="py-1 pr-12">
Top-level form
</td>
<td class="py-1 text-right">
<span class="kbd alt">Alt</span> + <span class="kbd">⇧</span> + <span class="kbd">⏎</span>
<span class="kbd alt">Mod</span> + <span class="kbd">⇧</span> + <span class="kbd">⏎</span>
</td>
</tr>
<tr class="border-t">
<td class="py-1 pr-12">
Cell
</td>
<td class="py-1 text-right">
<span class="kbd mod">Mod</span> + <span class="kbd">⏎</span>
<span class="kbd alt">Alt</span> + <span class="kbd">⏎</span>
</td>
</tr>
</tbody>
Expand Down
114 changes: 110 additions & 4 deletions public/squint/js/demo.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { default_extensions, complete_keymap } from '@nextjournal/clojure-mode';
import { extension as eval_ext, cursor_node_string, top_level_string } from '@nextjournal/clojure-mode/extensions/eval-region';
import { EditorView, drawSelection, keymap } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { syntaxHighlighting, defaultHighlightStyle, foldGutter } from '@codemirror/language';
import { compileString } from 'squint-cljs';

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

let evalCode = async function (code) {
let js = compileString(`(do ${code})`, {repl: true,
context: 'return',
"elide-exports": true})
let result;
try {
result = {value: await eval(`(async function() { ${js} })()`)};
}
catch (e) {
result = {error: true, ex: e};
}
if (result.error) {
document.getElementById("result").innerText = result.ex;
} else {
document.getElementById("result").innerText = '' + JSONstringify(result.value);
}
}

let evalCell = (opts) => {
let code = opts.state.doc.toString();
evalCode(code);
return true;
}

let evalToplevel = function (opts) {
let state = opts.state;
let code = top_level_string(state);
evalCode(code);
return true;
}

function JSONstringify(json) {
json = JSON.stringify(json, function(key, value) {
if (!value) return value;
if (typeof value === 'string') return value;
if (Array.isArray(value) || value.constructor === Object) return value;
if (value[Symbol.iterator]) {
return [...value];
}
if (typeof value === 'object') {
return `#object[${value.constructor.name}]`;
} else {
return value;
}
});
return json;
}

let evalAtCursor = function (opts) {
let state = opts.state;
let code = cursor_node_string(state);
evalCode(code);
return true;
}

let squintExtension = ( opts ) => {
return keymap.of([{key: "Alt-Enter", run: evalCell},
{key: opts.modifier + "-Enter",
run: evalAtCursor,
shift: evalToplevel
}])}


let extensions = [ theme, foldGutter(),
syntaxHighlighting(defaultHighlightStyle),
drawSelection(),
keymap.of(complete_keymap),
...default_extensions
...default_extensions,
eval_ext({modifier: "Meta"}),
squintExtension({modifier: "Meta"})
];

let state = EditorState.create( {doc: `(comment
let doc = `(comment
(fizz-buzz 1)
(fizz-buzz 3)
(fizz-buzz 5)
Expand All @@ -43,9 +110,48 @@ let state = EditorState.create( {doc: `(comment
15 "fizzbuzz"
3 "fizz"
5 "buzz"
n))`,
n))

(require '["https://esm.sh/[email protected]$default" :as confetti])

(do
(js-await (confetti))
(+ 1 2 3))
` ;

evalCode(doc);

let state = EditorState.create( {doc: doc,
extensions: extensions });

let editorElt = document.querySelector('#editor');
let editor = new EditorView({state: state,
parent: editorElt,
extensions: extensions });
extensions: extensions })

let keys = {"ArrowUp": "↑",
"ArrowDown": "↓",
"ArrowRight": "→",
"ArrowLeft": "←",
"Mod": "Ctrl"}

let macKeys = {"Alt": "⌥",
"Shift": "⇧",
"Enter": "⏎",
"Ctrl": "⌃",
"Mod": "⌘"}

let mac;

if (/^(Mac)|(iPhone)|(iPad)|(iPod)$/.test(window.navigator.platform.substring(0,3))) {
mac = true;
Object.assign(keys, macKeys);
}

document.querySelectorAll(".mod,.alt,.ctrl").forEach(node => {
let k = node.innerHTML;
let symbol = keys[k];
if (symbol) {
node.innerHTML = symbol;
}
});
3 changes: 2 additions & 1 deletion squint.edn
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
{:paths ["src-shared" "src-squint" "test" "node_modules/squint-macros/src"]
{:paths ["src-shared" "src-squint" "test"
"node_modules/@squint-cljs/macros/src"]
:output-dir "dist"}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
["@codemirror/state" :as state :refer [StateEffect StateField]]
["@codemirror/view" :as view :refer [EditorView Decoration keymap]]
["w3c-keyname" :refer [keyName]]
[applied-science.js-interop :as j]
#?@(:squint [] :cljs [[applied-science.js-interop :as j]])
[nextjournal.clojure-mode.util :as u]
[nextjournal.clojure-mode.node :as n]
[clojure.string :as str]))
[clojure.string :as str])
#?(:squint (:require-macros [applied-science.js-interop :as j])))

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

(defn main-selection [state]
(-> (j/call-in state [:selection :asSingle])
(->
(j/call-in state [:selection :asSingle])
(j/get-in [:ranges 0])))

(defn node-at-cursor
Expand All @@ -45,6 +47,7 @@

;; Modifier field
(defonce modifier-effect (.define StateEffect))

(defonce modifier-field
(.define StateField
(j/lit {:create (constantly {})
Expand All @@ -55,7 +58,7 @@

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

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

Expand Down Expand Up @@ -118,7 +121,7 @@
(when (not= prev next)
(set-modifier-field! view next))
false))
handle-backspace (j/fn [^:js {:as view :keys [state dispatch]}]
handle-backspace (j/fn [^:js {:as _view :keys [state dispatch]}]
(j/let [^:js {:keys [from to]} (current-range state)]
(when (not= from to)
(dispatch (j/lit {:changes {:from from :to to :insert ""}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@
(u/iter-changed-lines tr
(fn [^js line ^js changes]
(format-line state context (.-from line) (.-text line) (.-number line) changes true)))))))]
(.. tr -startState (update (j/assoc! changes :filter false)))
(do #_(js/console.log :changes changes)
(.. tr -startState (update (j/assoc! changes :filter false))))
tr)))

(defn format [state]
Expand Down
2 changes: 1 addition & 1 deletion src-shared/nextjournal/clojure_mode/node.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
(defn ^boolean prefix? [n] (prefix-type? (type n)))
(defn ^boolean prefix-edge? [n] (prefix-edge-type? (type n)))
(defn ^boolean prefix-container? [n] (prefix-container-type? (type n)))
(defn ^boolean same-edge? [n] (same-edge-type? (type n)))
(defn ^boolean same-edge? [n ](same-edge-type? (type n)))
(defn ^boolean start-edge? [n]
(start-edge-type? (type n)))
(defn ^boolean end-edge? [n] (end-edge-type? (type n)))
Expand Down
7 changes: 7 additions & 0 deletions src-squint/nextjournal/clojure_mode_tests/macros.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,10 @@
`(do ~@processed))
#?(:clj (throw (IllegalArgumentException. "The number of args doesn't match are's argv."))
:cljs (throw (js/Error "The number of args doesn't match are's argv.")))))

(defmacro is
[expr & _]
(if (and (seq? expr)
(= '= (first expr)))
(list* 'assert.equal (rest expr))
expr))
22 changes: 18 additions & 4 deletions test/nextjournal/clojure_mode_tests.cljc
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
(ns nextjournal.clojure-mode-tests
(:require #?@(:squint []
:cljs [[cljs.test :refer [are testing deftest]]])
:cljs [[cljs.test :refer [are testing deftest is]]])
[nextjournal.clojure-mode :as cm-clojure]
[nextjournal.clojure-mode.util :as util]
[nextjournal.clojure-mode.test-utils :as test-utils]
[nextjournal.clojure-mode.extensions.close-brackets :as close-brackets]
[nextjournal.clojure-mode.commands :as commands]
[nextjournal.clojure-mode.extensions.formatting :as format]
[nextjournal.clojure-mode.extensions.eval-region :as eval-region]
#?@(:squint []
:cljs [[nextjournal.livedoc :as livedoc]])
#?(:squint ["assert" :as assert]))
#?(:squint (:require-macros [nextjournal.clojure-mode-tests.macros :refer [deftest are testing]])))
#?(:squint (:require-macros [nextjournal.clojure-mode-tests.macros :refer [deftest are testing is]])))

(def extensions
cm-clojure/default-extensions
(.concat cm-clojure/default-extensions (eval-region/extension #js {}))
;; optionally test with live grammar
#_
#js[(cm-clojure/syntax live-grammar/parser)
Expand Down Expand Up @@ -322,4 +324,16 @@
"(()|)" "(()\n |)"
"(a |b)" "(a\n |b)"
"(a b|c)" "(a b\n |c)"
)))
))

(deftest eval-region-test
(are [input f expected]
(= (f (test-utils/make-state extensions input)) expected)
"(+ |1 2 3)" eval-region/cursor-node-string "1"
"(+ |(+ 1 2) 2 3)" eval-region/cursor-node-string "(+ 1 2)"
"(+ (+ 1 2)| 2 3)" eval-region/cursor-node-string "(+ 1 2)")
(let [state (test-utils/make-state extensions ";; dude\n|{:a 1}")]
(is (= "{:a 1}" (->> (eval-region/top-level-node state)
(util/range-str state)))))))

#_(prn (eval-region/cursor-node-string (test-utils/make-state extensions "(+ (+ 1 2)| 2 3)")))
Loading