From 2aec7b74c87e7f2f0f747a7db2df0214340d3c29 Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Mon, 4 Jun 2018 00:26:34 +0300 Subject: [PATCH] IDEA-127997 lst: fix rendering for folded regions * Mark lines with folded deletions/insertions as modified. * Scroll to the first block in collapsed area on click, if there are multiple options. * Mark lines with folded ignored and non-ignored changes as non-ignored First, convert ranges into affected visual lines. Next, merge interleaving ranges together. --- .../vcs/ex/LineStatusMarkerPopupRenderer.java | 12 +- .../vcs/ex/LineStatusMarkerRenderer.java | 385 +++++++++++++----- .../openapi/editor/impl/EditorImpl.java | 2 +- .../openapi/vcs/ex/LineStatusTracker.kt | 4 +- .../vcs/ex/PartialLocalLineStatusTracker.kt | 12 +- 5 files changed, 307 insertions(+), 108 deletions(-) diff --git a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java index 49f1e7435006..332d669dba5b 100644 --- a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java +++ b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java @@ -70,13 +70,19 @@ public abstract class LineStatusMarkerPopupRenderer extends LineStatusMarkerRend } @Override - protected boolean canDoAction(@NotNull Editor editor, @NotNull Range range, MouseEvent e) { + protected boolean canDoAction(@NotNull Editor editor, @NotNull List ranges, MouseEvent e) { return isInsideMarkerArea(e); } @Override - protected void doAction(@NotNull Editor editor, @NotNull Range range, @NotNull MouseEvent e) { - showHint(editor, range, e); + protected void doAction(@NotNull Editor editor, @NotNull List ranges, @NotNull MouseEvent e) { + Range range = ranges.get(0); + if (ranges.size() > 1) { + scrollAndShow(editor, range); + } + else { + showHint(editor, range, e); + } } diff --git a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerRenderer.java b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerRenderer.java index 4786cdaa6988..60b40ae1b86c 100644 --- a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerRenderer.java +++ b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerRenderer.java @@ -17,20 +17,24 @@ package com.intellij.openapi.vcs.ex; import com.intellij.diff.util.DiffDrawUtil; import com.intellij.diff.util.DiffUtil; +import com.intellij.diff.util.IntPair; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.DiffColors; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.EditorGutterComponentEx; import com.intellij.openapi.editor.impl.DocumentMarkupModel; +import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.TextRange; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.update.MergingUpdateQueue; @@ -42,10 +46,13 @@ import org.jetbrains.annotations.Nullable; import java.awt.*; import java.awt.event.MouseEvent; import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; import java.util.List; import static com.intellij.diff.util.DiffDrawUtil.lineToY; import static com.intellij.diff.util.DiffDrawUtil.yToLine; +import static com.intellij.diff.util.DiffUtil.getLineCount; import static com.intellij.openapi.diagnostic.Logger.getInstance; import static com.intellij.util.ui.update.MergingUpdateQueue.ANY_COMPONENT; @@ -136,36 +143,46 @@ public abstract class LineStatusMarkerRenderer { } private boolean canDoAction(@NotNull Editor editor, MouseEvent e) { - Range range = getSelectedRange(editor, e); - return range != null && canDoAction(editor, range, e); + List ranges = getSelectedRanges(editor, e.getY()); + return !ranges.isEmpty() && canDoAction(editor, ranges, e); } private void doAction(@NotNull Editor editor, MouseEvent e) { - Range range = getSelectedRange(editor, e); - if (range != null) { - doAction(editor, range, e); + List ranges = getSelectedRanges(editor, e.getY()); + if (!ranges.isEmpty()) { + doAction(editor, ranges, e); } } - @Nullable - private Range getSelectedRange(@NotNull Editor editor, MouseEvent e) { - int line = yToLine(editor, e.getY()); - Range range = myTracker.getRangeForLine(line); - if (range != null) return range; + @NotNull + protected List getSelectedRanges(@NotNull Editor editor, int y) { + int visualLine = editor.xyToVisualPosition(new Point(0, y)).line; + int line1 = editor.visualToLogicalPosition(new VisualPosition(visualLine, 0)).line; + int line2 = editor.visualToLogicalPosition(new VisualPosition(visualLine + 1, 0)).line; + BitSet lines = new BitSet(); + lines.set(line1, line2); + List ranges = myTracker.getRangesForLines(lines); + if (!ContainerUtil.isEmpty(ranges)) return ranges; // special handling for deletion at the end of file - int lineCount = DiffUtil.getLineCount(editor.getDocument()); - if (line == lineCount - 1) { - return myTracker.getRangeForLine(lineCount); + int lineCount = getLineCount(editor.getDocument()); + if (line2 == lineCount) { + Range range = myTracker.getRangeForLine(lineCount); + if (range != null) return Collections.singletonList(range); } - return null; + return Collections.emptyList(); } - protected boolean canDoAction(@NotNull Editor editor, @NotNull Range range, MouseEvent e) { + protected boolean canDoAction(@NotNull Editor editor, @NotNull List ranges, MouseEvent e) { return false; } - protected void doAction(@NotNull Editor editor, @NotNull Range range, MouseEvent e) { + protected void doAction(@NotNull Editor editor, @NotNull List ranges, MouseEvent e) { + } + + @NotNull + protected VisibleRangeMerger createMerger(@NotNull Editor editor) { + return new VisibleRangeMerger(editor); } @Nullable @@ -212,34 +229,30 @@ public abstract class LineStatusMarkerRenderer { List ranges = myTracker.getRanges(); if (ranges == null) return; - Rectangle area = editor.getScrollingModel().getVisibleArea(); - int line1 = yToLine(editor, area.y); - int line2 = yToLine(editor, area.y + area.height); + int framingBorder = getFramingBorderSize(); - for (Range range : ranges) { - if (range.getLine2() < line1) continue; - if (range.getLine1() > line2) return; - paint(editor, range, g); + List> blocks = createMerger(editor).run(ranges); + for (List block: blocks) { + paintChangedLines(g, editor, block, framingBorder); } } - protected void paint(@NotNull Editor editor, @NotNull Range range, @NotNull Graphics g) { - paintRange(g, editor, range, getFramingBorderSize()); - } + private static void paintChangedLines(@NotNull Graphics g, @NotNull Editor editor, @NotNull List block, int framingBorder) { + EditorImpl editorImpl = (EditorImpl)editor; - public static void paintRange(@NotNull Graphics g, - @NotNull Editor editor, - @NotNull Range range, - int framingBorder) { - Color gutterColor = getGutterColor(range, editor); Color borderColor = getGutterBorderColor(editor); Color gutterBackgroundColor = ((EditorEx)editor).getGutterComponentEx().getBackground(); - Rectangle area = getMarkerArea(editor, range.getLine1(), range.getLine2()); - final int x = area.x; - final int endX = area.x + area.width; - final int y = area.y; - final int endY = area.y + area.height; + int line1 = block.get(0).line1; + int line2 = block.get(block.size() - 1).line2; + + IntPair area = getGutterArea(editor); + final int x = area.val1; + final int endX = area.val2; + + final int y = editorImpl.visibleLineToY(line1); + final int endY = editorImpl.visibleLineToY(line2); + if (framingBorder > 0) { if (y != endY) { @@ -249,55 +262,56 @@ public abstract class LineStatusMarkerRenderer { } } - if (y == endY) { - paintTriangle(g, gutterColor, borderColor, x, endX, y); + for (ChangedLines change: block) { + if (change.line1 != change.line2 && + !change.isIgnored) { + int start = editorImpl.visibleLineToY(change.line1); + int end = editorImpl.visibleLineToY(change.line2); + + Color gutterColor = getGutterColor(change.type, editor); + paintRect(g, gutterColor, null, x, start, endX, end); + } + } + + if (borderColor == null) { + for (ChangedLines change: block) { + if (change.line1 != change.line2 && + change.isIgnored) { + int start = editorImpl.visibleLineToY(change.line1); + int end = editorImpl.visibleLineToY(change.line2); + + Color ignoredBorderColor = getIgnoredGutterBorderColor(change.type, editor); + paintRect(g, null, ignoredBorderColor, x, start, endX, end); + } + } } else { - if (range.getInnerRanges() == null) { // Mode.DEFAULT - paintRect(g, gutterColor, borderColor, x, y, endX, endY); - } - else { // Mode.SMART - List innerRanges = range.getInnerRanges(); - for (Range.InnerRange innerRange : innerRanges) { - if (innerRange.getType() == Range.DELETED) continue; + paintRect(g, null, borderColor, x, y, endX, endY); + } - int start = lineToY(editor, range.getLine1() + innerRange.getLine1()); - int end = lineToY(editor, range.getLine1() + innerRange.getLine2()); + for (ChangedLines change: block) { + if (change.line1 == change.line2) { + int start = editorImpl.visibleLineToY(change.line1); - paintRect(g, getGutterColor(innerRange, editor), null, x, start, endX, end); + if (!change.isIgnored) { + Color gutterColor = getGutterColor(change.type, editor); + paintTriangle(g, gutterColor, borderColor, x, endX, start); } - - paintRect(g, null, borderColor, x, y, endX, endY); - - for (Range.InnerRange innerRange : innerRanges) { - if (innerRange.getType() != Range.DELETED) continue; - - int start = lineToY(editor, range.getLine1() + innerRange.getLine1()); - - paintTriangle(g, getGutterColor(innerRange, editor), borderColor, x, endX, start); + else { + Color ignoredBorderColor = getIgnoredGutterBorderColor(change.type, editor); + paintTriangle(g, null, ignoredBorderColor, x, endX, start); } } } } - public static void paintIgnoredRange(@NotNull Graphics g, - @NotNull Editor editor, - @NotNull Range range) { - Color borderColor = getGutterBorderColor(editor); - Color ignoredBorderColor = getIgnoredGutterBorderColor(range, editor); - Color color = borderColor != null ? borderColor : ignoredBorderColor; - - Rectangle area = getMarkerArea(editor, range.getLine1(), range.getLine2()); - final int x = area.x; - final int endX = area.x + area.width; - final int y = area.y; - final int endY = area.y + area.height; - - if (area.height == 0) { - paintTriangle(g, null, color, x, endX, y); - } - else { - paintRect(g, null, color, x, y, endX, endY); + public static void paintRange(@NotNull Graphics g, + @NotNull Editor editor, + @NotNull Range range, + int framingBorder) { + List> blocks = new VisibleRangeMerger(editor).run(Collections.singletonList(range)); + for (List block: blocks) { + paintChangedLines(g, editor, block, framingBorder); } } @@ -313,10 +327,18 @@ public abstract class LineStatusMarkerRenderer { } @NotNull - public static Rectangle getMarkerArea(@NotNull Editor editor, int line1, int line2) { + private static IntPair getGutterArea(@NotNull Editor editor) { EditorGutterComponentEx gutter = ((EditorEx)editor).getGutterComponentEx(); int x = gutter.getLineMarkerFreePaintersAreaOffset() + 1; // leave 1px for brace highlighters int endX = gutter.getWhitespaceSeparatorOffset(); + return new IntPair(x, endX); + } + + @NotNull + public static Rectangle getMarkerArea(@NotNull Editor editor, int line1, int line2) { + IntPair horizontalArea = getGutterArea(editor); + int x = horizontalArea.val1; + int endX = horizontalArea.val2; int y = lineToY(editor, line1); int endY = lineToY(editor, line2); return new Rectangle(x, y, endX - x, endY - y); @@ -359,9 +381,9 @@ public abstract class LineStatusMarkerRenderer { } @Nullable - private static Color getGutterColor(@NotNull Range.InnerRange range, @Nullable Editor editor) { + private static Color getGutterColor(byte type, @Nullable Editor editor) { final EditorColorsScheme scheme = getColorScheme(editor); - switch (range.getType()) { + switch (type) { case Range.INSERTED: return scheme.getColor(EditorColors.ADDED_LINES_COLOR); case Range.DELETED: @@ -393,30 +415,18 @@ public abstract class LineStatusMarkerRenderer { } @Nullable - private static Color getGutterColor(@NotNull Range range, @Nullable Editor editor) { - final EditorColorsScheme scheme = getColorScheme(editor); - switch (range.getType()) { - case Range.INSERTED: - return scheme.getColor(EditorColors.ADDED_LINES_COLOR); - case Range.DELETED: - return scheme.getColor(EditorColors.DELETED_LINES_COLOR); - case Range.MODIFIED: - return scheme.getColor(EditorColors.MODIFIED_LINES_COLOR); - default: - assert false; - return null; - } - } + private static Color getIgnoredGutterBorderColor(byte type, @Nullable Editor editor) { + Color borderColor = getGutterBorderColor(editor); + if (borderColor != null) return borderColor; - @Nullable - private static Color getIgnoredGutterBorderColor(@NotNull Range range, @Nullable Editor editor) { final EditorColorsScheme scheme = getColorScheme(editor); - switch (range.getType()) { + switch (type) { case Range.INSERTED: return scheme.getColor(EditorColors.IGNORED_ADDED_LINES_BORDER_COLOR); case Range.DELETED: return scheme.getColor(EditorColors.IGNORED_DELETED_LINES_BORDER_COLOR); case Range.MODIFIED: + case Range.EQUAL: return scheme.getColor(EditorColors.IGNORED_MODIFIED_LINES_BORDER_COLOR); default: assert false; @@ -435,6 +445,191 @@ public abstract class LineStatusMarkerRenderer { } + protected static class VisibleRangeMerger { + @NotNull private final Editor myEditor; + + @NotNull private final List myBlock = new ArrayList<>(); + + @NotNull private final List> myResult = new ArrayList<>(); + + public VisibleRangeMerger(@NotNull Editor editor) { + myEditor = editor; + } + + protected boolean isIgnored(@NotNull Range range) { + return false; + } + + @NotNull + public List> run(@NotNull List ranges) { + Rectangle area = myEditor.getScrollingModel().getVisibleArea(); + int visibleLineStart = yToLine(myEditor, area.y); + int visibleLineEnd = yToLine(myEditor, area.y + area.height); + + for (Range range: ranges) { + int line1 = range.getLine1(); + int line2 = range.getLine2(); + + if (line2 < visibleLineStart) continue; + if (line1 > visibleLineEnd) break; + + boolean isIgnored = isIgnored(range); + List innerRanges = range.getInnerRanges(); + + if (innerRanges == null || isIgnored) { + processLine(line1, line2, range.getType(), isIgnored); + } + else { + for (Range.InnerRange innerRange: innerRanges) { + int innerLine1 = line1 + innerRange.getLine1(); + int innerLine2 = line1 + innerRange.getLine2(); + byte innerType = innerRange.getType(); + + processLine(innerLine1, innerLine2, innerType, isIgnored); + } + } + } + + finishBlock(); + return myResult; + } + + private void processLine(int start, int end, byte type, boolean isIgnored) { + EditorImpl editorImpl = (EditorImpl)myEditor; + Document document = myEditor.getDocument(); + int lineCount = getLineCount(document); + + int visualStart; + boolean startHasFolding; + if (start < lineCount) { + int startOffset = document.getLineStartOffset(start); + visualStart = editorImpl.offsetToVisualLine(startOffset); + startHasFolding = startOffset > 0 && myEditor.getFoldingModel().isOffsetCollapsed(startOffset - 1); + } + else { + LOG.assertTrue(start == lineCount); + int lastVisualLine = editorImpl.offsetToVisualLine(document.getTextLength()); + visualStart = lastVisualLine + start - lineCount + 1; + startHasFolding = false; + } + + if (start == end) { + if (startHasFolding) { + appendChange(new ChangedLines(visualStart, visualStart + 1, Range.MODIFIED, isIgnored)); + } + else { + appendChange(new ChangedLines(visualStart, visualStart, type, isIgnored)); + } + } + else { + int visualEnd; + boolean endHasFolding; + if (end < lineCount) { + int endOffset = document.getLineEndOffset(end - 1); + visualEnd = editorImpl.offsetToVisualLine(endOffset) + 1; + endHasFolding = myEditor.getFoldingModel().isOffsetCollapsed(endOffset); + } + else { + LOG.assertTrue(end == lineCount); + int lastVisualLine = editorImpl.offsetToVisualLine(document.getTextLength()); + visualEnd = lastVisualLine + end - lineCount + 1; + endHasFolding = false; + } + + if (type == Range.EQUAL || type == Range.MODIFIED) { + appendChange(new ChangedLines(visualStart, visualEnd, type, isIgnored)); + } + else { + if (startHasFolding && visualEnd - visualStart > 1) { + appendChange(new ChangedLines(visualStart, visualStart + 1, Range.MODIFIED, isIgnored)); + startHasFolding = false; + visualStart++; + } + if (endHasFolding && visualEnd - visualStart > 1) { + appendChange(new ChangedLines(visualStart, visualEnd - 1, type, isIgnored)); + appendChange(new ChangedLines(visualEnd - 1, visualEnd, Range.MODIFIED, isIgnored)); + } + else { + appendChange(new ChangedLines(visualStart, visualEnd, startHasFolding || endHasFolding ? Range.MODIFIED : type, isIgnored)); + } + } + } + } + + private void appendChange(@NotNull ChangedLines newChange) { + ChangedLines lastItem = ContainerUtil.getLastItem(myBlock); + if (lastItem != null && lastItem.line2 < newChange.line1) { + finishBlock(); + } + + if (myBlock.isEmpty()) { + myBlock.add(newChange); + return; + } + + ChangedLines lastChange = myBlock.remove(myBlock.size() - 1); + + if (lastChange.line1 == lastChange.line2 && + newChange.line1 == newChange.line2) { + assert lastChange.line1 == newChange.line1; + byte type = lastChange.type == newChange.type ? lastChange.type : Range.MODIFIED; + boolean isIgnored = lastChange.isIgnored && newChange.isIgnored; + myBlock.add(new ChangedLines(lastChange.line1, lastChange.line2, type, isIgnored)); + } + else if (lastChange.line1 == lastChange.line2 && newChange.type == Range.EQUAL || + newChange.line1 == newChange.line2 && lastChange.type == Range.EQUAL) { + myBlock.add(lastChange); + myBlock.add(newChange); + } + else if (lastChange.type == newChange.type && + lastChange.isIgnored == newChange.isIgnored) { + int union1 = Math.min(lastChange.line1, newChange.line1); + int union2 = Math.max(lastChange.line2, newChange.line2); + myBlock.add(new ChangedLines(union1, union2, lastChange.type, lastChange.isIgnored)); + } + else { + int intersection1 = Math.max(lastChange.line1, newChange.line1); + int intersection2 = Math.min(lastChange.line2, newChange.line2); + + if (lastChange.line1 != intersection1) { + myBlock.add(new ChangedLines(lastChange.line1, intersection1, lastChange.type, lastChange.isIgnored)); + } + + if (intersection1 != intersection2) { + byte type = lastChange.type == newChange.type ? lastChange.type : Range.MODIFIED; + boolean isIgnored = lastChange.isIgnored && newChange.isIgnored; + myBlock.add(new ChangedLines(intersection1, intersection2, type, isIgnored)); + } + + if (newChange.line2 != intersection2) { + myBlock.add(new ChangedLines(intersection2, newChange.line2, newChange.type, newChange.isIgnored)); + } + } + } + + private void finishBlock() { + if (myBlock.isEmpty()) return; + myResult.add(new ArrayList<>(myBlock)); + myBlock.clear(); + } + } + + private static class ChangedLines { + // VisualPosition.line + public final int line1; + public final int line2; + public final byte type; + private final boolean isIgnored; + + public ChangedLines(int line1, int line2, byte type, boolean isIgnored) { + this.line1 = line1; + this.line2 = line2; + this.type = type; + this.isIgnored = isIgnored; + } + } + + private class MyActiveGutterRenderer implements ActiveGutterRenderer { @Override public void paint(Editor editor, Graphics g, Rectangle r) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 61fd06b7f4f1..a027d6580f95 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -1361,7 +1361,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } // optimization: do not do column calculations here since we are interested in line number only - int offsetToVisualLine(int offset) { + public int offsetToVisualLine(int offset) { return myView.offsetToVisualLine(offset, false); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.kt index 80308ecb5930..2e6c84e41d52 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.kt @@ -101,9 +101,9 @@ abstract class LineStatusTracker constructor(override val project: Pr : LineStatusMarkerPopupRenderer(tracker) { override fun getEditorFilter(): MarkupEditorFilter? = MarkupEditorFilterFactory.createIsNotDiffFilter() - override fun canDoAction(editor: Editor, range: Range, e: MouseEvent?): Boolean { + override fun canDoAction(editor: Editor, ranges: List, e: MouseEvent?): Boolean { if (tracker.mode == Mode.SILENT) return false - return super.canDoAction(editor, range, e) + return super.canDoAction(editor, ranges, e) } override fun paint(editor: Editor, g: Graphics) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt index 50044c566499..f40684325a77 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt @@ -53,7 +53,6 @@ import com.intellij.util.ui.JBUI import com.intellij.vcsUtil.VcsUtil import org.jetbrains.annotations.CalledInAwt import java.awt.BorderLayout -import java.awt.Graphics import java.awt.Point import java.lang.ref.WeakReference import java.util.* @@ -506,12 +505,11 @@ class PartialLocalLineStatusTracker(project: Project, protected class MyLineStatusMarkerRenderer(override val tracker: PartialLocalLineStatusTracker) : LineStatusTracker.LocalLineStatusMarkerRenderer(tracker) { - override fun paint(editor: Editor, range: Range, g: Graphics) { - if (range !is LocalRange || - range.changelistId == tracker.defaultMarker.changelistId) { - super.paint(editor, range, g) - } else { - paintIgnoredRange(g, editor, range) + override fun createMerger(editor: Editor): VisibleRangeMerger { + return object : VisibleRangeMerger(editor) { + override fun isIgnored(range: Range): Boolean { + return range is LocalRange && range.changelistId != tracker.defaultMarker.changelistId + } } }