Skip to content

Commit

Permalink
Merge pull request #454 from koxudaxi/support_ruff_server
Browse files Browse the repository at this point in the history
Support new LSP integration
  • Loading branch information
koxudaxi authored Jun 28, 2024
2 parents 527a63a + 9d18844 commit 02b73f0
Show file tree
Hide file tree
Showing 15 changed files with 94 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## [Unreleased]
- Support new LSP integration [[#454](https://github.com/koxudaxi/ruff-pycharm-plugin/pull/454)]

## [0.0.33] - 2024-04-07

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ See [documentation](https://koxudaxi.github.io/ruff-pycharm-plugin/) for more de
- [x] Support `ruff` config file path as an option
- [x] Detect `ruff` executable in Conda environment
- [x] Detect `ruff` executable in WSL
- [x] Support `ruff-lsp` with [LSP integration](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/) for PyCharm Pro/IDEA Ultimate [Experimental]
- [x] Support the Ruff LSP with [LSP integration](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/) for PyCharm Pro/IDEA Ultimate [Experimental]
- [x] `ruff-lsp` integration
- [x] `ruff server` integration
- [x] Live Config Reload: Automatically updates from `pyproject.toml` and `ruff.toml` without restarting
- [x] Support `ruff format` for ruff version `0.0.289` or later [Experimental]

Expand Down
4 changes: 3 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ See [documentation](https://koxudaxi.github.io/ruff-pycharm-plugin/) for more de
- [x] Support `ruff` config file path as an option
- [x] Detect `ruff` executable in Conda environment
- [x] Detect `ruff` executable in WSL
- [x] Support `ruff-lsp` with [LSP integration](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/) for PyCharm Pro/IDEA Ultimate [Experimental]
- [x] Support the Ruff LSP with [LSP integration](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/) for PyCharm Pro/IDEA Ultimate [Experimental]
- [x] `ruff-lsp` integration
- [x] `ruff server` integration
- [x] Live Config Reload: Automatically updates from `pyproject.toml` and `ruff.toml` without restarting
- [x] Support `ruff format` for ruff version `0.0.289` or later [Experimental]

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup = com.koxudaxi.ruff
pluginName = Ruff
pluginRepositoryUrl = https://github.com/koxudaxi/ruff-pycharm-plugin
# SemVer format -> https://semver.org
pluginVersion = 0.0.33
pluginVersion = 0.0.34

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 233.11799.241
Expand Down
1 change: 1 addition & 0 deletions src/com/koxudaxi/ruff/Ruff.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ val NO_FIX_FORMAT_ARGS = ARGS_BASE + listOf("--no-fix", "--format", "json")
val NO_FIX_OUTPUT_FORMAT_ARGS = ARGS_BASE + listOf("--no-fix", "--output-format", "json")
val FORMAT_ARGS = listOf("format", "--force-exclude", "--quiet")
val FORMAT_CHECK_ARGS = FORMAT_ARGS + listOf("--check")
val LSP_PREVIEW_ARGS = listOf("server", "--preview")
val Project.FIX_ARGS: List<String>
get() = when {
RuffCacheService.hasCheck(this) == true -> CHECK + FIX_ARGS_BASE
Expand Down
14 changes: 11 additions & 3 deletions src/com/koxudaxi/ruff/RuffCacheService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ class RuffCacheService(val project: Project) {
private var hasFormatter: Boolean? = null
private var hasOutputFormat: Boolean? = null
private var hasCheck: Boolean? = null
private var hasLsp: Boolean? = null

fun getVersion(): RuffVersion? {
return version
}
private fun setVersionFromCommand() =
private inline fun setVersionFromCommand(crossinline action: () -> Unit) =
executeOnPooledThread {
val ruffVersion = runRuff(project, listOf("--version"), true)
?.let { getOrPutVersionFromVersionCache(it) }
setVersion(ruffVersion)
action()
}


Expand All @@ -42,14 +44,15 @@ class RuffCacheService(val project: Project) {
hasFormatter = version?.hasFormatter
hasOutputFormat = version?.hasOutputFormat
hasCheck = version?.hasCheck
hasLsp = version?.hasLsp
}

internal fun clearVersion() {
setVersion(null)
}

internal fun setVersion() {
return setVersionFromCommand()
internal inline fun setVersion(crossinline action: () -> Unit) {
return setVersionFromCommand(action)
}

internal fun hasFormatter(): Boolean {
Expand All @@ -64,6 +67,9 @@ class RuffCacheService(val project: Project) {
return hasCheck
}

internal fun hasLsp(): Boolean? {
return hasLsp
}
internal
companion object {
fun hasFormatter(project: Project): Boolean = getInstance(project).hasFormatter()
Expand All @@ -72,6 +78,8 @@ class RuffCacheService(val project: Project) {

fun hasCheck(project: Project): Boolean? = getInstance(project).hasCheck()

fun hasLsp(project: Project): Boolean? = getInstance(project).hasLsp()

internal fun getInstance(project: Project): RuffCacheService {
return project.getService(RuffCacheService::class.java)
}
Expand Down
12 changes: 10 additions & 2 deletions src/com/koxudaxi/ruff/RuffConfigPanel.form
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</properties>
<border type="none"/>
<children>
<grid id="da542" layout-manager="GridLayoutManager" row-count="8" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="da542" layout-manager="GridLayoutManager" row-count="9" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
Expand Down Expand Up @@ -74,12 +74,20 @@
</component>
<component id="d521d" class="javax.swing.JCheckBox" binding="useRuffFormatCheckBox">
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="8" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Use ruff format (Experimental) for version 0.0.289 or later"/>
</properties>
</component>
<component id="36608" class="javax.swing.JCheckBox" binding="useRuffServerCheckBox">
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Use `ruff server` for LSP functionality (if ruff supports the server option, this is preferred over ruff-lsp)."/>
</properties>
</component>
</children>
</grid>
<vspacer id="a33f9">
Expand Down
6 changes: 5 additions & 1 deletion src/com/koxudaxi/ruff/RuffConfigPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class RuffConfigPanel(project: Project) {
private lateinit var disableOnSaveOutsideOfProjectCheckBox: JCheckBox
private lateinit var useRuffLspCheckBox: JCheckBox
private lateinit var useRuffFormatCheckBox: JCheckBox
private lateinit var useRuffServerCheckBox: JCheckBox
init {
val ruffConfigService = getInstance(project)
runRuffOnSaveCheckBox.isSelected = ruffConfigService.runRuffOnSave
Expand All @@ -53,6 +54,8 @@ class RuffConfigPanel(project: Project) {
useRuffLspCheckBox.isSelected = ruffConfigService.useRuffLsp
useRuffFormatCheckBox.isEnabled = true
useRuffFormatCheckBox.isSelected = ruffConfigService.useRuffFormat
useRuffServerCheckBox.isEnabled = true
useRuffServerCheckBox.isSelected = ruffConfigService.useRuffServer
disableOnSaveOutsideOfProjectCheckBox.isSelected = ruffConfigService.disableOnSaveOutsideOfProject
runRuffOnSaveCheckBox.addActionListener {
disableOnSaveOutsideOfProjectCheckBox.isEnabled = runRuffOnSaveCheckBox.isSelected
Expand Down Expand Up @@ -178,7 +181,8 @@ class RuffConfigPanel(project: Project) {
get() = disableOnSaveOutsideOfProjectCheckBox.isSelected
val useRuffLsp: Boolean
get() = useRuffLspCheckBox.isSelected

val useRuffServer: Boolean
get() = useRuffServerCheckBox.isSelected
val useRuffFormat: Boolean
get() = useRuffFormatCheckBox.isSelected

Expand Down
1 change: 1 addition & 0 deletions src/com/koxudaxi/ruff/RuffConfigService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class RuffConfigService : PersistentStateComponent<RuffConfigService> {
var ruffConfigPath: @SystemDependent String? = null
var disableOnSaveOutsideOfProject: Boolean = true
var useRuffLsp: Boolean = false
var useRuffServer: Boolean = false
var useRuffFormat: Boolean = false

override fun getState(): RuffConfigService {
Expand Down
19 changes: 13 additions & 6 deletions src/com/koxudaxi/ruff/RuffConfigurable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class RuffConfigurable internal constructor(val project: Project) : Configurable
ruffConfigService.ruffConfigPath != configPanel.ruffConfigPath ||
ruffConfigService.disableOnSaveOutsideOfProject != configPanel.disableOnSaveOutsideOfProject ||
ruffConfigService.useRuffLsp != configPanel.useRuffLsp ||
ruffConfigService.useRuffFormat != configPanel.useRuffFormat

ruffConfigService.useRuffFormat != configPanel.useRuffFormat ||
ruffConfigService.useRuffServer != configPanel.useRuffServer
}

override fun apply() {
Expand All @@ -50,21 +50,28 @@ class RuffConfigurable internal constructor(val project: Project) : Configurable
ruffConfigService.ruffConfigPath = configPanel.ruffConfigPath
ruffConfigService.disableOnSaveOutsideOfProject = configPanel.disableOnSaveOutsideOfProject
ruffConfigService.useRuffFormat = configPanel.useRuffFormat
ruffCacheService.setVersion()
if (ruffConfigService.useRuffLsp != configPanel.useRuffLsp) {
val configPanelUseRuffLsp = configPanel.useRuffLsp
val configPanelUseRuffServer = configPanel.useRuffServer
if (ruffConfigService.useRuffLsp != configPanelUseRuffLsp || ruffConfigService.useRuffServer != configPanelUseRuffServer) {
ruffCacheService.setVersion{
@Suppress("UnstableApiUsage")
val lspServerManager = if (lspIsSupported) LspServerManager.getInstance(project) else null
if (lspServerManager != null) {
if (configPanel.useRuffLsp) {
if (configPanelUseRuffLsp || configPanelUseRuffServer) {
@Suppress("UnstableApiUsage")
lspServerManager.startServersIfNeeded(RuffLspServerSupportProvider::class.java)
} else {
@Suppress("UnstableApiUsage")
lspServerManager.stopServers(RuffLspServerSupportProvider::class.java)
}
}
ruffConfigService.useRuffLsp = configPanel.useRuffLsp
}
} else {
ruffCacheService.setVersion{}
}

ruffConfigService.useRuffLsp = configPanel.useRuffLsp
ruffConfigService.useRuffServer = configPanel.useRuffServer
}

override fun disposeUIResources() {
Expand Down
3 changes: 2 additions & 1 deletion src/com/koxudaxi/ruff/RuffExternalAnnotator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class RuffExternalAnnotator :
if (file !is PyFile) return null
val project = file.project
val config = RuffConfigService.getInstance(project)
if (config.useRuffLsp) return null
if (config.useRuffLsp && config.ruffLspExecutablePath is String) return null
if (config.useRuffServer && RuffCacheService.hasLsp(project) == true) return null
if (!file.isApplicableTo) return null
val profile: InspectionProfile = InspectionProjectProfileManager.getInstance(project).currentProfile
val key = HighlightDisplayKey.find(RuffInspection.INSPECTION_SHORT_NAME) ?: return null
Expand Down
47 changes: 35 additions & 12 deletions src/com/koxudaxi/ruff/RuffLspServerSupportProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import com.intellij.codeInspection.ex.InspectionToolRegistrar
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.lsp.api.LspServerManager

Check warning on line 5 in src/com/koxudaxi/ruff/RuffLspServerSupportProvider.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused import directive

Unused import directive
import com.intellij.platform.lsp.api.LspServerSupportProvider
import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor
import com.intellij.platform.lsp.api.customization.LspCompletionSupport
Expand All @@ -20,14 +21,27 @@ class RuffLspServerSupportProvider : LspServerSupportProvider {
serverStarter: LspServerSupportProvider.LspServerStarter
) {
val ruffConfigService = RuffConfigService.getInstance(project)
if (!ruffConfigService.useRuffLsp) return
if (!ruffConfigService.useRuffLsp && !ruffConfigService.useRuffServer) return
if (!isInspectionEnabled(project)) return
if (file.extension != "py") return
val executable =
ruffConfigService.ruffLspExecutablePath?.let { File(it) }?.takeIf { it.exists() } ?: detectRuffExecutable(
project, ruffConfigService, true
) ?: return
serverStarter.ensureServerStarted(RuffLspServerDescriptor(project, executable, ruffConfigService))
val ruffCacheService = RuffCacheService.getInstance(project)
if (ruffCacheService.getVersion() == null) return
val descriptor = when {
ruffConfigService.useRuffServer && ruffCacheService.hasLsp() == true -> {
getRuffExecutable(project, ruffConfigService , false)?.let { RuffServerServerDescriptor(project, it, ruffConfigService) }
}
else -> getRuffExecutable(project, ruffConfigService, true)?.let { RuffLspServerDescriptor(project, it, ruffConfigService) }
} ?: return
serverStarter.ensureServerStarted(descriptor)
}

private fun getRuffExecutable(project: Project, ruffConfigService: RuffConfigService, lsp: Boolean): File? {
return when {
lsp -> ruffConfigService.ruffLspExecutablePath
else -> ruffConfigService.ruffExecutablePath
}?.let { File(it) }?.takeIf { it.exists() } ?: detectRuffExecutable(
project, ruffConfigService, lsp
)
}

private fun isInspectionEnabled(project: Project): Boolean {
Expand All @@ -39,13 +53,9 @@ class RuffLspServerSupportProvider : LspServerSupportProvider {
}
}


@Suppress("UnstableApiUsage")
private class RuffLspServerDescriptor(project: Project, val executable: File, val ruffConfig: RuffConfigService) :
ProjectWideLspServerDescriptor(project, "Ruff") {

override fun isSupportedFile(file: VirtualFile) = file.extension == "py"

private class RuffLspServerDescriptor(project: Project, executable: File, ruffConfig: RuffConfigService) :
RuffLspServerDescriptorBase(project, executable, ruffConfig) {
private fun createBaseCommandLine(): GeneralCommandLine = GeneralCommandLine(executable.absolutePath)

override fun createCommandLine(): GeneralCommandLine {
Expand All @@ -55,7 +65,15 @@ private class RuffLspServerDescriptor(project: Project, val executable: File, va
}
return commandLine
}
}


@Suppress("UnstableApiUsage")
abstract class RuffLspServerDescriptorBase(project: Project, val executable: File, val ruffConfig: RuffConfigService) :
ProjectWideLspServerDescriptor(project, "Ruff") {

override fun isSupportedFile(file: VirtualFile) = file.extension == "py"
abstract override fun createCommandLine(): GeneralCommandLine
override fun createInitializationOptions(): Any? {
val config = ruffConfig.ruffConfigPath?.let { File(it) }?.takeIf { it.exists() } ?: return null
return InitOptions(Settings(listOf(CONFIG_ARG, config.absolutePath)))
Expand All @@ -65,6 +83,11 @@ private class RuffLspServerDescriptor(project: Project, val executable: File, va
override val lspCompletionSupport: LspCompletionSupport? = null
}

@Suppress("UnstableApiUsage")
private class RuffServerServerDescriptor(project: Project, executable: File, ruffConfig: RuffConfigService) :
RuffLspServerDescriptorBase(project, executable, ruffConfig) {
override fun createCommandLine(): GeneralCommandLine = GeneralCommandLine(listOf(executable.absolutePath) + LSP_PREVIEW_ARGS)
}
@Serializable
data class Settings(
val args: List<String>
Expand Down
5 changes: 3 additions & 2 deletions src/com/koxudaxi/ruff/RuffPackageManagerListener.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ class RuffPackageManagerListener(project: Project) : PyPackageManager.Listener {
override fun packagesRefreshed(sdk: Sdk) {
ruffConfigService.projectRuffExecutablePath = findRuffExecutableInSDK(sdk, false)?.absolutePath
ruffConfigService.projectRuffLspExecutablePath = findRuffExecutableInSDK(sdk, true)?.absolutePath
ruffCacheService.setVersion()
if (lspServerManager != null && ruffConfigService.useRuffLsp) {
ruffCacheService.setVersion{
if (lspServerManager != null && (ruffConfigService.useRuffLsp || ruffConfigService.useRuffServer)) {
try {
@Suppress("UnstableApiUsage")
lspServerManager.stopAndRestartIfNeeded(RuffLspServerSupportProvider::class.java)
} catch (_: Exception) {
}
}
}
}
}
7 changes: 2 additions & 5 deletions src/com/koxudaxi/ruff/RuffProjectInitializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class RuffProjectInitializer : ProjectActivity {
try {
val ruffCacheService = RuffCacheService.getInstance(project)
if (ruffCacheService.getVersion() == null) {
ruffCacheService.setVersion()
ruffCacheService.setVersion{}
}
if (lspIsSupported) {
setUpPyProjectTomlLister(project)
Expand Down Expand Up @@ -56,11 +56,8 @@ class RuffProjectInitializer : ProjectActivity {
) return
ApplicationManager.getApplication().invokeLater {
if (project.isDisposed) return@invokeLater
if (!ruffConfigService.useRuffLsp) return@invokeLater
if (!ruffConfigService.useRuffLsp && !ruffConfigService.useRuffServer) return@invokeLater
lspServerManager.stopAndRestartIfNeeded(RuffLspServerSupportProvider::class.java)

Check warning on line 60 in src/com/koxudaxi/ruff/RuffProjectInitializer.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unstable API Usage

'stopAndRestartIfNeeded(java.lang.Class)' is declared in unstable 'com.intellij.platform.lsp.api.LspServerManager' marked with @ApiStatus.Experimental

LspServerManager.getInstance(project)
.stopAndRestartIfNeeded(RuffLspServerSupportProvider::class.java)
}
} catch (_: AlreadyDisposedException) {
}
Expand Down
3 changes: 3 additions & 0 deletions src/com/koxudaxi/ruff/RuffVersion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ class RuffVersion(private val major: Int, private val minor: Int = 0, private va
val hasOutputFormat: Boolean get() = this >= SUPPORT_OUTPUT_FORMAT_VERSION

val hasCheck: Boolean get() = this >= SUPPORT_CHECK_VERSION

val hasLsp: Boolean get() = this >= SUPPORT_LSP_VERSION
companion object {
val SUPPORT_FORMAT_VERSION = RuffVersion(0, 0, 289)
val SUPPORT_OUTPUT_FORMAT_VERSION = RuffVersion(0, 0, 291)
val SUPPORT_CHECK_VERSION = RuffVersion(0, 3, 0)
val SUPPORT_LSP_VERSION = RuffVersion(0, 4, 5)

}
}

0 comments on commit 02b73f0

Please sign in to comment.