backport: 19.1.0
This commit is contained in:
parent
38aa656f46
commit
92f8fa2feb
20 changed files with 568 additions and 326 deletions
|
@ -17,12 +17,17 @@ Changelog structure reference:
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [19.0.0]
|
## [19.1.0]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Zig
|
- Zig
|
||||||
- Basic language injections in strings
|
- Language injections in strings
|
||||||
|
- Syntax highlighting for escape sequences in strings
|
||||||
|
|
||||||
|
- LSP
|
||||||
|
- Option to toggle inlay hints on/off
|
||||||
|
- Compacted error{...} blocks in inlay hints
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import de.undercouch.gradle.tasks.download.Download
|
||||||
import groovy.xml.XmlParser
|
import groovy.xml.XmlParser
|
||||||
import org.jetbrains.changelog.Changelog
|
import org.jetbrains.changelog.Changelog
|
||||||
import org.jetbrains.changelog.markdownToHTML
|
import org.jetbrains.changelog.markdownToHTML
|
||||||
|
import org.jetbrains.grammarkit.tasks.GenerateLexerTask
|
||||||
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
|
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
|
||||||
import org.jetbrains.intellij.platform.gradle.tasks.PatchPluginXmlTask
|
import org.jetbrains.intellij.platform.gradle.tasks.PatchPluginXmlTask
|
||||||
import org.jetbrains.intellij.platform.gradle.tasks.PublishPluginTask
|
import org.jetbrains.intellij.platform.gradle.tasks.PublishPluginTask
|
||||||
|
@ -211,11 +212,21 @@ project(":zig") {
|
||||||
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zig/lexer")
|
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zig/lexer")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register<GenerateLexerTask>("generateStringLexer") {
|
||||||
|
sourceFile = file("src/main/grammar/ZigString.flex")
|
||||||
|
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zig/stringlexer")
|
||||||
|
purgeOldFiles = true
|
||||||
|
}
|
||||||
|
|
||||||
generateParser {
|
generateParser {
|
||||||
sourceFile = file("src/main/grammar/Zig.bnf")
|
sourceFile = file("src/main/grammar/Zig.bnf")
|
||||||
pathToParser = "${rootPackagePath}/zig/psi/ZigParser.java"
|
pathToParser = "${rootPackagePath}/zig/psi/ZigParser.java"
|
||||||
pathToPsiRoot = "${rootPackagePath}/zig/psi"
|
pathToPsiRoot = "${rootPackagePath}/zig/psi"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
named("generateGrammars") {
|
||||||
|
dependsOn("generateStringLexer")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ baseIDE=clion
|
||||||
ideaVersion=2024.2.2
|
ideaVersion=2024.2.2
|
||||||
clionVersion=2024.2.2
|
clionVersion=2024.2.2
|
||||||
|
|
||||||
pluginVersion=19.0.0
|
pluginVersion=19.1.0
|
||||||
|
|
||||||
# Gradle Releases -> https://github.com/gradle/gradle/releases
|
# Gradle Releases -> https://github.com/gradle/gradle/releases
|
||||||
gradleVersion=8.10.2
|
gradleVersion=8.10.2
|
||||||
|
|
|
@ -54,6 +54,8 @@
|
||||||
|
|
||||||
<lang.elementManipulator forClass="com.falsepattern.zigbrains.zig.psi.ZigStringLiteral"
|
<lang.elementManipulator forClass="com.falsepattern.zigbrains.zig.psi.ZigStringLiteral"
|
||||||
implementationClass="com.falsepattern.zigbrains.zig.psi.ZigStringElementManipulator"/>
|
implementationClass="com.falsepattern.zigbrains.zig.psi.ZigStringElementManipulator"/>
|
||||||
|
<languageInjectionPerformer language="Zig"
|
||||||
|
implementationClass="com.falsepattern.zigbrains.zig.psi.ZigLanguageInjectionPerformer"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
|
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
|
||||||
|
|
|
@ -90,12 +90,12 @@ char_char= {mb_utf8_literal}
|
||||||
| {char_escape}
|
| {char_escape}
|
||||||
| {ascii_char_not_nl_slash_squote}
|
| {ascii_char_not_nl_slash_squote}
|
||||||
|
|
||||||
string_char= {char_escape}
|
string_char= \\ .
|
||||||
| [^\\\"\n]
|
| [^\"\n]
|
||||||
|
|
||||||
|
all_nl_wrap=[^\n]* [ \n]*
|
||||||
|
all_nl_nowrap=[^\n]* \n
|
||||||
|
|
||||||
CONTAINER_DOC_COMMENT=("//!" [^\n]* [ \n]*)+
|
|
||||||
DOC_COMMENT=("///" [^\n]* [ \n]*)+
|
|
||||||
LINE_COMMENT="//" [^\n]* | "////" [^\n]*
|
|
||||||
|
|
||||||
FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})?
|
FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})?
|
||||||
| {dec_int} "." {dec_int} ([eE] [-+]? {dec_int})?
|
| {dec_int} "." {dec_int} ([eE] [-+]? {dec_int})?
|
||||||
|
@ -124,14 +124,17 @@ BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
|
||||||
|
|
||||||
//Comments
|
//Comments
|
||||||
|
|
||||||
<YYINITIAL> "//!" { yypushback(3); yybegin(CDOC_CMT); }
|
<YYINITIAL> "//!" { yybegin(CDOC_CMT); }
|
||||||
<CDOC_CMT> {CONTAINER_DOC_COMMENT} { yybegin(YYINITIAL); return CONTAINER_DOC_COMMENT; }
|
<CDOC_CMT> {all_nl_wrap} "//!" { }
|
||||||
|
<CDOC_CMT> {all_nl_nowrap} { yybegin(YYINITIAL); return CONTAINER_DOC_COMMENT; }
|
||||||
|
|
||||||
<YYINITIAL> "///" { yypushback(3); yybegin(DOC_CMT); }
|
<YYINITIAL> "///" { yybegin(DOC_CMT); }
|
||||||
<DOC_CMT> {DOC_COMMENT} { yybegin(YYINITIAL); return DOC_COMMENT; }
|
<DOC_CMT> {all_nl_wrap} "///" { }
|
||||||
|
<DOC_CMT> {all_nl_nowrap} { yybegin(YYINITIAL); return DOC_COMMENT; }
|
||||||
|
|
||||||
<YYINITIAL> "//" { yypushback(2); yybegin(LINE_CMT); }
|
<YYINITIAL> "//" { yybegin(LINE_CMT); }
|
||||||
<LINE_CMT> {LINE_COMMENT} { yybegin(YYINITIAL); return LINE_COMMENT; }
|
<LINE_CMT> {all_nl_wrap} "//" { }
|
||||||
|
<LINE_CMT> {all_nl_nowrap} { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||||
|
|
||||||
//Symbols
|
//Symbols
|
||||||
<YYINITIAL> "&" { return AMPERSAND; }
|
<YYINITIAL> "&" { return AMPERSAND; }
|
||||||
|
@ -261,9 +264,9 @@ BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
|
||||||
<YYINITIAL> "\"" { yybegin(STR_LIT); }
|
<YYINITIAL> "\"" { yybegin(STR_LIT); }
|
||||||
<STR_LIT> {string_char}*"\"" { yybegin(YYINITIAL); return STRING_LITERAL_SINGLE; }
|
<STR_LIT> {string_char}*"\"" { yybegin(YYINITIAL); return STRING_LITERAL_SINGLE; }
|
||||||
<STR_LIT> [^] { yypushback(1); yybegin(UNT_QUOT); }
|
<STR_LIT> [^] { yypushback(1); yybegin(UNT_QUOT); }
|
||||||
<YYINITIAL> "\\\\" { yypushback(2); yybegin(STR_MULT_LINE); }
|
<YYINITIAL> "\\\\" { yybegin(STR_MULT_LINE); }
|
||||||
<STR_MULT_LINE> [^\n]* [ \n]* "\\\\" { }
|
<STR_MULT_LINE> {all_nl_wrap} "\\\\" { }
|
||||||
<STR_MULT_LINE> [^\n]* \n { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
<STR_MULT_LINE> {all_nl_nowrap} { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||||
|
|
||||||
<YYINITIAL> {IDENTIFIER_PLAIN} { return IDENTIFIER; }
|
<YYINITIAL> {IDENTIFIER_PLAIN} { return IDENTIFIER; }
|
||||||
<YYINITIAL> "@\"" { yybegin(ID_QUOT); }
|
<YYINITIAL> "@\"" { yybegin(ID_QUOT); }
|
||||||
|
|
60
modules/zig/src/main/grammar/ZigString.flex
Normal file
60
modules/zig/src/main/grammar/ZigString.flex
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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.stringlexer;
|
||||||
|
|
||||||
|
import com.intellij.lexer.FlexLexer;
|
||||||
|
import com.intellij.psi.tree.IElementType;
|
||||||
|
|
||||||
|
import static com.intellij.psi.TokenType.WHITE_SPACE;
|
||||||
|
import static com.intellij.psi.TokenType.BAD_CHARACTER;
|
||||||
|
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.*;
|
||||||
|
import static com.intellij.psi.StringEscapesTokenTypes.*;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
%public
|
||||||
|
%class ZigStringLexer
|
||||||
|
%implements FlexLexer
|
||||||
|
%function advance
|
||||||
|
%type IElementType
|
||||||
|
|
||||||
|
hex=[0-9a-fA-F]
|
||||||
|
|
||||||
|
char_escape_unicode= "\\x" {hex} {hex} | "\\u{" {hex}+ "}"
|
||||||
|
char_escape_unicode_invalid= "\\x" | "\\u"
|
||||||
|
|
||||||
|
char_escape_single_valid= "\\" [nr\\t'\"]
|
||||||
|
char_escape_single_invalid= "\\" [^nr\\t'\"]
|
||||||
|
|
||||||
|
non_escape= [^\\\"\n]
|
||||||
|
|
||||||
|
|
||||||
|
%state STR
|
||||||
|
%%
|
||||||
|
|
||||||
|
|
||||||
|
<YYINITIAL> {
|
||||||
|
"\"" { yybegin(STR); return STRING_LITERAL_SINGLE; }
|
||||||
|
[^] { return STRING_LITERAL_SINGLE; }
|
||||||
|
}
|
||||||
|
|
||||||
|
<STR> {
|
||||||
|
{char_escape_unicode} { return VALID_STRING_ESCAPE_TOKEN; }
|
||||||
|
{char_escape_unicode_invalid} { return INVALID_UNICODE_ESCAPE_TOKEN; }
|
||||||
|
{char_escape_single_valid} { return VALID_STRING_ESCAPE_TOKEN; }
|
||||||
|
{char_escape_single_invalid} { return INVALID_CHARACTER_ESCAPE_TOKEN; }
|
||||||
|
[^] { return STRING_LITERAL_SINGLE; }
|
||||||
|
}
|
|
@ -63,6 +63,9 @@ public class ZigColorSettingsPage implements ColorSettingsPage {
|
||||||
new AttributesDescriptor("Property//Declaration", ZigSyntaxHighlighter.PROPERTY_DECL),
|
new AttributesDescriptor("Property//Declaration", ZigSyntaxHighlighter.PROPERTY_DECL),
|
||||||
new AttributesDescriptor("Property//Reference", ZigSyntaxHighlighter.PROPERTY_REF),
|
new AttributesDescriptor("Property//Reference", ZigSyntaxHighlighter.PROPERTY_REF),
|
||||||
new AttributesDescriptor("String", ZigSyntaxHighlighter.STRING),
|
new AttributesDescriptor("String", ZigSyntaxHighlighter.STRING),
|
||||||
|
new AttributesDescriptor("String//Escape", ZigSyntaxHighlighter.STRING_ESC_V),
|
||||||
|
new AttributesDescriptor("String//Escape//Invalid char", ZigSyntaxHighlighter.STRING_ESC_I_C),
|
||||||
|
new AttributesDescriptor("String//Escape//Invalid unicode", ZigSyntaxHighlighter.STRING_ESC_I_U),
|
||||||
new AttributesDescriptor("Struct//Declaration", ZigSyntaxHighlighter.STRUCT_DECL),
|
new AttributesDescriptor("Struct//Declaration", ZigSyntaxHighlighter.STRUCT_DECL),
|
||||||
new AttributesDescriptor("Struct//Reference", ZigSyntaxHighlighter.STRUCT_REF),
|
new AttributesDescriptor("Struct//Reference", ZigSyntaxHighlighter.STRUCT_REF),
|
||||||
new AttributesDescriptor("Type//Declaration", ZigSyntaxHighlighter.TYPE_DECL),
|
new AttributesDescriptor("Type//Declaration", ZigSyntaxHighlighter.TYPE_DECL),
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.zig.highlighter;
|
package com.falsepattern.zigbrains.zig.highlighter;
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.zig.lexer.ZigHighlightingLexer;
|
||||||
import com.falsepattern.zigbrains.zig.lexer.ZigLexerAdapter;
|
import com.falsepattern.zigbrains.zig.lexer.ZigLexerAdapter;
|
||||||
import com.falsepattern.zigbrains.zig.psi.ZigTypes;
|
import com.falsepattern.zigbrains.zig.psi.ZigTypes;
|
||||||
import com.intellij.lexer.Lexer;
|
import com.intellij.lexer.Lexer;
|
||||||
|
@ -23,6 +24,7 @@ import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
|
||||||
import com.intellij.openapi.editor.HighlighterColors;
|
import com.intellij.openapi.editor.HighlighterColors;
|
||||||
import com.intellij.openapi.editor.colors.TextAttributesKey;
|
import com.intellij.openapi.editor.colors.TextAttributesKey;
|
||||||
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
|
import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
|
||||||
|
import com.intellij.psi.StringEscapesTokenTypes;
|
||||||
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 com.redhat.devtools.lsp4ij.features.semanticTokens.SemanticTokensHighlightingColors;
|
||||||
|
@ -35,55 +37,59 @@ import java.util.Map;
|
||||||
public class ZigSyntaxHighlighter extends SyntaxHighlighterBase {
|
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" , SemanticTokensHighlightingColors.STATIC_METHOD ),
|
BUILTIN = createKey("BUILTIN" , SemanticTokensHighlightingColors.STATIC_METHOD ),
|
||||||
CHAR = createKey("CHAR" , SemanticTokensHighlightingColors.NUMBER ),
|
CHAR = createKey("CHAR" , SemanticTokensHighlightingColors.NUMBER ),
|
||||||
COMMENT = createKey("COMMENT" , SemanticTokensHighlightingColors.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" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
ENUM_DECL = createKey("ENUM_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||||
ENUM_REF = createKey("ENUM" , SemanticTokensHighlightingColors.CLASS ),
|
ENUM_REF = createKey("ENUM" , SemanticTokensHighlightingColors.CLASS ),
|
||||||
ENUM_MEMBER_DECL = createKey("ENUM_MEMBER_DECL" , SemanticTokensHighlightingColors.STATIC_PROPERTY ),
|
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" , SemanticTokensHighlightingColors.STATIC_PROPERTY ),
|
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" , SemanticTokensHighlightingColors.PROPERTY ),
|
PROPERTY_DECL = createKey("PROPERTY_DECL" , SemanticTokensHighlightingColors.PROPERTY ),
|
||||||
PROPERTY_REF = createKey("PROPERTY" , PROPERTY_DECL ),
|
PROPERTY_REF = createKey("PROPERTY" , PROPERTY_DECL ),
|
||||||
FUNCTION_DECL = createKey("FUNCTION_DECL" , SemanticTokensHighlightingColors.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" , SemanticTokensHighlightingColors.FUNCTION ),
|
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" , SemanticTokensHighlightingColors.KEYWORD ),
|
KEYWORD = createKey("KEYWORD" , SemanticTokensHighlightingColors.KEYWORD ),
|
||||||
LABEL_DECL = createKey("LABEL_DECL" , SemanticTokensHighlightingColors.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" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
NAMESPACE_DECL = createKey("NAMESPACE_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||||
NAMESPACE_REF = createKey("NAMESPACE" , SemanticTokensHighlightingColors.CLASS ),
|
NAMESPACE_REF = createKey("NAMESPACE" , SemanticTokensHighlightingColors.CLASS ),
|
||||||
NUMBER = createKey("NUMBER" , SemanticTokensHighlightingColors.NUMBER ),
|
NUMBER = createKey("NUMBER" , SemanticTokensHighlightingColors.NUMBER ),
|
||||||
OPERATOR = createKey("OPERATOR" , SemanticTokensHighlightingColors.OPERATOR ),
|
OPERATOR = createKey("OPERATOR" , SemanticTokensHighlightingColors.OPERATOR ),
|
||||||
PARAMETER = createKey("PARAMETER" , SemanticTokensHighlightingColors.PARAMETER ),
|
PARAMETER = createKey("PARAMETER" , SemanticTokensHighlightingColors.PARAMETER ),
|
||||||
STRING = createKey("STRING" , SemanticTokensHighlightingColors.STRING ),
|
STRING = createKey("STRING" , SemanticTokensHighlightingColors.STRING ),
|
||||||
STRUCT_DECL = createKey("STRUCT_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
STRING_ESC_V = createKey("STRING_ESC_V" , DefaultLanguageHighlighterColors.VALID_STRING_ESCAPE ),
|
||||||
STRUCT_REF = createKey("STRUCT" , SemanticTokensHighlightingColors.CLASS ),
|
STRING_ESC_I_C = createKey("STRING_ESC_I_C" , DefaultLanguageHighlighterColors.INVALID_STRING_ESCAPE ),
|
||||||
TYPE_DECL = createKey("TYPE_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
STRING_ESC_I_U = createKey("STRING_ESC_I_U" , DefaultLanguageHighlighterColors.INVALID_STRING_ESCAPE ),
|
||||||
TYPE_DECL_GEN = createKey("TYPE_DECL_GEN" , TYPE_DECL ),
|
STRUCT_DECL = createKey("STRUCT_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||||
TYPE_REF = createKey("TYPE" , SemanticTokensHighlightingColors.TYPE ),
|
STRUCT_REF = createKey("STRUCT" , SemanticTokensHighlightingColors.CLASS ),
|
||||||
TYPE_REF_GEN = createKey("TYPE_GEN" , TYPE_REF ),
|
TYPE_DECL = createKey("TYPE_DECL" , SemanticTokensHighlightingColors.CLASS_DECLARATION ),
|
||||||
TYPE_PARAM = createKey("TYPE_PARAM" , SemanticTokensHighlightingColors.TYPE_PARAMETER ),
|
TYPE_DECL_GEN = createKey("TYPE_DECL_GEN" , TYPE_DECL ),
|
||||||
TYPE_PARAM_DECL = createKey("TYPE_PARAM_DECL" , TYPE_PARAM ),
|
TYPE_REF = createKey("TYPE" , SemanticTokensHighlightingColors.TYPE ),
|
||||||
VARIABLE_DECL = createKey("VARIABLE_DECL" , DefaultLanguageHighlighterColors.LOCAL_VARIABLE ),
|
TYPE_REF_GEN = createKey("TYPE_GEN" , TYPE_REF ),
|
||||||
VARIABLE_DECL_DEPR= createKey("VARIABLE_DECL_DEPR" , VARIABLE_DECL ),
|
TYPE_PARAM = createKey("TYPE_PARAM" , SemanticTokensHighlightingColors.TYPE_PARAMETER ),
|
||||||
VARIABLE_REF = createKey("VARIABLE" , VARIABLE_DECL ),
|
TYPE_PARAM_DECL = createKey("TYPE_PARAM_DECL" , TYPE_PARAM ),
|
||||||
VARIABLE_REF_DEPR = createKey("VARIABLE_REF_DEPL" , VARIABLE_REF );
|
VARIABLE_DECL = createKey("VARIABLE_DECL" , DefaultLanguageHighlighterColors.LOCAL_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 );
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
private static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0];
|
private static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0];
|
||||||
private static final Map<IElementType, TextAttributesKey[]> KEYMAP = new HashMap<>();
|
private static final Map<IElementType, TextAttributesKey[]> KEYMAP = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
addMapping(COMMENT, ZigTypes.LINE_COMMENT, ZigTypes.DOC_COMMENT, ZigTypes.CONTAINER_DOC_COMMENT);
|
addMapping(COMMENT, ZigTypes.LINE_COMMENT);
|
||||||
|
addMapping(COMMENT_DOC, ZigTypes.DOC_COMMENT, ZigTypes.CONTAINER_DOC_COMMENT);
|
||||||
|
|
||||||
//Keywords
|
//Keywords
|
||||||
{
|
{
|
||||||
|
@ -101,6 +107,9 @@ public class ZigSyntaxHighlighter extends SyntaxHighlighterBase {
|
||||||
}
|
}
|
||||||
addMapping(BUILTIN, ZigTypes.BUILTINIDENTIFIER);
|
addMapping(BUILTIN, ZigTypes.BUILTINIDENTIFIER);
|
||||||
addMapping(STRING, ZigTypes.STRING_LITERAL_SINGLE, ZigTypes.STRING_LITERAL_MULTI);
|
addMapping(STRING, ZigTypes.STRING_LITERAL_SINGLE, ZigTypes.STRING_LITERAL_MULTI);
|
||||||
|
addMapping(STRING_ESC_V, StringEscapesTokenTypes.VALID_STRING_ESCAPE_TOKEN);
|
||||||
|
addMapping(STRING_ESC_I_C, StringEscapesTokenTypes.INVALID_CHARACTER_ESCAPE_TOKEN);
|
||||||
|
addMapping(STRING_ESC_I_U, StringEscapesTokenTypes.INVALID_UNICODE_ESCAPE_TOKEN);
|
||||||
addMapping(BAD_CHAR, TokenType.BAD_CHARACTER);
|
addMapping(BAD_CHAR, TokenType.BAD_CHARACTER);
|
||||||
addMapping(NUMBER, ZigTypes.INTEGER, ZigTypes.FLOAT);
|
addMapping(NUMBER, ZigTypes.INTEGER, ZigTypes.FLOAT);
|
||||||
addMapping(CHAR, ZigTypes.CHAR_LITERAL);
|
addMapping(CHAR, ZigTypes.CHAR_LITERAL);
|
||||||
|
@ -119,7 +128,7 @@ public class ZigSyntaxHighlighter extends SyntaxHighlighterBase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Lexer getHighlightingLexer() {
|
public @NotNull Lexer getHighlightingLexer() {
|
||||||
return new ZigLexerAdapter();
|
return new ZigHighlightingLexer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.falsepattern.zigbrains.zig.lexer;
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.zig.psi.ZigTypes;
|
||||||
|
import com.intellij.lexer.FlexAdapter;
|
||||||
|
import com.intellij.lexer.LayeredLexer;
|
||||||
|
import com.intellij.lexer.MergingLexerAdapter;
|
||||||
|
import com.intellij.psi.tree.IElementType;
|
||||||
|
import com.intellij.psi.tree.TokenSet;
|
||||||
|
import lombok.val;
|
||||||
|
|
||||||
|
public class ZigHighlightingLexer extends LayeredLexer {
|
||||||
|
public ZigHighlightingLexer() {
|
||||||
|
super(new ZigLexerAdapter());
|
||||||
|
val stringLexer = new MergingLexerAdapter(new FlexAdapter(new com.falsepattern.zigbrains.zig.stringlexer.ZigStringLexer(null)), TokenSet.create(
|
||||||
|
ZigTypes.STRING_LITERAL_SINGLE));
|
||||||
|
registerSelfStoppingLayer(stringLexer, new IElementType[]{ZigTypes.STRING_LITERAL_SINGLE}, IElementType.EMPTY_ARRAY);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
package com.falsepattern.zigbrains.zig.lsp;
|
package com.falsepattern.zigbrains.zig.lsp;
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
|
||||||
|
import com.falsepattern.zigbrains.zig.settings.ZLSSettings;
|
||||||
|
import com.falsepattern.zigbrains.zig.settings.ZLSSettingsConfigProvider;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
import com.redhat.devtools.lsp4ij.LanguageServerEnablementSupport;
|
import com.redhat.devtools.lsp4ij.LanguageServerEnablementSupport;
|
||||||
|
@ -7,7 +10,10 @@ import com.redhat.devtools.lsp4ij.LanguageServerFactory;
|
||||||
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl;
|
import com.redhat.devtools.lsp4ij.client.LanguageClientImpl;
|
||||||
import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
|
import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
|
||||||
import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature;
|
import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature;
|
||||||
|
import com.redhat.devtools.lsp4ij.client.features.LSPInlayHintFeature;
|
||||||
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider;
|
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider;
|
||||||
|
import com.redhat.devtools.lsp4ij.server.capabilities.InlayHintCapabilityRegistry;
|
||||||
|
import lombok.val;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class ZLSLanguageServerFactory implements LanguageServerFactory, LanguageServerEnablementSupport {
|
public class ZLSLanguageServerFactory implements LanguageServerFactory, LanguageServerEnablementSupport {
|
||||||
|
@ -20,13 +26,20 @@ public class ZLSLanguageServerFactory implements LanguageServerFactory, Language
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
@Override
|
@Override
|
||||||
public @NotNull LSPClientFeatures createClientFeatures() {
|
public @NotNull LSPClientFeatures createClientFeatures() {
|
||||||
return new LSPClientFeatures()
|
val features = new LSPClientFeatures();
|
||||||
.setFormattingFeature(new LSPFormattingFeature() {
|
features.setFormattingFeature(new LSPFormattingFeature() {
|
||||||
@Override
|
@Override
|
||||||
protected boolean isExistingFormatterOverrideable(@NotNull PsiFile file) {
|
protected boolean isExistingFormatterOverrideable(@NotNull PsiFile file) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
features.setInlayHintFeature(new LSPInlayHintFeature() {
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(@NotNull PsiFile file) {
|
||||||
|
return ZLSProjectSettingsService.getInstance(features.getProject()).getState().inlayHints;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -109,9 +109,8 @@ public class ZLSSemanticTokensColorsProvider extends DefaultSemanticTokensColors
|
||||||
@Override
|
@Override
|
||||||
public @Nullable TextAttributesKey getTextAttributesKey(@NotNull String tokenType, @NotNull List<String> tokenModifiers, @NotNull PsiFile file) {
|
public @Nullable TextAttributesKey getTextAttributesKey(@NotNull String tokenType, @NotNull List<String> tokenModifiers, @NotNull PsiFile file) {
|
||||||
val tok = new TokenHelper(tokenModifiers);
|
val tok = new TokenHelper(tokenModifiers);
|
||||||
val res = switch (tokenType) {
|
return switch (tokenType) {
|
||||||
case Builtin -> BUILTIN;
|
case Builtin -> BUILTIN;
|
||||||
case Comment -> tok.has(Documentation) ? COMMENT_DOC : COMMENT;
|
|
||||||
case Enum -> tok.isDecl() ? ENUM_DECL : ENUM_REF;
|
case Enum -> tok.isDecl() ? ENUM_DECL : ENUM_REF;
|
||||||
case EnumMember -> tok.isDecl() ? ENUM_MEMBER_DECL : ENUM_MEMBER_REF;
|
case EnumMember -> tok.isDecl() ? ENUM_MEMBER_DECL : ENUM_MEMBER_REF;
|
||||||
case ErrorTag -> tok.isDecl() ? ERROR_TAG_DECL : ERROR_TAG_REF;
|
case ErrorTag -> tok.isDecl() ? ERROR_TAG_DECL : ERROR_TAG_REF;
|
||||||
|
@ -126,15 +125,14 @@ public class ZLSSemanticTokensColorsProvider extends DefaultSemanticTokensColors
|
||||||
case Number -> NUMBER;
|
case Number -> NUMBER;
|
||||||
case Operator -> OPERATOR;
|
case Operator -> OPERATOR;
|
||||||
case Parameter -> PARAMETER;
|
case Parameter -> PARAMETER;
|
||||||
case String -> STRING;
|
|
||||||
case Struct -> tok.isDecl() ? STRUCT_DECL : STRUCT_REF;
|
case Struct -> tok.isDecl() ? STRUCT_DECL : STRUCT_REF;
|
||||||
case Type -> tok.isDecl() ? (tok.has(Generic) ? TYPE_DECL_GEN : TYPE_DECL)
|
case Type -> tok.isDecl() ? (tok.has(Generic) ? TYPE_DECL_GEN : TYPE_DECL)
|
||||||
: (tok.has(Generic) ? TYPE_REF_GEN : TYPE_REF);
|
: (tok.has(Generic) ? TYPE_REF_GEN : TYPE_REF);
|
||||||
case TypeParameter -> tok.isDecl() ? TYPE_PARAM_DECL : TYPE_PARAM;
|
case TypeParameter -> tok.isDecl() ? TYPE_PARAM_DECL : TYPE_PARAM;
|
||||||
case Variable -> tok.isDecl() ? (tok.has(Deprecated) ? VARIABLE_DECL_DEPR : VARIABLE_DECL)
|
case Variable -> tok.isDecl() ? (tok.has(Deprecated) ? VARIABLE_DECL_DEPR : VARIABLE_DECL)
|
||||||
: (tok.has(Deprecated) ? VARIABLE_REF_DEPR : VARIABLE_REF);
|
: (tok.has(Deprecated) ? VARIABLE_REF_DEPR : VARIABLE_REF);
|
||||||
default -> null;
|
case Comment, String -> null;
|
||||||
|
default -> super.getTextAttributesKey(tokenType, tokenModifiers, file);
|
||||||
};
|
};
|
||||||
return res != null ? res : super.getTextAttributesKey(tokenType, tokenModifiers, file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,21 +17,29 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider;
|
import com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider;
|
||||||
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider;
|
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import org.eclipse.lsp4j.InlayHint;
|
||||||
|
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||||
|
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
|
||||||
|
import org.eclipse.lsp4j.services.LanguageServer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvider {
|
public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvider {
|
||||||
private static final Logger LOG = Logger.getInstance(ZLSStreamConnectionProvider.class);
|
private static final Logger LOG = Logger.getInstance(ZLSStreamConnectionProvider.class);
|
||||||
|
private final Project project;
|
||||||
public ZLSStreamConnectionProvider(Project project) {
|
public ZLSStreamConnectionProvider(Project project) {
|
||||||
|
this.project = project;
|
||||||
val command = getCommand(project);
|
val command = getCommand(project);
|
||||||
val projectDir = ProjectUtil.guessProjectDir(project);
|
val projectDir = ProjectUtil.guessProjectDir(project);
|
||||||
GeneralCommandLine commandLine = null;
|
GeneralCommandLine commandLine = null;
|
||||||
|
@ -50,6 +58,36 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
|
||||||
setCommandLine(commandLine);
|
setCommandLine(commandLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message message, LanguageServer languageServer, VirtualFile rootUri) {
|
||||||
|
if (ZLSProjectSettingsService.getInstance(project).getState().inlayHintsCompact) {
|
||||||
|
if (message instanceof ResponseMessage resp) {
|
||||||
|
val res = resp.getResult();
|
||||||
|
if (res instanceof Collection<?> c) {
|
||||||
|
c.forEach(e -> {
|
||||||
|
if (e instanceof InlayHint ih) {
|
||||||
|
tryMutateInlayHint(ih);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (res instanceof InlayHint ih) {
|
||||||
|
tryMutateInlayHint(ih);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.handleMessage(message, languageServer, rootUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Pattern ERROR_BLOCK = Pattern.compile("error\\{.*?}", Pattern.DOTALL);
|
||||||
|
|
||||||
|
|
||||||
|
private void tryMutateInlayHint(InlayHint inlayHint) {
|
||||||
|
if (inlayHint.getLabel().isLeft()) {
|
||||||
|
val str = inlayHint.getLabel().getLeft();
|
||||||
|
val shortened = ERROR_BLOCK.matcher(str).replaceAll("error{...}");
|
||||||
|
inlayHint.setLabel(shortened);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static List<String> doGetCommand(Project project, boolean safe) {
|
public static List<String> doGetCommand(Project project, boolean safe) {
|
||||||
var svc = ZLSProjectSettingsService.getInstance(project);
|
var svc = ZLSProjectSettingsService.getInstance(project);
|
||||||
val state = svc.getState();
|
val state = svc.getState();
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.falsepattern.zigbrains.zig.psi;
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
|
||||||
|
import com.intellij.lang.Language;
|
||||||
|
import com.intellij.lang.injection.MultiHostRegistrar;
|
||||||
|
import com.intellij.lang.injection.general.Injection;
|
||||||
|
import com.intellij.lang.injection.general.LanguageInjectionPerformer;
|
||||||
|
import com.intellij.openapi.util.TextRange;
|
||||||
|
import com.intellij.psi.PsiComment;
|
||||||
|
import com.intellij.psi.PsiElement;
|
||||||
|
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||||
|
import lombok.val;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ZigLanguageInjectionPerformer implements LanguageInjectionPerformer {
|
||||||
|
@Override
|
||||||
|
public boolean isPrimary() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean performInjection(@NotNull MultiHostRegistrar registrar, @NotNull Injection injection, @NotNull PsiElement context) {
|
||||||
|
if (!(context instanceof PsiLanguageInjectionHost host))
|
||||||
|
return false;
|
||||||
|
val language = injection.getInjectedLanguage();
|
||||||
|
if (language == null)
|
||||||
|
return false;
|
||||||
|
List<TextRange> ranges;
|
||||||
|
if (host instanceof ZigStringLiteral str) {
|
||||||
|
ranges = str.getContentRanges();
|
||||||
|
} else if (host instanceof PsiComment comment) {
|
||||||
|
val tt = comment.getTokenType();
|
||||||
|
if (tt == ZigTypes.LINE_COMMENT) {
|
||||||
|
ranges = PsiTextUtil.getMultiLineContent(comment.getText(), "//");
|
||||||
|
} else if (tt == ZigTypes.DOC_COMMENT) {
|
||||||
|
ranges = PsiTextUtil.getMultiLineContent(comment.getText(), "///");
|
||||||
|
} else if (tt == ZigTypes.CONTAINER_DOC_COMMENT) {
|
||||||
|
ranges = PsiTextUtil.getMultiLineContent(comment.getText(), "//!");
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
injectIntoStringMultiRanges(registrar, host, ranges, language, injection.getPrefix(), injection.getSuffix());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void injectIntoStringMultiRanges(MultiHostRegistrar registrar,
|
||||||
|
PsiLanguageInjectionHost context,
|
||||||
|
List<TextRange> ranges,
|
||||||
|
Language language,
|
||||||
|
String prefix,
|
||||||
|
String suffix) {
|
||||||
|
if (ranges.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
registrar.startInjecting(language);
|
||||||
|
|
||||||
|
if (ranges.size() == 1) {
|
||||||
|
registrar.addPlace(prefix, suffix, context, ranges.getFirst());
|
||||||
|
} else {
|
||||||
|
registrar.addPlace(prefix, null, context, ranges.getFirst());
|
||||||
|
for (val range : ranges.subList(1, ranges.size() - 1)) {
|
||||||
|
registrar.addPlace(null, null, context, range);
|
||||||
|
}
|
||||||
|
registrar.addPlace(null, suffix, context, ranges.getLast());
|
||||||
|
}
|
||||||
|
registrar.doneInjecting();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package com.falsepattern.zigbrains.zig.psi;
|
package com.falsepattern.zigbrains.zig.psi;
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.zig.ZigFileType;
|
import com.falsepattern.zigbrains.zig.ZigFileType;
|
||||||
import com.falsepattern.zigbrains.zig.util.PsiUtil;
|
import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
|
||||||
import com.intellij.openapi.util.TextRange;
|
import com.intellij.openapi.util.TextRange;
|
||||||
import com.intellij.openapi.util.text.StringUtil;
|
import com.intellij.openapi.util.text.StringUtil;
|
||||||
import com.intellij.psi.AbstractElementManipulator;
|
import com.intellij.psi.AbstractElementManipulator;
|
||||||
|
@ -12,21 +12,16 @@ 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.ByteArrayOutputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ZigStringElementManipulator extends AbstractElementManipulator<ZigStringLiteral> {
|
public class ZigStringElementManipulator extends AbstractElementManipulator<ZigStringLiteral> {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable ZigStringLiteral handleContentChange(@NotNull ZigStringLiteral element, @NotNull TextRange range, String newContent)
|
public @Nullable ZigStringLiteral handleContentChange(@NotNull ZigStringLiteral element, @NotNull TextRange range, String newContent)
|
||||||
throws IncorrectOperationException {
|
throws IncorrectOperationException {
|
||||||
assert (new TextRange(0, element.getTextLength())).contains(range);
|
assert (new TextRange(0, element.getTextLength())).contains(range);
|
||||||
val originalContext = element.getText();
|
val originalContext = element.getText();
|
||||||
val isMulti = element.getStringLiteralMulti() != null;
|
val isMulti = element.isMultiLine();
|
||||||
val elementRange = getRangeInElement(element);
|
val elementRange = getRangeInElement(element);
|
||||||
var replacement = originalContext.substring(elementRange.getStartOffset(),
|
var replacement = originalContext.substring(elementRange.getStartOffset(),
|
||||||
range.getStartOffset()) +
|
range.getStartOffset()) +
|
||||||
|
@ -36,12 +31,7 @@ public class ZigStringElementManipulator extends AbstractElementManipulator<ZigS
|
||||||
val psiFileFactory = PsiFileFactory.getInstance(element.getProject());
|
val psiFileFactory = PsiFileFactory.getInstance(element.getProject());
|
||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
val column = StringUtil.offsetToLineColumn(element.getContainingFile().getText(), element.getTextOffset()).column;
|
val column = StringUtil.offsetToLineColumn(element.getContainingFile().getText(), element.getTextOffset()).column;
|
||||||
val pfxB = new StringBuilder(column + 2);
|
val pfx = " ".repeat(Math.max(0, column)) + "\\\\";
|
||||||
for (int i = 0; i < column; i++) {
|
|
||||||
pfxB.append(' ');
|
|
||||||
}
|
|
||||||
pfxB.append("\\\\");
|
|
||||||
val pfx = pfxB.toString();
|
|
||||||
replacement = Arrays.stream(replacement.split("(\\r\\n|\\r|\\n)")).map(line -> pfx + line).collect(
|
replacement = Arrays.stream(replacement.split("(\\r\\n|\\r|\\n)")).map(line -> pfx + line).collect(
|
||||||
Collectors.joining("\n"));
|
Collectors.joining("\n"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,140 +45,27 @@ public class ZigStringElementManipulator extends AbstractElementManipulator<ZigS
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull TextRange getRangeInElement(@NotNull ZigStringLiteral element) {
|
public @NotNull TextRange getRangeInElement(@NotNull ZigStringLiteral element) {
|
||||||
if (element.getStringLiteralSingle() != null) {
|
return PsiTextUtil.getTextRangeBounds(element.getContentRanges());
|
||||||
return new TextRange(1, element.getTextLength() - 1);
|
|
||||||
}
|
|
||||||
return super.getRangeInElement(element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static String escape(String input) {
|
public static String escape(String input) {
|
||||||
val bytes = input.getBytes(StandardCharsets.UTF_8);
|
return input.codePoints().mapToObj(point -> switch (point) {
|
||||||
val result = new ByteArrayOutputStream();
|
case '\n' -> "\\n";
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
case '\r' -> "\\r";
|
||||||
byte c = bytes[i];
|
case '\t' -> "\\t";
|
||||||
switch (c) {
|
case '\\' -> "\\\\";
|
||||||
case '\n' -> result.write("\\n".getBytes(StandardCharsets.UTF_8));
|
case '"' -> "\\\"";
|
||||||
case '\r' -> result.write("\\r".getBytes(StandardCharsets.UTF_8));
|
case '\'', ' ', '!' -> Character.toString(point);
|
||||||
case '\t' -> result.write("\\t".getBytes(StandardCharsets.UTF_8));
|
default -> {
|
||||||
case '\\' -> result.write("\\\\".getBytes(StandardCharsets.UTF_8));
|
if (point >= '#' && point <= '&' ||
|
||||||
case '"' -> result.write("\\\"".getBytes(StandardCharsets.UTF_8));
|
point >= '(' && point <= '[' ||
|
||||||
case '\'', ' ', '!' -> result.write(c);
|
point >= ']' && point <= '~') {
|
||||||
default -> {
|
yield Character.toString(point);
|
||||||
if (c >= '#' && c <= '&' ||
|
} else {
|
||||||
c >= '(' && c <= '[' ||
|
yield "\\u{" + Integer.toHexString(point).toLowerCase() + "}";
|
||||||
c >= ']' && c <= '~') {
|
|
||||||
result.write(c);
|
|
||||||
} else {
|
|
||||||
result.write("\\x".getBytes(StandardCharsets.UTF_8));
|
|
||||||
result.write(String.format("%02x", c).getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}).collect(Collectors.joining(""));
|
||||||
return result.toString(StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public static String unescape(String input, boolean[] noErrors) {
|
|
||||||
noErrors[0] = true;
|
|
||||||
val result = new ByteArrayOutputStream();
|
|
||||||
val bytes = input.getBytes(StandardCharsets.UTF_8);
|
|
||||||
val len = bytes.length;
|
|
||||||
loop:
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
byte c = bytes[i];
|
|
||||||
switch (c) {
|
|
||||||
case '\\' -> {
|
|
||||||
i++;
|
|
||||||
if (i < len) {
|
|
||||||
switch (input.charAt(i)) {
|
|
||||||
case 'n' -> result.write('\n');
|
|
||||||
case 'r' -> result.write('\r');
|
|
||||||
case 't' -> result.write('\t');
|
|
||||||
case '\\' -> result.write('\\');
|
|
||||||
case '"' -> result.write('"');
|
|
||||||
case 'x' -> {
|
|
||||||
if (i + 2 < len) {
|
|
||||||
try {
|
|
||||||
int b1 = decodeHex(bytes[i + 1]);
|
|
||||||
int b2 = decodeHex(bytes[i + 2]);
|
|
||||||
result.write((b1 << 4) | b2);
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
noErrors[0] = false;
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 'u' -> {
|
|
||||||
i++;
|
|
||||||
if (i >= len || bytes[i] != '{') {
|
|
||||||
noErrors[0] = false;
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
int codePoint = 0;
|
|
||||||
try {
|
|
||||||
while (i < len && bytes[i] != '}') {
|
|
||||||
codePoint <<= 4;
|
|
||||||
codePoint |= decodeHex(bytes[i + 1]);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
noErrors[0] = false;
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
if (i >= len) {
|
|
||||||
noErrors[0] = false;
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
result.write(Character.toString(codePoint).getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
noErrors[0] = false;
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
noErrors[0] = false;
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default -> result.write(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toString(StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String unescapeWithLengthMappings(String input, List<Integer> inputOffsets, boolean[] noErrors) {
|
|
||||||
String output = "";
|
|
||||||
int lastOutputLength = 0;
|
|
||||||
int inputOffset = 0;
|
|
||||||
for (int i = 0; i < input.length(); i++) {
|
|
||||||
output = unescape(input.substring(0, i + 1), noErrors);
|
|
||||||
val outputLength = output.length();
|
|
||||||
if (noErrors[0]) {
|
|
||||||
inputOffset = i;
|
|
||||||
}
|
|
||||||
while (lastOutputLength < outputLength) {
|
|
||||||
inputOffsets.add(inputOffset);
|
|
||||||
lastOutputLength++;
|
|
||||||
inputOffset = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int decodeHex(int b) {
|
|
||||||
if (b >= '0' && b <= '9') {
|
|
||||||
return b - '0';
|
|
||||||
}
|
|
||||||
if (b >= 'A' && b <= 'F') {
|
|
||||||
return b - 'A' + 10;
|
|
||||||
}
|
|
||||||
if (b >= 'a' && b <= 'f') {
|
|
||||||
return b - 'a' + 10;
|
|
||||||
}
|
|
||||||
throw new NumberFormatException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
package com.falsepattern.zigbrains.zig.psi.impl.mixins;
|
package com.falsepattern.zigbrains.zig.psi.impl.mixins;
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.zig.psi.ZigStringElementManipulator;
|
|
||||||
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
|
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
|
||||||
|
import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
|
||||||
import com.intellij.extapi.psi.ASTWrapperPsiElement;
|
import com.intellij.extapi.psi.ASTWrapperPsiElement;
|
||||||
import com.intellij.lang.ASTNode;
|
import com.intellij.lang.ASTNode;
|
||||||
|
import com.intellij.openapi.util.Pair;
|
||||||
import com.intellij.openapi.util.TextRange;
|
import com.intellij.openapi.util.TextRange;
|
||||||
import com.intellij.psi.LiteralTextEscaper;
|
import com.intellij.psi.LiteralTextEscaper;
|
||||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||||
import com.intellij.psi.impl.source.tree.LeafElement;
|
import com.intellij.psi.impl.source.tree.LeafElement;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@ -25,6 +29,37 @@ public abstract class ZigStringLiteralMixinImpl extends ASTWrapperPsiElement imp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiLine() {
|
||||||
|
return getStringLiteralMulti() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull List<Pair<TextRange, String>> getDecodeReplacements(@NotNull CharSequence input) {
|
||||||
|
if (isMultiLine())
|
||||||
|
return List.of();
|
||||||
|
|
||||||
|
val result = new ArrayList<Pair<TextRange, String>>();
|
||||||
|
for (int i = 0; i + 1 < input.length(); i++) {
|
||||||
|
if (input.charAt(i) == '\\') {
|
||||||
|
val length = Escaper.findEscapementLength(input, i);
|
||||||
|
val charCode = Escaper.toUnicodeChar(input, i, length);
|
||||||
|
val range = TextRange.create(i, Math.min(i + length + 1, input.length()));
|
||||||
|
result.add(Pair.create(range, Character.toString(charCode)));
|
||||||
|
i += range.getLength() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull List<TextRange> getContentRanges() {
|
||||||
|
if (!isMultiLine()) {
|
||||||
|
return List.of(new TextRange(1, getTextLength() - 1));
|
||||||
|
} else {
|
||||||
|
return PsiTextUtil.getMultiLineContent(getText(), "\\\\");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PsiLanguageInjectionHost updateText(@NotNull String text) {
|
public PsiLanguageInjectionHost updateText(@NotNull String text) {
|
||||||
|
@ -36,121 +71,142 @@ public abstract class ZigStringLiteralMixinImpl extends ASTWrapperPsiElement imp
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @NotNull String processReplacements(@NotNull CharSequence input,
|
||||||
|
@NotNull List<? extends Pair<TextRange, String>> replacements) throws IndexOutOfBoundsException {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int currentOffset = 0;
|
||||||
|
for (val replacement: replacements) {
|
||||||
|
result.append(input.subSequence(currentOffset, replacement.getFirst().getStartOffset()));
|
||||||
|
result.append(replacement.getSecond());
|
||||||
|
currentOffset = replacement.getFirst().getEndOffset();
|
||||||
|
}
|
||||||
|
result.append(input.subSequence(currentOffset, input.length()));
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull LiteralTextEscaper<ZigStringLiteral> createLiteralTextEscaper() {
|
public @NotNull LiteralTextEscaper<ZigStringLiteral> createLiteralTextEscaper() {
|
||||||
if (this.getStringLiteralSingle() != null) {
|
|
||||||
return new LiteralTextEscaper<>(this) {
|
|
||||||
private final List<Integer> inputOffsets = new ArrayList<>();
|
|
||||||
@Override
|
|
||||||
public boolean decode(@NotNull TextRange rangeInsideHost, @NotNull StringBuilder outChars) {
|
|
||||||
boolean[] noErrors = new boolean[] {true};
|
|
||||||
outChars.append(ZigStringElementManipulator.unescapeWithLengthMappings(rangeInsideHost.substring(myHost.getText()), inputOffsets, noErrors));
|
|
||||||
return noErrors[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return new LiteralTextEscaper<>(this) {
|
||||||
public int getOffsetInHost(int offsetInDecoded, @NotNull TextRange rangeInsideHost) {
|
private String text;
|
||||||
int size = inputOffsets.size();
|
private List<TextRange> contentRanges;
|
||||||
int realOffset = 0;
|
@Override
|
||||||
if (size == 0) {
|
public boolean decode(@NotNull TextRange rangeInsideHost, @NotNull StringBuilder outChars) {
|
||||||
realOffset = rangeInsideHost.getStartOffset() + offsetInDecoded;
|
text = myHost.getText();
|
||||||
} else if (offsetInDecoded >= size) {
|
val isMultiline = myHost.isMultiLine();
|
||||||
realOffset = rangeInsideHost.getStartOffset() + inputOffsets.get(size - 1) +
|
contentRanges = myHost.getContentRanges();
|
||||||
(offsetInDecoded - (size - 1));
|
boolean decoded = false;
|
||||||
} else {
|
for (val range: contentRanges) {
|
||||||
realOffset = rangeInsideHost.getStartOffset() + inputOffsets.get(offsetInDecoded);
|
val intersection = range.intersection(rangeInsideHost);
|
||||||
|
if (intersection == null) continue;
|
||||||
|
decoded = true;
|
||||||
|
val substring = intersection.subSequence(text);
|
||||||
|
outChars.append(isMultiline ? substring : processReplacements(substring, myHost.getDecodeReplacements(substring)));
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull TextRange getRelevantTextRange() {
|
||||||
|
if (contentRanges == null) {
|
||||||
|
contentRanges = myHost.getContentRanges();
|
||||||
|
}
|
||||||
|
return PsiTextUtil.getTextRangeBounds(contentRanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffsetInHost(int offsetInDecoded, @NotNull TextRange rangeInsideHost) {
|
||||||
|
int currentOffsetInDecoded = 0;
|
||||||
|
|
||||||
|
TextRange last = null;
|
||||||
|
for (int i = 0; i < contentRanges.size(); i++) {
|
||||||
|
final TextRange range = rangeInsideHost.intersection(contentRanges.get(i));
|
||||||
|
if (range == null) continue;
|
||||||
|
last = range;
|
||||||
|
|
||||||
|
String curString = range.subSequence(text).toString();
|
||||||
|
|
||||||
|
final List<Pair<TextRange, String>> replacementsForThisLine = myHost.getDecodeReplacements(curString);
|
||||||
|
int encodedOffsetInCurrentLine = 0;
|
||||||
|
for (Pair<TextRange, String> replacement : replacementsForThisLine) {
|
||||||
|
final int deltaLength = replacement.getFirst().getStartOffset() - encodedOffsetInCurrentLine;
|
||||||
|
int currentOffsetBeforeReplacement = currentOffsetInDecoded + deltaLength;
|
||||||
|
if (currentOffsetBeforeReplacement > offsetInDecoded) {
|
||||||
|
return range.getStartOffset() + encodedOffsetInCurrentLine + (offsetInDecoded - currentOffsetInDecoded);
|
||||||
|
}
|
||||||
|
else if (currentOffsetBeforeReplacement == offsetInDecoded && !replacement.getSecond().isEmpty()) {
|
||||||
|
return range.getStartOffset() + encodedOffsetInCurrentLine + (offsetInDecoded - currentOffsetInDecoded);
|
||||||
|
}
|
||||||
|
currentOffsetInDecoded += deltaLength + replacement.getSecond().length();
|
||||||
|
encodedOffsetInCurrentLine += deltaLength + replacement.getFirst().getLength();
|
||||||
}
|
}
|
||||||
return realOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
final int deltaLength = curString.length() - encodedOffsetInCurrentLine;
|
||||||
public @NotNull TextRange getRelevantTextRange() {
|
if (currentOffsetInDecoded + deltaLength > offsetInDecoded) {
|
||||||
return new TextRange(1, myHost.getTextLength() - 1);
|
return range.getStartOffset() + encodedOffsetInCurrentLine + (offsetInDecoded - currentOffsetInDecoded);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOneLine() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (this.getStringLiteralMulti() != null) {
|
|
||||||
return new LiteralTextEscaper<>(this) {
|
|
||||||
@Override
|
|
||||||
public boolean decode(@NotNull TextRange rangeInsideHost, @NotNull StringBuilder outChars) {
|
|
||||||
val str = myHost.getText();
|
|
||||||
boolean inMultiLineString = false;
|
|
||||||
for (int i = 0; i < str.length(); i++) {
|
|
||||||
val cI = str.charAt(i);
|
|
||||||
if (!inMultiLineString) {
|
|
||||||
if (cI == '\\' &&
|
|
||||||
i + 1 < str.length() &&
|
|
||||||
str.charAt(i + 1) == '\\') {
|
|
||||||
i++;
|
|
||||||
inMultiLineString = true;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cI == '\r') {
|
|
||||||
outChars.append('\n');
|
|
||||||
if (i + 1 < str.length() && str.charAt(i + 1) == '\n') {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
inMultiLineString = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (cI == '\n') {
|
|
||||||
outChars.append('\n');
|
|
||||||
inMultiLineString = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
outChars.append(cI);
|
|
||||||
}
|
}
|
||||||
return true;
|
currentOffsetInDecoded += deltaLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return last != null ? last.getEndOffset() : -1;
|
||||||
public int getOffsetInHost(int offsetInDecoded, @NotNull TextRange rangeInsideHost) {
|
}
|
||||||
val str = myHost.getText();
|
|
||||||
boolean inMultiLineString = false;
|
@Override
|
||||||
int i = rangeInsideHost.getStartOffset();
|
public boolean isOneLine() {
|
||||||
for (; i < rangeInsideHost.getEndOffset() && offsetInDecoded > 0; i++) {
|
return !myHost.isMultiLine();
|
||||||
val cI = str.charAt(i);
|
}
|
||||||
if (!inMultiLineString) {
|
};
|
||||||
if (cI == '\\' &&
|
}
|
||||||
i + 1 < str.length() &&
|
|
||||||
str.charAt(i + 1) == '\\') {
|
@UtilityClass
|
||||||
i++;
|
private static class Escaper {
|
||||||
inMultiLineString = true;
|
private static final Int2IntMap ESC_TO_CODE = new Int2IntOpenHashMap();
|
||||||
}
|
static {
|
||||||
continue;
|
ESC_TO_CODE.put('n', '\n');
|
||||||
|
ESC_TO_CODE.put('r', '\r');
|
||||||
|
ESC_TO_CODE.put('t', '\t');
|
||||||
|
ESC_TO_CODE.put('\\', '\\');
|
||||||
|
ESC_TO_CODE.put('"', '"');
|
||||||
|
ESC_TO_CODE.put('\'', '\'');
|
||||||
|
}
|
||||||
|
static int findEscapementLength(@NotNull CharSequence text, int pos) {
|
||||||
|
if (pos + 1 < text.length() && text.charAt(pos) == '\\') {
|
||||||
|
char c = text.charAt(pos + 1);
|
||||||
|
return switch (c) {
|
||||||
|
case 'x' -> 3;
|
||||||
|
case 'u' -> {
|
||||||
|
if (pos + 3 >= text.length() || text.charAt(pos + 2) != '{') {
|
||||||
|
throw new IllegalArgumentException("Invalid unicode escape sequence");
|
||||||
}
|
}
|
||||||
if (cI == '\r') {
|
int digits = 0;
|
||||||
offsetInDecoded--;
|
while (pos + 3 + digits < text.length() && text.charAt(pos + 3 + digits) != '}') {
|
||||||
if (i + 1 < str.length() && str.charAt(i + 1) == '\n') {
|
digits++;
|
||||||
i++;
|
|
||||||
}
|
|
||||||
inMultiLineString = false;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (cI == '\n') {
|
yield 3 + digits;
|
||||||
offsetInDecoded--;
|
|
||||||
inMultiLineString = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
offsetInDecoded--;
|
|
||||||
}
|
}
|
||||||
if (offsetInDecoded != 0)
|
default -> 1;
|
||||||
return -1;
|
};
|
||||||
return i;
|
} else {
|
||||||
}
|
throw new IllegalArgumentException("This is not an escapement start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
static int toUnicodeChar(@NotNull CharSequence text, int pos, int length) {
|
||||||
public boolean isOneLine() {
|
if (length > 1) {
|
||||||
return false;
|
val s = switch (text.charAt(pos + 1)) {
|
||||||
|
case 'x' -> text.subSequence(pos + 2, Math.min(text.length(), pos + length + 1));
|
||||||
|
case 'u' -> text.subSequence(pos + 3, Math.min(text.length(), pos + length));
|
||||||
|
default -> throw new AssertionError();
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(s.toString(), 16);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 63;
|
||||||
}
|
}
|
||||||
};
|
} else {
|
||||||
} else {
|
val c = text.charAt(pos + 1);
|
||||||
throw new AssertionError();
|
return ESC_TO_CODE.getOrDefault(c, c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,18 @@ package com.falsepattern.zigbrains.zig.psi.mixins;
|
||||||
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
|
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
|
||||||
import com.intellij.extapi.psi.ASTWrapperPsiElement;
|
import com.intellij.extapi.psi.ASTWrapperPsiElement;
|
||||||
import com.intellij.lang.ASTNode;
|
import com.intellij.lang.ASTNode;
|
||||||
|
import com.intellij.openapi.util.Pair;
|
||||||
import com.intellij.openapi.util.TextRange;
|
import com.intellij.openapi.util.TextRange;
|
||||||
import com.intellij.psi.LiteralTextEscaper;
|
import com.intellij.psi.LiteralTextEscaper;
|
||||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||||
import com.intellij.psi.impl.source.tree.LeafElement;
|
import com.intellij.psi.impl.source.tree.LeafElement;
|
||||||
|
import lombok.val;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface ZigStringLiteralMixin extends PsiLanguageInjectionHost {
|
public interface ZigStringLiteralMixin extends PsiLanguageInjectionHost {
|
||||||
|
boolean isMultiLine();
|
||||||
|
@NotNull List<TextRange> getContentRanges();
|
||||||
|
@NotNull List<Pair<TextRange, String>> getDecodeReplacements(@NotNull CharSequence input);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ public final class ZLSProjectSettingsService extends WrappingStateComponent<ZLSS
|
||||||
modified |= !Objects.equals(myData.buildOnSaveStep, otherData.buildOnSaveStep);
|
modified |= !Objects.equals(myData.buildOnSaveStep, otherData.buildOnSaveStep);
|
||||||
modified |= myData.highlightGlobalVarDeclarations != otherData.highlightGlobalVarDeclarations;
|
modified |= myData.highlightGlobalVarDeclarations != otherData.highlightGlobalVarDeclarations;
|
||||||
modified |= myData.dangerousComptimeExperimentsDoNotEnable != otherData.dangerousComptimeExperimentsDoNotEnable;
|
modified |= myData.dangerousComptimeExperimentsDoNotEnable != otherData.dangerousComptimeExperimentsDoNotEnable;
|
||||||
|
modified |= myData.inlayHints != otherData.inlayHints;
|
||||||
|
modified |= myData.inlayHintsCompact != otherData.inlayHintsCompact;
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,10 @@ public final class ZLSSettings {
|
||||||
public @NotNull String buildOnSaveStep;
|
public @NotNull String buildOnSaveStep;
|
||||||
public boolean highlightGlobalVarDeclarations;
|
public boolean highlightGlobalVarDeclarations;
|
||||||
public boolean dangerousComptimeExperimentsDoNotEnable;
|
public boolean dangerousComptimeExperimentsDoNotEnable;
|
||||||
|
public boolean inlayHints;
|
||||||
|
public boolean inlayHintsCompact;
|
||||||
|
|
||||||
public ZLSSettings() {
|
public ZLSSettings() {
|
||||||
this(null, "", false, false, false, "install", false, false);
|
this(null, "", false, false, false, "install", false, false, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ public class ZLSSettingsPanel implements Disposable {
|
||||||
private final JBTextField buildOnSaveStep = new ExtendableTextField();
|
private final JBTextField buildOnSaveStep = new ExtendableTextField();
|
||||||
private final JBCheckBox highlightGlobalVarDeclarations = new JBCheckBox();
|
private final JBCheckBox highlightGlobalVarDeclarations = new JBCheckBox();
|
||||||
private final JBCheckBox dangerousComptimeExperimentsDoNotEnable = new JBCheckBox();
|
private final JBCheckBox dangerousComptimeExperimentsDoNotEnable = new JBCheckBox();
|
||||||
|
private final JBCheckBox inlayHints = new JBCheckBox();
|
||||||
|
private final JBCheckBox inlayHintsCompact = new JBCheckBox();
|
||||||
|
|
||||||
private final JBCheckBox messageTrace = new JBCheckBox();
|
private final JBCheckBox messageTrace = new JBCheckBox();
|
||||||
private final JBCheckBox debug = new JBCheckBox();
|
private final JBCheckBox debug = new JBCheckBox();
|
||||||
|
@ -56,6 +58,8 @@ public class ZLSSettingsPanel implements Disposable {
|
||||||
buildOnSaveStep.setToolTipText("Select which step should be executed on build-on-save");
|
buildOnSaveStep.setToolTipText("Select which step should be executed on build-on-save");
|
||||||
highlightGlobalVarDeclarations.setToolTipText("Whether to highlight global var declarations");
|
highlightGlobalVarDeclarations.setToolTipText("Whether to highlight global var declarations");
|
||||||
dangerousComptimeExperimentsDoNotEnable.setToolTipText("Whether to use the comptime interpreter");
|
dangerousComptimeExperimentsDoNotEnable.setToolTipText("Whether to use the comptime interpreter");
|
||||||
|
inlayHints.setToolTipText("Enable inlay hints");
|
||||||
|
inlayHintsCompact.setToolTipText("Replace extremely long error{...} snippets in inlay hints with a placeholder");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void autodetect(ActionEvent e) {
|
private void autodetect(ActionEvent e) {
|
||||||
|
@ -80,6 +84,8 @@ public class ZLSSettingsPanel implements Disposable {
|
||||||
r.button("Autodetect", $f(this::autodetect));
|
r.button("Autodetect", $f(this::autodetect));
|
||||||
});
|
});
|
||||||
p.cell("Config path (leave empty to use built-in config)", zlsConfigPath, AlignX.FILL);
|
p.cell("Config path (leave empty to use built-in config)", zlsConfigPath, AlignX.FILL);
|
||||||
|
p.cell("Inlay hints", inlayHints);
|
||||||
|
p.cell("Compact errors in inlay hint", inlayHintsCompact);
|
||||||
p.cell("Build on save", buildOnSave);
|
p.cell("Build on save", buildOnSave);
|
||||||
p.row("Build on save step", r -> {
|
p.row("Build on save step", r -> {
|
||||||
r.cell(buildOnSaveStep).resizableColumn().align(AlignX.FILL);
|
r.cell(buildOnSaveStep).resizableColumn().align(AlignX.FILL);
|
||||||
|
@ -95,13 +101,15 @@ public class ZLSSettingsPanel implements Disposable {
|
||||||
|
|
||||||
public ZLSSettings getData() {
|
public ZLSSettings getData() {
|
||||||
return new ZLSSettings(zlsPath.getText(),
|
return new ZLSSettings(zlsPath.getText(),
|
||||||
zlsConfigPath.getText(),
|
zlsConfigPath.getText(),
|
||||||
debug.isSelected(),
|
debug.isSelected(),
|
||||||
messageTrace.isSelected(),
|
messageTrace.isSelected(),
|
||||||
buildOnSave.isSelected(),
|
buildOnSave.isSelected(),
|
||||||
buildOnSaveStep.getText(),
|
buildOnSaveStep.getText(),
|
||||||
highlightGlobalVarDeclarations.isSelected(),
|
highlightGlobalVarDeclarations.isSelected(),
|
||||||
dangerousComptimeExperimentsDoNotEnable.isSelected());
|
dangerousComptimeExperimentsDoNotEnable.isSelected(),
|
||||||
|
inlayHints.isSelected(),
|
||||||
|
inlayHintsCompact.isSelected());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(ZLSSettings value) {
|
public void setData(ZLSSettings value) {
|
||||||
|
@ -113,6 +121,8 @@ public class ZLSSettingsPanel implements Disposable {
|
||||||
buildOnSaveStep.setText(value.buildOnSaveStep);
|
buildOnSaveStep.setText(value.buildOnSaveStep);
|
||||||
highlightGlobalVarDeclarations.setSelected(value.highlightGlobalVarDeclarations);
|
highlightGlobalVarDeclarations.setSelected(value.highlightGlobalVarDeclarations);
|
||||||
dangerousComptimeExperimentsDoNotEnable.setSelected(value.dangerousComptimeExperimentsDoNotEnable);
|
dangerousComptimeExperimentsDoNotEnable.setSelected(value.dangerousComptimeExperimentsDoNotEnable);
|
||||||
|
inlayHints.setSelected(value.inlayHints);
|
||||||
|
inlayHintsCompact.setSelected(value.inlayHintsCompact);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.falsepattern.zigbrains.zig.util;
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.TextRange;
|
||||||
|
import lombok.val;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PsiTextUtil {
|
||||||
|
public static @NotNull TextRange getTextRangeBounds(@NotNull List<TextRange> contentRanges) {
|
||||||
|
if (contentRanges.isEmpty()) {
|
||||||
|
return TextRange.EMPTY_RANGE;
|
||||||
|
}
|
||||||
|
return TextRange.create(contentRanges.getFirst().getStartOffset(), contentRanges.getLast().getEndOffset());
|
||||||
|
}
|
||||||
|
public static @NotNull List<TextRange> getMultiLineContent(@NotNull String text, @NotNull String startMark) {
|
||||||
|
val result = new ArrayList<TextRange>();
|
||||||
|
int stringStart = 0;
|
||||||
|
boolean inBody = false;
|
||||||
|
val textLength = text.length();
|
||||||
|
val firstChar = startMark.charAt(0);
|
||||||
|
val extraChars = startMark.substring(1);
|
||||||
|
for (int i = 0; i < textLength; i++) {
|
||||||
|
val cI = text.charAt(i);
|
||||||
|
if (!inBody) {
|
||||||
|
if (cI == firstChar &&
|
||||||
|
i + extraChars.length() < textLength) {
|
||||||
|
for (int j = 0; j < extraChars.length(); j++) {
|
||||||
|
if (text.charAt(i + j + 1) != startMark.charAt(j)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += extraChars.length();
|
||||||
|
inBody = true;
|
||||||
|
stringStart = i + 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cI == '\r') {
|
||||||
|
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
inBody = false;
|
||||||
|
result.add(new TextRange(stringStart, i + 1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cI == '\n') {
|
||||||
|
inBody = false;
|
||||||
|
result.add(new TextRange(stringStart, i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue