1 19 package org.netbeans.modules.diff.builtin.visualizer.editable; 20 21 import org.netbeans.api.diff.Difference; 22 import org.netbeans.editor.EditorUI; 23 import org.netbeans.editor.Utilities; 24 import org.netbeans.editor.BaseDocument; 25 import org.netbeans.spi.editor.highlighting.HighlightsContainer; 26 import org.netbeans.spi.diff.DiffProvider; 27 import org.openide.util.Lookup; 28 29 import javax.swing.event.ChangeListener ; 30 import javax.swing.event.ChangeEvent ; 31 import javax.swing.*; 32 import javax.swing.text.*; 33 import java.awt.Dimension ; 34 import java.util.*; 35 import java.io.*; 36 37 42 class DiffViewManager implements ChangeListener { 43 44 private final EditableDiffView master; 45 46 private final DiffContentPanel leftContentPanel; 47 private final DiffContentPanel rightContentPanel; 48 49 52 private boolean myScrollEvent; 53 54 private int cachedDiffSerial; 55 private DecoratedDifference [] decorationsCached = new DecoratedDifference[0]; 56 private HighLight [] secondHilitesCached = new HighLight[0]; 57 private HighLight [] firstHilitesCached = new HighLight[0]; 58 private final ScrollMapCached scrollMap = new ScrollMapCached(); 59 60 public DiffViewManager(EditableDiffView master) { 61 this.master = master; 62 this.leftContentPanel = master.getEditorPane1(); 63 this.rightContentPanel = master.getEditorPane2(); 64 } 65 66 void init() { 67 initScrolling(); 68 } 69 70 private void initScrolling() { 71 leftContentPanel.getScrollPane().getVerticalScrollBar().getModel().addChangeListener(this); 72 rightContentPanel.getScrollPane().getVerticalScrollBar().getModel().addChangeListener(this); 73 leftContentPanel.getScrollPane().getVerticalScrollBar().setPreferredSize(new Dimension (0, 0)); 76 } 77 78 public void stateChanged(ChangeEvent e) { 79 JScrollBar leftScrollBar = leftContentPanel.getScrollPane().getVerticalScrollBar(); 80 JScrollBar rightScrollBar = rightContentPanel.getScrollPane().getVerticalScrollBar(); 81 if (e.getSource() == leftContentPanel.getScrollPane().getVerticalScrollBar().getModel()) { 82 int value = leftScrollBar.getValue(); 83 leftContentPanel.getActionsScrollPane().getVerticalScrollBar().setValue(value); 84 if (myScrollEvent) return; 85 myScrollEvent = true; 86 rightScrollBar.setValue((int) (value / getScrollFactor())); 87 } else { 88 int value = rightScrollBar.getValue(); 89 rightContentPanel.getActionsScrollPane().getVerticalScrollBar().setValue(value); 90 if (myScrollEvent) return; 91 myScrollEvent = true; 92 smartScroll(); 93 } 94 master.getMyDivider().repaint(); 95 myScrollEvent = false; 96 } 97 98 public void scroll() { 99 myScrollEvent = true; 100 smartScroll(); 101 master.getMyDivider().repaint(); 102 myScrollEvent = false; 103 } 104 105 EditableDiffView getMaster() { 106 return master; 107 } 108 109 private void updateDifferences() { 110 assert Thread.holdsLock(this); 111 int mds = master.getDiffSerial(); 112 if (mds <= cachedDiffSerial) return; 113 cachedDiffSerial = mds; 114 computeDecorations(); 115 computeSecondHighlights(); 116 computeFirstHighlights(); 117 } 118 119 public synchronized DecoratedDifference [] getDecorations() { 120 updateDifferences(); 121 return decorationsCached; 122 } 123 124 public synchronized HighLight[] getSecondHighlights() { 125 updateDifferences(); 126 return secondHilitesCached; 127 } 128 129 public synchronized HighLight[] getFirstHighlights() { 130 updateDifferences(); 131 return firstHilitesCached; 132 } 133 134 private void computeFirstHighlights() { 135 List<HighLight> hilites = new ArrayList<HighLight>(); 136 Document doc = leftContentPanel.getEditorPane().getDocument(); 137 for (DecoratedDifference dd : decorationsCached) { 138 Difference diff = dd.getDiff(); 139 if (dd.getBottomLeft() == -1) continue; 140 int start = getRowStartFromLineOffset(doc, diff.getFirstStart() - 1); 141 if (isOneLineChange(diff)) { 142 CorrectRowTokenizer firstSt = new CorrectRowTokenizer(diff.getFirstText()); 143 CorrectRowTokenizer secondSt = new CorrectRowTokenizer(diff.getSecondText()); 144 for (int i = diff.getSecondStart(); i <= diff.getSecondEnd(); i++) { 145 String firstRow = firstSt.nextToken(); 146 String secondRow = secondSt.nextToken(); 147 List<HighLight> rowhilites = computeFirstRowHilites(start, firstRow, secondRow); 148 hilites.addAll(rowhilites); 149 start += firstRow.length() + 1; 150 } 151 } else { 152 int end = getRowStartFromLineOffset(doc, diff.getFirstEnd()); 153 if (end == -1) { 154 end = doc.getLength(); 155 } 156 SimpleAttributeSet attrs = new SimpleAttributeSet(); 157 StyleConstants.setBackground(attrs, master.getColor(diff)); 158 attrs.addAttribute(HighlightsContainer.ATTR_EXTENDS_EOL, Boolean.TRUE); 159 hilites.add(new HighLight(start, end, attrs)); 160 } 161 } 162 firstHilitesCached = hilites.toArray(new HighLight[hilites.size()]); 163 } 164 165 static int getRowStartFromLineOffset(Document doc, int lineIndex) { 166 if (doc instanceof BaseDocument) { 167 return Utilities.getRowStartFromLineOffset((BaseDocument) doc, lineIndex); 168 } else { 169 Element element = doc.getDefaultRootElement(); 171 Element line = element.getElement(lineIndex); 172 return line.getStartOffset(); 173 } 174 } 175 176 private void computeSecondHighlights() { 177 List<HighLight> hilites = new ArrayList<HighLight>(); 178 Document doc = rightContentPanel.getEditorPane().getDocument(); 179 for (DecoratedDifference dd : decorationsCached) { 180 Difference diff = dd.getDiff(); 181 if (dd.getBottomRight() == -1) continue; 182 int start = getRowStartFromLineOffset(doc, diff.getSecondStart() - 1); 183 if (isOneLineChange(diff)) { 184 CorrectRowTokenizer firstSt = new CorrectRowTokenizer(diff.getFirstText()); 185 CorrectRowTokenizer secondSt = new CorrectRowTokenizer(diff.getSecondText()); 186 for (int i = diff.getSecondStart(); i <= diff.getSecondEnd(); i++) { 187 try { 188 String firstRow = firstSt.nextToken(); 189 String secondRow = secondSt.nextToken(); 190 List<HighLight> rowhilites = computeSecondRowHilites(start, firstRow, secondRow); 191 hilites.addAll(rowhilites); 192 start += secondRow.length() + 1; 193 } catch (Exception e) { 194 e.printStackTrace(); 195 } 196 } 197 } else { 198 int end = getRowStartFromLineOffset(doc, diff.getSecondEnd()); 199 if (end == -1) { 200 end = doc.getLength(); 201 } 202 SimpleAttributeSet attrs = new SimpleAttributeSet(); 203 StyleConstants.setBackground(attrs, master.getColor(diff)); 204 attrs.addAttribute(HighlightsContainer.ATTR_EXTENDS_EOL, Boolean.TRUE); 205 hilites.add(new HighLight(start, end, attrs)); 206 } 207 } 208 secondHilitesCached = hilites.toArray(new HighLight[hilites.size()]); 209 } 210 211 private List<HighLight> computeFirstRowHilites(int rowStart, String left, String right) { 212 List<HighLight> hilites = new ArrayList<HighLight>(4); 213 214 String leftRows = wordsToRows(left); 215 String rightRows = wordsToRows(right); 216 217 DiffProvider diffprovider = Lookup.getDefault().lookup(DiffProvider.class); 218 if (diffprovider == null) { 219 return hilites; 220 } 221 222 Difference[] diffs; 223 try { 224 diffs = diffprovider.computeDiff(new StringReader(leftRows), new StringReader(rightRows)); 225 } catch (IOException e) { 226 return hilites; 227 } 228 229 for (Difference diff : diffs) { 231 if (diff.getType() == Difference.ADD) continue; 232 int start = rowOffset(leftRows, diff.getFirstStart()); 233 int end = rowOffset(leftRows, diff.getFirstEnd() + 1); 234 235 SimpleAttributeSet attrs = new SimpleAttributeSet(); 236 StyleConstants.setBackground(attrs, master.getColor(diff)); 237 hilites.add(new HighLight(rowStart + start, rowStart + end, attrs)); 238 } 239 return hilites; 240 } 241 242 private List<HighLight> computeSecondRowHilites(int rowStart, String left, String right) { 243 List<HighLight> hilites = new ArrayList<HighLight>(4); 244 245 String leftRows = wordsToRows(left); 246 String rightRows = wordsToRows(right); 247 248 DiffProvider diffprovider = Lookup.getDefault().lookup(DiffProvider.class); 249 if (diffprovider == null) { 250 return hilites; 251 } 252 253 Difference[] diffs; 254 try { 255 diffs = diffprovider.computeDiff(new StringReader(leftRows), new StringReader(rightRows)); 256 } catch (IOException e) { 257 return hilites; 258 } 259 260 for (Difference diff : diffs) { 262 if (diff.getType() == Difference.DELETE) continue; 263 int start = rowOffset(rightRows, diff.getSecondStart()); 264 int end = rowOffset(rightRows, diff.getSecondEnd() + 1); 265 266 SimpleAttributeSet attrs = new SimpleAttributeSet(); 267 StyleConstants.setBackground(attrs, master.getColor(diff)); 268 hilites.add(new HighLight(rowStart + start, rowStart + end, attrs)); 269 } 270 return hilites; 271 } 272 273 280 private int rowOffset(String row, int rowIndex) { 281 if (rowIndex == 1) return 0; 282 int newLines = 0; 283 for (int i = 0; i < row.length(); i++) { 284 char c = row.charAt(i); 285 if (c == '\n') { 286 newLines++; 287 if (--rowIndex == 1) { 288 return i + 1 - newLines; 289 } 290 } 291 } 292 return row.length(); 293 } 294 295 private String wordsToRows(String s) { 296 StringBuilder sb = new StringBuilder (s.length() * 2); 297 StringTokenizer st = new StringTokenizer(s, " \t\n[]{};:'\",.<>/?-=_+\\|~!@#$%^&*()", true); while (st.hasMoreTokens()) { 299 String token = st.nextToken(); 300 if (token.length() == 0) continue; 301 sb.append(token); 302 sb.append('\n'); 303 } 304 return sb.toString(); 305 } 306 307 private boolean isOneLineChange(Difference diff) { 308 return diff.getType() == Difference.CHANGE && 309 diff.getFirstEnd() - diff.getFirstStart() == diff.getSecondEnd() - diff.getSecondStart(); 310 } 311 312 private void computeDecorations() { 313 314 EditorUI editorUI = org.netbeans.editor.Utilities.getEditorUI(rightContentPanel.getEditorPane()); 315 int lineHeight = editorUI.getLineHeight(); 316 317 Difference [] diffs = master.getDifferences(); 318 decorationsCached = new DecoratedDifference[diffs.length]; 319 for (int i = 0; i < diffs.length; i++) { 320 Difference difference = diffs[i]; 321 DecoratedDifference dd = new DecoratedDifference(difference); 322 323 if (difference.getType() == Difference.ADD) { 324 dd.topRight = (difference.getSecondStart() - 1) * lineHeight; 325 dd.bottomRight = difference.getSecondEnd() * lineHeight; 326 dd.topLeft = difference.getFirstStart() * lineHeight; 327 dd.floodFill = true; 328 } else if (difference.getType() == Difference.DELETE) { 329 dd.topLeft = (difference.getFirstStart() - 1) * lineHeight; 330 dd.bottomLeft = difference.getFirstEnd() * lineHeight; 331 dd.topRight = difference.getSecondStart() * lineHeight; 332 dd.floodFill = true; 333 } else { 334 dd.topRight = (difference.getSecondStart() - 1) * lineHeight; 335 dd.bottomRight = difference.getSecondEnd() * lineHeight; 336 dd.topLeft = (difference.getFirstStart() - 1) * lineHeight; 337 dd.bottomLeft = difference.getFirstEnd() * lineHeight; 338 dd.floodFill = true; 339 } 340 decorationsCached[i] = dd; 341 } 342 } 343 344 351 private synchronized void smartScroll() { 352 DiffContentPanel rightPane = master.getEditorPane2(); 353 DiffContentPanel leftPane = master.getEditorPane1(); 354 355 int [] map = scrollMap.getScrollMap(rightPane.getSize().height, master.getDiffSerial()); 356 357 int rightOffet = rightPane.getScrollPane().getVerticalScrollBar().getValue(); 358 if (rightOffet >= map.length) return; 359 leftPane.getScrollPane().getVerticalScrollBar().setValue(map[rightOffet]); 360 } 361 362 private int computeLeftOffsetToMatchDifference(DifferencePosition differenceMatchStart, int lineHeight, int rightOffset) { 363 364 Difference diff = differenceMatchStart.getDiff(); 365 boolean matchStart = differenceMatchStart.isStart(); 366 367 int value; 368 int valueSecond; 369 if (matchStart) { 370 value = diff.getFirstStart() * lineHeight; valueSecond = diff.getSecondStart() * lineHeight; } else { 373 if (diff.getType() == Difference.ADD) { 374 value = diff.getFirstStart() * lineHeight; value -= lineHeight; 376 valueSecond = diff.getSecondEnd() * lineHeight; } else { 378 value = diff.getFirstEnd() * lineHeight; if (diff.getType() == Difference.DELETE) { 380 value += lineHeight; 381 valueSecond = diff.getSecondStart() * lineHeight; } else { 383 valueSecond = diff.getSecondEnd() * lineHeight; } 385 } 386 } 387 388 int secondOffset = rightOffset - valueSecond; 390 391 value += secondOffset; 392 if (diff.getType() == Difference.ADD) value += lineHeight; 393 if (diff.getType() == Difference.DELETE) value -= lineHeight; 394 395 return value; 396 } 397 398 private DifferencePosition findDifferenceToMatch(int rightOffset, int rightViewportHeight) { 399 400 DecoratedDifference candidate = null; 401 402 DecoratedDifference [] diffs = getDecorations(); 403 for (DecoratedDifference dd : diffs) { 404 if (dd.getTopRight() > rightOffset + rightViewportHeight) break; 405 if (dd.getBottomRight() != -1) { 406 if (dd.getBottomRight() <= rightOffset) continue; 407 } else { 408 if (dd.getTopRight() <= rightOffset) continue; 409 } 410 if (candidate != null) { 411 if (candidate.getDiff().getType() == Difference.DELETE) { 412 candidate = dd; 413 } else if (candidate.getTopRight() < rightOffset) { 414 candidate = dd; 415 } else if (dd.getTopRight() <= rightOffset + rightViewportHeight / 2) { 416 candidate = dd; 417 } 418 } else { 419 candidate = dd; 420 } 421 } 422 if (candidate == null) return null; 423 boolean matchStart = candidate.getTopRight() > rightOffset + rightViewportHeight / 2; 424 if (candidate.getDiff().getType() == Difference.DELETE && candidate.getTopRight() < rightOffset + rightViewportHeight * 4 / 5) matchStart = false; 425 if (candidate.getDiff().getType() == Difference.DELETE && candidate == diffs[diffs.length -1]) matchStart = false; 426 return new DifferencePosition(candidate.getDiff(), matchStart); 427 } 428 429 private double getScrollFactor() { 430 BoundedRangeModel m1 = leftContentPanel.getScrollPane().getVerticalScrollBar().getModel(); 431 BoundedRangeModel m2 = rightContentPanel.getScrollPane().getVerticalScrollBar().getModel(); 432 return ((double) m1.getMaximum() - m1.getExtent()) / (m2.getMaximum() - m2.getExtent()); 433 } 434 435 436 441 void editorPainting(DecoratedEditorPane decoratedEditorPane) { 442 if (!decoratedEditorPane.isFirst()) { 443 JComponent mydivider = master.getMyDivider(); 444 mydivider.paint(mydivider.getGraphics()); 445 } 446 } 447 448 public static class DifferencePosition { 449 450 private Difference diff; 451 private boolean isStart; 452 453 public DifferencePosition(Difference diff, boolean start) { 454 this.diff = diff; 455 isStart = start; 456 } 457 458 public Difference getDiff() { 459 return diff; 460 } 461 462 public boolean isStart() { 463 return isStart; 464 } 465 } 466 467 public static class DecoratedDifference { 468 private Difference diff; 469 private int topLeft; private int bottomLeft = -1; private int topRight; 472 private int bottomRight = -1; private boolean floodFill; 475 public DecoratedDifference(Difference difference) { 476 diff = difference; 477 } 478 479 public Difference getDiff() { 480 return diff; 481 } 482 483 public int getTopLeft() { 484 return topLeft; 485 } 486 487 public int getBottomLeft() { 488 return bottomLeft; 489 } 490 491 public int getTopRight() { 492 return topRight; 493 } 494 495 public int getBottomRight() { 496 return bottomRight; 497 } 498 499 public boolean isFloodFill() { 500 return floodFill; 501 } 502 } 503 504 public static class HighLight { 505 506 private final int startOffset; 507 private final int endOffset; 508 private final AttributeSet attrs; 509 510 public HighLight(int startOffset, int endOffset, AttributeSet attrs) { 511 this.startOffset = startOffset; 512 this.endOffset = endOffset; 513 this.attrs = attrs; 514 } 515 516 public int getStartOffset() { 517 return startOffset; 518 } 519 520 public int getEndOffset() { 521 return endOffset; 522 } 523 524 public AttributeSet getAttrs() { 525 return attrs; 526 } 527 } 528 529 532 private static class CorrectRowTokenizer { 533 534 private final String s; 535 private int idx; 536 537 public CorrectRowTokenizer(String s) { 538 this.s = s; 539 } 540 541 public String nextToken() { 542 String token = null; 543 for (int end = idx; end < s.length(); end++) { 544 if (s.charAt(end) == '\n') { 545 token = s.substring(idx, end); 546 idx = end + 1; 547 break; 548 } 549 } 550 return token; 551 } 552 } 553 554 private class ScrollMapCached { 555 556 private int rightPanelHeightCached; 557 private int [] scrollMapCached; 558 private int diffSerialCached; 559 560 public synchronized int[] getScrollMap(int rightPanelHeight, int diffSerial) { 561 if (rightPanelHeight != rightPanelHeightCached || diffSerialCached != diffSerial || scrollMapCached == null) { 562 diffSerialCached = diffSerial; 563 rightPanelHeightCached = rightPanelHeight; 564 scrollMapCached = compute(); 565 } 566 return scrollMapCached; 567 } 568 569 private int [] compute() { 570 DiffContentPanel rightPane = master.getEditorPane2(); 571 572 int rightViewportHeight = rightPane.getScrollPane().getViewport().getViewRect().height; 573 int rightHeight = rightPane.getEditorPane().getSize().height; 574 575 EditorUI editorUI = org.netbeans.editor.Utilities.getEditorUI(leftContentPanel.getEditorPane()); 576 int lineHeight = editorUI.getLineHeight(); 577 578 int [] scrollMap = new int[rightHeight]; 579 int lastOffset = 0; 580 for (int rightOffset = 0; rightOffset < rightHeight; rightOffset++) { 581 DifferencePosition dpos = findDifferenceToMatch(rightOffset, rightViewportHeight); 582 int leftOffset; 583 if (dpos == null) { 584 leftOffset = lastOffset + rightOffset; 585 } else { 586 leftOffset = computeLeftOffsetToMatchDifference(dpos, lineHeight, rightOffset); 587 lastOffset = leftOffset - rightOffset; 588 } 589 scrollMap[rightOffset] = leftOffset; 590 } 591 scrollMap = smooth(scrollMap); 592 return scrollMap; 593 } 594 595 private int[] smooth(int[] map) { 596 int [] newMap = new int [map.length]; 597 int leftShift = 0; 598 float correction = 0.0f; 599 for (int i = 0; i < map.length; i++) { 600 int leftOffset = map[i]; 601 int requestedShift = leftOffset - i; 602 if (requestedShift > leftShift) { 603 if (correction > requestedShift - leftShift) correction = requestedShift - leftShift; 604 leftShift += correction; 605 correction += 0.02f; 606 } else if (requestedShift < leftShift) { 607 leftShift -= 1; 608 } else { 609 correction = 1.0f; 610 } 611 newMap[i] = i + leftShift; 612 } 613 return newMap; 614 } 615 } 616 } 617 | Popular Tags |