Merge "Start displaying comments and drafts."
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.client.changes;
|
||||
|
||||
import com.google.gerrit.client.rpc.NativeMap;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
@@ -23,7 +24,41 @@ public class CommentApi {
|
||||
|
||||
public static void comments(PatchSet.Id id,
|
||||
AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
|
||||
ChangeApi.revision(id).view("comments").get(cb);
|
||||
revision(id, "comments").get(cb);
|
||||
}
|
||||
|
||||
public static void comment(PatchSet.Id id, String commentId,
|
||||
AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
|
||||
revision(id, "comments").id(commentId).get(cb);
|
||||
}
|
||||
|
||||
public static void drafts(PatchSet.Id id,
|
||||
AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
|
||||
revision(id, "drafts").get(cb);
|
||||
}
|
||||
|
||||
public static void draft(PatchSet.Id id, String draftId,
|
||||
AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
|
||||
revision(id, "drafts").id(draftId).get(cb);
|
||||
}
|
||||
|
||||
public static void createDraft(PatchSet.Id id, CommentInfo content,
|
||||
AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
|
||||
revision(id, "drafts").put(content, cb);
|
||||
}
|
||||
|
||||
public static void updateDraft(PatchSet.Id id, String draftId,
|
||||
CommentInfo content, AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
|
||||
revision(id, "drafts").id(draftId).put(content, cb);
|
||||
}
|
||||
|
||||
public static void deleteDraft(PatchSet.Id id, String draftId,
|
||||
AsyncCallback<NativeMap<JsArray<CommentInfo>>> cb) {
|
||||
revision(id, "drafts").id(draftId).delete(cb);
|
||||
}
|
||||
|
||||
private static RestApi revision(PatchSet.Id id, String type) {
|
||||
return ChangeApi.revision(id).view(type);
|
||||
}
|
||||
|
||||
private CommentApi() {
|
||||
|
||||
@@ -14,15 +14,22 @@
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gerrit.client.changes.CommentApi;
|
||||
import com.google.gerrit.client.changes.CommentInfo;
|
||||
import com.google.gerrit.client.diff.DiffInfo.Region;
|
||||
import com.google.gerrit.client.diff.DiffInfo.Span;
|
||||
import com.google.gerrit.client.rpc.CallbackGroup;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.NativeMap;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.Screen;
|
||||
import com.google.gerrit.common.changes.Side;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.core.client.JsArrayString;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.event.logical.shared.ResizeEvent;
|
||||
@@ -37,8 +44,13 @@ import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.LineCharacter;
|
||||
import net.codemirror.lib.ModeInjector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CodeMirrorDemo extends Screen {
|
||||
private static final int HEADER_FOOTER = 60 + 15 * 2 + 38;
|
||||
private static final JsArrayString EMPTY =
|
||||
JavaScriptObject.createArray().cast();
|
||||
private final PatchSet.Id base;
|
||||
private final PatchSet.Id revision;
|
||||
private final String path;
|
||||
@@ -47,6 +59,10 @@ public class CodeMirrorDemo extends Screen {
|
||||
private CodeMirror cmA;
|
||||
private CodeMirror cmB;
|
||||
private HandlerRegistration resizeHandler;
|
||||
private JsArray<CommentInfo> published;
|
||||
private JsArray<CommentInfo> drafts;
|
||||
private List<Runnable> resizeCallbacks;
|
||||
private LineMapper mapper;
|
||||
|
||||
public CodeMirrorDemo(
|
||||
PatchSet.Id base,
|
||||
@@ -85,7 +101,7 @@ public class CodeMirrorDemo extends Screen {
|
||||
new ModeInjector()
|
||||
.add(getContentType(diff.meta_a()))
|
||||
.add(getContentType(diff.meta_b()))
|
||||
.inject(new ScreenLoadCallback<Void>(CodeMirrorDemo.this){
|
||||
.inject(new ScreenLoadCallback<Void>(CodeMirrorDemo.this) {
|
||||
@Override
|
||||
protected void preDisplay(Void result) {
|
||||
display(diff);
|
||||
@@ -93,6 +109,16 @@ public class CodeMirrorDemo extends Screen {
|
||||
});
|
||||
}
|
||||
}));
|
||||
CommentApi.comments(revision,
|
||||
group.add(new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
|
||||
@Override
|
||||
public void onSuccess(NativeMap<JsArray<CommentInfo>> m) { published = m.get(path); }
|
||||
}));
|
||||
CommentApi.drafts(revision,
|
||||
group.add(new GerritCallback<NativeMap<JsArray<CommentInfo>>>() {
|
||||
@Override
|
||||
public void onSuccess(NativeMap<JsArray<CommentInfo>> m) { drafts = m.get(path); }
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,6 +131,15 @@ public class CodeMirrorDemo extends Screen {
|
||||
cmB.refresh();
|
||||
}
|
||||
Window.enableScrolling(false);
|
||||
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
|
||||
@Override
|
||||
public void execute() {
|
||||
for (Runnable r : resizeCallbacks) {
|
||||
r.run();
|
||||
}
|
||||
resizeCallbacks = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,6 +164,14 @@ public class CodeMirrorDemo extends Screen {
|
||||
cmA = displaySide(diff.meta_a(), diff.text_a(), diffTable.getCmA());
|
||||
cmB = displaySide(diff.meta_b(), diff.text_b(), diffTable.getCmB());
|
||||
render(diff);
|
||||
resizeCallbacks = new ArrayList<Runnable>();
|
||||
renderComments(published, false);
|
||||
renderComments(drafts, true);
|
||||
published = null;
|
||||
drafts = null;
|
||||
mapper = null;
|
||||
|
||||
// TODO: Probably need horizontal resize
|
||||
resizeHandler = Window.addResizeHandler(new ResizeHandler() {
|
||||
@Override
|
||||
public void onResize(ResizeEvent event) {
|
||||
@@ -159,40 +202,36 @@ public class CodeMirrorDemo extends Screen {
|
||||
.set("styleSelectedText", true)
|
||||
.set("value", contents);
|
||||
final CodeMirror cm = CodeMirror.create(ele, cfg);
|
||||
cm.setWidth("100%");
|
||||
cm.setHeight(Window.getClientHeight() - HEADER_FOOTER);
|
||||
return cm;
|
||||
}
|
||||
|
||||
private void render(DiffInfo diff) {
|
||||
JsArray<Region> regions = diff.content();
|
||||
int lineA = 0, lineB = 0;
|
||||
mapper = new LineMapper();
|
||||
for (int i = 0; i < regions.length(); i++) {
|
||||
Region current = regions.get(i);
|
||||
if (current.ab() != null) {
|
||||
lineA += current.ab().length();
|
||||
lineB += current.ab().length();
|
||||
} else if (current.a() == null && current.b() != null) {
|
||||
int delta = current.b().length();
|
||||
insertEmptyLines(cmA, lineA, delta);
|
||||
lineB = colorLines(cmB, lineB, delta);
|
||||
} else if (current.a() != null && current.b() == null) {
|
||||
int delta = current.a().length();
|
||||
insertEmptyLines(cmB, lineB, delta);
|
||||
lineA = colorLines(cmA, lineA, delta);
|
||||
} else {
|
||||
JsArrayString currentA = current.a();
|
||||
JsArrayString currentB = current.b();
|
||||
int origLineA = mapper.getLineA();
|
||||
int origLineB = mapper.getLineB();
|
||||
if (current.ab() != null) { // Common
|
||||
// TODO: Handle skips.
|
||||
mapper.appendCommon(current.ab().length());
|
||||
} else { // Insert, Delete or Edit
|
||||
JsArrayString currentA = current.a() == null ? EMPTY : current.a();
|
||||
JsArrayString currentB = current.b() == null ? EMPTY : current.b();
|
||||
int aLength = currentA.length();
|
||||
int bLength = currentB.length();
|
||||
int origLineA = lineA;
|
||||
int origLineB = lineB;
|
||||
lineA = colorLines(cmA, lineA, aLength);
|
||||
lineB = colorLines(cmB, lineB, bLength);
|
||||
if (aLength < bLength) {
|
||||
insertEmptyLines(cmA, lineA, bLength - aLength);
|
||||
} else if (aLength > bLength) {
|
||||
insertEmptyLines(cmB, lineB, aLength - bLength);
|
||||
colorLines(cmA, origLineA, aLength);
|
||||
colorLines(cmB, origLineB, bLength);
|
||||
mapper.appendCommon(Math.min(aLength, bLength));
|
||||
if (aLength < bLength) { // Edit with insertion
|
||||
int insertCnt = bLength - aLength;
|
||||
insertEmptyLines(cmA, mapper.getLineA(), insertCnt);
|
||||
mapper.appendInsert(insertCnt);
|
||||
} else if (aLength > bLength) { // Edit with deletion
|
||||
int deleteCnt = aLength - bLength;
|
||||
insertEmptyLines(cmB, mapper.getLineB(), deleteCnt);
|
||||
mapper.appendDelete(deleteCnt);
|
||||
}
|
||||
markEdit(cmA, currentA, current.edit_a(), origLineA);
|
||||
markEdit(cmB, currentB, current.edit_b(), origLineB);
|
||||
@@ -200,25 +239,44 @@ public class CodeMirrorDemo extends Screen {
|
||||
}
|
||||
}
|
||||
|
||||
private void insertEmptyLines(CodeMirror cm, int line, int cnt) {
|
||||
Element div = DOM.createDiv();
|
||||
div.setClassName(diffTable.style.padding());
|
||||
div.getStyle().setHeight(cnt, Unit.EM);
|
||||
Configuration config = Configuration.create()
|
||||
.set("coverGutter", true)
|
||||
.set("above", line == 0);
|
||||
cm.addLineWidget(line == 0 ? 0 : (line - 1), div, config);
|
||||
private void renderComments(JsArray<CommentInfo> comments, boolean isDraft) {
|
||||
Configuration config = Configuration.create().set("coverGutter", true);
|
||||
for (int i = 0; comments != null && i < comments.length(); i++) {
|
||||
CommentInfo info = comments.get(i);
|
||||
Side mySide = info.side();
|
||||
CodeMirror cm = mySide == Side.PARENT ? cmA : cmB;
|
||||
CodeMirror other = otherCM(cm);
|
||||
final CommentBox box = new CommentBox(info.author(), info.updated(),
|
||||
info.message(), isDraft);
|
||||
int line = info.line() - 1; // CommentInfo is 1-based, but CM is 0-based
|
||||
diffTable.add(box);
|
||||
cm.addLineWidget(line, box.getElement(), config);
|
||||
int lineToPad = mapper.lineOnOther(mySide, line);
|
||||
// Estimated height at 21px, fixed by deferring after display
|
||||
final Element paddingOtherside = addPaddingWidget(other,
|
||||
diffTable.style.padding(), lineToPad,
|
||||
21, Unit.PX);
|
||||
Runnable callback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
paddingOtherside.getStyle().setHeight(
|
||||
box.getOffsetHeight(), Unit.PX);
|
||||
}
|
||||
};
|
||||
resizeCallbacks.add(callback);
|
||||
box.setOpenCloseHandler(callback);
|
||||
}
|
||||
}
|
||||
|
||||
private int colorLines(CodeMirror cm, int line, int cnt) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
cm.addLineClass(line + i, LineClassWhere.WRAP, diffTable.style.diff());
|
||||
}
|
||||
return line + cnt;
|
||||
private CodeMirror otherCM(CodeMirror me) {
|
||||
return me == cmA ? cmB : cmA;
|
||||
}
|
||||
|
||||
private void markEdit(CodeMirror cm, JsArrayString lines,
|
||||
JsArray<Span> edits, int startLine) {
|
||||
if (edits == null) {
|
||||
return;
|
||||
}
|
||||
EditIterator iter = new EditIterator(lines, startLine);
|
||||
Configuration diffOpt = Configuration.create()
|
||||
.set("className", diffTable.style.diff())
|
||||
@@ -246,8 +304,32 @@ public class CodeMirrorDemo extends Screen {
|
||||
}
|
||||
}
|
||||
|
||||
public Runnable doScroll(final CodeMirror cm) {
|
||||
final CodeMirror other = cm == cmA ? cmB : cmA;
|
||||
private void colorLines(CodeMirror cm, int line, int cnt) {
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
cm.addLineClass(line + i, LineClassWhere.WRAP, diffTable.style.diff());
|
||||
}
|
||||
}
|
||||
|
||||
private void insertEmptyLines(CodeMirror cm, int nextLine, int cnt) {
|
||||
// -1 to compensate for the line we went past when this method is called.
|
||||
addPaddingWidget(cm, diffTable.style.padding(), nextLine - 1,
|
||||
cnt, Unit.EM);
|
||||
}
|
||||
|
||||
private Element addPaddingWidget(CodeMirror cm, String style, int line,
|
||||
int height, Unit unit) {
|
||||
Element div = DOM.createDiv();
|
||||
div.setClassName(style);
|
||||
div.getStyle().setHeight(height, unit);
|
||||
Configuration config = Configuration.create()
|
||||
.set("coverGutter", true)
|
||||
.set("above", line == -1);
|
||||
cm.addLineWidget(line == -1 ? 0 : line, div, config);
|
||||
return div;
|
||||
}
|
||||
|
||||
private Runnable doScroll(final CodeMirror cm) {
|
||||
final CodeMirror other = otherCM(cm);
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
cm.scrollToY(other.getScrollInfo().getTop());
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
//Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gerrit.client.AvatarImage;
|
||||
import com.google.gerrit.client.FormatUtil;
|
||||
import com.google.gerrit.client.account.AccountInfo;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.shared.HandlerRegistration;
|
||||
import com.google.gwt.resources.client.CssResource;
|
||||
import com.google.gwt.uibinder.client.UiBinder;
|
||||
import com.google.gwt.uibinder.client.UiField;
|
||||
import com.google.gwt.user.client.ui.Composite;
|
||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/** An HtmlPanel holding the DialogBox to display a comment */
|
||||
class CommentBox extends Composite {
|
||||
interface Binder extends UiBinder<HTMLPanel, CommentBox> {}
|
||||
private static Binder uiBinder = GWT.create(Binder.class);
|
||||
|
||||
interface CommentBoxStyle extends CssResource {
|
||||
String open();
|
||||
String close();
|
||||
}
|
||||
|
||||
private HandlerRegistration headerClick;
|
||||
private Runnable clickCallback;
|
||||
|
||||
@UiField
|
||||
Widget header;
|
||||
|
||||
@UiField
|
||||
AvatarImage avatar;
|
||||
|
||||
@UiField
|
||||
Element name;
|
||||
|
||||
@UiField
|
||||
Element summary;
|
||||
|
||||
@UiField
|
||||
Element date;
|
||||
|
||||
@UiField
|
||||
Element contentPanel;
|
||||
|
||||
@UiField
|
||||
Element contentPanelMessage;
|
||||
|
||||
@UiField
|
||||
CommentBoxStyle style;
|
||||
|
||||
CommentBox(AccountInfo author, Timestamp when, String message,
|
||||
boolean isDraft) {
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
// TODO: Format the comment box differently based on whether isDraft
|
||||
// is true.
|
||||
setAuthorNameText(author);
|
||||
date.setInnerText(FormatUtil.shortFormatDayTime(when));
|
||||
setMessageText(message);
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoad() {
|
||||
super.onLoad();
|
||||
|
||||
headerClick = header.addDomHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
setOpen(!isOpen());
|
||||
if (clickCallback != null) {
|
||||
clickCallback.run();
|
||||
}
|
||||
}
|
||||
}, ClickEvent.getType());
|
||||
}
|
||||
|
||||
void setOpenCloseHandler(final Runnable callback) {
|
||||
clickCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnload() {
|
||||
super.onUnload();
|
||||
|
||||
if (headerClick != null) {
|
||||
headerClick.removeHandler();
|
||||
headerClick = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setAuthorNameText(AccountInfo author) {
|
||||
// TODO: Set avatar's display to none if we get a 404.
|
||||
avatar = new AvatarImage(author, 26);
|
||||
name.setInnerText(FormatUtil.name(author));
|
||||
}
|
||||
|
||||
private void setMessageText(String message) {
|
||||
if (message == null) {
|
||||
message = "";
|
||||
} else {
|
||||
message = message.trim();
|
||||
}
|
||||
summary.setInnerText(message);
|
||||
// TODO: Change to use setInnerHtml
|
||||
contentPanelMessage.setInnerText(message);
|
||||
}
|
||||
|
||||
private void setOpen(boolean open) {
|
||||
if (open) {
|
||||
removeStyleName(style.close());
|
||||
addStyleName(style.open());
|
||||
} else {
|
||||
removeStyleName(style.open());
|
||||
addStyleName(style.close());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOpen() {
|
||||
return getStyleName().contains(style.open());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
||||
xmlns:c='urn:import:com.google.gerrit.client'>
|
||||
<ui:style type='com.google.gerrit.client.diff.CommentBox.CommentBoxStyle'>
|
||||
.commentBox {
|
||||
background-color: #e5ecf9;
|
||||
border: 1px solid black;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.summary {
|
||||
width: 60%;
|
||||
}
|
||||
.summaryText {
|
||||
color: #777;
|
||||
height: 1em;
|
||||
max-width: 80%;
|
||||
overflow: hidden;
|
||||
padding-bottom: 1px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.open .summaryText {
|
||||
display: none;
|
||||
}
|
||||
.date {
|
||||
width: 25%;
|
||||
text-align: right;
|
||||
}
|
||||
.close .contentPanel {
|
||||
display: none;
|
||||
}
|
||||
.message {
|
||||
margin: 5px;
|
||||
}
|
||||
</ui:style>
|
||||
<g:HTMLPanel styleName='{style.commentBox}'>
|
||||
<g:HTMLPanel ui:field='header'>
|
||||
<table class='{style.table}'>
|
||||
<tr>
|
||||
<td><c:AvatarImage ui:field='avatar' /></td>
|
||||
<td ui:field='name'></td>
|
||||
<td class='{style.summary}'>
|
||||
<div ui:field='summary' class='{style.summaryText}'></div>
|
||||
</td>
|
||||
<td ui:field='date' class='{style.date}'></td>
|
||||
</tr>
|
||||
</table>
|
||||
</g:HTMLPanel>
|
||||
<div ui:field='contentPanel' class='{style.contentPanel}'>
|
||||
<div><p ui:field='contentPanelMessage' class='{style.message}'></p></div>
|
||||
<div>
|
||||
<button ui:field='reply'><ui:msg>Reply ...</ui:msg></button>
|
||||
<button ui:field='replyDone'><ui:msg>Reply 'Done'</ui:msg></button>
|
||||
</div>
|
||||
</div>
|
||||
</g:HTMLPanel>
|
||||
</ui:UiBinder>
|
||||
@@ -15,12 +15,13 @@
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.DivElement;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.resources.client.CssResource;
|
||||
import com.google.gwt.uibinder.client.UiBinder;
|
||||
import com.google.gwt.uibinder.client.UiField;
|
||||
import com.google.gwt.user.client.ui.Composite;
|
||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
|
||||
/**
|
||||
* A table with one row and two columns to hold the two CodeMirrors displaying
|
||||
@@ -37,10 +38,10 @@ class DiffTable extends Composite {
|
||||
}
|
||||
|
||||
@UiField
|
||||
DivElement cmA;
|
||||
Element cmA;
|
||||
|
||||
@UiField
|
||||
DivElement cmB;
|
||||
Element cmB;
|
||||
|
||||
@UiField
|
||||
LineStyle style;
|
||||
@@ -49,12 +50,15 @@ class DiffTable extends Composite {
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
}
|
||||
|
||||
DivElement getCmA() {
|
||||
Element getCmA() {
|
||||
return cmA;
|
||||
}
|
||||
|
||||
DivElement getCmB() {
|
||||
Element getCmB() {
|
||||
return cmB;
|
||||
}
|
||||
|
||||
void add(Widget widget) {
|
||||
((HTMLPanel) getWidget()).add(widget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,13 @@ limitations under the License.
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.a, .b {
|
||||
width: 50%;
|
||||
}
|
||||
.a .diff,
|
||||
.a .diff .CodeMirror-linenumber {
|
||||
background-color: #fee;
|
||||
@@ -52,10 +59,10 @@ limitations under the License.
|
||||
}
|
||||
</ui:style>
|
||||
<g:HTMLPanel styleName='{style.difftable}'>
|
||||
<table>
|
||||
<table class='{style.table}'>
|
||||
<tr>
|
||||
<td><div ui:field='cmA' class='{style.a}'></div></td>
|
||||
<td><div ui:field='cmB' class='{style.b}'></div></td>
|
||||
<td ui:field='cmA' class='{style.a}'></td>
|
||||
<td ui:field='cmB' class='{style.b}'></td>
|
||||
</tr>
|
||||
</table>
|
||||
</g:HTMLPanel>
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gerrit.common.changes.Side;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Helper class to handle calculations involving line gaps. */
|
||||
class LineMapper {
|
||||
private int lineA;
|
||||
private int lineB;
|
||||
private List<LineGap> lineMapAtoB;
|
||||
private List<LineGap> lineMapBtoA;
|
||||
|
||||
LineMapper() {
|
||||
lineMapAtoB = new ArrayList<LineGap>();
|
||||
lineMapBtoA = new ArrayList<LineGap>();
|
||||
}
|
||||
|
||||
int getLineA() {
|
||||
return lineA;
|
||||
}
|
||||
|
||||
int getLineB() {
|
||||
return lineB;
|
||||
}
|
||||
|
||||
void appendCommon(int numLines) {
|
||||
lineA += numLines;
|
||||
lineB += numLines;
|
||||
}
|
||||
|
||||
void appendInsert(int numLines) {
|
||||
int origLineB = lineB;
|
||||
lineB += numLines;
|
||||
int bAheadOfA = lineB - lineA;
|
||||
lineMapAtoB.add(new LineGap(lineA, lineA, bAheadOfA));
|
||||
lineMapBtoA.add(new LineGap(origLineB, lineB - 1, -bAheadOfA));
|
||||
}
|
||||
|
||||
void appendDelete(int numLines) {
|
||||
int origLineA = lineA;
|
||||
lineA += numLines;
|
||||
int aAheadOfB = lineA - lineB;
|
||||
lineMapAtoB.add(new LineGap(origLineA, lineA - 1, -aAheadOfB));
|
||||
lineMapBtoA.add(new LineGap(lineB, lineB, aAheadOfB));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to retrieve the line number on the other side.
|
||||
*
|
||||
* Given a line number on one side, performs a binary search in the lineMap
|
||||
* to find the corresponding LineGap record.
|
||||
*
|
||||
* A LineGap records gap information from the start of an actual gap up to
|
||||
* the start of the next gap. In the following example,
|
||||
* lineMapAtoB will have LineGap: {start: 1, end: 1, delta: 3}
|
||||
* (end doesn't really matter here, as the binary search only looks at start)
|
||||
* lineMapBtoA will have LineGap: {start: 1, end: 3, delta: -3}
|
||||
* These LineGaps control lines between 1 and 5.
|
||||
*
|
||||
* The "delta" is computed as the number to add on our side to get the line
|
||||
* number on the other side given a line after the actual gap, so the result
|
||||
* will be (line + delta). All lines within the actual gap (1 to 3) are
|
||||
* considered corresponding to the last line above the region on the other
|
||||
* side, which is 0 in this case. For these lines, we do (end + delta).
|
||||
*
|
||||
* For example, to get the line number on the left corresponding to 1 on the
|
||||
* right (lineOnOther(REVISION, 1)), the method looks up in lineMapBtoA,
|
||||
* finds the "delta" to be -3, and returns 3 + (-3) = 0 since 1 falls in the
|
||||
* actual gap. On the other hand, the line corresponding to 5 on the right
|
||||
* will be 5 + (-3) = 2, since 5 is in the region after the gap (but still
|
||||
* controlled by the current LineGap).
|
||||
*
|
||||
* PARENT REVISION
|
||||
* 0 | 0
|
||||
* - | 1 \ \
|
||||
* - | 2 | Actual insertion gap |
|
||||
* - | 3 / | Region controlled by one LineGap
|
||||
* 1 | 4 <- delta = 4 - 1 = 3 |
|
||||
* 2 | 5 /
|
||||
* - | 6
|
||||
* ...
|
||||
*/
|
||||
int lineOnOther(Side mySide, int line) {
|
||||
List<LineGap> lineGaps = mySide == Side.PARENT ? lineMapAtoB : lineMapBtoA;
|
||||
// Create a dummy LineGap for the search.
|
||||
int ret = Collections.binarySearch(lineGaps, new LineGap(line));
|
||||
if (ret == -1) {
|
||||
return line;
|
||||
} else {
|
||||
LineGap lookup = lineGaps.get(0 <= ret ? ret : -ret - 2);
|
||||
int end = lookup.end;
|
||||
int delta = lookup.delta;
|
||||
if (lookup.start <= line && line <= end) { // Line falls within gap
|
||||
return end + delta;
|
||||
} else { // Line after gap
|
||||
return line + delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to record line gap info and assist in calculation of line
|
||||
* number on the other side.
|
||||
*
|
||||
* For a mapping from A to B, where A is the side with an insertion:
|
||||
* @field start The start line of the insertion in A.
|
||||
* @field end The exclusive end line of the insertion in A.
|
||||
* @field delta The offset added to A to get the line number in B calculated
|
||||
* from end.
|
||||
*/
|
||||
private static class LineGap implements Comparable<LineGap> {
|
||||
private final int start;
|
||||
private final int end;
|
||||
private final int delta;
|
||||
|
||||
private LineGap(int start, int end, int delta) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
private LineGap(int line) {
|
||||
this(line, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(LineGap o) {
|
||||
return start - o.start;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,9 +66,18 @@ public class CodeMirror extends JavaScriptObject {
|
||||
this.addLineClass(line, where, lineClass);
|
||||
}-*/;
|
||||
|
||||
public final native void addLineWidget(int line, Element node,
|
||||
public final native void addWidget(LineCharacter pos, Element node,
|
||||
boolean scrollIntoView) /*-{
|
||||
this.addWidget(pos, node, scrollIntoView);
|
||||
}-*/;
|
||||
|
||||
public final native LineWidget addLineWidget(int line, Element node,
|
||||
Configuration options) /*-{
|
||||
this.addLineWidget(line, node, options);
|
||||
return this.addLineWidget(line, node, options);
|
||||
}-*/;
|
||||
|
||||
public final native int lineAtHeight(int height) /*-{
|
||||
return this.lineAtHeight(height);
|
||||
}-*/;
|
||||
|
||||
public final native CodeMirrorDoc getDoc() /*-{
|
||||
|
||||
31
gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
vendored
Normal file
31
gerrit-gwtui/src/main/java/net/codemirror/lib/LineWidget.java
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package net.codemirror.lib;
|
||||
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
|
||||
/** LineWidget objects used within CodeMirror. */
|
||||
public class LineWidget extends JavaScriptObject {
|
||||
public static LineWidget create() {
|
||||
return createObject().cast();
|
||||
}
|
||||
|
||||
public final native void clear() /*-{ this.clear(); }-*/;
|
||||
public final native void changed() /*-{ this.changed(); }-*/;
|
||||
public final native JavaScriptObject getLine() /*-{ return this.line; }-*/;
|
||||
|
||||
protected LineWidget() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import com.google.gerrit.common.changes.Side;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/** Unit tests for LineMapper */
|
||||
public class LineMapperTest {
|
||||
|
||||
@Test
|
||||
public void testAppendCommon() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendCommon(10);
|
||||
assertEquals(10, mapper.getLineA());
|
||||
assertEquals(10, mapper.getLineB());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppendInsert() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendInsert(10);
|
||||
assertEquals(0, mapper.getLineA());
|
||||
assertEquals(10, mapper.getLineB());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppendDelete() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendDelete(10);
|
||||
assertEquals(10, mapper.getLineA());
|
||||
assertEquals(0, mapper.getLineB());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindInCommon() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendCommon(10);
|
||||
assertEquals(9, mapper.lineOnOther(Side.PARENT, 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAfterCommon() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendCommon(10);
|
||||
assertEquals(10, mapper.lineOnOther(Side.PARENT, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindInInsertGap() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendInsert(10);
|
||||
assertEquals(-1, mapper.lineOnOther(Side.REVISION, 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAfterInsertGap() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendInsert(10);
|
||||
assertEquals(0, mapper.lineOnOther(Side.REVISION, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindInDeleteGap() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendDelete(10);
|
||||
assertEquals(-1, mapper.lineOnOther(Side.PARENT, 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAfterDeleteGap() {
|
||||
LineMapper mapper = new LineMapper();
|
||||
mapper.appendDelete(10);
|
||||
assertEquals(0, mapper.lineOnOther(Side.PARENT, 10));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
VERSION = '3.13'
|
||||
SHA1 = '7a83ae686d75afd30bb152d7683f2dc27e59ea82'
|
||||
URL = 'http://codemirror.net/codemirror-%s.zip' % VERSION
|
||||
include_defs('//lib/maven.defs')
|
||||
|
||||
VERSION = 'bb73aeacb8'
|
||||
SHA1 = '3bc9c92b97210135bc83fca7a2bd5f3c4aab496a'
|
||||
URL = GERRIT + 'net/codemirror/codemirror-%s.zip' % VERSION
|
||||
|
||||
prebuilt_jar(
|
||||
name = 'codemirror',
|
||||
|
||||
Reference in New Issue
Block a user