// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.collide.client.diff;
import com.google.collide.client.AppContext;
import com.google.collide.client.code.EditableContentArea.Content;
import com.google.collide.client.editor.Buffer;
import com.google.collide.client.editor.Editor;
import com.google.collide.client.editor.Buffer.ScrollListener;
import com.google.collide.client.util.Elements;
import com.google.collide.client.util.PathUtil;
import com.google.collide.dto.DiffChunkResponse;
import com.google.collide.dto.Revision;
import com.google.collide.dto.client.DtoClientImpls.RevisionImpl;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.mvp.CompositeView;
import com.google.collide.mvp.UiComponent;
import com.google.collide.shared.document.Document;
import com.google.common.base.Preconditions;
import com.google.gwt.resources.client.CssResource;
import elemental.html.DivElement;
import elemental.html.Element;
/**
* Container for the diff editor which contains an editor for the before
* document (on the left) and the after document (on the right).
*
* TODO: In the future, we will support diffing across workspaces.
* When we do so, we need to revisit this class and consider making its
* terminology more general than "before" and "after".
*
*/
public class EditorDiffContainer extends UiComponent<EditorDiffContainer.View>
implements Content {
/**
* Static factory method for obtaining an instance of the EditorDiffContainer.
*/
public static EditorDiffContainer create(AppContext appContext) {
EditorDiffBundle editorBefore = new EditorDiffBundle(appContext);
editorBefore.getEditor().setReadOnly(true);
EditorDiffBundle editorAfter = new EditorDiffBundle(appContext);
editorAfter.getEditor().setReadOnly(true);
View view =
new View(editorBefore.getEditor(), editorAfter.getEditor(), appContext.getResources());
return new EditorDiffContainer(view, appContext, editorBefore, editorAfter);
}
public interface Css extends CssResource {
String root();
String diffColumn();
String editorLeftContainer();
String editorRightContainer();
}
public interface Resources extends DiffRenderer.Resources {
@Source("EditorDiffContainer.css")
Css editorDiffContainerCss();
}
/**
* The view for the container for two editors.
*/
public static class View extends CompositeView<Void> {
DivElement editorLeftContainer;
DivElement editorRightContainer;
DivElement diffLeft;
DivElement diffRight;
DivElement root;
public View(final Editor editorLeft, final Editor editorRight,
EditorDiffContainer.Resources resources) {
Css css = resources.editorDiffContainerCss();
editorLeftContainer = Elements.createDivElement(css.editorLeftContainer());
editorLeftContainer.appendChild(editorLeft.getElement());
diffLeft = Elements.createDivElement(css.diffColumn());
diffLeft.appendChild(editorLeftContainer);
editorRightContainer = Elements.createDivElement(css.editorRightContainer());
editorRightContainer.appendChild(editorRight.getElement());
diffRight = Elements.createDivElement(css.diffColumn());
diffRight.appendChild(editorRightContainer);
root = Elements.createDivElement(css.root());
root.appendChild(diffLeft);
root.appendChild(diffRight);
setElement(root);
}
}
private final EditorDiffBundle editorBefore;
private final EditorDiffBundle editorAfter;
private final AppContext appContext;
private PathUtil path;
public static final Revision UNKNOWN_REVISION = RevisionImpl.make().setRootId("").setNodeId("");
private Revision revisionBefore = UNKNOWN_REVISION;
private Revision revisionAfter = UNKNOWN_REVISION;
private Revision expectedRevisionBefore = UNKNOWN_REVISION;
private Revision expectedRevisionAfter = UNKNOWN_REVISION;
private EditorDiffContainer(View view, AppContext appContext, final EditorDiffBundle editorBefore,
final EditorDiffBundle editorAfter) {
super(view);
this.appContext = appContext;
this.editorBefore = editorBefore;
this.editorAfter = editorAfter;
this.editorBefore.getEditor().getBuffer().setVerticalScrollbarVisibility(false);
editorBefore.getEditor().getBuffer().getScrollListenerRegistrar().add(new ScrollListener() {
@Override
public void onScroll(Buffer buffer, int scrollTop) {
editorAfter.getEditor().getBuffer().setScrollTop(scrollTop);
}
});
editorAfter.getEditor().getBuffer().getScrollListenerRegistrar().add(new ScrollListener() {
@Override
public void onScroll(Buffer buffer, int scrollTop) {
editorBefore.getEditor().getBuffer().setScrollTop(scrollTop);
}
});
}
private static boolean isSameRevision(Revision revision, Revision expectedRevision) {
return revision.getRootId().equals(expectedRevision.getRootId())
&& revision.getNodeId().equals(expectedRevision.getNodeId());
}
private boolean areRevisionsExpected(Revision revisionBefore, Revision revisionAfter) {
return isSameRevision(revisionBefore, expectedRevisionBefore)
&& isSameRevision(revisionAfter, expectedRevisionAfter);
}
public void setExpectedRevisions(Revision revisionBefore, Revision revisionAfter) {
expectedRevisionBefore = Preconditions.checkNotNull(revisionBefore);
expectedRevisionAfter = Preconditions.checkNotNull(revisionAfter);
}
public boolean hasRevisions(Revision revisionBefore, Revision revisionAfter) {
Preconditions.checkNotNull(revisionBefore);
Preconditions.checkNotNull(revisionAfter);
if (this.revisionBefore == UNKNOWN_REVISION || this.revisionAfter == UNKNOWN_REVISION) {
return false;
}
return isSameRevision(revisionBefore, this.revisionBefore)
&& isSameRevision(revisionAfter, this.revisionAfter);
}
/**
* Replace the before and after documents with the new documents formed by the
* given diffChunks.
*
* TODO: Handle line numbers properly.
*
* TODO: Make the before editor read-only.
*
* TODO: collaboration. Because we don't currently have an origin ID,
* the diff editors will not be set up for collaboration, but this will only
* be meaningful once we do diff merging, so we will implement it then.
*/
public void setDiffChunks(PathUtil path, JsonArray<DiffChunkResponse> diffChunks,
Revision revisionBefore, Revision revisionAfter) {
if (!areRevisionsExpected(
Preconditions.checkNotNull(revisionBefore), Preconditions.checkNotNull(revisionAfter))) {
return;
}
this.revisionBefore = revisionBefore;
this.revisionAfter = revisionAfter;
this.path = path;
Document beforeDoc = Document.createEmpty();
Document afterDoc = Document.createEmpty();
DiffRenderer beforeRenderer = new DiffRenderer(beforeDoc, appContext.getResources(), true);
DiffRenderer afterRenderer = new DiffRenderer(afterDoc, appContext.getResources(), false);
for (int i = 0, n = diffChunks.size(); i < n; i++) {
DiffChunkResponse diffChunk = diffChunks.get(i);
String beforeText = diffChunk.getBeforeData();
String afterText = diffChunk.getAfterData();
beforeRenderer.addDiffChunk(diffChunk.getDiffType(), beforeText);
afterRenderer.addDiffChunk(diffChunk.getDiffType(), afterText);
}
// TODO: This setup is a bit awkward so that we can defer setting
// the editor's document in order to workaround some editor bugs. Clean this
// up once those bugs are resolved.
editorBefore.setDocument(beforeDoc, path, beforeRenderer);
editorAfter.setDocument(afterDoc, path, afterRenderer);
}
public int getScrollTop() {
return editorBefore.getEditor().getBuffer().getScrollTop();
}
public void setScrollTop(int scrollTop) {
editorBefore.getEditor().getBuffer().setScrollTop(scrollTop);
editorAfter.getEditor().getBuffer().setScrollTop(scrollTop);
}
public void clearDiffEditors() {
// TODO: update when setDocument(null) works
editorBefore.getEditor().setDocument(Document.createEmpty());
editorAfter.getEditor().setDocument(Document.createEmpty());
revisionAfter = UNKNOWN_REVISION;
revisionBefore = UNKNOWN_REVISION;
}
public PathUtil getPath() {
return path;
}
@Override
public Element getContentElement() {
return getView().getElement();
}
@Override
public void onContentDisplayed() {
}
}