@Override
  protected void invoke(@NotNull ActionContext context, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
    TextRange selection = context.selection();
    final Document document = element.getContainingFile().getFileDocument();
    if (!selection.isEmpty()) {
      // does not check if Unicode escape is inside char or string literal (garbage in, garbage out)
      final String text = document.getText(selection);
      final int textLength = selection.getLength();
      final StringBuilder replacement = new StringBuilder(textLength);
      int anchor = 0;
      while (true) {
        final int index = indexOfUnicodeEscape(text, anchor + 1);
        if (index < 0) {
          break;
        }
        replacement.append(text, anchor, index);
        int hexStart = index + 1;
        while (text.charAt(hexStart) == 'u') {
          hexStart++;
        }
        anchor = hexStart + 4;
        final int c = Integer.parseInt(text.substring(hexStart, anchor), 16);
        replacement.appendCodePoint(c);
      }
      replacement.append(text, anchor, textLength);
      final int start = selection.getStartOffset();
      final int end = selection.getEndOffset();
      document.replaceString(start, end, replacement);
    }
    else {
      final int lineNumber = document.getLineNumber(context.offset());
      final int lineStartOffset = document.getLineStartOffset(lineNumber);
      final String line = document.getText(new TextRange(lineStartOffset, document.getLineEndOffset(lineNumber)));
      final int column = context.offset() - lineStartOffset;
      final int index1 = indexOfUnicodeEscape(line, column);
      final int index2 = indexOfUnicodeEscape(line, column + 1);
      // if the caret is between two unicode escapes, replace the one to the right
      final int escapeStart = index2 == column ? index2 : index1;
      int hexStart = escapeStart + 1;
      while (line.charAt(hexStart) == 'u') {
        hexStart++;
      }
      final char c = (char)Integer.parseInt(line.substring(hexStart, hexStart + 4), 16);
      if (Character.isHighSurrogate(c)) {
        hexStart += 4;
        if (line.charAt(hexStart++) == '\\' && line.charAt(hexStart++) == 'u') {
          while (line.charAt(hexStart) == 'u') hexStart++;
          final char d = (char)Integer.parseInt(line.substring(hexStart, hexStart + 4), 16);
          document.replaceString(lineStartOffset + escapeStart, lineStartOffset + hexStart + 4, String.valueOf(new char[] {c, d}));
          return;
        }
      }
      else if (Character.isLowSurrogate(c)) {
        if (escapeStart >= 6 &&
            StringUtil.isHexDigit(line.charAt(escapeStart - 1)) && StringUtil.isHexDigit(line.charAt(escapeStart - 2)) &&
            StringUtil.isHexDigit(line.charAt(escapeStart - 3)) && StringUtil.isHexDigit(line.charAt(escapeStart - 4))) {
          int i = escapeStart - 5;
          while (i > 0 && line.charAt(i) == 'u') i--;
          if (line.charAt(i) == '\\' && (i == 0 || line.charAt(i - 1) != '\\')) {
            final char b = (char)Integer.parseInt(line.substring(escapeStart - 4, escapeStart), 16);
            document.replaceString(lineStartOffset + i, lineStartOffset + hexStart + 4, String.valueOf(new char[] {b, c}));
            return;
          }
        }
      }
      document.replaceString(lineStartOffset + escapeStart, lineStartOffset + hexStart + 4, String.valueOf(c));
    }
  }
@Override
  protected void invoke(@NotNull ActionContext context, @NotNull PsiElement element, @NotNull ModPsiUpdater updater)