// 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.code;
import com.google.collide.client.AppContext;
import com.google.collide.client.autoindenter.Autoindenter;
import com.google.collide.client.code.EditableContentArea.Content;
import com.google.collide.client.code.autocomplete.integration.AutocompleterFacade;
import com.google.collide.client.code.debugging.DebuggingModel;
import com.google.collide.client.code.debugging.DebuggingModelController;
import com.google.collide.client.code.errorrenderer.EditorErrorListener;
import com.google.collide.client.code.errorrenderer.ErrorReceiver;
import com.google.collide.client.code.errorrenderer.ErrorRenderer;
import com.google.collide.client.code.gotodefinition.GoToDefinitionHandler;
import com.google.collide.client.code.lang.LanguageHelper;
import com.google.collide.client.code.lang.LanguageHelperResolver;
import com.google.collide.client.code.parenmatch.ParenMatchHighlighter;
import com.google.collide.client.code.popup.EditorPopupController;
import com.google.collide.client.codeunderstanding.CubeClient;
import com.google.collide.client.codeunderstanding.CubeClientWrapper;
import com.google.collide.client.document.DocumentManager;
import com.google.collide.client.documentparser.DocumentParser;
import com.google.collide.client.editor.Editor;
import com.google.collide.client.editor.TextActions;
import com.google.collide.client.editor.Editor.Css;
import com.google.collide.client.editor.input.RootActionExecutor;
import com.google.collide.client.history.Place;
import com.google.collide.client.syntaxhighlighter.SyntaxHighlighter;
import com.google.collide.client.util.Elements;
import com.google.collide.client.util.PathUtil;
import com.google.collide.client.util.UserActivityManager;
import com.google.collide.client.util.logging.Log;
import com.google.collide.client.workspace.FileTreeModel;
import com.google.collide.client.workspace.outline.OutlineController;
import com.google.collide.client.workspace.outline.OutlineModel;
import com.google.collide.codemirror2.CodeMirror2;
import com.google.collide.codemirror2.Parser;
import com.google.collide.shared.document.Document;
import elemental.html.Element;
/**
* A class that bundles together all of the editor-related components, such as the editor widget,
* the collaboration controller, the document parser and syntax highlighter.
*/
public class EditorBundle implements Content {
/**
* Static factory method for obtaining an instance of the EditorBundle.
*/
public static EditorBundle create(AppContext appContext,
Place currentPlace,
DocumentManager documentManager,
ParticipantModel participantModel,
OutlineModel outlineModel,
FileTreeModel fileTreeModel,
ErrorReceiver errorReceiver) {
final Editor editor = Editor.create(appContext);
EditorErrorListener editorErrorListener = new EditorErrorListener(
editor, errorReceiver, new ErrorRenderer(appContext.getResources()));
EditorPopupController editorPopupController = EditorPopupController.create(
appContext.getResources(), editor);
// TODO: clean this up when things stabilize.
CubeClientWrapper cubeClientWrapper = new CubeClientWrapper(
appContext.getFrontendApi().GET_CODE_GRAPH);
CubeClient cubeClient = cubeClientWrapper.getCubeClient();
AutocompleterFacade autocompleter = AutocompleterFacade.create(
editor, cubeClient, appContext.getResources());
GoToDefinitionHandler goToDefinition = new GoToDefinitionHandler(currentPlace,
editor,
fileTreeModel,
appContext.getResources(),
cubeClient,
editorPopupController);
SelectionRestorer selectionRestorer = new SelectionRestorer(editor);
DebuggingModel debuggingModel = new DebuggingModel();
DebuggingModelController debuggingModelController =
DebuggingModelController.create(currentPlace,
appContext,
debuggingModel,
editor,
editorPopupController,
documentManager);
WorkspaceLocationBreadcrumbs breadcrumbs = new WorkspaceLocationBreadcrumbs();
OutlineController outlineController = new OutlineController(outlineModel, cubeClient, editor);
final EditorBundle editorBundle = new EditorBundle(documentManager,
editor,
editorErrorListener,
autocompleter,
goToDefinition,
selectionRestorer,
debuggingModelController,
breadcrumbs,
cubeClientWrapper,
outlineController,
appContext.getUserActivityManager(),
editorPopupController,
appContext.getResources().workspaceEditorCss());
return editorBundle;
}
private final AutocompleterFacade autocompleter;
private Autoindenter autoindenter;
private final DocumentManager documentManager;
private final Editor editor;
private final GoToDefinitionHandler goToDefinition;
private DocumentParser parser;
private final SelectionRestorer selectionRestorer;
private final DebuggingModelController debuggingModelController;
private final WorkspaceLocationBreadcrumbs breadcrumbs;
private final CubeClientWrapper cubeClientWrapper;
private final OutlineController outlineController;
/*
* TODO: EditorBundle shouldn't have path. It's here to satisfy legacy dependency
*/
private PathUtil path;
private SyntaxHighlighter syntaxHighlighter;
private final EditorErrorListener editorErrorListener;
private final UserActivityManager userActivityManager;
private final EditorPopupController editorPopupController;
private ParenMatchHighlighter matchHighlighter;
private RootActionExecutor.Remover languageActionsRemover;
private RootActionExecutor.Remover textActionsRemover;
private final Css editorCss;
private final boolean isReadOnly = false;
private EditorBundle(DocumentManager documentManager,
Editor editor,
EditorErrorListener editorErrorListener,
AutocompleterFacade autoCompleter,
GoToDefinitionHandler goToDefinition,
SelectionRestorer selectionRestorer,
DebuggingModelController debuggingModelController,
WorkspaceLocationBreadcrumbs breadcrumbs,
CubeClientWrapper cubeClientWrapper,
OutlineController outlineController,
UserActivityManager userActivityManager,
EditorPopupController editorPopupController,
Editor.Css editorCss) {
this.documentManager = documentManager;
this.editor = editor;
this.editorErrorListener = editorErrorListener;
this.autocompleter = autoCompleter;
this.goToDefinition = goToDefinition;
this.selectionRestorer = selectionRestorer;
this.debuggingModelController = debuggingModelController;
this.breadcrumbs = breadcrumbs;
this.cubeClientWrapper = cubeClientWrapper;
this.outlineController = outlineController;
this.userActivityManager = userActivityManager;
this.editorPopupController = editorPopupController;
this.editorCss = editorCss;
}
public Editor getEditor() {
return editor;
}
/**
* The readonly state of this workspace. The readonly state of the editor can change (i.e. when a
* file is deleted, the workspace temporarily goes into readonly mode until a current file is
* open), but the readonly state of the EditorBundle is final based on the workspace. This is now
* hardcoded to false.
*/
public boolean isReadOnly() {
return isReadOnly;
}
public WorkspaceLocationBreadcrumbs getBreadcrumbs() {
return breadcrumbs;
}
public PathUtil getPath() {
return path;
}
public DebuggingModelController getDebuggingModelController() {
return debuggingModelController;
}
public void cleanup() {
reset();
goToDefinition.cleanup();
autocompleter.cleanup();
editorErrorListener.cleanup();
editor.cleanup();
outlineController.cleanup();
cubeClientWrapper.cleanup();
editorPopupController.cleanup();
debuggingModelController.cleanup();
// TODO: remove
Element readOnlyElement = Elements.getElementById("readOnly");
if (readOnlyElement != null) {
readOnlyElement.removeFromParent();
}
}
/**
* Detach services attached on {@link #setDocument}.
*
* <p>
* These services are constructed on the base of document and path, When active document is
* changed or editor is closed, they should gracefully cleanup.
*/
private void reset() {
if (parser != null) {
parser.teardown();
parser = null;
}
if (syntaxHighlighter != null) {
editor.removeLineRenderer(syntaxHighlighter.getRenderer());
syntaxHighlighter.teardown();
syntaxHighlighter = null;
}
if (autoindenter != null) {
autoindenter.teardown();
autoindenter = null;
}
if (matchHighlighter != null) {
matchHighlighter.teardown();
}
if (languageActionsRemover != null) {
languageActionsRemover.remove();
languageActionsRemover = null;
}
if (textActionsRemover != null) {
textActionsRemover.remove();
textActionsRemover = null;
}
}
/**
* Replaces the document for the editor and related components.
*/
public void setDocument(Document document, PathUtil path, String fileEditSessionKey) {
selectionRestorer.onBeforeDocumentChanged();
reset();
this.path = path;
documentManager.attachToEditor(document, editor);
Parser codeMirrorParser = CodeMirror2.getParser(path);
parser = codeMirrorParser == null ? null
: DocumentParser.create(document, codeMirrorParser, userActivityManager);
LanguageHelper languageHelper = LanguageHelperResolver.getHelper(parser.getSyntaxType());
RootActionExecutor actionExecutor = editor.getInput().getActionExecutor();
languageActionsRemover = actionExecutor.addDelegate(languageHelper.getActionExecutor());
textActionsRemover = actionExecutor.addDelegate(TextActions.INSTANCE);
cubeClientWrapper.setDocument(document, path.getPathString());
goToDefinition.editorContentsReplaced(path, parser);
selectionRestorer.onDocumentChanged(fileEditSessionKey);
editorErrorListener.onDocumentChanged(document, fileEditSessionKey);
debuggingModelController.setDocument(document, path, parser);
outlineController.onDocumentChanged(parser);
try {
autocompleter.editorContentsReplaced(path, parser);
} catch (Throwable t) {
Log.error(getClass(), "Autocompletion subsystem failed to accept the changed document", t);
}
syntaxHighlighter = SyntaxHighlighter.create(document,
editor.getRenderer(),
editor.getViewport(),
editor.getSelection(),
parser,
editorCss);
editor.addLineRenderer(syntaxHighlighter.getRenderer());
/*
* Make sure we open the editor in the right state according to the workspace readonly state.
* (For example, deleting a file will temporarily set the editor in readonly mode while it's
* still open).
*/
editor.setReadOnly(isReadOnly);
autoindenter = Autoindenter.create(parser, editor);
parser.begin();
breadcrumbs.setPath(path);
if (!isReadOnly) {
matchHighlighter = ParenMatchHighlighter.create(document,
editor.getViewport(),
document.getAnchorManager(),
editor.getView().getResources(),
editor.getRenderer(),
editor.getSelection());
}
}
@Override
public Element getContentElement() {
return getEditor().getElement();
}
@Override
public void onContentDisplayed() {
getEditor().getBuffer().synchronizeScrollTop();
}
public OutlineController getOutlineController() {
return outlineController;
}
}