backport: 12.0.0

ci: 12.0.0

(cherry picked from commit 2f80528cb46ee5a13dd5cb960d361c41d62c7e28)
(cherry picked from commit 87d7db94410dd30be154e585138498c67c262db8)

chore: Isolate C++ toolchain into separate package to fix verifier error

(cherry picked from commit c393120bf24c40d5fc5e8ce41dacc560bcb29ae8)

chore: Forgot to bump gradle version in properties

(cherry picked from commit b8639b0e8dc7d4e8177339d5466d98af9b87c900)

fix(zig): Make go to references non-blocking

(cherry picked from commit a1cee2b1ea399776f5d4bbf33c2403a9c4bf9b03)

feat(zig)!: Go to declaration/usages and go to definition are now separate actions

(cherry picked from commit 18e130cc52e78b69dfdd08e5f160e82d3215deb2)

fix(zig): Refresh syntax highlighting after running code edits

(cherry picked from commit 64eba369d61073f1dd57c999449e9ee7b914bd49)

chore: Cleanup dependencies

(cherry picked from commit baabbb030dc8ad729f5a1f82c523c1d6da27489b)

docs: Extra information about module tree

(cherry picked from commit 12ad175f510124353fd9cc6b8994355e44965161)

feat(zig)!: Autogenerate zls config if not specified, based on project toolchain

(cherry picked from commit 59a56b67646b0253734130a84d8d9d825effe114)

feat(debugger): Library frame filter

(cherry picked from commit 4e3336add808801097acc792fa3e899b26cbdaee)

docs: update changelog

(cherry picked from commit 7db1c621288ba1cac25e85b682d981fd8cc2d4b8)

feat!: Reimplemented go to declaration/usages to replace the built-in action

Also removed mouse handling, no longer needed

(cherry picked from commit 6f481ac844f80701f22d9383a3ff228ea3ee440d)

feat(debugger): Detect C++ debugger toolchains

(cherry picked from commit 6df7cb6dc059c622a0e06ace38558c8b495bd91e)

fix(zig): Add proper lang key for notification group

(cherry picked from commit b8f64ac0062847279b6c85b00083711900e8f7cf)

chore(buildscript): Update gradle and gradle plugins

(cherry picked from commit 45ab2d9bb7834be9bc2914f962d3a21bef494fbd)

fix(lsp): Force always creating a new DocumentEventManager

34b29ee729
(cherry picked from commit 59c1f4612d353de3230419d005206dada4900a7f)

fix(lsp): unregisterManager method's cleanup

bc9c5ea31c
(cherry picked from commit 14a1a9d79f09573d3d7c8cfb27bb008cb63ca38b)

chore(lsp): Extract shared logic

75a5fd8919
(cherry picked from commit 857a0224897e27968fa03f93b0f9a02bc109f0b0)

fix(lsp): Remove duplicated changedConfiguration calls

9b2b0557c9
(cherry picked from commit ceb347d8723130c8ee4097a78fed5f14615805ee)

chore(lsp): Small code cleanup

8c1e6736df
(cherry picked from commit 1e1b4aaaeafabfe76abad22cc18aa51629098012)

fix(lsp): Code action annotations lose range

00bbd6ff45
(cherry picked from commit d920fa37f32549d7d7419fa07e478b94cc8ae8ba)

fix(lsp): Request code actions immediately after diagnostics arrive

0fe2cf98fe
(cherry picked from commit 36138213ba878eaf97e9c7f0642b9927672d2e59)
This commit is contained in:
FalsePattern 2024-02-29 20:42:34 +01:00
parent a026396072
commit 96163ae81e
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
44 changed files with 847 additions and 601 deletions

View file

@ -18,6 +18,30 @@ Changelog structure reference:
## [Unreleased]
## [12.0.0]
### Added
- Debugger
- Now uses the toolchains you set in Settings | Build, Execution, Deployment | Toolchains
- Standard library stack frames are now automatically filtered from the debug stack trace
- Zig
- Go to Declaration/Usages now functions as expected, taking you to the declaration of a symbol instead of its resolved
implementation.
- For the time being, the "Quick Definition" (CTRL+Shift+I) action has been repurposed as Go To Definition. This will be
replaced with a properly integrated solution once a way to couple the PSI symbol system and the LSP has been found.
### Fixed
- LSP
- Diagnostics race condition
- Code action annotations no longer lose range
- Zig
- Syntax highlighting no longer breaks after refactoring or reformatting
- Go to Usages no longer freezes the IDE
## [11.1.0]
### Changed

View file

@ -2,8 +2,18 @@ For now this project is still pretty small, but here are some general guidelines
- This project ships with a code style config in .idea, your IDE should automatically apply it when you pull the repo.
When making pull requests, please try to keep to this style as much as possible.
- Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), and scope as much as you can.
- The project has support for the PSI tree, but try to keep "intelligent" behaviour out of it, all code inspection and
other semantics-aware help should come from ZLS where possible.
- Generally, if the answer to "Can i write some glue code that can get this info from ZLS?" is YES, do not use the PSI
tree.
- Do not edit the relationship tree between modules without unanimous agreement from the project lead.
If you want an "upstream" module (for instance, the LSP) receive data from a "downstream" module
(for instance, the project module), you should use service providers, dependency injections, or other ways to implement
the dataflow in a way where the upstream module is not directly aware of downstream modules.
The main purpose of this is to avoid any circular dependencies, which could cause proprietary IDE-only features
(for instance, the CLion debugger module) to be depended on by FOSS modules. This restriction is non-negotiable.
- Any complex language inspection, syntax action, or similar, should be done by ZLS, and ZigBrains will just act as an
adapter for these features. This notion of "complexity" is determined by the project maintainer. Open an issue
that explains the feature before you start implementing anything!

44
MODULE_TREE.md Normal file
View file

@ -0,0 +1,44 @@
## What is this?
This document describes the relationships between the different "modules" of ZigBrains.
These relationships are strictly enforced, and modules that do not directly (or indirectly) depend on another
module cannot access their classes.
The primary purpose of this strictly enforced module system is to avoid code compatible with open source IDEs (IDEA/PyCharm Community)
from depending on code that only works on proprietary IDEs (IDEA Ultimate/CLion/...).
NOTE: These "modules" are in no way related to the module system introduced in Java 9. They're just called the same.
The suffix after the module name signifies which IDE it depends on.
- IC: IDEA Community
- CL: CLion
- EXT: External maven library
IC modules MUST NOT depend on CL modules, as this violates the restrictions set forth above.
## Modules
### Common (IC)
### LSP (IC)
- LSP4J (EXT)
- Flexmark (EXT)
- Apache Commons Lang 3 (EXT)
### Zig (IC)
- Grammarkit (EXT)
- Common (IC)
- LSP (IC)
### Project (IC)
- Common (IC)
- Zig (IC)
### Zon (IC)
- Grammarkit (EXT)
- Common (IC)
### Debugger (CL)
- Zig (IC)
- Project (IC)

View file

@ -80,7 +80,17 @@ LSP server is running.
## Debugging
Currently, the debugger only works with the bundled LLDB debugger, so make sure you have that.
ZigBrains uses the CLion C++ toolchains (Settings | Build, Execution, Deployment | Toolchains) for debugging purposes,
and it is fully compatible with both GDB and LLDB debuggers.
Additionally, ZigBrains will prioritize a toolchain if it is called `Zig`, otherwise it will use the default toolchain.
If no toolchain is available, ZigBrains will attempt to use the bundled LLDB debugger, and if that is not available either,
an error popup will be shown when you try to run with debugging.
Note: There is a small issue with the LLDB debugger which does not happen with GDB: The debugger will pause on the first
instruction (usually, deep inside the zig standard library's startup code). Unfortunately, we have not found a fix for
this yet, but fortunately it doesn't break anything, just a bit of inconvenience.
## Feature tracker:
@ -96,7 +106,7 @@ Currently, the debugger only works with the bundled LLDB debugger, so make sure
- Hover documentation
- Go to implementations / find usages
- Brace/Parenthesis/Bracket matching
- Breakpoints (CLion/IDEA Ultimate)
- Debugging (CLion/CLion Nova)
- File creation prompt
- Gutter launch buttons
- Commenter (thanks @MarioAriasC !)
@ -117,36 +127,6 @@ Currently, the debugger only works with the bundled LLDB debugger, so make sure
- Debugging (CLion/IDEA Ultimate)
- Project generation (thanks @JensvandeWiel !)
## The motivation
The other existing Zig language plugins for IntelliJ rely a lot on the PSI tree.
This seems correct in theory, until
the sheer power of Zig's comptime is taken into consideration.
The comptime makes any sort of contextual help implemented with the PSI tree a lot more restrictive,
and adding LSP integration at that point is an uphill battle.
## Current state of the project
This project takes the opposite approach: The initial implementation *completely* relies on ZLS, with no lexer or parser
in sight.
Using a language server immediately gives us access to advanced features such as refactoring, go to definition,
semantics-based highlighting, and so on.
However, this also restricts the amount of IDE integration the language plugin can achieve,
and things like live previews, peek definition, go to usage previews, and many other features that deeply integrate with
the PSI system just don't work at all.
## Long-term plans
The first and foremost goal of this project is deeply integrating ZLS into the IDE,
and LSP-provided information *always* takes the first seat.
However, we must also not completely reject the PSI tree,
as it has its own merits when used wisely, such as basic "dumb mode" syntax highlighting,
proper caret placements with go to usages, and so on.
Thus, this project will still use PSI trees and the IntelliJ lexer/parser system, but with heavy moderation, and any
sort of "smart inspection" *shall not* be implemented in the PSI, but instead retrieved from the language server.
## Licenses
<p>

View file

@ -12,9 +12,9 @@ fun environment(key: String) = providers.environmentVariable(key)
plugins {
id("java") // Java support
id("java-library")
id("org.jetbrains.intellij") version("1.17.0")
id("org.jetbrains.intellij") version("1.17.2")
id("org.jetbrains.changelog") version("2.2.0")
id("org.jetbrains.grammarkit") version("2022.3.2.1")
id("org.jetbrains.grammarkit") version("2022.3.2.2")
}
val grammarKitGenDir = "build/generated/sources/grammarkit/java"
@ -58,8 +58,6 @@ allprojects {
maven("https://cache-redirector.jetbrains.com/intellij-dependencies")
}
dependencies {
compileOnly("org.jetbrains:annotations:24.1.0")
compileOnly("org.projectlombok:lombok:1.18.30")
annotationProcessor("org.projectlombok:lombok:1.18.30")
}
@ -116,7 +114,7 @@ allprojects {
purgeOldFiles = true
}
generateParser {
targetRoot = "${grammarKitGenDir}/parser"
targetRootOutputDir = file("${grammarKitGenDir}/parser")
purgeOldFiles = true
}
@ -171,7 +169,7 @@ project(":lsp") {
plugin("java-library")
}
dependencies {
api("org.eclipse.lsp4j:org.eclipse.lsp4j:0.21.2")
api("org.eclipse.lsp4j:org.eclipse.lsp4j:0.22.0")
implementation("com.vladsch.flexmark:flexmark:0.64.8")
api("org.apache.commons:commons-lang3:3.14.0")
}
@ -186,8 +184,7 @@ project(":zig") {
generateLexer {
enabled = true
sourceFile = file("src/main/grammar/Zig.flex")
targetDir = "${grammarKitGenDir}/lexer/${rootPackagePath}/zig/lexer"
targetClass = "ZigFlexLexer"
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zig/lexer")
}
generateParser {
@ -219,8 +216,7 @@ project(":zon") {
generateLexer {
enabled = true
sourceFile = file("src/main/grammar/Zon.flex")
targetDir = "${grammarKitGenDir}/lexer/${rootPackagePath}/zon/lexer"
targetClass = "ZonFlexLexer"
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zon/lexer")
}
generateParser {

View file

@ -2,7 +2,7 @@ pluginGroup = com.falsepattern.zigbrains
pluginName = ZigBrains
pluginRepositoryUrl = https://github.com/FalsePattern/ZigBrains
# SemVer format -> https://semver.org
pluginVersion = 11.1.0
pluginVersion = 12.0.0
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 231
@ -14,7 +14,7 @@ ideaVersion = IC-2023.1.5
clionVersion = CL-2023.1.5
# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 8.2.1
gradleVersion = 8.6
# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
org.gradle.caching = true

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View file

@ -0,0 +1,58 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.cpp;
import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionGDBDriverConfiguration;
import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionLLDBDriverConfiguration;
import com.jetbrains.cidr.cpp.toolchains.CPPToolchains;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.Nullable;
public class CPPDebuggerDriverProvider implements DebuggerDriverProvider {
private static final Logger LOG = Logger.getInstance(CPPDebuggerDriverProvider.class);
@Override
public @Nullable DebuggerDriverConfiguration getDebuggerConfiguration(Project project) {
val toolchains = CPPToolchains.getInstance();
var toolchain = toolchains.getToolchainByNameOrDefault("Zig");
if (toolchain == null || !isDebuggerSupported(toolchain)) {
LOG.info("Couldn't find debug-compatible C++ toolchain with name \"Zig\"");
toolchain = toolchains.getDefaultToolchain();
}
if (toolchain == null || !isDebuggerSupported(toolchain)) {
LOG.info("Couldn't find debug-compatible C++ default toolchain");
return null;
}
return switch (toolchain.getDebuggerKind()) {
case CUSTOM_GDB, BUNDLED_GDB -> new CLionGDBDriverConfiguration(project, toolchain);
case BUNDLED_LLDB -> new CLionLLDBDriverConfiguration(project, toolchain);
};
}
private static boolean isDebuggerSupported(CPPToolchains.Toolchain toolchain) {
try {
toolchain.getDebugger();
return true;
} catch (Exception ignored) {
return false;
}
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugbridge;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.UserDataHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.stream.Stream;
/**
* getDebuggerConfiguration should return com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration, it's UserDataHolder here to
* avoid a plugin verifier error.
*/
public interface DebuggerDriverProvider {
ExtensionPointName<DebuggerDriverProvider> EXTENSION_POINT_NAME = ExtensionPointName.create("com.falsepattern.zigbrains.debuggerDriverProvider");
static @NotNull Stream<UserDataHolder> findDebuggerConfigurations(Project project) {
return EXTENSION_POINT_NAME.getExtensionList()
.stream()
.map(it -> it.getDebuggerConfiguration(project))
.filter(Objects::nonNull);
}
@Nullable UserDataHolder getDebuggerConfiguration(Project project);
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger;
import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.project.Project;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.Nullable;
public class Utils {
public static @Nullable DebuggerDriverConfiguration getDebuggerConfiguration(Project project) {
val providedDebugger = DebuggerDriverProvider.findDebuggerConfigurations(project)
.filter(x -> x instanceof DebuggerDriverConfiguration)
.map(x -> (DebuggerDriverConfiguration)x)
.findFirst()
.orElse(null);
if (providedDebugger != null)
return providedDebugger;
if (LLDBDriverConfiguration.hasBundledLLDB()) {
Notifications.Bus.notify(new Notification("ZigBrains.Debugger.Warn",
"Couldn't find a working debug toolchain, using bundled LLDB debugger!",
NotificationType.WARNING));
return new LLDBDriverConfiguration();
} else {
return null;
}
}
}

View file

@ -21,14 +21,16 @@ import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.RunParameters;
import com.jetbrains.cidr.execution.TrivialInstaller;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@RequiredArgsConstructor
public class ZigDebugRunParameters extends RunParameters {
private final GeneralCommandLine cmd;
private final DebuggerDriverConfiguration driverConfiguration;
@Override
public @NotNull Installer getInstaller() {
return new TrivialInstaller(cmd);
@ -36,11 +38,7 @@ public class ZigDebugRunParameters extends RunParameters {
@Override
public @NotNull DebuggerDriverConfiguration getDebuggerDriverConfiguration() {
if (LLDBDriverConfiguration.hasBundledLLDB()) {
return new LLDBDriverConfiguration();
} else {
throw new IllegalStateException("The bundled LLDB debugger is missing from your IDE!");
}
return driverConfiguration;
}
@Override

View file

@ -24,6 +24,9 @@ import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.process.ProcessTerminatedListener;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.xdebugger.XDebugProcess;
import com.intellij.xdebugger.XDebugProcessStarter;
import com.intellij.xdebugger.XDebugSession;
@ -32,6 +35,8 @@ import lombok.val;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class ZigDebugRunnerBase extends ZigExecutableRunner {
public ZigDebugRunnerBase() {
super(DefaultDebugExecutor.EXECUTOR_ID, "Unable to run Zig debugger");
@ -40,17 +45,24 @@ public class ZigDebugRunnerBase extends ZigExecutableRunner {
@Override
protected RunContentDescriptor showRunContent(ZigRunExecutionConfigurationRunProfileState state, ExecutionEnvironment environment, GeneralCommandLine runExecutable)
throws ExecutionException {
val runParameters = new ZigDebugRunParameters(runExecutable);
return XDebuggerManager.getInstance(environment.getProject())
.startSession(environment, new XDebugProcessStarter() {
@Override
public @NotNull XDebugProcess start(@NotNull XDebugSession session) throws ExecutionException {
val process = new ZigLocalDebugProcess(runParameters, session, state.getConsoleBuilder());
ProcessTerminatedListener.attach(process.getProcessHandler(), environment.getProject());
process.start();
return process;
}
}).getRunContentDescriptor();
val project = environment.getProject();
val debuggerDriver = Utils.getDebuggerConfiguration(project);
if (debuggerDriver == null) {
Notifications.Bus.notify(new Notification("ZigBrains.Debugger.Error", "Couldn't find a working GDB or LLDB debugger! Please check your Toolchains! (Settings | Build, Execution, Deployment | Toolchains)", NotificationType.ERROR));
return null;
}
val runParameters = new ZigDebugRunParameters(runExecutable, debuggerDriver);
val manager = XDebuggerManager.getInstance(project);
return manager.startSession(environment,
new XDebugProcessStarter() {
@Override
public @NotNull XDebugProcess start(@NotNull XDebugSession session) throws ExecutionException {
val process = new ZigLocalDebugProcess(runParameters, session, state.getConsoleBuilder());
ProcessTerminatedListener.attach(process.getProcessHandler(), environment.getProject());
process.start();
return process;
}
}).getRunContentDescriptor();
}
@Override

View file

@ -29,11 +29,4 @@ public class ZigLocalDebugProcess extends CidrLocalDebugProcess {
throws ExecutionException {
super(parameters, session, consoleBuilder, (project) -> Filter.EMPTY_ARRAY, false);
}
@Override
public boolean isLibraryFrameFilterSupported() {
return false;
}
}

View file

@ -0,0 +1,23 @@
<!--
~ Copyright 2023-2024 FalsePattern
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<idea-plugin package="com.falsepattern.zigbrains.zig.cpp">
<depends>com.intellij.modules.clion</depends>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<debuggerDriverProvider implementation="com.falsepattern.zigbrains.zig.cpp.CPPDebuggerDriverProvider"/>
</extensions>
</idea-plugin>

View file

@ -16,10 +16,20 @@
<idea-plugin package="com.falsepattern.zigbrains.zig.debugger">
<depends>com.intellij.modules.cidr.debugger</depends>
<resource-bundle>zigbrains.zig.debugger.Bundle</resource-bundle>
<extensions defaultExtensionNs="com.intellij">
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.ZigDebugRunner"
id="ZigDebugRunner"/>
<notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.debugger.Bundle"
key="notif-debug-error"
id="ZigBrains.Debugger.Error"/>
<notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.debugger.Bundle"
key="notif-debug-warn"
id="ZigBrains.Debugger.Warn"/>
</extensions>
<extensions defaultExtensionNs="cidr.debugger">

View file

@ -0,0 +1,2 @@
notif-debug-warn=ZigBrains debugger warning
notif-debug-error=ZigBrains debugger error

View file

@ -0,0 +1,62 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.requests.ReformatHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class LSPGotoDeclarationAction extends GotoDeclarationAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null || project == null) {
super.actionPerformed(e);
return;
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null || !IntellijLanguageClient.isExtensionSupported(file.getVirtualFile())) {
super.actionPerformed(e);
return;
}
EditorEventManager manager = EditorEventManagerBase.forEditor(editor);
if (manager == null) {
super.actionPerformed(e);
return;
}
val offset = editor.getCaretModel().getOffset();
val psiElement = file.findElementAt(offset);
if (psiElement == null) {
super.actionPerformed(e);
return;
}
manager.gotoDeclarationOrUsages(psiElement);
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.intellij.codeInsight.hint.actions.ShowImplementationsAction;
import com.intellij.codeInsight.navigation.CtrlMouseAction;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class LSPGotoDefinitionAction extends ShowImplementationsAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null || project == null) {
super.actionPerformed(e);
return;
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null || !IntellijLanguageClient.isExtensionSupported(file.getVirtualFile())) {
super.actionPerformed(e);
return;
}
EditorEventManager manager = EditorEventManagerBase.forEditor(editor);
if (manager == null) {
super.actionPerformed(e);
return;
}
val offset = editor.getCaretModel().getOffset();
val psiElement = file.findElementAt(offset);
if (psiElement == null) {
super.actionPerformed(e);
return;
}
manager.gotoDefinition(psiElement);
}
}

View file

@ -24,6 +24,8 @@ import com.intellij.find.findUsages.FindUsagesOptions;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.project.DumbAwareAction;
@ -41,6 +43,8 @@ import com.intellij.usages.UsageInfo2UsageAdapter;
import com.intellij.usages.UsageTarget;
import com.intellij.usages.UsageViewManager;
import com.intellij.usages.UsageViewPresentation;
import com.intellij.util.concurrency.AppExecutorUtil;
import lombok.val;
import javax.swing.JLabel;
import java.awt.Color;
@ -63,8 +67,8 @@ public class LSPReferencesAction extends DumbAwareAction {
}
List<PsiElement2UsageTargetAdapter> targets = new ArrayList<>();
Pair<List<PsiElement>, List<VirtualFile>> references = eventManager
.references(editor.getCaretModel().getCurrentCaret().getOffset());
if (references.first != null && references.second != null) {
.references(editor.getCaretModel().getCurrentCaret().getOffset(), true, true);
if (references.first != null) {
references.first.forEach(element -> targets.add(new PsiElement2UsageTargetAdapter(element, true)));
}
showReferences(editor, targets, editor.getCaretModel().getCurrentCaret().getLogicalPosition());
@ -72,13 +76,22 @@ public class LSPReferencesAction extends DumbAwareAction {
}
public void forManagerAndOffset(EditorEventManager manager, int offset) {
List<PsiElement2UsageTargetAdapter> targets = new ArrayList<>();
Pair<List<PsiElement>, List<VirtualFile>> references = manager.references(offset);
if (references.first != null && references.second != null) {
references.first.forEach(element -> targets.add(new PsiElement2UsageTargetAdapter(element, true)));
}
Editor editor = manager.editor;
showReferences(editor, targets, editor.offsetToLogicalPosition(offset));
ReadAction.nonBlocking(() -> {
val references = manager.references(offset, true, true);
val targets = new ArrayList<PsiElement2UsageTargetAdapter>();
if (references.first != null) {
references.first.forEach(element -> targets.add(new PsiElement2UsageTargetAdapter(element, true)));
}
return targets;
})
.expireWhen(() -> manager.editor.isDisposed())
.finishOnUiThread(ModalityState.NON_MODAL, targets -> {
val editor = manager.editor;
if (editor.isDisposed())
return;
showReferences(editor, targets, editor.offsetToLogicalPosition(offset));
})
.submit(AppExecutorUtil.getAppExecutorService());
}
private void showReferences(Editor editor, List<PsiElement2UsageTargetAdapter> targets, LogicalPosition position) {
@ -108,7 +121,7 @@ public class LSPReferencesAction extends DumbAwareAction {
UsageViewPresentation presentation = createPresentation(targets.get(0).getElement(),
new FindUsagesOptions(editor.getProject()), false);
UsageViewManager.getInstance(project)
.showUsages(new UsageTarget[] { targets.get(0) }, usages.toArray(new Usage[usages.size()]),
.showUsages(new UsageTarget[0], usages.toArray(new Usage[0]),
presentation);
}
}

View file

@ -43,19 +43,20 @@ public class LSPReformatAction extends ReformatCodeAction implements DumbAware {
Project project = e.getData(CommonDataKeys.PROJECT);
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null || project == null) {
super.actionPerformed(e);
return;
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (IntellijLanguageClient.isExtensionSupported(file.getVirtualFile())) {
ApplicationUtils.writeAction(() -> FileDocumentManager.getInstance().saveDocument(editor.getDocument()));
// if editor hasSelection, only reformat selection, not reformat the whole file
if (editor.getSelectionModel().hasSelection()) {
ReformatHandler.reformatSelection(editor);
} else {
ReformatHandler.reformatFile(editor);
}
} else {
if (file == null || !IntellijLanguageClient.isExtensionSupported(file.getVirtualFile())) {
super.actionPerformed(e);
return;
}
ApplicationUtils.writeAction(() -> FileDocumentManager.getInstance().saveDocument(editor.getDocument()));
// if editor hasSelection, only reformat selection, not reformat the whole file
if (editor.getSelectionModel().hasSelection()) {
ReformatHandler.reformatSelection(editor);
} else {
ReformatHandler.reformatFile(editor);
}
}

View file

@ -33,6 +33,7 @@ import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DeclarationParams;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
@ -572,6 +573,21 @@ public class DefaultRequestManager implements RequestManager {
return null;
}
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> declaration(DeclarationParams params) {
if (checkStatus()) {
try {
return Optional.ofNullable(serverCapabilities.getDefinitionProvider())
.map(e -> e.getLeft() || e.getRight() != null).orElse(false) ?
textDocumentService.declaration(params) : null;
} catch (Exception e) {
crashed(e);
return null;
}
}
return null;
}
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
if (checkStatus()) {

View file

@ -28,8 +28,6 @@ import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager;
import com.falsepattern.zigbrains.lsp.listeners.DocumentListenerImpl;
import com.falsepattern.zigbrains.lsp.listeners.EditorMouseListenerImpl;
import com.falsepattern.zigbrains.lsp.listeners.EditorMouseMotionListenerImpl;
import com.falsepattern.zigbrains.lsp.listeners.LSPCaretListenerImpl;
import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
@ -67,6 +65,7 @@ import org.eclipse.lsp4j.DidChangeWatchedFilesCapabilities;
import org.eclipse.lsp4j.DocumentHighlightCapabilities;
import org.eclipse.lsp4j.ExecuteCommandCapabilities;
import org.eclipse.lsp4j.FoldingRangeCapabilities;
import org.eclipse.lsp4j.FoldingRangeKind;
import org.eclipse.lsp4j.FoldingRangeKindSupportCapabilities;
import org.eclipse.lsp4j.FoldingRangeSupportCapabilities;
import org.eclipse.lsp4j.FormattingCapabilities;
@ -355,30 +354,24 @@ public class LanguageServerWrapper {
//Todo - Implement
// SelectionListenerImpl selectionListener = new SelectionListenerImpl();
DocumentListenerImpl documentListener = new DocumentListenerImpl();
EditorMouseListenerImpl mouseListener = new EditorMouseListenerImpl();
EditorMouseMotionListenerImpl mouseMotionListener = new EditorMouseMotionListenerImpl();
LSPCaretListenerImpl caretListener = new LSPCaretListenerImpl();
ServerOptions serverOptions = new ServerOptions(capabilities);
EditorEventManager manager;
if (extManager != null) {
manager = extManager.getExtendedEditorEventManagerFor(editor, documentListener,
mouseListener, mouseMotionListener, caretListener, requestManager, serverOptions,
caretListener, requestManager, serverOptions,
this);
if (manager == null) {
manager = new EditorEventManager(editor, documentListener, mouseListener,
mouseMotionListener, caretListener,
manager = new EditorEventManager(editor, documentListener, caretListener,
requestManager, serverOptions, this);
}
} else {
manager = new EditorEventManager(editor, documentListener, mouseListener,
mouseMotionListener, caretListener,
manager = new EditorEventManager(editor, documentListener, caretListener,
requestManager, serverOptions, this);
}
// selectionListener.setManager(manager);
documentListener.setManager(manager);
mouseListener.setManager(manager);
mouseMotionListener.setManager(manager);
caretListener.setManager(manager);
manager.registerListeners();
if (!urisUnderLspControl.contains(uri)) {
@ -464,7 +457,6 @@ public class LanguageServerWrapper {
// sadly this whole editor closing stuff runs asynchronously, so we cannot be sure the state is really clean here...
// therefore clear the mapping from here as it should be empty by now.
DocumentEventManager.clearState();
uriToEditorManagers.clear();
urisUnderLspControl.clear();
launcherFuture = null;
@ -590,7 +582,8 @@ public class LanguageServerWrapper {
textDocumentClientCapabilities.setSynchronization(new SynchronizationCapabilities(true, true, true));
FoldingRangeCapabilities foldingRangeCapabilities = new FoldingRangeCapabilities();
foldingRangeCapabilities.setFoldingRangeKind(new FoldingRangeKindSupportCapabilities(List.of("comment", "region", "imports")));
foldingRangeCapabilities.setFoldingRangeKind(new FoldingRangeKindSupportCapabilities(List.of(
FoldingRangeKind.Comment, FoldingRangeKind.Imports, FoldingRangeKind.Region)));
foldingRangeCapabilities.setFoldingRange(new FoldingRangeSupportCapabilities(true));
textDocumentClientCapabilities.setFoldingRange(foldingRangeCapabilities);

View file

@ -109,16 +109,7 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
if (eventManager == null) {
return;
}
if (eventManager.isCodeActionSyncRequired()) {
try {
updateAnnotations(holder, eventManager);
} catch (ConcurrentModificationException e) {
// Todo - Add proper fix to handle concurrent modifications gracefully.
LOG.warn("Error occurred when updating LSP diagnostics due to concurrent modifications.", e);
} catch (Throwable t) {
LOG.warn("Error occurred when updating LSP diagnostics.", t);
}
} else if (eventManager.isDiagnosticSyncRequired()) {
if (eventManager.isDiagnosticSyncRequired()) {
try {
createAnnotations(holder, eventManager);
} catch (ConcurrentModificationException e) {
@ -127,6 +118,15 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
} catch (Throwable t) {
LOG.warn("Error occurred when updating LSP code actions.", t);
}
} else if (eventManager.isCodeActionSyncRequired()) {
try {
updateAnnotations(holder, eventManager);
} catch (ConcurrentModificationException e) {
// Todo - Add proper fix to handle concurrent modifications gracefully.
LOG.warn("Error occurred when updating LSP diagnostics due to concurrent modifications.", e);
} catch (Throwable t) {
LOG.warn("Error occurred when updating LSP diagnostics.", t);
}
}
}
}
@ -140,7 +140,12 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
annotations.forEach(annotation -> {
if (annotation.getQuickFixes() != null && !annotation.getQuickFixes().isEmpty()) {
AnnotationBuilder builder = holder.newAnnotation(annotation.getSeverity(), annotation.getMessage());
boolean range = true;
for (Annotation.QuickFixInfo quickFixInfo : annotation.getQuickFixes()) {
if (range) {
builder = builder.range(quickFixInfo.textRange);
range = false;
}
builder = builder.withFix(quickFixInfo.quickFix);
}
builder.create();

View file

@ -39,9 +39,7 @@ import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class DocumentEventManager {
@ -52,7 +50,6 @@ public class DocumentEventManager {
private final TextDocumentIdentifier identifier;
private int version = -1;
protected Logger LOG = Logger.getInstance(EditorEventManager.class);
private static final Map<String, DocumentEventManager> uriToDocumentEventManager = new HashMap<>();
private final Set<Document> openDocuments = new HashSet<>();
@ -64,22 +61,6 @@ public class DocumentEventManager {
this.identifier = new TextDocumentIdentifier(FileUtils.documentToUri(document));
}
public static void clearState() {
uriToDocumentEventManager.clear();
}
public static DocumentEventManager getOrCreateDocumentManager(Document document, DocumentListener listener, TextDocumentSyncKind syncKind, LanguageServerWrapper wrapper) {
DocumentEventManager manager = uriToDocumentEventManager.get(FileUtils.documentToUri(document));
if (manager != null) {
return manager;
}
manager = new DocumentEventManager(document, listener, syncKind, wrapper);
uriToDocumentEventManager.put(FileUtils.documentToUri(document), manager);
return manager;
}
public void removeListeners() {
document.removeDocumentListener(documentListener);
}

View file

@ -72,11 +72,13 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.ui.Hint;
import com.intellij.util.SmartList;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
@ -86,6 +88,7 @@ import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DeclarationParams;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
@ -119,7 +122,9 @@ import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon;
import java.awt.Point;
@ -138,12 +143,11 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase.getCtrlRange;
import static com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase.getIsCtrlDown;
import static com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase.setCtrlRange;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableReadAction;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableWriteAction;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.invokeLater;
@ -173,8 +177,6 @@ public class EditorEventManager {
public LanguageServerWrapper wrapper;
private Project project;
private TextDocumentIdentifier identifier;
private EditorMouseListener mouseListener;
private EditorMouseMotionListener mouseMotionListener;
private LSPCaretListenerImpl caretListener;
public List<String> completionTriggers;
@ -200,13 +202,10 @@ public class EditorEventManager {
public static final String SNIPPET_PLACEHOLDER_REGEX = "(\\$\\{\\d+:?([^{^}]*)}|\\$\\d+)";
//Todo - Revisit arguments order and add remaining listeners
public EditorEventManager(Editor editor, DocumentListener documentListener, EditorMouseListener mouseListener,
EditorMouseMotionListener mouseMotionListener, LSPCaretListenerImpl caretListener,
public EditorEventManager(Editor editor, DocumentListener documentListener, LSPCaretListenerImpl caretListener,
RequestManager requestmanager, ServerOptions serverOptions, LanguageServerWrapper wrapper) {
this.editor = editor;
this.mouseListener = mouseListener;
this.mouseMotionListener = mouseMotionListener;
this.wrapper = wrapper;
this.caretListener = caretListener;
@ -229,7 +228,7 @@ public class EditorEventManager {
this.currentHint = null;
this.documentEventManager = DocumentEventManager.getOrCreateDocumentManager(editor.getDocument(), documentListener, syncKind, wrapper);
this.documentEventManager = new DocumentEventManager(editor.getDocument(), documentListener, syncKind, wrapper);
}
@SuppressWarnings("unused")
@ -258,159 +257,53 @@ public class EditorEventManager {
}
}
/**
* Tells the manager that the mouse is in the editor
*/
public void mouseEntered() {
mouseInEditor = true;
}
/**
* Tells the manager that the mouse is not in the editor
*/
public void mouseExited() {
mouseInEditor = false;
}
/**
* Will show documentation if the mouse doesn't move for a given time (Hover)
*
* @param e the event
*/
public void mouseMoved(EditorMouseEvent e) {
// if (e.getEditor() != editor) {
// LOG.error("Wrong editor for EditorEventManager");
// return;
// }
//
// PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
// if (psiFile == null) {
// return;
// }
// Language language = psiFile.getLanguage();
// if ((!LanguageDocumentation.INSTANCE.allForLanguage(language).isEmpty() && !isSupportedLanguageFile(psiFile))
// || (!getIsCtrlDown() && !EditorSettingsExternalizable.getInstance().isShowQuickDocOnMouseOverElement())) {
// return;
// }
//
// long curTime = System.nanoTime();
// if (predTime == (-1L) || ctrlTime == (-1L)) {
// predTime = curTime;
// ctrlTime = curTime;
// } else {
// LogicalPosition lPos = getPos(e);
// if (lPos == null || getIsKeyPressed() && !getIsCtrlDown()) {
// return;
// }
//
// int offset = editor.logicalPositionToOffset(lPos);
// if ((getIsCtrlDown() || EditorSettingsExternalizable.getInstance().isShowQuickDocOnMouseOverElement())
// && curTime - ctrlTime > CTRL_THRESH) {
// if (getCtrlRange() == null || !getCtrlRange().highlightContainsOffset(offset)) {
// if (currentHint != null) {
// currentHint.hide();
// }
// currentHint = null;
// if (getCtrlRange() != null) {
// getCtrlRange().dispose();
// }
// setCtrlRange(null);
// pool(() -> requestAndShowDoc(lPos, e.getMouseEvent().getPoint()));
// } else if (getCtrlRange().definitionContainsOffset(offset)) {
// createAndShowEditorHint(editor, "Click to show usages", editor.offsetToXY(offset));
// } else {
// editor.getContentComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
// }
// ctrlTime = curTime;
// }
// predTime = curTime;
// }
}
private boolean isSupportedLanguageFile(PsiFile file) {
return file.getLanguage().isKindOf(PlainTextLanguage.INSTANCE)
|| FileUtils.isFileSupported(file.getVirtualFile());
}
/**
* Called when the mouse is clicked
* At the moment, is used by CTRL+click to see references / goto definition
*
* @param e The mouse event
*/
public void mouseClicked(EditorMouseEvent e) {
if (e.getEditor() != editor) {
LOG.error("Wrong editor for EditorEventManager");
return;
}
if (getIsCtrlDown()) {
// If CTRL/CMD key is pressed, triggers goto definition/references and hover.
try {
trySourceNavigationAndHover(e);
} catch (Exception err) {
LOG.warn("Error occurred when trying source navigation", err);
}
}
}
private void createCtrlRange(Position logicalPos, Range range) {
Location location = requestDefinition(logicalPos);
if (location == null || location.getRange() == null || editor.isDisposed()) {
return;
}
Range corRange;
if (range == null) {
corRange = new Range(logicalPos, logicalPos);
} else {
corRange = range;
}
int startOffset = DocumentUtils.LSPPosToOffset(editor, corRange.getStart());
int endOffset = DocumentUtils.LSPPosToOffset(editor, corRange.getEnd());
boolean isDefinition = DocumentUtils.LSPPosToOffset(editor, location.getRange().getStart()) == startOffset;
CtrlRangeMarker ctrlRange = getCtrlRange();
if (!editor.isDisposed()) {
if (ctrlRange != null) {
ctrlRange.dispose();
}
setCtrlRange(new CtrlRangeMarker(location, editor, !isDefinition ?
(editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.HYPERLINK,
editor.getColorsScheme().getAttributes(EditorColors.REFERENCE_HYPERLINK_COLOR),
HighlighterTargetArea.EXACT_RANGE)) : null));
}
}
/**
* Returns the position of the definition given a position in the editor
*
* @param position The position
* @return The location of the definition
*/
private Location requestDeclaration(Position position) {
return requestDefinitionOrDeclaration(position, Timeouts.DECLARATION, DeclarationParams::new, TextDocumentService::declaration);
}
/**
* Returns the position of the declaration given a position in the editor
*
* @param position The position
* @return The location of the declaration
*/
private Location requestDefinition(Position position) {
DefinitionParams params = new DefinitionParams(identifier, position);
CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>> request =
wrapper.getRequestManager().definition(params);
return requestDefinitionOrDeclaration(position, Timeouts.DEFINITION, DefinitionParams::new, TextDocumentService::definition);
}
private <T> Location requestDefinitionOrDeclaration(Position position, Timeouts timeout, BiFunction<TextDocumentIdentifier, Position, T> paramsConstructor, BiFunction<TextDocumentService, T, CompletableFuture<Either<List<? extends Location>, List<? extends LocationLink>>>> requester) {
val params = paramsConstructor.apply(identifier, position);
val request = requester.apply(wrapper.getRequestManager(), params);
if (request == null) {
return null;
}
try {
Either<List<? extends Location>, List<? extends LocationLink>> definition =
request.get(Timeout.getTimeout(Timeouts.DEFINITION), TimeUnit.MILLISECONDS);
wrapper.notifySuccess(Timeouts.DEFINITION);
if (definition == null) {
val result = request.get(Timeout.getTimeout(timeout), TimeUnit.MILLISECONDS);
wrapper.notifySuccess(timeout);
if (result == null) {
return null;
}
if (definition.isLeft() && !definition.getLeft().isEmpty()) {
return definition.getLeft().get(0);
} else if (definition.isRight() && !definition.getRight().isEmpty()) {
var def = definition.getRight().get(0);
if (result.isLeft() && !result.getLeft().isEmpty()) {
return result.getLeft().get(0);
} else if (result.isRight() && !result.getRight().isEmpty()) {
var def = result.getRight().get(0);
return new Location(def.getTargetUri(), def.getTargetRange());
}
} catch (TimeoutException e) {
LOG.warn(e);
wrapper.notifyFailure(Timeouts.DEFINITION);
wrapper.notifyFailure(timeout);
return null;
} catch (InterruptedException | JsonRpcException | ExecutionException e) {
LOG.warn(e);
@ -459,7 +352,7 @@ public class EditorEventManager {
* @param offset The offset in the editor
* @return An array of PsiElement
*/
public Pair<List<PsiElement>, List<VirtualFile>> references(int offset, boolean getOriginalElement, boolean close) {
public Pair<List<PsiElement>, List<VirtualFile>> references(int offset, boolean getOriginalElement, boolean fast) {
renameCache = null;
Position lspPos = DocumentUtils.offsetToLSPPos(editor, offset);
TextDocumentIdentifier textDocumentIdentifier = new TextDocumentIdentifier(FileUtils.editorToURIString(editor));
@ -471,36 +364,48 @@ public class EditorEventManager {
try {
List<? extends Location> res = request.get(Timeout.getTimeout(Timeouts.REFERENCES), TimeUnit.MILLISECONDS);
wrapper.notifySuccess(Timeouts.REFERENCES);
if (res != null && res.size() > 0) {
List<VirtualFile> openedEditors = new ArrayList<>();
if (res != null && !res.isEmpty()) {
List<VirtualFile> openedEditors = fast ? null : new ArrayList<>();
List<PsiElement> elements = new ArrayList<>();
res.forEach(l -> {
Position start = l.getRange().getStart();
Position end = l.getRange().getEnd();
String uri = FileUtils.sanitizeURI(l.getUri());
VirtualFile file = FileUtils.virtualFileFromURI(uri);
Editor curEditor = FileUtils.editorFromUri(uri, project);
if (curEditor == null && file != null) {
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file, start.getLine(), start.getCharacter());
curEditor = computableWriteAction(
() -> FileEditorManager.getInstance(project).openTextEditor(descriptor, false));
openedEditors.add(file);
if (fast) {
if (file == null)
return;
val document = FileDocumentManager.getInstance().getDocument(file);
if (document == null)
return;
val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
if (psiFile == null)
return;
val element = psiFile.findElementAt(DocumentUtils.LSPPosToOffset(document, start));
if (element == null)
return;
elements.add(element);
} else {
Editor curEditor = FileUtils.editorFromUri(uri, project);
if (curEditor == null && file != null) {
OpenFileDescriptor descriptor =
new OpenFileDescriptor(project, file, start.getLine(), start.getCharacter());
curEditor = computableWriteAction(
() -> FileEditorManager.getInstance(project).openTextEditor(descriptor, false));
openedEditors.add(file);
}
if (curEditor == null) {
LOG.warn("Error occurred in LSP references.");
return;
}
int logicalStart = DocumentUtils.LSPPosToOffset(curEditor, start);
int logicalEnd = DocumentUtils.LSPPosToOffset(curEditor, end);
String name = curEditor.getDocument().getText(new TextRange(logicalStart, logicalEnd));
elements.add(new LSPPsiElement(name, project, logicalStart, logicalEnd,
PsiDocumentManager.getInstance(project)
.getPsiFile(curEditor.getDocument())));
}
if (curEditor == null) {
LOG.warn("Error occurred in LSP references.");
return;
}
int logicalStart = DocumentUtils.LSPPosToOffset(curEditor, start);
int logicalEnd = DocumentUtils.LSPPosToOffset(curEditor, end);
String name = curEditor.getDocument().getText(new TextRange(logicalStart, logicalEnd));
elements.add(new LSPPsiElement(name, project, logicalStart, logicalEnd,
PsiDocumentManager.getInstance(project).getPsiFile(curEditor.getDocument())));
});
if (close) {
writeAction(
() -> openedEditors.forEach(f -> FileEditorManager.getInstance(project).closeFile(f)));
openedEditors.clear();
}
return new Pair<>(elements, openedEditors);
} else {
return new Pair<>(null, null);
@ -797,57 +702,6 @@ public class EditorEventManager {
});
}
/**
* Gets the hover request and shows it
*
* @param editorPos The editor position
* @param point The point at which to show the hint
*/
private void requestAndShowDoc(LogicalPosition editorPos, Point point) {
Position serverPos = computableReadAction(() -> DocumentUtils.logicalToLSPPos(editorPos, editor));
CompletableFuture<Hover> request = wrapper.getRequestManager().hover(new HoverParams(identifier, serverPos));
if (request == null) {
return;
}
try {
Hover hover = request.get(Timeout.getTimeout(Timeouts.HOVER), TimeUnit.MILLISECONDS);
wrapper.notifySuccess(Timeouts.HOVER);
if (hover == null) {
LOG.debug(String.format("Hover is null for file %s and pos (%d;%d)", identifier.getUri(),
serverPos.getLine(), serverPos.getCharacter()));
return;
}
String string = HoverHandler.getHoverString(hover);
if (StringUtils.isEmpty(string)) {
LOG.warn(String.format("Hover string returned is empty for file %s and pos (%d;%d)",
identifier.getUri(), serverPos.getLine(), serverPos.getCharacter()));
return;
}
if (getIsCtrlDown()) {
invokeLater(() -> {
if (!editor.isDisposed()) {
currentHint = createAndShowEditorHint(editor, string, point, HintManager.HIDE_BY_OTHER_HINT);
}
});
} else {
invokeLater(() -> {
if (!editor.isDisposed()) {
currentHint = createAndShowEditorHint(editor, string, point);
}
});
}
} catch (TimeoutException e) {
LOG.warn(e);
wrapper.notifyFailure(Timeouts.HOVER);
} catch (InterruptedException | JsonRpcException | ExecutionException e) {
LOG.warn(e);
wrapper.crashed(e);
}
}
/**
* Returns the completion suggestions given a position
*
@ -1290,8 +1144,6 @@ public class EditorEventManager {
* Adds all the listeners
*/
public void registerListeners() {
editor.addEditorMouseListener(mouseListener);
editor.addEditorMouseMotionListener(mouseMotionListener);
editor.getCaretModel().addCaretListener(caretListener);
// Todo - Implement
// editor.getSelectionModel.addSelectionListener(selectionListener)
@ -1301,8 +1153,6 @@ public class EditorEventManager {
* Removes all the listeners
*/
public void removeListeners() {
editor.removeEditorMouseListener(mouseListener);
editor.removeEditorMouseMotionListener(mouseMotionListener);
editor.getCaretModel().removeCaretListener(caretListener);
// Todo - Implement
// editor.getSelectionModel.removeSelectionListener(selectionListener)
@ -1444,50 +1294,58 @@ public class EditorEventManager {
saveDocument();
}
}
// Tries to go to definition / show usages based on the element which is
private void trySourceNavigationAndHover(EditorMouseEvent e) {
// Tries to go to definition
public void gotoDefinition(PsiElement element) {
if (editor.isDisposed()) {
return;
}
val sourceOffset = element.getTextOffset();
val loc = requestDefinition(DocumentUtils.offsetToLSPPos(editor, sourceOffset));
invokeLater(() -> {
if (editor.isDisposed()) {
return;
}
gotoLocation(loc);
});
}
// Tries to go to declaration / show usages based on the element which is
public void gotoDeclarationOrUsages(PsiElement element) {
if (editor.isDisposed()) {
return;
}
val sourceOffset = element.getTextOffset();
val loc = requestDeclaration(DocumentUtils.offsetToLSPPos(editor, sourceOffset));
if (loc == null) {
LSPReferencesAction referencesAction = (LSPReferencesAction) ActionManager.getInstance()
.getAction("LSPFindUsages");
if (referencesAction != null) {
referencesAction.forManagerAndOffset(this, sourceOffset);
}
return;
}
createCtrlRange(DocumentUtils.logicalToLSPPos(editor.xyToLogicalPosition(e.getMouseEvent().getPoint()), editor),
null);
final CtrlRangeMarker ctrlRange = getCtrlRange();
if (ctrlRange == null) {
int offset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(e.getMouseEvent().getPoint()));
LSPReferencesAction referencesAction = (LSPReferencesAction) ActionManager.getInstance()
.getAction("LSPFindUsages");
if (referencesAction != null) {
referencesAction.forManagerAndOffset(this, offset);
}
return;
}
Location loc = ctrlRange.location;
invokeLater(() -> {
if (editor.isDisposed()) {
return;
}
int offset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(e.getMouseEvent().getPoint()));
String locUri = FileUtils.sanitizeURI(loc.getUri());
if (identifier.getUri().equals(locUri)
&& offset >= DocumentUtils.LSPPosToOffset(editor, loc.getRange().getStart())
&& offset <= DocumentUtils.LSPPosToOffset(editor, loc.getRange().getEnd())) {
&& sourceOffset >= DocumentUtils.LSPPosToOffset(editor, loc.getRange().getStart())
&& sourceOffset <= DocumentUtils.LSPPosToOffset(editor, loc.getRange().getEnd())) {
LSPReferencesAction referencesAction = (LSPReferencesAction) ActionManager.getInstance()
.getAction("LSPFindUsages");
if (referencesAction != null) {
referencesAction.forManagerAndOffset(this, offset);
referencesAction.forManagerAndOffset(this, sourceOffset);
}
} else {
gotoLocation(loc);
}
ctrlRange.dispose();
setCtrlRange(null);
});
}
@ -1599,7 +1457,8 @@ public class EditorEventManager {
});
// If code actions are updated, forcefully triggers the inspection tool.
if (codeActionSyncRequired) {
updateErrorAnnotations();
// double-delay the update to ensure that the code analyzer finishes.
invokeLater(this::updateErrorAnnotations);
}
});
});

View file

@ -19,6 +19,7 @@ import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.lsp.utils.OSUtils;
import com.intellij.openapi.editor.Editor;
import lombok.val;
import org.eclipse.lsp4j.Diagnostic;
import java.awt.KeyboardFocusManager;
@ -36,56 +37,6 @@ public class EditorEventManagerBase {
private static final Map<String, Set<EditorEventManager>> uriToManagers = new ConcurrentHashMap<>();
private static final Map<Editor, EditorEventManager> editorToManager = new ConcurrentHashMap<>();
private static final int CTRL_KEY_CODE = OSUtils.isMac() ? KeyEvent.VK_META : KeyEvent.VK_CONTROL;
private volatile static boolean isKeyPressed = false;
private volatile static boolean isCtrlDown = false;
private volatile static CtrlRangeMarker ctrlRange;
static {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher((KeyEvent e) -> {
int eventId = e.getID();
if (eventId == KeyEvent.KEY_PRESSED) {
setIsKeyPressed(true);
if (e.getKeyCode() == CTRL_KEY_CODE) {
setIsCtrlDown(true);
}
} else if (eventId == KeyEvent.KEY_RELEASED) {
setIsKeyPressed(false);
if (e.getKeyCode() == CTRL_KEY_CODE) {
setIsCtrlDown(false);
if (getCtrlRange() != null) {
getCtrlRange().dispose();
setCtrlRange(null);
}
}
}
return false;
});
}
static synchronized CtrlRangeMarker getCtrlRange() {
return ctrlRange;
}
static synchronized void setCtrlRange(CtrlRangeMarker ctrlRange) {
EditorEventManagerBase.ctrlRange = ctrlRange;
}
static synchronized boolean getIsCtrlDown() {
return isCtrlDown;
}
static synchronized void setIsCtrlDown(boolean isCtrlDown) {
EditorEventManagerBase.isCtrlDown = isCtrlDown;
}
static synchronized boolean getIsKeyPressed() {
return isKeyPressed;
}
static synchronized void setIsKeyPressed(boolean isKeyPressed) {
EditorEventManagerBase.isKeyPressed = isKeyPressed;
}
private static void prune() {
pruneEditorManagerMap();
@ -159,9 +110,12 @@ public class EditorEventManagerBase {
String uri = FileUtils.editorToURIString(manager.editor);
synchronized (uriToManagers) {
Set<EditorEventManager> set = getEditorEventManagerCopy(uri);
if (set.isEmpty()) {
uriToManagers.remove(uri);
val managers = uriToManagers.get(uri);
if (managers != null) {
managers.remove(manager);
if (managers.isEmpty()) {
uriToManagers.remove(uri);
}
}
}
}

View file

@ -26,8 +26,6 @@ import com.falsepattern.zigbrains.lsp.contributors.icon.LSPIconProvider;
import com.falsepattern.zigbrains.lsp.contributors.label.LSPDefaultLabelProvider;
import com.falsepattern.zigbrains.lsp.contributors.label.LSPLabelProvider;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.listeners.EditorMouseListenerImpl;
import com.falsepattern.zigbrains.lsp.listeners.EditorMouseMotionListenerImpl;
import com.falsepattern.zigbrains.lsp.listeners.LSPCaretListenerImpl;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentListener;
@ -69,8 +67,6 @@ public interface LSPExtensionManager {
*/
EditorEventManager getExtendedEditorEventManagerFor(Editor editor,
DocumentListener documentListener,
EditorMouseListenerImpl mouseListener,
EditorMouseMotionListenerImpl mouseMotionListener,
LSPCaretListenerImpl caretListener,
RequestManager requestManager,
ServerOptions serverOptions,

View file

@ -1,56 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.listeners;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseListener;
/**
* An EditorMouseListener implementation which listens to mouseExited, mouseEntered and mouseClicked events.
*/
public class EditorMouseListenerImpl extends LSPListener implements EditorMouseListener {
@Override
public void mousePressed(EditorMouseEvent editorMouseEvent) {
}
@Override
public void mouseClicked(EditorMouseEvent editorMouseEvent) {
if (checkEnabled()) {
manager.mouseClicked(editorMouseEvent);
}
}
@Override
public void mouseReleased(EditorMouseEvent editorMouseEvent) {
}
@Override
public void mouseEntered(EditorMouseEvent editorMouseEvent) {
if (checkEnabled()) {
manager.mouseEntered();
}
}
@Override
public void mouseExited(EditorMouseEvent editorMouseEvent) {
if (checkEnabled()) {
manager.mouseExited();
}
}
}

View file

@ -1,37 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.listeners;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseMotionListener;
/**
* Class listening for mouse movement in an editor (used for hover)
*/
public class EditorMouseMotionListenerImpl extends LSPListener implements EditorMouseMotionListener {
@Override
public void mouseMoved(EditorMouseEvent e) {
if (checkEnabled()) {
manager.mouseMoved(e);
}
}
@Override
public void mouseDragged(EditorMouseEvent editorMouseEvent) {
}
}

View file

@ -102,28 +102,7 @@ class LSPFileEventManager {
return;
}
String oldFileUri = String.format("%s/%s", oldParentUri, event.getFileName());
ApplicationUtils.invokeAfterPsiEvents(() -> {
// Notifies the language server.
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(oldFileUri,
FileUtils.projectToUri(p), FileChangeType.Deleted));
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(newFileUri,
FileUtils.projectToUri(p), FileChangeType.Created));
FileUtils.findProjectsFor(file).forEach(p -> {
// Detaches old file from the wrappers.
Set<LanguageServerWrapper> wrappers = IntellijLanguageClient.getAllServerWrappersFor(FileUtils.projectToUri(p));
if (wrappers != null) {
wrappers.forEach(wrapper -> wrapper.disconnect(oldFileUri, FileUtils.projectToUri(p)));
}
// Re-open file to so that the new editor will be connected to the language server.
FileEditorManager fileEditorManager = FileEditorManager.getInstance(p);
ApplicationUtils.invokeLater(() -> {
fileEditorManager.closeFile(file);
fileEditorManager.openFile(file, true);
});
});
});
closeAndReopenAffectedFile(file, oldFileUri);
} catch (Exception e) {
LOG.warn("LSP file move event failed due to :", e);
}
@ -169,36 +148,7 @@ class LSPFileEventManager {
}
String newFileUri = FileUtils.VFSToURI(file);
String oldFileUri = newFileUri.replace(file.getName(), oldFileName);
// Notifies the language server.
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(oldFileUri,
FileUtils.projectToUri(p), FileChangeType.Deleted));
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(newFileUri,
FileUtils.projectToUri(p), FileChangeType.Created));
FileUtils.findProjectsFor(file).forEach(p -> {
// Detaches old file from the wrappers.
Set<LanguageServerWrapper> wrappers = IntellijLanguageClient.getAllServerWrappersFor(FileUtils.projectToUri(p));
if (wrappers != null) {
wrappers.forEach(wrapper -> {
// make these calls first since the disconnect might stop the LS client if its last file.
wrapper.getRequestManager().didChangeWatchedFiles(
getDidChangeWatchedFilesParams(oldFileUri, FileChangeType.Deleted));
wrapper.getRequestManager().didChangeWatchedFiles(
getDidChangeWatchedFilesParams(newFileUri, FileChangeType.Created));
wrapper.disconnect(oldFileUri, FileUtils.projectToUri(p));
});
}
if (!newFileUri.equals(oldFileUri)) {
// Re-open file to so that the new editor will be connected to the language server.
FileEditorManager fileEditorManager = FileEditorManager.getInstance(p);
ApplicationUtils.invokeLater(() -> {
fileEditorManager.closeFile(file);
fileEditorManager.openFile(file, true);
});
}
});
closeAndReopenAffectedFile(file, oldFileUri);
}
} catch (Exception e) {
LOG.warn("LSP file rename event failed due to : ", e);
@ -206,6 +156,30 @@ class LSPFileEventManager {
});
}
private static void closeAndReopenAffectedFile(VirtualFile file, String oldFileUri) {
String newFileUri = FileUtils.VFSToURI(file);
// Notifies the language server.
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(oldFileUri,
FileUtils.projectToUri(p), FileChangeType.Deleted));
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(newFileUri,
FileUtils.projectToUri(p), FileChangeType.Created));
FileUtils.findProjectsFor(file).forEach(p -> {
// Detaches old file from the wrappers.
Set<LanguageServerWrapper> wrappers = IntellijLanguageClient.getAllServerWrappersFor(FileUtils.projectToUri(p));
wrappers.forEach(wrapper -> wrapper.disconnect(oldFileUri, FileUtils.projectToUri(p)));
if (!newFileUri.equals(oldFileUri)) {
// Re-open file to so that the new editor will be connected to the language server.
FileEditorManager fileEditorManager = FileEditorManager.getInstance(p);
ApplicationUtils.invokeLater(() -> {
fileEditorManager.closeFile(file);
fileEditorManager.openFile(file, true);
});
}
});
}
/**
* Called when a file is created. Notifies the server if needed.
*

View file

@ -19,7 +19,7 @@ package com.falsepattern.zigbrains.lsp.requests;
* Enumeration for the timeouts
*/
public enum Timeouts {
CODEACTION(2000), CODELENS(2000), COMPLETION(1000), DEFINITION(2000), DOC_HIGHLIGHT(1000), EXECUTE_COMMAND(
CODEACTION(2000), CODELENS(2000), COMPLETION(1000), DECLARATION(2000), DEFINITION(2000), DOC_HIGHLIGHT(1000), EXECUTE_COMMAND(
2000), FORMATTING(2000), HOVER(2000), INIT(10000), REFERENCES(2000), SIGNATURE(1000), SHUTDOWN(
5000), SYMBOLS(2000), WILLSAVE(2000), FOLDING(1000), HIGHLIGHTING(1000), INLAY_HINTS(2000);

View file

@ -90,6 +90,10 @@ public class DocumentUtils {
});
}
public static int LSPPosToOffset(Document document, Position pos) {
return document.getLineStartOffset(pos.getLine()) + pos.getCharacter();
}
/**
* Transforms an LSP position to an editor offset
*

View file

@ -17,6 +17,7 @@
package com.falsepattern.zigbrains.project.ide.project;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.lsp.ZLSStartupActivity;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
@ -66,10 +67,14 @@ public class ZigProjectConfigurable implements Configurable {
public void apply() throws ConfigurationException {
val zigSettings = ZigProjectSettingsService.getInstance(project);
val settingsData = settingsPanel.getData();
boolean modified = isModified();
zigSettings.modify((settings) -> {
settings.setToolchain(settingsData.toolchain());
settings.setExplicitPathToStd(settingsData.explicitPathToStd());
});
if (modified) {
ZLSStartupActivity.initZLS(project);
}
}
@Override

View file

@ -0,0 +1,40 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.toolchain;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.environment.ZLSConfig;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public class ToolchainZLSConfigProvider implements ZLSConfigProvider {
@Override
public @NotNull ZLSConfig getEnvironment(Project project) {
val projectSettings = ZigProjectSettingsService.getInstance(project);
val toolchain = projectSettings.getToolchain();
if (toolchain == null)
return ZLSConfig.EMPTY;
val projectDir = ProjectUtil.guessProjectDir(project);
val env = toolchain.zig().getEnv(projectDir == null ? Path.of(".") : projectDir.toNioPath());
return env.map(e -> new ZLSConfig(e.zigExecutable(), e.libDirectory())).orElse(ZLSConfig.EMPTY);
}
}

View file

@ -38,6 +38,7 @@
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<toolchainFlavour implementation="com.falsepattern.zigbrains.project.toolchain.flavours.ZigSystemPathToolchainFlavour"/>
<toolchainProvider implementation="com.falsepattern.zigbrains.project.toolchain.LocalZigToolchainProvider"/>
<zlsConfigProvider implementation="com.falsepattern.zigbrains.project.toolchain.ToolchainZLSConfigProvider"/>
</extensions>
<actions>

View file

@ -0,0 +1,45 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.environment;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public record ZLSConfig(@NotNull Optional<String> zigExePath,
@NotNull Optional<String> zigLibPath) {
public ZLSConfig(String zigExePath, String zigLibPath) {
this(Optional.ofNullable(zigExePath), Optional.ofNullable(zigLibPath));
}
public ZLSConfig overrideWith(ZLSConfig other) {
return new ZLSConfig(other.zigExePath.or(() -> zigExePath),
other.zigLibPath.or(() -> zigLibPath));
}
public static final ZLSConfig EMPTY = new ZLSConfig(Optional.empty(), Optional.empty());
public JsonObject toJson() {
val result = new JsonObject();
zigExePath.ifPresent(s -> result.addProperty("zig_exe_path", s));
zigLibPath.ifPresent(s -> result.addProperty("zig_lib_path", s));
return result;
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.environment;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
public interface ZLSConfigProvider {
ExtensionPointName<ZLSConfigProvider> EXTENSION_POINT_NAME = ExtensionPointName.create("com.falsepattern.zigbrains.zlsConfigProvider");
static @NotNull ZLSConfig findEnvironment(Project project) {
return EXTENSION_POINT_NAME.getExtensionList()
.stream()
.map(it -> it.getEnvironment(project))
.reduce(ZLSConfig.EMPTY, ZLSConfig::overrideWith);
}
@NotNull ZLSConfig getEnvironment(Project project);
}

View file

@ -23,17 +23,20 @@ import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.listeners.LSPCaretListenerImpl;
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.zig.ide.SemaEdit;
import com.falsepattern.zigbrains.zig.util.HighlightingUtil;
import com.falsepattern.zigbrains.zig.util.TokenDecoder;
import com.intellij.lang.annotation.Annotation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.event.EditorMouseListener;
import com.intellij.openapi.editor.event.EditorMouseMotionListener;
import lombok.val;
import org.eclipse.lsp4j.InsertReplaceEdit;
import org.eclipse.lsp4j.SemanticTokens;
import org.eclipse.lsp4j.SemanticTokensDelta;
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
import org.eclipse.lsp4j.SemanticTokensEdit;
import org.eclipse.lsp4j.SemanticTokensParams;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
@ -49,8 +52,8 @@ import static com.falsepattern.zigbrains.lsp.requests.Timeout.getTimeout;
public class ZLSEditorEventManager extends EditorEventManager {
private static String previousResultID = null;
public ZLSEditorEventManager(Editor editor, DocumentListener documentListener, EditorMouseListener mouseListener, EditorMouseMotionListener mouseMotionListener, LSPCaretListenerImpl caretListener, RequestManager requestmanager, ServerOptions serverOptions, LanguageServerWrapper wrapper) {
super(editor, documentListener, mouseListener, mouseMotionListener, caretListener, requestmanager,
public ZLSEditorEventManager(Editor editor, DocumentListener documentListener, LSPCaretListenerImpl caretListener, RequestManager requestmanager, ServerOptions serverOptions, LanguageServerWrapper wrapper) {
super(editor, documentListener, caretListener, requestmanager,
serverOptions, wrapper);
}
@ -108,4 +111,15 @@ public class ZLSEditorEventManager extends EditorEventManager {
}
return result;
}
@Override
public Runnable getEditsRunnable(int version, List<Either<TextEdit, InsertReplaceEdit>> edits, String name, boolean setCaret) {
val run = super.getEditsRunnable(version, edits, name, setCaret);
return () -> {
run.run();
if (!editor.isDisposed()) {
ApplicationManager.getApplication().invokeLater(() -> HighlightingUtil.refreshHighlighting(this));
}
};
}
}

View file

@ -21,8 +21,6 @@ import com.falsepattern.zigbrains.lsp.client.languageserver.ServerOptions;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager;
import com.falsepattern.zigbrains.lsp.listeners.EditorMouseListenerImpl;
import com.falsepattern.zigbrains.lsp.listeners.EditorMouseMotionListenerImpl;
import com.falsepattern.zigbrains.lsp.listeners.LSPCaretListenerImpl;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentListener;
@ -37,9 +35,9 @@ public class ZLSExtensionManager implements LSPExtensionManager {
}
@Override
public ZLSEditorEventManager getExtendedEditorEventManagerFor(Editor editor, DocumentListener documentListener, EditorMouseListenerImpl mouseListener, EditorMouseMotionListenerImpl mouseMotionListener, LSPCaretListenerImpl caretListener, RequestManager requestManager, ServerOptions serverOptions, LanguageServerWrapper wrapper) {
return new ZLSEditorEventManager(editor, documentListener, mouseListener, mouseMotionListener,
caretListener, requestManager, serverOptions, wrapper);
public ZLSEditorEventManager getExtendedEditorEventManagerFor(Editor editor, DocumentListener documentListener, LSPCaretListenerImpl caretListener, RequestManager requestManager, ServerOptions serverOptions, LanguageServerWrapper wrapper) {
return new ZLSEditorEventManager(editor, documentListener,
caretListener, requestManager, serverOptions, wrapper);
}
@Override

View file

@ -18,18 +18,23 @@ package com.falsepattern.zigbrains.zig.lsp;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
import com.falsepattern.zigbrains.zig.settings.ZLSSettingsState;
import com.google.gson.Gson;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.ProjectActivity;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
@ -37,6 +42,7 @@ import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
public class ZLSStartupActivity implements ProjectActivity {
private static final Logger LOG = Logger.getInstance(ZLSStartupActivity.class);
private static final ReentrantLock lock = new ReentrantLock();
public static void initZLS(Project project) {
@ -59,12 +65,31 @@ public class ZLSStartupActivity implements ProjectActivity {
var configPath = settings.zlsConfigPath;
boolean configOK = true;
if (!"".equals(configPath) && !validatePath("ZLS Config", configPath, false)) {
configOK = false;
Notifications.Bus.notify(new Notification("ZigBrains.Nag", "Using default config path.",
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Using default config path.",
NotificationType.INFORMATION));
configPath = "";
}
if ("".equals(configPath)) {
configOK = false;
blk:
try {
val tmpFile = Files.createTempFile("zigbrains-zls-autoconf", ".json");
val config = ZLSConfigProvider.findEnvironment(project);
if (config.zigExePath().isEmpty() && config.zigLibPath().isEmpty()) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "(ZLS) Failed to detect zig path from project toolchain", NotificationType.WARNING));
configOK = false;
break blk;
}
try (val writer = Files.newBufferedWriter(tmpFile)) {
val gson = new Gson();
gson.toJson(config.toJson(), writer);
}
configPath = tmpFile.toAbsolutePath().toString();
} catch (IOException e) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Failed to create automatic zls config file",
NotificationType.WARNING));
LOG.warn(e);
configOK = false;
}
}
if (IntellijLanguageClient.getExtensionManagerFor("zig") == null) {
@ -112,18 +137,18 @@ public class ZLSStartupActivity implements ProjectActivity {
path = Path.of(pathTxt);
} catch (InvalidPathException e) {
Notifications.Bus.notify(
new Notification("ZigBrains.Nag", "No " + name, "Invalid " + name + " path \"" + pathTxt + "\"",
new Notification("ZigBrains.ZLS", "No " + name, "Invalid " + name + " path \"" + pathTxt + "\"",
NotificationType.ERROR));
return false;
}
if (!Files.exists(path)) {
Notifications.Bus.notify(new Notification("ZigBrains.Nag", "No " + name,
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" doesn't exist!",
NotificationType.ERROR));
return false;
}
if (Files.isDirectory(path) != dir) {
Notifications.Bus.notify(new Notification("ZigBrains.Nag", "No " + name,
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" is a " +
(Files.isDirectory(path) ? "directory" : "file") +
" , expected a " + (dir ? "directory" : "file"),
@ -146,8 +171,8 @@ public class ZLSStartupActivity implements ProjectActivity {
zlsPath = settings.zlsPath = thePath.get();
}
}
if ("".equals(zlsPath)) {
Notifications.Bus.notify(new Notification("ZigBrains.Nag", "No ZLS binary",
if (zlsPath.isEmpty()) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No ZLS binary",
"Please configure the path to the zls executable in the Zig language configuration menu!",
NotificationType.INFORMATION));
return null;

View file

@ -46,7 +46,7 @@ public class ZLSSettingsComponent {
.addVerticalGap(10)
.addLabeledComponent(new JBLabel("ZLS path: "), zlsPathText, 1, false)
.addComponent(autodetectZls)
.addLabeledComponent(new JBLabel("ZLS config path (leave empty to use default): "),
.addLabeledComponent(new JBLabel("ZLS config path (leave empty to use built-in config): "),
zlsConfigPathText, 1, false)
.addLabeledComponent(new JBLabel("Increase timeouts"), increaseTimeouts, 1, false)
.addLabeledComponent(new JBLabel("Asynchronous code folding ranges: "),

View file

@ -105,7 +105,9 @@
<postStartupActivity implementation="com.falsepattern.zigbrains.zig.lsp.ZLSStartupActivity"/>
<notificationGroup displayType="BALLOON"
id="ZigBrains.Nag"/>
bundle="zigbrains.zig.Bundle"
key="notif-zls"
id="ZigBrains.ZLS"/>
</extensions>
@ -121,6 +123,10 @@
</action>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPReformatAction" id="ReformatCode" use-shortcut-of="ReformatCode"
overrides="true" text="Reformat Code"/>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPGotoDeclarationAction" id="GotoDeclaration" use-shortcut-of="GotoDeclaration"
overrides="true" text="Go to Declaration or Usages"/>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPGotoDefinitionAction" id="QuickImplementations" use-shortcut-of="QuickImplementations"
overrides="true" text="View Implementations"/>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPShowReformatDialogAction" id="ShowReformatFileDialog"
use-shortcut-of="ShowReformatFileDialog" overrides="true" text="Show Reformat File Dialog"/>

View file

@ -1 +1,2 @@
inlayprovider=ZLS Inlay Provider
inlayprovider=ZLS Inlay Provider
notif-zls=ZLS Error Notification

View file

@ -11,6 +11,7 @@
<xi:include href="/META-INF/zigbrains-project.xml"/>
<!--suppress PluginXmlValidity -->
<depends optional="true" config-file="zigbrains-zig-debugger.xml">com.intellij.modules.cidr.debugger</depends>
<depends optional="true" config-file="zigbrains-zig-cpp.xml">com.intellij.modules.clion</depends>
<extensionPoints>
<!-- region zigbrains-project -->
@ -20,6 +21,12 @@
<extensionPoint
interface="com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider" dynamic="true"
name="toolchainProvider"/>
<extensionPoint
interface="com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider" dynamic="true"
name="zlsConfigProvider"/>
<extensionPoint
interface="com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider" dynamic="true"
name="debuggerDriverProvider"/>
<!-- endregion zigbrains-project -->
</extensionPoints>
</idea-plugin>