feat(zig,lsp)!: Inlay hints

This commit is contained in:
FalsePattern 2023-08-18 10:42:52 +02:00 committed by FalsePattern
parent c1989f0b0b
commit 0947fe5b0f
Signed by: falsepattern
GPG key ID: FDF7126A9E124447
10 changed files with 219 additions and 9 deletions

View file

@ -18,6 +18,11 @@ Changelog structure reference:
## [Unreleased]
### Added
#### Zig/LSP
- Inlay hints
## [0.6.0]
### Added

View file

@ -13,6 +13,7 @@ Go to `Settings` -> `Languages & Frameworks` -> `Zig` -> `ZLS path` -> select yo
- Code completion
- Code folding
- Syntax highlighting
- Inlay hints
- Basic error diagnostics
- Go to definition
- Rename symbol

View file

@ -57,6 +57,8 @@ import org.eclipse.lsp4j.ImplementationParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.MessageActionItem;
@ -676,6 +678,20 @@ public class DefaultRequestManager implements RequestManager {
return null;
}
@Override
public CompletableFuture<List<InlayHint>> inlayHint(InlayHintParams params) {
if (checkStatus()) {
try {
return (getServerCapabilities().getInlayHintProvider() != null)
? getTextDocumentService().inlayHint(params) : null;
} catch (Exception e) {
crashed(e);
return null;
}
}
return null;
}
@Override
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
if (checkStatus()) {

View file

@ -69,6 +69,7 @@ import org.eclipse.lsp4j.HoverCapabilities;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.InlayHintCapabilities;
import org.eclipse.lsp4j.OnTypeFormattingCapabilities;
import org.eclipse.lsp4j.RangeFormattingCapabilities;
import org.eclipse.lsp4j.ReferencesCapabilities;
@ -578,6 +579,7 @@ public class LanguageServerWrapper {
textDocumentClientCapabilities.setDocumentHighlight(new DocumentHighlightCapabilities());
textDocumentClientCapabilities.setFormatting(new FormattingCapabilities());
textDocumentClientCapabilities.setHover(new HoverCapabilities());
textDocumentClientCapabilities.setInlayHint(new InlayHintCapabilities());
textDocumentClientCapabilities.setOnTypeFormatting(new OnTypeFormattingCapabilities());
textDocumentClientCapabilities.setRangeFormatting(new RangeFormattingCapabilities());
textDocumentClientCapabilities.setReferences(new ReferencesCapabilities());

View file

@ -0,0 +1,122 @@
/*
* Copyright 2023 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.contributors;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.codeInsight.hints.declarative.InlayHintsCollector;
import com.intellij.codeInsight.hints.declarative.InlayHintsProvider;
import com.intellij.codeInsight.hints.declarative.InlayTreeSink;
import com.intellij.codeInsight.hints.declarative.InlineInlayPosition;
import com.intellij.codeInsight.hints.declarative.OwnBypassCollector;
import com.intellij.codeInsight.hints.declarative.impl.DeclarativeInlayHintsPassFactory;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiFile;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
public class LSPInlayHintProvider implements InlayHintsProvider {
protected static Logger LOG = Logger.getInstance(LSPInlayHintProvider.class);
private static final LSPInlayHintsCollector DEFAULT_COLLECTOR = new LSPInlayHintsCollector();
@Nullable
@Override
public InlayHintsCollector createCollector(@NotNull PsiFile psiFile, @NotNull Editor editor) {
if (FileUtils.isFileSupported(psiFile.getVirtualFile())) {
return getCollector();
}
return null;
}
public LSPInlayHintsCollector getCollector() {
return DEFAULT_COLLECTOR;
}
public static class LSPInlayHintsCollector implements OwnBypassCollector {
@Override
public void collectHintsForFile(@NotNull PsiFile psiFile, @NotNull InlayTreeSink inlayTreeSink) {
var editor = FileUtils.editorFromPsiFile(psiFile);
if (editor == null) {
return;
}
var manager = EditorEventManagerBase.forEditor(editor);
if (manager == null || manager.editor != editor) {
EditorEventManagerBase.runWhenManagerGetsRegistered(editor,
() -> {
if (editor.isDisposed()) {
return;
}
var project = editor.getProject();
if (project == null) {
return;
}
DeclarativeInlayHintsPassFactory.Companion.scheduleRecompute(editor, project);
});
return;
}
var res = manager.inlayHint();
if (res == null) {
return;
}
for (var hint: res) {
var pos = DocumentUtils.LSPPosToOffset(editor, hint.getPosition());
var inlayPos = new InlineInlayPosition(pos, false, 0);
var tt = hint.getTooltip();
String tooltipText;
if (tt.isLeft()) {
tooltipText = tt.getLeft();
} else {
var markup = tt.getRight();
tooltipText = switch (markup.getKind()) {
case "markdown" -> {
var markedContent = markup.getValue();
if (markedContent.isEmpty()) {
yield "";
}
Parser parser = Parser.builder().build();
HtmlRenderer renderer = HtmlRenderer.builder().build();
yield "<html>" + renderer.render(parser.parse(markedContent)) + "</html>";
}
default -> markup.getValue();
};
}
inlayTreeSink.addPresentation(inlayPos, Collections.emptyList(), tooltipText, true, (builder) -> {
var label = hint.getLabel();
StringBuilder text = new StringBuilder();
if (label.isLeft()) {
text.append(label.getLeft());
} else if (label.isRight()) {
var parts = label.getRight();
for (var part: parts) {
text.append(part.getValue());
}
}
if (text.length() == 0) {
text.append(" ");
}
builder.text(text.toString(), null);
return null;
});
}
}
}
}

View file

@ -21,6 +21,7 @@ import com.falsepattern.zigbrains.lsp.requests.HoverHandler;
import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.requests.WorkspaceEditHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.hint.HintManager;
@ -1307,6 +1308,32 @@ public class EditorEventManager {
});
}
public List<InlayHint> inlayHint() {
var range = ApplicationUtils.computableReadAction(() -> {
var start = DocumentUtils.offsetToLSPPos(editor, 0);
var end = DocumentUtils.offsetToLSPPos(editor, editor.getDocument().getTextLength());
return new Range(start, end);
});
var promise = getRequestManager().inlayHint(new InlayHintParams(FileUtils.editorToLSPIdentifier(editor), range));
if (promise == null) {
return Collections.emptyList();
}
try {
var res = promise.get(Timeout.getTimeout(Timeouts.INLAY_HINTS), TimeUnit.MILLISECONDS);
wrapper.notifySuccess(Timeouts.INLAY_HINTS);
if (res != null) {
return res;
}
} catch (TimeoutException e) {
LOG.warn(e);
wrapper.notifyFailure(Timeouts.CODEACTION);
} catch (InterruptedException | JsonRpcException | ExecutionException e) {
LOG.warn(e);
wrapper.crashed(e);
}
return Collections.emptyList();
}
/**
* If the server supports willSaveWaitUntil, the LSPVetoer will check if a save is needed
* (needSave will basically alternate between true or false, so the document will always be saved)

View file

@ -15,6 +15,7 @@
*/
package com.falsepattern.zigbrains.lsp.editor;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.intellij.openapi.editor.Editor;
import org.eclipse.lsp4j.Diagnostic;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
@ -22,11 +23,13 @@ import com.falsepattern.zigbrains.lsp.utils.OSUtils;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
public class EditorEventManagerBase {
@ -112,6 +115,8 @@ public class EditorEventManagerBase {
});
}
private static final WeakHashMap<Editor, List<Runnable>> runOnRegistry = new WeakHashMap<>();
public static void registerManager(EditorEventManager manager) {
String uri = FileUtils.editorToURIString(manager.editor);
synchronized (uriToManagers) {
@ -124,7 +129,29 @@ public class EditorEventManagerBase {
}
}
synchronized (runOnRegistry) {
editorToManager.put(manager.editor, manager);
if (runOnRegistry.containsKey(manager.editor)) {
var tasks = runOnRegistry.remove(manager.editor);
for (var task: tasks) {
ApplicationUtils.invokeLater(task);
}
}
}
}
public static void runWhenManagerGetsRegistered(Editor editor, Runnable... runnables) {
synchronized (runOnRegistry) {
var manager = forEditor(editor);
if (manager != null) {
for (var task: runnables) {
ApplicationUtils.invokeLater(task);
}
} else {
runOnRegistry.computeIfAbsent(editor, (ignored) -> new ArrayList<>()).addAll(List.of(runnables));
}
}
}
public static void unregisterManager(EditorEventManager manager) {

View file

@ -21,7 +21,7 @@ package com.falsepattern.zigbrains.lsp.requests;
public enum Timeouts {
CODEACTION(2000), CODELENS(2000), COMPLETION(1000), 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);
5000), SYMBOLS(2000), WILLSAVE(2000), FOLDING(1000), HIGHLIGHTING(1000), INLAY_HINTS(2000);
private final int defaultTimeout;

View file

@ -5,11 +5,11 @@
<vendor>FalsePattern</vendor>
<depends>com.intellij.modules.platform</depends>
<resource-bundle>zigbrains.Bundle</resource-bundle>
<extensions defaultExtensionNs="com.intellij">
<!-- region Zig -->
<!-- region LSP4IntelliJ -->
<!-- region LSP -->
<!-- register a listener on editor events, required for lsp file sync -->
<editorFactoryListener implementation="com.falsepattern.zigbrains.lsp.listeners.LSPEditorListener"/>
@ -58,7 +58,16 @@
<!-- needed for documentation -->
<platform.backend.documentation.targetProvider implementation="com.falsepattern.zigbrains.lsp.contributors.LSPDocumentationTargetProvider"/>
<!-- endregion LSP4IntelliJ -->
<!-- needed for inlay hints -->
<codeInsight.declarativeInlayProvider implementationClass="com.falsepattern.zigbrains.lsp.contributors.LSPInlayHintProvider"
bundle="zigbrains.Bundle"
nameKey="inlayprovider"
providerId="ZigBrains"
isEnabledByDefault="true"
group="PARAMETERS_GROUP"
language="Zig"/>
<!-- endregion LSP -->
<fileType name="Zig File"
implementationClass="com.falsepattern.zigbrains.zig.ZigFileType"
@ -125,7 +134,7 @@
<actions>
<!-- region Zig -->
<!-- region LSP4IntelliJ -->
<!-- region LSP -->
<!-- needed for find references -->
<action class="com.falsepattern.zigbrains.lsp.actions.LSPReferencesAction"
@ -134,13 +143,13 @@
keymap="$default"/>
</action>
<!-- endregion LSP4IntelliJ -->
<!-- endregion LSP -->
<!-- endregion Zig -->
</actions>
<applicationListeners>
<!-- region LSP4IntelliJ -->
<!-- region LSP -->
<!-- required for lsp file sync -->
<listener class="com.falsepattern.zigbrains.lsp.listeners.VFSListener"
@ -148,7 +157,7 @@
<listener class="com.falsepattern.zigbrains.lsp.listeners.LSPProjectManagerListener"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
<!-- endregion LSP4IntelliJ -->
<!-- endregion LSP -->
</applicationListeners>

View file

@ -0,0 +1 @@
inlayprovider=ZLS Inlay Provider