feat!: Migrate semantic tokens to lsp4ij

This commit is contained in:
FalsePattern 2024-07-04 16:28:51 +02:00
parent cde7033130
commit 4ec98e5322
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
16 changed files with 219 additions and 902 deletions

View file

@ -20,7 +20,7 @@ Changelog structure reference:
### Changed ### Changed
- LSP - LSP
- Migrated to Red Hat's LSP4IJ LSP backend. - Migrated to Red Hat's LSP4IJ LSP adapter.
## [15.2.0] ## [15.2.0]

View file

@ -38,7 +38,7 @@ val clionVersion = properties("clionVersion").get()
val clionPlugins = listOf("com.intellij.clion", "com.intellij.cidr.lang", "com.intellij.cidr.base", "com.intellij.nativeDebug") val clionPlugins = listOf("com.intellij.clion", "com.intellij.cidr.lang", "com.intellij.cidr.base", "com.intellij.nativeDebug")
val lsp4jVersion = "0.21.1" val lsp4jVersion = "0.21.1"
val lsp4ijVersion = "0.3.0-20240702-174041" val lsp4ijVersion = "0.3.0-20240704-134935"
val lsp4ijNightly = lsp4ijVersion.contains("-") val lsp4ijNightly = lsp4ijVersion.contains("-")
val lsp4ijDepString = "${if (lsp4ijNightly) "nightly." else ""}com.jetbrains.plugins:com.redhat.devtools.lsp4ij:$lsp4ijVersion" val lsp4ijDepString = "${if (lsp4ijNightly) "nightly." else ""}com.jetbrains.plugins:com.redhat.devtools.lsp4ij:$lsp4ijVersion"
@ -390,6 +390,7 @@ dependencies {
"idea" -> intellijIdeaCommunity(ideaVersion) "idea" -> intellijIdeaCommunity(ideaVersion)
"clion" -> clion(clionVersion) "clion" -> clion(clionVersion)
} }
plugin(lsp4ijPluginString)
} }
} }

View file

@ -1,7 +1,7 @@
inlayprovider=ZLS Inlay Provider inlayprovider=ZLS Inlay Provider
notif-zb=ZigBrains general notification notif-zb=ZigBrains general notification
notif-zig-project=Zig project notification notif-zig-project=Zig project notification
notif-zls-error=ZLS error notification notif-zls-error=ZLS notification
notif-debug-info=ZigBrains debugger info notif-debug-info=ZigBrains debugger info
notif-debug-warn=ZigBrains debugger warning notif-debug-warn=ZigBrains debugger warning
notif-debug-error=ZigBrains debugger error notif-debug-error=ZigBrains debugger error

View file

@ -1,145 +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.zig.completion;
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());
}
}

View file

@ -25,6 +25,7 @@ import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
import com.intellij.psi.TokenType; import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IElementType;
import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensHighlightingColors;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@ -35,44 +36,44 @@ public class ZigSyntaxHighlighter extends SyntaxHighlighterBase {
// @formatter:off // @formatter:off
public static final TextAttributesKey public static final TextAttributesKey
BAD_CHAR = createKey("BAD_CHARACTER" , HighlighterColors.BAD_CHARACTER ), BAD_CHAR = createKey("BAD_CHARACTER" , HighlighterColors.BAD_CHARACTER ),
BUILTIN = createKey("BUILTIN" , DefaultLanguageHighlighterColors.STATIC_METHOD ), BUILTIN = createKey("BUILTIN" , SemanticTokensHighlightingColors.STATIC_METHOD ),
CHAR = createKey("CHAR" , DefaultLanguageHighlighterColors.NUMBER ), CHAR = createKey("CHAR" , SemanticTokensHighlightingColors.NUMBER ),
COMMENT = createKey("COMMENT" , DefaultLanguageHighlighterColors.LINE_COMMENT ), COMMENT = createKey("COMMENT" , SemanticTokensHighlightingColors.COMMENT ),
COMMENT_DOC = createKey("COMMENT_DOC" , DefaultLanguageHighlighterColors.DOC_COMMENT ), COMMENT_DOC = createKey("COMMENT_DOC" , DefaultLanguageHighlighterColors.DOC_COMMENT ),
ENUM_DECL = createKey("ENUM_DECL" , DefaultLanguageHighlighterColors.CLASS_NAME ), ENUM_DECL = createKey("ENUM_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
ENUM_REF = createKey("ENUM" , DefaultLanguageHighlighterColors.CLASS_REFERENCE ), ENUM_REF = createKey("ENUM" , SemanticTokensHighlightingColors.CLASS ),
ENUM_MEMBER_DECL = createKey("ENUM_MEMBER_DECL" , DefaultLanguageHighlighterColors.STATIC_FIELD ), ENUM_MEMBER_DECL = createKey("ENUM_MEMBER_DECL" , SemanticTokensHighlightingColors.STATIC_PROPERTY ),
ENUM_MEMBER_REF = createKey("ENUM_MEMBER" , ENUM_MEMBER_DECL ), ENUM_MEMBER_REF = createKey("ENUM_MEMBER" , ENUM_MEMBER_DECL ),
ERROR_TAG_DECL = createKey("ERROR_TAG_DECL" , DefaultLanguageHighlighterColors.STATIC_FIELD ), ERROR_TAG_DECL = createKey("ERROR_TAG_DECL" , SemanticTokensHighlightingColors.STATIC_PROPERTY ),
ERROR_TAG_REF = createKey("ERROR_TAG" , ERROR_TAG_DECL ), ERROR_TAG_REF = createKey("ERROR_TAG" , ERROR_TAG_DECL ),
PROPERTY_DECL = createKey("PROPERTY_DECL" , DefaultLanguageHighlighterColors.INSTANCE_FIELD ), PROPERTY_DECL = createKey("PROPERTY_DECL" , SemanticTokensHighlightingColors.PROPERTY ),
PROPERTY_REF = createKey("PROPERTY" , PROPERTY_DECL ), PROPERTY_REF = createKey("PROPERTY" , PROPERTY_DECL ),
FUNCTION_DECL = createKey("FUNCTION_DECL" , DefaultLanguageHighlighterColors.FUNCTION_DECLARATION), FUNCTION_DECL = createKey("FUNCTION_DECL" , SemanticTokensHighlightingColors.FUNCTION_DECLARATION),
FUNCTION_DECL_GEN = createKey("FUNCTION_DECL_GEN" , FUNCTION_DECL ), FUNCTION_DECL_GEN = createKey("FUNCTION_DECL_GEN" , FUNCTION_DECL ),
FUNCTION_REF = createKey("FUNCTION" , DefaultLanguageHighlighterColors.FUNCTION_CALL ), FUNCTION_REF = createKey("FUNCTION" , SemanticTokensHighlightingColors.FUNCTION ),
FUNCTION_REF_GEN = createKey("FUNCTION_GEN" , FUNCTION_REF ), FUNCTION_REF_GEN = createKey("FUNCTION_GEN" , FUNCTION_REF ),
KEYWORD = createKey("KEYWORD" , DefaultLanguageHighlighterColors.KEYWORD ), KEYWORD = createKey("KEYWORD" , SemanticTokensHighlightingColors.KEYWORD ),
LABEL_DECL = createKey("LABEL_DECL" , DefaultLanguageHighlighterColors.LABEL ), LABEL_DECL = createKey("LABEL_DECL" , SemanticTokensHighlightingColors.LABEL ),
LABEL_REF = createKey("LABEL" , LABEL_DECL ), LABEL_REF = createKey("LABEL" , LABEL_DECL ),
METHOD_DECL = createKey("METHOD_DECL" , FUNCTION_DECL ), METHOD_DECL = createKey("METHOD_DECL" , FUNCTION_DECL ),
METHOD_DECL_GEN = createKey("METHOD_DECL_GEN" , METHOD_DECL ), METHOD_DECL_GEN = createKey("METHOD_DECL_GEN" , METHOD_DECL ),
METHOD_REF = createKey("METHOD" , FUNCTION_REF ), METHOD_REF = createKey("METHOD" , FUNCTION_REF ),
METHOD_REF_GEN = createKey("METHOD_GEN" , METHOD_REF ), METHOD_REF_GEN = createKey("METHOD_GEN" , METHOD_REF ),
NAMESPACE_DECL = createKey("NAMESPACE_DECL" , DefaultLanguageHighlighterColors.CLASS_REFERENCE ), NAMESPACE_DECL = createKey("NAMESPACE_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
NAMESPACE_REF = createKey("NAMESPACE" , DefaultLanguageHighlighterColors.CLASS_NAME ), NAMESPACE_REF = createKey("NAMESPACE" , SemanticTokensHighlightingColors.CLASS ),
NUMBER = createKey("NUMBER" , DefaultLanguageHighlighterColors.NUMBER ), NUMBER = createKey("NUMBER" , SemanticTokensHighlightingColors.NUMBER ),
OPERATOR = createKey("OPERATOR" , DefaultLanguageHighlighterColors.OPERATION_SIGN ), OPERATOR = createKey("OPERATOR" , SemanticTokensHighlightingColors.OPERATOR ),
PARAMETER = createKey("PARAMETER" , DefaultLanguageHighlighterColors.PARAMETER ), PARAMETER = createKey("PARAMETER" , SemanticTokensHighlightingColors.PARAMETER ),
STRING = createKey("STRING" , DefaultLanguageHighlighterColors.STRING ), STRING = createKey("STRING" , SemanticTokensHighlightingColors.STRING ),
STRUCT_DECL = createKey("STRUCT_DECL" , DefaultLanguageHighlighterColors.CLASS_NAME ), STRUCT_DECL = createKey("STRUCT_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
STRUCT_REF = createKey("STRUCT" , DefaultLanguageHighlighterColors.CLASS_REFERENCE ), STRUCT_REF = createKey("STRUCT" , SemanticTokensHighlightingColors.CLASS ),
TYPE_DECL = createKey("TYPE_DECL" , DefaultLanguageHighlighterColors.CLASS_NAME ), TYPE_DECL = createKey("TYPE_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
TYPE_DECL_GEN = createKey("TYPE_DECL_GEN" , TYPE_DECL ), TYPE_DECL_GEN = createKey("TYPE_DECL_GEN" , TYPE_DECL ),
TYPE_REF = createKey("TYPE" , DefaultLanguageHighlighterColors.CLASS_REFERENCE ), TYPE_REF = createKey("TYPE" , SemanticTokensHighlightingColors.TYPE ),
TYPE_REF_GEN = createKey("TYPE_GEN" , TYPE_REF ), TYPE_REF_GEN = createKey("TYPE_GEN" , TYPE_REF ),
TYPE_PARAM = createKey("TYPE_PARAM" , TYPE_REF ), TYPE_PARAM = createKey("TYPE_PARAM" , SemanticTokensHighlightingColors.TYPE_PARAMETER ),
TYPE_PARAM_DECL = createKey("TYPE_PARAM_DECL" , TYPE_PARAM ), TYPE_PARAM_DECL = createKey("TYPE_PARAM_DECL" , TYPE_PARAM ),
VARIABLE_DECL = createKey("VARIABLE_DECL" , DefaultLanguageHighlighterColors.LOCAL_VARIABLE ), VARIABLE_DECL = createKey("VARIABLE_DECL" , SemanticTokensHighlightingColors.VARIABLE ),
VARIABLE_DECL_DEPR= createKey("VARIABLE_DECL_DEPR" , VARIABLE_DECL ), VARIABLE_DECL_DEPR= createKey("VARIABLE_DECL_DEPR" , VARIABLE_DECL ),
VARIABLE_REF = createKey("VARIABLE" , VARIABLE_DECL ), VARIABLE_REF = createKey("VARIABLE" , VARIABLE_DECL ),
VARIABLE_REF_DEPR = createKey("VARIABLE_REF_DEPL" , VARIABLE_REF ); VARIABLE_REF_DEPR = createKey("VARIABLE_REF_DEPL" , VARIABLE_REF );

View file

@ -1,22 +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.zig.ide;
import java.util.List;
public record SemaEdit(int start, int remove, List<SemaRange> add) {
}

View file

@ -1,22 +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.zig.ide;
import com.intellij.openapi.editor.colors.TextAttributesKey;
public record SemaRange(int start, int end, TextAttributesKey color) {
}

View file

@ -1,173 +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.zig.ide;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import kotlin.Pair;
import lombok.val;
import org.jetbrains.annotations.Unmodifiable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.BUILTIN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.COMMENT;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.COMMENT_DOC;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_MEMBER_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_MEMBER_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ERROR_TAG_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ERROR_TAG_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.FUNCTION_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.FUNCTION_DECL_GEN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.FUNCTION_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.FUNCTION_REF_GEN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.KEYWORD;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.LABEL_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.LABEL_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.METHOD_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.METHOD_DECL_GEN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.METHOD_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.METHOD_REF_GEN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.NAMESPACE_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.NAMESPACE_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.NUMBER;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.OPERATOR;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PARAMETER;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PROPERTY_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PROPERTY_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRING;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRUCT_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRUCT_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_DECL_GEN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_PARAM;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_PARAM_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_REF_GEN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.VARIABLE_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.VARIABLE_REF;
public enum ZigAttributes {
Builtin(BUILTIN),
Comment_Doc(COMMENT_DOC, "documentation"),
Comment(COMMENT),
Enum_Decl(ENUM_DECL, "declaration"),
Enum(ENUM_REF),
EnumMember_Decl(ENUM_MEMBER_DECL, "declaration"),
EnumMember(ENUM_MEMBER_REF),
ErrorTag_Decl(ERROR_TAG_DECL, "declaration"),
ErrorTag(ERROR_TAG_REF),
Property_Decl(PROPERTY_DECL, "declaration"),
Property(PROPERTY_REF),
Function_Decl_Gen(FUNCTION_DECL_GEN, "declaration", "generic"),
Function_Decl(FUNCTION_DECL, "declaration"),
Function_Gen(FUNCTION_REF_GEN, "generic"),
Function(FUNCTION_REF),
Keyword(KEYWORD),
KeywordLiteral(KEYWORD),
Label_Decl(LABEL_DECL, "declaration"),
Label(LABEL_REF),
Method_Decl_Gen(METHOD_DECL_GEN, "declaration", "generic"),
Method_Decl(METHOD_DECL, "declaration"),
Method_Gen(METHOD_REF_GEN, "generic"),
Method(METHOD_REF),
Namespace_Decl(NAMESPACE_DECL, "declaration"),
Namespace(NAMESPACE_REF),
Number(NUMBER),
Operator(OPERATOR),
Parameter_Decl(PARAMETER, "declaration"),
Parameter(PARAMETER),
String(STRING),
Struct_Decl(STRUCT_DECL, "declaration"),
Struct(STRUCT_REF),
Type_Decl_Gen(TYPE_DECL_GEN, "declaration", "generic"),
Type_Decl(TYPE_DECL, "declaration"),
Type_Gen(TYPE_REF_GEN, "generic"),
Type(TYPE_REF),
TypeParameter_Decl(TYPE_PARAM_DECL, "declaration"),
TypeParameter(TYPE_PARAM),
Variable_Decl_Depr(VARIABLE_DECL, "declaration", "deprecated"),
Variable_Decl(VARIABLE_DECL, "declaration"),
Variable_Depr(VARIABLE_REF, "deprecated"),
Variable(VARIABLE_REF),
;
public final TextAttributesKey KEY;
public final String type;
public final @Unmodifiable Set<String> modifiers;
ZigAttributes(TextAttributesKey defaultKey, String... modifiers) {
var name = name();
int underscoreIndex = name.indexOf('_');
type = Character.toLowerCase(name.charAt(0)) + (underscoreIndex > 0 ? name.substring(1, underscoreIndex) : name.substring(1));
KEY = defaultKey;
this.modifiers = new HashSet<>(List.of(modifiers));
}
private static final Map<String, List<ZigAttributes>> types = new HashMap<>();
static {
for (val known: values()) {
types.computeIfAbsent(known.type, (ignored) -> new ArrayList<>()).add(known);
}
}
private static final Logger LOG = Logger.getInstance(ZigAttributes.class);
private static final Set<Pair<String, Set<String>>> warnedUnknowns = new HashSet<>();
private static void complainAboutUnknownCombo(String type, Set<String> modifiers) {
val thePair = new Pair<>(type, modifiers);
synchronized (warnedUnknowns) {
if (warnedUnknowns.contains(thePair))
return;
warnedUnknowns.add(thePair);
LOG.warn("Unrecognized semantic token! type " + type + ", modifiers: " + modifiers);
}
}
public static Optional<TextAttributesKey> getKey(String type, Set<String> modifiers) {
if (type == null) {
return Optional.empty();
}
if (!types.containsKey(type)) {
complainAboutUnknownCombo(type, modifiers);
return Optional.empty();
}
val values = types.get(type);
for (var known : values) {
if ((modifiers != null && modifiers.equals(known.modifiers)) ||
(modifiers == null && known.modifiers.isEmpty())) {
return Optional.of(known.KEY);
}
}
complainAboutUnknownCombo(type, modifiers);
//Fallback with weaker matching
for (var known : values) {
if ((modifiers != null && modifiers.containsAll(known.modifiers)) ||
(modifiers == null && known.modifiers.isEmpty())) {
return Optional.of(known.KEY);
}
}
return Optional.empty();
}
}

View file

@ -9,11 +9,4 @@ public class ZLSLanguageClient extends LanguageClientImpl {
public ZLSLanguageClient(Project project) { public ZLSLanguageClient(Project project) {
super(project); super(project);
} }
@Override
public CompletableFuture<Void> refreshSemanticTokens() {
return CompletableFuture.completedFuture(null);
}
} }

View file

@ -31,6 +31,6 @@ public class ZLSLanguageServerFactory implements LanguageServerFactory, Language
@Override @Override
public void setEnabled(boolean enabled, @NotNull Project project) { public void setEnabled(boolean enabled, @NotNull Project project) {
this.enabled = enabled && ZLSStartupActivity.getCommand(project) != null; this.enabled = enabled && ZLSStreamConnectionProvider.getCommand(project) != null;
} }
} }

View file

@ -0,0 +1,77 @@
package com.falsepattern.zigbrains.zig.lsp;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.psi.PsiFile;
import com.redhat.devtools.lsp4ij.features.semanticTokens.DefaultSemanticTokensColorsProvider;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.*;
public class ZLSSemanticTokensColorsProvider extends DefaultSemanticTokensColorsProvider {
private record TokenHelper(List<String> tokenModifiers) {
public boolean hasAny(String... keys) {
if (tokenModifiers.isEmpty())
return false;
for (val key: keys) {
if (tokenModifiers.contains(key))
return true;
}
return false;
}
public boolean has(String... keys) {
if (tokenModifiers.isEmpty())
return false;
for (val key: keys) {
if (!tokenModifiers.contains(key))
return false;
}
return true;
}
public boolean isDecl() {
return hasAny("declaration", "definition");
}
}
@Override
public @Nullable TextAttributesKey getTextAttributesKey(@NotNull String tokenType,
@NotNull List<String> tokenModifiers,
@NotNull PsiFile file) {
val tok = new TokenHelper(tokenModifiers);
val res = switch (tokenType) {
case "builtin" -> BUILTIN;
case "comment" -> tok.has("documentation") ? COMMENT_DOC : COMMENT;
case "enum" -> tok.isDecl() ? ENUM_DECL : ENUM_REF;
case "enumMember" -> tok.isDecl() ? ENUM_MEMBER_DECL : ENUM_MEMBER_REF;
case "errorTag" -> tok.isDecl() ? ERROR_TAG_DECL : ERROR_TAG_REF;
case "property" -> tok.isDecl() ? PROPERTY_DECL : PROPERTY_REF;
case "function" -> tok.isDecl() ? (tok.has("generic") ? FUNCTION_DECL_GEN : FUNCTION_DECL)
: (tok.has("generic") ? FUNCTION_REF_GEN : FUNCTION_REF);
case "keyword", "keywordLiteral" -> KEYWORD;
case "label" -> tok.isDecl() ? LABEL_DECL : LABEL_REF;
case "method" -> tok.isDecl() ? (tok.has("generic") ? METHOD_DECL_GEN : METHOD_DECL)
: (tok.has("generic") ? METHOD_REF_GEN : METHOD_REF);
case "namespace" -> tok.isDecl() ? NAMESPACE_DECL : NAMESPACE_REF;
case "number" -> NUMBER;
case "operator" -> OPERATOR;
case "parameter" -> PARAMETER;
case "string" -> STRING;
case "struct" -> tok.isDecl() ? STRUCT_DECL : STRUCT_REF;
case "type" -> tok.isDecl() ? (tok.has("generic") ? TYPE_DECL_GEN : TYPE_DECL)
: (tok.has("generic") ? TYPE_REF_GEN : TYPE_REF);
case "typeParameter" -> tok.isDecl() ? TYPE_PARAM_DECL : TYPE_PARAM;
case "variable" -> tok.isDecl() ? (tok.has("deprecated") ? VARIABLE_DECL_DEPR : VARIABLE_DECL)
: (tok.has("deprecated") ? VARIABLE_REF_DEPR : VARIABLE_REF);
default -> null;
};
return res != null ? res : super.getTextAttributesKey(tokenType, tokenModifiers, file);
}
}

View file

@ -18,10 +18,7 @@ package com.falsepattern.zigbrains.zig.lsp;
import com.falsepattern.zigbrains.ZigBundle; import com.falsepattern.zigbrains.ZigBundle;
import com.falsepattern.zigbrains.common.util.ApplicationUtil; import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService; import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
import com.google.gson.Gson;
import com.intellij.ide.BrowserUtil; import com.intellij.ide.BrowserUtil;
import com.intellij.ide.plugins.PluginManager; import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.plugins.PluginManagerConfigurable; import com.intellij.ide.plugins.PluginManagerConfigurable;
@ -31,12 +28,10 @@ import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications; import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.ProjectActivity; import com.intellij.openapi.startup.ProjectActivity;
import com.intellij.openapi.util.io.FileUtil;
import com.redhat.devtools.lsp4ij.LanguageServerManager; import com.redhat.devtools.lsp4ij.LanguageServerManager;
import com.redhat.devtools.lsp4ij.ServerStatus; import com.redhat.devtools.lsp4ij.ServerStatus;
import kotlin.Unit; import kotlin.Unit;
@ -45,78 +40,9 @@ import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class ZLSStartupActivity implements ProjectActivity { public class ZLSStartupActivity implements ProjectActivity {
private static final Logger LOG = Logger.getInstance(ZLSStartupActivity.class);
public static List<String> getCommand(Project project) {
var svc = ZLSProjectSettingsService.getInstance(project);
val state = svc.getState();
var zlsPath = state.zlsPath;
if (!validatePath("ZLS Binary", zlsPath, false)) {
return null;
}
var configPath = state.zlsConfigPath;
boolean configOK = true;
if (!configPath.isBlank() && !validatePath("ZLS Config", configPath, false)) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Using default config path.",
NotificationType.INFORMATION));
configPath = null;
}
if (configPath == null || configPath.isBlank()) {
blk:
try {
val tmpFile = FileUtil.createTempFile("zigbrains-zls-autoconf", ".json", true).toPath();
val config = ZLSConfigProvider.findEnvironment(project);
if (StringUtil.isEmpty(config.zig_exe_path()) && StringUtil.isEmpty(config.zig_lib_path())) {
// TODO this generates unnecessary noise in non-zig projects, find an alternative.
// 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, 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;
}
}
var cmd = new ArrayList<String>();
cmd.add(zlsPath);
if (configOK) {
cmd.add("--config-path");
cmd.add(configPath);
}
if (state.debug) {
cmd.add("--enable-debug-log");
}
if (state.messageTrace) {
cmd.add("--enable-message-tracing");
}
if (System.getProperty("os.name").toLowerCase().contains("win")) {
for (int i = 0; i < cmd.size(); i++) {
if (cmd.get(i).contains(" ")) {
cmd.set(i, '"' + cmd.get(i) + '"');
}
}
}
return cmd;
}
public static void startLSP(Project project, boolean restart) { public static void startLSP(Project project, boolean restart) {
ApplicationManager.getApplication().executeOnPooledThread(() -> { ApplicationManager.getApplication().executeOnPooledThread(() -> {
val manager = LanguageServerManager.getInstance(project); val manager = LanguageServerManager.getInstance(project);
@ -127,36 +53,6 @@ public class ZLSStartupActivity implements ProjectActivity {
}); });
} }
private static boolean validatePath(String name, String pathTxt, boolean dir) {
if (pathTxt == null || pathTxt.isBlank()) {
return false;
}
Path path;
try {
path = Path.of(pathTxt);
} catch (InvalidPathException e) {
Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "No " + name, "Invalid " + name + " at path \"" + pathTxt + "\"",
NotificationType.ERROR));
return false;
}
if (!Files.exists(path)) {
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.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" is a " +
(Files.isDirectory(path) ? "directory" : "file") +
", expected a " + (dir ? "directory" : "file"),
NotificationType.ERROR));
return false;
}
return true;
}
private static boolean firstInit = true; private static boolean firstInit = true;
@Nullable @Nullable

View file

@ -1,54 +1,119 @@
package com.falsepattern.zigbrains.zig.lsp; package com.falsepattern.zigbrains.zig.lsp;
import com.falsepattern.zigbrains.zig.util.HighlightingUtil; import com.falsepattern.zigbrains.common.util.StringUtil;
import com.intellij.openapi.application.ApplicationManager; import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
import com.google.gson.Gson;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.redhat.devtools.lsp4ij.LSPIJUtils;
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider; import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider;
import lombok.val; import lombok.val;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
import org.eclipse.lsp4j.services.LanguageServer;
import java.io.File; import java.io.IOException;
import java.net.URI; import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class ZLSStreamConnectionProvider extends ProcessStreamConnectionProvider { public class ZLSStreamConnectionProvider extends ProcessStreamConnectionProvider {
private final Project project; private static final Logger LOG = Logger.getInstance(ZLSStreamConnectionProvider.class);
public ZLSStreamConnectionProvider(Project project) { public ZLSStreamConnectionProvider(Project project) {
this.project = project; super.setCommands(getCommand(project));
super.setCommands(ZLSStartupActivity.getCommand(project));
} }
public static List<String> getCommand(Project project) {
var svc = ZLSProjectSettingsService.getInstance(project);
val state = svc.getState();
var zlsPath = state.zlsPath;
if (!validatePath("ZLS Binary", zlsPath, false)) {
return null;
}
var configPath = state.zlsConfigPath;
boolean configOK = true;
if (!configPath.isBlank() && !validatePath("ZLS Config", configPath, false)) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Using default config path.",
NotificationType.INFORMATION));
configPath = null;
}
if (configPath == null || configPath.isBlank()) {
blk:
try {
val tmpFile = FileUtil.createTempFile("zigbrains-zls-autoconf", ".json", true).toPath();
val config = ZLSConfigProvider.findEnvironment(project);
if (StringUtil.isEmpty(config.zig_exe_path()) && StringUtil.isEmpty(config.zig_lib_path())) {
// TODO this generates unnecessary noise in non-zig projects, find an alternative.
// 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, 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;
}
}
var cmd = new ArrayList<String>();
cmd.add(zlsPath);
if (configOK) {
cmd.add("--config-path");
cmd.add(configPath);
}
@Override if (state.debug) {
public void handleMessage(Message message, LanguageServer languageServer, VirtualFile rootUri) { cmd.add("--enable-debug-log");
if (message instanceof NotificationMessage notif) {
switch (notif.getMethod()) {
case "textDocument/didOpen", "textDocument/didChange" -> ApplicationManager.getApplication().executeOnPooledThread(() -> {
val params = notif.getParams();
VirtualFile file;
if (params instanceof DidOpenTextDocumentParams didOpen) {
file = VfsUtil.findFileByIoFile(new File(URI.create(didOpen.getTextDocument().getUri())), true);
} else if (params instanceof DidChangeTextDocumentParams didChange) {
file = VfsUtil.findFileByIoFile(new File(URI.create(didChange.getTextDocument().getUri())), true);
} else {
file = null;
} }
if (file == null) if (state.messageTrace) {
return; cmd.add("--enable-message-tracing");
val editors = LSPIJUtils.editorsForFile(file, project);
for (val editor: editors) {
HighlightingUtil.refreshHighlighting(editor);
} }
}); if (System.getProperty("os.name").toLowerCase().contains("win")) {
for (int i = 0; i < cmd.size(); i++) {
if (cmd.get(i).contains(" ")) {
cmd.set(i, '"' + cmd.get(i) + '"');
} }
} }
super.handleMessage(message, languageServer, rootUri); }
return cmd;
}
private static boolean validatePath(String name, String pathTxt, boolean dir) {
if (pathTxt == null || pathTxt.isBlank()) {
return false;
}
Path path;
try {
path = Path.of(pathTxt);
} catch (InvalidPathException e) {
Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "No " + name, "Invalid " + name + " at path \"" + pathTxt + "\"",
NotificationType.ERROR));
return false;
}
if (!Files.exists(path)) {
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.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" is a " +
(Files.isDirectory(path) ? "directory" : "file") +
", expected a " + (dir ? "directory" : "file"),
NotificationType.ERROR));
return false;
}
return true;
} }
} }

View file

@ -1,281 +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.zig.util;
import com.falsepattern.zigbrains.zig.ide.SemaEdit;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.redhat.devtools.lsp4ij.LSPFileSupport;
import com.redhat.devtools.lsp4ij.LSPIJUtils;
import com.redhat.devtools.lsp4ij.LanguageServerItem;
import com.redhat.devtools.lsp4ij.LanguageServerWrapper;
import com.redhat.devtools.lsp4ij.LanguageServersRegistry;
import com.redhat.devtools.lsp4ij.LanguageServiceAccessor;
import com.redhat.devtools.lsp4ij.internal.CompletableFutures;
import lombok.SneakyThrows;
import lombok.val;
import org.eclipse.lsp4j.SemanticTokens;
import org.eclipse.lsp4j.SemanticTokensDelta;
import org.eclipse.lsp4j.SemanticTokensDeltaParams;
import org.eclipse.lsp4j.SemanticTokensEdit;
import org.eclipse.lsp4j.SemanticTokensLegend;
import org.eclipse.lsp4j.SemanticTokensParams;
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageServer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
public class HighlightingUtil {
private static final Key<Integer> HL_HASH = Key.create("HIGHLIGHTING_HASH");
public static void refreshHighlighting(Editor editor) {
var app = ApplicationManager.getApplication();
app.executeOnPooledThread(() -> {
if (editor.isDisposed())
return;
val project = editor.getProject();
if (project == null)
return;
val highlightRanges = semanticHighlighting(editor);
var newHash = highlightRanges.hashCode();
if (editor.isDisposed()) {
return;
}
var markup = editor.getMarkupModel();
var hash = markup.getUserData(HL_HASH);
if (hash != null && hash == newHash) {
return;
}
markup.putUserData(HL_HASH, newHash);
var highlightersSorted = new ArrayList<>(Stream.of(WriteAction.computeAndWait(markup::getAllHighlighters))
.map(hl -> Map.entry(hl, hl.getStartOffset()))
.sorted(Comparator.comparingInt(Map.Entry::getValue))
.toList());
val writes = new ArrayList<Runnable>();
app.runReadAction(() -> {
if (editor.isDisposed()) {
return;
}
if (highlightRanges.size() == 1 &&
highlightRanges.get(0).start() == 0 &&
highlightRanges.get(0).remove() == -1) {
writes.add(markup::removeAllHighlighters);
}
var documentLength = editor.getDocument().getTextLength();
for (var range : highlightRanges) {
var start = range.start();
var toRemove = range.remove();
if (toRemove > 0) {
for (int i = 0; i < highlightersSorted.size(); i++) {
var hl = highlightersSorted.get(i);
if (hl.getValue() >= start) {
for (int j = 0; j < toRemove; j++) {
highlightersSorted.remove(i);
val key = hl.getKey();
writes.add(() -> markup.removeHighlighter(key));
}
}
}
}
for (var edit : range.add()) {
var editStart = edit.start();
var end = edit.end();
if (end > documentLength || editStart > documentLength) {
continue;
}
val color = edit.color();
writes.add(() -> markup.addRangeHighlighter(color, editStart, end, HighlighterLayer.ADDITIONAL_SYNTAX, HighlighterTargetArea.EXACT_RANGE));
}
}
app.invokeLater(() -> {
for (val write: writes)
write.run();
});
});
});
}
private static String previousResultID = null;
private static Optional<Pair<LanguageServerItem, SemanticTokensLegend>> sematicLegend(Collection<LanguageServerItem> servers) {
for (val server: servers) {
val caps = server.getServerCapabilities();
if (caps == null)
continue;
val provider = caps.getSemanticTokensProvider();
if (provider == null)
continue;
val legend = provider.getLegend();
if (legend == null)
continue;
return Optional.of(new Pair<>(server, legend));
}
return Optional.empty();
}
@SneakyThrows
private static List<SemaEdit> semanticHighlighting(Editor editor) {
var result = new ArrayList<SemaEdit>();
val virtualFile = editor.getVirtualFile();
if (virtualFile == null)
return result;
val project = editor.getProject();
if (project == null)
return result;
val definition = LanguageServersRegistry.getInstance().getServerDefinition("ZigBrains");
val servers = LanguageServiceAccessor.getInstance(project).getLanguageServers(virtualFile, (ignored) -> true, definition).get();
val legendOptional = sematicLegend(servers);
if (legendOptional.isEmpty()) {
return result;
}
val legend = legendOptional.get().second;
val server = legendOptional.get().first.getServer();
CompletableFuture<Either<SemanticTokens, SemanticTokensDelta>> request = null;
val service = server.getTextDocumentService();
if (previousResultID == null) {
var param = new SemanticTokensParams(LSPIJUtils.toTextDocumentIdentifier(editor.getVirtualFile()));
request = service.semanticTokensFull(param)
.thenApply(tokens -> tokens != null ? Either.forLeft(tokens) : null);
} else {
var param = new SemanticTokensDeltaParams(LSPIJUtils.toTextDocumentIdentifier(editor.getVirtualFile()), previousResultID);
request = service.semanticTokensFullDelta(param);
}
try {
CompletableFutures.waitUntilDone(request);
if (!CompletableFutures.isDoneNormally(request))
return result;
var res = request.getNow(null);
if (res == null) {
return result;
}
if (res.isLeft()) {
var response = res.getLeft();
previousResultID = response.getResultId();
var responseData = response.getData();
result.add(new SemaEdit(0, -1, TokenDecoder.decodePayload(0, editor, legend, responseData)));
} else {
var response = res.getRight();
previousResultID = response.getResultId();
var edits = response.getEdits();
for (SemanticTokensEdit edit : edits) {
var add = TokenDecoder.decodePayload(0, editor, legend, edit.getData());
result.add(new SemaEdit(edit.getStart(), edit.getDeleteCount(), add));
}
}
} catch (CancellationException e) {
return null;
} catch (ExecutionException e) {
System.err.println("Error while consuming LSP 'textDocument/semanticTokens' request");
e.printStackTrace();
}
return result;
}
// public static void refreshHighlighting(EditorEventManager eem) {
// var app = ApplicationManager.getApplication();
// app.executeOnPooledThread(() -> {
// if (!(eem instanceof ZLSEditorEventManager manager)) {
// return;
// }
// var editor = manager.editor;
// if (editor == null) {
// return;
// }
// if (editor.isDisposed()) {
// return;
// }
// var highlightRanges = manager.semanticHighlighting();
// var newHash = highlightRanges.hashCode();
// if (editor.isDisposed()) {
// return;
// }
// var markup = editor.getMarkupModel();
// var hash = markup.getUserData(HL_HASH);
// if (hash != null && hash == newHash) {
// return;
// }
// markup.putUserData(HL_HASH, newHash);
// var highlightersSorted = new ArrayList<>(Stream.of(WriteAction.computeAndWait(markup::getAllHighlighters))
// .map(hl -> Map.entry(hl, hl.getStartOffset()))
// .sorted(Comparator.comparingInt(Map.Entry::getValue))
// .toList());
// val writes = new ArrayList<Runnable>();
// app.runReadAction(() -> {
// if (editor.isDisposed()) {
// return;
// }
// if (highlightRanges.size() == 1 &&
// highlightRanges.get(0).start() == 0 &&
// highlightRanges.get(0).remove() == -1) {
// writes.add(markup::removeAllHighlighters);
// }
// var documentLength = editor.getDocument().getTextLength();
// for (var range : highlightRanges) {
// var start = range.start();
// var toRemove = range.remove();
// if (toRemove > 0) {
// for (int i = 0; i < highlightersSorted.size(); i++) {
// var hl = highlightersSorted.get(i);
// if (hl.getValue() >= start) {
// for (int j = 0; j < toRemove; j++) {
// highlightersSorted.remove(i);
// val key = hl.getKey();
// writes.add(() -> markup.removeHighlighter(key));
// }
// }
// }
// }
// for (var edit : range.add()) {
// var editStart = edit.start();
// var end = edit.end();
// if (end > documentLength || editStart > documentLength) {
// continue;
// }
// val color = edit.color();
// writes.add(() -> markup.addRangeHighlighter(color, editStart, end, HighlighterLayer.ADDITIONAL_SYNTAX, HighlighterTargetArea.EXACT_RANGE));
// }
// }
// app.invokeLater(() -> {
// for (val write: writes)
// write.run();
// });
// });
// });
// }
}

View file

@ -1,80 +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.zig.util;
import com.falsepattern.zigbrains.zig.ide.SemaRange;
import com.falsepattern.zigbrains.zig.ide.ZigAttributes;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.util.Computable;
import org.eclipse.lsp4j.SemanticTokensLegend;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class TokenDecoder {
public static List<SemaRange> decodePayload(int baseOffset, Editor editor, SemanticTokensLegend legend, List<Integer> responseData) {
var result = new ArrayList<SemaRange>();
var application = ApplicationManager.getApplication();
int dataSize = responseData.size();
var startPos = application.runReadAction(
(Computable<LogicalPosition>) () -> editor.offsetToLogicalPosition(baseOffset));
Token prevToken = new Token(startPos.line, startPos.column, 0, 0, 0);
var types = legend.getTokenTypes();
var modifiers = legend.getTokenModifiers();
var modCount = Math.min(31, modifiers.size());
for (int i = 0; i <= dataSize - 5; i += 5) {
var token = Token.from(prevToken, responseData, i);
var logiPosStart = new LogicalPosition(token.line(), token.start());
int tokenStartOffset =
application.runReadAction((Computable<Integer>) () -> editor.logicalPositionToOffset(logiPosStart));
var type = types.size() > token.type() ? types.get(token.type()) : null;
Set<String> modifierSet = null;
if (token.modifiers() != 0) {
modifierSet = new HashSet<>();
for (int m = 0; m < modCount; m++) {
if ((token.modifiers() & (1 << m)) != 0) {
modifierSet.add(modifiers.get(m));
}
}
}
var key = ZigAttributes.getKey(type, modifierSet);
key.ifPresent(textAttributesKey -> result.add(
new SemaRange(tokenStartOffset, tokenStartOffset + token.length(), textAttributesKey)));
prevToken = token;
}
return result;
}
private record Token(int line, int start, int length, int type, int modifiers) {
public static Token from(Token prevToken, List<Integer> data, int index) {
int line = data.get(index);
int start = data.get(index + 1);
if (prevToken != null) {
if (line == 0) {
start += prevToken.start();
}
line += prevToken.line();
}
return new Token(line, start, data.get(index + 2), data.get(index + 3), data.get(index + 4));
}
}
}

View file

@ -15,6 +15,7 @@
--> -->
<idea-plugin package="com.falsepattern.zigbrains.zig"> <idea-plugin package="com.falsepattern.zigbrains.zig">
<depends>com.redhat.devtools.lsp4ij</depends>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<fileType name="Zig File" <fileType name="Zig File"
implementationClass="com.falsepattern.zigbrains.zig.ZigFileType" implementationClass="com.falsepattern.zigbrains.zig.ZigFileType"
@ -47,6 +48,11 @@
<lang.foldingBuilder language="Zig" <lang.foldingBuilder language="Zig"
implementationClass="com.redhat.devtools.lsp4ij.features.foldingRange.LSPFoldingRangeBuilder" implementationClass="com.redhat.devtools.lsp4ij.features.foldingRange.LSPFoldingRangeBuilder"
order="first"/> order="first"/>
<notificationGroup displayType="BALLOON"
bundle="zigbrains.Bundle"
key="notif-zls-error"
id="ZigBrains.ZLS"/>
</extensions> </extensions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains"> <extensions defaultExtensionNs="com.falsepattern.zigbrains">
@ -63,6 +69,7 @@ The <a href="https://github.com/Zigtools/ZLS">Zig Language Server</a>, for ZigBr
</description> </description>
</server> </server>
<languageMapping language="Zig" serverId="ZigBrains" languageId="zig"/> <languageMapping language="Zig" serverId="ZigBrains" languageId="zig"/>
<semanticTokensColorsProvider serverId="ZigBrains"
class="com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokensColorsProvider"/>
</extensions> </extensions>
</idea-plugin> </idea-plugin>