/******************************************************************************* * Copyright (c) 2009 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 * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.autoEdit; import org.eclipse.jface.text.*; import org.eclipse.php.internal.core.format.FormatterUtils; import org.eclipse.php.internal.core.format.IFormatterCommonPreferences; import org.eclipse.php.internal.ui.Logger; import org.eclipse.php.internal.ui.PHPUiPlugin; import org.eclipse.php.internal.ui.preferences.PreferenceConstants; import org.eclipse.wst.html.core.text.IHTMLPartitions; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; /** * This class handles the events coming from the Tab key. It also handles cases * in which Tab suppose to insert indentation to the line * * @author yaronm * */ public class TabAutoEditStrategy implements IAutoEditStrategy { private int lastIndentSize = 0; private String lastIndentString = ""; //$NON-NLS-1$ private StringBuilder helpBuffer = new StringBuilder(); private IStructuredDocument document; private DocumentCommand command; private IndentLineAutoEditStrategy autoIndentLineStrategy = new IndentLineAutoEditStrategy(); private boolean resetIndentationOnEachDocumentCommand; public TabAutoEditStrategy(boolean resetIndentationOnEachDocumentCommand) { this.resetIndentationOnEachDocumentCommand = resetIndentationOnEachDocumentCommand; } @Override public void customizeDocumentCommand(IDocument document, DocumentCommand command) { if ((command.text != null) && command.text.equals("\t")) { //$NON-NLS-1$ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=464605 // Workaround for bug 464605: // tabulation handling conflicts here with JavaAutoIndentStrategy's // handling when command.text.length() is equal to 1 and // command.offset is inside a JavaScript partition. // // NB: the JavaScript indentation preferences will be used // in this case and no more the PHP indentation preferences! // // See also JavaAutoIndentStrategy#customizeDocumentCommand(...) // and JavaAutoIndentStrategy#smartIndentOnKeypress(...). // XXX: in same way, we should probably disable // IndentLineAutoEditStrategy when inside a JavaScript partition, // and let JavaAutoIndentStrategy do all the job... String partitionType = FormatterUtils.getPartitionType((IStructuredDocument) document, command.offset); if (partitionType == null || FormatterUtils.PARTITION_JS_SCRIPT.equals(partitionType) || IHTMLPartitions.HTML_DEFAULT.equals(partitionType) || IHTMLPartitions.SCRIPT.equals(partitionType)) { return; } // override original tab command command.text = ""; //$NON-NLS-1$ this.command = command; this.document = (IStructuredDocument) document; if (resetIndentationOnEachDocumentCommand) { autoIndentLineStrategy.setIndentationObject(null); // reset } boolean isAutoIndent = PHPUiPlugin.getDefault().getPreferenceStore() .getBoolean(PreferenceConstants.EDITOR_SMART_TAB); if (!isAutoIndent) { applyTabRule(); return; } if (command.length != 0) { return; } tabPressed(); } } private void tabPressed() { try { // get original line information final int lineNumber = document.getLineOfOffset(command.offset); final IRegion originalLineInfo = document.getLineInformation(lineNumber); final int originalLineStart = originalLineInfo.getOffset(); int originalIndentSize = 0; int autoIndentSize = 0; boolean caretInBegining = (command.offset == originalLineStart); // if caret is either : // - located in line's begining // OR // - there's no alphabetical text before it if (caretInBegining || !hasTextBeforeCaret(originalLineInfo)) { // the current "visual" indentation size (Tab = 4 x space) originalIndentSize = calculateOriginalIndentSize(lineNumber, originalLineInfo); // the automatic needed total "visual" indentation size (Tab = 4 // x space) autoIndentSize = calculateAutoIndentSize(lineNumber); // calculateAutoIndentSize(lineNumber) had a runtime Exception // and returns -1 if (autoIndentSize == -1) { applyTabRule(); return; } // checks whether caret is before a space OR Tab character boolean emptyCharAfterOldCaretPos = hasEmptyCharAfterCaret(lineNumber, originalLineStart); if (autoIndentSize >= originalIndentSize) { applyIndent(lineNumber, originalLineStart); // if the caret "Visual" position did not change and it is // exactly in the begining // of an alphabetical text if ((autoIndentSize == originalIndentSize) && !emptyCharAfterOldCaretPos) { applyTabRule(); } } // autoIndentSize < originalIndentSize, means that indentation // will shorten the line else { if (hasEmptyCharAfterCaret(lineNumber, originalLineStart)) { // this indentation will shorten the line applyIndent(lineNumber, originalLineStart); } else { // this will not do auto indentation, but will apply a // Tab applyTabRule(); } } } // caret is located in the middle of line and there's text before it else { applyTabRule(); } } catch (BadLocationException exp) { Logger.logException(exp); } } // This method check if there's an alphabetical text before the caret's // location private boolean hasTextBeforeCaret(IRegion originalLineInfo) throws BadLocationException { int length = originalLineInfo.getLength(); int lineOffset = originalLineInfo.getOffset(); String lineText = document.get(lineOffset, length); int caretIndexInLine = (command.offset - lineOffset); for (int i = 0; i < caretIndexInLine; i++) { if ((lineText.charAt(i) != ' ') && (lineText.charAt(i) != '\t')) return true; } return false; } // This method checks if there's an empty character (' ' OR '\t') private boolean hasEmptyCharAfterCaret(int lineNumber, int originalLineStart) throws BadLocationException { boolean result = false; IRegion lineInfo = document.getLineInformation(lineNumber); int length = lineInfo.getLength(); if (length == 0) return false; String lineText = document.get(originalLineStart, length); int caretIndexInString = command.offset - originalLineStart; // in case the caret is located at the line's end if (caretIndexInString == lineText.length()) return false; char charAfterCaret = lineText.charAt(caretIndexInString); if (charAfterCaret == ' ' || charAfterCaret == '\t') result = true; return result; } // This method calculates the current indentation size of the given line // Note : it considers Tab as 4 spaces private int calculateOriginalIndentSize(int lineNumber, IRegion lineInfo) throws BadLocationException { int startOffset = lineInfo.getOffset(); int length = lineInfo.getLength(); String lineText = document.get(startOffset, length); int result = 0; for (int i = 0; i < lineText.length(); i++) { switch (lineText.charAt(i)) { default: i = lineText.length(); break; case ' ': result += 1; break; case '\t': result += 4; break; } } return result; } // This method applies the appropriate indentation on the given line private void applyIndent(int lineNumber, int offset) throws BadLocationException { command.offset = offset; command.length = getCurrentIndentLength(lineNumber); command.text = helpBuffer.toString(); } // This method returns the length of the line's indentation. // Note : Tab in this case is considered as 1 character. private int getCurrentIndentLength(int lineNumber) throws BadLocationException { IRegion lineInfo = document.getLineInformation(lineNumber); int startOffset = lineInfo.getOffset(); int length = lineInfo.getLength(); String lineText = document.get(startOffset, length); // find the first non blank char of the element. int i; for (i = 0; i < length && (lineText.charAt(i) == ' ' || lineText.charAt(i) == '\t'); i++) ; return i; } // this method returns the auto indentation size. (calculates tab as X4 // spaces) private int calculateAutoIndentSize(int lineNumber) { helpBuffer.setLength(0); try { autoIndentLineStrategy.placeMatchingBlanks(document, helpBuffer, lineNumber, command); } catch (Exception e) { Logger.logException(e); return -1; } int result = 0; String str = helpBuffer.toString(); for (int i = 0; i < str.length(); i++) { switch (str.charAt(i)) { case ' ': result += 1; break; case '\t': result += 4; break; } } return result; } // This method applies the standard Tab rule and will perform a regular tab private void applyTabRule() { IFormatterCommonPreferences formatterCommonPreferences = FormatterUtils.getFormatterCommonPreferences(); char indentChar = formatterCommonPreferences.getIndentationChar(document); if (indentChar == ' ') { // determine where in line this command begins int lineOffset = -1; try { IRegion lineInfo = document.getLineInformationOfOffset(command.offset); lineOffset = command.offset - lineInfo.getOffset(); } catch (BadLocationException e) { Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e); } int indentSize = formatterCommonPreferences.getIndentationSize(document); if (lineOffset > 0) { lineOffset %= indentSize; indentSize -= lineOffset; } if (indentSize == 0) { indentSize = formatterCommonPreferences.getIndentationSize(document); } command.text += getIndentationString(indentSize); } else { command.text += "\t"; //$NON-NLS-1$ } } // This method creates a string with spaces private String getIndentationString(int indentSize) { if (indentSize == lastIndentSize) { return lastIndentString; } lastIndentSize = indentSize; char[] result = new char[indentSize]; for (int i = 0; i < result.length; i++) { result[i] = ' '; } lastIndentString = String.valueOf(result); return lastIndentString; } }