Merge "Start displaying comments and drafts."

This commit is contained in:
Shawn Pearce
2013-06-25 17:17:57 +00:00
committed by Gerrit Code Review
11 changed files with 681 additions and 54 deletions

View File

@@ -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() {

View File

@@ -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());

View File

@@ -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());
}
}

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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() /*-{

View 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() {
}
}

View File

@@ -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));
}
}

View File

@@ -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',