backport: 18.1.0
This commit is contained in:
parent
c0f84df37e
commit
a2f3f8bc95
14 changed files with 478 additions and 29 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -17,6 +17,18 @@ Changelog structure reference:
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [18.1.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Zig
|
||||
- Basic language injections in strings
|
||||
|
||||
### Fixed
|
||||
|
||||
- LSP
|
||||
- No more error spam when zig or zls binary is missing.
|
||||
|
||||
## [18.0.0]
|
||||
|
||||
### Added
|
||||
|
|
|
@ -15,6 +15,7 @@ through the built-in plugin browser:
|
|||
1. Go to `Settings -> Plugins`
|
||||
2. To the right of the `Installed` button at the top, click on the `...` dropdown menu, then select `Manage Plugin Repositories...`
|
||||
3. Click the add button, and then enter the ZigBrains updater URL, based on your IDE version:
|
||||
- `2024.3.*`: https://falsepattern.com/zigbrains/updatePlugins-243.xml
|
||||
- `2024.2.*`: https://falsepattern.com/zigbrains/updatePlugins-242.xml
|
||||
- `2024.1.*`: https://falsepattern.com/zigbrains/updatePlugins-241.xml
|
||||
- `2023.3.*`: https://falsepattern.com/zigbrains/updatePlugins-233.xml
|
||||
|
|
|
@ -202,6 +202,7 @@ project(":zig") {
|
|||
lsp4ijDep()
|
||||
intellijPlatform {
|
||||
plugin(lsp4ijPluginString)
|
||||
bundledPlugin("org.intellij.intelliLang")
|
||||
}
|
||||
}
|
||||
tasks {
|
||||
|
|
|
@ -11,7 +11,7 @@ baseIDE=clion
|
|||
ideaVersion=2024.1.6
|
||||
clionVersion=2024.1.5
|
||||
|
||||
pluginVersion=18.0.0
|
||||
pluginVersion=18.1.0
|
||||
|
||||
# Gradle Releases -> https://github.com/gradle/gradle/releases
|
||||
gradleVersion=8.10.2
|
||||
|
|
|
@ -51,6 +51,9 @@
|
|||
bundle="zigbrains.Bundle"
|
||||
key="notif-zls-error"
|
||||
id="ZigBrains.ZLS"/>
|
||||
|
||||
<lang.elementManipulator forClass="com.falsepattern.zigbrains.zig.psi.ZigStringLiteral"
|
||||
implementationClass="com.falsepattern.zigbrains.zig.psi.ZigStringElementManipulator"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
|
||||
|
@ -181,6 +184,7 @@ The <a href="https://github.com/Zigtools/ZLS">Zig Language Server</a>, via ZigBr
|
|||
<depends optional="true" config-file="zigbrains-zig-cidr-workspace.xml">com.intellij.cidr.base</depends>
|
||||
<depends optional="true" config-file="zigbrains-zig-debugger.xml">com.intellij.modules.cidr.debugger</depends>
|
||||
<depends optional="true" config-file="zigbrains-zig-clion.xml">com.intellij.modules.clion</depends>
|
||||
<depends optional="true" config-file="zigbrains-zig-intellilang.xml">org.intellij.intelliLang</depends>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<notificationGroup displayType="BALLOON"
|
||||
|
|
|
@ -163,6 +163,10 @@
|
|||
IDENTIFIER='identifier'
|
||||
BUILTINIDENTIFIER='builtin identifier'
|
||||
]
|
||||
|
||||
//Mixins
|
||||
mixin("StringLiteral")="com.falsepattern.zigbrains.zig.psi.impl.mixins.ZigStringLiteralMixinImpl"
|
||||
implements("StringLiteral")="com.falsepattern.zigbrains.zig.psi.mixins.ZigStringLiteralMixin"
|
||||
}
|
||||
|
||||
Root ::= CONTAINER_DOC_COMMENT? ContainerMembers?
|
||||
|
|
|
@ -96,7 +96,6 @@ string_char= {char_escape}
|
|||
CONTAINER_DOC_COMMENT=("//!" [^\n]* [ \n]*)+
|
||||
DOC_COMMENT=("///" [^\n]* [ \n]*)+
|
||||
LINE_COMMENT="//" [^\n]* | "////" [^\n]*
|
||||
line_string=("\\\\" [^\n]* [ \n]*)+
|
||||
|
||||
FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})?
|
||||
| {dec_int} "." {dec_int} ([eE] [-+]? {dec_int})?
|
||||
|
@ -112,6 +111,7 @@ IDENTIFIER_PLAIN=[A-Za-z_][A-Za-z0-9_]*
|
|||
BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
|
||||
|
||||
%state STR_LIT
|
||||
%state STR_MULT_LINE
|
||||
%state CHAR_LIT
|
||||
|
||||
%state ID_QUOT
|
||||
|
@ -261,7 +261,9 @@ BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
|
|||
<YYINITIAL> "\"" { yybegin(STR_LIT); }
|
||||
<STR_LIT> {string_char}*"\"" { yybegin(YYINITIAL); return STRING_LITERAL_SINGLE; }
|
||||
<STR_LIT> [^] { yypushback(1); yybegin(UNT_QUOT); }
|
||||
<YYINITIAL> {line_string}+ { return STRING_LITERAL_MULTI; }
|
||||
<YYINITIAL> "\\\\" { yypushback(2); yybegin(STR_MULT_LINE); }
|
||||
<STR_MULT_LINE> [^\n]* [ \n]* "\\\\" { }
|
||||
<STR_MULT_LINE> [^\n]* \n { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||
|
||||
<YYINITIAL> {IDENTIFIER_PLAIN} { return IDENTIFIER; }
|
||||
<YYINITIAL> "@\"" { yybegin(ID_QUOT); }
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package com.falsepattern.zigbrains.zig.intellilang;
|
||||
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import org.intellij.plugins.intelliLang.inject.AbstractLanguageInjectionSupport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ZigLanguageInjectionSupport extends AbstractLanguageInjectionSupport {
|
||||
@Override
|
||||
public @NotNull String getId() {
|
||||
return "zig";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> @NotNull [] getPatternClasses() {
|
||||
return new Class[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicableTo(PsiLanguageInjectionHost host) {
|
||||
return host instanceof ZigStringLiteral;
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ public class ZLSLanguageServerFactory implements LanguageServerFactory, Language
|
|||
|
||||
@Override
|
||||
public boolean isEnabled(@NotNull Project project) {
|
||||
return enabled;
|
||||
return enabled && ZLSStreamConnectionProvider.doGetCommand(project, false) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -34,40 +34,48 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
|
|||
public ZLSStreamConnectionProvider(Project project) {
|
||||
val command = getCommand(project);
|
||||
val projectDir = ProjectUtil.guessProjectDir(project);
|
||||
GeneralCommandLine commandLine;
|
||||
GeneralCommandLine commandLine = null;
|
||||
try {
|
||||
val cmd = command.get();
|
||||
if (cmd != null) {
|
||||
commandLine = new GeneralCommandLine(command.get());
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (projectDir != null) {
|
||||
if (commandLine != null && projectDir != null) {
|
||||
commandLine.setWorkDirectory(projectDir.getPath());
|
||||
}
|
||||
setCommandLine(commandLine);
|
||||
}
|
||||
|
||||
private static List<String> doGetCommand(Project project) {
|
||||
public static List<String> doGetCommand(Project project, boolean warn) {
|
||||
var svc = ZLSProjectSettingsService.getInstance(project);
|
||||
val state = svc.getState();
|
||||
var zlsPath = state.zlsPath;
|
||||
if (StringUtil.isEmpty(zlsPath)) {
|
||||
zlsPath = com.falsepattern.zigbrains.common.util.FileUtil.findExecutableOnPATH("zls").map(Path::toString).orElse(null);
|
||||
if (zlsPath == null) {
|
||||
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Could not detect ZLS binary! Please configure it!",
|
||||
if (warn) {
|
||||
Notifications.Bus.notify(
|
||||
new Notification("ZigBrains.ZLS", "Could not detect ZLS binary! Please configure it!",
|
||||
NotificationType.ERROR));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
state.setZlsPath(zlsPath);
|
||||
}
|
||||
if (!validatePath("ZLS Binary", zlsPath, false)) {
|
||||
if (!validatePath("ZLS Binary", zlsPath, false, warn)) {
|
||||
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));
|
||||
if (!configPath.isBlank() && !validatePath("ZLS Config", configPath, false, warn)) {
|
||||
if (warn) {
|
||||
Notifications.Bus.notify(
|
||||
new Notification("ZigBrains.ZLS", "Using default config path.", NotificationType.INFORMATION));
|
||||
}
|
||||
configPath = null;
|
||||
}
|
||||
if (configPath == null || configPath.isBlank()) {
|
||||
|
@ -87,8 +95,11 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
|
|||
}
|
||||
configPath = tmpFile.toAbsolutePath().toString();
|
||||
} catch (IOException e) {
|
||||
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Failed to create automatic zls config file",
|
||||
if (warn) {
|
||||
Notifications.Bus.notify(
|
||||
new Notification("ZigBrains.ZLS", "Failed to create automatic zls config file",
|
||||
NotificationType.WARNING));
|
||||
}
|
||||
LOG.warn(e);
|
||||
configOK = false;
|
||||
}
|
||||
|
@ -121,7 +132,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
|
|||
val future = new CompletableFuture<List<String>>();
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
try {
|
||||
future.complete(doGetCommand(project));
|
||||
future.complete(doGetCommand(project, true));
|
||||
} catch (Throwable t) {
|
||||
future.completeExceptionally(t);
|
||||
}
|
||||
|
@ -129,7 +140,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
|
|||
return future;
|
||||
}
|
||||
|
||||
private static boolean validatePath(String name, String pathTxt, boolean dir) {
|
||||
private static boolean validatePath(String name, String pathTxt, boolean dir, boolean warn) {
|
||||
if (pathTxt == null || pathTxt.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -137,23 +148,29 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
|
|||
try {
|
||||
path = Path.of(pathTxt);
|
||||
} catch (InvalidPathException e) {
|
||||
Notifications.Bus.notify(
|
||||
new Notification("ZigBrains.ZLS", "No " + name, "Invalid " + name + " at path \"" + pathTxt + "\"",
|
||||
if (warn) {
|
||||
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
|
||||
"Invalid " + name + " at path \"" + pathTxt + "\"",
|
||||
NotificationType.ERROR));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!Files.exists(path)) {
|
||||
if (warn) {
|
||||
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) {
|
||||
if (warn) {
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
package com.falsepattern.zigbrains.zig.psi;
|
||||
|
||||
import com.falsepattern.zigbrains.zig.ZigFileType;
|
||||
import com.falsepattern.zigbrains.zig.util.PsiUtil;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.AbstractElementManipulator;
|
||||
import com.intellij.psi.PsiFileFactory;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.val;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ZigStringElementManipulator extends AbstractElementManipulator<ZigStringLiteral> {
|
||||
|
||||
|
||||
@Override
|
||||
public @Nullable ZigStringLiteral handleContentChange(@NotNull ZigStringLiteral element, @NotNull TextRange range, String newContent)
|
||||
throws IncorrectOperationException {
|
||||
assert (new TextRange(0, element.getTextLength())).contains(range);
|
||||
val originalContext = element.getText();
|
||||
val isMulti = element.getStringLiteralMulti() != null;
|
||||
val elementRange = getRangeInElement(element);
|
||||
var replacement = originalContext.substring(elementRange.getStartOffset(),
|
||||
range.getStartOffset()) +
|
||||
(isMulti ? newContent : escape(newContent)) +
|
||||
originalContext.substring(range.getEndOffset(),
|
||||
elementRange.getEndOffset());
|
||||
val psiFileFactory = PsiFileFactory.getInstance(element.getProject());
|
||||
if (isMulti) {
|
||||
val column = StringUtil.offsetToLineColumn(element.getContainingFile().getText(), element.getTextOffset()).column;
|
||||
val pfxB = new StringBuilder(column + 2);
|
||||
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(
|
||||
Collectors.joining("\n"));
|
||||
} else {
|
||||
replacement = "\"" + replacement + "\"";
|
||||
}
|
||||
val dummy = psiFileFactory.createFileFromText("dummy." + ZigFileType.INSTANCE.getDefaultExtension(),
|
||||
ZigFileType.INSTANCE, "const x = \n" + replacement + "\n;");
|
||||
val stringLiteral = ((ZigPrimaryTypeExpr)((ZigContainerMembers) dummy.getFirstChild()).getContainerDeclarationsList().get(0).getDeclList().get(0).getGlobalVarDecl().getExpr()).getStringLiteral();
|
||||
return (ZigStringLiteral) element.replace(stringLiteral);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TextRange getRangeInElement(@NotNull ZigStringLiteral element) {
|
||||
if (element.getStringLiteralSingle() != null) {
|
||||
return new TextRange(1, element.getTextLength() - 1);
|
||||
}
|
||||
return super.getRangeInElement(element);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String escape(String input) {
|
||||
val bytes = input.getBytes(StandardCharsets.UTF_8);
|
||||
val result = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
byte c = bytes[i];
|
||||
switch (c) {
|
||||
case '\n' -> result.write("\\n".getBytes(StandardCharsets.UTF_8));
|
||||
case '\r' -> result.write("\\r".getBytes(StandardCharsets.UTF_8));
|
||||
case '\t' -> result.write("\\t".getBytes(StandardCharsets.UTF_8));
|
||||
case '\\' -> result.write("\\\\".getBytes(StandardCharsets.UTF_8));
|
||||
case '"' -> result.write("\\\"".getBytes(StandardCharsets.UTF_8));
|
||||
case '\'', ' ', '!' -> result.write(c);
|
||||
default -> {
|
||||
if (c >= '#' && c <= '&' ||
|
||||
c >= '(' && c <= '[' ||
|
||||
c >= ']' && c <= '~') {
|
||||
result.write(c);
|
||||
} else {
|
||||
result.write("\\x".getBytes(StandardCharsets.UTF_8));
|
||||
result.write(String.format("%02x", c).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package com.falsepattern.zigbrains.zig.psi.impl.mixins;
|
||||
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigStringElementManipulator;
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
|
||||
import com.intellij.extapi.psi.ASTWrapperPsiElement;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.LiteralTextEscaper;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import com.intellij.psi.impl.source.tree.LeafElement;
|
||||
import lombok.val;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ZigStringLiteralMixinImpl extends ASTWrapperPsiElement implements ZigStringLiteral {
|
||||
public ZigStringLiteralMixinImpl(@NotNull ASTNode node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidHost() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public PsiLanguageInjectionHost updateText(@NotNull String text) {
|
||||
if (this.getStringLiteralSingle() instanceof LeafElement leaf) {
|
||||
leaf.replaceWithText(text);
|
||||
} else if (this.getStringLiteralMulti() instanceof LeafElement leaf) {
|
||||
leaf.replaceWithText(text);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public int getOffsetInHost(int offsetInDecoded, @NotNull TextRange rangeInsideHost) {
|
||||
int size = inputOffsets.size();
|
||||
int realOffset = 0;
|
||||
if (size == 0) {
|
||||
realOffset = rangeInsideHost.getStartOffset() + offsetInDecoded;
|
||||
} else if (offsetInDecoded >= size) {
|
||||
realOffset = rangeInsideHost.getStartOffset() + inputOffsets.get(size - 1) +
|
||||
(offsetInDecoded - (size - 1));
|
||||
} else {
|
||||
realOffset = rangeInsideHost.getStartOffset() + inputOffsets.get(offsetInDecoded);
|
||||
}
|
||||
return realOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TextRange getRelevantTextRange() {
|
||||
return new TextRange(1, myHost.getTextLength() - 1);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffsetInHost(int offsetInDecoded, @NotNull TextRange rangeInsideHost) {
|
||||
val str = myHost.getText();
|
||||
boolean inMultiLineString = false;
|
||||
int i = rangeInsideHost.getStartOffset();
|
||||
for (; i < rangeInsideHost.getEndOffset() && offsetInDecoded > 0; 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') {
|
||||
offsetInDecoded--;
|
||||
if (i + 1 < str.length() && str.charAt(i + 1) == '\n') {
|
||||
i++;
|
||||
}
|
||||
inMultiLineString = false;
|
||||
continue;
|
||||
}
|
||||
if (cI == '\n') {
|
||||
offsetInDecoded--;
|
||||
inMultiLineString = false;
|
||||
continue;
|
||||
}
|
||||
offsetInDecoded--;
|
||||
}
|
||||
if (offsetInDecoded != 0)
|
||||
return -1;
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOneLine() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.falsepattern.zigbrains.zig.psi.mixins;
|
||||
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
|
||||
import com.intellij.extapi.psi.ASTWrapperPsiElement;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.LiteralTextEscaper;
|
||||
import com.intellij.psi.PsiLanguageInjectionHost;
|
||||
import com.intellij.psi.impl.source.tree.LeafElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface ZigStringLiteralMixin extends PsiLanguageInjectionHost {
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<idea-plugin package="com.falsepattern.zigbrains.zig.intellilang">
|
||||
<depends>org.intellij.intelliLang</depends>
|
||||
<extensions defaultExtensionNs="org.intellij.intelliLang">
|
||||
<languageSupport implementation="com.falsepattern.zigbrains.zig.intellilang.ZigLanguageInjectionSupport"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
Loading…
Add table
Reference in a new issue