Skip to content

Commit

Permalink
Port WSL support to command line mode (#93, #96)
Browse files Browse the repository at this point in the history
  • Loading branch information
InSyncWithFoo authored Dec 23, 2024
1 parent 522b6fb commit b343cb6
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 64 deletions.
13 changes: 12 additions & 1 deletion src/main/kotlin/com/insyncwithfoo/pyright/Module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,18 @@ internal val Module.path: Path?


internal val Module.interpreterPath: Path?
get() = sdk?.homePath?.let { Path.of(it) } ?: project.interpreterPath
get() = sdk?.path ?: project.interpreterPath


internal val Module.osDependentInterpreterPath: String?
get() {
val interpreterPath = this.interpreterPath?.toString()

return when (wslDistribution) {
null -> interpreterPath
else -> interpreterPath?.replace("\\", "/")
}
}


internal val PsiElement.module: Module?
Expand Down
3 changes: 1 addition & 2 deletions src/main/kotlin/com/insyncwithfoo/pyright/Paths.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.intellij.openapi.util.SystemInfo
import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.io.path.div
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.nameWithoutExtension
Expand Down Expand Up @@ -38,7 +37,7 @@ internal fun String.toOSDependentFileName() = when {


internal fun Path.toNullIfNotExists() =
this.takeIf { it.exists() }
this.takeIf { it.toFile().exists() }


private fun Path.directoryIsEmpty() = listDirectoryEntries().isEmpty()
Expand Down
29 changes: 29 additions & 0 deletions src/main/kotlin/com/insyncwithfoo/pyright/WSL.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
package com.insyncwithfoo.pyright

import com.intellij.execution.wsl.WSLDistribution
import com.intellij.execution.wsl.WslPath
import com.intellij.execution.wsl.target.WslTargetEnvironmentConfiguration
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.io.OSAgnosticPathUtil
import com.jetbrains.python.target.PyTargetAwareAdditionalData
import java.net.URI
import java.nio.file.Path


internal val Sdk.path: Path?
get() = homePath?.toPathOrNull()


internal val Path.isUncPath: Boolean
get() = WslPath.parseWindowsUncPath(this.toString()) != null


internal val URI.pathIsAbsoluteDos: Boolean
get() = OSAgnosticPathUtil.isAbsoluteDosPath(Path.of(this).toString())


internal fun String.asFileURI(): String {
val (scheme, host, fragment) = Triple("file", "", null)
return URI(scheme, host, this, fragment).toASCIIString()
}


internal fun WSLDistribution?.getPureLinuxOrWindowsPath(path: Path) = when {
this != null && path.isUncPath -> this.getWslPath(path)!!
else -> path.toString()
}


internal val Project.wslDistribution: WSLDistribution?
get() = sdk?.wslDistribution
?: this.path?.toString()?.let { WslPath.getDistributionByWindowsUncPath(it) }


internal val Module.wslDistribution: WSLDistribution?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,92 @@
package com.insyncwithfoo.pyright.commandline

import com.insyncwithfoo.pyright.ProcessOutputSurrogate
import com.insyncwithfoo.pyright.configurations.Locale
import com.insyncwithfoo.pyright.configurations.PyrightConfigurations
import com.insyncwithfoo.pyright.configurations.pyrightConfigurations
import com.insyncwithfoo.pyright.configurations.pyrightExecutable
import com.insyncwithfoo.pyright.interpreterPath
import com.insyncwithfoo.pyright.osDependentInterpreterPath
import com.insyncwithfoo.pyright.path
import com.insyncwithfoo.pyright.toNullIfNotExists
import com.insyncwithfoo.pyright.toPathIfItExists
import com.insyncwithfoo.pyright.wslDistribution
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessOutput
import com.intellij.execution.wsl.WSLCommandLineOptions
import com.intellij.execution.wsl.WSLDistribution
import com.intellij.execution.wsl.WslPath
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import java.nio.file.Path


private fun WSLDistribution?.resolve(path: String?) = when {
path == null -> null
this == null -> path
else -> WslPath.parseWindowsUncPath(path)?.linuxPath?.replace("\\", "/")
}


private fun WSLDistribution?.resolve(path: Path?) = path?.let { this.resolve(it.toString()) }


internal data class FileCommand(
val executable: Path,
val target: Path,
val projectPath: String,
val executable: String,
val target: String,
val projectPath: Path,
val extraArguments: List<String>,
val environmentVariables: Map<String, String>
val environmentVariables: Map<String, String>,
val module: Module
) {

private val wslDistribution by lazy { module.wslDistribution }

private val fragments: List<String>
get() = listOf(
executable.toString(),
executable,
*extraArguments.toTypedArray(),
target.toString()
target
)

private val commandLine: GeneralCommandLine
get() = GeneralCommandLine(fragments).apply {
withCharset(Charsets.UTF_8)
withWorkingDirectory(projectPath.toPathIfItExists())
withWorkingDirectory(projectPath)
withEnvironment(environmentVariables)

wslDistribution?.patchCommandLine(this, module.project, WSLCommandLineOptions())
}

private val processHandler: CapturingProcessHandler
get() = CapturingProcessHandler(commandLine)

fun run() =
processHandler.runProcess()
override fun toString() = commandLine.commandLineString

fun run(): ProcessOutput {
thisLogger().info("Running: ($projectPath) $this")

return processHandler.runProcess().also {
thisLogger().info("Output: ${ProcessOutputSurrogate(it)}")
}
}

companion object {

private fun create(
configurations: PyrightConfigurations,
executable: Path,
target: Path,
executable: String,
target: String,
argumentForProject: String,
projectPath: Path,
interpreterPath: Path
interpreterPath: String,
module: Module
): FileCommand {
val configurationFile = configurations.configurationFile

val argumentForProject = configurationFile ?: projectPath
val extraArguments: MutableList<String> = mutableListOf(
"--outputjson",
"--project", argumentForProject.toString(),
"--pythonpath", interpreterPath.toString()
"--project", argumentForProject,
"--pythonpath", interpreterPath
)

val environmentVariables = mutableMapOf<String, String>()

if (configurations.minimumSeverityLevel != DiagnosticSeverity.INFORMATION) {
Expand All @@ -74,19 +103,23 @@ internal data class FileCommand(
environmentVariables["LC_ALL"] = configurations.locale.toString()
}

return FileCommand(executable, target, projectPath.toString(), extraArguments, environmentVariables)
return FileCommand(executable, target, projectPath, extraArguments, environmentVariables, module)
}

internal fun create(module: Module, path: Path): FileCommand? {
val wslDistribution = module.wslDistribution
val project = module.project
val projectPath = project.path ?: return null
val configurations = project.pyrightConfigurations

val filePath = path.toNullIfNotExists() ?: return null
val projectPath = project.path ?: return null
val executable = project.pyrightExecutable ?: return null
val interpreterPath = module.interpreterPath ?: return null
val target = wslDistribution.resolve(path.toNullIfNotExists()) ?: return null
val executable = wslDistribution.resolve(project.pyrightExecutable) ?: return null
val interpreterPath = module.osDependentInterpreterPath ?: return null

val configurationFile = wslDistribution.resolve(configurations.configurationFile)
val argumentForProject = configurationFile ?: wslDistribution.resolve(projectPath) ?: return null

return create(configurations, executable, filePath, projectPath, interpreterPath)
return create(configurations, executable, target, argumentForProject, projectPath, interpreterPath, module)
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,29 @@
package com.insyncwithfoo.pyright.lsp

import com.insyncwithfoo.pyright.asFileURI
import com.insyncwithfoo.pyright.configurations.Locale
import com.insyncwithfoo.pyright.configurations.WorkspaceFolders
import com.insyncwithfoo.pyright.configurations.pyrightConfigurations
import com.insyncwithfoo.pyright.configurations.targetedFileExtensionList
import com.insyncwithfoo.pyright.getPureLinuxOrWindowsPath
import com.insyncwithfoo.pyright.message
import com.insyncwithfoo.pyright.modules
import com.insyncwithfoo.pyright.path
import com.insyncwithfoo.pyright.pathIsAbsoluteDos
import com.insyncwithfoo.pyright.wslDistribution
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.wsl.WSLCommandLineOptions
import com.intellij.execution.wsl.WSLDistribution
import com.intellij.execution.wsl.WslPath
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.BaseProjectDirectories.Companion.getBaseDirectories
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.util.io.OSAgnosticPathUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.lsp.api.LspServerDescriptor
import java.net.URI
import java.nio.file.Path


private fun makeFileURI(path: String): String {
val (scheme, host, fragment) = Triple("file", "", null)
return URI(scheme, host, path, fragment).toASCIIString()
}


private val Path.isUncPath: Boolean
get() = WslPath.parseWindowsUncPath(this.toString()) != null


private val URI.pathIsAbsoluteDos: Boolean
get() = OSAgnosticPathUtil.isAbsoluteDosPath(Path.of(this).toString())


private fun Project.getModuleSourceRoots(): Collection<VirtualFile> =
modules.flatMap { module ->
ModuleRootManager.getInstance(module).sourceRoots.asIterable()
Expand Down Expand Up @@ -70,6 +56,8 @@ internal class PyrightServerDescriptor(project: Project, module: Module?, privat
private val wslDistribution by lazy { module?.wslDistribution }

init {
LOGGER.info("Executable: $executable")
LOGGER.info("WSL distro: $wslDistribution")
LOGGER.info(configurations.toString())
}

Expand All @@ -79,7 +67,7 @@ internal class PyrightServerDescriptor(project: Project, module: Module?, privat
override fun getFileUri(file: VirtualFile): String {
return when {
wslDistribution == null -> super.getFileUri(file)
else -> makeFileURI(wslDistribution!!.getWslPath(Path.of(file.path))!!)
else -> wslDistribution!!.getWslPath(Path.of(file.path))!!.asFileURI()
}
}

Expand Down Expand Up @@ -116,11 +104,6 @@ internal class PyrightServerDescriptor(project: Project, module: Module?, privat
wslDistribution?.patchCommandLine(this, project, WSLCommandLineOptions())
}

private fun WSLDistribution?.getPureLinuxOrWindowsPath(path: Path) = when {
this != null && path.isUncPath -> this.getWslPath(path)!!
else -> path.toString()
}

companion object {

private val LOGGER = Logger.getInstance(PyrightServerDescriptor::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
package com.insyncwithfoo.pyright.shared

import com.insyncwithfoo.pyright.configurations.pyrightConfigurations
import com.insyncwithfoo.pyright.interpreterPath
import com.insyncwithfoo.pyright.wslDistribution
import com.insyncwithfoo.pyright.osDependentInterpreterPath
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project


private val Module.osDependentInterpreterPath: String?
get() {
val interpreterPath = this.interpreterPath?.toString()

return when (wslDistribution) {
null -> interpreterPath
else -> interpreterPath?.replace("\\", "/")
}
}


internal fun Project.createLSPSettingsObject(module: Module? = null) = Settings().apply {
val configurations = pyrightConfigurations

Expand Down

0 comments on commit b343cb6

Please sign in to comment.