Handle edge cases in offset mapping

This commit is contained in:
Jakub Valtar
2016-05-06 23:40:42 +02:00
parent 0ca3181cfa
commit 6aedf09854
4 changed files with 108 additions and 51 deletions

View File

@@ -305,8 +305,15 @@ public class JavaTextAreaPainter extends TextAreaPainter
int rightTrimmedLength = trimRight(badCode).length();
int leftTrimLength = rightTrimmedLength - trimmedLength;
// Fix offsets when bad code is just whitespace
if (trimmedLength == 0) {
leftTrimLength = 0;
rightTrimmedLength = badCode.length();
}
int x1 = textArea.offsetToX(line, goodCode.length() + leftTrimLength);
int x2 = textArea.offsetToX(line, goodCode.length() + rightTrimmedLength);
if (x1 == x2) x2 += fm.stringWidth(" ");
int y1 = y + fm.getHeight() - 2;
if (line != problem.getLineNumber()) {

View File

@@ -188,6 +188,7 @@ public class PDEX {
} else {
Messages.log("found declaration, offset " + decl.getStartPosition() + ", name: " + declName);
SketchInterval si = ps.mapJavaToSketch(declName);
if (!ps.inRange(si)) return;
EventQueue.invokeLater(() -> {
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
});
@@ -315,59 +316,59 @@ public class PDEX {
break;
}
String elementName = binding.getName();
// Find usages, map to tree nodes, add to root node
String bindingKey = binding.getKey();
List<SketchInterval> intervals =
findAllOccurrences(ps.compilationUnit, bindingKey).stream()
.map(ps::mapJavaToSketch)
// remove occurrences which fall into generated header
.filter(ps::inRange)
.collect(Collectors.toList());
int usageCount = intervals.size();
// Get element name from PDE code if possible, otherwise use one from Java
String elementName = intervals.stream()
.findAny()
.map(si -> ps.pdeCode.substring(si.startPdeOffset, si.stopPdeOffset))
.orElseGet(binding::getName);
// Create root node
DefaultMutableTreeNode rootNode =
new DefaultMutableTreeNode(bindingType + ": " + elementName);
int usageCount;
intervals.stream()
// Convert to TreeNodes
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
// Group by tab index
.collect(Collectors.groupingBy(node -> node.tabIndex))
// Stream Map Entries of (tab index) <-> (List<ShowUsageTreeNode>)
.entrySet().stream()
// Sort by tab index
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(entry -> {
Integer tabIndex = entry.getKey();
List<ShowUsageTreeNode> nodes = entry.getValue();
{ // Find usages, map to tree nodes, add to root node
String bindingKey = binding.getKey();
List<SketchInterval> intervals =
findAllOccurrences(ps.compilationUnit, bindingKey).stream()
.map(ps::mapJavaToSketch)
// TODO: this has to be fixed with better token mapping
// remove occurrences which fall into generated header
.filter(in -> in.tabIndex != 0 ||
(in.startTabOffset >= 0 && in.stopTabOffset > 0))
.collect(Collectors.toList());
int count = nodes.size();
String usageLabel = count == 1 ? "usage" : "usages";
usageCount = intervals.size();
// Create new DefaultMutableTreeNode for this tab
String tabLabel = "<html><font color=#222222>" +
ps.sketch.getCode(tabIndex).getPrettyName() +
"</font> <font color=#999999>" + count + " " + usageLabel + "</font></html>";
DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel);
Map<Integer, List<ShowUsageTreeNode>> tabGroupedTreeNodes = intervals.stream()
// Convert to TreeNodes
.map(in -> ShowUsageTreeNode.fromSketchInterval(ps, in))
// Group by tab
.collect(Collectors.groupingBy(node -> node.tabIndex));
tabGroupedTreeNodes.entrySet().stream()
// Sort by tab index
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(entry -> {
Integer tabIndex = entry.getKey();
List<ShowUsageTreeNode> nodes = entry.getValue();
int count = nodes.size();
String usageLabel = count == 1 ? "usage" : "usages";
// Create new DefaultMutableTreeNode for this tab
String tabLabel = "<html><font color=#222222>" +
ps.sketch.getCode(tabIndex).getPrettyName() +
"</font> <font color=#999999>" + count + " " + usageLabel + "</font></html>";
DefaultMutableTreeNode tabNode = new DefaultMutableTreeNode(tabLabel);
nodes.stream()
// Convert TreeNodes to DefaultMutableTreeNodes
.map(DefaultMutableTreeNode::new)
// Add all as children of tab node
.forEach(tabNode::add);
return tabNode;
})
// Add all tab nodes as children of root node
.forEach(rootNode::add);
}
// Stream nodes belonging to this tab
nodes.stream()
// Convert TreeNodes to DefaultMutableTreeNodes
.map(DefaultMutableTreeNode::new)
// Add all as children of tab node
.forEach(tabNode::add);
return tabNode;
})
// Add all tab nodes as children of root node
.forEach(rootNode::add);
TreeModel treeModel = new DefaultTreeModel(rootNode);
@@ -654,6 +655,7 @@ public class PDEX {
Map<Integer, List<SketchInterval>> mappedNodes = occurrences.stream()
.map(ps::mapJavaToSketch)
.filter(ps::inRange)
.collect(Collectors.groupingBy(interval -> interval.tabIndex));
Sketch sketch = ps.sketch;
@@ -774,6 +776,7 @@ public class PDEX {
ASTNode node = (ASTNode) tnode.getUserObject();
pps.whenDone(ps -> {
SketchInterval si = ps.mapJavaToSketch(node);
if (!ps.inRange(si)) return;
EventQueue.invokeLater(() -> {
editor.highlight(si.tabIndex, si.startTabOffset, si.stopTabOffset);
});
@@ -886,11 +889,13 @@ public class PDEX {
int start = iproblem.getSourceStart();
int stop = iproblem.getSourceEnd() + 1; // make it exclusive
SketchInterval in = ps.mapJavaToSketch(start, stop);
if (in == SketchInterval.BEFORE_START) return null;
int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset);
Problem p = new Problem(iproblem, in.tabIndex, line);
p.setPDEOffsets(in.startTabOffset, in.stopTabOffset);
return p;
})
.filter(p -> p != null)
.collect(Collectors.toList());
// Handle import suggestions

View File

@@ -50,6 +50,9 @@ public class PreprocessedSketch {
public static class SketchInterval {
public static final SketchInterval BEFORE_START = new SketchInterval(-1, -1, -1, -1, -1);
private SketchInterval(int tabIndex,
int startTabOffset, int stopTabOffset,
int startPdeOffset, int stopPdeOffset) {
@@ -69,6 +72,12 @@ public class PreprocessedSketch {
}
public boolean inRange(SketchInterval interval) {
return interval != SketchInterval.BEFORE_START &&
interval.stopPdeOffset < pdeCode.length();
}
public SketchInterval mapJavaToSketch(ASTNode node) {
return mapJavaToSketch(node.getStartPosition(),
node.getStartPosition() + node.getLength());
@@ -76,13 +85,29 @@ public class PreprocessedSketch {
public SketchInterval mapJavaToSketch(int startJavaOffset, int stopJavaOffset) {
boolean zeroLength = stopJavaOffset == startJavaOffset;
int length = stopJavaOffset - startJavaOffset;
int startPdeOffset = javaOffsetToPdeOffset(startJavaOffset);
int stopPdeOffset = zeroLength ?
javaOffsetToPdeOffset(stopJavaOffset) :
javaOffsetToPdeOffset(stopJavaOffset-1)+1;
int stopPdeOffset;
if (length == 0) {
stopPdeOffset = startPdeOffset;
} else {
stopPdeOffset = javaOffsetToPdeOffset(stopJavaOffset-1);
if (stopPdeOffset >= 0 && (stopPdeOffset > startPdeOffset || length == 1)) {
stopPdeOffset += 1;
}
}
if (startPdeOffset < 0 || stopPdeOffset < 0) {
return SketchInterval.BEFORE_START;
}
int tabIndex = pdeOffsetToTabIndex(startPdeOffset);
if (startPdeOffset >= pdeCode.length()) {
startPdeOffset = pdeCode.length() - 1;
stopPdeOffset = startPdeOffset + 1;
}
return new SketchInterval(tabIndex,
pdeOffsetToTabOffset(tabIndex, startPdeOffset),
pdeOffsetToTabOffset(tabIndex, stopPdeOffset),
@@ -96,6 +121,7 @@ public class PreprocessedSketch {
private int pdeOffsetToTabIndex(int pdeOffset) {
pdeOffset = Math.max(0, pdeOffset);
int tab = Arrays.binarySearch(tabStartOffsets, pdeOffset);
if (tab < 0) {
tab = -(tab + 1) - 1;

View File

@@ -132,7 +132,7 @@ public class TextTransform {
// Process encountered input edits
while (inEdit != null && inOffset >= inEditOff) {
inOffset += inEdit.fromLength;
inMap.add(inEdit);
if (inEdit.fromLength > 0) inMap.add(inEdit);
inEdit = inIt.hasNext() ? inIt.next() : null;
inEditOff = inEdit != null ? inEdit.fromOffset : inLength;
}
@@ -140,7 +140,7 @@ public class TextTransform {
// Process encountered output edits
while (outEdit != null && inOffset >= outEditOff) {
outEdit.toOffset = outOffset;
outMap.add(outEdit);
if (outEdit.toLength > 0) outMap.add(outEdit);
outOffset += outEdit.toLength;
outEdit = outIt.hasNext() ? outIt.next() : null;
outEditOff = outEdit != null ? outEdit.toOffset : inLength;
@@ -225,13 +225,31 @@ public class TextTransform {
private List<Edit> inMap = new ArrayList<>();
private List<Edit> outMap = new ArrayList<>();
private int outputOffsetOfInputStart;
private int inputOffsetOfOutputStart;
private SimpleOffsetMapper(List<Edit> inMap, List<Edit> outMap) {
this.inMap.addAll(inMap);
this.outMap.addAll(outMap);
Edit inStart = null;
for (Edit in : this.inMap) {
inStart = in;
if (in.fromLength > 0) break;
}
outputOffsetOfInputStart = inStart == null ? 0 : inStart.toOffset;
Edit outStart = null;
for (Edit out : this.inMap) {
outStart = out;
if (out.toLength > 0) break;
}
inputOffsetOfOutputStart = outStart == null ? 0 : outStart.fromOffset;
}
@Override
public int getInputOffset(int outputOffset) {
if (outputOffset < outputOffsetOfInputStart) return -1;
Edit searchKey = new Edit(0, 0, outputOffset, Integer.MAX_VALUE, null);
int i = Collections.binarySearch(outMap, searchKey, OUTPUT_OFFSET_COMP);
if (i < 0) {
@@ -246,6 +264,7 @@ public class TextTransform {
@Override
public int getOutputOffset(int inputOffset) {
if (inputOffset < inputOffsetOfOutputStart) return -1;
Edit searchKey = new Edit(inputOffset, Integer.MAX_VALUE, 0, 0, null);
int i = Collections.binarySearch(inMap, searchKey, INPUT_OFFSET_COMP);
if (i < 0) {