Skip to content

Commit 02fb938

Browse files
author
Andrey Dernov
committed
PSES Named pipes support (#6)
1 parent 359d4b0 commit 02fb938

File tree

2 files changed

+114
-35
lines changed

2 files changed

+114
-35
lines changed

build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ repositories {
5252
dependencies {
5353
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8"
5454
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
55+
compile group: 'no.fiken.oss.junixsocket', name: 'junixsocket-common', version: '1.0.2'
56+
compile group: 'no.fiken.oss.junixsocket', name: 'junixsocket-native-common', version: '1.0.2'
5557
// compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '0.21'
5658
compile ("org.eclipse.lsp4j:org.eclipse.lsp4j:0.3.0") {
5759
exclude group: 'com.google.code.gson', module: 'gson'

src/main/kotlin/com/intellij/plugin/powershell/lang/lsp/languagehost/EditorServicesLanguageHostStarter.kt

+112-35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.intellij.plugin.powershell.lang.lsp.languagehost
22

33
import com.google.common.io.Files
4+
import com.google.gson.JsonObject
45
import com.google.gson.JsonParser
56
import com.intellij.execution.configurations.GeneralCommandLine
67
import com.intellij.notification.BrowseNotificationAction
@@ -12,6 +13,7 @@ import com.intellij.openapi.application.ApplicationManager
1213
import com.intellij.openapi.application.ApplicationNamesInfo
1314
import com.intellij.openapi.diagnostic.Logger
1415
import com.intellij.openapi.project.Project
16+
import com.intellij.openapi.util.SystemInfo
1517
import com.intellij.openapi.util.io.FileUtil
1618
import com.intellij.openapi.util.text.StringUtil
1719
import com.intellij.plugin.powershell.PowerShellIcons
@@ -27,16 +29,21 @@ import com.intellij.plugin.powershell.lang.lsp.languagehost.PSLanguageHostUtils.
2729
import com.sun.jna.Pointer
2830
import com.sun.jna.platform.win32.Kernel32
2931
import com.sun.jna.platform.win32.WinNT
32+
import org.newsclub.net.unix.AFUNIXSocket
33+
import org.newsclub.net.unix.AFUNIXSocketAddress
3034
import java.io.*
3135
import java.net.Socket
36+
import java.nio.channels.Channels
3237
import java.nio.charset.Charset
3338
import java.util.concurrent.TimeUnit
3439

3540

3641
open class EditorServicesLanguageHostStarter(protected val myProject: Project) : LanguageHostConnectionManager {
3742

38-
private val LOG: Logger = Logger.getInstance(javaClass)
43+
private val LOG: Logger = Logger.getInstance(javaClass)
3944
private var socket: Socket? = null
45+
private var myReadPipe: RandomAccessFile? = null
46+
private var myWritePipe: RandomAccessFile? = null
4047
private var sessionInfoFile: File? = null
4148
private var myProcess: Process? = null
4249
private var sessionInfo: SessionInfo? = null
@@ -58,9 +65,28 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
5865
"${ApplicationInfo.getInstance().majorVersion}.${ApplicationInfo.getInstance().minorVersion}"
5966

6067
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+
}
6490
}
6591
}
6692

@@ -100,21 +126,41 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
100126
* @throws PowerShellNotInstalled
101127
*/
102128
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+
}
118164
}
119165
return Pair(null, null)
120166
}
@@ -145,15 +191,23 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
145191
}
146192
val sessionDetailsPath = FileUtil.toCanonicalPath(getSessionDetailsFile().canonicalPath)
147193
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+
}
149203
val bundledModulesPath = getPSExtensionModulesDir(psExtensionPath)
150204
val additionalModules = ""//todo check if something could be added here
151205
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}' " +
154208
"-HostVersion '${myHostDetails.version}' -AdditionalModules @($additionalModules) " +
155209
"-BundledModulesPath '$bundledModulesPath' $useReplSwitch " +
156-
"-LogLevel '$logLevel' -LogPath '$logPath' -SessionDetailsPath '$sessionDetailsPath' -FeatureFlags @()"
210+
"-LogLevel '$logLevel' -LogPath '$logPath' -SessionDetailsPath '$sessionDetailsPath' -FeatureFlags @() $splitInOutPipesSwitch"
157211
val scriptText = "${escapePath(startupScript)} $args\n"
158212

159213
val scriptFile = File.createTempFile("start-pses-host", ".ps1")
@@ -234,21 +288,39 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
234288
return true
235289
}
236290

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+
237317
private fun readSessionFile(sessionFile: File): SessionInfo? {
238318
try {
239319
val line = Files.readFirstLine(sessionFile, Charset.forName("utf8"))
240320
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)
250322
} 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")
252324
return null
253325
}
254326
}
@@ -293,17 +365,22 @@ open class EditorServicesLanguageHostStarter(protected val myProject: Project) :
293365
try {
294366
socket?.close()
295367
socket = null
368+
myReadPipe?.close()
369+
myWritePipe?.close()
296370
sessionInfoFile?.delete()
297-
sessionInfoFile = null
298371
val process = myProcess?.destroyForcibly()
299372
if (process?.isAlive != true) {
300373
LOG.info("PowerShell language host process exited: ${process?.exitValue()}")
301374
} else {
302375
LOG.info("PowerShell language host process terminated")
303376
}
304-
myProcess = null
305377
} catch (e: Exception) {
306378
LOG.error("Error when shutting down language server process: $e")
379+
} finally {
380+
sessionInfoFile = null
381+
myProcess = null
382+
myReadPipe = null
383+
myWritePipe = null
307384
}
308385
}
309386

0 commit comments

Comments
 (0)