blob: fc57ba33cd5c3cc346a48a25ac5070aad4dd7cf6 [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.psi.PsiElement;
import com.intellij.psi.PsiLiteralExpression;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ipp.base.Intention;
import com.siyeh.ipp.base.PsiElementEditorPredicate;
import com.siyeh.ipp.base.PsiElementPredicate;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Bas Leijdekkers
*/
public class ReplaceOctalEscapeWithUnicodeEscapeIntention 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 = indexOfOctalEscape(text, anchor + 1);
if (index < 0) {
break;
}
replacement.append(text.substring(anchor, index));
anchor = appendUnicodeEscape(text, index, replacement);
}
replacement.append(text.substring(anchor, textLength));
document.replaceString(start, end, replacement);
}
else if (element instanceof PsiLiteralExpression) {
final PsiLiteralExpression literalExpression = (PsiLiteralExpression)element;
final String text = literalExpression.getText();
final CaretModel model = editor.getCaretModel();
final int offset = model.getOffset() - literalExpression.getTextOffset();
final StringBuilder newLiteralText = new StringBuilder();
final int index1 = indexOfOctalEscape(text, offset);
final int index2 = indexOfOctalEscape(text, offset + 1);
final int escapeStart = index2 == offset ? index2 : index1;
newLiteralText.append(text.substring(0, escapeStart));
final int escapeEnd = appendUnicodeEscape(text, escapeStart, newLiteralText);
newLiteralText.append(text.substring(escapeEnd, text.length()));
PsiReplacementUtil.replaceExpression(literalExpression, newLiteralText.toString());
}
}
private static int appendUnicodeEscape(String text, int escapeStart, @NonNls StringBuilder out) {
final int textLength = text.length();
int length = 1;
boolean zeroToThree = false;
while (escapeStart + length < textLength) {
final char c = text.charAt(escapeStart + length);
if (length == 1 && (c == '0' || c == '1' || c == '2' || c == '3')) {
zeroToThree = true;
}
if (c < '0' || c > '7' || length > 3 || (length > 2 && !zeroToThree)) {
final int ch = Integer.parseInt(text.substring(escapeStart + 1, escapeStart + length), 8);
out.append("\\u").append(String.format("%04x", Integer.valueOf(ch)));
break;
}
length++;
}
return escapeStart + length;
}
private static int indexOfOctalEscape(String text, int offset) {
final int textLength = text.length();
int escapeStart = -1;
outer: while (true) {
escapeStart = text.indexOf('\\', escapeStart + 1);
if (escapeStart < 0) {
break;
}
if (escapeStart < offset - 4 || escapeStart < textLength - 1 && text.charAt(escapeStart + 1) == '\\') {
continue;
}
boolean isEscape = true;
int previousChar = escapeStart - 1;
while (previousChar >= 0 && text.charAt(previousChar) == '\\') {
isEscape = !isEscape;
previousChar--;
}
if (!isEscape) {
continue;
}
int length = 1;
// see JLS 3.10.6. Escape Sequences for Character and String Literals
boolean zeroToThree = false;
while (escapeStart + length < textLength) {
final char c = text.charAt(escapeStart + length);
if (length == 1 && (c == '0' || c == '1' || c == '2' || c == '3')) {
zeroToThree = true;
}
if (c < '0' || c > '7' || length > 3 || (length > 2 && !zeroToThree)) {
if (offset <= escapeStart + length && length > 1) {
return escapeStart;
}
continue outer;
}
length++;
}
return escapeStart;
}
return -1;
}
@NotNull
@Override
protected PsiElementPredicate getElementPredicate() {
return new OctalEscapePredicate();
}
private static class OctalEscapePredicate extends PsiElementEditorPredicate {
@Override
public boolean satisfiedBy(PsiElement element, @Nullable Editor editor) {
if (editor == null) {
return false;
}
final SelectionModel selectionModel = editor.getSelectionModel();
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/51155
return false;
}
final String text = editor.getDocument().getCharsSequence().subSequence(start, end).toString();
return indexOfOctalEscape(text, 1) >= 0;
}
else if (element instanceof PsiLiteralExpression) {
final PsiLiteralExpression literalExpression = (PsiLiteralExpression)element;
final String text = literalExpression.getText();
final CaretModel model = editor.getCaretModel();
final int offset = model.getOffset() - literalExpression.getTextOffset();
final int index = indexOfOctalEscape(text, offset);
return index >= 0 && offset >= index;
}
return false;
}
}
}