Add keyboard shortcuts and VIM bindings

Added global keyboard shortcuts.

Added shortcuts for:
- Opening / closing comment boxes.
- Saving drafts.
- Going up to change.

Included CodeMirror's VIM bindings for navigation. Removed the ones
that trigger insert mode. We probably need a way to include only a
subset of VIM bindings.

Fixed CodeMirror.on(). Added EventHandler for handling more types of
events.

Removed hiddenSkipMap hack and imported CodeMirror's official fix.

Change-Id: I75923e20e27434ca95882e2d70d10346bdf2323c
This commit is contained in:
Michael Zhou
2013-07-11 15:43:56 -07:00
parent 1ee35db3d6
commit 8add596333
12 changed files with 294 additions and 69 deletions

View File

@@ -17,12 +17,14 @@ package com.google.gerrit.client.diff;
import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi; import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeInfo; import com.google.gerrit.client.changes.ChangeInfo;
import com.google.gerrit.client.changes.ChangeScreen;
import com.google.gerrit.client.changes.CommentApi; import com.google.gerrit.client.changes.CommentApi;
import com.google.gerrit.client.changes.CommentInfo; import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.diff.DiffInfo.Region; import com.google.gerrit.client.diff.DiffInfo.Region;
import com.google.gerrit.client.diff.DiffInfo.Span; import com.google.gerrit.client.diff.DiffInfo.Span;
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo; import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
import com.google.gerrit.client.diff.PaddingManager.LineWidgetElementPair; import com.google.gerrit.client.diff.PaddingManager.LineWidgetElementPair;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.patches.SkippedLine; import com.google.gerrit.client.patches.SkippedLine;
import com.google.gerrit.client.projects.ConfigInfoCache; import com.google.gerrit.client.projects.ConfigInfoCache;
import com.google.gerrit.client.rpc.CallbackGroup; import com.google.gerrit.client.rpc.CallbackGroup;
@@ -31,6 +33,7 @@ import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.ScreenLoadCallback; import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor; import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.Screen; import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.changes.Side; import com.google.gerrit.common.changes.Side;
import com.google.gerrit.reviewdb.client.AccountDiffPreference; import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.client.PatchSet;
@@ -39,13 +42,19 @@ import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
import net.codemirror.lib.CodeMirror; import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.LineClassWhere; import net.codemirror.lib.CodeMirror.LineClassWhere;
@@ -87,9 +96,14 @@ public class CodeMirrorDemo extends Screen {
private Map<LineHandle, PublishedBox> lineLastPublishedBoxMap; private Map<LineHandle, PublishedBox> lineLastPublishedBoxMap;
private Map<LineHandle, PaddingManager> linePaddingManagerMap; private Map<LineHandle, PaddingManager> linePaddingManagerMap;
private List<SkippedLine> skips; private List<SkippedLine> skips;
private Map<LineHandle, Integer> hiddenSkipMap;
private int context; private int context;
private KeyCommandSet keysNavigation;
private KeyCommandSet keysAction;
private KeyCommandSet keysComment;
private KeyCommandSet keysOpenByEnter;
private List<HandlerRegistration> keyHandlers;
public CodeMirrorDemo( public CodeMirrorDemo(
PatchSet.Id base, PatchSet.Id base,
PatchSet.Id revision, PatchSet.Id revision,
@@ -97,6 +111,9 @@ public class CodeMirrorDemo extends Screen {
this.base = base; this.base = base;
this.revision = revision; this.revision = revision;
this.path = path; this.path = path;
this.keyHandlers = new ArrayList<HandlerRegistration>(4);
// TODO: Re-implement necessary GlobalKey bindings.
addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
} }
@Override @Override
@@ -184,6 +201,7 @@ public class CodeMirrorDemo extends Screen {
protected void onUnload() { protected void onUnload() {
super.onUnload(); super.onUnload();
removeKeyHandlerRegs();
if (resizeHandler != null) { if (resizeHandler != null) {
resizeHandler.removeHandler(); resizeHandler.removeHandler();
resizeHandler = null; resizeHandler = null;
@@ -199,6 +217,69 @@ public class CodeMirrorDemo extends Screen {
Window.enableScrolling(true); Window.enableScrolling(true);
} }
private void removeKeyHandlerRegs() {
for (HandlerRegistration h : keyHandlers) {
h.removeHandler();
}
keyHandlers.clear();
}
private void registerCmEvents(CodeMirror cm) {
cm.on("cursorActivity", updateActiveLine(cm));
cm.on("scroll", doScroll(otherCM(cm)));
/**
* TODO: Trying to prevent right click from updating the cursor.
* Doesn't seem to work for now.
*/
cm.on("mousedown", ignoreRightClick());
cm.addKeyMap(KeyMap.create().on("'u'", upToChange()));
cm.addKeyMap(KeyMap.create().on("'o'", toggleOpenBox(cm)));
cm.addKeyMap(KeyMap.create().on("Enter", toggleOpenBox(cm)));
if (Gerrit.isSignedIn()) {
cm.addKeyMap(KeyMap.create().on("'c'", insertNewDraft(cm)));
}
// TODO: Examine if a better way exists.
for (String c : new String[]{"A", "C", "D", "I", "O", "P", "R", "S", "U",
"X", "Y", "~"}) {
CodeMirror.disableUnwantedKey("vim", c);
}
}
@Override
public void registerKeys() {
super.registerKeys();
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
keysNavigation.add(new NoOpKeyCommand(0, 'u', PatchUtil.C.upToChange()));
keysNavigation.add(new NoOpKeyCommand(0, 'j', PatchUtil.C.lineNext()));
keysNavigation.add(new NoOpKeyCommand(0, 'k', PatchUtil.C.linePrev()));
keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
keysAction.add(new NoOpKeyCommand(0, 'o', PatchUtil.C.expandComment()));
keysOpenByEnter = new KeyCommandSet(Gerrit.C.sectionNavigation());
keysOpenByEnter.add(new NoOpKeyCommand(0, KeyCodes.KEY_ENTER,
PatchUtil.C.expandComment()));
if (Gerrit.isSignedIn()) {
keysAction.add(new NoOpKeyCommand(0, 'c', PatchUtil.C.commentInsert()));
keysComment = new KeyCommandSet(PatchUtil.C.commentEditorSet());
keysComment.add(new NoOpKeyCommand(KeyCommand.M_CTRL, 's',
PatchUtil.C.commentSaveDraft()));
keysComment.add(new NoOpKeyCommand(0, KeyCodes.KEY_ESCAPE,
PatchUtil.C.commentCancelEdit()));
} else {
keysComment = null;
}
removeKeyHandlerRegs();
keyHandlers.add(GlobalKey.add(this, keysNavigation));
keyHandlers.add(GlobalKey.add(this, keysAction));
keyHandlers.add(GlobalKey.add(this, keysOpenByEnter));
if (keysComment != null) {
keyHandlers.add(GlobalKey.add(this, keysComment));
}
}
private void display(DiffInfo diffInfo) { private void display(DiffInfo diffInfo) {
cmA = displaySide(diffInfo.meta_a(), diffInfo.text_a(), diffTable.getCmA()); cmA = displaySide(diffInfo.meta_a(), diffInfo.text_a(), diffTable.getCmA());
cmB = displaySide(diffInfo.meta_b(), diffInfo.text_b(), diffTable.getCmB()); cmB = displaySide(diffInfo.meta_b(), diffInfo.text_b(), diffTable.getCmB());
@@ -219,12 +300,8 @@ public class CodeMirrorDemo extends Screen {
published = null; published = null;
drafts = null; drafts = null;
skips = null; skips = null;
cmA.on("cursorActivity", updateActiveLine(cmA)); registerCmEvents(cmA);
cmB.on("cursorActivity", updateActiveLine(cmB)); registerCmEvents(cmB);
if (Gerrit.isSignedIn()) {
cmA.addKeyMap(KeyMap.create().on("'c'", insertNewDraft(cmA)));
cmB.addKeyMap(KeyMap.create().on("'c'", insertNewDraft(cmB)));
}
// TODO: Probably need horizontal resize // TODO: Probably need horizontal resize
resizeHandler = Window.addResizeHandler(new ResizeHandler() { resizeHandler = Window.addResizeHandler(new ResizeHandler() {
@Override @Override
@@ -239,8 +316,6 @@ public class CodeMirrorDemo extends Screen {
} }
} }
}); });
cmA.on("scroll", doScroll(cmB));
cmB.on("scroll", doScroll(cmA));
} }
private CodeMirror displaySide(DiffInfo.FileMeta meta, String contents, private CodeMirror displaySide(DiffInfo.FileMeta meta, String contents,
@@ -255,6 +330,7 @@ public class CodeMirrorDemo extends Screen {
.set("mode", getContentType(meta)) .set("mode", getContentType(meta))
.set("styleSelectedText", true) .set("styleSelectedText", true)
.set("showTrailingSpace", true) .set("showTrailingSpace", true)
.set("keyMap", "vim")
.set("value", contents); .set("value", contents);
final CodeMirror cm = CodeMirror.create(ele, cfg); final CodeMirror cm = CodeMirror.create(ele, cfg);
cm.setHeight(Window.getClientHeight() - HEADER_FOOTER); cm.setHeight(Window.getClientHeight() - HEADER_FOOTER);
@@ -334,13 +410,14 @@ public class CodeMirrorDemo extends Screen {
} }
private DraftBox addDraftBox(CommentInfo info, boolean doSave) { private DraftBox addDraftBox(CommentInfo info, boolean doSave) {
DraftBox box = new DraftBox(this, revision, info, commentLinkProcessor, CodeMirror cm = getCmFromSide(info.side());
DraftBox box = new DraftBox(this, cm, revision, info, commentLinkProcessor,
true, doSave); true, doSave);
addCommentBox(info, box); addCommentBox(info, box);
if (!doSave) { if (!doSave) {
box.setEdit(true); box.setEdit(true);
} }
LineHandle handle = getCmFromSide(info.side()).getLineHandle(info.line() - 1); LineHandle handle = cm.getLineHandle(info.line() - 1);
lineActiveBoxMap.put(handle, box); lineActiveBoxMap.put(handle, box);
return box; return box;
} }
@@ -424,7 +501,8 @@ public class CodeMirrorDemo extends Screen {
List<CommentInfo> sorted = sortComment(drafts); List<CommentInfo> sorted = sortComment(drafts);
for (CommentInfo info : sorted) { for (CommentInfo info : sorted) {
final DraftBox box = final DraftBox box =
new DraftBox(this, revision, info, commentLinkProcessor, false, false); new DraftBox(this, getCmFromSide(info.side()), revision, info,
commentLinkProcessor, false, false);
box.setOpen(false); box.setOpen(false);
box.setEdit(false); box.setEdit(false);
initialBoxes.add(box); initialBoxes.add(box);
@@ -441,7 +519,6 @@ public class CodeMirrorDemo extends Screen {
} }
private void renderSkips() { private void renderSkips() {
hiddenSkipMap = new HashMap<LineHandle, Integer>();
for (CommentBox box : initialBoxes) { for (CommentBox box : initialBoxes) {
List<SkippedLine> temp = new ArrayList<SkippedLine>(); List<SkippedLine> temp = new ArrayList<SkippedLine>();
for (SkippedLine skip : skips) { for (SkippedLine skip : skips) {
@@ -482,8 +559,7 @@ public class CodeMirrorDemo extends Screen {
int size = skip.getSize(); int size = skip.getSize();
int markStart = cm == cmA ? skip.getStartA() - 1 : skip.getStartB() - 1; int markStart = cm == cmA ? skip.getStartA() - 1 : skip.getStartB() - 1;
int markEnd = markStart + size; int markEnd = markStart + size;
hiddenSkipMap.put(cm.getLineHandle(markEnd), size); SkipBar bar = new SkipBar(cm);
SkipBar bar = new SkipBar(cm, hiddenSkipMap);
diffTable.add(bar); diffTable.add(bar);
/** /**
* Due to CodeMirror limitation, there's no way to make the first * Due to CodeMirror limitation, there's no way to make the first
@@ -600,17 +676,8 @@ public class CodeMirrorDemo extends Screen {
other.removeLineClass(other.getActiveLine(), other.removeLineClass(other.getActiveLine(),
LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg()); LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg());
} }
int line = cm.getCursor("head").getLine(); LineHandle handle = cm.getLineHandleVisualStart(cm.getCursor().getLine());
LineHandle handle = cm.getLineHandle(line); int line = cm.getLineNumber(handle);
/**
* Ugly workaround because CodeMirror never hides lines completely.
* TODO: Change to use CodeMirror's official workaround after
* updating the library to latest HEAD.
*/
if (hiddenSkipMap.containsKey(handle)) {
line -= hiddenSkipMap.get(handle);
handle = cm.getLineHandle(line);
}
cm.setActiveLine(handle); cm.setActiveLine(handle);
if (cm.somethingSelected()) { if (cm.somethingSelected()) {
return; return;
@@ -648,6 +715,35 @@ public class CodeMirrorDemo extends Screen {
}; };
} }
private Runnable toggleOpenBox(final CodeMirror cm) {
return new Runnable() {
public void run() {
CommentBox box = lineActiveBoxMap.get(cm.getActiveLine());
if (box != null) {
box.setOpen(!box.isOpen());
}
}
};
}
private Runnable upToChange() {
return new Runnable() {
public void run() {
Gerrit.display(PageLinks.toChange(revision), new ChangeScreen(revision));
}
};
}
private CodeMirror.EventHandler ignoreRightClick() {
return new CodeMirror.EventHandler() {
public void handle(CodeMirror instance, NativeEvent event) {
if (event.getButton() == NativeEvent.BUTTON_RIGHT) {
event.preventDefault();
}
}
};
}
private static String getContentType(DiffInfo.FileMeta meta) { private static String getContentType(DiffInfo.FileMeta meta) {
return meta != null && meta.content_type() != null return meta != null && meta.content_type() != null
? ModeInjector.getContentType(meta.content_type()) ? ModeInjector.getContentType(meta.content_type())
@@ -690,4 +786,14 @@ public class CodeMirrorDemo extends Screen {
currLineOffset = 0; currLineOffset = 0;
} }
} }
private static class NoOpKeyCommand extends KeyCommand {
private NoOpKeyCommand(int mask, int key, String help) {
super(mask, key, help);
}
@Override
public void onKeyPress(final KeyPressEvent event) {
}
}
} }

View File

@@ -20,11 +20,10 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.ClickEvent; 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.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.Widget;
@@ -43,7 +42,6 @@ abstract class CommentBox extends Composite {
} }
private CommentLinkProcessor commentLinkProcessor; private CommentLinkProcessor commentLinkProcessor;
private HandlerRegistration headerClick;
private CommentInfo original; private CommentInfo original;
private PatchSet.Id patchSetId; private PatchSet.Id patchSetId;
private PaddingManager widgetManager; private PaddingManager widgetManager;
@@ -79,25 +77,9 @@ abstract class CommentBox extends Composite {
protected void onLoad() { protected void onLoad() {
super.onLoad(); super.onLoad();
headerClick = header.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
setOpen(!isOpen());
}
}, ClickEvent.getType());
res.style().ensureInjected(); res.style().ensureInjected();
} }
@Override
protected void onUnload() {
super.onUnload();
if (headerClick != null) {
headerClick.removeHandler();
headerClick = null;
}
}
void resizePaddingWidget() { void resizePaddingWidget() {
Scheduler.get().scheduleDeferred(new ScheduledCommand(){ Scheduler.get().scheduleDeferred(new ScheduledCommand(){
public void execute() { public void execute() {
@@ -138,7 +120,7 @@ abstract class CommentBox extends Composite {
resizePaddingWidget(); resizePaddingWidget();
} }
private boolean isOpen() { boolean isOpen() {
return getStyleName().contains(res.style().open()); return getStyleName().contains(res.style().open());
} }
@@ -177,4 +159,9 @@ abstract class CommentBox extends Composite {
LineWidget getSelfWidget() { LineWidget getSelfWidget() {
return selfWidget; return selfWidget;
} }
@UiHandler("header")
void onHeaderClick(ClickEvent e) {
setOpen(!isOpen());
}
} }

View File

@@ -20,6 +20,10 @@ import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.patches.PatchUtil; import com.google.gerrit.client.patches.PatchUtil;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element; 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.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Composite;
@@ -32,7 +36,7 @@ import java.sql.Timestamp;
* the author's avatar (if applicable), the author's name, the summary, * the author's avatar (if applicable), the author's name, the summary,
* and the date. * and the date.
*/ */
class CommentBoxHeader extends Composite { class CommentBoxHeader extends Composite implements HasClickHandlers {
interface Binder extends UiBinder<HTMLPanel, CommentBoxHeader> {} interface Binder extends UiBinder<HTMLPanel, CommentBoxHeader> {}
private static Binder uiBinder = GWT.create(Binder.class); private static Binder uiBinder = GWT.create(Binder.class);
@@ -76,4 +80,9 @@ class CommentBoxHeader extends Composite {
void setSummaryText(String message) { void setSummaryText(String message) {
summary.setInnerText(message); summary.setInnerText(message);
} }
@Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
} }

View File

@@ -20,7 +20,8 @@ limitations under the License.
@external .CodeMirror, .CodeMirror-selectedtext; @external .CodeMirror, .CodeMirror-selectedtext;
@external .CodeMirror-linenumber, .CodeMirror-vscrollbar; @external .CodeMirror-linenumber, .CodeMirror-vscrollbar;
@external .CodeMirror-hscrollbar; @external .CodeMirror-hscrollbar;
@external .cm-trailingspace; @external .cm-keymap-fat-cursor, CodeMirror-cursor;
@external .cm-searching, .cm-trailingspace;
.difftable .CodeMirror pre { .difftable .CodeMirror pre {
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
@@ -48,9 +49,6 @@ limitations under the License.
.b .diff .CodeMirror-linenumber { .b .diff .CodeMirror-linenumber {
background-color: #9f9; background-color: #9f9;
} }
.CodeMirror-selectedtext.diff, .CodeMirror-selectedtext.intralineBg {
background-color: inherit !important;
}
.padding { .padding {
background-color: #eee; background-color: #eee;
} }
@@ -62,20 +60,30 @@ limitations under the License.
} }
.activeLine .CodeMirror-linenumber, .activeLine .CodeMirror-linenumber,
.activeLine .diff, .activeLine .intralineBg { .activeLine .diff, .activeLine .intralineBg {
background-color: #FFFF99 !important; background-color: #E0FFFF !important;
} }
.activeLineBg { .activeLineBg {
background-color: #FFFF99 !important; background-color: #E0FFFF !important;
}
.cm-searching {
background-color: #ffa !important;
} }
.cm-trailingspace { .cm-trailingspace {
background-color: red !important; background-color: red !important;
} }
.CodeMirror-selectedtext {
background-color: inherit !important;
}
.hideNumber .CodeMirror-linenumber { .hideNumber .CodeMirror-linenumber {
color: transparent; color: transparent;
background-color: #def; background-color: #def;
width: 23px !important; width: 23px !important;
height: 16px; height: 16px;
} }
.difftable .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
opacity: 0.8;
z-index: 2;
}
</ui:style> </ui:style>
<g:HTMLPanel styleName='{style.difftable}'> <g:HTMLPanel styleName='{style.difftable}'>
<table class='{style.table}'> <table class='{style.table}'>

View File

@@ -25,6 +25,8 @@ import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler; import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.event.shared.HandlerRegistration;
@@ -36,6 +38,8 @@ import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwtexpui.globalkey.client.NpTextArea; import com.google.gwtexpui.globalkey.client.NpTextArea;
import net.codemirror.lib.CodeMirror;
/** An HtmlPanel for displaying and editing a draft */ /** An HtmlPanel for displaying and editing a draft */
class DraftBox extends CommentBox { class DraftBox extends CommentBox {
interface Binder extends UiBinder<HTMLPanel, DraftBox> {} interface Binder extends UiBinder<HTMLPanel, DraftBox> {}
@@ -69,9 +73,11 @@ class DraftBox extends CommentBox {
private HandlerRegistration messageClick; private HandlerRegistration messageClick;
private boolean isNew; private boolean isNew;
private PublishedBox replyToBox; private PublishedBox replyToBox;
private CodeMirror cm;
DraftBox( DraftBox(
CodeMirrorDemo host, CodeMirrorDemo host,
CodeMirror cm,
PatchSet.Id id, PatchSet.Id id,
CommentInfo info, CommentInfo info,
CommentLinkProcessor linkProcessor, CommentLinkProcessor linkProcessor,
@@ -79,6 +85,7 @@ class DraftBox extends CommentBox {
boolean saveOnInit) { boolean saveOnInit) {
super(host, uiBinder, id, info, linkProcessor, true); super(host, uiBinder, id, info, linkProcessor, true);
this.cm = cm;
this.isNew = isNew; this.isNew = isNew;
editArea.setText(contentPanelMessage.getText()); editArea.setText(contentPanelMessage.getText());
if (saveOnInit) { if (saveOnInit) {
@@ -145,6 +152,7 @@ class DraftBox extends CommentBox {
PaddingManager manager = getPaddingManager(); PaddingManager manager = getPaddingManager();
manager.remove(this); manager.remove(this);
manager.resizePaddingWidget(); manager.resizePaddingWidget();
cm.focus();
} }
@UiHandler("edit") @UiHandler("edit")
@@ -179,11 +187,13 @@ class DraftBox extends CommentBox {
} else { } else {
CommentApi.updateDraft(getPatchSetId(), original.id(), input, cb); CommentApi.updateDraft(getPatchSetId(), original.id(), input, cb);
} }
cm.focus();
} }
@UiHandler("cancel") @UiHandler("cancel")
void onCancel(ClickEvent e) { void onCancel(ClickEvent e) {
setEdit(false); setEdit(false);
cm.focus();
} }
@UiHandler("discard") @UiHandler("discard")
@@ -200,4 +210,24 @@ class DraftBox extends CommentBox {
}); });
} }
} }
@UiHandler("editArea")
void onCtrlS(KeyDownEvent e) {
if (e.isControlKeyDown() && e.getNativeKeyCode() == 83) {
onSave(null);
e.preventDefault();
}
}
/** TODO: Unused now. Re-enable this after implementing auto-save */
void onEsc(KeyDownEvent e) {
if (e.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
if (isNew) {
removeUI();
} else {
onCancel(null);
}
e.preventDefault();
}
}
} }

View File

@@ -27,14 +27,11 @@ import com.google.gwt.user.client.ui.HTMLPanel;
import net.codemirror.lib.CodeMirror; import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.LineClassWhere; import net.codemirror.lib.CodeMirror.LineClassWhere;
import net.codemirror.lib.CodeMirror.LineHandle;
import net.codemirror.lib.Configuration; import net.codemirror.lib.Configuration;
import net.codemirror.lib.LineWidget; import net.codemirror.lib.LineWidget;
import net.codemirror.lib.TextMarker; import net.codemirror.lib.TextMarker;
import net.codemirror.lib.TextMarker.FromTo; import net.codemirror.lib.TextMarker.FromTo;
import java.util.Map;
/** The Widget that handles expanding of skipped lines */ /** The Widget that handles expanding of skipped lines */
class SkipBar extends Composite { class SkipBar extends Composite {
interface Binder extends UiBinder<HTMLPanel, SkipBar> {} interface Binder extends UiBinder<HTMLPanel, SkipBar> {}
@@ -67,11 +64,9 @@ class SkipBar extends Composite {
private SkipBar otherBar; private SkipBar otherBar;
private CodeMirror cm; private CodeMirror cm;
private int numSkipLines; private int numSkipLines;
private Map<LineHandle, Integer> hiddenSkipMap;
SkipBar(CodeMirror cm, Map<LineHandle, Integer> hiddenSkipMap) { SkipBar(CodeMirror cm) {
this.cm = cm; this.cm = cm;
this.hiddenSkipMap = hiddenSkipMap;
skipNum = new Anchor(true); skipNum = new Anchor(true);
upArrow = new Anchor(true); upArrow = new Anchor(true);
downArrow = new Anchor(true); downArrow = new Anchor(true);
@@ -123,10 +118,9 @@ class SkipBar extends Composite {
} }
private void expandAll() { private void expandAll() {
hiddenSkipMap.remove(
cm.getLineHandle(marker.find().getTo().getLine()));
clearMarkerAndWidget(); clearMarkerAndWidget();
removeFromParent(); removeFromParent();
cm.focus();
} }
private void expandBefore() { private void expandBefore() {
@@ -140,7 +134,7 @@ class SkipBar extends Composite {
LineWidget newWidget = cm.addLineWidget(newStart, getElement(), config); LineWidget newWidget = cm.addLineWidget(newStart, getElement(), config);
setWidget(newWidget); setWidget(newWidget);
updateSkipNum(); updateSkipNum();
hiddenSkipMap.put(cm.getLineHandle(end), numSkipLines); cm.focus();
} }
private void expandAfter() { private void expandAfter() {
@@ -152,8 +146,7 @@ class SkipBar extends Composite {
CodeMirror.pos(newEnd), CodeMirror.pos(newEnd),
COLLAPSED); COLLAPSED);
updateSkipNum(); updateSkipNum();
hiddenSkipMap.remove(cm.getLineHandle(oldEnd)); cm.focus();
hiddenSkipMap.put(cm.getLineHandle(newEnd), numSkipLines);
} }
@UiHandler("skipNum") @UiHandler("skipNum")

View File

@@ -18,5 +18,6 @@
<inherits name='com.google.gwt.resources.Resources'/> <inherits name='com.google.gwt.resources.Resources'/>
<source path='addon'/> <source path='addon'/>
<source path='lib'/> <source path='lib'/>
<source path='keymap'/>
<source path='mode'/> <source path='mode'/>
</module> </module>

View File

@@ -18,6 +18,7 @@ import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.DataResource; import com.google.gwt.resources.client.DataResource;
import com.google.gwt.resources.client.DataResource.DoNotEmbed; import com.google.gwt.resources.client.DataResource.DoNotEmbed;
import com.google.gwt.resources.client.ExternalTextResource;
public interface Addons extends ClientBundle { public interface Addons extends ClientBundle {
public static final Addons I = GWT.create(Addons.class); public static final Addons I = GWT.create(Addons.class);
@@ -29,4 +30,19 @@ public interface Addons extends ClientBundle {
@Source("edit/trailingspace.js") @Source("edit/trailingspace.js")
@DoNotEmbed @DoNotEmbed
DataResource trailingspace(); DataResource trailingspace();
@Source("dialog/dialog.css")
ExternalTextResource dialogCss();
@Source("dialog/dialog.js")
@DoNotEmbed
DataResource dialog();
@Source("search/searchcursor.js")
@DoNotEmbed
DataResource searchcursor();
@Source("search/search.js")
@DoNotEmbed
DataResource search();
} }

View File

@@ -0,0 +1,28 @@
// 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.keymap;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.resources.client.DataResource.DoNotEmbed;
public interface Keymap extends ClientBundle {
static final Keymap I = GWT.create(Keymap.class);
@Source("vim.js")
@DoNotEmbed
DataResource vim();
}

View File

@@ -16,6 +16,7 @@ package net.codemirror.lib;
import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
/** /**
@@ -128,15 +129,29 @@ public class CodeMirror extends JavaScriptObject {
}-*/; }-*/;
public final native void on(String event, Runnable thunk) /*-{ public final native void on(String event, Runnable thunk) /*-{
this.on(event, function() { this.on(event, $entry(function() {
$entry(thunk.@java.lang.Runnable::run()()); thunk.@java.lang.Runnable::run()();
}); }));
}-*/;
public final native void on(String event, EventHandler handler) /*-{
this.on(event, $entry(function(cm, e) {
handler.@net.codemirror.lib.CodeMirror.EventHandler::handle(Lnet/codemirror/lib/CodeMirror;Lcom/google/gwt/dom/client/NativeEvent;)(cm, e);
}));
}-*/;
public final native LineCharacter getCursor() /*-{
return this.getCursor();
}-*/; }-*/;
public final native LineCharacter getCursor(String start) /*-{ public final native LineCharacter getCursor(String start) /*-{
return this.getCursor(start); return this.getCursor(start);
}-*/; }-*/;
public final native void setCursor(LineCharacter lineCh) /*-{
this.setCursor(lineCh);
}-*/;
public final native boolean somethingSelected() /*-{ public final native boolean somethingSelected() /*-{
return this.somethingSelected(); return this.somethingSelected();
}-*/; }-*/;
@@ -155,6 +170,10 @@ public class CodeMirror extends JavaScriptObject {
public final native void addKeyMap(KeyMap map) /*-{ this.addKeyMap(map); }-*/; public final native void addKeyMap(KeyMap map) /*-{ this.addKeyMap(map); }-*/;
public final native void removeKeyMap(KeyMap map) /*-{ this.removeKeyMap(map); }-*/;
public final native void removeKeyMap(String name) /*-{ this.removeKeyMap(name); }-*/;
public static final native LineCharacter pos(int line, int ch) /*-{ public static final native LineCharacter pos(int line, int ch) /*-{
return $wnd.CodeMirror.Pos(line, ch); return $wnd.CodeMirror.Pos(line, ch);
}-*/; }-*/;
@@ -167,10 +186,24 @@ public class CodeMirror extends JavaScriptObject {
return this.getLineHandle(line); return this.getLineHandle(line);
}-*/; }-*/;
public final native LineHandle getLineHandleVisualStart(int line) /*-{
return this.getLineHandleVisualStart(line);
}-*/;
public final native int getLineNumber(LineHandle handle) /*-{ public final native int getLineNumber(LineHandle handle) /*-{
return this.getLineNumber(handle); return this.getLineNumber(handle);
}-*/; }-*/;
public final native void focus() /*-{
this.focus();
}-*/;
/** Hack into CodeMirror to disable unwanted keys */
public static final native void disableUnwantedKey(String category,
String name) /*-{
$wnd.CodeMirror.keyMap[category][name] = undefined;
}-*/;
protected CodeMirror() { protected CodeMirror() {
} }
@@ -178,4 +211,8 @@ public class CodeMirror extends JavaScriptObject {
protected LineHandle(){ protected LineHandle(){
} }
} }
public interface EventHandler {
public void handle(CodeMirror instance, NativeEvent event);
}
} }

View File

@@ -33,8 +33,8 @@ public class LineCharacter extends JavaScriptObject {
return lineCh; return lineCh;
} }
private final native void setLine(int line) /*-{ this.line = line; }-*/; public final native void setLine(int line) /*-{ this.line = line; }-*/;
private final native void setCh(int ch) /*-{ this.ch = ch; }-*/; public final native void setCh(int ch) /*-{ this.ch = ch; }-*/;
public final native int getLine() /*-{ return this.line; }-*/; public final native int getLine() /*-{ return this.line; }-*/;
public final native int getCh() /*-{ return this.ch; }-*/; public final native int getCh() /*-{ return this.ch; }-*/;

View File

@@ -27,6 +27,7 @@ import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import net.codemirror.addon.Addons; import net.codemirror.addon.Addons;
import net.codemirror.keymap.Keymap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -41,8 +42,17 @@ class Loader {
} else { } else {
CallbackGroup group = new CallbackGroup(); CallbackGroup group = new CallbackGroup();
injectCss(Lib.I.css()); injectCss(Lib.I.css());
injectCss(Addons.I.dialogCss());
injectScript(Lib.I.js().getSafeUri(), injectScript(Lib.I.js().getSafeUri(),
group.add(CallbackGroup.<Void>emptyCallback())); group.add(CallbackGroup.<Void>emptyCallback()));
injectScript(Keymap.I.vim().getSafeUri(),
group.add(CallbackGroup.<Void>emptyCallback()));
injectScript(Addons.I.dialog().getSafeUri(),
group.add(CallbackGroup.<Void>emptyCallback()));
injectScript(Addons.I.searchcursor().getSafeUri(),
group.add(CallbackGroup.<Void>emptyCallback()));
injectScript(Addons.I.search().getSafeUri(),
group.add(CallbackGroup.<Void>emptyCallback()));
injectScript(Addons.I.mark_selection().getSafeUri(), injectScript(Addons.I.mark_selection().getSafeUri(),
group.add(CallbackGroup.<Void>emptyCallback())); group.add(CallbackGroup.<Void>emptyCallback()));
injectScript(Addons.I.trailingspace().getSafeUri(), injectScript(Addons.I.trailingspace().getSafeUri(),