/******************************************************************************* * 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; } }