feat!: LSP misconfiguration warning banner
This commit is contained in:
parent
03a86defb9
commit
05ff125c1d
8 changed files with 149 additions and 33 deletions
|
@ -17,6 +17,11 @@ Changelog structure reference:
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- LSP
|
||||
- Error/Warning banner at the top of the editor when ZLS is misconfigured/not running
|
||||
|
||||
### Fixed
|
||||
|
||||
- Debugging
|
||||
|
|
|
@ -26,8 +26,12 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd
|
|||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
||||
import com.falsepattern.zigbrains.direnv.getDirenv
|
||||
import com.falsepattern.zigbrains.lsp.settings.zlsSettings
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.intellij.ui.EditorNotifications
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class ZLSStartup: ProjectActivity {
|
||||
|
@ -43,5 +47,16 @@ class ZLSStartup: ProjectActivity {
|
|||
project.zlsSettings.state = zlsState
|
||||
}
|
||||
}
|
||||
project.zigCoroutineScope.launch {
|
||||
var currentState = project.zlsRunningAsync()
|
||||
while (!project.isDisposed) {
|
||||
val running = project.zlsRunningAsync()
|
||||
if (currentState != running) {
|
||||
EditorNotifications.getInstance(project).updateAllNotifications()
|
||||
}
|
||||
currentState = running
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,15 +71,43 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS
|
|||
return features
|
||||
}
|
||||
|
||||
override fun isEnabled(project: Project): Boolean {
|
||||
return (project.getUserData(ENABLED_KEY) != false) && project.zlsSettings.validate()
|
||||
}
|
||||
override fun isEnabled(project: Project) = project.zlsEnabledSync()
|
||||
|
||||
override fun setEnabled(enabled: Boolean, project: Project) {
|
||||
project.putUserData(ENABLED_KEY, enabled)
|
||||
project.zlsEnabled(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Project.zlsEnabledAsync(): Boolean {
|
||||
return (getUserData(ENABLED_KEY) != false) && zlsSettings.validateAsync()
|
||||
}
|
||||
|
||||
fun Project.zlsEnabledSync(): Boolean {
|
||||
return (getUserData(ENABLED_KEY) != false) && zlsSettings.validateSync()
|
||||
}
|
||||
|
||||
fun Project.zlsEnabled(value: Boolean) {
|
||||
putUserData(ENABLED_KEY, value)
|
||||
}
|
||||
|
||||
suspend fun Project.zlsRunningAsync(): Boolean {
|
||||
if (!zlsEnabledAsync())
|
||||
return false
|
||||
return zlsRunningLsp4ij()
|
||||
}
|
||||
|
||||
fun Project.zlsRunningSync(): Boolean {
|
||||
if (!zlsEnabledSync())
|
||||
return false
|
||||
return zlsRunningLsp4ij()
|
||||
}
|
||||
|
||||
private fun Project.zlsRunningLsp4ij(): Boolean {
|
||||
val manager = service<LanguageServerManager>()
|
||||
val status = manager.getServerStatus("ZigBrains")
|
||||
return status == ServerStatus.started || status == ServerStatus.starting
|
||||
}
|
||||
|
||||
class ZLSStarter: LanguageServerStarter {
|
||||
override fun startLSP(project: Project, restart: Boolean) {
|
||||
project.zigCoroutineScope.launch {
|
||||
|
@ -87,7 +115,10 @@ class ZLSStarter: LanguageServerStarter {
|
|||
val status = manager.getServerStatus("ZigBrains")
|
||||
if ((status == ServerStatus.started || status == ServerStatus.starting) && !restart)
|
||||
return@launch
|
||||
manager.start("ZigBrains")
|
||||
manager.stop("ZigBrains")
|
||||
if (project.zlsSettings.validateAsync()) {
|
||||
manager.start("ZigBrains")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* This file is part of ZigBrains.
|
||||
*
|
||||
* Copyright (C) 2023-2025 FalsePattern
|
||||
* All Rights Reserved
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* ZigBrains is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, only version 3 of the License.
|
||||
*
|
||||
* ZigBrains is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.lsp.notification
|
||||
|
||||
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
||||
import com.falsepattern.zigbrains.lsp.settings.zlsSettings
|
||||
import com.falsepattern.zigbrains.lsp.zlsRunningSync
|
||||
import com.falsepattern.zigbrains.zig.ZigFileType
|
||||
import com.falsepattern.zigbrains.zon.ZonFileType
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.ui.EditorNotificationPanel
|
||||
import com.intellij.ui.EditorNotificationProvider
|
||||
import java.util.function.Function
|
||||
import javax.swing.JComponent
|
||||
|
||||
class ZigEditorNotificationProvider: EditorNotificationProvider, DumbAware {
|
||||
override fun collectNotificationData(
|
||||
project: Project,
|
||||
file: VirtualFile
|
||||
): Function<in FileEditor, out JComponent?>? {
|
||||
when (file.fileType) {
|
||||
ZigFileType, ZonFileType -> {}
|
||||
else -> return null
|
||||
}
|
||||
if (project.zlsRunningSync()) {
|
||||
return null
|
||||
}
|
||||
return Function { editor ->
|
||||
val status: EditorNotificationPanel.Status
|
||||
val message: String
|
||||
if (!project.zlsSettings.validateSync()) {
|
||||
status = EditorNotificationPanel.Status.Error
|
||||
message = ZLSBundle.message("notification.banner.zls-bad-config")
|
||||
} else {
|
||||
status = EditorNotificationPanel.Status.Warning
|
||||
message = ZLSBundle.message("notification.banner.zls-not-running")
|
||||
}
|
||||
EditorNotificationPanel(editor, status).also { it.text = message }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ package com.falsepattern.zigbrains.lsp.settings
|
|||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
||||
import com.falsepattern.zigbrains.direnv.getDirenv
|
||||
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
||||
import com.falsepattern.zigbrains.lsp.startLSP
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
|
@ -32,9 +33,9 @@ import com.intellij.platform.ide.progress.ModalTaskOwner
|
|||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||
import com.intellij.util.application
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.io.path.isExecutable
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
@ -51,47 +52,45 @@ class ZLSProjectSettingsService(val project: Project): PersistentStateComponent<
|
|||
@Volatile
|
||||
private var valid = false
|
||||
|
||||
private val mutex = ReentrantLock()
|
||||
private val mutex = Mutex()
|
||||
override fun getState(): ZLSSettings {
|
||||
return state.copy()
|
||||
}
|
||||
|
||||
fun setState(value: ZLSSettings) {
|
||||
mutex.withLock {
|
||||
this.state = value
|
||||
dirty = true
|
||||
runBlocking {
|
||||
mutex.withLock {
|
||||
this@ZLSProjectSettingsService.state = value
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
startLSP(project, true)
|
||||
}
|
||||
|
||||
override fun loadState(state: ZLSSettings) {
|
||||
mutex.withLock {
|
||||
this.state = state
|
||||
dirty = true
|
||||
}
|
||||
setState(state)
|
||||
}
|
||||
|
||||
fun isModified(otherData: ZLSSettings): Boolean {
|
||||
return state != otherData
|
||||
}
|
||||
|
||||
fun validate(): Boolean {
|
||||
suspend fun validateAsync(): Boolean {
|
||||
mutex.withLock {
|
||||
if (dirty) {
|
||||
val state = this.state
|
||||
valid = if (application.isDispatchThread) {
|
||||
runWithModalProgressBlocking(ModalTaskOwner.project(project), ZLSBundle.message("progress.title.validate")) {
|
||||
doValidate(project, state)
|
||||
}
|
||||
} else {
|
||||
runBlocking {
|
||||
doValidate(project, state)
|
||||
}
|
||||
}
|
||||
valid = doValidate(project, state)
|
||||
dirty = false
|
||||
}
|
||||
return valid
|
||||
}
|
||||
}
|
||||
|
||||
fun validateSync() = if (application.isDispatchThread) {
|
||||
runWithModalProgressBlocking(ModalTaskOwner.project(project), ZLSBundle.message("progress.title.validate")) {
|
||||
validateAsync()
|
||||
}
|
||||
} else {
|
||||
runBlocking {
|
||||
validateAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doValidate(project: Project, state: ZLSSettings): Boolean {
|
||||
|
|
|
@ -42,11 +42,7 @@ class ZLSSettingsConfigurable(private val project: Project): SubConfigurable {
|
|||
override fun apply() {
|
||||
val data = appSettingsComponent?.data ?: return
|
||||
val settings = project.zlsSettings
|
||||
val reloadZLS = settings.isModified(data)
|
||||
settings.state = data
|
||||
if (reloadZLS) {
|
||||
startLSP(project, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
|
|
|
@ -45,6 +45,10 @@
|
|||
/>
|
||||
<postStartupActivity
|
||||
implementation="com.falsepattern.zigbrains.lsp.ZLSStartup"/>
|
||||
|
||||
<editorNotificationProvider
|
||||
implementation="com.falsepattern.zigbrains.lsp.notification.ZigEditorNotificationProvider"
|
||||
/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
|
||||
|
|
|
@ -59,6 +59,8 @@ notification.message.zls-config-not-exists.content=ZLS config file does not exis
|
|||
notification.message.zls-config-not-file.content=ZLS config file is not a regular file: {0}
|
||||
notification.message.zls-config-path-invalid.content=ZLS config path could not be parted: {0}
|
||||
notification.message.zls-config-autogen-failed.content=Failed to autogenerate ZLS config from toolchain
|
||||
notification.banner.zls-not-running=Zig Language Server is not running. Check the [Language Servers] tool menu!
|
||||
notification.banner.zls-bad-config=Zig Language Server is misconfigured. Check [Settings | Languages \\& Frameworks | Zig]!
|
||||
progress.title.create-connection-provider=Creating ZLS connection provider
|
||||
progress.title.validate=Validating ZLS
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
|
Loading…
Add table
Reference in a new issue