com.google.gerrit.client.diff.SideBySide.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.client.diff.SideBySide.java

Source

// 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.LineMapper.LineOnOtherInfo;
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.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.reviewdb.client.Patch;
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.FocusEvent;
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.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.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
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.KeyMap;
import net.codemirror.lib.Pos;

public class SideBySide extends DiffScreen {
    interface Binder extends UiBinder<FlowPanel, SideBySide> {
    }

    private static final Binder uiBinder = GWT.create(Binder.class);
    private static final String LINE_NUMBER_CLASSNAME = "CodeMirror-linenumber";

    @UiField(provided = true)
    SideBySideTable diffTable;

    private CodeMirror cmA;
    private CodeMirror cmB;

    private ScrollSynchronizer scrollSynchronizer;

    private SideBySideChunkManager chunkManager;
    private SideBySideCommentManager commentManager;

    public SideBySide(DiffObject base, DiffObject revision, String path, DisplaySide startSide, int startLine) {
        super(base, revision, path, startSide, startLine, DiffView.SIDE_BY_SIDE);

        diffTable = new SideBySideTable(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>(SideBySide.this) {
            @Override
            protected void preDisplay(ConfigInfoCache.Entry result) {
                commentManager = new SideBySideCommentManager(SideBySide.this, base, revision, path,
                        result.getCommentLinkProcessor(), getChangeStatus().isOpen());
                setTheme(result.getTheme());
                display(comments);
                header.setupPrevNextFiles(comments);
            }
        };
    }

    @Override
    public void onShowView() {
        super.onShowView();

        operation(() -> {
            resizeCodeMirror();
            chunkManager.adjustPadding();
            cmA.refresh();
            cmB.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);
                    setStartLine(lineOnOther(d.getSide(), d.getStart()).getLine() + 1);
                } else {
                    setStartSide(d.getSide());
                    setStartLine(d.getStart() + 1);
                }
            }
        }
        if (getStartSide() != null && getStartLine() > 0) {
            CodeMirror cm = getCmFromSide(getStartSide());
            cm.scrollToLine(getStartLine() - 1);
            cm.focus();
        } else {
            cmA.setCursor(Pos.create(0));
            cmA.focus();
        }
        if (Gerrit.isSignedIn() && prefs.autoReview()) {
            header.autoReview();
        }
        prefetchNextFile();
    }

    @Override
    void registerCmEvents(final CodeMirror cm) {
        super.registerCmEvents(cm);

        KeyMap keyMap = KeyMap.create().on("Shift-A", diffTable.toggleA())
                .on("Shift-Left", moveCursorToSide(cm, DisplaySide.A))
                .on("Shift-Right", moveCursorToSide(cm, DisplaySide.B));
        cm.addKeyMap(keyMap);
        maybeRegisterRenderEntireFileKeyMap(cm);
    }

    @Override
    public void registerKeys() {
        super.registerKeys();

        getKeysNavigation().add(new NoOpKeyCommand(KeyCommand.M_SHIFT, KeyCodes.KEY_LEFT, PatchUtil.C.focusSideA()),
                new NoOpKeyCommand(KeyCommand.M_SHIFT, KeyCodes.KEY_RIGHT, PatchUtil.C.focusSideB()));
        getKeysAction().add(new KeyCommand(KeyCommand.M_SHIFT, 'a', PatchUtil.C.toggleSideA()) {
            @Override
            public void onKeyPress(KeyPressEvent event) {
                diffTable.toggleA().run();
            }
        });

        registerHandlers();
    }

    @Override
    FocusHandler getFocusHandler() {
        return new FocusHandler() {
            @Override
            public void onFocus(FocusEvent event) {
                cmB.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());
        }

        cmA = newCm(diff.metaA(), diff.textA(), diffTable.cmA);
        cmB = newCm(diff.metaB(), diff.textB(), diffTable.cmB);

        getDiffTable().setUpBlameIconA(cmA, base.isBaseOrAutoMerge(),
                base.isBaseOrAutoMerge() ? revision : base.asPatchSetId(), path);
        getDiffTable().setUpBlameIconB(cmB, revision, path);

        cmA.extras().side(DisplaySide.A);
        cmB.extras().side(DisplaySide.B);
        setShowTabs(prefs.showTabs());

        chunkManager = new SideBySideChunkManager(this, cmA, cmB, diffTable.scrollbar);

        operation(() -> {
            // Estimate initial CodeMirror height, fixed up in onShowView.
            int height = Window.getClientHeight() - (Gerrit.getHeaderFooterHeight() + 18);
            cmA.setHeight(height);
            cmB.setHeight(height);

            render(diff);
            commentManager.render(comments, prefs.expandAllComments());
            skipManager.render(prefs.context(), diff);
        });

        registerCmEvents(cmA);
        registerCmEvents(cmB);
        scrollSynchronizer = new ScrollSynchronizer(diffTable, cmA, cmB, chunkManager.lineMapper);

        setPrefsAction(new PreferencesAction(this, prefs));
        header.init(getPrefsAction(), getUnifiedDiffLink(), diff.sideBySideWebLinks());
        scrollSynchronizer.setAutoHideDiffTableHeader(prefs.autoHideDiffTableHeader());

        setupSyntaxHighlighting();
    }

    private List<InlineHyperlink> getUnifiedDiffLink() {
        InlineHyperlink toUnifiedDiffLink = new InlineHyperlink();
        toUnifiedDiffLink.setHTML(new ImageResourceRenderer().render(Gerrit.RESOURCES.unifiedDiff()));
        toUnifiedDiffLink.setTargetHistoryToken(Dispatcher.toUnified(base, revision, path));
        toUnifiedDiffLink.setTitle(PatchUtil.C.unifiedDiff());
        return Collections.singletonList(toUnifiedDiffLink);
    }

    @Override
    CodeMirror newCm(DiffInfo.FileMeta meta, String contents, Element parent) {
        return CodeMirror.create(parent, Configuration.create().set("cursorBlinkRate", prefs.cursorBlinkRate())
                .set("cursorHeight", 0.85).set("inputStyle", "textarea").set("keyMap", "vim_ro")
                .set("lineNumbers", prefs.showLineNumbers()).set("matchBrackets", prefs.matchBrackets())
                .set("lineWrapping", prefs.lineWrapping())
                .set("mode", getFileSize() == FileSize.SMALL ? getContentType(meta) : null).set("readOnly", true)
                .set("scrollbarStyle", "overlay").set("showTrailingSpace", prefs.showWhitespaceErrors())
                .set("styleSelectedText", true).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);

        cmA.setOption("lineNumbers", b);
        cmB.setOption("lineNumbers", b);
    }

    @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()) {
                        cmA.setOption("mode", getContentType(diff.metaA()));
                        cmB.setOption("mode", getContentType(diff.metaB()));
                    }
                }

                @Override
                public void onFailure(Throwable caught) {
                    prefs.syntaxHighlighting(false);
                }
            });
        } else {
            cmA.setOption("mode", (String) null);
            cmB.setOption("mode", (String) null);
        }
    }

    @Override
    void setAutoHideDiffHeader(boolean hide) {
        scrollSynchronizer.setAutoHideDiffTableHeader(hide);
    }

    CodeMirror otherCm(CodeMirror me) {
        return me == cmA ? cmB : cmA;
    }

    @Override
    CodeMirror getCmFromSide(DisplaySide side) {
        return side == DisplaySide.A ? cmA : cmB;
    }

    @Override
    int getCmLine(int line, DisplaySide side) {
        return line;
    }

    @Override
    Runnable updateActiveLine(CodeMirror cm) {
        CodeMirror other = otherCm(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(new ScheduledCommand() {
                @Override
                public void execute() {
                    operation(() -> {
                        LineHandle handle = cm.getLineHandleVisualStart(cm.getCursor("end").line());
                        if (!cm.extras().activeLine(handle)) {
                            return;
                        }

                        LineOnOtherInfo info = lineOnOther(cm.side(), cm.getLineNumber(handle));
                        if (info.isAligned()) {
                            other.extras().activeLine(other.getLineHandle(info.getLine()));
                        } else {
                            other.extras().clearActiveLine();
                        }
                    });
                }
            });
        };
    }

    private Runnable moveCursorToSide(CodeMirror cmSrc, DisplaySide sideDst) {
        CodeMirror cmDst = getCmFromSide(sideDst);
        if (cmDst == cmSrc) {
            return () -> {
            };
        }

        DisplaySide sideSrc = cmSrc.side();
        return () -> {
            if (cmSrc.extras().hasActiveLine()) {
                cmDst.setCursor(Pos
                        .create(lineOnOther(sideSrc, cmSrc.getLineNumber(cmSrc.extras().activeLine())).getLine()));
            }
            cmDst.focus();
        };
    }

    void syncScroll(DisplaySide masterSide) {
        if (scrollSynchronizer != null) {
            scrollSynchronizer.syncScroll(masterSide);
        }
    }

    @Override
    void operation(Runnable apply) {
        cmA.operation(() -> cmB.operation(apply::run));
    }

    @Override
    CodeMirror[] getCms() {
        return new CodeMirror[] { cmA, cmB };
    }

    @Override
    SideBySideTable getDiffTable() {
        return diffTable;
    }

    @Override
    SideBySideChunkManager getChunkManager() {
        return chunkManager;
    }

    @Override
    SideBySideCommentManager getCommentManager() {
        return commentManager;
    }

    @Override
    boolean isSideBySide() {
        return true;
    }

    @Override
    String getLineNumberClassName() {
        return LINE_NUMBER_CLASSNAME;
    }
}