@naanlang/naan 1.4.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +4 -4
  3. package/bin/index.js +25 -3
  4. package/dist/env_web.js +164 -22
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/browser.nlg +2 -2
  7. package/frameworks/browser/https_request.nlg +2 -2
  8. package/frameworks/browser/sworker.js +28 -26
  9. package/frameworks/browser/terminals.nlg +342 -700
  10. package/frameworks/browser/worker.nlg +255 -0
  11. package/frameworks/browser/workers.nlg +5 -5
  12. package/frameworks/browser/ws_client.nlg +15 -10
  13. package/frameworks/client/apiclient.nlg +204 -19
  14. package/frameworks/client/client.nlg +22 -2
  15. package/frameworks/client/psm_client.nlg +12 -3
  16. package/frameworks/client/relaycon.nlg +87 -161
  17. package/frameworks/common/events.nlg +101 -0
  18. package/frameworks/common/repltools.nlg +80 -1
  19. package/frameworks/node/apiserver.nlg +39 -246
  20. package/frameworks/node/filesystem.nlg +39 -12
  21. package/frameworks/node/gitter.nlg +9 -7
  22. package/frameworks/node/http_request.nlg +34 -13
  23. package/frameworks/node/https_request.nlg +8 -3
  24. package/frameworks/node/node.nlg +2 -0
  25. package/frameworks/node/pty.nlg +346 -0
  26. package/frameworks/node/shell.nlg +4 -4
  27. package/frameworks/node/worker.nlg +409 -539
  28. package/frameworks/node/ws_client.nlg +2 -2
  29. package/frameworks/project/build.nlg +1 -1
  30. package/frameworks/project/projects.nlg +2 -1
  31. package/frameworks/running/debugnub.nlg +17 -4
  32. package/frameworks/running/executors.nlg +27 -274
  33. package/frameworks/running/instances.nlg +393 -0
  34. package/frameworks/running/remote_req.nlg +143 -0
  35. package/frameworks/running/remote_res.nlg +89 -0
  36. package/frameworks/running/taskexec.nlg +2 -2
  37. package/frameworks/service/imp.nlg +736 -0
  38. package/frameworks/service/model.nlg +71 -0
  39. package/frameworks/service/nubnode.nlg +105 -0
  40. package/frameworks/service/nubweb.nlg +106 -0
  41. package/frameworks/service/service.nlg +28 -0
  42. package/frameworks/storage/dbt_pouch.nlg +26 -13
  43. package/frameworks/storage/psm.nlg +39 -5
  44. package/frameworks/storage/psm_dbtables.nlg +10 -2
  45. package/lib/browser/env_web.js +171 -29
  46. package/lib/browser/env_webworker.js +91 -5
  47. package/lib/core/naanlib.js +5 -5
  48. package/lib/env_node.js +184 -8
  49. package/lib/env_nodeworker.js +74 -1
  50. package/package.json +1 -1
  51. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +2 -1
  52. package/plugins/serviceAws/aws_dynamo.nlg +1 -0
  53. package/plugins/serviceAws/aws_s3.nlg +3 -3
  54. package/plugins/serviceAws/aws_sqs.nlg +3 -1
  55. package/plugins/serviceAws/aws_utils.nlg +3 -1
  56. package/plugins/serviceAws/dbt_aws.nlg +22 -20
  57. package/plugins/serviceAws/psm_aws.nlg +2 -1
  58. package/plugins/serviceNexara/speechrec.nlg +10 -6
  59. package/test/test_02_context.nlg +24 -1
  60. package/test/test_03_jsinterop.nlg +32 -3
  61. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +0 -33
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2025 by Richard C. Zulch
9
+ * Copyright (c) 2019-2026 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -18,6 +18,69 @@
18
18
  gWorkers;
19
19
 
20
20
 
21
+ /*
22
+ * ioctl terminal control and events
23
+ *
24
+ * Example "raw" mode:
25
+ *
26
+ // rawTest
27
+ // echo typed keystrokes in BOLD until Control-C stops it.
28
+ //
29
+ closure rawTest(local dokey) {
30
+ dokey = xnew(function(msg, arg) {
31
+ if arg.key == "\x03" {
32
+ js.t.ioctl("raw", false)
33
+ js.t.ioctl_remove_listener("raw-key", dokey)
34
+ }
35
+ else
36
+ print("\x1b[1m${arg.key}\x1b[22m")
37
+ })
38
+ js.t.ioctl_add_listener("raw-key", dokey)
39
+ js.t.ioctl("raw", true)
40
+ };
41
+
42
+ rawTest();
43
+ *
44
+ *
45
+ * Example echo control:
46
+ *
47
+ js.t.ioctl("echo", 0) // no echo at all
48
+ js.t.ioctl("echo", 1) // echo newlines (best for hiding REPL commands)
49
+ js.t.ioctl("echo", 2) // normal echo
50
+
51
+ js.t.ioctl("sane") // restore echo and raw
52
+ *
53
+ *
54
+ * Example simulated keystrokes:
55
+ *
56
+ future(function(){ js.t.ioctl("keystroke", "joe") }, 10); // "joe" in keyboard buffer
57
+ *
58
+ *
59
+ * Example terminal resize notification:
60
+ *
61
+ doresize = xnew(function(msg, dim) { printline("(${dim.cols}, ${dim.rows})")})
62
+ js.t.ioctl_add_listener("term-resize", doresize);
63
+ *
64
+ *
65
+ * Example terminal connection notification:
66
+ *
67
+ // add removable listener:
68
+ listener = xnew(function(msg,arg) { debuglog("listener:", msg, arg) });
69
+ js.t.ioctl_add_listener("term-attach", listener);
70
+ js.t.ioctl_add_listener("term-detach", listener);
71
+
72
+ // remove the listeners:
73
+ js.t.ioctl_remove_listener("term-attach", listener);
74
+ js.t.ioctl_remove_listener("term-detach", listener);
75
+ *
76
+ * Note that js.t.ioctl(...) controls the *terminal* even if attached to a different instance. But
77
+ * ioctl event listeners are attached to an *instance* regardless of what terminal is connected.
78
+ * But in general it does the "right thing" for multiple terminals on a single instance, and when
79
+ * switching among instances.
80
+ *
81
+ */
82
+
83
+
21
84
  /*
22
85
  * TermHost
23
86
  *
@@ -27,387 +90,95 @@ gWorkers;
27
90
  * {
28
91
  * type: <string> // target category, e.g. "Host", "api.yyy.com"
29
92
  * workerID: <string> // kind of worker in server subsystem
30
- * dispatcher: <tuple> // dispatcher function for incoming messages
31
93
  * startup: <dictionary> // startup options
32
- * clientID: <string> // (rare) our identification on client
33
- * serverID: <string> // (rare) server subsystem for us
34
94
  * }
35
95
  *
36
96
  */
37
97
 
38
- closure TermHost(track, api, name, options, local target, nlregex, xtarg)
98
+ closure TermHost(track, name, options,
99
+ local relay, error, destID, target, xc)
39
100
  {
40
- global(window)
41
- options = merge({
42
- type: "Host"
43
- workerID: "myDebugWorker"
44
- clientID: "DebugWorkers"
45
- serverID: "Workers"
46
- }, options)
47
- target = require("../running/executors.nlg").ExecutorBase(track, options.type, name)
48
- nlregex = RegExp("\\n", "g")
49
- target.workerID = options.workerID
50
- target.msgQueue = []
51
- target.dispatcher = call(options.dispatcher)
101
+ global(App)
52
102
 
53
- // findWorker
103
+ // findHostInstance
54
104
  //
55
- function findWorker(testID, local instance) {
56
- for instance in track.instances {
57
- if instance.executor.hostWorkerMatch(api, testID)
58
- return (instance.executor)
59
- }
60
- false
61
- }
62
-
63
- // connect
64
- // Connect with the host if not already connected.
65
- closure connect(local url, pending) {
66
- if target.ws
67
- return(list(false, { connected: true }))
68
- try {
69
- url = xnew(window.URL, api.url)
70
- } catch {
71
- return(list(Error("invalid URL ${api.url}", exception)))
105
+ // Find the host instance name/type, or create new remote worker. The default for no name/type is
106
+ // the server itself.
107
+ //
108
+ function findHostInstance(relay, name, type, local error, found, dest, worker) {
109
+ `(error, found) = relay.clients()
110
+ if !error {
111
+ dest = found.find(function(item) { item.name == "NaanIDE Server" && item.type == "Server IMP" })
112
+ if !dest
113
+ error = Error("cannot locate host")
72
114
  }
73
- if url.protocol == "https:"
74
- url.protocol = "wss:"
75
- else
76
- url.protocol = "ws:"
77
- target.ws = xnew(window.WebSocket, url.href)
78
- pending = new(nonce)
79
-
80
- // onopen
81
- // The WebSocket connection is now open.
82
- target.ws.onopen = function onopen(e, local info, spawner) {
83
- info = {
84
- name: name
85
- workerID: target.workerID
115
+ if !error
116
+ `(error, found) = relay.clients(dest.destID)
117
+ if !error {
118
+ if name && type {
119
+ dest = found.find(function(item) { item.name == name && item.type == type })
120
+ if dest
121
+ return (list(false, dest.destID)) // found worker
86
122
  }
87
- if options.dispatcher
88
- info.destID = api.instanceID // valid destination for relay
89
- target.ws.send(window.JSON.stringify({
90
- guid: api.guid
91
- init: info
92
- }))
93
- spawner = new(options.startup) || { }
94
- spawner.name = name
95
- sendControl("spawn", spawner)
96
- pending.signal(list(false, { ok: true }))
97
- }
98
-
99
- // onmessage
100
- // A message was received from the worker via WebSockets.
101
- target.ws.onmessage = function onmessage(e, message, local msg) {
102
- try {
103
- message = new(window.JSON.parse(e.data))
104
- } catch {
105
- if true {
106
- debuglog("can't decode incoming ws message:", exception)
107
- return }
123
+ if !name || !type { // find main instance
124
+ dest = found.find(function(item) { item.name == "NaanIDE Server" && item.type == "NaaN Server" })
125
+ if !dest
126
+ return (list(Error("cannot locate NaanIDE Server")))
127
+ else
128
+ return (list(false, dest.destID))
108
129
  }
109
- if message.respOp == "msgout" {
110
- msg = message.payload
111
- if msg.id == "spawned" || msg.id == "workerlist" || msg.id == "termination"
112
- return (hostWorkerUpdate(msg))
113
- if msg.id != "targetout" {
114
- if msg.id == "status" {
115
- target.lastStatus = milliseconds()
116
- target.execSetStatus(msg.status)
117
- }
118
- if target.msgQueue {
119
- if msg.id != "status"
120
- target.msgQueue.push(msg)
121
- return } }
122
- target.doMessage(msg) }
123
- else if message.respOp == "error"
124
- debuglog("worker controller error:", ErrorString(message.payload[0]))
125
- else if message.respOp == "linked"
126
- future(function termLinked(){
127
- if !target.written
128
- target.term.WriteLn("(reconnected)")
129
- }, 1000).run()
130
- else if message.workerOp == "msgin" && message.payload.id == "targetin"
131
- dispatcher(message) // request from Host
132
- else if message.respOp || message.id
133
- debuglog("unknown host response message:", message.respOp || message.id)
134
- }
135
-
136
- // dispatcher
137
- // Respond to a host request while preserving context to permit reply routing
138
- closure dispatcher(message) {
139
- target.dispatcher(message.payload.data, function(reply) {
140
- target.ws.send(window.JSON.stringify({
141
- guid: api.guid
142
- clientID: message.clientID
143
- serverID: message.serverID
144
- workerID: message.workerID
145
- respOp: "msgout"
146
- payload: {
147
- id: "targetout",
148
- data: reply
149
- }
150
- }))
151
- })
152
- }
153
-
154
- // onerror
155
- // An error occurred on the WebSocket connection.
156
- // This can happen in normal operation when the connection times out.
157
- target.ws.onerror = function onerror(e) {
158
- pending.signal(list(Error("websocket failed", e)))
159
- }
160
-
161
- // onclose
162
- // The WebSocket connection was closed.
163
- target.ws.onclose = function onclose(e) {
164
- if e.code != 1000 && e.code != 1001 && e.code != 1005 && e.code != 1006
165
- debuglog("ws close:", e.code, e.reason)
166
- target.execClosed(Error("socket closed", e.code, e.reason))
167
- target.ws = false
168
- }
169
-
170
- pending.wait()
171
- }
172
-
173
- //
174
- // hostWorkerUpdate
175
- //
176
- // New workers created/discovered on this host.
177
- //
178
- function hostWorkerUpdate(msg, local localID, worker) {
179
- if msg.id == "spawned" {
180
- if !findWorker(msg.workerID)
181
- TermHost(track, api, msg.name, { type: options.type, workerID: msg.workerID })
182
- }
183
- else if msg.id == "termination"
184
- track.delete(findWorker(msg.workerID))
185
- else if msg.id == "workerlist" {
186
- for `(localID, worker) in msg.workers
187
- if !findWorker(localID)
188
- TermHost(track, api, worker.name, { type: options.type, workerID: localID })
189
- }
190
- }
191
-
192
- //
193
- // hostWorkerMatch
194
- //
195
- // True iff the specified information matches us.
196
- //
197
- target.hostWorkerMatch = function hostWorkerMatch(testApi, testID) {
198
- testApi === api && testID == target.workerID
199
- }
200
-
201
- //
202
- // doMessage
203
- //
204
- // Process the specified undecoded message.
205
- target.doMessage = function doMessage(msg) {
206
- if msg.id == "textout" {
207
- target.written = true
208
- target.term.Write(msg.text.replace(nlregex, "\r\n")) }
209
- else if msg.id == "debugtext" {
210
- target.written = true
211
- target.debugWrite(msg.text, msg.level) }
212
- else if msg.id == "targetout"
213
- target.execReceived(msg.data)
214
- else if msg.id == "started"
215
- { }
216
- else if msg.id == "typed"
217
- target.term.Write(msg.text.replace(nlregex, "\r\n"))
218
- else if msg.id == "status"
219
- target.statusCB(target, msg.status)
220
- else if msg.id == "debugout"
221
- target.debugger.debugData(target, msg.data)
222
- else
223
- debuglog("unknown host worker message:", msg.id)
224
- }
225
-
226
- //
227
- // sendControl
228
- //
229
- // Send a message to the worker controller.
230
-
231
- target.sendControl = function sendControl(workerOp, payload) {
232
- target.ws.send(window.JSON.stringify({
233
- guid: api.guid
234
- clientID: options.clientID
235
- serverID: options.serverID
236
- workerID: target.workerID
237
- workerOp: workerOp
238
- payload: payload
239
- }))
240
- }
241
-
242
- //
243
- // send
244
- //
245
- // Send a message to the worker.
246
-
247
- target.send = function send(msg) {
248
- sendControl("msgin", msg)
249
- }
250
-
251
- //
252
- // oninterrupt
253
- //
254
-
255
- function oninterrupt() {
256
- send({ // send interrupt to worker
257
- id: "interrupt"
258
- })
259
- }
260
-
261
- //
262
- // onreturn
263
- //
264
-
265
- function onreturn(local text) {
266
- text = target.term.TakeText()
267
- echotext(text)
268
- send({ // send user-entered text to worker
269
- id: "keyline",
270
- text: text
271
- })
272
- }
273
-
274
- // echotext
275
- //
276
- // Echo text to other terminals.
277
-
278
- function echotext(text) {
279
- sendControl("echo", { // send user-entered text to worker
280
- id: "typed",
281
- text: "\x1b[90m\x1b[3m".concat(text, "\x1b[0m\n") // dark-grey, italic
282
- })
283
- }
284
-
285
- // target.KeyText
286
- //
287
- // Send text to the worker, with optional echo to terminal.
288
-
289
- target.KeyText = function KeyText(text, echo) {
290
- if echo {
291
- target.term.WriteLn(text.trim())
292
- echotext(text.trim())
130
+ dest = found.find(function(item) { item.name == "NaanIDE Server" && item.type == "Node Remote" })
131
+ if !dest
132
+ return (list(Error("cannot locate NaanIDE Server Remote")))
133
+ `(error, xc) = relay.context(dest.destID)
134
+ if !error {
135
+ `(error) = xc.evalq(chns("node"))
136
+ if error
137
+ return (list(Error("cannot switch namespace", error)))
138
+ }
139
+ if !error
140
+ closure (options) {
141
+ `(error, worker) = xc.evalq(NobBot(options), `(options))
142
+ }({
143
+ name: name
144
+ type: options.type
145
+ startup: options.startup
146
+ topOriginID: App.imp.us.instanceID
147
+ })
148
+ if error
149
+ error = Error("cannot spawn remote instance", error)
150
+ else
151
+ return (list(false, worker.instanceID))
293
152
  }
294
- send({ // send user-entered text to worker
295
- id: "keyline",
296
- text: text
297
- })
153
+ list(error)
298
154
  }
299
155
 
300
- //
301
- // target.PostMessage
302
- //
303
- // Post a message to the target.
304
- //
305
-
306
- target.PostMessage = function PostMessage(data, local result) {
307
- result = connect()
308
- if result.0
309
- return (result)
310
- send({ // send data to worker
311
- id: "targetin",
312
- data: data
313
- })
314
- list(false, { posted: true })
315
- }
316
-
317
- //
318
- // target.DebugCmd
319
- //
320
- // Post a debug command to the target.
321
- //
322
-
323
- target.DebugCmd = function DebugCmd(data) {
324
- connect()
325
- send({ // send debug command to worker
326
- id: "debugin",
327
- data: data
328
- })
329
- }
156
+ options = merge({
157
+ type: "Host"
158
+ }, options)
330
159
 
331
- //
332
- // target.debugWrite
333
- //
334
- // Write a debug message to the repl.
335
- //
336
-
337
- target.debugWrite = function(text, level) {
338
- text = text.replaceAll("\n", "\r\n")
339
- if (level >= 5)
340
- target.term.WriteLn(text, target.term.Cyan) // cyan for builtin logging
341
- else if (level >= 4)
342
- target.term.WriteLn(text, target.term.Blue) // blue for Naan function logging
343
- else if (level >= 3)
344
- target.term.WriteLn(text, target.term.Red) // light red for warnings
345
- else if (level >= 2)
346
- target.term.WriteLn(text, target.term.Green) // green for Naan.debug.debuglog logging
347
- else
348
- target.term.WriteLn(text, target.term.Red+target.term.Bold) // heavy red for errors
349
- }
160
+ target = findIDEtarget(track, name, options.workerID)
161
+ if target
162
+ return (list(false, target)) // already exists
163
+
164
+ relay = require("naanlib:frameworks/client/relaycon.nlg").RelayCon(App.imp)
165
+ if options.workerID == "NideServer" // server main thread
166
+ `(error, destID) = findHostInstance(relay)
167
+ else
168
+ `(error, destID) = findHostInstance(relay, name, options.type)
169
+ relay.close()
170
+ if error
171
+ return (list(error))
350
172
 
351
- //
352
- // termAttach
353
- //
354
- // A terminal and/or debugger was attached to our instance.
355
-
356
- target.termAttach = function termAttach(repl, db, statusCB, local msg) {
357
- target.term = repl
358
- target.debugger = db
359
- target.statusCB = statusCB
360
- if target.termSaved {
361
- target.term.StateLoad(target.termSaved)
362
- target.termSaved = false }
363
- else
364
- target.term.Reset()
365
- target.debugger.attach(target)
366
- while target.msgQueue.length > 0 {
367
- for msg in target.msgQueue.splice(0) // all current messages
368
- target.doMessage(msg)
369
- }
370
- target.msgQueue = false
371
- target.term.On("interrupt", oninterrupt.proc)
372
- target.term.On("return", onreturn.proc)
373
- target.term.Focus()
374
- connect() // ensure we are connected
375
- target
376
- }
377
-
378
- //
379
- // termDetach
380
- //
381
- // The terminal is being deattached from our instance.
382
-
383
- target.termDetach = function termDetach(repl, db) {
384
- target.term.On("interrupt")
385
- target.term.On("return")
386
- target.msgQueue = []
387
- target.debugger.detach(target)
388
- target.termSaved = target.term.StateSave()
389
- target.term.Reset()
390
- target.statusCB = false
391
- target.debugger = false
392
- target.term = false
393
- }
394
-
395
- // destroy
396
- //
397
- // Destroy the execution instance.
398
-
399
- target.destroy = function destroy() {
400
- track.delete(target)
401
- termDetach()
402
- sendControl("terminate", { })
403
- target.ws.close(1000) // 1000: normal close
404
- }
173
+ target = require("../running/instances.nlg").InTop(track, {
174
+ name: name
175
+ type: options.type
176
+ })
177
+ target.workerID = options.workerID
405
178
 
406
- xtarg = findWorker(target.workerID)
407
- if xtarg
408
- return (list(false, xtarg)) // already exists
409
- track.add(target)
410
- connect() // connect right away to start receiving messages
179
+ `(error) = target.open(destID)
180
+ if error
181
+ return (list(Error("cannot create InTop", error)))
411
182
 
412
183
  // finis
413
184
 
@@ -424,14 +195,13 @@ closure TermHost(track, api, name, options, local target, nlregex, xtarg)
424
195
  * {
425
196
  * workerID: <string> // kind of worker in server subsystem
426
197
  * // "NaanIDE" for local IDE
427
- * // "NaanIDE-Debug" for browser debug target
428
198
  * startup: <dictionary> // startup options
429
199
  * type: <string> // (rare) target category, e.g. "Local"
430
200
  * }
431
201
  *
432
202
  */
433
203
 
434
- closure TermLocal(track, name, options, local target, nlregex, listener)
204
+ closure TermLocal(track, name, options, local target, worker, inbot, error)
435
205
  {
436
206
  global(js, window, gWorkers)
437
207
  if !dictionary(gWorkers)
@@ -442,293 +212,28 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
442
212
  if options.workerID == "NaanIDE" {
443
213
  if !js.t
444
214
  return (list(Error("Cannot access Local IDE executor")))
445
- termInstallVirtualWatcher(track, js.t)
215
+ termInstallVirtualWatcher(track, window)
446
216
  return (termIDE(track, name, options.workerID, js.t)) // connect to IDE execution instance itself
447
217
  }
448
- target = require("../running/executors.nlg").ExecutorBase(track, options.type, name)
449
- nlregex = RegExp("\\n", "g")
450
- target.workerID = options.workerID
451
- target.msgQueue = []
452
- if options.workerID == "NaanIDE-Debug" {
453
- target.worker = window.open(window.location.href, "_blank")
454
- target.worker.addEventListener("beforeunload", function(e) { // our target is closing
455
- destroy()
456
- })
457
- window.addEventListener("beforeunload", function(e) { // we are closing
458
- // ### tell target we are closing (if needed)
459
- })
460
- listener = window
461
- target.guid = 1
462
- } else {
463
- if not window.Worker
464
- return (list(Error("Local environment does not support workers")))
465
- target.worker = xnew(window.Worker, "env_webworker.js")
466
- listener = target.worker
467
- target.guid = UUID()
468
- }
469
- gWorkers[target.guid] = target
470
218
 
471
- // onmessage
472
- // A message was received from the worker.
473
-
474
- listener.addEventListener("message", function(e, msg) {
475
- msg = new(e.data)
476
- if msg.id == "termination" {
477
- destroy()
478
- return }
479
- else if msg.id != "targetout" && msg.id != "loaded" {
480
- if msg.id == "status" {
481
- target.lastStatus = milliseconds()
482
- target.execSetStatus(msg.status)
483
- }
484
- if target.msgQueue {
485
- if msg.id != "status"
486
- target.msgQueue.push(msg)
487
- return } }
488
- target.doMessage(msg)
489
- })
219
+ target = findIDEtarget(track, name, options.workerID)
220
+ if target
221
+ return (list(false, target)) // already exists
490
222
 
491
- // onerror
492
- // The worker had an error.
493
-
494
- target.worker.addEventListener("error", function(e) {
495
- debuglog("TermLocal worker error:", e.message)
496
- target.execClosed(Error("instance error", e.message))
223
+ worker = xnew(window.Worker, "env_webworker.js")
224
+ inbot = NubBot(worker, {
225
+ name: name
226
+ type: options.workerID
227
+ startup: options.startup
497
228
  })
498
-
499
- //
500
- // oninterrupt
501
- //
502
-
503
- function oninterrupt() {
504
- target.worker.postMessage({ // send interrupt to worker
505
- id: "interrupt"
506
- })
507
- }
508
-
509
- //
510
- // onreturn
511
- //
512
-
513
- function onreturn() {
514
- target.worker.postMessage({ // send user-entered text to worker
515
- id: "keyline",
516
- text: target.term.TakeText()
517
- })
518
- }
519
-
520
- // target.KeyText
521
- //
522
- // Send text to the worker, with optional echo to terminal.
523
-
524
- target.KeyText = function KeyText(text, echo) {
525
- if echo
526
- target.term.WriteLn(text.trim())
527
- target.worker.postMessage({ // send text to worker
528
- id: "keyline",
529
- text: text
530
- })
531
- }
532
-
533
- //
534
- // doMessage
535
- //
536
- // Process the specified undecoded message.
537
-
538
- target.doMessage = function doMessage(msg) {
539
- if msg.id == "textout"
540
- target.term.Write(msg.text.replace(nlregex, "\r\n"))
541
- else if msg.id == "debugtext"
542
- target.debugWrite(msg.text, msg.level)
543
- else if msg.id == "targetout"
544
- target.execReceived(msg.data)
545
- else if msg.id == "status"
546
- target.statusCB(target, msg.status)
547
- else if msg.id == "debugout"
548
- target.debugger.debugData(target, msg.data)
549
- else if msg.id == "download"
550
- FileDownload(xnew(window.File, msg.bits, msg.name, msg.options))
551
- else if msg.id == "loaded" {
552
- if !target.started
553
- target.worker.postMessage({
554
- id: "start",
555
- state: false // resume state, if we have any
556
- altcmd: options.startup.initcmds
557
- workerID: target.workerID
558
- workerGUID: target.guid
559
- })
560
- target.started = true
561
- } else if msg.id == "targetsend" {
562
- msg.data._guid = target.guid
563
- js.t.DispatchMessage(msg.data)
564
- }
565
- }
566
-
567
- //
568
- // target.DebugCmd
569
- //
570
- // Post a debug command to the target.
571
- //
572
-
573
- target.DebugCmd = function DebugCmd(data) {
574
- target.worker.postMessage({ // send data to worker
575
- id: "debugin"
576
- data: data
577
- })
578
- }
579
-
580
- //
581
- // target.PostMessage
582
- //
583
- // Post a message to the target, if it has registered a listener.
584
- //
585
-
586
- target.PostMessage = function PostMessage(data) {
587
- target.worker.postMessage({ // send data to worker
588
- id: "targetin",
589
- data: data
590
- })
591
- list(false, { posted: true })
592
- }
593
-
594
- //
595
- // target.debugWrite
596
- //
597
- // Write a debug message to the repl.
598
- //
599
-
600
- target.debugWrite = function(text, level) {
601
- text = text.replaceAll("\n", "\r\n")
602
- if (level >= 5)
603
- target.term.WriteLn(text, target.term.Cyan) // cyan for builtin logging
604
- else if (level >= 4)
605
- target.term.WriteLn(text, target.term.Blue) // blue for Naan function logging
606
- else if (level >= 3)
607
- target.term.WriteLn(text, target.term.Red) // light red for warnings
608
- else if (level >= 2)
609
- target.term.WriteLn(text, target.term.Green) // green for Naan.debug.debuglog logging
610
- else
611
- target.term.WriteLn(text, target.term.Red+target.term.Bold) // heavy red for errors
612
- }
613
-
614
- //
615
- // termAttach
616
- //
617
- // A terminal was attached to our instance.
618
-
619
- target.termAttach = function termAttach(repl, db, statusCB, local msg) {
620
- target.term = repl
621
- target.debugger = db
622
- target.statusCB = statusCB
623
- if target.termSaved {
624
- target.term.StateLoad(target.termSaved)
625
- target.termSaved = false }
626
- else
627
- target.term.Reset()
628
- target.debugger.attach(target)
629
- while target.msgQueue.length > 0 {
630
- for msg in target.msgQueue.splice(0) // all current messages
631
- target.doMessage(msg)
632
- }
633
- target.msgQueue = false
634
- target.term.On("interrupt", oninterrupt.proc)
635
- target.term.On("return", onreturn.proc)
636
- target.term.Focus()
637
- target
638
- }
639
-
640
- //
641
- // termDetach
642
- //
643
- // The terminal is being deattached from our instance.
644
-
645
- target.termDetach = function termDetach() {
646
- target.term.On("interrupt")
647
- target.term.On("return")
648
- target.msgQueue = []
649
- target.debugger.detach(target)
650
- target.termSaved = target.term.StateSave()
651
- target.statusCB = false
652
- target.debugger = false
653
- target.term = false
654
- }
655
-
656
- // destroy
657
- //
658
- // Destroy the execution instance.
659
-
660
- target.destroy = function destroy() {
661
- gWorkers[target.guid] = undefined
662
- target.execClosed(Error("instance destroyed"))
663
- track.delete(target)
664
- termDetach()
665
- target.worker.terminate() // kill without saving
666
- target.worker = false
667
- }
668
-
669
- track.add(target)
670
-
671
- // finis
672
-
673
- list(false, target)
674
- };
675
-
676
-
677
- /*
678
- * WorkerConnect
679
- *
680
- * Return the existing web worker for a workerGUID, or false if it does not exist.
681
- *
682
- */
683
-
684
- function WorkerConnect(workerGUID) {
685
- gWorkers[workerGUID]
686
- };
687
-
688
-
689
- /*
690
- * termMainProxy
691
- *
692
- * Make a browser proxy representing the main thread. This runs inside the worker and allows it
693
- * to do remote execution with Main.
694
- *
695
- */
696
-
697
- closure termMainProxy(track, name, options, local target) {
698
- global(js)
699
- target = require("../running/executors.nlg").ExecutorBase(track, "mainProxy", name)
700
- target.name = name
701
-
702
- //
703
- // target.PostMessage
704
- //
705
- // Post a message to the main thread.
706
- //
707
- target.PostMessage = function PostMessage(data) {
708
- js.t.DispatchMessage(data)
709
- list(false, { posted: true })
710
- }
711
-
712
- //
713
- // onreceive
714
- //
715
- // Process a received message from the main thread, typically in response.
716
- //
717
- function onreceive(data) {
718
- target.execReceived(data)
719
- }
720
-
721
- // destroy
722
- //
723
- // Destroy the execution instance.
724
- //
725
- target.destroy = function destroy() {
726
- target.execClosed(Error("instance destroyed"))
727
- track.delete(target)
728
- }
729
-
730
- js.t.OnReceive(onreceive.proc)
731
- track.add(target)
229
+ target = require("../running/instances.nlg").InTop(track, {
230
+ name: name
231
+ type: options.type
232
+ workerID: options.workerID
233
+ })
234
+ `(error) = target.open(inbot.instanceID)
235
+ if error
236
+ return (list(Error("cannot create InTop", error)))
732
237
 
733
238
  // finis
734
239
 
@@ -736,42 +241,21 @@ closure termMainProxy(track, name, options, local target) {
736
241
  };
737
242
 
738
243
 
739
- /*
740
- * WorkerToMain
741
- *
742
- * Connect our worker to the main thread, returning a result tuple with an executor representing
743
- * Main. Use this to create a context object for Main, allowing remote evaluation.
744
- *
745
- */
746
-
747
- closure WorkerToMain(local nideRunning, track) {
748
- // mainProxy -- link tracker to main thread proxy
749
- function mainProxy(track, name, options) {
750
- termMainProxy(track, name, options)
751
- }
752
-
753
- if !js.s {
754
- error = Error("WorkerToMain only available in browser workers")
755
- return (list(error))
756
- }
757
- nideRunning = require("naanlib:frameworks/running/executors.nlg")
758
- track = nideRunning.ExecutorTracker()
759
- track.register(mainProxy, "proxy")
760
- track.spawn("proxy") // returns result tuple
761
- };
762
-
763
-
764
244
  /*
765
245
  * findIDEtarget
766
246
  *
767
- * Find a target for our IDE that matches the specified name, workerID, and naancont.
247
+ * Find a target for our IDE that matches the specified name, workerID, and naancont. Either the
248
+ * title or the naancont must match. The title will match but not the naancont if the window was
249
+ * closed/reloaded but we didn't get notified because browsers are annoying that way. Alternatively
250
+ * the naancont will match but not the title if we have updated the version.
768
251
  *
769
252
  */
770
253
 
771
- closure findIDEtarget(track, name, workerID, naancont, local target) {
254
+ closure findIDEtarget(track, name, workerID, title, naancont, local target) {
772
255
  global()
773
256
  for target in track.enumlist()
774
- if target.name == name && target.workerID == workerID && (!target.naancont || target.naancont === naancont)
257
+ if target.name == name && target.workerID == workerID
258
+ && (target.title == title || !target.naancont || target.naancont === naancont)
775
259
  return (target)
776
260
  false
777
261
  };
@@ -788,8 +272,10 @@ closure findIDEtarget(track, name, workerID, naancont, local target) {
788
272
 
789
273
  closure termIDE(track, name, workerID, naancont, title, local target, connected) {
790
274
  global(js, gWorkers)
791
- target = findIDEtarget(track, name, workerID, naancont)
275
+ target = findIDEtarget(track, name, workerID, title, naancont)
792
276
  if target { // new naancont on existing target
277
+ if target.naancont && naancont !== target.naancont
278
+ target.targetClose()
793
279
  target.updateController(naancont)
794
280
  target.title = title
795
281
  track.update(target)
@@ -800,6 +286,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
800
286
  target.title = title
801
287
  target.workerID = workerID
802
288
  target.naancont = naancont
289
+ naancont.ioctl_open(ioctl_callback.proc)
803
290
  connected = []
804
291
 
805
292
  // replyHook
@@ -814,6 +301,20 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
814
301
  })
815
302
  }
816
303
 
304
+ // ioctl_callback
305
+ //
306
+ // Ioctl from our instance.
307
+ //
308
+ function ioctl_callback(op, arg) { // where ioctls go when they have nowhere to go
309
+ debuglog("no-terminal ioctl:", op, arg)
310
+ }
311
+
312
+ // ioctl_event
313
+ //
314
+ target.ioctl_event = function ioctl_event(msg, arg) {
315
+ naancont.ioctl_event(msg, arg)
316
+ }
317
+
817
318
  // target.DebugCmd
818
319
  //
819
320
  // Post a debug command to the target, ourselves.
@@ -835,15 +336,16 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
835
336
  //
836
337
  // Create a closure for an attaching terminal.
837
338
  //
838
- target.termAttach = closure termAttach(term, db, statusCB, local connection, inbound) {
339
+ target.termAttach = closure termAttach(term, db, notify, local connection, jsterm, inbound) {
839
340
  connection = new(object, this)
341
+ jsterm = term.jsAcquire() // termIDE must be direct
840
342
 
841
343
  function oninterrupt() {
842
344
  naancont.Interrupt(inbound)
843
345
  }
844
346
 
845
347
  function onreturn() {
846
- naancont.Return(term.TakeText(), inbound)
348
+ naancont.Return(jsterm.TakeText(), inbound)
847
349
  }
848
350
 
849
351
  function onstatus(status) {
@@ -861,7 +363,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
861
363
 
862
364
  connection.KeyText = function KeyText(text, echo) { // send text to instance
863
365
  if echo
864
- term.WriteLn(text.trim()) // echo to terminal
366
+ jsterm.WriteLn(text.trim()) // echo to terminal
865
367
  naancont.Return(text, inbound)
866
368
  }
867
369
 
@@ -871,16 +373,15 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
871
373
  }
872
374
 
873
375
  connection.WriteLn = function WriteLn(text) {
874
- term.WriteLn(text)
376
+ jsterm.WriteLn(text)
875
377
  }
876
378
 
877
379
  connection.termDetach = function termDetach() { // disconnect the terminal
878
380
  db.detach(target)
879
- term.On("interrupt")
880
- term.On("return")
881
381
  if inbound
882
382
  naancont.Detach(inbound)
883
383
  connected = connected.filter(function(conn) { conn !== connection })
384
+ term.jsRelease()
884
385
  }
885
386
 
886
387
  connection.popup = true // we can handle multiple terminals
@@ -888,15 +389,15 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
888
389
  let (replstate) {
889
390
  replstate = naancont.StateGet()
890
391
  if (replstate)
891
- term.StateLoad(replstate)
392
+ jsterm.StateLoad(replstate)
892
393
  else
893
- term.Reset()
894
- term.On("interrupt", oninterrupt.proc)
895
- term.On("return", onreturn.proc)
896
- term.Focus()
394
+ jsterm.Reset()
395
+ jsterm.On("interrupt", oninterrupt.proc)
396
+ jsterm.On("return", onreturn.proc)
397
+ jsterm.Focus()
897
398
  inbound = xnew({
898
- OnMessage: term.OnMessage
899
- StateSave: term.StateSave
399
+ OnMessage: jsterm.OnMessage
400
+ StateSave: jsterm.StateSave
900
401
  OnStatus: onstatus.proc
901
402
  OnMessageOut: onmessageout.proc
902
403
  OnDebugOut: ondebugout.proc
@@ -907,7 +408,33 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
907
408
  connected.push(connection)
908
409
  db.attach(target)
909
410
  } ()
910
-
411
+
412
+ naancont.ioctl_open(function (op, arg) {
413
+ if op == "sane"
414
+ jsterm.Sane()
415
+ else if op == "raw" {
416
+ if arg // enable raw mode
417
+ jsterm.OnRaw(function(str, origin) {
418
+ ioctl_event("raw-key", {
419
+ key: str
420
+ origin: origin
421
+ })
422
+ })
423
+ else // else disable raw
424
+ jsterm.OnRaw()
425
+ } else if op == "keystroke"
426
+ jsterm.PutKey(arg) // simulate a keystroke
427
+ else if op == "echo"
428
+ jsterm.EchoSet(arg) // echo: 0, 1, 2
429
+ })
430
+
431
+ jsterm.OnResize(function(columns, rows) {
432
+ ioctl_event("term-resize", {
433
+ cols: columns
434
+ rows: rows
435
+ })
436
+ })
437
+
911
438
  // finis
912
439
  connection
913
440
  }
@@ -917,7 +444,9 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
917
444
  // Update the naan controller on this target, which means updating for all the terminals.
918
445
  //
919
446
  target.updateController = function updateController(newnc, local conn) {
447
+ naancont.ioctl_close()
920
448
  naancont = newnc
449
+ naancont.ioctl_open(ioctl_callback.proc)
921
450
  target.naancont = newnc
922
451
  for conn in connected {
923
452
  if naancont {
@@ -934,12 +463,13 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
934
463
  //
935
464
  // Mark the target window as closed.
936
465
  //
937
- function targetClose(local conn) {
466
+ target.targetClose = function targetClose(local conn) {
938
467
  for conn in connected {
939
468
  conn.db.detach(target)
940
469
  conn.WriteLn("\x1b[90m\x1b[3m".concat("\ntarget closed", "\x1b[0m"))
941
470
  }
942
471
  target.execReset()
472
+ naancont.ioctl_close()
943
473
  target.naancont = naancont = false
944
474
  }
945
475
 
@@ -1012,12 +542,12 @@ closure termInstallVirtualWatcher(track, naancont) {
1012
542
  target.attention()
1013
543
  }
1014
544
  else if msg.op == "VsiteClose" {
1015
- target = findIDEtarget(track, msg.name, "NaanVsite", msg.naancont)
545
+ target = findIDEtarget(track, msg.name, "NaanVsite", msg.title, msg.naancont)
1016
546
  if target
1017
547
  target.updateController()
1018
548
  }
1019
549
  else if msg.op == "VsiteVisibilityChange" {
1020
- target = findIDEtarget(track, msg.name, "NaanVsite", msg.naancont)
550
+ target = findIDEtarget(track, msg.name, "NaanVsite", msg.title, msg.naancont)
1021
551
  if target
1022
552
  target.setCheckStatus(msg.hidden, msg.window)
1023
553
  }
@@ -1027,6 +557,120 @@ closure termInstallVirtualWatcher(track, naancont) {
1027
557
  };
1028
558
 
1029
559
 
560
+ /*
561
+ * testTab
562
+ *
563
+ * Quick test of the InTop/InBot connection.
564
+ *
565
+ */
566
+
567
+ closure testTab(destID,
568
+ local comMod, relay, error, found, dest, xc, inbotDestID, intop) {
569
+
570
+ // findBottomID
571
+ //
572
+ // Find the inbot destID that goes with our desired workerID.
573
+ //
574
+ function findBottomID(found, workerID, local item) {
575
+ for item in found
576
+ if item.name == "Play" && item.type == workerID
577
+ return (item.destID)
578
+ false
579
+ }
580
+
581
+ comMod = require("naanlib:frameworks/client/relaycon.nlg")
582
+ relay = comMod.RelayCon(App.imp)
583
+ `(error, found) = relay.clients(destID)
584
+ dest = found.find(function(item) { item.name == "NaanIDE" && item.services.indexOf("naan-remote-responder") >= 0 })
585
+ if !dest
586
+ return (list(Error("cannot locate remoting")))
587
+ `(error, xc)= relay.context(dest.destID)
588
+ if error
589
+ return (list(Error("cannot access remoting", error)))
590
+ inbotDestID = findBottomID(found, "Webworker-Play")
591
+ if !inbotDestID
592
+ return (list(Error("cannot find web worker")))
593
+ intop = require("../running/instances.nlg").InTop(App.nide.track, {
594
+ name: "NaanIDE-Play"
595
+ type: "Relay"
596
+ })
597
+ `(error) = intop.open(inbotDestID)
598
+ if error
599
+ return (list(Error("cannot create InTop", error)))
600
+ list(false, xc, intop)
601
+ };
602
+
603
+
604
+ /*
605
+ * relayTab
606
+ *
607
+ * Open a relay tab for the first peer NaanIDE found on the same server.
608
+ *
609
+ */
610
+
611
+ closure relayTab(destID, local comMod, relay, error, found, dest, xc, reterm, originID, inbot) {
612
+
613
+ // remoteFindTerm
614
+ //
615
+ // Executes on remote to give us a (proxy) terminal.
616
+ //
617
+ function remoteFindTerm(name, workerID, local target) {
618
+ for target in App.nide.track.enumlist()
619
+ if target.name == name && target.workerID == workerID
620
+ return (target)
621
+ false
622
+ }
623
+
624
+ comMod = require("naanlib:frameworks/client/relaycon.nlg")
625
+ relay = comMod.RelayCon(App.imp)
626
+ if !destID {
627
+ `(error, found) = relay.clients()
628
+ if !error {
629
+ dest = found.find(function(item) { item.name == "NaanIDE Server" && item.type == "Server IMP" })
630
+ if !dest
631
+ error = Error("cannot locate server")
632
+ }
633
+ if !error
634
+ `(error, found) = relay.clients(dest.destID)
635
+ if !error {
636
+ dest = found.find(function(item) { item.name == "NaanIDE"
637
+ && item.type == "Client IMP" && item.destID != App.imp.us.instanceID })
638
+ if !dest
639
+ error = Error("cannot locate peer tab")
640
+ else
641
+ destID = dest.destID
642
+ }
643
+ }
644
+ if error
645
+ return (list(Error("cannot locate target", error)))
646
+ `(error, xc) = relay.context(destID)
647
+ if error
648
+ return (list(Error("cannot access remoting", error)))
649
+ `(error) = xc.evalq(chns("browser"))
650
+ if error
651
+ return (list(Error("cannot switch namespace", error)))
652
+ `(error, reterm) = xc.evalq(remoteFindTerm("NaanIDE", "NaanIDE"), `(remoteFindTerm))
653
+ if error
654
+ return (list(Error("cannot get remote terminal", error)))
655
+ originID = App.imp.us.instanceID
656
+ `(error, inbot) = xc.evalq(InBot(window, {
657
+ name: "inbot"
658
+ type: reterm.workerID
659
+ topOriginID: originID
660
+ }), `(reterm, originID))
661
+ if error
662
+ return (list(Error("cannot create inBot", error)))
663
+ intop = require("../running/instances.nlg").InTop(App.nide.track, {
664
+ name: "NaanIDE Relay"
665
+ type: "Relay"
666
+ })
667
+ `(error) = intop.open(inbot.instanceID)
668
+ if error
669
+ return (list(Error("cannot create InTop", error)))
670
+ list(false, xc, intop)
671
+ };
672
+
673
+
1030
674
  /*
1031
675
  * termInit
1032
676
  *
@@ -1035,15 +679,13 @@ closure termInstallVirtualWatcher(track, naancont) {
1035
679
  */
1036
680
 
1037
681
  function termInit(local manifest) {
1038
- manifest = `(TermHost, TermLocal, WorkerConnect, termMainProxy, WorkerToMain, findIDEtarget,
1039
- termIDE, termInstallVirtualWatcher, termInit)
682
+ manifest = `(TermHost, TermLocal,
683
+ findIDEtarget, termIDE, termInstallVirtualWatcher, testTab, relayTab, termInit)
1040
684
 
1041
685
  Naan.module.build(module.id, "terminals", function(modobj, compobj) {
1042
- require("./browser.nlg")
686
+ require("./worker.nlg")
1043
687
  compobj.manifest = manifest
1044
688
  modobj.exports.TermLocal = TermLocal
1045
689
  modobj.exports.TermHost = TermHost
1046
- modobj.exports.WorkerConnect = WorkerConnect
1047
- modobj.exports.WorkerToMain = WorkerToMain
1048
690
  })
1049
691
  } ();