@lvce-editor/shared-process 0.89.1 → 0.89.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/shared-process",
3
- "version": "0.89.1",
3
+ "version": "0.89.3",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -18,28 +18,28 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@lvce-editor/assert": "1.7.0",
21
- "@lvce-editor/auth-process": "1.8.0",
22
- "@lvce-editor/extension-host-helper-process": "0.89.1",
21
+ "@lvce-editor/auth-process": "1.9.0",
22
+ "@lvce-editor/extension-host-helper-process": "0.89.3",
23
23
  "@lvce-editor/ipc": "16.3.0",
24
24
  "@lvce-editor/json-rpc": "8.2.0",
25
25
  "@lvce-editor/jsonc-parser": "1.5.0",
26
26
  "@lvce-editor/pretty-error": "2.0.0",
27
27
  "@lvce-editor/process-explorer": "3.10.0",
28
- "@lvce-editor/rpc-registry": "9.33.0",
28
+ "@lvce-editor/rpc-registry": "9.35.0",
29
29
  "@lvce-editor/verror": "1.7.0",
30
30
  "is-object": "^1.0.2",
31
31
  "xdg-basedir": "^5.1.0"
32
32
  },
33
33
  "optionalDependencies": {
34
34
  "@lvce-editor/embeds-process": "4.9.1",
35
- "@lvce-editor/file-system-process": "5.3.0",
36
- "@lvce-editor/file-watcher-process": "4.2.0",
35
+ "@lvce-editor/file-system-process": "5.4.0",
36
+ "@lvce-editor/file-watcher-process": "4.4.0",
37
37
  "@lvce-editor/network-process": "5.2.0",
38
38
  "@lvce-editor/preload": "1.5.0",
39
39
  "@lvce-editor/preview-process": "11.0.0",
40
- "@lvce-editor/pty-host": "8.2.0",
40
+ "@lvce-editor/pty-host": "8.3.0",
41
41
  "@lvce-editor/search-process": "15.1.0",
42
- "@lvce-editor/typescript-compile-process": "5.2.0",
42
+ "@lvce-editor/typescript-compile-process": "5.3.0",
43
43
  "open": "^11.0.0",
44
44
  "tail": "^2.2.6",
45
45
  "tmp-promise": "^3.0.3",
@@ -3,6 +3,6 @@ import { fileURLToPath } from 'url'
3
3
 
4
4
  export const getBuiltinExtensionsPath = () => {
5
5
  const staticServerPath = fileURLToPath(import.meta.resolve('@lvce-editor/static-server'))
6
- const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', 'd7a85a3', 'extensions')
6
+ const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', 'f1db6cb', 'extensions')
7
7
  return builtinExtensionsPath
8
8
  }
@@ -3,6 +3,9 @@ import * as ElectronProcess from './ElectronProcess.js'
3
3
  export const name = 'ElectronProcess'
4
4
 
5
5
  export const Commands = {
6
+ getArgv: ElectronProcess.getArgv,
6
7
  getChromeVersion: ElectronProcess.getChromeVersion,
7
8
  getElectronVersion: ElectronProcess.getElectronVersion,
9
+ writeStderr: ElectronProcess.writeStderr,
10
+ writeStdout: ElectronProcess.writeStdout,
8
11
  }
@@ -1,5 +1,9 @@
1
1
  import * as ParentIpc from '../MainProcess/MainProcess.js'
2
2
 
3
+ export const getArgv = () => {
4
+ return ParentIpc.invoke('Process.getArgv')
5
+ }
6
+
3
7
  export const getElectronVersion = () => {
4
8
  return ParentIpc.invoke('Process.getElectronVersion')
5
9
  }
@@ -7,3 +11,11 @@ export const getElectronVersion = () => {
7
11
  export const getChromeVersion = () => {
8
12
  return ParentIpc.invoke('Process.getChromeVersion')
9
13
  }
14
+
15
+ export const writeStderr = (value) => {
16
+ return ParentIpc.invoke('Process.writeStderr', value)
17
+ }
18
+
19
+ export const writeStdout = (value) => {
20
+ return ParentIpc.invoke('Process.writeStdout', value)
21
+ }
@@ -1,5 +1,8 @@
1
1
  import * as ParentIpc from '../MainProcess/MainProcess.js'
2
2
 
3
- export const exit = () => {
4
- return ParentIpc.invoke('Exit.exit')
3
+ export const exit = (code) => {
4
+ if (code === undefined) {
5
+ return ParentIpc.invoke('Exit.exit')
6
+ }
7
+ return ParentIpc.invoke('Exit.exit', code)
5
8
  }
@@ -0,0 +1,6 @@
1
+ const promptFlag = '--prompt'
2
+ const promptWithValuePrefix = `${promptFlag}=`
3
+
4
+ export const isPromptMode = (argv) => {
5
+ return argv.some((argument) => argument === promptFlag || argument.startsWith(promptWithValuePrefix))
6
+ }
@@ -0,0 +1,7 @@
1
+ import * as LanguageServer from './LanguageServer.js'
2
+
3
+ export const name = 'LanguageServer'
4
+
5
+ export const Commands = {
6
+ complete: LanguageServer.complete,
7
+ }
@@ -0,0 +1,78 @@
1
+ import { dirname } from 'node:path'
2
+ import { pathToFileURL } from 'node:url'
3
+ import { LanguageServerConnection } from '../LanguageServerConnection/LanguageServerConnection.js'
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+ const connections = new Map ()
26
+
27
+ const normalizeDocumentUri = (uri) => {
28
+ if (uri.startsWith('/')) {
29
+ return pathToFileURL(uri).href
30
+ }
31
+ return uri
32
+ }
33
+
34
+ const getRootUri = (documentUri) => {
35
+ if (documentUri.startsWith('file:')) {
36
+ return new URL('.', documentUri).href
37
+ }
38
+ if (documentUri.startsWith('/')) {
39
+ return pathToFileURL(dirname(documentUri)).href
40
+ }
41
+ return new URL('.', documentUri).href
42
+ }
43
+
44
+ const hasSameOptions = (state, uri, argv) => {
45
+ return state.uri === uri && state.argv.length === argv.length && state.argv.every((argument, index) => argument === argv[index])
46
+ }
47
+
48
+ const getConnection = (id, uri, argv, rootUri) => {
49
+ const existing = connections.get(id)
50
+ if (existing && existing.connection.isRunning() && hasSameOptions(existing, uri, argv)) {
51
+ return existing.connection
52
+ }
53
+ existing?.connection.dispose()
54
+ const connection = new LanguageServerConnection({ argv, rootUri, uri })
55
+ connections.set(id, {
56
+ argv: [...argv],
57
+ connection,
58
+ uri,
59
+ })
60
+ return connection
61
+ }
62
+
63
+ export const complete = async ({ argv, id, offset, textDocument, uri } ) => {
64
+ const normalizedDocument = {
65
+ ...textDocument,
66
+ uri: normalizeDocumentUri(textDocument.uri),
67
+ }
68
+ const rootUri = getRootUri(normalizedDocument.uri)
69
+ const connection = getConnection(`${id}:${rootUri}`, uri, argv, rootUri)
70
+ return connection.complete(normalizedDocument, offset)
71
+ }
72
+
73
+ export const disposeAll = () => {
74
+ for (const state of connections.values()) {
75
+ state.connection.dispose()
76
+ }
77
+ connections.clear()
78
+ }
@@ -0,0 +1,303 @@
1
+ import { spawn, } from 'node:child_process'
2
+ import { basename } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { LanguageServerMessageParser } from '../LanguageServerMessageParser/LanguageServerMessageParser.js'
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+ const getPosition = (text, offset) => {
41
+ const safeOffset = Math.max(0, Math.min(offset, text.length))
42
+ const before = text.slice(0, safeOffset)
43
+ const lastLineBreak = before.lastIndexOf('\n')
44
+ return {
45
+ character: lastLineBreak === -1 ? safeOffset : safeOffset - lastLineBreak - 1,
46
+ line: before.split('\n').length - 1,
47
+ }
48
+ }
49
+
50
+ const getWorkspaceName = (rootUri) => {
51
+ try {
52
+ return basename(fileURLToPath(rootUri)) || 'workspace'
53
+ } catch {
54
+ return 'workspace'
55
+ }
56
+ }
57
+
58
+ export class LanguageServerConnection {
59
+ child
60
+ documents = new Map ()
61
+ parser = new LanguageServerMessageParser()
62
+ pendingRequests = new Map ()
63
+ ready
64
+ rootUri
65
+ nextRequestId = 1
66
+ running = true
67
+ stderr = ''
68
+
69
+ constructor({ argv, rootUri, uri } ) {
70
+ this.rootUri = rootUri
71
+ this.child = spawn(fileURLToPath(uri), [...argv], {
72
+ stdio: ['pipe', 'pipe', 'pipe'],
73
+ })
74
+ this.child.stdout.on('data', (chunk) => {
75
+ this.handleData(chunk)
76
+ })
77
+ this.child.stderr.on('data', (chunk) => {
78
+ this.stderr += chunk.toString()
79
+ })
80
+ this.child.on('error', (error) => {
81
+ this.handleExit(error)
82
+ })
83
+ this.child.on('exit', (code, signal) => {
84
+ const detail = this.stderr.trim()
85
+ const suffix = detail ? `: ${detail}` : ''
86
+ this.handleExit(new Error(`Language server exited with code ${code} and signal ${signal}${suffix}`))
87
+ })
88
+ this.ready = this.initialize()
89
+ }
90
+
91
+ isRunning() {
92
+ return this.running
93
+ }
94
+
95
+ dispose() {
96
+ if (!this.running) {
97
+ return
98
+ }
99
+ this.running = false
100
+ this.child.kill()
101
+ this.rejectPendingRequests(new Error('Language server was disposed'))
102
+ }
103
+
104
+ async complete(textDocument, offset) {
105
+ await this.ready
106
+ this.syncDocument(textDocument)
107
+ const result = await this.sendRequest('textDocument/completion', {
108
+ context: {
109
+ triggerKind: 1,
110
+ },
111
+ position: getPosition(textDocument.text, offset),
112
+ textDocument: {
113
+ uri: textDocument.uri,
114
+ },
115
+ })
116
+ if (Array.isArray(result)) {
117
+ return result
118
+ }
119
+ if (result && typeof result === 'object' && Array.isArray((result).items)) {
120
+ return (result).items
121
+ }
122
+ return []
123
+ }
124
+
125
+ async initialize() {
126
+ await this.sendRequest('initialize', {
127
+ capabilities: {
128
+ textDocument: {
129
+ completion: {
130
+ completionItem: {
131
+ snippetSupport: true,
132
+ },
133
+ },
134
+ synchronization: {
135
+ didSave: true,
136
+ dynamicRegistration: false,
137
+ },
138
+ },
139
+ workspace: {
140
+ configuration: true,
141
+ workspaceFolders: true,
142
+ },
143
+ },
144
+ clientInfo: {
145
+ name: 'Lvce Editor',
146
+ },
147
+ processId: process.pid,
148
+ rootUri: this.rootUri,
149
+ workspaceFolders: [
150
+ {
151
+ name: getWorkspaceName(this.rootUri),
152
+ uri: this.rootUri,
153
+ },
154
+ ],
155
+ })
156
+ this.sendNotification('initialized', {})
157
+ }
158
+
159
+ syncDocument(textDocument) {
160
+ const previous = this.documents.get(textDocument.uri)
161
+ if (!previous || previous.languageId !== textDocument.languageId) {
162
+ if (previous) {
163
+ this.sendNotification('textDocument/didClose', {
164
+ textDocument: {
165
+ uri: textDocument.uri,
166
+ },
167
+ })
168
+ }
169
+ const next = {
170
+ languageId: textDocument.languageId,
171
+ text: textDocument.text,
172
+ version: 1,
173
+ }
174
+ this.documents.set(textDocument.uri, next)
175
+ this.sendNotification('textDocument/didOpen', {
176
+ textDocument: {
177
+ languageId: next.languageId,
178
+ text: next.text,
179
+ uri: textDocument.uri,
180
+ version: next.version,
181
+ },
182
+ })
183
+ return
184
+ }
185
+ if (previous.text === textDocument.text) {
186
+ return
187
+ }
188
+ const next = {
189
+ ...previous,
190
+ text: textDocument.text,
191
+ version: previous.version + 1,
192
+ }
193
+ this.documents.set(textDocument.uri, next)
194
+ this.sendNotification('textDocument/didChange', {
195
+ contentChanges: [
196
+ {
197
+ text: next.text,
198
+ },
199
+ ],
200
+ textDocument: {
201
+ uri: textDocument.uri,
202
+ version: next.version,
203
+ },
204
+ })
205
+ }
206
+
207
+ handleData(chunk) {
208
+ try {
209
+ const messages = this.parser.push(chunk)
210
+ for (const message of messages) {
211
+ this.handleMessage(message)
212
+ }
213
+ } catch (error) {
214
+ this.handleExit(error instanceof Error ? error : new Error(String(error)))
215
+ }
216
+ }
217
+
218
+ handleMessage(message) {
219
+ if (message.method && message.id !== undefined && message.id !== null) {
220
+ this.handleServerRequest(message)
221
+ return
222
+ }
223
+ if (message.id === undefined || message.id === null) {
224
+ return
225
+ }
226
+ const pending = this.pendingRequests.get(message.id)
227
+ if (!pending) {
228
+ return
229
+ }
230
+ this.pendingRequests.delete(message.id)
231
+ if (message.error) {
232
+ pending.reject(new Error(message.error.message || `Language server request failed with code ${message.error.code}`))
233
+ return
234
+ }
235
+ pending.resolve(message.result)
236
+ }
237
+
238
+ handleServerRequest(message) {
239
+ let result = null
240
+ if (message.method === 'workspace/configuration') {
241
+ const itemCount = Array.isArray(message.params?.items) ? message.params.items.length : 0
242
+ result = Array.from({ length: itemCount }).fill(null)
243
+ } else if (message.method === 'workspace/workspaceFolders') {
244
+ result = [
245
+ {
246
+ name: getWorkspaceName(this.rootUri),
247
+ uri: this.rootUri,
248
+ },
249
+ ]
250
+ }
251
+ this.sendMessage({
252
+ id: message.id,
253
+ jsonrpc: '2.0',
254
+ result,
255
+ })
256
+ }
257
+
258
+ handleExit(error) {
259
+ if (!this.running) {
260
+ return
261
+ }
262
+ this.running = false
263
+ this.rejectPendingRequests(error)
264
+ }
265
+
266
+ rejectPendingRequests(error) {
267
+ for (const pending of this.pendingRequests.values()) {
268
+ pending.reject(error)
269
+ }
270
+ this.pendingRequests.clear()
271
+ }
272
+
273
+ sendNotification(method, params) {
274
+ this.sendMessage({
275
+ jsonrpc: '2.0',
276
+ method,
277
+ params,
278
+ })
279
+ }
280
+
281
+ sendRequest(method, params) {
282
+ if (!this.running) {
283
+ return Promise.reject(new Error('Language server is not running'))
284
+ }
285
+ const id = this.nextRequestId++
286
+ const promise = new Promise ((resolve, reject) => {
287
+ this.pendingRequests.set(id, { reject, resolve })
288
+ })
289
+ this.sendMessage({
290
+ id,
291
+ jsonrpc: '2.0',
292
+ method,
293
+ params,
294
+ })
295
+ return promise
296
+ }
297
+
298
+ sendMessage(message) {
299
+ const content = JSON.stringify(message)
300
+ const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`
301
+ this.child.stdin.write(header + content)
302
+ }
303
+ }
@@ -0,0 +1,32 @@
1
+ const headerSeparator = Buffer.from('\r\n\r\n')
2
+ const contentLengthRegex = /(?:^|\r\n)Content-Length:\s*(\d+)/i
3
+
4
+ export class LanguageServerMessageParser {
5
+ buffer = Buffer.alloc(0)
6
+
7
+ push(chunk) {
8
+ this.buffer = Buffer.concat([this.buffer, chunk])
9
+ const messages = []
10
+ while (true) {
11
+ const headerEnd = this.buffer.indexOf(headerSeparator)
12
+ if (headerEnd === -1) {
13
+ break
14
+ }
15
+ const header = this.buffer.subarray(0, headerEnd).toString('ascii')
16
+ const match = contentLengthRegex.exec(header)
17
+ if (!match) {
18
+ throw new Error('Language server message is missing Content-Length')
19
+ }
20
+ const contentLength = Number(match[1])
21
+ const contentStart = headerEnd + headerSeparator.length
22
+ const contentEnd = contentStart + contentLength
23
+ if (this.buffer.length < contentEnd) {
24
+ break
25
+ }
26
+ const content = this.buffer.subarray(contentStart, contentEnd).toString('utf8')
27
+ this.buffer = this.buffer.subarray(contentEnd)
28
+ messages.push(JSON.parse(content))
29
+ }
30
+ return messages
31
+ }
32
+ }
@@ -110,6 +110,8 @@ export const load = (moduleId) => {
110
110
  return import('../InstallExtension/InstallExtension.ipc.js')
111
111
  case ModuleId.IsAutoUpdateSupported:
112
112
  return import('../IsAutoUpdateSupported/IsAutoUpdateSupported.ipc.js')
113
+ case ModuleId.LanguageServer:
114
+ return import('../LanguageServer/LanguageServer.ipc.js')
113
115
  case ModuleId.OpenExternal:
114
116
  return import('../OpenExternal/OpenExternal.ipc.js')
115
117
  case ModuleId.OpenNativeFolder:
@@ -78,3 +78,4 @@ export const HandleMessagePortForClipBoardProcess = 82
78
78
  export const Exec = 83
79
79
  export const PlatformPaths = 84
80
80
  export const HandleMessagePortForAuthProcess = 86
81
+ export const LanguageServer = 87
@@ -67,8 +67,11 @@ export const getModuleId = (commandId) => {
67
67
  case 'ElectronPowerSaveBlocker.start':
68
68
  case 'ElectronPowerSaveBlocker.stop':
69
69
  return ModuleId.ElectronPowerSaveBlocker
70
+ case 'ElectronProcess.getArgv':
70
71
  case 'ElectronProcess.getChromeVersion':
71
72
  case 'ElectronProcess.getElectronVersion':
73
+ case 'ElectronProcess.writeStderr':
74
+ case 'ElectronProcess.writeStdout':
72
75
  return ModuleId.ElectronProcess
73
76
  case 'ElectronSafeStorage.decryptString':
74
77
  case 'ElectronSafeStorage.encryptString':
@@ -244,6 +247,8 @@ export const getModuleId = (commandId) => {
244
247
  return ModuleId.InstallExtension
245
248
  case 'IsAutoUpdateSupported.isAutoUpdateSupported':
246
249
  return ModuleId.IsAutoUpdateSupported
250
+ case 'LanguageServer.complete':
251
+ return ModuleId.LanguageServer
247
252
  case 'ListProcessesWithMemoryUsage.listProcessesWithMemoryUsage':
248
253
  return ModuleId.ListProcessesWithMemoryUsage
249
254
  case 'Native.openFolder':
@@ -61,11 +61,11 @@ export const getSetupName = () => {
61
61
  return 'Lvce-Setup'
62
62
  }
63
63
 
64
- export const version = '0.89.1'
64
+ export const version = '0.89.3'
65
65
 
66
- export const commit = 'd7a85a3'
66
+ export const commit = 'f1db6cb'
67
67
 
68
- export const date = '2026-07-14T10:09:23.000Z'
68
+ export const date = '2026-07-14T11:58:48.000Z'
69
69
 
70
70
  export const getVersion = () => {
71
71
  return version
@@ -2,5 +2,5 @@ import { join } from 'node:path'
2
2
  import * as Root from '../Root/Root.js'
3
3
 
4
4
  export const getPreloadUrl = () => {
5
- return join(Root.root, 'static', 'd7a85a3', 'packages', 'preload', 'dist', 'index.js')
5
+ return join(Root.root, 'static', 'f1db6cb', 'packages', 'preload', 'dist', 'index.js')
6
6
  }
@@ -5,6 +5,7 @@ import * as Env from '../Env/Env.js'
5
5
  import * as GetWorkspaceId from '../GetWorkspaceId/GetWorkspaceId.js'
6
6
  import * as IsAbsolutePath from '../IsAbsolutePath/IsAbsolutePath.js'
7
7
  import * as IsElectron from '../IsElectron/IsElectron.js'
8
+ import * as IsPromptMode from '../IsPromptMode/IsPromptMode.js'
8
9
  import * as ParentIpc from '../MainProcess/MainProcess.js'
9
10
  import * as Platform from '../Platform/Platform.js'
10
11
  import * as PlatformPaths from '../PlatformPaths/PlatformPaths.js'
@@ -33,7 +34,7 @@ export const resolveRoot = async () => {
33
34
  const argv = await ParentIpc.invoke('Process.getArgv')
34
35
  const relevantArgv = argv.slice(1)
35
36
  const last = relevantArgv.at(-1)
36
- if (last && last === '.') {
37
+ if (IsPromptMode.isPromptMode(relevantArgv) || (last && last === '.')) {
37
38
  const actual = process.cwd()
38
39
  return {
39
40
  homeDir: PlatformPaths.getHomeDir(),