blob: 8935e3658bb90aff93b594869a22df224489cf4b [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 impl ied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.diff;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.WHOLE_FILE_CONTEXT;
import static java.lang.Double.POSITIVE_INFINITY;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.DiffPreferences;
import com.google.gerrit.client.change.ChangeScreen;
import com.google.gerrit.client.change.FileTable;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.diff.DiffInfo.FileMeta;
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
import com.google.gerrit.client.info.ChangeInfo;
import com.google.gerrit.client.info.ChangeInfo.CommitInfo;
import com.google.gerrit.client.info.ChangeInfo.EditInfo;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.info.FileInfo;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.projects.ConfigInfoCache;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
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.event.dom.client.FocusHandler;
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.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
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.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
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.LineHandle;
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;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
/** Base class for SideBySide and Unified */
abstract class DiffScreen extends Screen {
private static final KeyMap RENDER_ENTIRE_FILE_KEYMAP = KeyMap.create()
.propagate("Ctrl-F").propagate("Ctrl-G").propagate("Shift-Ctrl-G");
enum FileSize {
SMALL(0),
LARGE(500),
HUGE(4000);
final int lines;
FileSize(int n) {
this.lines = n;
}
}
private final Change.Id changeId;
final PatchSet.Id base;
final PatchSet.Id revision;
final String path;
final DiffPreferences prefs;
final SkipManager skipManager;
private DisplaySide startSide;
private int startLine;
private Change.Status changeStatus;
private HandlerRegistration resizeHandler;
private DiffInfo diff;
private FileSize fileSize;
private EditInfo edit;
private KeyCommandSet keysNavigation;
private KeyCommandSet keysAction;
private KeyCommandSet keysComment;
private List<HandlerRegistration> handlers;
private PreferencesAction prefsAction;
private int reloadVersionId;
private int parents;
@UiField(provided = true)
Header header;
DiffScreen(
PatchSet.Id base,
PatchSet.Id revision,
String path,
DisplaySide startSide,
int startLine,
DiffView diffScreenType) {
this.base = base;
this.revision = revision;
this.changeId = revision.getParentKey();
this.path = path;
this.startSide = startSide;
this.startLine = startLine;
prefs = DiffPreferences.create(Gerrit.getDiffPreferences());
handlers = new ArrayList<>(6);
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
header = new Header(
keysNavigation, base, revision, path, diffScreenType, prefs);
skipManager = new SkipManager(this);
}
@Override
protected void onInitUI() {
super.onInitUI();
setHeaderVisible(false);
setWindowTitle(FileInfo.getFileName(path));
}
@Override
protected void onLoad() {
super.onLoad();
CallbackGroup group1 = new CallbackGroup();
final CallbackGroup group2 = new CallbackGroup();
CodeMirror.initLibrary(group1.add(new AsyncCallback<Void>() {
final AsyncCallback<Void> themeCallback = group2.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) {
}
}));
DiffApi.diff(revision, path)
.base(base)
.wholeFile()
.intraline(prefs.intralineDifference())
.ignoreWhitespace(prefs.ignoreWhitespace())
.get(group1.addFinal(new GerritCallback<DiffInfo>() {
final AsyncCallback<Void> modeInjectorCb = group2.addEmpty();
@Override
public void onSuccess(DiffInfo diffInfo) {
diff = diffInfo;
fileSize = bucketFileSize(diffInfo);
if (prefs.syntaxHighlighting()) {
if (fileSize.compareTo(FileSize.SMALL) > 0) {
modeInjectorCb.onSuccess(null);
} else {
injectMode(diffInfo, modeInjectorCb);
}
} else {
modeInjectorCb.onSuccess(null);
}
}
}));
if (Gerrit.isSignedIn()) {
ChangeApi.edit(changeId.get(), group2.add(
new AsyncCallback<EditInfo>() {
@Override
public void onSuccess(EditInfo result) {
edit = result;
}
@Override
public void onFailure(Throwable caught) {
}
}));
}
final CommentsCollections comments =
new CommentsCollections(base, revision, path);
comments.load(group2);
countParents(group2);
RestApi call = ChangeApi.detail(changeId.get());
ChangeList.addOptions(call, EnumSet.of(
ListChangesOption.ALL_REVISIONS));
call.get(group2.add(new AsyncCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo info) {
changeStatus = info.status();
info.revisions().copyKeysIntoChildren("name");
if (edit != null) {
edit.setName(edit.commit().commit());
info.setEdit(edit);
info.revisions().put(edit.name(), RevisionInfo.fromEdit(edit));
}
String currentRevision = info.currentRevision();
boolean current = currentRevision != null &&
revision.get() == info.revision(currentRevision)._number();
JsArray<RevisionInfo> list = info.revisions().values();
RevisionInfo.sortRevisionInfoByNumber(list);
getDiffTable().set(prefs, list, parents, diff, edit != null, current,
changeStatus.isOpen(), diff.binary());
header.setChangeInfo(info);
}
@Override
public void onFailure(Throwable caught) {
}
}));
ConfigInfoCache.get(changeId, group2.addFinal(
getScreenLoadCallback(comments)));
}
private void countParents(CallbackGroup cbg) {
ChangeApi.revision(changeId.get(), revision.getId())
.view("commit")
.get(cbg.add(new AsyncCallback<CommitInfo>() {
@Override
public void onSuccess(CommitInfo info) {
parents = info.parents().length();
}
@Override
public void onFailure(Throwable caught) {
parents = 0;
}
}));
}
@Override
public void onShowView() {
super.onShowView();
Window.enableScrolling(false);
if (prefs.hideTopMenu()) {
Gerrit.setHeaderVisible(false);
}
resizeHandler = Window.addResizeHandler(new ResizeHandler() {
@Override
public void onResize(ResizeEvent event) {
resizeCodeMirror();
}
});
}
KeyCommandSet getKeysNavigation() {
return keysNavigation;
}
KeyCommandSet getKeysAction() {
return keysAction;
}
@Override
protected void onUnload() {
super.onUnload();
removeKeyHandlerRegistrations();
if (getCommentManager() != null) {
CallbackGroup group = new CallbackGroup();
getCommentManager().saveAllDrafts(group);
group.done();
}
if (resizeHandler != null) {
resizeHandler.removeHandler();
resizeHandler = null;
}
for (CodeMirror cm : getCms()) {
if (cm != null) {
cm.getWrapperElement().removeFromParent();
}
}
if (prefsAction != null) {
prefsAction.hide();
}
Window.enableScrolling(true);
Gerrit.setHeaderVisible(true);
}
private void removeKeyHandlerRegistrations() {
for (HandlerRegistration h : handlers) {
h.removeHandler();
}
handlers.clear();
}
void registerCmEvents(final CodeMirror cm) {
cm.on("cursorActivity", updateActiveLine(cm));
cm.on("focus", updateActiveLine(cm));
KeyMap keyMap = KeyMap.create()
.on("A", upToChange(true))
.on("U", upToChange(false))
.on("'['", header.navigate(Direction.PREV))
.on("']'", header.navigate(Direction.NEXT))
.on("R", header.toggleReviewed())
.on("O", getCommentManager().toggleOpenBox(cm))
.on("N", maybeNextVimSearch(cm))
.on("Ctrl-Alt-E", openEditScreen(cm))
.on("P", getChunkManager().diffChunkNav(cm, Direction.PREV))
.on("Shift-M", header.reviewedAndNext())
.on("Shift-N", maybePrevVimSearch(cm))
.on("Shift-P", getCommentManager().commentNav(cm, Direction.PREV))
.on("Shift-O", getCommentManager().openCloseAll(cm))
.on("I", new Runnable() {
@Override
public void run() {
switch (getIntraLineStatus()) {
case OFF:
case OK:
toggleShowIntraline();
break;
case FAILURE:
case TIMEOUT:
default:
break;
}
}
})
.on("','", new Runnable() {
@Override
public void run() {
prefsAction.show();
}
})
.on("Shift-/", new Runnable() {
@Override
public void run() {
new ShowHelpCommand().onKeyPress(null);
}
})
.on("Space", new Runnable() {
@Override
public void run() {
cm.vim().handleKey("<C-d>");
}
})
.on("Shift-Space", new Runnable() {
@Override
public void run() {
cm.vim().handleKey("<C-u>");
}
})
.on("Ctrl-F", new Runnable() {
@Override
public void run() {
cm.execCommand("find");
}
})
.on("Ctrl-G", new Runnable() {
@Override
public void run() {
cm.execCommand("findNext");
}
})
.on("Enter", maybeNextCmSearch(cm))
.on("Shift-Ctrl-G", new Runnable() {
@Override
public void run() {
cm.execCommand("findPrev");
}
})
.on("Shift-Enter", new Runnable() {
@Override
public void run() {
cm.execCommand("findPrev");
}
})
.on("Esc", new Runnable() {
@Override
public void run() {
cm.setCursor(cm.getCursor());
cm.execCommand("clearSearch");
cm.vim().handleEx("nohlsearch");
}
})
.on("Ctrl-A", new Runnable() {
@Override
public void run() {
cm.execCommand("selectAll");
}
})
.on("G O", new Runnable() {
@Override
public void run() {
Gerrit.display(PageLinks.toChangeQuery("status:open"));
}
})
.on("G M", new Runnable() {
@Override
public void run() {
Gerrit.display(PageLinks.toChangeQuery("status:merged"));
}
})
.on("G A", new Runnable() {
@Override
public void run() {
Gerrit.display(PageLinks.toChangeQuery("status:abandoned"));
}
});
if (Gerrit.isSignedIn()) {
keyMap.on("G I", new Runnable() {
@Override
public void run() {
Gerrit.display(PageLinks.MINE);
}
})
.on("G D", new Runnable() {
@Override
public void run() {
Gerrit.display(PageLinks.toChangeQuery("owner:self is:draft"));
}
})
.on("G C", new Runnable() {
@Override
public void run() {
Gerrit.display(PageLinks.toChangeQuery("has:draft"));
}
})
.on("G W", new Runnable() {
@Override
public void run() {
Gerrit.display(
PageLinks.toChangeQuery("is:watched status:open"));
}
})
.on("G S", new Runnable() {
@Override
public void run() {
Gerrit.display(PageLinks.toChangeQuery("is:starred"));
}
});
}
if (revision.get() != 0) {
cm.on("beforeSelectionChange", onSelectionChange(cm));
cm.on("gutterClick", onGutterClick(cm));
keyMap.on("C", getCommentManager().newDraftCallback(cm));
}
CodeMirror.normalizeKeyMap(keyMap); // Needed to for multi-stroke keymaps
cm.addKeyMap(keyMap);
}
void maybeRegisterRenderEntireFileKeyMap(CodeMirror cm) {
if (renderEntireFile()) {
cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
}
}
private BeforeSelectionChangeHandler onSelectionChange(final CodeMirror cm) {
return new BeforeSelectionChangeHandler() {
private InsertCommentBubble bubble;
@Override
public void handle(CodeMirror cm, Pos anchor, Pos head) {
if (anchor.equals(head)) {
if (bubble != null) {
bubble.setVisible(false);
}
return;
} else if (bubble == null) {
init(anchor);
} else {
bubble.setVisible(true);
}
bubble.position(cm.charCoords(head, "local"));
}
private void init(Pos anchor) {
bubble = new InsertCommentBubble(getCommentManager(), cm);
add(bubble);
cm.addWidget(anchor, bubble.getElement());
}
};
}
@Override
public void registerKeys() {
super.registerKeys();
keysNavigation.add(new UpToChangeCommand(revision, 0, 'u'));
keysNavigation.add(
new NoOpKeyCommand(0, 'j', PatchUtil.C.lineNext()),
new NoOpKeyCommand(0, 'k', PatchUtil.C.linePrev()));
keysNavigation.add(
new NoOpKeyCommand(0, 'n', PatchUtil.C.chunkNext()),
new NoOpKeyCommand(0, 'p', PatchUtil.C.chunkPrev()));
keysNavigation.add(
new NoOpKeyCommand(KeyCommand.M_SHIFT, 'n', PatchUtil.C.commentNext()),
new NoOpKeyCommand(KeyCommand.M_SHIFT, 'p', PatchUtil.C.commentPrev()));
keysNavigation.add(
new NoOpKeyCommand(KeyCommand.M_CTRL, 'f', Gerrit.C.keySearch()));
keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
keysAction.add(new NoOpKeyCommand(0, KeyCodes.KEY_ENTER,
PatchUtil.C.expandComment()));
keysAction.add(new NoOpKeyCommand(0, 'o', PatchUtil.C.expandComment()));
keysAction.add(new NoOpKeyCommand(
KeyCommand.M_SHIFT, 'o', PatchUtil.C.expandAllCommentsOnCurrentLine()));
if (Gerrit.isSignedIn()) {
keysAction.add(new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
@Override
public void onKeyPress(KeyPressEvent event) {
header.toggleReviewed().run();
}
});
keysAction.add(new NoOpKeyCommand(KeyCommand.M_CTRL | KeyCommand.M_ALT,
'e', Gerrit.C.keyEditor()));
}
keysAction.add(new KeyCommand(
KeyCommand.M_SHIFT, 'm', PatchUtil.C.markAsReviewedAndGoToNext()) {
@Override
public void onKeyPress(KeyPressEvent event) {
header.reviewedAndNext().run();
}
});
keysAction.add(new KeyCommand(0, 'a', PatchUtil.C.openReply()) {
@Override
public void onKeyPress(KeyPressEvent event) {
upToChange(true).run();
}
});
keysAction.add(new KeyCommand(0, ',', PatchUtil.C.showPreferences()) {
@Override
public void onKeyPress(KeyPressEvent event) {
prefsAction.show();
}
});
if (getIntraLineStatus() == DiffInfo.IntraLineStatus.OFF
|| getIntraLineStatus() == DiffInfo.IntraLineStatus.OK) {
keysAction.add(new KeyCommand(0, 'i', PatchUtil.C.toggleIntraline()) {
@Override
public void onKeyPress(KeyPressEvent event) {
toggleShowIntraline();
}
});
}
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;
}
}
void registerHandlers() {
removeKeyHandlerRegistrations();
handlers.add(GlobalKey.add(this, keysAction));
handlers.add(GlobalKey.add(this, keysNavigation));
if (keysComment != null) {
handlers.add(GlobalKey.add(this, keysComment));
}
handlers.add(ShowHelpCommand.addFocusHandler(getFocusHandler()));
}
void setupSyntaxHighlighting() {
if (prefs.syntaxHighlighting() && fileSize.compareTo(FileSize.SMALL) > 0) {
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
@Override
public boolean execute() {
if (prefs.syntaxHighlighting() && isAttached()) {
setSyntaxHighlighting(prefs.syntaxHighlighting());
}
return false;
}
}, 250);
}
}
abstract CodeMirror newCm(
DiffInfo.FileMeta meta, String contents, Element parent);
void render(DiffInfo diff) {
header.setNoDiff(diff);
getChunkManager().render(diff);
}
void setShowLineNumbers(boolean b) {
if (b) {
getDiffTable().addStyleName(
Resources.I.diffTableStyle().showLineNumbers());
} else {
getDiffTable().removeStyleName(
Resources.I.diffTableStyle().showLineNumbers());
}
}
void setShowIntraline(boolean b) {
if (b && getIntraLineStatus() == DiffInfo.IntraLineStatus.OFF) {
reloadDiffInfo();
} else if (b) {
getDiffTable().removeStyleName(Resources.I.diffTableStyle().noIntraline());
} else {
getDiffTable().addStyleName(Resources.I.diffTableStyle().noIntraline());
}
}
private void toggleShowIntraline() {
prefs.intralineDifference(!prefs.intralineDifference());
setShowIntraline(prefs.intralineDifference());
prefsAction.update();
}
abstract void setSyntaxHighlighting(boolean b);
void setContext(final int context) {
operation(new Runnable() {
@Override
public void run() {
skipManager.removeAll();
skipManager.render(context, diff);
updateRenderEntireFile();
}
});
}
private int adjustCommitMessageLine(int line) {
/* When commit messages are shown in the diff screen they include
a header block that looks like this:
1 Parent: deadbeef (Parent commit title)
2 Author: A. U. Thor <author@example.com>
3 AuthorDate: 2015-02-27 19:20:52 +0900
4 Commit: A. U. Thor <author@example.com>
5 CommitDate: 2015-02-27 19:20:52 +0900
6 [blank line]
7 Commit message title
8
9 Commit message body
10 ...
11 ...
If the commit is a merge commit, both parent commits are listed in the
first two lines instead of a 'Parent' line:
1 Merge Of: deadbeef (Parent 1 commit title)
2 beefdead (Parent 2 commit title)
*/
// Offset to compensate for header lines until the blank line
// after 'CommitDate'
int offset = 6;
// Adjust for merge commits, which have two parent lines
if (diff.textB().startsWith("Merge")) {
offset += 1;
}
// If the cursor is inside the header line, reset to the first line of the
// commit message. Otherwise if the cursor is on an actual line of the commit
// message, adjust the line number to compensate for the header lines, so the
// focus is on the correct line.
if (line <= offset) {
return 1;
}
return line - offset;
}
private Runnable openEditScreen(final CodeMirror cm) {
return new Runnable() {
@Override
public void run() {
LineHandle handle = cm.extras().activeLine();
int line = cm.getLineNumber(handle) + 1;
if (Patch.COMMIT_MSG.equals(path)) {
line = adjustCommitMessageLine(line);
}
String token = Dispatcher.toEditScreen(revision, path, line);
if (!Gerrit.isSignedIn()) {
Gerrit.doSignIn(token);
} else {
Gerrit.display(token);
}
}
};
}
void updateRenderEntireFile() {
boolean entireFile = renderEntireFile();
for (CodeMirror cm : getCms()) {
cm.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
if (entireFile) {
cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
}
cm.setOption("viewportMargin", entireFile ? POSITIVE_INFINITY : 10);
}
}
void resizeCodeMirror() {
int height = header.getOffsetHeight() + getDiffTable().getHeaderHeight();
for (CodeMirror cm : getCms()) {
cm.adjustHeight(height);
}
}
abstract ChunkManager getChunkManager();
abstract CommentManager getCommentManager();
Change.Status getChangeStatus() {
return changeStatus;
}
int getStartLine() {
return startLine;
}
void setStartLine(int startLine) {
this.startLine = startLine;
}
DisplaySide getStartSide() {
return startSide;
}
void setStartSide(DisplaySide startSide) {
this.startSide = startSide;
}
DiffInfo getDiff() {
return diff;
}
FileSize getFileSize() {
return fileSize;
}
PreferencesAction getPrefsAction() {
return prefsAction;
}
void setPrefsAction(PreferencesAction prefsAction) {
this.prefsAction = prefsAction;
}
abstract void operation(final Runnable apply);
private Runnable upToChange(final boolean openReplyBox) {
return new Runnable() {
@Override
public void run() {
CallbackGroup group = new CallbackGroup();
getCommentManager().saveAllDrafts(group);
group.done();
group.addListener(new GerritCallback<Void>() {
@Override
public void onSuccess(Void result) {
String b = base != null ? String.valueOf(base.get()) : null;
String rev = String.valueOf(revision.get());
Gerrit.display(
PageLinks.toChange(changeId, b, rev),
new ChangeScreen(changeId, b, rev, openReplyBox,
FileTable.Mode.REVIEW));
}
});
}
};
}
private Runnable maybePrevVimSearch(final CodeMirror cm) {
return new Runnable() {
@Override
public void run() {
if (cm.vim().hasSearchHighlight()) {
cm.vim().handleKey("N");
} else {
getCommentManager().commentNav(cm, Direction.NEXT).run();
}
}
};
}
private Runnable maybeNextVimSearch(final CodeMirror cm) {
return new Runnable() {
@Override
public void run() {
if (cm.vim().hasSearchHighlight()) {
cm.vim().handleKey("n");
} else {
getChunkManager().diffChunkNav(cm, Direction.NEXT).run();
}
}
};
}
Runnable maybeNextCmSearch(final CodeMirror cm) {
return new Runnable() {
@Override
public void run() {
if (cm.hasSearchHighlight()) {
cm.execCommand("findNext");
} else {
cm.execCommand("clearSearch");
getCommentManager().toggleOpenBox(cm).run();
}
}
};
}
boolean renderEntireFile() {
return prefs.renderEntireFile() && canRenderEntireFile(prefs);
}
boolean canRenderEntireFile(DiffPreferences prefs) {
// CodeMirror is too slow to layout an entire huge file.
return fileSize.compareTo(FileSize.HUGE) < 0
|| (prefs.context() != WHOLE_FILE_CONTEXT && prefs.context() < 100);
}
DiffInfo.IntraLineStatus getIntraLineStatus() {
return diff.intralineStatus();
}
void setThemeStyles(boolean d) {
if (d) {
getDiffTable().addStyleName(Resources.I.diffTableStyle().dark());
} else {
getDiffTable().removeStyleName(Resources.I.diffTableStyle().dark());
}
}
void setShowTabs(boolean show) {
for (CodeMirror cm : getCms()) {
cm.extras().showTabs(show);
}
}
void setLineLength(int columns) {
for (CodeMirror cm : getCms()) {
cm.extras().lineLength(columns);
}
}
String getContentType(DiffInfo.FileMeta meta) {
if (prefs.syntaxHighlighting() && meta != null
&& meta.contentType() != null) {
ModeInfo m = ModeInfo.findMode(meta.contentType(), path);
return m != null ? m.mime() : null;
}
return null;
}
String getContentType() {
return getContentType(diff.metaB());
}
void injectMode(DiffInfo diffInfo, AsyncCallback<Void> cb) {
new ModeInjector()
.add(getContentType(diffInfo.metaA()))
.add(getContentType(diffInfo.metaB()))
.inject(cb);
}
abstract void setAutoHideDiffHeader(boolean hide);
void prefetchNextFile() {
String nextPath = header.getNextPath();
if (nextPath != null) {
DiffApi.diff(revision, nextPath)
.base(base)
.wholeFile()
.intraline(prefs.intralineDifference())
.ignoreWhitespace(prefs.ignoreWhitespace())
.get(new AsyncCallback<DiffInfo>() {
@Override
public void onSuccess(DiffInfo info) {
new ModeInjector()
.add(getContentType(info.metaA()))
.add(getContentType(info.metaB()))
.inject(CallbackGroup.<Void> emptyCallback());
}
@Override
public void onFailure(Throwable caught) {
}
});
}
}
void reloadDiffInfo() {
final int id = ++reloadVersionId;
DiffApi.diff(revision, path)
.base(base)
.wholeFile()
.intraline(prefs.intralineDifference())
.ignoreWhitespace(prefs.ignoreWhitespace())
.get(new GerritCallback<DiffInfo>() {
@Override
public void onSuccess(DiffInfo diffInfo) {
if (id == reloadVersionId && isAttached()) {
diff = diffInfo;
operation(new Runnable() {
@Override
public void run() {
skipManager.removeAll();
getChunkManager().reset();
getDiffTable().scrollbar.removeDiffAnnotations();
setShowIntraline(prefs.intralineDifference());
render(diff);
skipManager.render(prefs.context(), diff);
}
});
}
}
});
}
private static FileSize bucketFileSize(DiffInfo diff) {
FileMeta a = diff.metaA();
FileMeta b = diff.metaB();
FileSize[] sizes = FileSize.values();
for (int i = sizes.length - 1; 0 <= i; i--) {
FileSize s = sizes[i];
if ((a != null && s.lines <= a.lines())
|| (b != null && s.lines <= b.lines())) {
return s;
}
}
return FileSize.SMALL;
}
abstract Runnable updateActiveLine(CodeMirror cm);
private GutterClickHandler onGutterClick(final CodeMirror cm) {
return new GutterClickHandler() {
@Override
public void handle(CodeMirror instance, final int line,
final String gutterClass, NativeEvent clickEvent) {
if (Element.as(clickEvent.getEventTarget())
.hasClassName(getLineNumberClassName())
&& clickEvent.getButton() == NativeEvent.BUTTON_LEFT
&& !clickEvent.getMetaKey()
&& !clickEvent.getAltKey()
&& !clickEvent.getCtrlKey()
&& !clickEvent.getShiftKey()) {
cm.setCursor(Pos.create(line));
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
getCommentManager().newDraftOnGutterClick(
cm, gutterClass, line + 1);
}
});
}
}
};
}
abstract FocusHandler getFocusHandler();
abstract CodeMirror[] getCms();
abstract CodeMirror getCmFromSide(DisplaySide side);
abstract DiffTable getDiffTable();
abstract int getCmLine(int line, DisplaySide side);
abstract String getLineNumberClassName();
LineOnOtherInfo lineOnOther(DisplaySide side, int line) {
return getChunkManager().lineMapper.lineOnOther(side, line);
}
abstract ScreenLoadCallback<ConfigInfoCache.Entry> getScreenLoadCallback(
CommentsCollections comments);
abstract boolean isSideBySide();
}