@lvce-editor/shared-process 0.15.16 → 0.15.18

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 (80) hide show
  1. package/config/builtinCommands.json +8 -0
  2. package/config/defaultSettings.json +2 -0
  3. package/extensions/builtin.language-basics-python/extension.json +1 -1
  4. package/extensions/builtin.language-basics-xml/extension.json +2 -1
  5. package/extensions/builtin.language-basics-xml/src/tokenizeXml.js +1 -1
  6. package/extensions/builtin.theme-atom-one-dark/color-theme.json +4 -0
  7. package/package.json +4 -4
  8. package/src/parts/AttachDebugger/AttachDebugger.ipc.js +7 -0
  9. package/src/parts/AttachDebugger/AttachDebugger.js +6 -0
  10. package/src/parts/Callback/Callback.js +17 -26
  11. package/src/parts/ChildProcessError/ChildProcessError.js +4 -1
  12. package/src/parts/CleanStack/CleanStack.js +4 -0
  13. package/src/parts/CommandNotFoundError/CommandNotFoundError.js +2 -1
  14. package/src/parts/CreateCpuProfile/CreateCpuProfile.js +59 -0
  15. package/src/parts/DestroySocket/DestroySocket.js +5 -0
  16. package/src/parts/DevtoolsProtocolRpc/DevtoolsProtocolRpc.js +32 -0
  17. package/src/parts/ElectronInitialize/ElectronInitialize.ipc.js +8 -0
  18. package/src/parts/ElectronInitialize/ElectronInitialize.js +11 -0
  19. package/src/parts/ErrorCodes/ErrorCodes.js +2 -0
  20. package/src/parts/ErrorHandling/ErrorHandling.js +12 -14
  21. package/src/parts/FirstWebSocketEventType/FirstWebSocketEventType.js +2 -0
  22. package/src/parts/GetActualPath/GetActualPath.js +8 -0
  23. package/src/parts/GetErrorResponse/GetErrorResponse.js +6 -3
  24. package/src/parts/GetFirstWebSocketEvent/GetFirstWebSocketEvent.js +41 -0
  25. package/src/parts/GetHelpfulChildProcessError/GetHelpfulChildProcessError.js +13 -3
  26. package/src/parts/GetResponse/GetResponse.js +3 -3
  27. package/src/parts/HandleElectronMessagePort/HandleElectronMessagePort.ipc.js +7 -0
  28. package/src/parts/HandleElectronMessagePort/HandleElectronMessagePort.js +14 -0
  29. package/src/parts/HandleIpc/HandleIpc.js +30 -0
  30. package/src/parts/HandleNodeMessagePort/HandleNodeMessagePort.ipc.js +7 -0
  31. package/src/parts/HandleNodeMessagePort/HandleNodeMessagePort.js +12 -0
  32. package/src/parts/HandleWebSocket/HandleWebSocket.ipc.js +7 -0
  33. package/src/parts/HandleWebSocket/HandleWebSocket.js +26 -0
  34. package/src/parts/HandleWebSocketForExtensionHost/HandleWebSocketForExtensionHost.js +15 -0
  35. package/src/parts/HandleWebSocketForExtensionHostHelperProcess/HandleWebSocketForExtensionHostHelperProcess.js +6 -0
  36. package/src/parts/HandleWebSocketForSharedProcess/HandleWebSocketForSharedProcess.js +23 -0
  37. package/src/parts/HandleWebSocketForTerminalProcess/HandleWebSocketForTerminalProcess.js +22 -0
  38. package/src/parts/HandleWebSocketForUnknown/HandleWebSocketForUnknown.js +10 -0
  39. package/src/parts/HandleWebSocketModule/HandleWebSocketModule.js +20 -0
  40. package/src/parts/Id/Id.js +7 -0
  41. package/src/parts/IpcChild/IpcChild.js +5 -2
  42. package/src/parts/IpcChildModule/IpcChildModule.js +8 -0
  43. package/src/parts/IpcChildType/IpcChildType.js +7 -0
  44. package/src/parts/IpcChildWithElectronMessagePort/IpcChildWithElectronMessagePort.js +41 -0
  45. package/src/parts/IpcChildWithElectronUtilityProcess/IpcChildWithElectronUtilityProcess.js +51 -0
  46. package/src/parts/IpcChildWithNodeForkedProcess/IpcChildWithNodeForkedProcess.js +25 -1
  47. package/src/parts/IpcChildWithNodeMessagePort/IpcChildWithNodeMessagePort.js +41 -0
  48. package/src/parts/IpcChildWithNodeWorker/IpcChildWithNodeWorker.js +17 -1
  49. package/src/parts/IpcChildWithWebSocket/IpcChildWithWebSocket.js +51 -0
  50. package/src/parts/IpcError/IpcError.js +6 -0
  51. package/src/parts/IpcParentWithNodeForkedProcess/IpcParentWithNodeForkedProcess.js +7 -3
  52. package/src/parts/IsElectron/IsElectron.js +3 -0
  53. package/src/parts/IsMessagePort/IsMessagePort.js +3 -0
  54. package/src/parts/IsMessagePortMain/IsMessagePortMain.js +3 -0
  55. package/src/parts/IsSocket/IsSocket.js +5 -0
  56. package/src/parts/IsWebSocketOpen/IsWebSocketOpen.js +5 -0
  57. package/src/parts/JsonRpc/JsonRpc.js +19 -7
  58. package/src/parts/Module/Module.js +14 -0
  59. package/src/parts/ModuleId/ModuleId.js +7 -0
  60. package/src/parts/ModuleMap/ModuleMap.js +18 -0
  61. package/src/parts/ParentIpc/ParentIpc.js +10 -188
  62. package/src/parts/PrettyError/PrettyError.js +4 -19
  63. package/src/parts/Process/Process.ipc.js +1 -0
  64. package/src/parts/Process/Process.js +8 -0
  65. package/src/parts/ProcessDisplayName/ProcessDisplayName.js +1 -0
  66. package/src/parts/ProtocolType/ProtocolType.js +2 -1
  67. package/src/parts/PtyHost/PtyHost.js +81 -0
  68. package/src/parts/RebuildNodePty/RebuildNodePty.ipc.js +7 -0
  69. package/src/parts/RebuildNodePty/RebuildNodePty.js +48 -0
  70. package/src/parts/Signal/Signal.js +2 -0
  71. package/src/parts/Terminal/Terminal.js +52 -130
  72. package/src/parts/TerminalState/TerminalState.js +15 -0
  73. package/src/parts/WebSocketServer/WebSocketServer.js +11 -25
  74. package/src/sharedProcessMain.cjs +1 -0
  75. package/src/sharedProcessMain.js +3 -2
  76. package/extensions/builtin.language-basics-json5/README.md +0 -16
  77. package/extensions/builtin.language-basics-json5/extension.json +0 -12
  78. package/extensions/builtin.language-basics-json5/src/tokenizeJson5.js +0 -321
  79. package/src/parts/Socket/Socket.js +0 -77
  80. /package/src/parts/{GetWindowsNsisDOwnloadUrl → GetWindowsNsisDownloadUrl}/GetWindowsNsisDownloadUrl.js +0 -0
@@ -1,15 +1,7 @@
1
- import { MessagePort, parentPort } from 'node:worker_threads'
2
- import * as Callback from '../Callback/Callback.js'
3
- import * as Command from '../Command/Command.js'
4
- import * as Debug from '../Debug/Debug.js'
5
- import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
6
- import * as ExtensionHostHelperProcessIpc from '../ExtensionHostHelperProcessIpc/ExtensionHostHelperProcessIpc.js'
7
- import * as ExtensionHostIpc from '../ExtensionHostIpc/ExtensionHostIpc.js'
8
- import * as ExtensionHostRpc from '../ExtensionHostRpc/ExtensionHostRpc.js'
9
- import * as GetResponse from '../GetResponse/GetResponse.js'
10
- import * as Logger from '../Logger/Logger.js'
11
- import * as ProtocolType from '../ProtocolType/ProtocolType.js'
12
- import { VError } from '../VError/VError.js'
1
+ import * as HandleIpc from '../HandleIpc/HandleIpc.js'
2
+ import * as IpcChild from '../IpcChild/IpcChild.js'
3
+ import * as IpcChildType from '../IpcChildType/IpcChildType.js'
4
+
13
5
  // TODO add tests for this
14
6
 
15
7
  // TODO handle structure: one shared process multiple extension hosts
@@ -20,181 +12,11 @@ export const state = {
20
12
  electronPortMap: new Map(),
21
13
  }
22
14
 
23
- // TODO apparent there are multiple ways for listening to ipc
24
- // 1. parentPort
25
- // 2. process.send
26
- // instead of if/else use interface and abstraction: ParentPortIpc.listen(), ProcessIpc.listen(), ParentPortIpc.send(message), ProcessIpc.send(message)
27
-
28
- export const electronSend = (message) => {
29
- if (parentPort) {
30
- // @ts-ignore
31
- // process.send(message)
32
- parentPort.postMessage(message)
33
- } else if (process.send) {
34
- process.send(message)
35
- }
36
- }
37
-
38
- const handleWebSocketSharedProcess = (message, handle) => {
39
- // TODO when it is an extension host websocket, spawn extension host
40
- handle.on('error', (error) => {
41
- if (error && error.code === ErrorCodes.ECONNRESET) {
42
- return
43
- }
44
- console.info('[info shared process: handle error]', error)
45
- })
46
- Command.execute(/* WebSocketServer.handleUpgrade */ 'WebSocketServer.handleUpgrade', /* message */ message, /* handle */ handle)
47
- }
48
-
49
- // TODO lazyload modules for spawning extension host, they are only needed later
50
- const handleWebSocketExtensionHost = async (message, handle) => {
51
- // console.log('[shared-process] received extension host websocket', message)
52
- const ipc = await ExtensionHostIpc.create()
53
- console.info('[sharedprocess] creating extension ipc')
54
- const rpc = await ExtensionHostRpc.create(ipc, handle)
55
- console.info('[sharedprocess] created extension host rpc')
56
- ipc._process.send(message, handle)
57
- // rpc.send(message)
58
- // console.log('spawned extension host')
59
- // console.log(rpc)
60
- }
61
-
62
- const handleWebSocketExtensionHostHelperProcess = async (message, handle) => {
63
- const ipc = await ExtensionHostHelperProcessIpc.create()
64
- ipc._process.send(message, handle)
65
- }
66
-
67
- const handleWebSocketUnknown = (message, handle, protocol) => {
68
- Logger.warn(`[shared-process] unsupported sec-websocket-procotol ${protocol}`)
69
- try {
70
- handle.destroy()
71
- } catch {
72
- // ignore
73
- }
74
- }
75
-
76
- const handleWebSocket = (message, handle) => {
77
- const headers = message.headers
78
- if (!headers) {
79
- throw new VError('missing websocket headers')
80
- }
81
- const protocol = headers['sec-websocket-protocol']
82
- if (!protocol) {
83
- throw new VError('missing sec websocket protocol header')
84
- }
85
- switch (protocol) {
86
- case ProtocolType.SharedProcess:
87
- return handleWebSocketSharedProcess(message, handle)
88
- case ProtocolType.ExtensionHost:
89
- return handleWebSocketExtensionHost(message, handle)
90
- case ProtocolType.ExtensionHostHelperProcess:
91
- return handleWebSocketExtensionHostHelperProcess(message, handle)
92
- default:
93
- return handleWebSocketUnknown(message, handle, protocol)
94
- }
95
- }
96
-
97
- const handleJsonRpcResult = (message) => {
98
- Callback.resolve(message.id, message.result)
99
- }
100
-
101
- const handleJsonRpcMessage = async (message, handle) => {
102
- if (message.id) {
103
- const response = await GetResponse.getResponse(message, handle)
104
- electronSend(response)
105
- } else {
106
- // TODO handle error
107
- Command.execute(message.method, null, ...message.params)
108
- }
109
- }
110
-
111
- const handleMessageFromParentProcess = async (message, handle) => {
112
- if (handle) {
113
- return handleWebSocket(message, handle)
114
- }
115
- if (message.result) {
116
- return handleJsonRpcResult(message)
117
- }
118
- if (message.method) {
119
- return handleJsonRpcMessage(message, handle)
120
- }
121
- console.warn('unknown message', message)
122
- }
123
-
124
- /**
125
- *
126
- * @param {{initialize:{port: MessagePort, folder:string}}} initializeMessage
127
- */
128
- const electronInitialize = (initializeMessage) => {
129
- const port = initializeMessage.initialize.port
130
- // TODO handle error
131
- const fakeSocket = {
132
- send: port.postMessage.bind(port),
133
- on(event, listener) {
134
- switch (event) {
135
- case 'close':
136
- port.on('close', listener)
137
- break
138
- case 'message':
139
- port.on('message', listener)
140
- break
141
- default:
142
- console.warn('socket event not implemented', event, listener)
143
- break
144
- }
145
- },
146
- }
147
- const handleOtherMessagesFromMessagePort = async (message) => {
148
- // console.log('got port message', message)
149
- if (message.result) {
150
- Callback.resolve(message.id, message.result)
151
- } else if (message.method) {
152
- if (message.id) {
153
- const response = await GetResponse.getResponse(message, fakeSocket)
154
- port.postMessage(response)
155
- } else {
156
- console.warn(`[shared process] sending messages without id is deprecated: ${message.method}`)
157
- Command.execute(message.method, fakeSocket, ...message.params)
158
- }
159
- } else {
160
- console.warn('unknown message', message)
161
- }
162
- }
163
- port.on('message', handleOtherMessagesFromMessagePort)
164
- }
165
-
166
- const handleMessageFromParentProcessElectron = async (message) => {
167
- if (message.initialize) {
168
- electronInitialize(message)
169
- return
170
- }
171
- if ('result' in message) {
172
- Callback.resolve(message.id, message.result)
173
- return
174
- }
175
-
176
- console.log('unknown message from electron', message)
177
- console.log({ message })
178
- }
179
-
180
15
  // TODO maybe rename to hydrate
181
- export const listen = () => {
182
- // TODO tree-shake out if-else
183
- // console.log({ ...process.env })
184
- if (process.env.ELECTRON_RUN_AS_NODE && parentPort) {
185
- // electron process listens to main process ipc
186
- Debug.debug('is electron')
187
- // @ts-ignore
188
- parentPort.on('message', handleMessageFromParentProcessElectron)
189
- electronSend('ready')
190
- } else {
191
- Debug.debug('is not electron')
192
- // otherwise listen to web process ipc
193
- // and when a socket is transferred,
194
- // listen to socket messages also
195
- process.on('message', handleMessageFromParentProcess)
196
- if (process.send) {
197
- process.send('ready')
198
- }
199
- }
16
+ export const listen = async () => {
17
+ const method = IpcChildType.Auto()
18
+ const ipc = await IpcChild.listen({
19
+ method,
20
+ })
21
+ HandleIpc.handleIpc(ipc)
200
22
  }
@@ -1,22 +1,14 @@
1
1
  import { codeFrameColumns } from '@babel/code-frame'
2
2
  import { LinesAndColumns } from 'lines-and-columns'
3
3
  import { readFileSync } from 'node:fs'
4
- import { fileURLToPath } from 'node:url'
5
- import { AssertionError } from '../AssertionError/AssertionError.js'
6
4
  import * as CleanStack from '../CleanStack/CleanStack.js'
7
5
  import * as EncodingType from '../EncodingType/EncodingType.js'
8
6
  import * as ErrorCodes from '../ErrorCodes/ErrorCodes.js'
7
+ import * as GetActualPath from '../GetActualPath/GetActualPath.js'
9
8
  import * as JoinLines from '../JoinLines/JoinLines.js'
10
9
  import * as Json from '../Json/Json.js'
11
10
  import * as SplitLines from '../SplitLines/SplitLines.js'
12
11
 
13
- const getActualPath = (fileUri) => {
14
- if (fileUri.startsWith('file://')) {
15
- return fileURLToPath(fileUri)
16
- }
17
- return fileUri
18
- }
19
-
20
12
  const RE_MODULE_NOT_FOUND_STACK = /Cannot find package '([^']+)' imported from (.+)$/
21
13
 
22
14
  const prepareModuleNotFoundError = (error) => {
@@ -61,13 +53,6 @@ const prepareModuleNotFoundError = (error) => {
61
53
  }
62
54
  }
63
55
 
64
- const getStackLinesToCut = (error) => {
65
- if (error instanceof AssertionError) {
66
- return 1
67
- }
68
- return 0
69
- }
70
-
71
56
  export const prepare = (error) => {
72
57
  if (error && error.code === ErrorCodes.ERR_MODULE_NOT_FOUND) {
73
58
  return prepareModuleNotFoundError(error)
@@ -79,8 +64,7 @@ export const prepare = (error) => {
79
64
  error = cause
80
65
  }
81
66
  }
82
- const linesToCut = getStackLinesToCut(error)
83
- const lines = CleanStack.cleanStack(error.stack).slice(linesToCut)
67
+ const lines = CleanStack.cleanStack(error.stack)
84
68
  const file = lines[0]
85
69
  let codeFrame = ''
86
70
  if (error.codeFrame) {
@@ -92,7 +76,7 @@ export const prepare = (error) => {
92
76
  }
93
77
  if (match) {
94
78
  const [_, path, line, column] = match
95
- const actualPath = getActualPath(path)
79
+ const actualPath = GetActualPath.getActualPath(path)
96
80
  const rawLines = readFileSync(actualPath, EncodingType.Utf8)
97
81
  const location = {
98
82
  start: {
@@ -109,6 +93,7 @@ export const prepare = (error) => {
109
93
  stack: relevantStack,
110
94
  codeFrame,
111
95
  type: error.constructor.name,
96
+ code: error.code,
112
97
  }
113
98
  }
114
99
 
@@ -5,4 +5,5 @@ export const name = 'Process'
5
5
  export const Commands = {
6
6
  crash: Process.crash,
7
7
  crashAsync: Process.crashAsync,
8
+ getPid: Process.getPid,
8
9
  }
@@ -28,4 +28,12 @@ export const isConnected = () => {
28
28
  return process.connected
29
29
  }
30
30
 
31
+ export const kill = (pid, signal) => {
32
+ process.kill(pid, signal)
33
+ }
34
+
35
+ export const getPid = () => {
36
+ return process.pid
37
+ }
38
+
31
39
  export const { platform } = process
@@ -0,0 +1 @@
1
+ export const processDisplayName = 'shared process'
@@ -1,3 +1,4 @@
1
- export const SharedProcess = 'lvce.shared-process'
2
1
  export const ExtensionHost = 'lvce.extension-host'
3
2
  export const ExtensionHostHelperProcess = 'lvce.extension-host-helper-process'
3
+ export const SharedProcess = 'lvce.shared-process'
4
+ export const TerminalProcess = 'lvce.terminal-process'
@@ -0,0 +1,81 @@
1
+ import exitHook from 'exit-hook'
2
+ import * as Debug from '../Debug/Debug.js'
3
+ import * as IpcParent from '../IpcParent/IpcParent.js'
4
+ import * as IpcParentType from '../IpcParentType/IpcParentType.js'
5
+ import * as PtyHostPath from '../PtyHostPath/PtyHostPath.js'
6
+
7
+ export const state = {
8
+ /**
9
+ * @type {any}
10
+ */
11
+ ptyHost: undefined,
12
+ /**
13
+ * @type {any}
14
+ */
15
+ ptyHostPromise: undefined,
16
+ /**
17
+ * @type {any[]}
18
+ */
19
+ pendingMessages: [],
20
+ send(message) {
21
+ state.pendingMessages.push(message)
22
+ },
23
+ }
24
+
25
+ const cleanUpAll = () => {
26
+ if (state.ptyHost) {
27
+ state.ptyHost.dispose()
28
+ state.ptyHost = undefined
29
+ }
30
+ }
31
+
32
+ const handleProcessExit = () => {
33
+ Debug.debug('shared process exit, clean terminals')
34
+ cleanUpAll()
35
+ }
36
+
37
+ const createPtyHost = async () => {
38
+ exitHook(handleProcessExit)
39
+ const ptyHostPath = await PtyHostPath.getPtyHostPath()
40
+ const ptyHost = await IpcParent.create({
41
+ method: IpcParentType.NodeForkedProcess,
42
+ path: ptyHostPath,
43
+ argv: [],
44
+ stdio: 'inherit',
45
+ name: 'Terminal Process',
46
+ })
47
+ const handleClose = () => {
48
+ ptyHost.off('close', handleClose)
49
+ state.ptyHost = undefined
50
+ state.ptyHostPromise = undefined
51
+ }
52
+ ptyHost.on('close', handleClose)
53
+ state.ptyHost = ptyHost
54
+ return ptyHost
55
+ }
56
+
57
+ export const getOrCreate = () => {
58
+ Debug.debug('creating pty host')
59
+ if (!state.ptyHostPromise) {
60
+ state.ptyHostPromise = createPtyHost()
61
+ }
62
+ return state.ptyHostPromise
63
+ }
64
+
65
+ export const getCurrentInstance = () => {
66
+ return state.ptyHost
67
+ }
68
+
69
+ export const disposeAll = () => {
70
+ if (state.ptyHost) {
71
+ state.ptyHost.removeAllListeners()
72
+ if (state.ptyHost) {
73
+ state.ptyHost.dispose()
74
+ state.ptyHost = undefined
75
+ }
76
+ state.ptyHostState = /* None */ 0
77
+ state.send = (message) => {
78
+ state.pendingMessages.push(message)
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,7 @@
1
+ import * as RebuildNodePty from './RebuildNodePty.js'
2
+
3
+ export const name = 'RebuildNodePty'
4
+
5
+ export const Commands = {
6
+ rebuildNodePty: RebuildNodePty.rebuildNodePty,
7
+ }
@@ -0,0 +1,48 @@
1
+ import { spawn } from 'node:child_process'
2
+ import * as IsElectron from '../IsElectron/IsElectron.js'
3
+ import * as Path from '../Path/Path.js'
4
+ import * as Root from '../Root/Root.js'
5
+ import { VError } from '../VError/VError.js'
6
+
7
+ const getElectronRebuildPath = () => {
8
+ return Path.join(Root.root, 'packages', 'main-process', 'node_modules', '.bin', 'electron-rebuild')
9
+ }
10
+
11
+ const getPtyHostPath = () => {
12
+ return Path.join(Root.root, 'packages', 'pty-host')
13
+ }
14
+
15
+ /**
16
+ * @param {string} cwd
17
+ */
18
+ const rebuildNodePtyElectron = async (cwd) => {
19
+ const electronRebuildPath = getElectronRebuildPath()
20
+ const childProcess = spawn(electronRebuildPath, [], {
21
+ cwd,
22
+ stdio: 'inherit',
23
+ })
24
+ // TODO wait for child process to be finished
25
+ }
26
+
27
+ /**
28
+ * @param {string} cwd
29
+ */
30
+ const rebuildNodePtyNode = async (cwd) => {
31
+ const childProcess = spawn('npm', ['rebuild'], {
32
+ cwd,
33
+ stdio: 'inherit',
34
+ })
35
+ }
36
+
37
+ export const rebuildNodePty = async () => {
38
+ try {
39
+ const ptyHostPath = getPtyHostPath()
40
+ if (IsElectron.isElectron()) {
41
+ await rebuildNodePtyElectron(ptyHostPath)
42
+ } else {
43
+ await rebuildNodePtyNode(ptyHostPath)
44
+ }
45
+ } catch (error) {
46
+ throw new VError(error, `Failed to rebuild node-pty`)
47
+ }
48
+ }
@@ -0,0 +1,2 @@
1
+ export const SIGUSR1 = 'SIGUSR1'
2
+ export const SIGTERM = 'SIGTERM'
@@ -1,138 +1,63 @@
1
- import exitHook from 'exit-hook'
2
1
  import * as Assert from '../Assert/Assert.js'
3
- import * as Debug from '../Debug/Debug.js'
4
- import * as IpcParent from '../IpcParent/IpcParent.js'
5
- import * as IpcParentType from '../IpcParentType/IpcParentType.js'
6
2
  import * as JsonRpcVersion from '../JsonRpcVersion/JsonRpcVersion.js'
7
- import * as PtyHostPath from '../PtyHostPath/PtyHostPath.js'
3
+ import * as PtyHost from '../PtyHost/PtyHost.js'
4
+ import * as TerminalState from '../TerminalState/TerminalState.js'
5
+ import { VError } from '../VError/VError.js'
8
6
 
9
- export const state = {
10
- /**
11
- * @type {any}
12
- */
13
- ptyHost: undefined,
14
- ptyHostState: /* None */ 0,
15
- /**
16
- * @type {any[]}
17
- */
18
- pendingMessages: [],
19
- send(message) {
20
- state.pendingMessages.push(message)
21
- },
22
- }
23
-
24
- // const getSpawnOptions = () => {
25
- // if (Platform.isWindows) {
26
- // return {
27
- // command: 'powershell.exe',
28
- // args: [],
29
- // }
30
- // }
31
- // return {
32
- // command: 'bash',
33
- // args: ['-i'],
34
- // }
35
- // }
36
-
37
- const cleanUpAll = () => {
38
- if (state.ptyHost) {
39
- state.ptyHost.kill()
7
+ const createTerminal = (ptyHost, socket) => {
8
+ const handleMessage = (message) => {
9
+ socket.send(message)
10
+ }
11
+ const handleClose = () => {
12
+ // socket.off('close', handleClose)
13
+ ptyHost.off('message', handleMessage)
14
+ ptyHost.dispose()
15
+ }
16
+ const dispose = () => {
17
+ ptyHost.off('message', handleMessage)
18
+ socket.off('close', handleClose)
19
+ }
20
+ ptyHost.on('message', handleMessage)
21
+ socket.on('close', handleClose)
22
+ return {
23
+ ptyHost,
24
+ socket,
25
+ handleMessage,
26
+ handleClose,
27
+ dispose,
40
28
  }
41
29
  }
42
30
 
43
- const handleProcessExit = () => {
44
- Debug.debug('shared process exit, clean terminals')
45
- cleanUpAll()
46
- }
47
-
48
- const createPtyHost = async () => {
49
- exitHook(handleProcessExit)
50
- const ptyHostPath = await PtyHostPath.getPtyHostPath()
51
- const ptyHost = await IpcParent.create({
52
- method: IpcParentType.NodeForkedProcess,
53
- path: ptyHostPath,
54
- argv: ['--ipc-type=node-forked-process'],
55
- stdio: 'inherit',
56
- name: 'Terminal Process',
57
- })
58
- return ptyHost
59
- }
60
-
61
- // const send = (method, ...params) => {
62
-
63
- // state.ptyHost.send({
64
- // jsonrpc: '2.0',
65
- // method,
66
- // params,
67
- // })
68
- // }
69
-
70
31
  export const create = async (socket, id, cwd) => {
71
- Assert.object(socket)
72
- Assert.number(id)
73
- Assert.string(cwd)
74
-
75
- Debug.debug('creating pty host')
76
- switch (state.ptyHostState) {
77
- case /* None */ 0: {
78
- state.ptyHostState = /* Creating */ 1
79
- const ptyHost = await createPtyHost()
80
-
81
- Debug.debug('pty host ready')
82
- state.ptyHostState = /* Ready */ 2
83
- const handleMessage = (message) => {
84
- const data = message.params[1]
85
- socket.send({
86
- jsonrpc: JsonRpcVersion.Two,
87
- method: 'Viewlet.send',
88
- params: ['Terminal', 'handleData', data],
89
- })
90
- }
91
- ptyHost.on('message', handleMessage)
92
- state.send = (message) => {
93
- ptyHost.send(message)
94
- }
95
- while (state.pendingMessages.length > 0) {
96
- state.send(state.pendingMessages.shift())
97
- }
98
- state.ptyHost = ptyHost
99
- break
100
- }
101
- case /* Creating */ 1: {
102
- break
103
- }
104
- case /* Ready */ 2: {
105
- break
106
- }
107
- default:
108
- break
32
+ try {
33
+ Assert.object(socket)
34
+ Assert.number(id)
35
+ Assert.string(cwd)
36
+ // TODO race condition because of await
37
+ const ptyHost = await PtyHost.getOrCreate()
38
+ const terminal = createTerminal(ptyHost, socket)
39
+ TerminalState.add(id, terminal)
40
+ // TODO use invoke
41
+ ptyHost.send({
42
+ jsonrpc: JsonRpcVersion.Two,
43
+ method: 'Terminal.create',
44
+ params: [id, cwd],
45
+ })
46
+ } catch (error) {
47
+ throw new VError(error, `Failed to create terminal`)
109
48
  }
110
- state.send({
111
- jsonrpc: JsonRpcVersion.Two,
112
- method: 'Terminal.create',
113
- params: [id, cwd],
114
- })
115
- const handleClose = () => {
116
- if (state.ptyHost) {
117
- state.ptyHost.dispose()
118
- state.ptyHost = undefined
119
- state.ptyHostState = /* None */ 0
120
- state.send = (message) => {
121
- state.pendingMessages.push(message)
122
- }
123
- }
124
- }
125
- socket.on('close', handleClose)
126
49
  }
127
50
 
128
51
  export const write = (id, data) => {
129
52
  Assert.number(id)
130
53
  Assert.string(data)
131
- if (!state.ptyHost) {
54
+ const ptyHost = PtyHost.getCurrentInstance()
55
+ if (!ptyHost) {
132
56
  console.log('[shared-process] pty host not ready')
133
57
  return
134
58
  }
135
- state.send({
59
+ // TODO should use invoke
60
+ ptyHost.send({
136
61
  jsonrpc: JsonRpcVersion.Two,
137
62
  method: 'Terminal.write',
138
63
  params: [id, data],
@@ -143,8 +68,13 @@ export const resize = (state, columns, rows) => {
143
68
  // state.pty.resize(columns, rows)
144
69
  }
145
70
 
146
- export const dispose = (id) => {
147
- state.send({
71
+ export const dispose = async (id) => {
72
+ const terminal = TerminalState.get(id)
73
+ terminal.dispose()
74
+ TerminalState.remove(id)
75
+ const ptyHost = await PtyHost.getOrCreate()
76
+ // TODO use invoke
77
+ ptyHost.send({
148
78
  jsonrpc: JsonRpcVersion.Two,
149
79
  method: 'Terminal.dispose',
150
80
  params: [id],
@@ -152,13 +82,5 @@ export const dispose = (id) => {
152
82
  }
153
83
 
154
84
  export const disposeAll = () => {
155
- if (state.ptyHost) {
156
- state.ptyHost.removeAllListeners()
157
- state.ptyHost.kill()
158
- state.ptyHost = undefined
159
- state.ptyHostState = /* None */ 0
160
- state.send = (message) => {
161
- state.pendingMessages.push(message)
162
- }
163
- }
85
+ PtyHost.disposeAll()
164
86
  }
@@ -0,0 +1,15 @@
1
+ export const state = {
2
+ terminals: Object.create(null),
3
+ }
4
+
5
+ export const add = (id, terminal) => {
6
+ state.terminals[id] = terminal
7
+ }
8
+
9
+ export const get = (id) => {
10
+ return state.terminals[id]
11
+ }
12
+
13
+ export const remove = (id) => {
14
+ delete state.terminals[id]
15
+ }