// 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.editor; import com.google.collide.client.editor.Editor.BeforeTextListener; import com.google.collide.client.editor.Editor.TextListener; import com.google.collide.client.editor.selection.SelectionModel; import com.google.collide.shared.document.DocumentMutator; import com.google.collide.shared.document.Line; import com.google.collide.shared.document.Position; import com.google.collide.shared.document.TextChange; import com.google.collide.shared.document.util.LineUtils; import com.google.collide.shared.util.ListenerManager; import com.google.collide.shared.util.ListenerRegistrar; import com.google.collide.shared.util.ListenerManager.Dispatcher; /** * A document mutator for the editor which will notify editor text listeners * whenever a editor-initiated document mutation occurs. * */ public class EditorDocumentMutator implements DocumentMutator { private final ListenerManager<BeforeTextListener> beforeTextListenerManager = ListenerManager .create(); private final Editor editor; private boolean isMutatingDocument; private final ListenerManager<TextListener> textListenerManager = ListenerManager.create(); EditorDocumentMutator(Editor editor) { this.editor = editor; } @Override public TextChange deleteText(Line line, int column, int deleteCount) { return deleteText(line, line.getDocument().getLineFinder().findLine(line).number(), column, deleteCount); } @Override public TextChange deleteText(Line line, int lineNumber, int column, int deleteCount) { String deletedText = editor.getDocument().getText(line, column, deleteCount); return deleteText(line, lineNumber, column, deletedText); } private TextChange deleteText(Line line, int lineNumber, int column, String deletedText) { if (editor.isReadOnly()) { return null; } TextChange textChange = TextChange.createDeletion(line, lineNumber, column, deletedText); dispatchBeforeTextChange(textChange); isMutatingDocument = true; editor.getDocument().deleteText(line, lineNumber, column, deletedText.length()); isMutatingDocument = false; dispatchTextChange(textChange); return textChange; } /** * If there is a selection, the inserted text will replace the selected text. * * @see DocumentMutator#insertText(Line, int, String) */ @Override public TextChange insertText(Line line, int column, String text) { return insertText(line, line.getDocument().getLineFinder().findLine(line).number(), column, text); } @Override public TextChange insertText(Line line, int lineNumber, int column, String text) { return insertText(line, lineNumber, column, text, true); } @Override public TextChange insertText(Line line, int lineNumber, int column, String text, boolean canReplaceSelection) { if (editor.isReadOnly()) { return null; } TextChange textChange = null; SelectionModel selection = editor.getSelection(); if (canReplaceSelection && selection.hasSelection()) { Position[] selectionRange = selection.getSelectionRange(true); /* * TODO: this isn't going to scale for document-sized * selections, need to change some APIs */ Line beginLine = selectionRange[0].getLine(); int beginLineNumber = selectionRange[0].getLineNumber(); int beginColumn = selectionRange[0].getColumn(); String textToDelete = LineUtils.getText(beginLine, beginColumn, selectionRange[1].getLine(), selectionRange[1].getColumn()); textChange = deleteText(beginLine, beginLineNumber, beginColumn, textToDelete); // The insertion should go where the selection was line = beginLine; lineNumber = beginLineNumber; column = beginColumn; } if (text.length() == 0) { return textChange; } /* * The contract for the before text change event is to pass the insertion * line as the "last" line since the text hasn't been inserted yet. */ textChange = TextChange.createInsertion(line, lineNumber, column, line, lineNumber, text); dispatchBeforeTextChange(textChange); isMutatingDocument = true; editor.getDocument().insertText(line, lineNumber, column, text); isMutatingDocument = false; dispatchTextChange(textChange); return textChange; } /** * Returns true if this mutator is currently mutating the document. */ public boolean isMutatingDocument() { return isMutatingDocument; } void dispatchBeforeTextChange(final TextChange textChange) { beforeTextListenerManager.dispatch(new Dispatcher<BeforeTextListener>() { @Override public void dispatch(BeforeTextListener listener) { listener.onBeforeTextChange(textChange); } }); } void dispatchTextChange(final TextChange textChange) { textListenerManager.dispatch(new Dispatcher<TextListener>() { @Override public void dispatch(TextListener listener) { listener.onTextChange(textChange); } }); } ListenerRegistrar<BeforeTextListener> getBeforeTextListenerRegistrar() { return beforeTextListenerManager; } ListenerRegistrar<TextListener> getTextListenerRegistrar() { return textListenerManager; } }