@naanlang/naan 1.5.0 → 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 (44) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +2 -2
  3. package/bin/index.js +7 -3
  4. package/dist/env_web.js +162 -20
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/https_request.nlg +2 -2
  7. package/frameworks/browser/sworker.js +28 -26
  8. package/frameworks/browser/terminals.nlg +329 -693
  9. package/frameworks/browser/worker.nlg +255 -0
  10. package/frameworks/browser/workers.nlg +5 -5
  11. package/frameworks/browser/ws_client.nlg +15 -10
  12. package/frameworks/client/apiclient.nlg +181 -9
  13. package/frameworks/client/relaycon.nlg +87 -161
  14. package/frameworks/common/events.nlg +101 -0
  15. package/frameworks/common/repltools.nlg +80 -1
  16. package/frameworks/node/apiserver.nlg +39 -246
  17. package/frameworks/node/filesystem.nlg +31 -6
  18. package/frameworks/node/http_request.nlg +34 -13
  19. package/frameworks/node/https_request.nlg +8 -3
  20. package/frameworks/node/node.nlg +2 -0
  21. package/frameworks/node/pty.nlg +346 -0
  22. package/frameworks/node/worker.nlg +409 -539
  23. package/frameworks/node/ws_client.nlg +2 -2
  24. package/frameworks/project/build.nlg +1 -1
  25. package/frameworks/project/projects.nlg +1 -1
  26. package/frameworks/running/debugnub.nlg +17 -4
  27. package/frameworks/running/executors.nlg +27 -274
  28. package/frameworks/running/instances.nlg +393 -0
  29. package/frameworks/running/remote_req.nlg +143 -0
  30. package/frameworks/running/remote_res.nlg +89 -0
  31. package/frameworks/running/taskexec.nlg +2 -2
  32. package/frameworks/service/imp.nlg +736 -0
  33. package/frameworks/service/model.nlg +71 -0
  34. package/frameworks/service/nubnode.nlg +105 -0
  35. package/frameworks/service/nubweb.nlg +106 -0
  36. package/frameworks/service/service.nlg +28 -0
  37. package/lib/browser/env_web.js +169 -27
  38. package/lib/browser/env_webworker.js +91 -4
  39. package/lib/core/naanlib.js +5 -5
  40. package/lib/env_node.js +184 -8
  41. package/lib/env_nodeworker.js +74 -1
  42. package/package.json +1 -1
  43. package/plugins/serviceNexara/speechrec.nlg +10 -6
  44. package/test/test_02_context.nlg +24 -1
@@ -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,31 +241,6 @@ 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
  *
@@ -806,6 +286,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
806
286
  target.title = title
807
287
  target.workerID = workerID
808
288
  target.naancont = naancont
289
+ naancont.ioctl_open(ioctl_callback.proc)
809
290
  connected = []
810
291
 
811
292
  // replyHook
@@ -820,6 +301,20 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
820
301
  })
821
302
  }
822
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
+
823
318
  // target.DebugCmd
824
319
  //
825
320
  // Post a debug command to the target, ourselves.
@@ -841,15 +336,16 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
841
336
  //
842
337
  // Create a closure for an attaching terminal.
843
338
  //
844
- target.termAttach = closure termAttach(term, db, statusCB, local connection, inbound) {
339
+ target.termAttach = closure termAttach(term, db, notify, local connection, jsterm, inbound) {
845
340
  connection = new(object, this)
341
+ jsterm = term.jsAcquire() // termIDE must be direct
846
342
 
847
343
  function oninterrupt() {
848
344
  naancont.Interrupt(inbound)
849
345
  }
850
346
 
851
347
  function onreturn() {
852
- naancont.Return(term.TakeText(), inbound)
348
+ naancont.Return(jsterm.TakeText(), inbound)
853
349
  }
854
350
 
855
351
  function onstatus(status) {
@@ -867,7 +363,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
867
363
 
868
364
  connection.KeyText = function KeyText(text, echo) { // send text to instance
869
365
  if echo
870
- term.WriteLn(text.trim()) // echo to terminal
366
+ jsterm.WriteLn(text.trim()) // echo to terminal
871
367
  naancont.Return(text, inbound)
872
368
  }
873
369
 
@@ -877,16 +373,15 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
877
373
  }
878
374
 
879
375
  connection.WriteLn = function WriteLn(text) {
880
- term.WriteLn(text)
376
+ jsterm.WriteLn(text)
881
377
  }
882
378
 
883
379
  connection.termDetach = function termDetach() { // disconnect the terminal
884
380
  db.detach(target)
885
- term.On("interrupt")
886
- term.On("return")
887
381
  if inbound
888
382
  naancont.Detach(inbound)
889
383
  connected = connected.filter(function(conn) { conn !== connection })
384
+ term.jsRelease()
890
385
  }
891
386
 
892
387
  connection.popup = true // we can handle multiple terminals
@@ -894,15 +389,15 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
894
389
  let (replstate) {
895
390
  replstate = naancont.StateGet()
896
391
  if (replstate)
897
- term.StateLoad(replstate)
392
+ jsterm.StateLoad(replstate)
898
393
  else
899
- term.Reset()
900
- term.On("interrupt", oninterrupt.proc)
901
- term.On("return", onreturn.proc)
902
- term.Focus()
394
+ jsterm.Reset()
395
+ jsterm.On("interrupt", oninterrupt.proc)
396
+ jsterm.On("return", onreturn.proc)
397
+ jsterm.Focus()
903
398
  inbound = xnew({
904
- OnMessage: term.OnMessage
905
- StateSave: term.StateSave
399
+ OnMessage: jsterm.OnMessage
400
+ StateSave: jsterm.StateSave
906
401
  OnStatus: onstatus.proc
907
402
  OnMessageOut: onmessageout.proc
908
403
  OnDebugOut: ondebugout.proc
@@ -913,7 +408,33 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
913
408
  connected.push(connection)
914
409
  db.attach(target)
915
410
  } ()
916
-
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
+
917
438
  // finis
918
439
  connection
919
440
  }
@@ -923,7 +444,9 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
923
444
  // Update the naan controller on this target, which means updating for all the terminals.
924
445
  //
925
446
  target.updateController = function updateController(newnc, local conn) {
447
+ naancont.ioctl_close()
926
448
  naancont = newnc
449
+ naancont.ioctl_open(ioctl_callback.proc)
927
450
  target.naancont = newnc
928
451
  for conn in connected {
929
452
  if naancont {
@@ -946,6 +469,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
946
469
  conn.WriteLn("\x1b[90m\x1b[3m".concat("\ntarget closed", "\x1b[0m"))
947
470
  }
948
471
  target.execReset()
472
+ naancont.ioctl_close()
949
473
  target.naancont = naancont = false
950
474
  }
951
475
 
@@ -1033,6 +557,120 @@ closure termInstallVirtualWatcher(track, naancont) {
1033
557
  };
1034
558
 
1035
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
+
1036
674
  /*
1037
675
  * termInit
1038
676
  *
@@ -1041,15 +679,13 @@ closure termInstallVirtualWatcher(track, naancont) {
1041
679
  */
1042
680
 
1043
681
  function termInit(local manifest) {
1044
- manifest = `(TermHost, TermLocal, WorkerConnect, termMainProxy, WorkerToMain, findIDEtarget,
1045
- termIDE, termInstallVirtualWatcher, termInit)
682
+ manifest = `(TermHost, TermLocal,
683
+ findIDEtarget, termIDE, termInstallVirtualWatcher, testTab, relayTab, termInit)
1046
684
 
1047
685
  Naan.module.build(module.id, "terminals", function(modobj, compobj) {
1048
- require("./browser.nlg")
686
+ require("./worker.nlg")
1049
687
  compobj.manifest = manifest
1050
688
  modobj.exports.TermLocal = TermLocal
1051
689
  modobj.exports.TermHost = TermHost
1052
- modobj.exports.WorkerConnect = WorkerConnect
1053
- modobj.exports.WorkerToMain = WorkerToMain
1054
690
  })
1055
691
  } ();