feat: Integrated parameter info popup
This commit is contained in:
parent
a266ff94c3
commit
868a83326a
4 changed files with 164 additions and 72 deletions
|
@ -35,7 +35,6 @@ import com.falsepattern.zigbrains.lsp.utils.FileUtils;
|
|||
import com.falsepattern.zigbrains.lsp.utils.GUIUtils;
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
|
||||
import com.intellij.codeInsight.hint.HintManager;
|
||||
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
|
@ -96,7 +95,6 @@ import org.eclipse.lsp4j.InsertReplaceEdit;
|
|||
import org.eclipse.lsp4j.InsertTextFormat;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.MarkupContent;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.ReferenceContext;
|
||||
|
@ -144,7 +142,6 @@ import static com.falsepattern.zigbrains.common.util.ApplicationUtil.invokeLater
|
|||
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.pool;
|
||||
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.writeAction;
|
||||
import static com.falsepattern.zigbrains.lsp.utils.DocumentUtils.toEither;
|
||||
import static com.falsepattern.zigbrains.lsp.utils.GUIUtils.createAndShowEditorHint;
|
||||
|
||||
/**
|
||||
* Class handling events related to an Editor (a Document)
|
||||
|
@ -236,17 +233,6 @@ public class EditorEventManager {
|
|||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls onTypeFormatting or signatureHelp if the character typed was a trigger character
|
||||
*
|
||||
* @param c The character just typed
|
||||
*/
|
||||
public void characterTyped(char c) {
|
||||
if (signatureTriggers.contains(Character.toString(c))) {
|
||||
signatureHelp();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSupportedLanguageFile(PsiFile file) {
|
||||
return file.getLanguage().isKindOf(PlainTextLanguage.INSTANCE)
|
||||
|| FileUtils.isFileSupported(file.getVirtualFile());
|
||||
|
@ -518,70 +504,40 @@ public class EditorEventManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls signatureHelp at the current editor caret position
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public void signatureHelp() {
|
||||
public CompletableFuture<SignatureHelp> getSignatureHelp(int offset) {
|
||||
if (editor.isDisposed()) {
|
||||
return;
|
||||
return CompletableFuture.failedFuture(new RuntimeException("Editor is disposed"));
|
||||
}
|
||||
LogicalPosition lPos = editor.getCaretModel().getCurrentCaret().getLogicalPosition();
|
||||
Point point = editor.logicalPositionToXY(lPos);
|
||||
SignatureHelpParams params = new SignatureHelpParams(identifier, DocumentUtils.logicalToLSPPos(lPos, editor));
|
||||
SignatureHelpParams params = new SignatureHelpParams(identifier, DocumentUtils.offsetToLSPPos(editor, offset));
|
||||
val result = new CompletableFuture<SignatureHelp>();
|
||||
pool(() -> {
|
||||
CompletableFuture<SignatureHelp> future = wrapper.getRequestManager().signatureHelp(params);
|
||||
val future = wrapper.getRequestManager().signatureHelp(params);
|
||||
if (future == null) {
|
||||
result.complete(null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
SignatureHelp signatureResp = future.get(Timeout.getTimeout(Timeouts.SIGNATURE), TimeUnit.MILLISECONDS);
|
||||
val signatureResp = future.get(Timeout.getTimeout(Timeouts.SIGNATURE), TimeUnit.MILLISECONDS);
|
||||
wrapper.notifySuccess(Timeouts.SIGNATURE);
|
||||
if (signatureResp == null) {
|
||||
result.complete(null);
|
||||
return;
|
||||
}
|
||||
List<SignatureInformation> signatures = signatureResp.getSignatures();
|
||||
if (signatures == null || signatures.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int activeSignatureIndex = signatureResp.getActiveSignature();
|
||||
int activeParameterIndex = signatureResp.getActiveParameter();
|
||||
|
||||
String activeParameter = signatures.get(activeSignatureIndex).getParameters().size() > activeParameterIndex ?
|
||||
extractLabel(signatures.get(activeSignatureIndex), signatures.get(activeSignatureIndex).getParameters().get(activeParameterIndex).getLabel()) : "";
|
||||
Either<String, MarkupContent> signatureDescription = signatures.get(activeSignatureIndex).getDocumentation();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("<html>");
|
||||
if (signatureDescription == null) {
|
||||
builder.append("<b>").append(signatures.get(activeSignatureIndex).getLabel().
|
||||
replace(" " + activeParameter, String.format("<font color=\"orange\"> %s</font>",
|
||||
activeParameter))).append("</b>");
|
||||
} else if (signatureDescription.isLeft()) {
|
||||
// Todo - Add parameter Documentation
|
||||
String descriptionLeft = signatureDescription.getLeft().replace(System.lineSeparator(), "<br />");
|
||||
builder.append("<b>").append(signatures.get(activeSignatureIndex).getLabel()
|
||||
.replace(" " + activeParameter, String.format("<font color=\"orange\"> %s</font>",
|
||||
activeParameter))).append("</b>");
|
||||
builder.append("<div>").append(descriptionLeft).append("</div>");
|
||||
} else if (signatureDescription.isRight()) {
|
||||
// Todo - Add marked content parsing
|
||||
builder.append("<b>").append(signatures.get(activeSignatureIndex).getLabel()).append("</b>");
|
||||
}
|
||||
|
||||
builder.append("</html>");
|
||||
invokeLater(() -> currentHint = createAndShowEditorHint(editor, builder.toString(), point, HintManager.UNDER, HintManager.HIDE_BY_OTHER_HINT));
|
||||
|
||||
result.complete(signatureResp);
|
||||
} catch (TimeoutException e) {
|
||||
LOG.warn(e);
|
||||
wrapper.notifyFailure(Timeouts.SIGNATURE);
|
||||
result.completeExceptionally(e);
|
||||
} catch (JsonRpcException | ExecutionException | InterruptedException e) {
|
||||
LOG.warn(e);
|
||||
wrapper.crashed(e);
|
||||
result.completeExceptionally(e);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Internal error occurred when processing signature help");
|
||||
result.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private String extractLabel(SignatureInformation signatureInformation, Either<String, Tuple.Two<Integer, Integer>> label) {
|
||||
|
|
|
@ -29,22 +29,8 @@ import org.jetbrains.annotations.NotNull;
|
|||
* This class notifies an EditorEventManager that a character has been typed in the editor
|
||||
*/
|
||||
public class LSPTypedHandler extends TypedHandlerDelegate {
|
||||
|
||||
@Override
|
||||
public Result charTyped(char c, Project project, @NotNull Editor editor, @NotNull PsiFile file) {
|
||||
if (!FileUtils.isFileSupported(file.getVirtualFile())) {
|
||||
return Result.CONTINUE;
|
||||
}
|
||||
|
||||
EditorEventManager eventManager = EditorEventManagerBase.forEditor(editor);
|
||||
if (eventManager != null) {
|
||||
eventManager.characterTyped(c);
|
||||
}
|
||||
return Result.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result checkAutoPopup(char charTyped, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
|
||||
public @NotNull Result checkAutoPopup(char charTyped, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
|
||||
if (!FileUtils.isFileSupported(file.getVirtualFile())) {
|
||||
return Result.CONTINUE;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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.completion;
|
||||
|
||||
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
|
||||
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigExprList;
|
||||
import com.intellij.lang.parameterInfo.CreateParameterInfoContext;
|
||||
import com.intellij.lang.parameterInfo.ParameterInfoHandler;
|
||||
import com.intellij.lang.parameterInfo.ParameterInfoUIContext;
|
||||
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import lombok.val;
|
||||
import org.eclipse.lsp4j.ParameterInformation;
|
||||
import org.eclipse.lsp4j.SignatureHelp;
|
||||
import org.eclipse.lsp4j.SignatureInformation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class ZigParameterInfoHandler implements ParameterInfoHandler<PsiElement, ZigParameterInfoHandler.IndexedSignatureInformation> {
|
||||
private final WeakHashMap<PsiElement, CompletableFuture<SignatureHelp>> requests = new WeakHashMap<>();
|
||||
private final Logger LOG = Logger.getInstance(ZigParameterInfoHandler.class);
|
||||
|
||||
public record IndexedSignatureInformation(SignatureInformation information, boolean active) {}
|
||||
|
||||
private @Nullable PsiElement fetchQuery(@NotNull PsiFile file, int offset) {
|
||||
val sourceElem = file.findElementAt(offset);
|
||||
if (sourceElem == null)
|
||||
return null;
|
||||
PsiElement element = PsiTreeUtil.getParentOfType(sourceElem, ZigExprList.class);
|
||||
if (element == null) {
|
||||
element = sourceElem.getPrevSibling();
|
||||
while (element instanceof PsiWhiteSpace)
|
||||
element = element.getPrevSibling();
|
||||
if (!(element instanceof ZigExprList))
|
||||
return null;
|
||||
}
|
||||
val editor = FileUtils.editorFromPsiFile(file);
|
||||
if (editor == null)
|
||||
return null;
|
||||
val manager = EditorEventManagerBase.forEditor(editor);
|
||||
if (manager == null)
|
||||
return null;
|
||||
val request = manager.getSignatureHelp(offset);
|
||||
requests.put(element, request);
|
||||
return element;
|
||||
}
|
||||
|
||||
private @Nullable SignatureHelp getResponse(PsiElement element) {
|
||||
val request = requests.get(element);
|
||||
if (request == null)
|
||||
return null;
|
||||
final SignatureHelp response;
|
||||
try {
|
||||
response = request.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
LOG.warn(e);
|
||||
return null;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiElement findElementForParameterInfo(@NotNull CreateParameterInfoContext context) {
|
||||
return fetchQuery(context.getFile(), context.getOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showParameterInfo(@NotNull PsiElement element, @NotNull CreateParameterInfoContext context) {
|
||||
val response = getResponse(element);
|
||||
if (response == null)
|
||||
return;
|
||||
val signatures = response.getSignatures();
|
||||
val indexedSignatures = new IndexedSignatureInformation[signatures.size()];
|
||||
val active = response.getActiveSignature();
|
||||
for (int i = 0; i < indexedSignatures.length; i++) {
|
||||
indexedSignatures[i] = new IndexedSignatureInformation(signatures.get(i), i == active);
|
||||
}
|
||||
context.setItemsToShow(indexedSignatures);
|
||||
context.showHint(element, 0, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PsiElement findElementForUpdatingParameterInfo(@NotNull UpdateParameterInfoContext context) {
|
||||
return fetchQuery(context.getFile(), context.getOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateParameterInfo(@NotNull PsiElement psiElement, @NotNull UpdateParameterInfoContext context) {
|
||||
val response = getResponse(psiElement);
|
||||
if (response == null)
|
||||
return;
|
||||
context.setCurrentParameter(response.getActiveParameter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUI(IndexedSignatureInformation p, @NotNull ParameterInfoUIContext context) {
|
||||
if (p == null) {
|
||||
context.setUIComponentEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
val txt = new StringBuilder();
|
||||
int hStart = 0;
|
||||
int hEnd = 0;
|
||||
List<ParameterInformation> parameters = p.information.getParameters();
|
||||
val active = context.getCurrentParameterIndex();
|
||||
for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) {
|
||||
var param = parameters.get(i);
|
||||
if (i != 0) {
|
||||
txt.append(", ");
|
||||
}
|
||||
if (i == active) {
|
||||
hStart = txt.length();
|
||||
}
|
||||
txt.append(param.getLabel().getLeft());
|
||||
if (i == active) {
|
||||
hEnd = txt.length();
|
||||
}
|
||||
}
|
||||
|
||||
context.setupUIComponentPresentation(txt.toString(), hStart, hEnd, !p.active, false, true, context.getDefaultParameterColor());
|
||||
}
|
||||
}
|
|
@ -109,6 +109,9 @@
|
|||
key="notif-zls"
|
||||
id="ZigBrains.ZLS"/>
|
||||
|
||||
<codeInsight.parameterInfo language="Zig"
|
||||
implementationClass="com.falsepattern.zigbrains.zig.completion.ZigParameterInfoHandler"/>
|
||||
|
||||
<platform.backend.documentation.linkHandler implementation="com.falsepattern.zigbrains.lsp.contributors.LSPDocumentationLinkHandler"/>
|
||||
</extensions>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue