Unified: Support range comments
Handle range comments in Unified. In general this works the same way as in SideBySide. The interesting cases come up when the user's selection spans multiple diff regions that are impossible to combine in SideBySide. For example, the selection can span a deletion chunk and an insertion chunk. In these cases, we forcibly move the start line to the corresponding line on the other side as if it were displayed on SideBySide. This is a natural "fix" to the lousy selection made by the user and probably reflects what the user means. Note that range comments spanning multiple regions might cause text that aren't actually part of the comment range to be highlighted. For example, if the user puts a range comment that spans the entire file, then every line will be highlighted in Unified, even though the deletion chunks are technically not part of the comment range. While we might be able to refine the highlighting, it will need a more complicated implementation that doesn't seem to worth the trouble. Change-Id: I8ec6ee3f710a31085ebe8c3f28414d76d498286c
This commit is contained in:
parent
b9ed112277
commit
067a7e8dc4
@ -24,6 +24,7 @@ import com.google.gwt.user.client.ui.Composite;
|
|||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
import net.codemirror.lib.CodeMirror;
|
||||||
import net.codemirror.lib.Configuration;
|
import net.codemirror.lib.Configuration;
|
||||||
|
import net.codemirror.lib.Pos;
|
||||||
import net.codemirror.lib.TextMarker;
|
import net.codemirror.lib.TextMarker;
|
||||||
import net.codemirror.lib.TextMarker.FromTo;
|
import net.codemirror.lib.TextMarker.FromTo;
|
||||||
|
|
||||||
@ -56,7 +57,13 @@ abstract class CommentBox extends Composite {
|
|||||||
CommentBox(CommentGroup group, CommentRange range) {
|
CommentBox(CommentGroup group, CommentRange range) {
|
||||||
this.group = group;
|
this.group = group;
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
fromTo = FromTo.create(range);
|
DiffScreen screen = group.getManager().getDiffScreen();
|
||||||
|
int startCmLine =
|
||||||
|
screen.getCmLine(range.startLine() - 1, group.getSide());
|
||||||
|
int endCmLine = screen.getCmLine(range.endLine() - 1, group.getSide());
|
||||||
|
fromTo = FromTo.create(
|
||||||
|
Pos.create(startCmLine, range.startCharacter()),
|
||||||
|
Pos.create(endCmLine, range.endCharacter()));
|
||||||
rangeMarker = group.getCm().markText(
|
rangeMarker = group.getCm().markText(
|
||||||
fromTo.from(),
|
fromTo.from(),
|
||||||
fromTo.to(),
|
fromTo.to(),
|
||||||
|
@ -63,6 +63,10 @@ abstract class CommentGroup extends Composite {
|
|||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisplaySide getSide() {
|
||||||
|
return side;
|
||||||
|
}
|
||||||
|
|
||||||
void add(PublishedBox box) {
|
void add(PublishedBox box) {
|
||||||
comments.add(box);
|
comments.add(box);
|
||||||
comments.setVisible(true);
|
comments.setVisible(true);
|
||||||
|
@ -24,6 +24,8 @@ import com.google.gerrit.reviewdb.client.PatchSet;
|
|||||||
import com.google.gwt.core.client.JsArray;
|
import com.google.gwt.core.client.JsArray;
|
||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
import net.codemirror.lib.CodeMirror;
|
||||||
|
import net.codemirror.lib.Pos;
|
||||||
|
import net.codemirror.lib.TextMarker.FromTo;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -130,6 +132,16 @@ abstract class CommentManager {
|
|||||||
return forSide;
|
return forSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static FromTo adjustSelection(CodeMirror cm) {
|
||||||
|
FromTo fromTo = cm.getSelectedRange();
|
||||||
|
Pos to = fromTo.to();
|
||||||
|
if (to.ch() == 0) {
|
||||||
|
to.line(to.line() - 1);
|
||||||
|
to.ch(cm.getLine(to.line()).length());
|
||||||
|
}
|
||||||
|
return fromTo;
|
||||||
|
}
|
||||||
|
|
||||||
abstract void insertNewDraft(DisplaySide side, int line);
|
abstract void insertNewDraft(DisplaySide side, int line);
|
||||||
|
|
||||||
abstract Runnable newDraftCallback(final CodeMirror cm);
|
abstract Runnable newDraftCallback(final CodeMirror cm);
|
||||||
|
@ -934,6 +934,8 @@ abstract class DiffScreen extends Screen {
|
|||||||
|
|
||||||
abstract DiffTable getDiffTable();
|
abstract DiffTable getDiffTable();
|
||||||
|
|
||||||
|
abstract int getCmLine(int line, DisplaySide side);
|
||||||
|
|
||||||
LineOnOtherInfo lineOnOther(DisplaySide side, int line) {
|
LineOnOtherInfo lineOnOther(DisplaySide side, int line) {
|
||||||
return getChunkManager().getLineMapper().lineOnOther(side, line);
|
return getChunkManager().getLineMapper().lineOnOther(side, line);
|
||||||
}
|
}
|
||||||
|
@ -309,6 +309,11 @@ public class SideBySide extends DiffScreen {
|
|||||||
return side == DisplaySide.A ? cmA : cmB;
|
return side == DisplaySide.A ? cmA : cmB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int getCmLine(int line, DisplaySide side) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Runnable updateActiveLine(final CodeMirror cm) {
|
Runnable updateActiveLine(final CodeMirror cm) {
|
||||||
final CodeMirror other = otherCm(cm);
|
final CodeMirror other = otherCm(cm);
|
||||||
|
@ -333,18 +333,13 @@ class SideBySideCommentManager extends CommentManager {
|
|||||||
private void newDraft(CodeMirror cm) {
|
private void newDraft(CodeMirror cm) {
|
||||||
int line = cm.getLineNumber(cm.extras().activeLine()) + 1;
|
int line = cm.getLineNumber(cm.extras().activeLine()) + 1;
|
||||||
if (cm.somethingSelected()) {
|
if (cm.somethingSelected()) {
|
||||||
FromTo fromTo = cm.getSelectedRange();
|
FromTo fromTo = adjustSelection(cm);
|
||||||
Pos end = fromTo.to();
|
|
||||||
if (end.ch() == 0) {
|
|
||||||
end.line(end.line() - 1);
|
|
||||||
end.ch(cm.getLine(end.line()).length());
|
|
||||||
}
|
|
||||||
|
|
||||||
addDraftBox(cm.side(), CommentInfo.create(
|
addDraftBox(cm.side(), CommentInfo.create(
|
||||||
getPath(),
|
getPath(),
|
||||||
getStoredSideFromDisplaySide(cm.side()),
|
getStoredSideFromDisplaySide(cm.side()),
|
||||||
line,
|
line,
|
||||||
CommentRange.create(fromTo))).setEdit(true);
|
CommentRange.create(fromTo))).setEdit(true);
|
||||||
|
cm.setCursor(fromTo.to());
|
||||||
cm.setSelection(cm.getCursor());
|
cm.setSelection(cm.getCursor());
|
||||||
} else {
|
} else {
|
||||||
insertNewDraft(cm.side(), line);
|
insertNewDraft(cm.side(), line);
|
||||||
|
@ -18,7 +18,7 @@ import static java.lang.Double.POSITIVE_INFINITY;
|
|||||||
|
|
||||||
import com.google.gerrit.client.Dispatcher;
|
import com.google.gerrit.client.Dispatcher;
|
||||||
import com.google.gerrit.client.Gerrit;
|
import com.google.gerrit.client.Gerrit;
|
||||||
import com.google.gerrit.client.diff.UnifiedChunkManager.LineSidePair;
|
import com.google.gerrit.client.diff.UnifiedChunkManager.LineRegionInfo;
|
||||||
import com.google.gerrit.client.patches.PatchUtil;
|
import com.google.gerrit.client.patches.PatchUtil;
|
||||||
import com.google.gerrit.client.projects.ConfigInfoCache;
|
import com.google.gerrit.client.projects.ConfigInfoCache;
|
||||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||||
@ -363,12 +363,13 @@ public class Unified extends DiffScreen {
|
|||||||
return cm;
|
return cm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
int getCmLine(int line, DisplaySide side) {
|
int getCmLine(int line, DisplaySide side) {
|
||||||
return chunkManager.getCmLine(line, side);
|
return chunkManager.getCmLine(line, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
LineSidePair getLineSidePairFromCmLine(int cmLine) {
|
LineRegionInfo getLineRegionInfoFromCmLine(int cmLine) {
|
||||||
return chunkManager.getLineSidePairFromCmLine(cmLine);
|
return chunkManager.getLineRegionInfoFromCmLine(cmLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -279,52 +279,72 @@ class UnifiedChunkManager extends ChunkManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LineSidePair getLineSidePairFromCmLine(int cmLine) {
|
LineRegionInfo getLineRegionInfoFromCmLine(int cmLine) {
|
||||||
int res =
|
int res =
|
||||||
Collections.binarySearch(chunks,
|
Collections.binarySearch(chunks,
|
||||||
new UnifiedDiffChunkInfo(
|
new UnifiedDiffChunkInfo(
|
||||||
DisplaySide.A, 0, 0, cmLine, false), // Dummy DiffChunkInfo
|
DisplaySide.A, 0, 0, cmLine, false), // Dummy DiffChunkInfo
|
||||||
getDiffChunkComparatorCmLine());
|
getDiffChunkComparatorCmLine());
|
||||||
if (res >= 0) {
|
if (res >= 0) { // The line is right at the start of a diff chunk.
|
||||||
UnifiedDiffChunkInfo info = chunks.get(res);
|
UnifiedDiffChunkInfo info = chunks.get(res);
|
||||||
return new LineSidePair(info.getStart(), info.getSide());
|
return new LineRegionInfo(
|
||||||
} else { // The line might be within a DiffChunk
|
info.getStart(), displaySideToRegionType(info.getSide()));
|
||||||
|
} else { // The line might be within or after a diff chunk.
|
||||||
res = -res - 1;
|
res = -res - 1;
|
||||||
if (res > 0) {
|
if (res > 0) {
|
||||||
UnifiedDiffChunkInfo info = chunks.get(res - 1);
|
UnifiedDiffChunkInfo info = chunks.get(res - 1);
|
||||||
int lineOnInfoSide = info.getStart() + cmLine - info.getCmLine();
|
int lineOnInfoSide = info.getStart() + cmLine - info.getCmLine();
|
||||||
if (lineOnInfoSide > info.getEnd()
|
if (lineOnInfoSide > info.getEnd()) { // After a diff chunk
|
||||||
&& info.getSide() == DisplaySide.A) {
|
if (info.getSide() == DisplaySide.A) {
|
||||||
// For the common region after a deletion chunk, return the line and
|
// For the common region after a deletion chunk, associate the line
|
||||||
// side info on side B
|
// on side B with a common region.
|
||||||
return new LineSidePair(
|
return new LineRegionInfo(
|
||||||
getLineMapper().lineOnOther(DisplaySide.A, lineOnInfoSide)
|
getLineMapper().lineOnOther(DisplaySide.A, lineOnInfoSide)
|
||||||
.getLine(), DisplaySide.B);
|
.getLine(), RegionType.COMMON);
|
||||||
} else {
|
} else {
|
||||||
return new LineSidePair(lineOnInfoSide, info.getSide());
|
return new LineRegionInfo(lineOnInfoSide, RegionType.COMMON);
|
||||||
|
}
|
||||||
|
} else { // Within a diff chunk
|
||||||
|
return new LineRegionInfo(
|
||||||
|
lineOnInfoSide, displaySideToRegionType(info.getSide()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Always return side B
|
// The line is before any diff chunk, so it always equals cmLine and
|
||||||
return new LineSidePair(cmLine, DisplaySide.B);
|
// belongs to a common region.
|
||||||
|
return new LineRegionInfo(cmLine, RegionType.COMMON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class LineSidePair {
|
enum RegionType {
|
||||||
private int line;
|
INSERT, DELETE, COMMON,
|
||||||
private DisplaySide side;
|
}
|
||||||
|
|
||||||
LineSidePair(int line, DisplaySide side) {
|
private static RegionType displaySideToRegionType(DisplaySide side) {
|
||||||
|
return side == DisplaySide.A ? RegionType.DELETE : RegionType.INSERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to associate a line in the original file with the type of the
|
||||||
|
* region it belongs to.
|
||||||
|
*
|
||||||
|
* @field line The 0-based line number in the original file. Note that this
|
||||||
|
* might be different from the line number shown in CodeMirror.
|
||||||
|
* @field type The type of the region the line belongs to. Can be INSERT,
|
||||||
|
* DELETE or COMMON.
|
||||||
|
*/
|
||||||
|
static class LineRegionInfo {
|
||||||
|
final int line;
|
||||||
|
final RegionType type;
|
||||||
|
|
||||||
|
LineRegionInfo(int line, RegionType type) {
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.side = side;
|
this.type = type;
|
||||||
}
|
|
||||||
|
|
||||||
int getLine() {
|
|
||||||
return line;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplaySide getSide() {
|
DisplaySide getSide() {
|
||||||
return side;
|
// Always return DisplaySide.B for INSERT or COMMON
|
||||||
|
return type == RegionType.DELETE ? DisplaySide.A : DisplaySide.B;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,9 @@ package com.google.gerrit.client.diff;
|
|||||||
|
|
||||||
import com.google.gerrit.client.Gerrit;
|
import com.google.gerrit.client.Gerrit;
|
||||||
import com.google.gerrit.client.changes.CommentInfo;
|
import com.google.gerrit.client.changes.CommentInfo;
|
||||||
import com.google.gerrit.client.diff.UnifiedChunkManager.LineSidePair;
|
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
|
||||||
|
import com.google.gerrit.client.diff.UnifiedChunkManager.LineRegionInfo;
|
||||||
|
import com.google.gerrit.client.diff.UnifiedChunkManager.RegionType;
|
||||||
import com.google.gerrit.client.patches.SkippedLine;
|
import com.google.gerrit.client.patches.SkippedLine;
|
||||||
import com.google.gerrit.client.rpc.Natives;
|
import com.google.gerrit.client.rpc.Natives;
|
||||||
import com.google.gerrit.client.ui.CommentLinkProcessor;
|
import com.google.gerrit.client.ui.CommentLinkProcessor;
|
||||||
@ -26,6 +28,7 @@ import com.google.gwt.core.client.JsArray;
|
|||||||
import net.codemirror.lib.CodeMirror;
|
import net.codemirror.lib.CodeMirror;
|
||||||
import net.codemirror.lib.CodeMirror.LineHandle;
|
import net.codemirror.lib.CodeMirror.LineHandle;
|
||||||
import net.codemirror.lib.Pos;
|
import net.codemirror.lib.Pos;
|
||||||
|
import net.codemirror.lib.TextMarker.FromTo;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -179,10 +182,10 @@ class UnifiedCommentManager extends CommentManager {
|
|||||||
((PublishedBox)last).doReply();
|
((PublishedBox)last).doReply();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LineSidePair pair = host.getLineSidePairFromCmLine(cmLinePlusOne - 1);
|
LineRegionInfo info = host.getLineRegionInfoFromCmLine(cmLinePlusOne - 1);
|
||||||
int line = pair.getLine();
|
int line = info.line;
|
||||||
if (pair.getSide() != side) {
|
if (info.getSide() != side) {
|
||||||
line = host.lineOnOther(pair.getSide(), line).getLine();
|
line = host.lineOnOther(info.getSide(), line).getLine();
|
||||||
}
|
}
|
||||||
addDraftBox(side, CommentInfo.create(
|
addDraftBox(side, CommentInfo.create(
|
||||||
getPath(),
|
getPath(),
|
||||||
@ -335,13 +338,68 @@ class UnifiedCommentManager extends CommentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void newDraft(CodeMirror cm) {
|
private void newDraft(CodeMirror cm) {
|
||||||
int cmLine = cm.getLineNumber(cm.extras().activeLine());
|
|
||||||
LineSidePair pair = host.getLineSidePairFromCmLine(cmLine);
|
|
||||||
DisplaySide side = pair.getSide();
|
|
||||||
if (cm.somethingSelected()) {
|
if (cm.somethingSelected()) {
|
||||||
// TODO: Handle range comment
|
FromTo fromTo = adjustSelection(cm);
|
||||||
|
Pos from = fromTo.from();
|
||||||
|
Pos to = fromTo.to();
|
||||||
|
UnifiedChunkManager manager = host.getChunkManager();
|
||||||
|
LineRegionInfo fromInfo =
|
||||||
|
host.getLineRegionInfoFromCmLine(from.line());
|
||||||
|
LineRegionInfo toInfo =
|
||||||
|
host.getLineRegionInfoFromCmLine(to.line());
|
||||||
|
DisplaySide side = toInfo.getSide();
|
||||||
|
|
||||||
|
// Handle special cases in selections that span multiple regions. Force
|
||||||
|
// start line to be on the same side as the end line.
|
||||||
|
if ((fromInfo.type == RegionType.INSERT
|
||||||
|
|| fromInfo.type == RegionType.COMMON)
|
||||||
|
&& toInfo.type == RegionType.DELETE) {
|
||||||
|
LineOnOtherInfo infoOnSideA = manager.getLineMapper()
|
||||||
|
.lineOnOther(DisplaySide.B, fromInfo.line);
|
||||||
|
int startLineOnSideA = infoOnSideA.getLine();
|
||||||
|
if (infoOnSideA.isAligned()) {
|
||||||
|
from.line(startLineOnSideA);
|
||||||
|
} else {
|
||||||
|
from.line(startLineOnSideA + 1);
|
||||||
|
}
|
||||||
|
from.ch(0);
|
||||||
|
to.line(toInfo.line);
|
||||||
|
} else if (fromInfo.type == RegionType.DELETE
|
||||||
|
&& toInfo.type == RegionType.INSERT) {
|
||||||
|
LineOnOtherInfo infoOnSideB = manager.getLineMapper()
|
||||||
|
.lineOnOther(DisplaySide.A, fromInfo.line);
|
||||||
|
int startLineOnSideB = infoOnSideB.getLine();
|
||||||
|
if (infoOnSideB.isAligned()) {
|
||||||
|
from.line(startLineOnSideB);
|
||||||
|
} else {
|
||||||
|
from.line(startLineOnSideB + 1);
|
||||||
|
}
|
||||||
|
from.ch(0);
|
||||||
|
to.line(toInfo.line);
|
||||||
|
} else if (fromInfo.type == RegionType.DELETE
|
||||||
|
&& toInfo.type == RegionType.COMMON) {
|
||||||
|
int toLineOnSideA = manager.getLineMapper()
|
||||||
|
.lineOnOther(DisplaySide.B, toInfo.line).getLine();
|
||||||
|
from.line(fromInfo.line);
|
||||||
|
// Force the end line to be on the same side as the start line.
|
||||||
|
to.line(toLineOnSideA);
|
||||||
|
side = DisplaySide.A;
|
||||||
|
} else { // Common case
|
||||||
|
from.line(fromInfo.line);
|
||||||
|
to.line(toInfo.line);
|
||||||
|
}
|
||||||
|
|
||||||
|
addDraftBox(side, CommentInfo.create(
|
||||||
|
getPath(),
|
||||||
|
getStoredSideFromDisplaySide(side),
|
||||||
|
to.line() + 1,
|
||||||
|
CommentRange.create(fromTo))).setEdit(true);
|
||||||
|
cm.setCursor(Pos.create(host.getCmLine(to.line(), side), to.ch()));
|
||||||
|
cm.setSelection(cm.getCursor());
|
||||||
} else {
|
} else {
|
||||||
insertNewDraft(side, cmLine + 1);
|
int cmLine = cm.getLineNumber(cm.extras().activeLine());
|
||||||
|
LineRegionInfo info = host.getLineRegionInfoFromCmLine(cmLine);
|
||||||
|
insertNewDraft(info.getSide(), cmLine + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user