feat!: Migrate semantic tokens to lsp4ij
This commit is contained in:
parent
cde7033130
commit
4ec98e5322
16 changed files with 219 additions and 902 deletions
|
@ -20,7 +20,7 @@ Changelog structure reference:
|
|||
### Changed
|
||||
|
||||
- LSP
|
||||
- Migrated to Red Hat's LSP4IJ LSP backend.
|
||||
- Migrated to Red Hat's LSP4IJ LSP adapter.
|
||||
|
||||
## [15.2.0]
|
||||
|
||||
|
|
|
@ -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 lsp4jVersion = "0.21.1"
|
||||
val lsp4ijVersion = "0.3.0-20240702-174041"
|
||||
val lsp4ijVersion = "0.3.0-20240704-134935"
|
||||
|
||||
val lsp4ijNightly = lsp4ijVersion.contains("-")
|
||||
val lsp4ijDepString = "${if (lsp4ijNightly) "nightly." else ""}com.jetbrains.plugins:com.redhat.devtools.lsp4ij:$lsp4ijVersion"
|
||||
|
@ -390,6 +390,7 @@ dependencies {
|
|||
"idea" -> intellijIdeaCommunity(ideaVersion)
|
||||
"clion" -> clion(clionVersion)
|
||||
}
|
||||
plugin(lsp4ijPluginString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
inlayprovider=ZLS Inlay Provider
|
||||
notif-zb=ZigBrains general 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-warn=ZigBrains debugger warning
|
||||
notif-debug-error=ZigBrains debugger error
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import com.intellij.openapi.editor.colors.TextAttributesKey;
|
|||
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
|
||||
import com.intellij.psi.TokenType;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensHighlightingColors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -35,44 +36,44 @@ public class ZigSyntaxHighlighter extends SyntaxHighlighterBase {
|
|||
// @formatter:off
|
||||
public static final TextAttributesKey
|
||||
BAD_CHAR = createKey("BAD_CHARACTER" , HighlighterColors.BAD_CHARACTER ),
|
||||
BUILTIN = createKey("BUILTIN" , DefaultLanguageHighlighterColors.STATIC_METHOD ),
|
||||
CHAR = createKey("CHAR" , DefaultLanguageHighlighterColors.NUMBER ),
|
||||
COMMENT = createKey("COMMENT" , DefaultLanguageHighlighterColors.LINE_COMMENT ),
|
||||
BUILTIN = createKey("BUILTIN" , SemanticTokensHighlightingColors.STATIC_METHOD ),
|
||||
CHAR = createKey("CHAR" , SemanticTokensHighlightingColors.NUMBER ),
|
||||
COMMENT = createKey("COMMENT" , SemanticTokensHighlightingColors.COMMENT ),
|
||||
COMMENT_DOC = createKey("COMMENT_DOC" , DefaultLanguageHighlighterColors.DOC_COMMENT ),
|
||||
ENUM_DECL = createKey("ENUM_DECL" , DefaultLanguageHighlighterColors.CLASS_NAME ),
|
||||
ENUM_REF = createKey("ENUM" , DefaultLanguageHighlighterColors.CLASS_REFERENCE ),
|
||||
ENUM_MEMBER_DECL = createKey("ENUM_MEMBER_DECL" , DefaultLanguageHighlighterColors.STATIC_FIELD ),
|
||||
ENUM_DECL = createKey("ENUM_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||
ENUM_REF = createKey("ENUM" , SemanticTokensHighlightingColors.CLASS ),
|
||||
ENUM_MEMBER_DECL = createKey("ENUM_MEMBER_DECL" , SemanticTokensHighlightingColors.STATIC_PROPERTY ),
|
||||
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 ),
|
||||
PROPERTY_DECL = createKey("PROPERTY_DECL" , DefaultLanguageHighlighterColors.INSTANCE_FIELD ),
|
||||
PROPERTY_DECL = createKey("PROPERTY_DECL" , SemanticTokensHighlightingColors.PROPERTY ),
|
||||
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_REF = createKey("FUNCTION" , DefaultLanguageHighlighterColors.FUNCTION_CALL ),
|
||||
FUNCTION_REF = createKey("FUNCTION" , SemanticTokensHighlightingColors.FUNCTION ),
|
||||
FUNCTION_REF_GEN = createKey("FUNCTION_GEN" , FUNCTION_REF ),
|
||||
KEYWORD = createKey("KEYWORD" , DefaultLanguageHighlighterColors.KEYWORD ),
|
||||
LABEL_DECL = createKey("LABEL_DECL" , DefaultLanguageHighlighterColors.LABEL ),
|
||||
KEYWORD = createKey("KEYWORD" , SemanticTokensHighlightingColors.KEYWORD ),
|
||||
LABEL_DECL = createKey("LABEL_DECL" , SemanticTokensHighlightingColors.LABEL ),
|
||||
LABEL_REF = createKey("LABEL" , LABEL_DECL ),
|
||||
METHOD_DECL = createKey("METHOD_DECL" , FUNCTION_DECL ),
|
||||
METHOD_DECL_GEN = createKey("METHOD_DECL_GEN" , METHOD_DECL ),
|
||||
METHOD_REF = createKey("METHOD" , FUNCTION_REF ),
|
||||
METHOD_REF_GEN = createKey("METHOD_GEN" , METHOD_REF ),
|
||||
NAMESPACE_DECL = createKey("NAMESPACE_DECL" , DefaultLanguageHighlighterColors.CLASS_REFERENCE ),
|
||||
NAMESPACE_REF = createKey("NAMESPACE" , DefaultLanguageHighlighterColors.CLASS_NAME ),
|
||||
NUMBER = createKey("NUMBER" , DefaultLanguageHighlighterColors.NUMBER ),
|
||||
OPERATOR = createKey("OPERATOR" , DefaultLanguageHighlighterColors.OPERATION_SIGN ),
|
||||
PARAMETER = createKey("PARAMETER" , DefaultLanguageHighlighterColors.PARAMETER ),
|
||||
STRING = createKey("STRING" , DefaultLanguageHighlighterColors.STRING ),
|
||||
STRUCT_DECL = createKey("STRUCT_DECL" , DefaultLanguageHighlighterColors.CLASS_NAME ),
|
||||
STRUCT_REF = createKey("STRUCT" , DefaultLanguageHighlighterColors.CLASS_REFERENCE ),
|
||||
TYPE_DECL = createKey("TYPE_DECL" , DefaultLanguageHighlighterColors.CLASS_NAME ),
|
||||
NAMESPACE_DECL = createKey("NAMESPACE_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||
NAMESPACE_REF = createKey("NAMESPACE" , SemanticTokensHighlightingColors.CLASS ),
|
||||
NUMBER = createKey("NUMBER" , SemanticTokensHighlightingColors.NUMBER ),
|
||||
OPERATOR = createKey("OPERATOR" , SemanticTokensHighlightingColors.OPERATOR ),
|
||||
PARAMETER = createKey("PARAMETER" , SemanticTokensHighlightingColors.PARAMETER ),
|
||||
STRING = createKey("STRING" , SemanticTokensHighlightingColors.STRING ),
|
||||
STRUCT_DECL = createKey("STRUCT_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||
STRUCT_REF = createKey("STRUCT" , SemanticTokensHighlightingColors.CLASS ),
|
||||
TYPE_DECL = createKey("TYPE_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||
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_PARAM = createKey("TYPE_PARAM" , TYPE_REF ),
|
||||
TYPE_PARAM = createKey("TYPE_PARAM" , SemanticTokensHighlightingColors.TYPE_PARAMETER ),
|
||||
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_REF = createKey("VARIABLE" , VARIABLE_DECL ),
|
||||
VARIABLE_REF_DEPR = createKey("VARIABLE_REF_DEPL" , VARIABLE_REF );
|
||||
|
|
|
@ -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) {
|
||||
}
|
|
@ -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) {
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -9,11 +9,4 @@ public class ZLSLanguageClient extends LanguageClientImpl {
|
|||
public ZLSLanguageClient(Project project) {
|
||||
super(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> refreshSemanticTokens() {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,6 @@ public class ZLSLanguageServerFactory implements LanguageServerFactory, Language
|
|||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled, @NotNull Project project) {
|
||||
this.enabled = enabled && ZLSStartupActivity.getCommand(project) != null;
|
||||
this.enabled = enabled && ZLSStreamConnectionProvider.getCommand(project) != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -18,10 +18,7 @@ package com.falsepattern.zigbrains.zig.lsp;
|
|||
|
||||
import com.falsepattern.zigbrains.ZigBundle;
|
||||
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.google.gson.Gson;
|
||||
import com.intellij.ide.BrowserUtil;
|
||||
import com.intellij.ide.plugins.PluginManager;
|
||||
import com.intellij.ide.plugins.PluginManagerConfigurable;
|
||||
|
@ -31,12 +28,10 @@ import com.intellij.notification.NotificationType;
|
|||
import com.intellij.notification.Notifications;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.PluginId;
|
||||
import com.intellij.openapi.options.ShowSettingsUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.startup.ProjectActivity;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.redhat.devtools.lsp4ij.LanguageServerManager;
|
||||
import com.redhat.devtools.lsp4ij.ServerStatus;
|
||||
import kotlin.Unit;
|
||||
|
@ -45,78 +40,9 @@ 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;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
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;
|
||||
|
||||
@Nullable
|
||||
|
|
|
@ -1,54 +1,119 @@
|
|||
package com.falsepattern.zigbrains.zig.lsp;
|
||||
|
||||
import com.falsepattern.zigbrains.zig.util.HighlightingUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.falsepattern.zigbrains.common.util.StringUtil;
|
||||
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.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.redhat.devtools.lsp4ij.LSPIJUtils;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider;
|
||||
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.net.URI;
|
||||
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;
|
||||
|
||||
public class ZLSStreamConnectionProvider extends ProcessStreamConnectionProvider {
|
||||
private final Project project;
|
||||
private static final Logger LOG = Logger.getInstance(ZLSStreamConnectionProvider.class);
|
||||
public ZLSStreamConnectionProvider(Project project) {
|
||||
this.project = project;
|
||||
super.setCommands(ZLSStartupActivity.getCommand(project));
|
||||
super.setCommands(getCommand(project));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message, LanguageServer languageServer, VirtualFile rootUri) {
|
||||
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)
|
||||
return;
|
||||
val editors = LSPIJUtils.editorsForFile(file, project);
|
||||
for (val editor: editors) {
|
||||
HighlightingUtil.refreshHighlighting(editor);
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
super.handleMessage(message, languageServer, rootUri);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
-->
|
||||
|
||||
<idea-plugin package="com.falsepattern.zigbrains.zig">
|
||||
<depends>com.redhat.devtools.lsp4ij</depends>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<fileType name="Zig File"
|
||||
implementationClass="com.falsepattern.zigbrains.zig.ZigFileType"
|
||||
|
@ -47,6 +48,11 @@
|
|||
<lang.foldingBuilder language="Zig"
|
||||
implementationClass="com.redhat.devtools.lsp4ij.features.foldingRange.LSPFoldingRangeBuilder"
|
||||
order="first"/>
|
||||
|
||||
<notificationGroup displayType="BALLOON"
|
||||
bundle="zigbrains.Bundle"
|
||||
key="notif-zls-error"
|
||||
id="ZigBrains.ZLS"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
|
||||
|
@ -63,6 +69,7 @@ The <a href="https://github.com/Zigtools/ZLS">Zig Language Server</a>, for ZigBr
|
|||
</description>
|
||||
</server>
|
||||
<languageMapping language="Zig" serverId="ZigBrains" languageId="zig"/>
|
||||
|
||||
<semanticTokensColorsProvider serverId="ZigBrains"
|
||||
class="com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokensColorsProvider"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
Loading…
Add table
Reference in a new issue