1
1
package com.intellij.plugin.powershell.lang.lsp.languagehost
2
2
3
3
import com.google.common.io.Files
4
+ import com.google.gson.JsonObject
4
5
import com.google.gson.JsonParser
5
6
import com.intellij.execution.configurations.GeneralCommandLine
6
7
import com.intellij.notification.BrowseNotificationAction
@@ -12,6 +13,7 @@ import com.intellij.openapi.application.ApplicationManager
12
13
import com.intellij.openapi.application.ApplicationNamesInfo
13
14
import com.intellij.openapi.diagnostic.Logger
14
15
import com.intellij.openapi.project.Project
16
+ import com.intellij.openapi.util.SystemInfo
15
17
import com.intellij.openapi.util.io.FileUtil
16
18
import com.intellij.openapi.util.text.StringUtil
17
19
import com.intellij.plugin.powershell.PowerShellIcons
@@ -27,16 +29,21 @@ import com.intellij.plugin.powershell.lang.lsp.languagehost.PSLanguageHostUtils.
27
29
import com.sun.jna.Pointer
28
30
import com.sun.jna.platform.win32.Kernel32
29
31
import com.sun.jna.platform.win32.WinNT
32
+ import org.newsclub.net.unix.AFUNIXSocket
33
+ import org.newsclub.net.unix.AFUNIXSocketAddress
30
34
import java.io.*
31
35
import java.net.Socket
36
+ import java.nio.channels.Channels
32
37
import java.nio.charset.Charset
33
38
import java.util.concurrent.TimeUnit
34
39
35
40
36
41
open class EditorServicesLanguageHostStarter (protected val myProject : Project ) : LanguageHostConnectionManager {
37
42
38
- private val LOG : Logger = Logger .getInstance(javaClass)
43
+ private val LOG : Logger = Logger .getInstance(javaClass)
39
44
private var socket: Socket ? = null
45
+ private var myReadPipe: RandomAccessFile ? = null
46
+ private var myWritePipe: RandomAccessFile ? = null
40
47
private var sessionInfoFile: File ? = null
41
48
private var myProcess: Process ? = null
42
49
private var sessionInfo: SessionInfo ? = null
@@ -58,9 +65,28 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
58
65
" ${ApplicationInfo .getInstance().majorVersion} .${ApplicationInfo .getInstance().minorVersion} "
59
66
60
67
private data class HostDetails (val name : String , val profileId : String , val version : String )
61
- private data class SessionInfo (val languageServicePort : Int , val debugServicePort : Int , val powerShellVersion : String? , val status : String? ) {
62
- override fun toString (): String {
63
- return " {languageServicePort:$languageServicePort ,debugServicePort:$debugServicePort ,powerShellVersion:$powerShellVersion ,status:$status }"
68
+ private open class SessionInfo private constructor(protected val powerShellVersion : String? , protected val status : String? ) {
69
+
70
+ internal class Pipes internal constructor(internal val languageServiceReadPipeName : String ,
71
+ internal val languageServiceWritePipeName : String ,
72
+ internal val debugServiceReadPipeName : String ,
73
+ internal val debugServiceWritePipeName : String ,
74
+ powerShellVersion : String? ,
75
+ status : String? ) : SessionInfo(powerShellVersion, status) {
76
+ override fun toString (): String {
77
+ return " {languageServiceReadPipeName:$languageServiceReadPipeName ,languageServiceWritePipeName:$languageServiceWritePipeName ," +
78
+ " debugServiceReadPipeName:$debugServiceReadPipeName ,debugServiceWritePipeName:$debugServiceWritePipeName ," +
79
+ " powerShellVersion:$powerShellVersion ,status:$status }"
80
+ }
81
+ }
82
+
83
+ internal class Tcp internal constructor(internal val languageServicePort : Int ,
84
+ internal val debugServicePort : Int ,
85
+ powerShellVersion : String? ,
86
+ status : String? ) : SessionInfo(powerShellVersion, status) {
87
+ override fun toString (): String {
88
+ return " {languageServicePort:$languageServicePort ,debugServicePort:$debugServicePort ,powerShellVersion:$powerShellVersion ,status:$status }"
89
+ }
64
90
}
65
91
}
66
92
@@ -100,21 +126,41 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
100
126
* @throws PowerShellNotInstalled
101
127
*/
102
128
override fun establishConnection (): Pair <InputStream ?, OutputStream ?> {
103
- val port = startServerSession()?.languageServicePort ? : return Pair (null , null )// long operation
104
- try {
105
- socket = Socket (" 127.0.0.1" , port)
106
- } catch (e: Exception ) {
107
- LOG .error(" Unable to open connection to language host: $e " )
108
- }
109
- if (socket == null ) {
110
- LOG .error(" Unable to create socket: " + toString())
111
- }
112
- if (socket?.isConnected == true ) {
113
- LOG .info(" Connection to language host established: ${socket?.localPort} -> ${socket?.port} " )
114
- val inputStream = socket?.getInputStream()
115
- val outputStream = socket?.getOutputStream()
116
- return if (inputStream == null || outputStream == null ) Pair (null , null ) else Pair (inputStream, outputStream)
117
-
129
+ val sessionInfo = startServerSession() ? : return Pair (null , null )// long operation
130
+ if (sessionInfo is SessionInfo .Pipes ) {
131
+ val readPipeName = sessionInfo.languageServiceReadPipeName
132
+ val writePipeName = sessionInfo.languageServiceWritePipeName
133
+ if (SystemInfo .isWindows) {
134
+ val readPipe = RandomAccessFile (readPipeName, " rwd" )
135
+ val writePipe = RandomAccessFile (writePipeName, " rwd" )
136
+ val serverReadChannel = readPipe.channel
137
+ val serverWriteChannel = writePipe.channel
138
+ val inSf = Channels .newInputStream(serverWriteChannel)
139
+ val outSf = BufferedOutputStream (Channels .newOutputStream(serverReadChannel))
140
+ return Pair (inSf, outSf)
141
+ } else {
142
+ val readSock = AFUNIXSocket .newInstance();
143
+ val writeSock = AFUNIXSocket .newInstance();
144
+ readSock.connect(AFUNIXSocketAddress (File (readPipeName)));
145
+ writeSock.connect(AFUNIXSocketAddress (File (writePipeName)));
146
+ return Pair (writeSock.inputStream, readSock.outputStream)
147
+ }
148
+ } else {
149
+ val port = (sessionInfo as ? SessionInfo .Tcp )?.languageServicePort ? : return Pair (null , null )
150
+ try {
151
+ socket = Socket (" 127.0.0.1" , port)
152
+ } catch (e: Exception ) {
153
+ LOG .error(" Unable to open connection to language host: $e " )
154
+ }
155
+ if (socket == null ) {
156
+ LOG .error(" Unable to create socket: " + toString())
157
+ }
158
+ if (socket?.isConnected == true ) {
159
+ LOG .info(" Connection to language host established: ${socket?.localPort} -> ${socket?.port} " )
160
+ val inputStream = socket?.getInputStream()
161
+ val outputStream = socket?.getOutputStream()
162
+ if (inputStream != null && outputStream != null ) return Pair (inputStream, outputStream)
163
+ }
118
164
}
119
165
return Pair (null , null )
120
166
}
@@ -145,15 +191,23 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
145
191
}
146
192
val sessionDetailsPath = FileUtil .toCanonicalPath(getSessionDetailsFile().canonicalPath)
147
193
val logPath = createLogPath(psExtensionPath)
148
- val editorServicesVersion = getEditorServicesVersion(psExtensionPath)
194
+ var psesVersionString = getEditorServicesVersion(psExtensionPath)
195
+ var splitInOutPipesSwitch = " "
196
+ val psesVersion = PSESVersion .create(psesVersionString)
197
+ if (psesVersion != null && psesVersion.isGreaterOrEqual(PSESVersion .v1_7_0)) {
198
+ psesVersionString = " "
199
+ splitInOutPipesSwitch = " -SplitInOutPipes"
200
+ } else {
201
+ psesVersionString = " -EditorServicesVersion '$psesVersionString '"
202
+ }
149
203
val bundledModulesPath = getPSExtensionModulesDir(psExtensionPath)
150
204
val additionalModules = " " // todo check if something could be added here
151
205
val useReplSwitch = if (useConsoleRepl()) " -EnableConsoleRepl" else " "
152
- val logLevel = " Verbose " // "" Diagnostic" -< does not work for older PS versions
153
- val args = " -EditorServicesVersion ' $editorServicesVersion ' -HostName '${myHostDetails.name} ' -HostProfileId '${myHostDetails.profileId} ' " +
206
+ val logLevel = if (useConsoleRepl()) " Normal " else " Diagnostic"
207
+ val args = " $psesVersionString -HostName '${myHostDetails.name} ' -HostProfileId '${myHostDetails.profileId} ' " +
154
208
" -HostVersion '${myHostDetails.version} ' -AdditionalModules @($additionalModules ) " +
155
209
" -BundledModulesPath '$bundledModulesPath ' $useReplSwitch " +
156
- " -LogLevel '$logLevel ' -LogPath '$logPath ' -SessionDetailsPath '$sessionDetailsPath ' -FeatureFlags @()"
210
+ " -LogLevel '$logLevel ' -LogPath '$logPath ' -SessionDetailsPath '$sessionDetailsPath ' -FeatureFlags @() $splitInOutPipesSwitch "
157
211
val scriptText = " ${escapePath(startupScript)} $args \n "
158
212
159
213
val scriptFile = File .createTempFile(" start-pses-host" , " .ps1" )
@@ -234,21 +288,39 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
234
288
return true
235
289
}
236
290
291
+ private fun readTcpInfo (jsonResult : JsonObject ): SessionInfo ? {
292
+ val langServicePort = jsonResult.get(" languageServicePort" )?.asInt
293
+ val debugServicePort = jsonResult.get(" debugServicePort" )?.asInt
294
+ val powerShellVersion = jsonResult.get(" powerShellVersion" )?.asString
295
+ val status = jsonResult.get(" status" )?.asString
296
+ if (langServicePort == null || debugServicePort == null ) {
297
+ LOG .warn(" languageServicePort or debugServicePort are null" )
298
+ return null
299
+ }
300
+ return SessionInfo .Tcp (langServicePort, debugServicePort, powerShellVersion, status)
301
+ }
302
+
303
+ private fun readPipesInfo (jsonResult : JsonObject ): SessionInfo ? {
304
+ val langServiceReadPipeName = jsonResult.get(" languageServiceReadPipeName" )?.asString
305
+ val langServiceWritePipeName = jsonResult.get(" languageServiceWritePipeName" )?.asString
306
+ val debugServiceReadPipeName = jsonResult.get(" debugServiceReadPipeName" )?.asString
307
+ val debugServiceWritePipeName = jsonResult.get(" debugServiceWritePipeName" )?.asString
308
+ val powerShellVersion = jsonResult.get(" powerShellVersion" )?.asString
309
+ val status = jsonResult.get(" status" )?.asString
310
+ if (langServiceReadPipeName == null || langServiceWritePipeName == null || debugServiceReadPipeName == null || debugServiceWritePipeName == null ) {
311
+ LOG .warn(" languageServiceReadPipeName or debugServiceReadPipeName are null" )
312
+ return null
313
+ }
314
+ return SessionInfo .Pipes (langServiceReadPipeName, langServiceWritePipeName, debugServiceReadPipeName, debugServiceWritePipeName, powerShellVersion, status)
315
+ }
316
+
237
317
private fun readSessionFile (sessionFile : File ): SessionInfo ? {
238
318
try {
239
319
val line = Files .readFirstLine(sessionFile, Charset .forName(" utf8" ))
240
320
val jsonResult = JsonParser ().parse(line).asJsonObject
241
- val langServicePort = jsonResult.get(" languageServicePort" )?.asInt
242
- val debugServicePort = jsonResult.get(" debugServicePort" )?.asInt
243
- val powerShellVersion = jsonResult.get(" powerShellVersion" )?.asString
244
- val status = jsonResult.get(" status" )?.asString
245
- if (langServicePort == null || debugServicePort == null ) {
246
- LOG .warn(" languageServicePort or debugServicePort are null" )
247
- return null
248
- }
249
- return SessionInfo (langServicePort, debugServicePort, powerShellVersion, status)
321
+ return readPipesInfo(jsonResult) ? : readTcpInfo(jsonResult)
250
322
} catch (e: Exception ) {
251
- LOG .error (" Error reading/parsing session details file $sessionFile : $e " )
323
+ LOG .warn (" Error reading/parsing session details file $sessionFile : $e " )
252
324
return null
253
325
}
254
326
}
@@ -293,17 +365,22 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
293
365
try {
294
366
socket?.close()
295
367
socket = null
368
+ myReadPipe?.close()
369
+ myWritePipe?.close()
296
370
sessionInfoFile?.delete()
297
- sessionInfoFile = null
298
371
val process = myProcess?.destroyForcibly()
299
372
if (process?.isAlive != true ) {
300
373
LOG .info(" PowerShell language host process exited: ${process?.exitValue()} " )
301
374
} else {
302
375
LOG .info(" PowerShell language host process terminated" )
303
376
}
304
- myProcess = null
305
377
} catch (e: Exception ) {
306
378
LOG .error(" Error when shutting down language server process: $e " )
379
+ } finally {
380
+ sessionInfoFile = null
381
+ myProcess = null
382
+ myReadPipe = null
383
+ myWritePipe = null
307
384
}
308
385
}
309
386
0 commit comments