///******************************************************************************* // * Copyright (c) 2000, 2007 IBM Corporation and others. // * 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 // * // // *******************************************************************************/ //package org.eclipse.vjet.eclipse.ui.actions; // //import java.util.ResourceBundle; // //import org.eclipse.core.runtime.Assert; //import org.eclipse.dltk.mod.core.DLTKCore; //import org.eclipse.dltk.mod.core.IScriptProject; //import org.eclipse.dltk.mod.core.ISourceModule; //import org.eclipse.dltk.mod.internal.ui.editor.ScriptEditor; //import org.eclipse.dltk.mod.ui.DLTKUIPlugin; //import org.eclipse.jface.text.BadLocationException; //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.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.ui.IEditorInput; //import org.eclipse.ui.texteditor.IDocumentProvider; //import org.eclipse.ui.texteditor.ITextEditor; //import org.eclipse.ui.texteditor.ITextEditorExtension3; //import org.eclipse.ui.texteditor.TextEditorAction; //import org.eclipse.vjet.eclipse.internal.ui.formatting.DefaultCodeFormatterConstants; //import org.eclipse.vjet.eclipse.internal.ui.scriptdoc.JavaHeuristicScanner; //import org.eclipse.vjet.eclipse.internal.ui.scriptdoc.JavaIndenter; //import org.eclipse.vjet.eclipse.internal.ui.text.IJavaScriptPartitions; // ///** // * 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 algorithm used is fast for single lines, but does not store // * any information and therefore not so efficient for large line ranges. // * // * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner // * @see org.eclipse.jdt.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 accommodate 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 // // DLTKCore.log(new Status(IStatus.ERROR, // // JavaPlugin.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(); // // try { // JavaHeuristicScanner scanner = new JavaHeuristicScanner( // document); // JavaIndenter indenter = new JavaIndenter(document, // scanner, getJavaProject()); // final boolean multiLine = nLines > 1; // boolean hasChanged = false; // for (int i = 0; i < nLines; i++) { // hasChanged |= indentLine(document, firstLine + i, // offset, indenter, scanner, multiLine); // } // // // update caret position: move to new position when // // indenting just one line // // keep selection when indenting multiple // int newOffset, newLength; // if (!fIsTabAction && multiLine) { // 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 single line non-tab invocation // if (newOffset != -1 // && (hasChanged || newOffset != offset || newLength != length)) // selectAndReveal(newOffset, newLength); // // document.removePosition(end); // } catch (BadLocationException e) { // // will only happen on concurrent modification // // JavaPlugin.log(new Status(IStatus.ERROR, // // JavaPlugin.getPluginId(), IStatus.OK, // // "ConcurrentModification in IndentAction", e)); // // //$NON-NLS-1$ // // } finally { // if (target != null) // target.endCompoundChange(); // } // } // }; // // 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 ScriptEditor) { // ISourceViewer viewer = ((ScriptEditor) 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 // * @param multiLine // * <code>true</code> if more than one line is being indented // * @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, // boolean multiLine) 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, // IJavaScriptPartitions.JS_PARTITIONING, offset, true); // ITypedRegion startingPartition = TextUtilities.getPartition( // document, IJavaScriptPartitions.JS_PARTITIONING, offset, // false); // String type = partition.getType(); // if (type.equals(IJavaScriptPartitions.JS_DOC) // || type.equals(IJavaScriptPartitions.JS_SINGLE_COMMENT) // || type.equals(IJavaScriptPartitions.JS_MULTI_COMMENT)) { // indent = computeJavadocIndent(document, line, scanner, // startingPartition); // } else if (!fIsTabAction // && startingPartition.getOffset() == offset // && (startingPartition.getType().equals( // IJavaScriptPartitions.JS_SINGLE_COMMENT) || startingPartition // .getType().equals( // IJavaScriptPartitions.JS_MULTI_COMMENT))) { // // // line comment starting at position 0 -> indent inside // int max = document.getLength() - offset; // int slashes = 2; // while (slashes < max - 1 // && document.get(offset + slashes, 2).equals("//")) //$NON-NLS-1$ // slashes += 2; // // wsStart = offset + slashes; // // StringBuffer computed = indenter.computeIndentation(offset); // if (computed == null) // computed = new StringBuffer(0); // int tabSize = getTabSize(); // 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 = ""; //$NON-NLS-1$ // } // // // change document: // // get current white space // int lineLength = currentLine.getLength(); // int end = scanner.findNonWhitespaceForwardInAnyPartition(wsStart, // offset + lineLength); // if (end == JavaHeuristicScanner.NOT_FOUND) { // // an empty line // end = offset + lineLength; // if (multiLine && !indentEmptyLines()) // indent = ""; //$NON-NLS-1$ // } // 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)) { // document.replace(offset, length, indent); // return true; // } else // return false; // } // // /** // * Computes and returns the indentation for a javadoc line. The line must be // * inside a javadoc comment. // * // * @param document // * the document // * @param line // * the line in document // * @param scanner // * the scanner // * @param partition // * the javadoc partition // * @return the indent, or <code>null</code> if not computable // * @throws BadLocationException // * @since 3.1 // */ // private String computeJavadocIndent(IDocument document, int line, // JavaHeuristicScanner scanner, ITypedRegion partition) // throws BadLocationException { // if (line == 0) // impossible - the first line is never inside a javadoc // // comment // return null; // // // don't make any assumptions if the line does not start with \s*\* - it // // might be // // commented out code, for which we don't want to change the indent // final IRegion lineInfo = document.getLineInformation(line); // final int lineStart = lineInfo.getOffset(); // final int lineLength = lineInfo.getLength(); // final int lineEnd = lineStart + lineLength; // int nonWS = scanner.findNonWhitespaceForwardInAnyPartition(lineStart, // lineEnd); // if (nonWS == JavaHeuristicScanner.NOT_FOUND // || document.getChar(nonWS) != '*') { // if (nonWS == JavaHeuristicScanner.NOT_FOUND) // return document.get(lineStart, lineLength); // return document.get(lineStart, nonWS - lineStart); // } // // // take the indent from the previous line and reuse // IRegion previousLine = document.getLineInformation(line - 1); // int previousLineStart = previousLine.getOffset(); // int previousLineLength = previousLine.getLength(); // int previousLineEnd = previousLineStart + previousLineLength; // // StringBuffer buf = new StringBuffer(); // int previousLineNonWS = scanner.findNonWhitespaceForwardInAnyPartition( // previousLineStart, previousLineEnd); // if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND // || document.getChar(previousLineNonWS) != '*') { // // align with the comment start if the previous line is not an // // asterisked line // previousLine = document.getLineInformationOfOffset(partition // .getOffset()); // previousLineStart = previousLine.getOffset(); // previousLineLength = previousLine.getLength(); // previousLineEnd = previousLineStart + previousLineLength; // previousLineNonWS = scanner.findNonWhitespaceForwardInAnyPartition( // previousLineStart, previousLineEnd); // if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND) // previousLineNonWS = previousLineEnd; // // // add the initial space // // TODO this may be controlled by a formatter preference in the // // future // buf.append(' '); // } // // String indentation = document.get(previousLineStart, previousLineNonWS // - previousLineStart); // buf.insert(0, indentation); // return buf.toString(); // } // // /** // * 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 the size in characters of a string // */ // private int whiteSpaceLength(String indent) { // if (indent == null) // return 0; // else { // int size = 0; // int l = indent.length(); // int tabSize = getTabSize(); // // 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 (" " // .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) { // int size = getTabSize(); // 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 tab size used by the java editor, which is deduced from the // * formatter preferences. // * // * @return the tab size as defined in the current formatter preferences // */ // private int getTabSize() { // return getCoreFormatterOption( // DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, 4); // } // // /** // * Returns <code>true</code> if empty lines should be indented, false // * otherwise. // * // * @return <code>true</code> if empty lines should be indented, false // * otherwise // * @since 3.2 // */ // private boolean indentEmptyLines() { // return DefaultCodeFormatterConstants.TRUE // .equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_EMPTY_LINES)); // } // // /** // * Returns the possibly project-specific core preference defined under // * <code>key</code>. // * // * @param key // * the key of the preference // * @return the value of the preference // * @since 3.1 // */ // private String getCoreFormatterOption(String key) { // IScriptProject project = getJavaProject(); // if (project == null) // return DLTKCore.getOption(key); // return project.getOption(key, true); // } // // /** // * Returns the possibly project-specific core preference defined under // * <code>key</code>, or <code>def</code> if the value is not a integer. // * // * @param key // * the key of the preference // * @param def // * the default value // * @return the value of the preference // * @since 3.1 // */ // private int getCoreFormatterOption(String key, int def) { // try { // return Integer.parseInt(getCoreFormatterOption(key)); // } catch (NumberFormatException e) { // return def; // } // } // // /** // * Returns the <code>IJavaProject</code> of the current editor input, or // * <code>null</code> if it cannot be found. // * // * @return the <code>IJavaProject</code> of the current editor input, or // * <code>null</code> if it cannot be found // * @since 3.1 // */ // private IScriptProject getJavaProject() { // ITextEditor editor = getTextEditor(); // if (editor == null) // return null; // // ISourceModule cu = DLTKUIPlugin.getDefault().getWorkingCopyManager() // .getWorkingCopy(editor.getEditorInput()); // if (cu == null) // return null; // return cu.getScriptProject(); // } // // /** // * 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(); // } // //}