@@ -4,6 +4,11 @@ import * as path from 'node:path'
44import * as fs from 'node:fs/promises'
55import { type ScanOptions , type ScanResult } from './oxide'
66
7+ interface ServerHandle {
8+ helper : proc . ChildProcess
9+ connection : rpc . MessageConnection
10+ }
11+
712/**
813 * This helper starts a session in which we can use Oxide in *another process*
914 * to communicate content scanning results.
@@ -19,75 +24,98 @@ import { type ScanOptions, type ScanResult } from './oxide'
1924 * us sidestep the problem as the process will only be running as needed.
2025 */
2126export class OxideSession {
22- helper : proc . ChildProcess | null = null
23- connection : rpc . MessageConnection | null = null
27+ /**
28+ * A cached helper path
29+ *
30+ * We store this so the lookup happens at most once
31+ */
32+ private static helperPath : Promise < string > | null = null
33+
34+ /**
35+ * An object that represents the connection to the server
36+ *
37+ * This ensures that either everything is initialized or nothing is
38+ */
39+ private server : Promise < ServerHandle > | null = null
2440
2541 public async scan ( options : ScanOptions ) : Promise < ScanResult > {
26- await this . startIfNeeded ( )
42+ let server = await this . startIfNeeded ( )
2743
28- return await this . connection . sendRequest ( 'scan' , options )
44+ return await server . connection . sendRequest ( 'scan' , options )
2945 }
3046
31- async startIfNeeded ( ) : Promise < void > {
32- if ( this . connection ) return
33-
34- // TODO: Can we find a way to not require a build first?
35- // let module = path.resolve(path.dirname(__filename), './oxide-helper.ts')
36-
37- let modulePaths = [
38- // Separate Language Server package
39- '../bin/oxide-helper.js' ,
40-
41- // Bundled with the VSCode extension
42- '../dist/oxide-helper.js' ,
43- ]
47+ async startIfNeeded ( ) : Promise < ServerHandle > {
48+ console . log ( 'startIfNeeded?' )
4449
45- let module : string | null = null
50+ this . server ??= this . start ( )
4651
47- for ( let relativePath of modulePaths ) {
48- let filepath = path . resolve ( path . dirname ( __filename ) , relativePath )
52+ return this . server
53+ }
4954
50- if (
51- await fs . access ( filepath ) . then (
52- ( ) => true ,
53- ( ) => false ,
54- )
55- ) {
56- module = filepath
57- break
58- }
59- }
55+ private async start ( ) : Promise < ServerHandle > {
56+ console . log ( 'start server?' )
6057
61- if ( ! module ) throw new Error ( 'unable to load' )
58+ let helperPath = await OxideSession . locateHelper ( )
6259
63- let helper = proc . fork ( module )
60+ let helper = proc . fork ( helperPath )
6461 let connection = rpc . createMessageConnection (
6562 new rpc . IPCMessageReader ( helper ) ,
6663 new rpc . IPCMessageWriter ( helper ) ,
6764 )
6865
6966 helper . on ( 'disconnect' , ( ) => {
67+ console . log ( 'Helper disconnected' )
7068 connection . dispose ( )
71- this . connection = null
72- this . helper = null
69+ this . server = null
7370 } )
7471
7572 helper . on ( 'exit' , ( ) => {
73+ console . log ( 'Helper exited' )
7674 connection . dispose ( )
77- this . connection = null
78- this . helper = null
75+ this . server = null
7976 } )
8077
8178 connection . listen ( )
8279
83- this . helper = helper
84- this . connection = connection
80+ return { helper, connection }
8581 }
8682
8783 async stop ( ) {
88- if ( ! this . helper ) return
84+ if ( ! this . server ) return
85+
86+ let handle = await this . server
87+ handle . helper . disconnect ( )
88+ handle . helper . kill ( )
89+ }
90+
91+ private static async locateHelper ( ) : Promise < string > {
92+ console . log ( 'locate helper?' )
93+
94+ // TODO: Can we find a way to not require a build first?
95+ // let module = path.resolve(path.dirname(__filename), './oxide-helper.ts')
96+
97+ let modulePaths = [
98+ // Separate Language Server package
99+ '../bin/oxide-helper.js' ,
100+
101+ // Bundled with the VSCode extension
102+ '../dist/oxide-helper.js' ,
103+ ]
104+
105+ OxideSession . helperPath ??= ( async ( ) => {
106+ for ( let relativePath of modulePaths ) {
107+ let filepath = path . resolve ( path . dirname ( __filename ) , relativePath )
108+ let exists = await fs . access ( filepath ) . then (
109+ ( ) => true ,
110+ ( ) => false ,
111+ )
112+
113+ if ( exists ) return filepath
114+ }
115+
116+ throw new Error ( 'unable to load' )
117+ } ) ( )
89118
90- this . helper . disconnect ( )
91- this . helper . kill ( )
119+ return OxideSession . helperPath
92120 }
93121}
0 commit comments