blob: 9bc896f283674aa03d83e2972cf6defdbcf55e46 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Thomas Wolf - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text.java;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.ui.text.ISourceVersionDependent;
/**
* A double click strategy that handles Java character escapes as "words".
*/
public class JavaStringDoubleClickStrategy extends PartitionDoubleClickSelector implements ISourceVersionDependent {
private boolean hasSpaceEscape = false;
public JavaStringDoubleClickStrategy(String partitioning, int leftBorder, int rightBorder) {
super(partitioning, leftBorder, rightBorder);
}
public JavaStringDoubleClickStrategy(String partitioning, int leftBorder, int rightBorder, int hitDeltaOffset) {
super(partitioning, leftBorder, rightBorder, hitDeltaOffset);
}
protected IRegion getPartitionRegion(IDocument document, int offset) throws BadLocationException {
return TextUtilities.getPartition(document, fPartitioning, offset, true);
}
@Override
protected IRegion findWord(IDocument document, int offset) {
IRegion word= super.findWord(document, offset);
try {
IRegion line= document.getLineInformationOfOffset(offset);
IRegion partition= getPartitionRegion(document, offset);
// Restrict the line to the partition
int regionStart= Math.max(line.getOffset(), partition.getOffset() + fLeftBorder);
int regionEnd= Math.min(line.getOffset() + line.getLength(), partition.getOffset() + partition.getLength() - fRightBorder);
if (regionEnd <= regionStart) {
return word;
}
line= new Region(regionStart, regionEnd - regionStart);
if (offset <= line.getOffset() || offset > regionEnd) {
return word;
}
String text= document.get(line.getOffset(), line.getLength());
int textOffset;
int posInLine= offset - line.getOffset();
if (word == null) {
textOffset= posInLine;
} else {
textOffset= word.getOffset() - line.getOffset();
}
if (textOffset <= 0) {
return word;
}
int escapeStart= findEscapeStart(text, textOffset);
if (escapeStart < 0) {
return word;
}
int escapeEnd= findEscapeEnd(text, escapeStart + 1);
if (escapeEnd < 0) {
// Wasn't a valid escape after all
return word;
}
if (escapeEnd > textOffset) {
if (posInLine <= escapeEnd) {
// The escape is the word
return new Region(escapeStart + line.getOffset(), escapeEnd - escapeStart);
}
// The word is to be shortened
if (word == null) {
return null;
}
int start= escapeEnd + line.getOffset();
int length= word.getLength() - (escapeEnd - textOffset);
if (length <= 0) {
return null;
}
return new Region(start, length);
}
return word;
} catch (BadLocationException x) {
return word;
}
}
private int findEscapeStart(String text, int from) {
int i= Math.min(from, text.length() - 1);
int limit= Math.max(0, from - 6); // Longest escape is \u0123: 6 characters
char ch= ' ';
for (; i >= limit; i--) {
ch= text.charAt(i);
if (ch == '\\' || Character.isWhitespace(ch)) {
break;
}
}
if (ch != '\\') {
return -1;
}
// Count backslashes. It's only the start of an escape if there is an odd number of backslashes.
int n= 1;
for (int j= i - 1; j >= 0 && text.charAt(j) == '\\'; j--) {
n++;
}
// If the number is even, the previous backslash is the start of the escape.
return ((n & 1) == 0) ? i - 1 : i;
}
private int findEscapeEnd(String text, int from) {
int length= text.length();
if (from >= length) {
return -1;
}
char ch= text.charAt(from);
switch (ch) {
case 'b': // backspace
case 'f': // form feed
case 'n': // newline
case 'r': // carriage return
case 't': // tab
case '\\':
case '\'':
case '"':
return from + 1;
case 's':
return hasSpaceEscape ? from + 1 : -1;
case 'u':
// Four hex digits
if (length - from > 4) {
for (int i= from + 4; i > from; i--) {
if (!isHex(text.charAt(i))) {
return -1;
}
}
return from + 5;
}
return -1;
default:
if (ch >= '0' && ch <= '7') {
// Octal escape
int max= (ch <= '3') ? 3 : 2;
int i= from + 1;
int limit= Math.min(length, from + max);
for (; i < limit; i++) {
ch= text.charAt(i);
if (!(ch >= '0' && ch <= '7')) {
break;
}
}
return i;
}
return -1;
}
}
private boolean isHex(char ch) {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
}
@Override
public void setSourceVersion(String version) {
hasSpaceEscape= JavaCore.compareJavaVersions(JavaCore.VERSION_15, version) <= 0;
}
}