This repository was archived by the owner on Oct 21, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathjs.cljs
277 lines (245 loc) · 12 KB
/
js.cljs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
(ns lt.plugins.js
(:require [lt.object :as object]
[lt.objs.plugins :as plugins]
[lt.objs.eval :as eval]
[lt.objs.editor :as ed]
[lt.objs.clients.ws :as ws]
[lt.objs.clients :as clients]
[lt.objs.sidebar.clients :as scl]
[lt.objs.browser :as browser]
[lt.objs.notifos :as notifos]
[lt.objs.sidebar.command :as cmd]
[lt.objs.popup :as popup]
[lt.plugins.watches :as watches]
[lt.util.load :as load]
[clojure.string :as string]
[lt.util.dom :refer [$ append]])
(:require-macros [lt.macros :refer [behavior defui]]))
(def util-inspect (.-inspect (js/require "util")))
(def acorn (.-parse (js/require (plugins/local-module "javascript" "acorn"))))
(def head ($ :head))
(defn inspect [thing depth]
(util-inspect thing false (or depth 5)))
(defui script [src]
[:script {:src src :type "text/javascript"}])
(defn load-script [s]
(append head (script s)))
(defn parse [code]
(acorn code #js {:locations true, :ecmaVersion 6 :allowReturnOutsideFunction true}))
(defn ->body [tree]
(if (> 1 (count (.-body tree)))
(.-body tree)
(let [node (aget (.-body tree) 0)]
(if (and (= (.-type node) "ExpressionStatement")
(.-expression.callee node)
(.-expression.callee.body node))
;;we have a wrapping function
(.-expression.callee.body.body node)
(.-body tree)
))))
(defn ->forms [body]
(doall (map (fn [f]
{:loc (.-loc f)
:type (.-type f)})
body)))
(defn by-pos [locs pos]
(let [line (:line pos)]
(first (filter #(and (<= (.-start.line (:loc %)) line)
(>= (.-end.line (:loc %)) line))
locs))))
(defn expression? [{:keys [type]}]
(= type "ExpressionStatement"))
(defn pos->form [text pos]
(let [pos (update-in pos [:line] inc)
{:keys [loc type]} (-> text
(parse)
(->body)
(->forms)
(by-pos pos))
start (when loc (.-start loc))
end (when loc (.-end loc))]
(when start
{:type type
:start {:line (dec (.-line start))
:ch (.-column start)}
:end {:line (dec (.-line end))
:ch (.-column end)}})))
(defn code->forms [text]
(let [forms (try
(-> text
(parse)
(->body)
(->forms)))
lines (vec (string/split-lines text))]
(for [f forms
:let [loc (:loc f)
start (dec (.-start.line loc))
end (.-end.line loc)]]
{:start {:line (dec start)}
:end {:line (dec end)}
:type (:type f)
:lines (string/join "\n" (subvec lines start end))})))
(defn src->watch [meta src]
(let [[src semi] (if (= (last src) ";")
[(subs src 0 (dec (count src))) ";"]
[src ""])
opts (clj->js (assoc meta :ev :editor.eval.js.watch))
opts-str (.stringify js/JSON opts)]
(str "lttools.watch(" src ", " opts-str ")" semi)))
(defn fill-placeholders [src exp meta]
(-> exp
(string/replace "__SELECTION*__", (str "'" src "'"))
(string/replace "__SELECTION__" src)
(string/replace "__ID__" (:id meta))))
(defn custom-src->watch [src exp meta]
(let [hassemi (= (last src) ";")
subsrc (if hassemi (butlast src) src)
end (if hassemi ";" "")
sym (name (gensym "jswatch_temp"))]
(str "(function() {"
"var " sym " = (" subsrc ");"
(src->watch meta (fill-placeholders sym exp meta)) ";"
"return " sym ";"
"}())" end)))
(defn filter-shebang [src]
(string/replace src (js/RegExp. "^#!.*\n") "\n"))
(behavior ::watch-src
:triggers #{:watch.src+}
:reaction (fn [editor cur meta src]
(src->watch meta src)))
(behavior ::watch-custom-src
:triggers #{:watch.custom.src+}
:reaction (fn [editor cur meta {:keys [exp]} src]
(let [type (-> (parse exp) (.-body) (aget 0) (.-type))]
(if (= "ExpressionStatement" type)
(custom-src->watch src exp meta)
(do
(notifos/set-msg! "Custom expression is not a syntactic statement" {:class "error"})
(src->watch meta src))))))
(behavior ::on-eval
:triggers #{:eval}
:reaction (fn [editor]
(let [code (-> (watches/watched-range editor nil nil src->watch)
(filter-shebang))
forms (try
(code->forms code)
(catch :default e
{:ex e
:meta {:start {:line 0}
:notify true
:end {:line (dec (.-loc.line e))}}}))]
(if (map? forms)
(object/raise editor :editor.eval.js.exception forms)
(doseq [f forms]
(object/raise js-lang :eval! {:origin editor
:info (assoc (@editor :info)
:meta (-> (dissoc f :lines)
(assoc :notify true))
:code (:lines f))}))))))
(behavior ::on-eval.one
:triggers #{:eval.one}
:reaction (fn [editor]
(try
(let [code (filter-shebang (ed/->val editor))
pos (ed/->cursor editor)
info (:info @editor)
info (if (ed/selection? editor)
(assoc info
:code (ed/selection editor)
:meta {:start {:line (-> (ed/->cursor editor "start") :line)}
:end {:line (-> (ed/->cursor editor "end") :line)}
:type "ExpressionStatement"})
(let [{:keys [start end] :as meta} (pos->form code pos)
form (when meta (watches/watched-range editor start end src->watch))]
(when form
(assoc info :pos pos :code form :meta meta))))
info (update-in info [:code] #(-> %
(eval/pad (-> info :meta :start :line))
;(eval/append-source-file (-> @editor :info :path))
))]
(when info
(object/raise js-lang :eval! {:origin editor
:info info})))
(catch js/global.Error e
(object/raise editor :editor.eval.js.exception {:ex e :meta {:notify true
:end {:line (dec (.-loc.line e))}}})))
))
(behavior ::js-result
:triggers #{:editor.eval.js.result}
:reaction (fn [editor res]
(notifos/done-working)
(let [loc (-> res :meta :end)
loc (assoc loc :start-line (-> res :meta :start :line))]
(if (expression? (:meta res))
(let [str-result (if (:no-inspect res)
(if (:result res)
(:result res)
"undefined")
(inspect (:result res) nil))]
(object/raise editor :editor.result str-result loc {:prefix " = "}))
(object/raise editor :editor.result "✓" loc {:prefix " "})))))
(behavior ::js-watch
:triggers #{:editor.eval.js.watch}
:reaction (fn [editor res]
(when-let [watch (get (:watches @editor) (-> res :meta :id))]
(let [str-result (if (-> res :meta :no-inspect)
(:result res)
(inspect (:result res) 0))]
(object/raise (:inline-result watch) :update! str-result)
)
)))
(behavior ::js-exception
:triggers #{:editor.eval.js.exception}
:reaction (fn [editor ex]
(notifos/done-working)
(let [stack (if (.-stack (:ex ex))
(.-stack (:ex ex))
(:ex ex))
loc (-> ex :meta :end)
loc (when loc (assoc loc :start-line (-> ex :meta :start :line)))]
(if loc
(do
(when (-> ex :meta :notify)
(notifos/set-msg! (pr-str (:ex ex)) {:class "error"}))
(object/raise editor :editor.exception stack loc))
(notifos/set-msg! (pr-str (:ex ex)) {:class "error"})))
))
(behavior ::js-success
:triggers #{:editor.eval.js.file.success}
:reaction (fn [editor]
(notifos/done-working)
(notifos/set-msg! (str "Eval success: " (-> @editor :info :name)))))
(behavior ::eval!
:triggers #{:eval!}
:reaction (fn [this event]
(let [{:keys [info origin]} event]
(notifos/working "")
(clients/send (eval/get-client! {:command :editor.eval.js
:origin origin
:info info})
:editor.eval.js
(assoc info :ed-id (object/->id origin))
:only origin))))
(object/object* ::js-lang
:tags #{}
:behaviors [::eval!]
:triggers #{:eval!})
(def js-lang (object/create ::js-lang))
(cmd/command {:command :connect-to-browser
:desc "Connect: Browser (Script-tag)"
:exec (fn []
(popup/popup! {:header "Connect to a browser"
:body [:p "To connect just include the following script tag in the head of your web page:"
[:code "<script type='text/javascript' id='lt_ws' src='http://localhost:" ws/port "/socket.io/lighttable/ws.js'></script>"]]
:buttons [{:label "ok"}]}))})
(scl/add-connector {:name "Browser (External)"
:desc "Connect to an external browser via script tag to eval JavaScript, CSS, and HTML live."
:connect (fn []
(cmd/exec! :connect-to-browser))})
(browser/add-util :watch (fn [exp meta]
(when-let [obj (object/by-id (.-obj meta))]
(object/raise obj (keyword (.-ev meta)) {:result exp :meta (js->clj meta :keywordize-keys true)}))
exp))
(browser/add-util :raise (fn [id ev data]
(when-let [obj (object/by-id id)]
(object/raise obj ev data))))