Skip to content

Commit 1b23c9f

Browse files
Fix configurationFile being ignored in Native LSP mode (#222)
Co-authored-by: InSync <insyncwithfoo@gmail.com>
1 parent 89a1fb4 commit 1b23c9f

10 files changed

Lines changed: 128 additions & 25 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.insyncwithfoo.pyright
2+
3+
import com.insyncwithfoo.pyright.configurations.WorkspaceFolders
4+
import com.insyncwithfoo.pyright.configurations.pyrightConfigurations
5+
import com.insyncwithfoo.pyright.configurations.resolveConfigurationFileWorkspaceRoot
6+
import com.intellij.openapi.project.BaseProjectDirectories.Companion.getBaseDirectories
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.roots.ModuleRootManager
9+
import com.intellij.openapi.vfs.LocalFileSystem
10+
import com.intellij.openapi.vfs.VirtualFile
11+
import com.intellij.openapi.vfs.toNioPathOrNull
12+
13+
14+
private fun Project.getModuleSourceRoots(): Collection<VirtualFile> =
15+
modules.flatMap { module ->
16+
ModuleRootManager.getInstance(module).sourceRoots.asIterable()
17+
}
18+
19+
20+
private fun Project.getWorkspaceFolders(type: WorkspaceFolders): Collection<VirtualFile> =
21+
when (type) {
22+
WorkspaceFolders.PROJECT_BASE -> getBaseDirectories()
23+
WorkspaceFolders.SOURCE_ROOTS -> getModuleSourceRoots()
24+
}
25+
26+
27+
internal fun Project.getPyrightWorkspaceFolders(): Collection<VirtualFile> {
28+
val configRoot = resolveConfigurationFileWorkspaceRoot()
29+
30+
if (configRoot != null) {
31+
LocalFileSystem.getInstance().findFileByNioFile(configRoot)?.let { return listOf(it) }
32+
}
33+
34+
return getWorkspaceFolders(pyrightConfigurations.workspaceFolders)
35+
}
36+
37+
38+
internal fun VirtualFile.toFileUriString(): String =
39+
toNioPathOrNull()?.toUri()?.toASCIIString() ?: url

src/main/kotlin/com/insyncwithfoo/pyright/configurations/Configurations.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ internal class PyrightConfigurations : DisplayableState(), Copyable {
9595
var languageServerExecutable by string(null)
9696
var smartLanguageServerExecutableResolution by property(false)
9797
var configurationFile by string(null)
98+
var useConfigurationFileInLSPModes by property(false)
9899
var runningMode by enum(RunningMode.LSP)
99100

100101
var autoRestartServers by property(true)

src/main/kotlin/com/insyncwithfoo/pyright/configurations/Interactions.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import com.insyncwithfoo.pyright.interpreterDirectory
77
import com.insyncwithfoo.pyright.path
88
import com.insyncwithfoo.pyright.toNullIfNotExists
99
import com.insyncwithfoo.pyright.toPathOrNull
10+
import com.intellij.openapi.diagnostic.Logger
1011
import com.intellij.openapi.project.Project
1112
import java.nio.file.Path
1213
import kotlin.io.path.isDirectory
1314
import kotlin.io.path.listDirectoryEntries
1415
import kotlin.io.path.nameWithoutExtension
1516

1617

18+
private val LOGGER = Logger.getInstance("com.insyncwithfoo.pyright.configurations")
19+
1720
private val EXECUTABLE_NAMES = listOf("pyright", "basedpyright")
1821
private val LANGSERVER_EXECUTABLE_NAMES = listOf("pyright-langserver", "basedpyright-langserver")
1922

23+
private val SUPPORTED_LSP_CONFIG_FILE_NAMES = setOf("pyrightconfig.json", "pyproject.toml")
24+
2025

2126
internal fun findPyrightExecutableInPath() =
2227
EXECUTABLE_NAMES.firstNotNullOfOrNull { findExecutableInPath(it) }
@@ -78,6 +83,39 @@ internal val Project.pyrightLangserverExecutable: Path?
7883
}
7984

8085

86+
internal fun Project.resolveConfigurationFileWorkspaceRoot(): Path? {
87+
val configurations = pyrightConfigurations
88+
89+
if (!configurations.useConfigurationFileInLSPModes) {
90+
return null
91+
}
92+
93+
val configFile = configurations.configurationFile?.toPathOrNull() ?: return null
94+
95+
val resolved = when {
96+
configFile.isAbsolute -> configFile
97+
else -> path?.resolve(configFile)
98+
} ?: return null
99+
100+
if (!resolved.toFile().exists()) {
101+
LOGGER.warn("Configuration file does not exist: $resolved")
102+
return null
103+
}
104+
105+
val fileName = resolved.fileName?.toString()
106+
107+
if (fileName != null && fileName !in SUPPORTED_LSP_CONFIG_FILE_NAMES) {
108+
LOGGER.warn(
109+
"Configuration file '$fileName' is not supported in LSP mode. " +
110+
"Only ${SUPPORTED_LSP_CONFIG_FILE_NAMES.joinToString()} are recognized by Pyright's language server."
111+
)
112+
return null
113+
}
114+
115+
return resolved.parent
116+
}
117+
118+
81119
internal fun Project.changePyrightConfigurations(action: PyrightConfigurations.() -> Unit) {
82120
PyrightLocalService.getInstance(this).state.apply(action)
83121
}

src/main/kotlin/com/insyncwithfoo/pyright/configurations/Panels.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ private fun Row.configurationFileInput(block: Cell<TextFieldWithBrowseButton>.()
5858
singleFileTextField().makeFlexible().apply(block)
5959

6060

61+
private fun Row.useConfigurationFileInLSPModesInput(block: Cell<JBCheckBox>.() -> Unit) =
62+
checkBox(message("configurations.useConfigurationFileInLSPModes.label")).apply(block)
63+
64+
6165
private fun Panel.runningModeInputGroup(block: Panel.() -> Unit) =
6266
buttonsGroup(init = block)
6367

@@ -214,6 +218,10 @@ private fun PyrightPanel.makeComponent() = panel {
214218
configurationFileInput { bindText(state::configurationFile.toNonNullableProperty("")) }
215219
overrideCheckbox(state::configurationFile)
216220
}
221+
row("") {
222+
useConfigurationFileInLSPModesInput { bindSelected(state::useConfigurationFileInLSPModes) }
223+
overrideCheckbox(state::useConfigurationFileInLSPModes)
224+
}
217225

218226
val runningModeInputGroup = runningModeInputGroup {
219227
row(message("configurations.runningMode.label")) {

src/main/kotlin/com/insyncwithfoo/pyright/lsp/PyrightServerDescriptor.kt

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package com.insyncwithfoo.pyright.lsp
22

33
import com.insyncwithfoo.pyright.asFileURI
44
import com.insyncwithfoo.pyright.configurations.Locale
5-
import com.insyncwithfoo.pyright.configurations.WorkspaceFolders
65
import com.insyncwithfoo.pyright.configurations.pyrightConfigurations
76
import com.insyncwithfoo.pyright.configurations.targetedFileExtensionList
7+
import com.insyncwithfoo.pyright.getPyrightWorkspaceFolders
88
import com.insyncwithfoo.pyright.getPureLinuxOrWindowsPath
99
import com.insyncwithfoo.pyright.message
1010
import com.insyncwithfoo.pyright.modules
@@ -15,9 +15,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
1515
import com.intellij.execution.wsl.WSLCommandLineOptions
1616
import com.intellij.openapi.diagnostic.Logger
1717
import com.intellij.openapi.module.Module
18-
import com.intellij.openapi.project.BaseProjectDirectories.Companion.getBaseDirectories
1918
import com.intellij.openapi.project.Project
20-
import com.intellij.openapi.roots.ModuleRootManager
2119
import com.intellij.openapi.vfs.VirtualFile
2220
import com.intellij.platform.lsp.api.LspServerDescriptor
2321
import com.intellij.platform.lsp.api.customization.LspCompletionCustomizer
@@ -32,25 +30,8 @@ import java.nio.file.Path
3230
import org.eclipse.lsp4j.ClientCapabilities
3331

3432

35-
private fun Project.getModuleSourceRoots(): Collection<VirtualFile> =
36-
modules.flatMap { module ->
37-
ModuleRootManager.getInstance(module).sourceRoots.asIterable()
38-
}
39-
40-
41-
private fun Project.getWorkspaceFolders(type: WorkspaceFolders): Collection<VirtualFile> =
42-
when (type) {
43-
WorkspaceFolders.PROJECT_BASE -> getBaseDirectories()
44-
WorkspaceFolders.SOURCE_ROOTS -> getModuleSourceRoots()
45-
}
46-
47-
48-
private fun Project.getWorkspaceFolders(): Collection<VirtualFile> =
49-
getWorkspaceFolders(pyrightConfigurations.workspaceFolders)
50-
51-
5233
internal class PyrightServerDescriptor(project: Project, module: Module?, private val executable: Path) :
53-
LspServerDescriptor(project, getPresentableName(project, module), *project.getWorkspaceFolders().toTypedArray()) {
34+
LspServerDescriptor(project, getPresentableName(project, module), *project.getPyrightWorkspaceFolders().toTypedArray()) {
5435

5536
private val configurations = project.pyrightConfigurations
5637

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.insyncwithfoo.pyright.lsp4ij
2+
3+
import com.insyncwithfoo.pyright.getPyrightWorkspaceFolders
4+
import com.insyncwithfoo.pyright.toFileUriString
5+
import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures
6+
import org.eclipse.lsp4j.InitializeParams
7+
import org.eclipse.lsp4j.WorkspaceFolder
8+
9+
10+
@Suppress("UnstableApiUsage")
11+
internal class PyrightClientFeatures : LSPClientFeatures() {
12+
13+
override fun initializeParams(params: InitializeParams) {
14+
val workspaceRoots = project.getPyrightWorkspaceFolders()
15+
val workspaceFolders = workspaceRoots.map { folder ->
16+
WorkspaceFolder(folder.toFileUriString(), folder.name)
17+
}
18+
19+
params.workspaceFolders = workspaceFolders
20+
21+
@Suppress("DEPRECATION")
22+
if (workspaceRoots.size == 1) {
23+
val root = workspaceRoots.single()
24+
params.rootUri = root.toFileUriString()
25+
params.rootPath = root.path
26+
} else {
27+
params.rootUri = null
28+
params.rootPath = null
29+
}
30+
}
31+
32+
}

src/main/kotlin/com/insyncwithfoo/pyright/lsp4ij/PyrightServerConnectionProvider.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.insyncwithfoo.pyright.lsp4ij
33
import com.insyncwithfoo.pyright.configurations.Locale
44
import com.insyncwithfoo.pyright.configurations.pyrightConfigurations
55
import com.insyncwithfoo.pyright.configurations.pyrightLangserverExecutable
6+
import com.insyncwithfoo.pyright.configurations.resolveConfigurationFileWorkspaceRoot
67
import com.insyncwithfoo.pyright.path
78
import com.intellij.openapi.project.Project
89
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider
@@ -20,7 +21,8 @@ internal class PyrightServerConnectionProvider(
2021
val executable = project.pyrightLangserverExecutable!!
2122

2223
val commands: List<String> = listOf(executable.toString(), "--stdio")
23-
val workingDirectory = project.path?.toString()
24+
val configRoot = project.resolveConfigurationFileWorkspaceRoot()
25+
val workingDirectory = configRoot?.toString() ?: project.path?.toString()
2426

2527
val environmentVariables = when {
2628
configurations.locale == Locale.DEFAULT -> emptyMap()

src/main/kotlin/com/insyncwithfoo/pyright/lsp4ij/PyrightServerFactory.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import com.intellij.openapi.project.Project
1010
import com.redhat.devtools.lsp4ij.LanguageServerEnablementSupport
1111
import com.redhat.devtools.lsp4ij.LanguageServerFactory
1212
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl
13-
import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures
1413
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider
1514

1615

@@ -47,7 +46,7 @@ internal class PyrightServerFactory : LanguageServerFactory, LanguageServerEnabl
4746
}
4847

4948
@Suppress("UnstableApiUsage")
50-
override fun createClientFeatures() = LSPClientFeatures().apply {
49+
override fun createClientFeatures() = PyrightClientFeatures().apply {
5150
hoverFeature = HoverFeature()
5251
diagnosticFeature = DiagnosticFeature()
5352
completionFeature = CompletionFeature()

src/main/resources/messages/pyright.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ configurations.smartLanguageServerExecutableResolution.label = \
2020
Resolve against interpreter directory, ignoring extension
2121

2222
configurations.configurationFile.label = Configuration file:
23+
configurations.useConfigurationFileInLSPModes.label = \
24+
Use configuration file location as workspace root in LSP modes
2325

2426
configurations.runningMode.label = Running mode:
2527
configurations.runningMode.commandLine = Command line

src/test/kotlin/com/insyncwithfoo/pyright/configurations/PyrightConfigurationsTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ internal class PyrightConfigurationsTest : ConfigurationsTest<PyrightConfigurati
1111

1212
@Test
1313
fun `test shape`() {
14-
assertEquals(33, fields.size)
14+
assertEquals(34, fields.size)
1515

1616
state.apply {
1717
assertEquals(null, executable)
1818
assertEquals(false, smartExecutableResolution)
1919
assertEquals(null, languageServerExecutable)
2020
assertEquals(false, smartLanguageServerExecutableResolution)
2121
assertEquals(null, configurationFile)
22+
assertEquals(false, useConfigurationFileInLSPModes)
2223
assertEquals(RunningMode.LSP, runningMode)
2324

2425
assertEquals(true, autoRestartServers)

0 commit comments

Comments
 (0)