package ee.edio.garmin; import com.intellij.openapi.util.TextRange; import com.intellij.psi.LiteralTextEscaper; import com.intellij.psi.PsiLanguageInjectionHost; import org.jetbrains.annotations.NotNull; import java.util.Locale; public class MonkeyStringLiteralEscaper<T extends PsiLanguageInjectionHost> extends LiteralTextEscaper<T> { private int[] outSourceOffsets; public MonkeyStringLiteralEscaper(@NotNull T host) { super(host); } @Override public boolean decode(@NotNull final TextRange rangeInsideHost, @NotNull final StringBuilder outChars) { TextRange.assertProperRange(rangeInsideHost); String subText = rangeInsideHost.substring(myHost.getText()); return parseStringCharacters(subText, outChars); } @Override public int getOffsetInHost(final int offsetInDecoded, @NotNull final TextRange rangeInsideHost) { TextRange.assertProperRange(rangeInsideHost); int result = offsetInDecoded < outSourceOffsets.length ? outSourceOffsets[offsetInDecoded] : -1; if (result == -1) return -1; return (result <= rangeInsideHost.getLength() ? result : rangeInsideHost.getLength()) + rangeInsideHost.getStartOffset(); } /** * Escapes the specified string in accordance with https://golang.org/ref/spec#Rune_literals * * @param chars * @param outChars */ public static void escapeString(@NotNull String chars, @NotNull StringBuilder outChars) { int index = 0; while (index < chars.length()) { int c = chars.codePointAt(index); switch (c) { case (char) 7: outChars.append("\\a"); break; case '\b': outChars.append("\\b"); break; case '\f': outChars.append("\\f"); break; case '\n': outChars.append("\\n"); break; case '\r': outChars.append("\\r"); break; case '\t': outChars.append("\\t"); break; case (char) 0x0b: outChars.append("\\v"); break; case '\\': outChars.append("\\\\"); break; case '\'': outChars.append("\\'"); break; case '"': outChars.append("\\\""); break; default: switch (Character.getType(c)) { case Character.CONTROL: case Character.PRIVATE_USE: case Character.UNASSIGNED: if (c <= 0xffff) { outChars.append("\\u").append(String.format(Locale.US, "%04X", c)); } else { outChars.append("\\U").append(String.format(Locale.US, "%08X", c)); } break; default: outChars.appendCodePoint(c); } } index += Character.charCount(c); } } private boolean parseStringCharacters(String chars, StringBuilder outChars) { outSourceOffsets = new int[chars.length() + 1]; outSourceOffsets[chars.length()] = -1; if (chars.indexOf('\\') < 0) { outChars.append(chars); for (int i = 0; i < outSourceOffsets.length; i++) { outSourceOffsets[i] = i; } return true; } int index = 0; while (index < chars.length()) { char c = chars.charAt(index++); outSourceOffsets[outChars.length()] = index - 1; outSourceOffsets[outChars.length() + 1] = index; if (c != '\\') { outChars.append(c); continue; } if (index == chars.length()) return false; c = chars.charAt(index++); switch (c) { case 'a': outChars.append((char) 7); break; case 'b': outChars.append('\b'); break; case 'f': outChars.append('\f'); break; case 'n': outChars.append('\n'); break; case 'r': outChars.append('\r'); break; case 't': outChars.append('\t'); break; case 'v': outChars.append((char) 0x0b); break; case '\\': outChars.append('\\'); break; case '\'': outChars.append('\''); break; case '"': outChars.append('"'); break; case '\n': outChars.append('\n'); break; // octal case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { char startC = c; int v = (int) c - '0'; if (index < chars.length()) { c = chars.charAt(index++); if ('0' <= c && c <= '7') { v <<= 3; v += c - '0'; if (startC <= '3' && index < chars.length()) { c = chars.charAt(index++); if ('0' <= c && c <= '7') { v <<= 3; v += c - '0'; } else { index--; } } } else { index--; } } outChars.append((char) v); } break; // hex case 'x': if (index + 2 <= chars.length()) { try { int v = Integer.parseInt(chars.substring(index, index + 2), 16); outChars.append((char) v); index += 2; } catch (Exception e) { return false; } } else { return false; } break; // little unicode case 'u': if (index + 4 <= chars.length()) { try { int v = Integer.parseInt(chars.substring(index, index + 4), 16); c = chars.charAt(index); if (c == '+' || c == '-') return false; outChars.append((char) v); index += 4; } catch (Exception e) { return false; } } else { return false; } break; // big unicode case 'U': if (index + 8 <= chars.length()) { try { int v = Integer.parseInt(chars.substring(index, index + 8), 16); c = chars.charAt(index); if (c == '+' || c == '-') return false; outChars.append((char) v); index += 8; } catch (Exception e) { return false; } } else { return false; } break; default: return false; } outSourceOffsets[outChars.length()] = index; } return true; } @Override public boolean isOneLine() { return true; } }