blob: 7bd980415e0f11e8e112582fdff537df2fcdeadb [file] [log] [blame]
// 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 java.lang.Double.POSITIVE_INFINITY;
import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.diff.UnifiedChunkManager.LineRegionInfo;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.projects.ConfigInfoCache;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwt.user.client.ui.InlineHTML;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import java.util.Collections;
import java.util.List;
import net.codemirror.lib.CodeMirror;
import net.codemirror.lib.CodeMirror.LineHandle;
import net.codemirror.lib.Configuration;
import net.codemirror.lib.Pos;
import net.codemirror.lib.ScrollInfo;
public class Unified extends DiffScreen {
interface Binder extends UiBinder<FlowPanel, Unified> {}
private static final Binder uiBinder = GWT.create(Binder.class);
@UiField(provided = true)
UnifiedTable diffTable;
private CodeMirror cm;
private UnifiedChunkManager chunkManager;
private UnifiedCommentManager commentManager;
private boolean autoHideDiffTableHeader;
public Unified(
@Nullable Project.NameKey project,
DiffObject base,
DiffObject revision,
String path,
DisplaySide startSide,
int startLine) {
super(project, base, revision, path, startSide, startLine, DiffView.UNIFIED_DIFF);
diffTable = new UnifiedTable(this, base, revision, path);
add(uiBinder.createAndBindUi(this));
addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
}
@Override
ScreenLoadCallback<ConfigInfoCache.Entry> getScreenLoadCallback(
final CommentsCollections comments) {
return new ScreenLoadCallback<ConfigInfoCache.Entry>(Unified.this) {
@Override
protected void preDisplay(ConfigInfoCache.Entry result) {
commentManager =
new UnifiedCommentManager(
Unified.this,
getProject(),
base,
revision,
path,
result.getCommentLinkProcessor(),
getChangeStatus().isOpen());
setTheme(result.getTheme());
display(comments);
header.setupPrevNextFiles(comments);
}
};
}
@Override
public void onShowView() {
super.onShowView();
operation(
() -> {
resizeCodeMirror();
cm.refresh();
});
setLineLength(Patch.COMMIT_MSG.equals(path) ? 72 : prefs.lineLength());
diffTable.refresh();
if (getStartLine() == 0) {
DiffChunkInfo d = chunkManager.getFirst();
if (d != null) {
if (d.isEdit() && d.getSide() == DisplaySide.A) {
setStartSide(DisplaySide.B);
} else {
setStartSide(d.getSide());
}
setStartLine(chunkManager.getCmLine(d.getStart(), d.getSide()) + 1);
}
}
if (getStartSide() != null && getStartLine() > 0) {
cm.scrollToLine(chunkManager.getCmLine(getStartLine() - 1, getStartSide()));
cm.focus();
} else {
cm.setCursor(Pos.create(0));
cm.focus();
}
if (Gerrit.isSignedIn() && prefs.autoReview()) {
header.autoReview();
}
prefetchNextFile();
}
@Override
void registerCmEvents(CodeMirror cm) {
super.registerCmEvents(cm);
cm.on(
"scroll",
() -> {
ScrollInfo si = cm.getScrollInfo();
if (autoHideDiffTableHeader) {
updateDiffTableHeader(si);
}
});
maybeRegisterRenderEntireFileKeyMap(cm);
}
@Override
public void registerKeys() {
super.registerKeys();
registerHandlers();
}
@Override
FocusHandler getFocusHandler() {
return new FocusHandler() {
@Override
public void onFocus(FocusEvent event) {
cm.focus();
}
};
}
private void display(CommentsCollections comments) {
DiffInfo diff = getDiff();
setThemeStyles(prefs.theme().isDark());
setShowIntraline(prefs.intralineDifference());
if (prefs.showLineNumbers()) {
diffTable.addStyleName(Resources.I.diffTableStyle().showLineNumbers());
}
cm =
newCm(diff.metaA() == null ? diff.metaB() : diff.metaA(), diff.textUnified(), diffTable.cm);
setShowTabs(prefs.showTabs());
chunkManager = new UnifiedChunkManager(this, cm, diffTable.scrollbar);
operation(
() -> {
// Estimate initial CodeMirror height, fixed up in onShowView.
int height = Window.getClientHeight() - (Gerrit.getHeaderFooterHeight() + 18);
cm.setHeight(height);
render(diff);
commentManager.render(comments, prefs.expandAllComments());
skipManager.render(prefs.context(), diff);
});
registerCmEvents(cm);
setPrefsAction(new PreferencesAction(this, prefs));
header.init(getPrefsAction(), getSideBySideDiffLink(), diff.unifiedWebLinks());
setAutoHideDiffHeader(prefs.autoHideDiffTableHeader());
setupSyntaxHighlighting();
}
private List<InlineHyperlink> getSideBySideDiffLink() {
InlineHyperlink toSideBySideDiffLink = new InlineHyperlink();
toSideBySideDiffLink.setHTML(
new ImageResourceRenderer().render(Gerrit.RESOURCES.sideBySideDiff()));
toSideBySideDiffLink.setTargetHistoryToken(
Dispatcher.toSideBySide(getProject(), base, revision, path));
toSideBySideDiffLink.setTitle(PatchUtil.C.sideBySideDiff());
return Collections.singletonList(toSideBySideDiffLink);
}
@Override
CodeMirror newCm(DiffInfo.FileMeta meta, String contents, Element parent) {
JsArrayString gutters = JavaScriptObject.createArray().cast();
gutters.push(UnifiedTable.style.lineNumbersLeft());
gutters.push(UnifiedTable.style.lineNumbersRight());
return CodeMirror.create(
parent,
Configuration.create()
.set("cursorBlinkRate", prefs.cursorBlinkRate())
.set("cursorHeight", 0.85)
.set("gutters", gutters)
.set("inputStyle", "textarea")
.set("keyMap", "vim_ro")
.set("lineNumbers", false)
.set("lineWrapping", prefs.lineWrapping())
.set("matchBrackets", prefs.matchBrackets())
.set("mode", getFileSize() == FileSize.SMALL ? getContentType(meta) : null)
.set("readOnly", true)
.set("scrollbarStyle", "overlay")
.set("styleSelectedText", true)
.set("showTrailingSpace", prefs.showWhitespaceErrors())
.set("tabSize", prefs.tabSize())
.set("theme", prefs.theme().name().toLowerCase())
.set("value", meta != null ? contents : "")
.set("viewportMargin", renderEntireFile() ? POSITIVE_INFINITY : 10));
}
@Override
void setShowLineNumbers(boolean b) {
super.setShowLineNumbers(b);
cm.refresh();
}
private void setLineNumber(DisplaySide side, int cmLine, Integer line, String styleName) {
SafeHtml html = SafeHtml.asis(line != null ? line.toString() : "&nbsp;");
InlineHTML gutter = new InlineHTML(html);
diffTable.add(gutter);
gutter.setStyleName(styleName);
cm.setGutterMarker(
cmLine,
side == DisplaySide.A
? UnifiedTable.style.lineNumbersLeft()
: UnifiedTable.style.lineNumbersRight(),
gutter.getElement());
}
void setLineNumber(DisplaySide side, int cmLine, int line) {
setLineNumber(side, cmLine, line, UnifiedTable.style.unifiedLineNumber());
}
void setLineNumberEmpty(DisplaySide side, int cmLine) {
setLineNumber(side, cmLine, null, UnifiedTable.style.unifiedLineNumberEmpty());
}
@Override
void setSyntaxHighlighting(boolean b) {
final DiffInfo diff = getDiff();
if (b) {
injectMode(
diff,
new AsyncCallback<Void>() {
@Override
public void onSuccess(Void result) {
if (prefs.syntaxHighlighting()) {
cm.setOption(
"mode", getContentType(diff.metaA() == null ? diff.metaB() : diff.metaA()));
}
}
@Override
public void onFailure(Throwable caught) {
prefs.syntaxHighlighting(false);
}
});
} else {
cm.setOption("mode", (String) null);
}
}
@Override
void setAutoHideDiffHeader(boolean autoHide) {
if (autoHide) {
updateDiffTableHeader(cm.getScrollInfo());
} else {
diffTable.setHeaderVisible(true);
}
autoHideDiffTableHeader = autoHide;
}
private void updateDiffTableHeader(ScrollInfo si) {
if (si.top() == 0) {
diffTable.setHeaderVisible(true);
} else if (si.top() > 0.5 * si.clientHeight()) {
diffTable.setHeaderVisible(false);
}
}
@Override
Runnable updateActiveLine(CodeMirror cm) {
return () -> {
// 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(
() -> {
LineHandle handle = cm.getLineHandleVisualStart(cm.getCursor("end").line());
cm.extras().activeLine(handle);
});
};
}
@Override
CodeMirror getCmFromSide(DisplaySide side) {
return cm;
}
@Override
int getCmLine(int line, DisplaySide side) {
return chunkManager.getCmLine(line, side);
}
LineRegionInfo getLineRegionInfoFromCmLine(int cmLine) {
return chunkManager.getLineRegionInfoFromCmLine(cmLine);
}
@Override
void operation(Runnable apply) {
cm.operation(apply::run);
}
@Override
CodeMirror[] getCms() {
return new CodeMirror[] {cm};
}
@Override
UnifiedTable getDiffTable() {
return diffTable;
}
@Override
UnifiedChunkManager getChunkManager() {
return chunkManager;
}
@Override
UnifiedCommentManager getCommentManager() {
return commentManager;
}
@Override
boolean isSideBySide() {
return false;
}
@Override
String getLineNumberClassName() {
return UnifiedTable.style.unifiedLineNumber();
}
}