+++ /dev/null
-/*******************************************************************************
- * Copyright (c) 2000, 2004 IBM Corporation and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Common Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/cpl-v10.html
- *
- * Contributors:
- * IBM Corporation - initial API and implementation
- *******************************************************************************/
-package net.sourceforge.phpdt.internal.ui.actions;
-
-import java.util.ResourceBundle;
-
-import net.sourceforge.phpdt.core.JavaCore;
-import net.sourceforge.phpdt.core.formatter.DefaultCodeFormatterConstants;
-import net.sourceforge.phpdt.internal.ui.text.IPHPPartitions;
-import net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner;
-import net.sourceforge.phpdt.internal.ui.text.JavaIndenter;
-import net.sourceforge.phpdt.internal.ui.text.SmartBackspaceManager;
-import net.sourceforge.phpdt.internal.ui.text.SmartBackspaceManager.UndoSpec;
-import net.sourceforge.phpdt.internal.ui.text.phpdoc.JavaDocAutoIndentStrategy;
-import net.sourceforge.phpdt.ui.PreferenceConstants;
-import net.sourceforge.phpeclipse.PHPeclipsePlugin;
-import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
-
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.jface.text.Assert;
-import org.eclipse.jface.text.BadLocationException;
-import org.eclipse.jface.text.DocumentCommand;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.jface.text.IRegion;
-import org.eclipse.jface.text.IRewriteTarget;
-import org.eclipse.jface.text.ITextSelection;
-import org.eclipse.jface.text.ITypedRegion;
-import org.eclipse.jface.text.Position;
-import org.eclipse.jface.text.Region;
-import org.eclipse.jface.text.TextSelection;
-import org.eclipse.jface.text.TextUtilities;
-import org.eclipse.jface.text.source.ISourceViewer;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.ISelectionProvider;
-import org.eclipse.swt.custom.BusyIndicator;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.text.edits.MalformedTreeException;
-import org.eclipse.text.edits.ReplaceEdit;
-import org.eclipse.text.edits.TextEdit;
-import org.eclipse.ui.IEditorInput;
-import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
-import org.eclipse.ui.texteditor.IDocumentProvider;
-import org.eclipse.ui.texteditor.ITextEditor;
-import org.eclipse.ui.texteditor.ITextEditorExtension3;
-import org.eclipse.ui.texteditor.TextEditorAction;
-
-/**
- * Indents a line or range of lines in a Java document to its correct position.
- * No complete AST must be present, the indentation is computed using
- * heuristics. The algorith used is fast for single lines, but does not store
- * any information and therefore not so efficient for large line ranges.
- *
- * @see net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner
- * @see net.sourceforge.phpdt.internal.ui.text.JavaIndenter
- * @since 3.0
- */
-public class IndentAction extends TextEditorAction {
-
- /** The caret offset after an indent operation. */
- private int fCaretOffset;
-
- /**
- * Whether this is the action invoked by TAB. When <code>true</code>,
- * indentation behaves differently to accomodate normal TAB operation.
- */
- private final boolean fIsTabAction;
-
- /**
- * Creates a new instance.
- *
- * @param bundle
- * the resource bundle
- * @param prefix
- * the prefix to use for keys in <code>bundle</code>
- * @param editor
- * the text editor
- * @param isTabAction
- * whether the action should insert tabs if over the indentation
- */
- public IndentAction(ResourceBundle bundle, String prefix,
- ITextEditor editor, boolean isTabAction) {
- super(bundle, prefix, editor);
- fIsTabAction = isTabAction;
- }
-
- /*
- * @see org.eclipse.jface.action.Action#run()
- */
- public void run() {
- // update has been called by the framework
- if (!isEnabled() || !validateEditorInputState())
- return;
-
- ITextSelection selection = getSelection();
- final IDocument document = getDocument();
-
- if (document != null) {
-
- final int offset = selection.getOffset();
- final int length = selection.getLength();
- final Position end = new Position(offset + length);
- final int firstLine, nLines;
- fCaretOffset = -1;
-
- try {
- document.addPosition(end);
- firstLine = document.getLineOfOffset(offset);
- // check for marginal (zero-length) lines
- int minusOne = length == 0 ? 0 : 1;
- nLines = document.getLineOfOffset(offset + length - minusOne)
- - firstLine + 1;
- } catch (BadLocationException e) {
- // will only happen on concurrent modification
- PHPeclipsePlugin.log(new Status(IStatus.ERROR, PHPeclipsePlugin
- .getPluginId(), IStatus.OK, "", e)); //$NON-NLS-1$
- return;
- }
-
- Runnable runnable = new Runnable() {
- public void run() {
- IRewriteTarget target = (IRewriteTarget) getTextEditor()
- .getAdapter(IRewriteTarget.class);
- if (target != null) {
- target.beginCompoundChange();
- target.setRedraw(false);
- }
-
- try {
- JavaHeuristicScanner scanner = new JavaHeuristicScanner(
- document);
- JavaIndenter indenter = new JavaIndenter(document,
- scanner);
- boolean hasChanged = false;
- for (int i = 0; i < nLines; i++) {
- hasChanged |= indentLine(document, firstLine + i,
- offset, indenter, scanner);
- }
-
- // update caret position: move to new position when
- // indenting just one line
- // keep selection when indenting multiple
- int newOffset, newLength;
- if (fIsTabAction) {
- newOffset = fCaretOffset;
- newLength = 0;
- } else if (nLines > 1) {
- newOffset = offset;
- newLength = end.getOffset() - offset;
- } else {
- newOffset = fCaretOffset;
- newLength = 0;
- }
-
- // always reset the selection if anything was replaced
- // but not when we had a singleline nontab invocation
- if (newOffset != -1
- && (hasChanged || newOffset != offset || newLength != length))
- selectAndReveal(newOffset, newLength);
-
- document.removePosition(end);
- } catch (BadLocationException e) {
- // will only happen on concurrent modification
- PHPeclipsePlugin.log(new Status(IStatus.ERROR,
- PHPeclipsePlugin.getPluginId(), IStatus.OK,
- "ConcurrentModification in IndentAction", e)); //$NON-NLS-1$
-
- } finally {
-
- if (target != null) {
- target.endCompoundChange();
- target.setRedraw(true);
- }
- }
- }
- };
-
- if (nLines > 50) {
- Display display = getTextEditor().getEditorSite()
- .getWorkbenchWindow().getShell().getDisplay();
- BusyIndicator.showWhile(display, runnable);
- } else
- runnable.run();
-
- }
- }
-
- /**
- * Selects the given range on the editor.
- *
- * @param newOffset
- * the selection offset
- * @param newLength
- * the selection range
- */
- private void selectAndReveal(int newOffset, int newLength) {
- Assert.isTrue(newOffset >= 0);
- Assert.isTrue(newLength >= 0);
- ITextEditor editor = getTextEditor();
- if (editor instanceof PHPEditor) {
- ISourceViewer viewer = ((PHPEditor) editor).getViewer();
- if (viewer != null)
- viewer.setSelectedRange(newOffset, newLength);
- } else
- // this is too intrusive, but will never get called anyway
- getTextEditor().selectAndReveal(newOffset, newLength);
-
- }
-
- /**
- * Indents a single line using the java heuristic scanner. Javadoc and
- * multiline comments are indented as specified by the
- * <code>JavaDocAutoIndentStrategy</code>.
- *
- * @param document
- * the document
- * @param line
- * the line to be indented
- * @param caret
- * the caret position
- * @param indenter
- * the java indenter
- * @param scanner
- * the heuristic scanner
- * @return <code>true</code> if <code>document</code> was modified,
- * <code>false</code> otherwise
- * @throws BadLocationException
- * if the document got changed concurrently
- */
- private boolean indentLine(IDocument document, int line, int caret,
- JavaIndenter indenter, JavaHeuristicScanner scanner)
- throws BadLocationException {
- IRegion currentLine = document.getLineInformation(line);
- int offset = currentLine.getOffset();
- int wsStart = offset; // where we start searching for non-WS; after
- // the "//" in single line comments
-
- String indent = null;
- if (offset < document.getLength()) {
- ITypedRegion partition = TextUtilities.getPartition(document,
- IPHPPartitions.PHP_PARTITIONING, offset, true);
- String type = partition.getType();
- if (type.equals(IPHPPartitions.PHP_PHPDOC_COMMENT)
- || type.equals(IPHPPartitions.PHP_MULTILINE_COMMENT)) {
-
- // TODO this is a hack
- // what I want to do
- // new JavaDocAutoIndentStrategy().indentLineAtOffset(document,
- // offset);
- // return;
-
- int start = 0;
- if (line > 0) {
-
- IRegion previousLine = document
- .getLineInformation(line - 1);
- start = previousLine.getOffset() + previousLine.getLength();
- }
-
- DocumentCommand command = new DocumentCommand() {
- };
- command.text = "\n"; //$NON-NLS-1$
- command.offset = start;
- new JavaDocAutoIndentStrategy(IPHPPartitions.PHP_PARTITIONING)
- .customizeDocumentCommand(document, command);
- int to = 1;
- while (to < command.text.length()
- && Character.isWhitespace(command.text.charAt(to)))
- to++;
- indent = command.text.substring(1, to);
-
- } else if (!fIsTabAction && partition.getOffset() == offset
- && type.equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)) {
-
- // line comment starting at position 0 -> indent inside
- int slashes = 2;
- while (slashes < document.getLength() - 1
- && document.get(offset + slashes, 2).equals("//")) //$NON-NLS-1$
- slashes += 2;
-
- wsStart = offset + slashes;
-
- StringBuffer computed = indenter.computeIndentation(offset);
- int tabSize = PHPeclipsePlugin
- .getDefault()
- .getPreferenceStore()
- .getInt(
- AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
- while (slashes > 0 && computed.length() > 0) {
- char c = computed.charAt(0);
- if (c == '\t')
- if (slashes > tabSize)
- slashes -= tabSize;
- else
- break;
- else if (c == ' ')
- slashes--;
- else
- break;
-
- computed.deleteCharAt(0);
- }
-
- indent = document.get(offset, wsStart - offset) + computed;
-
- }
- }
-
- // standard java indentation
- if (indent == null) {
- StringBuffer computed = indenter.computeIndentation(offset);
- if (computed != null)
- indent = computed.toString();
- else
- indent = new String();
- }
-
- // change document:
- // get current white space
- int lineLength = currentLine.getLength();
- int end = scanner.findNonWhitespaceForwardInAnyPartition(wsStart,
- offset + lineLength);
- if (end == JavaHeuristicScanner.NOT_FOUND)
- end = offset + lineLength;
- int length = end - offset;
- String currentIndent = document.get(offset, length);
-
- // if we are right before the text start / line end, and already after
- // the insertion point
- // then just insert a tab.
- if (fIsTabAction && caret == end
- && whiteSpaceLength(currentIndent) >= whiteSpaceLength(indent)) {
- String tab = getTabEquivalent();
- document.replace(caret, 0, tab);
- fCaretOffset = caret + tab.length();
- return true;
- }
-
- // set the caret offset so it can be used when setting the selection
- if (caret >= offset && caret <= end)
- fCaretOffset = offset + indent.length();
- else
- fCaretOffset = -1;
-
- // only change the document if it is a real change
- if (!indent.equals(currentIndent)) {
- String deletedText = document.get(offset, length);
- document.replace(offset, length, indent);
-
- if (fIsTabAction
- && indent.length() > currentIndent.length()
- && PHPeclipsePlugin.getDefault().getPreferenceStore()
- .getBoolean(
- PreferenceConstants.EDITOR_SMART_BACKSPACE)) {
- ITextEditor editor = getTextEditor();
- if (editor != null) {
- final SmartBackspaceManager manager = (SmartBackspaceManager) editor
- .getAdapter(SmartBackspaceManager.class);
- if (manager != null) {
- try {
- // restore smart portion
- ReplaceEdit smart = new ReplaceEdit(offset, indent
- .length(), deletedText);
-
- final UndoSpec spec = new UndoSpec(offset
- + indent.length(), new Region(caret, 0),
- new TextEdit[] { smart }, 2, null);
- manager.register(spec);
- } catch (MalformedTreeException e) {
- // log & ignore
- PHPeclipsePlugin.log(new Status(IStatus.ERROR,
- PHPeclipsePlugin.getPluginId(), IStatus.OK,
- "Illegal smart backspace action", e)); //$NON-NLS-1$
- }
- }
- }
- }
-
- return true;
- } else
- return false;
- }
-
- /**
- * Returns the size in characters of a string. All characters count one,
- * tabs count the editor's preference for the tab display
- *
- * @param indent
- * the string to be measured.
- * @return
- */
- private int whiteSpaceLength(String indent) {
- if (indent == null)
- return 0;
- else {
- int size = 0;
- int l = indent.length();
- int tabSize = PHPeclipsePlugin
- .getDefault()
- .getPreferenceStore()
- .getInt(
- AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
-
- for (int i = 0; i < l; i++)
- size += indent.charAt(i) == '\t' ? tabSize : 1;
- return size;
- }
- }
-
- /**
- * Returns a tab equivalent, either as a tab character or as spaces,
- * depending on the editor and formatter preferences.
- *
- * @return a string representing one tab in the editor, never
- * <code>null</code>
- */
- private String getTabEquivalent() {
- String tab;
- if (PHPeclipsePlugin.getDefault().getPreferenceStore().getBoolean(
- PreferenceConstants.EDITOR_SPACES_FOR_TABS)) {
- int size = JavaCore.getPlugin().getPluginPreferences().getInt(
- DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE);
- StringBuffer buf = new StringBuffer();
- for (int i = 0; i < size; i++)
- buf.append(' ');
- tab = buf.toString();
- } else
- tab = "\t"; //$NON-NLS-1$
-
- return tab;
- }
-
- /**
- * Returns the editor's selection provider.
- *
- * @return the editor's selection provider or <code>null</code>
- */
- private ISelectionProvider getSelectionProvider() {
- ITextEditor editor = getTextEditor();
- if (editor != null) {
- return editor.getSelectionProvider();
- }
- return null;
- }
-
- /*
- * @see org.eclipse.ui.texteditor.IUpdate#update()
- */
- public void update() {
- super.update();
-
- if (isEnabled())
- if (fIsTabAction)
- setEnabled(canModifyEditor() && isSmartMode()
- && isValidSelection());
- else
- setEnabled(canModifyEditor() && !getSelection().isEmpty());
- }
-
- /**
- * Returns if the current selection is valid, i.e. whether it is empty and
- * the caret in the whitespace at the start of a line, or covers multiple
- * lines.
- *
- * @return <code>true</code> if the selection is valid for an indent
- * operation
- */
- private boolean isValidSelection() {
- ITextSelection selection = getSelection();
- if (selection.isEmpty())
- return false;
-
- int offset = selection.getOffset();
- int length = selection.getLength();
-
- IDocument document = getDocument();
- if (document == null)
- return false;
-
- try {
- IRegion firstLine = document.getLineInformationOfOffset(offset);
- int lineOffset = firstLine.getOffset();
-
- // either the selection has to be empty and the caret in the WS at
- // the line start
- // or the selection has to extend over multiple lines
- if (length == 0)
- return document.get(lineOffset, offset - lineOffset).trim()
- .length() == 0;
- else
- // return lineOffset + firstLine.getLength() < offset + length;
- return false; // only enable for empty selections for now
-
- } catch (BadLocationException e) {
- }
-
- return false;
- }
-
- /**
- * Returns the smart preference state.
- *
- * @return <code>true</code> if smart mode is on, <code>false</code>
- * otherwise
- */
- private boolean isSmartMode() {
- ITextEditor editor = getTextEditor();
-
- if (editor instanceof ITextEditorExtension3)
- return ((ITextEditorExtension3) editor).getInsertMode() == ITextEditorExtension3.SMART_INSERT;
-
- return false;
- }
-
- /**
- * Returns the document currently displayed in the editor, or
- * <code>null</code> if none can be obtained.
- *
- * @return the current document or <code>null</code>
- */
- private IDocument getDocument() {
-
- ITextEditor editor = getTextEditor();
- if (editor != null) {
-
- IDocumentProvider provider = editor.getDocumentProvider();
- IEditorInput input = editor.getEditorInput();
- if (provider != null && input != null)
- return provider.getDocument(input);
-
- }
- return null;
- }
-
- /**
- * Returns the selection on the editor or an invalid selection if none can
- * be obtained. Returns never <code>null</code>.
- *
- * @return the current selection, never <code>null</code>
- */
- private ITextSelection getSelection() {
- ISelectionProvider provider = getSelectionProvider();
- if (provider != null) {
-
- ISelection selection = provider.getSelection();
- if (selection instanceof ITextSelection)
- return (ITextSelection) selection;
- }
-
- // null object
- return TextSelection.emptySelection();
- }
-
-}