blob: 0137f987a32e6e3d970d357f5914ade04bd17002 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.siyeh.ipp.unicode;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.siyeh.ipp.base.Intention;
import com.siyeh.ipp.base.PsiElementEditorPredicate;
import com.siyeh.ipp.base.PsiElementPredicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Bas Leijdekkers
*/
public class UnicodeUnescapeIntention extends Intention {
@Override
protected void processIntention(@NotNull PsiElement element) {}
@Override
protected void processIntention(Editor editor, @NotNull PsiElement element) {
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasSelection()) {
// does not check if octal escape is inside char or string literal (garbage in, garbage out)
final Document document = editor.getDocument();
final int start = selectionModel.getSelectionStart();
final int end = selectionModel.getSelectionEnd();
final String text = document.getText(new TextRange(start, end));
final int textLength = end - start;
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.substring(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.substring(anchor, textLength));
document.replaceString(start, end, replacement);
}
else {
final CaretModel caretModel = editor.getCaretModel();
final Document document = editor.getDocument();
final int lineNumber = document.getLineNumber(caretModel.getOffset());
final int lineStartOffset = document.getLineStartOffset(lineNumber);
final String line = document.getText(new TextRange(lineStartOffset, document.getLineEndOffset(lineNumber)));
final int column = caretModel.getLogicalPosition().column;
final int index1 = indexOfUnicodeEscape(line, column);
final int index2 = indexOfUnicodeEscape(line, column + 1);
final int escapeStart = index2 == column ? index2 : index1; // if caret is between two unicode escape, replace the right one
int hexStart = escapeStart + 1;
while (line.charAt(hexStart) == 'u') {
hexStart++;
}
final int c = Integer.parseInt(line.substring(hexStart, hexStart + 4), 16);
document.replaceString(lineStartOffset + escapeStart, lineStartOffset + hexStart + 4, String.valueOf((char) c));
}
}
/**
* see JLS 3.3. Unicode Escapes
*/
private static int indexOfUnicodeEscape(@NotNull String text, int offset) {
final int length = text.length();
for (int i = 0; i < length; i++) {
final char c = text.charAt(i);
if (c != '\\') {
continue;
}
boolean isEscape = true;
int previousChar = i - 1;
while (previousChar >= 0 && text.charAt(previousChar) == '\\') {
isEscape = !isEscape;
previousChar--;
}
if (!isEscape) {
continue;
}
int nextChar = i;
do {
nextChar++;
if (nextChar >= length) {
break;
}
}
while (text.charAt(nextChar) == 'u'); // \uuuu0061 is a legal unicode escape
if (nextChar == i + 1 || nextChar + 3 >= length) {
break;
}
if (StringUtil.isHexDigit(text.charAt(nextChar)) &&
StringUtil.isHexDigit(text.charAt(nextChar + 1)) &&
StringUtil.isHexDigit(text.charAt(nextChar + 2)) &&
StringUtil.isHexDigit(text.charAt(nextChar + 3))) {
final int escapeEnd = nextChar + 4;
if (offset <= escapeEnd) {
return i;
}
}
}
return -1;
}
@NotNull
@Override
protected PsiElementPredicate getElementPredicate() {
return new UnicodeEscapePredicate();
}
private static class UnicodeEscapePredicate extends PsiElementEditorPredicate {
@Override
public boolean satisfiedBy(PsiElement element, @Nullable Editor editor) {
if (editor == null) {
return false;
}
final SelectionModel selectionModel = editor.getSelectionModel();
final Document document = editor.getDocument();
if (selectionModel.hasSelection()) {
final int start = selectionModel.getSelectionStart();
final int end = selectionModel.getSelectionEnd();
if (start < 0 || end < 0 || start > end) {
// shouldn't happen but http://ea.jetbrains.com/browser/ea_problems/50192
return false;
}
final String text = document.getCharsSequence().subSequence(start, end).toString();
return indexOfUnicodeEscape(text, 1) >= 0;
}
else {
final CaretModel caretModel = editor.getCaretModel();
final int lineNumber = document.getLineNumber(caretModel.getOffset());
final String line = document.getText(new TextRange(document.getLineStartOffset(lineNumber), document.getLineEndOffset(lineNumber)));
final int column = caretModel.getLogicalPosition().column;
final int index = indexOfUnicodeEscape(line, column);
return index >= 0 && column >= index;
}
}
}
}