// 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() { } }