Merge changes from topic 'inline-4'

* changes:
  Inline Edit: Bind Ctrl/Cmd-S to save
  Inline Edit: Confirm discard of edits
  Inline Edit: Honor diff preferences like SideBySide2
  Refactor common SideBySide2 CSS for CodeMirror
  Inline Edit: Set content during editor creation
  Inline Edit: Correctly handle browser resizing
This commit is contained in:
David Pursehouse
2015-01-06 06:21:30 +00:00
committed by Gerrit Code Review
15 changed files with 517 additions and 230 deletions

View File

@@ -147,7 +147,7 @@ class ChunkManager {
void adjustPadding() {
if (paddingDivs != null) {
double h = host.getLineHeightPx();
double h = cmB.extras().lineHeightPx();
for (Element div : paddingDivs) {
int lines = div.getPropertyInt(DATA_LINES);
div.getStyle().setHeight(lines * h, Unit.PX);
@@ -291,7 +291,9 @@ class ChunkManager {
return new Runnable() {
@Override
public void run() {
int line = cm.hasActiveLine() ? cm.getLineNumber(cm.activeLine()) : 0;
int line = cm.extras().hasActiveLine()
? cm.getLineNumber(cm.extras().activeLine())
: 0;
int res = Collections.binarySearch(
chunks,
new DiffChunkInfo(cm.side(), line, 0, false),

View File

@@ -91,8 +91,8 @@ class CommentManager {
// It is only necessary to search one side to find a comment
// on either side of the editor pair.
SortedMap<Integer, CommentGroup> map = map(src.side());
int line = src.hasActiveLine()
? src.getLineNumber(src.activeLine()) + 1
int line = src.extras().hasActiveLine()
? src.getLineNumber(src.extras().activeLine()) + 1
: 0;
if (dir == Direction.NEXT) {
map = map.tailMap(line + 1);
@@ -295,9 +295,9 @@ class CommentManager {
return new Runnable() {
@Override
public void run() {
if (cm.hasActiveLine()) {
if (cm.extras().hasActiveLine()) {
CommentGroup w = map(cm.side()).get(
cm.getLineNumber(cm.activeLine()) + 1);
cm.getLineNumber(cm.extras().activeLine()) + 1);
if (w != null) {
w.openCloseLast();
}
@@ -310,9 +310,9 @@ class CommentManager {
return new Runnable() {
@Override
public void run() {
if (cm.hasActiveLine()) {
if (cm.extras().hasActiveLine()) {
CommentGroup w = map(cm.side()).get(
cm.getLineNumber(cm.activeLine()) + 1);
cm.getLineNumber(cm.extras().activeLine()) + 1);
if (w != null) {
w.openCloseAll();
}
@@ -327,8 +327,8 @@ class CommentManager {
@Override
public void run() {
String token = host.getToken();
if (cm.hasActiveLine()) {
LineHandle handle = cm.activeLine();
if (cm.extras().hasActiveLine()) {
LineHandle handle = cm.extras().activeLine();
int line = cm.getLineNumber(handle) + 1;
token += "@" + (cm.side() == DisplaySide.A ? "a" : "") + line;
}
@@ -340,7 +340,7 @@ class CommentManager {
return new Runnable() {
@Override
public void run() {
if (cm.hasActiveLine()) {
if (cm.extras().hasActiveLine()) {
newDraft(cm);
}
}
@@ -348,7 +348,7 @@ class CommentManager {
}
private void newDraft(CodeMirror cm) {
int line = cm.getLineNumber(cm.activeLine()) + 1;
int line = cm.getLineNumber(cm.extras().activeLine()) + 1;
if (cm.somethingSelected()) {
FromTo fromTo = cm.getSelectedRange();
Pos end = fromTo.to();

View File

@@ -48,14 +48,11 @@ class DiffTable extends Composite {
String dark();
String diff();
String noIntraline();
String activeLine();
String range();
String rangeHighlight();
String showTabs();
String showLineNumbers();
String hideA();
String hideB();
String columnMargin();
String padding();
}

View File

@@ -18,12 +18,11 @@ limitations under the License.
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:d='urn:import:com.google.gerrit.client.diff'>
<ui:style type='com.google.gerrit.client.diff.DiffTable.DiffTableStyle'>
@external .CodeMirror, .CodeMirror-lines, .CodeMirror-selectedtext;
@external .CodeMirror, .CodeMirror-selectedtext;
@external .CodeMirror-linenumber;
@external .CodeMirror-overlayscroll-vertical, .CodeMirror-scroll;
@external .CodeMirror-dialog-bottom;
@external .cm-animate-fat-cursor, .CodeMirror-cursor;
@external .cm-searching, .cm-trailingspace, .cm-tab;
.fullscreen {
background-color: #f7f7f7;
@@ -39,13 +38,10 @@ limitations under the License.
-ms-user-select: none;
}
.difftable .CodeMirror-lines { padding: 0; }
.difftable .CodeMirror pre {
padding: 0;
overflow: hidden;
border-right: 0;
width: auto;
line-height: normal;
}
/* Preserve space for underscores. If this changes
@@ -106,23 +102,12 @@ limitations under the License.
overflow-x: auto;
}
.activeLine .CodeMirror-linenumber {
background-color: #bcf !important;
color: #000;
}
.range {
background-color: #ffd500 !important;
}
.rangeHighlight {
background-color: #ffff00 !important;
}
.cm-searching {
background-color: #ffa !important;
}
.cm-trailingspace {
background-color: red !important;
}
.difftable .CodeMirror-selectedtext {
background-color: inherit !important;
}
@@ -154,24 +139,10 @@ limitations under the License.
bottom: auto;
left: auto;
}
.showTabs .cm-tab:before {
position: absolute;
content: "\00bb";
color: #f00;
}
.showLineNumbers .padding {
margin-left: 21px;
border-left: 2px solid #d64040;
}
.columnMargin {
position: absolute;
top: 0;
bottom: 0;
width: 0;
border-right: 1px dashed #ffa500;
z-index: 2;
cursor: text;
}
.diff_header {
font-size: 12px;

View File

@@ -13,26 +13,6 @@
* limitations under the License.
*/
@external .CodeMirror;
@external .CodeMirror-overlayscroll-horizontal;
@external .CodeMirror-overlayscroll-vertical;
.CodeMirror-overlayscroll-horizontal div {
min-width: 25px;
}
.CodeMirror-overlayscroll-vertical div {
min-height: 25px;
}
.CodeMirror .CodeMirror-overlayscroll-vertical {
z-index: inherit;
}
.CodeMirror .CodeMirror-overlayscroll-horizontal div,
.CodeMirror .CodeMirror-overlayscroll-vertical div {
background-color: rgba(128, 128, 128, 0.50);
z-index: 8;
}
.comment, .draft, .insert, .delete, .edit {
min-height: 5px;
position: absolute;

View File

@@ -48,7 +48,6 @@ import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -58,7 +57,6 @@ import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
@@ -71,7 +69,6 @@ import com.google.gwtexpui.globalkey.client.ShowHelpCommand;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler;
import net.codemirror.lib.CodeMirror.GutterClickHandler;
import net.codemirror.lib.CodeMirror.LineClassWhere;
import net.codemirror.lib.CodeMirror.LineHandle;
import net.codemirror.lib.Configuration;
import net.codemirror.lib.KeyMap;
@@ -120,10 +117,6 @@ public class SideBySide2 extends Screen {
private CodeMirror cmA;
private CodeMirror cmB;
private Element columnMarginA;
private Element columnMarginB;
private double charWidthPx;
private double lineHeightPx;
private HandlerRegistration resizeHandler;
private ScrollSynchronizer scrollSynchronizer;
@@ -268,12 +261,10 @@ public class SideBySide2 extends Screen {
}
});
final int height = getCodeMirrorHeight();
operation(new Runnable() {
@Override
public void run() {
cmA.setHeight(height);
cmB.setHeight(height);
resizeCodeMirror();
chunkManager.adjustPadding();
cmA.refresh();
cmB.refresh();
@@ -297,6 +288,7 @@ public class SideBySide2 extends Screen {
if (startSide != null && startLine > 0) {
int line = startLine - 1;
CodeMirror cm = getCmFromSide(startSide);
int height = cm.getHeight();
if (cm.lineAtHeight(height - 20) < line) {
cm.scrollToY(cm.heightAtLine(line, "local") - 0.5 * height);
}
@@ -549,24 +541,21 @@ public class SideBySide2 extends Screen {
private void display(final CommentsCollections comments) {
setThemeStyles(prefs.theme().isDark());
setShowTabs(prefs.showTabs());
setShowIntraline(prefs.intralineDifference());
if (prefs.showLineNumbers()) {
diffTable.addStyleName(DiffTable.style.showLineNumbers());
}
cmA = newCM(diff.meta_a(), diff.text_a(), diffTable.cmA).side(DisplaySide.A);
cmB = newCM(diff.meta_b(), diff.text_b(), diffTable.cmB).side(DisplaySide.B);
cmA = newCM(diff.meta_a(), diff.text_a(), diffTable.cmA);
cmB = newCM(diff.meta_b(), diff.text_b(), diffTable.cmB);
cmA.extras().side(DisplaySide.A);
cmB.extras().side(DisplaySide.B);
setShowTabs(prefs.showTabs());
chunkManager = new ChunkManager(this, cmA, cmB, diffTable.scrollbar);
skipManager = new SkipManager(this, commentManager);
columnMarginA = DOM.createDiv();
columnMarginB = DOM.createDiv();
columnMarginA.setClassName(DiffTable.style.columnMargin());
columnMarginB.setClassName(DiffTable.style.columnMargin());
cmA.mover().appendChild(columnMarginA);
cmB.mover().appendChild(columnMarginB);
if (prefs.renderEntireFile() && !canEnableRenderEntireFile(prefs)) {
// CodeMirror is too slow to layout an entire huge file.
prefs.renderEntireFile(false);
@@ -679,61 +668,14 @@ public class SideBySide2 extends Screen {
}
}
void setShowTabs(boolean b) {
if (b) {
diffTable.addStyleName(DiffTable.style.showTabs());
} else {
diffTable.removeStyleName(DiffTable.style.showTabs());
}
void setShowTabs(boolean show) {
cmA.extras().showTabs(show);
cmB.extras().showTabs(show);
}
void setLineLength(int columns) {
double w = columns * getCharWidthPx();
columnMarginA.getStyle().setMarginLeft(w, Style.Unit.PX);
columnMarginB.getStyle().setMarginLeft(w, Style.Unit.PX);
}
double getLineHeightPx() {
if (lineHeightPx <= 1) {
Element p = DOM.createDiv();
int lines = 1;
for (int i = 0; i < lines; i++) {
Element e = DOM.createDiv();
p.appendChild(e);
Element pre = DOM.createElement("pre");
pre.setInnerText("gqyŚŻŹŃ");
e.appendChild(pre);
}
cmB.measure().appendChild(p);
lineHeightPx = ((double) p.getOffsetHeight()) / lines;
p.removeFromParent();
}
return lineHeightPx;
}
private double getCharWidthPx() {
if (charWidthPx <= 1) {
int len = 100;
StringBuilder s = new StringBuilder();
for (int i = 0; i < len; i++) {
s.append('m');
}
Element e = DOM.createSpan();
e.getStyle().setDisplay(Style.Display.INLINE_BLOCK);
e.setInnerText(s.toString());
cmA.measure().appendChild(e);
double a = ((double) e.getOffsetWidth()) / len;
e.removeFromParent();
cmB.measure().appendChild(e);
double b = ((double) e.getOffsetWidth()) / len;
e.removeFromParent();
charWidthPx = Math.max(a, b);
}
return charWidthPx;
cmA.extras().lineLength(columns);
cmB.extras().lineLength(columns);
}
void setShowLineNumbers(boolean b) {
@@ -815,15 +757,6 @@ public class SideBySide2 extends Screen {
return chunkManager.getLineMapper().lineOnOther(side, line);
}
private void clearActiveLine(CodeMirror cm) {
if (cm.hasActiveLine()) {
LineHandle activeLine = cm.activeLine();
cm.removeLineClass(activeLine,
LineClassWhere.WRAP, DiffTable.style.activeLine());
cm.activeLine(null);
}
}
private Runnable updateActiveLine(final CodeMirror cm) {
final CodeMirror other = otherCm(cm);
return new Runnable() {
@@ -842,22 +775,16 @@ public class SideBySide2 extends Screen {
public void run() {
LineHandle handle =
cm.getLineHandleVisualStart(cm.getCursor("end").line());
if (cm.hasActiveLine() && cm.activeLine().equals(handle)) {
if (!cm.extras().activeLine(handle)) {
return;
}
clearActiveLine(cm);
clearActiveLine(other);
cm.activeLine(handle);
cm.addLineClass(
handle, LineClassWhere.WRAP, DiffTable.style.activeLine());
LineOnOtherInfo info =
lineOnOther(cm.side(), cm.getLineNumber(handle));
if (info.isAligned()) {
LineHandle oLineHandle = other.getLineHandle(info.getLine());
other.activeLine(oLineHandle);
other.addLineClass(oLineHandle, LineClassWhere.WRAP,
DiffTable.style.activeLine());
other.extras().activeLine(other.getLineHandle(info.getLine()));
} else {
other.extras().clearActiveLine();
}
}
});
@@ -877,8 +804,8 @@ public class SideBySide2 extends Screen {
&& !clickEvent.getAltKey()
&& !clickEvent.getCtrlKey()
&& !clickEvent.getShiftKey()) {
if (!(cm.hasActiveLine() &&
cm.getLineNumber(cm.activeLine()) == line)) {
if (!(cm.extras().hasActiveLine() &&
cm.getLineNumber(cm.extras().activeLine()) == line)) {
cm.setCursor(Pos.create(line));
}
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@@ -928,10 +855,10 @@ public class SideBySide2 extends Screen {
return new Runnable() {
@Override
public void run() {
if (cmSrc.hasActiveLine()) {
if (cmSrc.extras().hasActiveLine()) {
cmDst.setCursor(Pos.create(lineOnOther(
sideSrc,
cmSrc.getLineNumber(cmSrc.activeLine())).getLine()));
cmSrc.getLineNumber(cmSrc.extras().activeLine())).getLine()));
}
cmDst.focus();
}
@@ -977,17 +904,9 @@ public class SideBySide2 extends Screen {
}
void resizeCodeMirror() {
int height = getCodeMirrorHeight();
cmA.setHeight(height);
cmB.setHeight(height);
}
private int getCodeMirrorHeight() {
int rest = Gerrit.getHeaderFooterHeight()
+ header.getOffsetHeight()
+ diffTable.getHeaderHeight()
+ 5; // Estimate
return Window.getClientHeight() - rest;
int hdr = header.getOffsetHeight() + diffTable.getHeaderHeight();
cmA.adjustHeight(hdr);
cmB.adjustHeight(hdr);
}
void syncScroll(DisplaySide masterSide) {

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2015 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.editor;
import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.Constants;
interface EditConstants extends Constants {
static final EditConstants I = GWT.create(EditConstants.class);
String closeUnsavedChanges();
String cancelUnsavedChanges();
}

View File

@@ -0,0 +1,5 @@
closeUnsavedChanges = Unsaved changes were made to this file.
cancelUnsavedChanges = Unsaved changes were made to this file.\n\
\n\
Discard unsaved changes?

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.client.editor;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.JumpKeys;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.account.DiffPreferences;
import com.google.gerrit.client.changes.ChangeApi;
@@ -30,13 +31,20 @@ import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
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.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel;
@@ -44,9 +52,13 @@ import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.ChangesHandler;
import net.codemirror.lib.Configuration;
import net.codemirror.lib.KeyMap;
import net.codemirror.lib.Pos;
import net.codemirror.mode.ModeInfo;
import net.codemirror.mode.ModeInjector;
import net.codemirror.theme.ThemeLoader;
public class EditScreen extends Screen {
interface Binder extends UiBinder<HTMLPanel, EditScreen> {}
@@ -58,12 +70,17 @@ public class EditScreen extends Screen {
private CodeMirror cm;
private String type;
@UiField Element header;
@UiField Element project;
@UiField Element filePath;
@UiField Button cancel;
@UiField Button close;
@UiField Button save;
@UiField Element editor;
private HandlerRegistration resizeHandler;
private HandlerRegistration closeHandler;
private int generation;
public EditScreen(Patch.Key patch) {
this.revision = patch.getParentKey();
this.path = patch.get();
@@ -84,9 +101,22 @@ public class EditScreen extends Screen {
super.onLoad();
CallbackGroup cmGroup = new CallbackGroup();
CodeMirror.initLibrary(cmGroup.<Void> addEmpty());
CallbackGroup group = new CallbackGroup();
if (!Patch.COMMIT_MSG.equals(path)) {
final CallbackGroup group = new CallbackGroup();
CodeMirror.initLibrary(cmGroup.add(new AsyncCallback<Void>() {
final AsyncCallback<Void> themeCallback = group.addEmpty();
@Override
public void onSuccess(Void result) {
// Load theme after CM library to ensure theme can override CSS.
ThemeLoader.loadTheme(prefs.theme(), themeCallback);
}
@Override
public void onFailure(Throwable caught) {
}
}));
if (prefs.syntaxHighlighting() && !Patch.COMMIT_MSG.equals(path)) {
final AsyncCallback<Void> modeInjectorCb = group.addEmpty();
ChangeFileApi.getContentType(revision, path,
cmGroup.add(new GerritCallback<String>() {
@@ -121,60 +151,151 @@ public class EditScreen extends Screen {
@Override
public void onShowView() {
super.onShowView();
Window.enableScrolling(false);
JumpKeys.enable(false);
if (prefs.hideTopMenu()) {
Gerrit.setHeaderVisible(false);
}
int rest = Gerrit.getHeaderFooterHeight()
+ 30; // Estimate
cm.setHeight(Window.getClientHeight() - rest);
resizeHandler = Window.addResizeHandler(new ResizeHandler() {
@Override
public void onResize(ResizeEvent event) {
cm.adjustHeight(header.getOffsetHeight());
}
});
closeHandler = Window.addWindowClosingHandler(new ClosingHandler() {
@Override
public void onWindowClosing(ClosingEvent event) {
if (!cm.isClean(generation)) {
event.setMessage(EditConstants.I.closeUnsavedChanges());
}
}
});
generation = cm.changeGeneration(true);
save.setEnabled(false);
cm.on(new ChangesHandler() {
@Override
public void handle(CodeMirror cm) {
save.setEnabled(!cm.isClean(generation));
}
});
cm.adjustHeight(header.getOffsetHeight());
cm.on("cursorActivity", updateCursorPosition());
cm.extras().showTabs(prefs.showTabs());
cm.extras().lineLength(prefs.lineLength());
cm.refresh();
cm.focus();
updateActiveLine();
}
@Override
protected void onUnload() {
super.onUnload();
if (cm != null) {
cm.getWrapperElement().removeFromParent();
}
if (resizeHandler != null) {
resizeHandler.removeHandler();
}
if (closeHandler != null) {
closeHandler.removeHandler();
}
Window.enableScrolling(true);
Gerrit.setHeaderVisible(true);
JumpKeys.enable(true);
}
@UiHandler("save")
void onSave(@SuppressWarnings("unused") ClickEvent e) {
ChangeFileApi.putContentOrMessage(revision, path, cm.getValue(),
new GerritCallback<VoidResult>() {
@Override
public void onSuccess(VoidResult result) {
Gerrit.display(PageLinks.toChangeInEditMode(
revision.getParentKey()));
}
});
save().run();
}
@UiHandler("cancel")
void onCancel(@SuppressWarnings("unused") ClickEvent e) {
@UiHandler("close")
void onClose(@SuppressWarnings("unused") ClickEvent e) {
if (cm.isClean(generation)
|| Window.confirm(EditConstants.I.cancelUnsavedChanges())) {
upToChange();
}
}
private void upToChange() {
Gerrit.display(PageLinks.toChangeInEditMode(revision.getParentKey()));
}
private void initEditor(String content) {
cm = CodeMirror.create(editor, getConfig());
cm.setValue(content);
ModeInfo mode = prefs.syntaxHighlighting()
? ModeInfo.findMode(type, path)
: null;
cm = CodeMirror.create(editor, Configuration.create()
.set("value", content)
.set("readOnly", false)
.set("cursorBlinkRate", 0)
.set("cursorHeight", 0.85)
.set("lineNumbers", true)
.set("tabSize", prefs.tabSize())
.set("lineWrapping", false)
.set("scrollbarStyle", "overlay")
.set("styleSelectedText", true)
.set("showTrailingSpace", true)
.set("keyMap", "default")
.set("theme", prefs.theme().name().toLowerCase())
.set("mode", mode != null ? mode.mode() : null));
cm.addKeyMap(KeyMap.create()
.on("Cmd-S", save())
.on("Ctrl-S", save()));
}
private Runnable updateCursorPosition() {
return new Runnable() {
@Override
public void run() {
// The rendering of active lines has to be deferred. Reflow
// caused by adding and removing styles chokes Firefox when arrow
// key (or j/k) is held down. Performance on Chrome is fine
// without the deferral.
//
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
cm.operation(new Runnable() {
@Override
public void run() {
updateActiveLine();
}
});
}
});
}
};
}
private void updateActiveLine() {
Pos p = cm.getCursor("end");
cm.extras().activeLine(cm.getLineHandleVisualStart(p.line()));
}
private Runnable save() {
return new Runnable() {
@Override
public void run() {
if (!cm.isClean(generation)) {
String text = cm.getValue();
final int g = cm.changeGeneration(false);
ChangeFileApi.putContentOrMessage(revision, path, text,
new GerritCallback<VoidResult>() {
@Override
public void onSuccess(VoidResult result) {
generation = g;
save.setEnabled(!cm.isClean(g));
}
});
}
}
};
}
private void injectMode(String type, AsyncCallback<Void> cb) {
new ModeInjector().add(type).inject(cb);
}
private Configuration getConfig() {
// TODO(davido): Retrieve user preferences from AllUsers repository
return Configuration.create()
.set("readOnly", false)
.set("cursorBlinkRate", 0)
.set("cursorHeight", 0.85)
.set("lineNumbers", true)
.set("tabSize", 4)
.set("lineWrapping", false)
.set("styleSelectedText", true)
.set("showTrailingSpace", true)
.set("keyMap", "default")
.set("mode", type);
}
}

View File

@@ -31,7 +31,9 @@ limitations under the License.
}
.headerButtons button:disabled {
background-color: #999;
background-color: #ddd;
font-weight: normal;
cursor: default;
}
.headerButtons button {
@@ -62,13 +64,13 @@ limitations under the License.
}
</ui:style>
<g:HTMLPanel>
<div class='{style.headerLine}'>
<div class='{style.headerLine}' ui:field='header'>
<div class='{style.headerButtons}'>
<g:Button ui:field='cancel'
<g:Button ui:field='close'
styleName=''
title='Cancel'>
title='Close file and return to change'>
<ui:attribute name='title'/>
<div><ui:msg>Cancel</ui:msg></div>
<div><ui:msg>Close</ui:msg></div>
</g:Button>
<g:Button ui:field='save'
styleName='{style.save}'

View File

@@ -14,11 +14,14 @@
package net.codemirror.lib;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.diff.DisplaySide;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import net.codemirror.lib.TextMarker.FromTo;
@@ -37,7 +40,23 @@ public class CodeMirror extends JavaScriptObject {
Loader.initLibrary(cb);
}
public static native CodeMirror create(Element p, Configuration cfg) /*-{
interface Style extends CssResource {
String activeLine();
String showTabs();
String margin();
}
static Style style() {
return Lib.I.style();
}
public static CodeMirror create(Element p, Configuration cfg) {
CodeMirror cm = newCM(p, cfg);
Extras.attach(cm);
return cm;
}
private static native CodeMirror newCM(Element p, Configuration cfg) /*-{
return $wnd.CodeMirror(p, cfg);
}-*/;
@@ -62,10 +81,25 @@ public class CodeMirror extends JavaScriptObject {
public final native String getValue() /*-{ return this.getValue() }-*/;
public final native void setValue(String v) /*-{ this.setValue(v) }-*/;
public final native int changeGeneration(boolean closeEvent)
/*-{ return this.changeGeneration(closeEvent) }-*/;
public final native boolean isClean(int generation)
/*-{ return this.isClean(generation) }-*/;
public final native void setWidth(double w) /*-{ this.setSize(w, null) }-*/;
public final native void setWidth(String w) /*-{ this.setSize(w, null) }-*/;
public final native void setHeight(double h) /*-{ this.setSize(null, h) }-*/;
public final native void setHeight(String h) /*-{ this.setSize(null, h) }-*/;
public final int getHeight() {
return getWrapperElement().getClientHeight();
}
public final void adjustHeight(int localHeader) {
int rest = Gerrit.getHeaderFooterHeight()
+ localHeader
+ 5; // Estimate
setHeight(Window.getClientHeight() - rest);
}
public final native String getLine(int n) /*-{ return this.getLine(n) }-*/;
public final native double barHeight() /*-{ return this.display.barHeight }-*/;
public final native double barWidth() /*-{ return this.display.barWidth }-*/;
@@ -226,6 +260,13 @@ public class CodeMirror extends JavaScriptObject {
}))
}-*/;
public final native void on(ChangesHandler handler) /*-{
this.on('changes', $entry(function(cm, o) {
handler.@net.codemirror.lib.CodeMirror.ChangesHandler::handle(
Lnet/codemirror/lib/CodeMirror;)(cm);
}))
}-*/;
public final native void setCursor(Pos p) /*-{ this.setCursor(p) }-*/;
public final native Pos getCursor() /*-{ return this.getCursor() }-*/;
public final native Pos getCursor(String start) /*-{
@@ -245,18 +286,6 @@ public class CodeMirror extends JavaScriptObject {
return this.somethingSelected()
}-*/;
public final native boolean hasActiveLine() /*-{
return !!this.state.activeLine
}-*/;
public final native LineHandle activeLine() /*-{
return this.state.activeLine
}-*/;
public final native void activeLine(LineHandle line) /*-{
this.state.activeLine = line
}-*/;
public final native void addKeyMap(KeyMap map) /*-{ this.addKeyMap(map) }-*/;
public final native void removeKeyMap(KeyMap map) /*-{ this.removeKeyMap(map) }-*/;
@@ -321,11 +350,13 @@ public class CodeMirror extends JavaScriptObject {
return this;
}-*/;
public final native DisplaySide side() /*-{ return this._sbs2_side }-*/;
public final native CodeMirror side(DisplaySide side) /*-{
this._sbs2_side = side;
return this;
}-*/;
public final DisplaySide side() {
return extras().side();
}
public final Extras extras() {
return Extras.get(this);
}
protected CodeMirror() {
}
@@ -367,4 +398,8 @@ public class CodeMirror extends JavaScriptObject {
public interface BeforeSelectionChangeHandler {
public void handle(CodeMirror instance, Pos anchor, Pos head);
}
public interface ChangesHandler {
public void handle(CodeMirror instance);
}
}

View File

@@ -0,0 +1,143 @@
// Copyright (C) 2015 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 static com.google.gwt.dom.client.Style.Display.INLINE_BLOCK;
import static com.google.gwt.dom.client.Style.Unit.PX;
import static net.codemirror.lib.CodeMirror.style;
import static net.codemirror.lib.CodeMirror.LineClassWhere.WRAP;
import com.google.gerrit.client.diff.DisplaySide;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import net.codemirror.lib.CodeMirror.LineHandle;
import java.util.Objects;
/** Additional features added to CodeMirror by Gerrit Code Review. */
public class Extras {
static final native Extras get(CodeMirror c) /*-{ return c.gerritExtras }-*/;
private static final native void set(CodeMirror c, Extras e)
/*-{ c.gerritExtras = e }-*/;
static void attach(CodeMirror c) {
set(c, new Extras(c));
}
private final CodeMirror cm;
private Element margin;
private DisplaySide side;
private double charWidthPx;
private double lineHeightPx;
private LineHandle activeLine;
private Extras(CodeMirror cm) {
this.cm = cm;
}
public DisplaySide side() {
return side;
}
public void side(DisplaySide s) {
side = s;
}
public double charWidthPx() {
if (charWidthPx <= 1) {
int len = 100;
StringBuilder s = new StringBuilder();
for (int i = 0; i < len; i++) {
s.append('m');
}
Element e = DOM.createSpan();
e.getStyle().setDisplay(INLINE_BLOCK);
e.setInnerText(s.toString());
cm.measure().appendChild(e);
charWidthPx = ((double) e.getOffsetWidth()) / len;
e.removeFromParent();
}
return charWidthPx;
}
public double lineHeightPx() {
if (lineHeightPx <= 1) {
Element p = DOM.createDiv();
int lines = 1;
for (int i = 0; i < lines; i++) {
Element e = DOM.createDiv();
p.appendChild(e);
Element pre = DOM.createElement("pre");
pre.setInnerText("gqyŚŻŹŃ");
e.appendChild(pre);
}
cm.measure().appendChild(p);
lineHeightPx = ((double) p.getOffsetHeight()) / lines;
p.removeFromParent();
}
return lineHeightPx;
}
public void lineLength(int columns) {
if (margin == null) {
margin = DOM.createDiv();
margin.setClassName(style().margin());
cm.mover().appendChild(margin);
}
margin.getStyle().setMarginLeft(columns * charWidthPx(), PX);
}
public void showTabs(boolean show) {
Element e = cm.getWrapperElement();
if (show) {
e.addClassName(style().showTabs());
} else {
e.removeClassName(style().showTabs());
}
}
public final boolean hasActiveLine() {
return activeLine != null;
}
public final LineHandle activeLine() {
return activeLine;
}
public final boolean activeLine(LineHandle line) {
if (Objects.equals(activeLine, line)) {
return false;
}
if (activeLine != null) {
cm.removeLineClass(activeLine, WRAP, style().activeLine());
}
activeLine = line;
cm.addLineClass(activeLine, WRAP, style().activeLine());
return true;
}
public final void clearActiveLine() {
if (activeLine != null) {
cm.removeLineClass(activeLine, WRAP, style().activeLine());
activeLine = null;
}
}
}

View File

@@ -29,4 +29,7 @@ interface Lib extends ClientBundle {
@Source("cm.js")
@DoNotEmbed
DataResource js();
@Source("style.css")
CodeMirror.Style style();
}

View File

@@ -56,6 +56,7 @@ public class Loader {
@Override
public void onSuccess(TextResource resource) {
StyleInjector.inject(resource.getText());
Lib.I.style().ensureInjected();
cb.onSuccess(null);
}

View File

@@ -0,0 +1,83 @@
/* Copyright (C) 2015 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.
*/
@external .CodeMirror;
@external .CodeMirror-lines;
@external .CodeMirror-linenumber;
@external .CodeMirror-overlayscroll-horizontal;
@external .CodeMirror-overlayscroll-vertical;
@external .cm-tab;
@external .cm-searching;
@external .cm-trailingspace;
/* Reduce margins around CodeMirror to save space. */
.CodeMirror-lines {
padding: 0;
}
.CodeMirror pre {
padding: 0;
line-height: normal;
}
/* Minimum scrollbar bubble size even on large files. */
.CodeMirror-overlayscroll-horizontal div {
min-width: 25px;
}
.CodeMirror-overlayscroll-vertical div {
min-height: 25px;
}
/* Stack the scrollbar so annotations can receive clicks. */
.CodeMirror-overlayscroll-vertical {
z-index: inherit;
}
.CodeMirror-overlayscroll-horizontal div,
.CodeMirror-overlayscroll-vertical div {
background-color: rgba(128, 128, 128, 0.50);
z-index: 8;
}
/* Highlight current line number in the line gutter. */
.activeLine .CodeMirror-linenumber {
background-color: #bcf !important;
color: #000;
}
.showTabs .cm-tab:before {
position: absolute;
content: "\00bb";
color: #f00;
}
.cm-searching {
background-color: #ffa;
}
.cm-trailingspace {
background-color: red;
}
/* Line length margin displayed at NN columns to provide
* a visual guide for length of any single line of code.
*/
.margin {
position: absolute;
top: 0;
bottom: 0;
width: 0;
border-right: 1px dashed #ffa500;
z-index: 2;
cursor: text;
}