backport: feat: Documentation link handler

This commit is contained in:
FalsePattern 2024-03-12 17:00:18 +01:00
parent aef19cb372
commit 9514a22af0
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
4 changed files with 102 additions and 6 deletions

View file

@ -0,0 +1,92 @@
/*
* 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.contributors;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.backend.documentation.DocumentationLinkHandler;
import com.intellij.platform.backend.documentation.DocumentationTarget;
import com.intellij.platform.backend.documentation.LinkResolveResult;
import lombok.val;
import org.eclipse.lsp4j.Position;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.MalformedURLException;
import java.net.URL;
import static com.intellij.codeInsight.documentation.DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL;
public class LSPDocumentationLinkHandler implements DocumentationLinkHandler {
private static final String prefix = PSI_ELEMENT_PROTOCOL + "zigbrains://";
private final static Logger LOG = Logger.getInstance(LSPDocumentationLinkHandler.class);
@Override
public @Nullable LinkResolveResult resolveLink(@NotNull DocumentationTarget target, @NotNull String url) {
if (!url.startsWith(PSI_ELEMENT_PROTOCOL) || ! (target instanceof LSPDocumentationTargetProvider.LSPDocumentationTarget tgt)) {
return null;
}
url = url.replace(prefix, "file://");
val separator = url.indexOf("#L");
if (separator < 0)
return null;
val link = url.substring(0, separator);
final int line;
{
int theLine = 1;
try {
theLine = Integer.parseInt(url.substring(separator + 2));
} catch (NumberFormatException e) {
LOG.error("Could not parse file line: " + url.substring(separator + 2));
}
line = theLine - 1;
}
val app = ApplicationManager.getApplication();
app.executeOnPooledThread(() -> {
val project = tgt.file.getProject();
VirtualFile file;
try {
file = VfsUtil.findFileByURL(new URL(link));
} catch (MalformedURLException e1) {
LOG.warn("Syntax Exception occurred for uri: " + link);
return;
}
if (file == null)
return;
val descriptor = new OpenFileDescriptor(project, file);
app.invokeLater(() -> {
FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
val editor = FileUtils.editorFromVirtualFile(file, project);
if (editor == null) {
return;
}
val logicalPos = DocumentUtils.getTabsAwarePosition(editor, new Position(line, 0));
if (logicalPos == null)
return;
editor.getCaretModel().moveToLogicalPosition(logicalPos);
editor.getScrollingModel().scrollTo(logicalPos, ScrollType.CENTER);
});
});
return null;
}
}

View file

@ -47,6 +47,8 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import static com.intellij.codeInsight.documentation.DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL;
public class LSPDocumentationTargetProvider implements DocumentationTargetProvider { public class LSPDocumentationTargetProvider implements DocumentationTargetProvider {
@Override @Override
public @NotNull List<? extends @NotNull DocumentationTarget> documentationTargets(@NotNull PsiFile file, int offset) { public @NotNull List<? extends @NotNull DocumentationTarget> documentationTargets(@NotNull PsiFile file, int offset) {
@ -58,7 +60,7 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
public static class LSPDocumentationTarget implements DocumentationTarget { public static class LSPDocumentationTarget implements DocumentationTarget {
private final Pointer<LSPDocumentationTarget> pointer; private final Pointer<LSPDocumentationTarget> pointer;
private final PsiFile file; public final PsiFile file;
private final int offset; private final int offset;
public LSPDocumentationTarget(PsiFile file, int offset) { public LSPDocumentationTarget(PsiFile file, int offset) {
this.file = file; this.file = file;
@ -103,7 +105,7 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
return null; return null;
} }
val markdown = HoverHandler.getHoverString(hover); val markdown = HoverHandler.getHoverString(hover).replaceAll("file://", PSI_ELEMENT_PROTOCOL + "zigbrains://");
val string = ApplicationUtil.computableReadAction(() -> DocMarkdownToHtmlConverter val string = ApplicationUtil.computableReadAction(() -> DocMarkdownToHtmlConverter
.convert(manager.getProject(), markdown)); .convert(manager.getProject(), markdown));
if (StringUtils.isEmpty(string)) { if (StringUtils.isEmpty(string)) {

View file

@ -161,7 +161,7 @@ import static com.falsepattern.zigbrains.lsp.utils.GUIUtils.createAndShowEditorH
public class EditorEventManager { public class EditorEventManager {
public final DocumentEventManager documentEventManager; public final DocumentEventManager documentEventManager;
protected Logger LOG = Logger.getInstance(EditorEventManager.class); protected static Logger LOG = Logger.getInstance(EditorEventManager.class);
public Editor editor; public Editor editor;
public LanguageServerWrapper wrapper; public LanguageServerWrapper wrapper;
@ -1294,7 +1294,7 @@ public class EditorEventManager {
if (loc == null) if (loc == null)
return false; return false;
return gotoLocation(loc); return gotoLocation(project, loc);
} }
// Tries to go to declaration / show usages based on the element which is // Tries to go to declaration / show usages based on the element which is
@ -1325,11 +1325,11 @@ public class EditorEventManager {
referencesAction.forManagerAndOffset(this, sourceOffset); referencesAction.forManagerAndOffset(this, sourceOffset);
} }
} else { } else {
gotoLocation(loc); gotoLocation(project, loc);
} }
} }
public boolean gotoLocation(Location loc) { public static boolean gotoLocation(Project project, Location loc) {
VirtualFile file = null; VirtualFile file = null;
try { try {
file = VfsUtil.findFileByURL(new URL(loc.getUri())); file = VfsUtil.findFileByURL(new URL(loc.getUri()));

View file

@ -108,6 +108,8 @@
bundle="zigbrains.zig.Bundle" bundle="zigbrains.zig.Bundle"
key="notif-zls" key="notif-zls"
id="ZigBrains.ZLS"/> id="ZigBrains.ZLS"/>
<platform.backend.documentation.linkHandler implementation="com.falsepattern.zigbrains.lsp.contributors.LSPDocumentationLinkHandler"/>
</extensions> </extensions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains"> <extensions defaultExtensionNs="com.falsepattern.zigbrains">