/*******************************************************************************
* Copyright (c) 2009 Andrey Loskutov.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* Contributor: Andrey Loskutov - initial API and implementation
*******************************************************************************/
/* This class is started as extension of Rahul Kuchal's whitespace plugin.
* Rahul Kuchal - http://www.kuchhal.com/ */
package de.loskutov.anyedit.actions;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionDelegate;
import de.loskutov.anyedit.AnyEditToolsPlugin;
import de.loskutov.anyedit.IAnyEditConstants;
import de.loskutov.anyedit.Messages;
import de.loskutov.anyedit.ui.editor.AbstractEditor;
import de.loskutov.anyedit.util.LineReplaceResult;
import de.loskutov.anyedit.util.TextReplaceResultSet;
import de.loskutov.anyedit.util.TextUtil;
public abstract class AbstractTextAction extends AbstractAction {
public static final String ACTION_ID_CONVERT_TABS = IAnyEditConstants.ACTION_ID_CONVERT_TABS;
public static final String ACTION_ID_CONVERT_SPACES = IAnyEditConstants.ACTION_ID_CONVERT_SPACES;
public static final String ACTION_ID_UNESCAPE = "AnyEdit.unescape";
public static final String ACTION_ID_ENCODE = "AnyEdit.base64encode";
public static final String ACTION_ID_UNICODIFY = "AnyEdit.unicodify";
public static final String ACTION_ID_TO_UPPER = "AnyEdit.toUpperCase";
public static final String ACTION_ID_TO_LOWER = "AnyEdit.toLowerCase";
public static final String ACTION_ID_CAPITALIZE = "AnyEdit.capitalize";
public static final String ACTION_ID_CAMEL = "AnyEdit.camel";
public static final String ACTION_ID_CAMEL_TO_PASCAL = "AnyEdit.camelToPascal";
protected TextUtil textUtil;
private boolean isUsedOnSave;
public AbstractTextAction() {
super();
init();
}
protected final void init() {
textUtil = TextUtil.getDefaultTextUtilities();
}
protected abstract TextReplaceResultSet estimateActionRange(IDocument doc);
/**
* @see IActionDelegate#run(IAction)
*/
@Override
public final void run(IAction action) {
super.run(action);
if(getEditor() == null){
return;
}
AbstractEditor currEditor = getEditor();
IDocument doc = currEditor.getDocument();
if (doc == null) {
return;
}
TextReplaceResultSet result = estimateActionRange(doc);
// no lines affected - return immediately
if(result.getNumberOfLines() == 0){
return;
}
// save dirty buffer, if enabled and if this action is not chained with
// "save" operation
if(!isUsedOnSave() && currEditor.isDirty() && isSaveDirtyBufferEnabled()){
IProgressMonitor monitor = new NullProgressMonitor();
currEditor.doSave(monitor);
if(monitor.isCanceled()){
boolean ok = MessageDialog.openConfirm(
AnyEditToolsPlugin.getShell(),
Messages.title,
Messages.continueOperationMessage);
if(!ok){
return;
}
}
}
try {
doTextOperation(doc, action.getId(), result);
} catch (Exception ex) {
AnyEditToolsPlugin.errorDialog(null, ex);
return;
}
if (!result.areResultsChanged()) {
return;
}
// prepare to save: make sure, that file is not readonly
currEditor.validateEditorInputState();
if (!currEditor.isEditorInputModifiable()) {
if(!isUsedOnSave()) {
Shell shell = AnyEditToolsPlugin.getShell();
MessageDialog.openInformation(
shell,
Messages.title,
Messages.fileIsReadOnly);
}
return;
}
int docLinesNbr = doc.getNumberOfLines();
int changedLinesNbr = result.getNumberOfLines();
boolean rewriteWholeDoc = changedLinesNbr >= docLinesNbr;
DocumentRewriteSession rewriteSession =
currEditor.startSequentialRewriteMode(rewriteWholeDoc);
// some oddities with document??? prevent overflow in changedLinesNbr
if(rewriteWholeDoc){
changedLinesNbr = docLinesNbr;
}
// Map partitioners = null;
// if(EclipseUtils.is31Compatible()){
// // seems to have problems with StructuredTextReconciler in 3.0
// partitioners = TextUtilities.removeDocumentPartitioners(doc);
// }
/*
* TODO think on long running operations
*/
// if (changedLinesNbr > 150) {
// Display display= getTextEditor().getEditorSite().getWorkbenchWindow().getShell().getDisplay();
// BusyIndicator.showWhile(display, runnable);
// } else
// runnable.run();
try {
for (int i = 0; i < changedLinesNbr; i++) {
LineReplaceResult trr = result.get(i);
if(trr != null){
IRegion lineInfo = doc.getLineInformation(i + result.getStartLine());
int startReplaceIndex = trr.startReplaceIndex;
int rangeToReplace = trr.rangeToReplace;
// to replace entire line we can specify "-1" for the range
if(startReplaceIndex == 0 && rangeToReplace == -1) {
rangeToReplace = lineInfo.getLength();
}
doc.replace(lineInfo.getOffset() + startReplaceIndex,
rangeToReplace, trr.textToReplace);
}
}
} catch (Exception ex) {
AnyEditToolsPlugin.errorDialog(null, ex);
} finally {
currEditor.stopSequentialRewriteMode(rewriteSession);
// seems to have problems with StructuredTextReconciler
// if(partitioners != null){
// TextUtilities.addDocumentPartitioners(doc, partitioners);
// }
result.clear();
}
}
/**
* Should be invoked always after estimateActionRange() to ensure that
* operaton is possible
* @param doc cannot be null
* @param actionID desired text action id
* @param resultSet cannot be null
*/
protected abstract void doTextOperation(IDocument doc, String actionID, TextReplaceResultSet resultSet) throws BadLocationException;
protected static boolean isSaveDirtyBufferEnabled() {
return AnyEditToolsPlugin.getDefault().getPreferenceStore().getBoolean(
IAnyEditConstants.SAVE_DIRTY_BUFFER);
}
/**
* @return true, only if this action is intended to be run (chained) always
* just before "save" operation.
*/
public boolean isUsedOnSave() {
return isUsedOnSave;
}
/**
* @param isUsedOnSave true, only if this action is intended to be run
* (chained) always just before "save" operation.
*/
public void setUsedOnSave(boolean isUsedOnSave) {
this.isUsedOnSave = isUsedOnSave;
}
}