/*******************************************************************************
* Copyright (c) 2009, 2015 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
* Dawid PakuĊa [459462]
* Thierry Blind [464039]
*******************************************************************************/
package org.eclipse.php.internal.ui.autoEdit;
import java.io.IOException;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.jface.text.*;
import org.eclipse.php.core.ast.nodes.Program;
import org.eclipse.php.internal.core.documentModel.partitioner.PHPPartitionTypes;
import org.eclipse.php.internal.core.format.*;
import org.eclipse.php.internal.ui.Logger;
import org.eclipse.php.internal.ui.PHPUiPlugin;
import org.eclipse.php.internal.ui.editor.PHPStructuredEditor;
import org.eclipse.php.ui.editor.SharedASTProvider;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
/**
* @author guy.g
*
*/
public class IndentLineAutoEditStrategy extends DefaultIndentationStrategy implements IAutoEditStrategy {
private final CurlyCloseAutoEditStrategy curlyCloseAutoEditStrategy = new CurlyCloseAutoEditStrategy();
private final StringBuilder helpBuffer = new StringBuilder();
IAfterNewLineAutoEditStrategy pairCurlyBracketAutoEditStrategy = new PairCurlyBracketAutoEditStrategy();
private IndentationExtensionRegistry registry = IndentationExtensionRegistry.getInstance();
public IndentLineAutoEditStrategy() {
}
/**
*
* @param indentationObject
* basic indentation preferences, can be null
*/
public IndentLineAutoEditStrategy(IndentationObject indentationObject) {
setIndentationObject(indentationObject);
}
private void autoIndentAfterNewLine(final IStructuredDocument document, final DocumentCommand command) {
try {
helpBuffer.setLength(0);
helpBuffer.append(command.text);
placeMatchingBlanks(document, helpBuffer, document.getLineOfOffset(command.offset), command);
int futureCaretPosition = -1;
if (command.offset > 0) {
final IAfterNewLineAutoEditStrategy autoEditStrategy = getAfterNewLineAutoEditStrategy(document,
command);
if (autoEditStrategy != null)
futureCaretPosition = autoEditStrategy.autoEditAfterNewLine(document, command, helpBuffer);
}
final int startOffset = command.offset;
final int endOffset = startOffset + command.length;
IRegion firstLineInfo = document.getLineInformationOfOffset(startOffset);
int firstLineOffset = firstLineInfo.getOffset();
int firstLineLength = firstLineInfo.getLength();
String firstLineText = document.get(firstLineOffset, firstLineLength);
IRegion lastLineInfo;
int lastLineOffset;
int lastLineLength;
String lastLineText;
// In most case, selections are empty or single-line selections.
if (firstLineOffset <= endOffset && endOffset <= firstLineOffset + firstLineLength) {
lastLineInfo = firstLineInfo;
lastLineOffset = firstLineOffset;
lastLineLength = firstLineLength;
lastLineText = firstLineText;
} else {
lastLineInfo = document.getLineInformationOfOffset(endOffset);
lastLineOffset = lastLineInfo.getOffset();
lastLineLength = lastLineInfo.getLength();
lastLineText = document.get(lastLineOffset, lastLineLength);
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=464039
// How it works: remove blank chars in front of first and last line
// of selection, because "helpBuffer" already contains (new &
// correct) indentation for those lines.
//
// Start by removing blank chars BEFORE actual selection.
// Only valid if all chars between firstLineOffset and
// startOffset - 1 are blank chars.
if (startOffset > firstLineOffset) {
int pos = 0;
for (; pos < startOffset - firstLineOffset
&& (firstLineText.charAt(pos) == ' ' || firstLineText.charAt(pos) == '\t'); pos++)
;
if (pos == startOffset - firstLineOffset) {
// Tweak: also try to keep previous indentation when
// "helpBuffer" starts with a newline char. It's mainly
// cosmetic, first line of selection should keep its
// blank chars (between firstLineOffset and startOffset - 1)
// if its content (starting from startOffset) is put/pushed
// on a new line.
if (TextUtilities.startsWith(document.getLegalLineDelimiters(), helpBuffer.toString()) != -1) {
helpBuffer.insert(0, firstLineText.substring(0, startOffset - firstLineOffset));
}
command.length += startOffset - firstLineOffset;
command.offset = firstLineOffset;
}
}
// Continue by removing all blank chars AFTER end of
// selection.
for (int i = endOffset - lastLineOffset; i < lastLineLength
&& (lastLineText.charAt(i) == ' ' || lastLineText.charAt(i) == '\t'); i++, command.length++)
;
command.text = helpBuffer.toString();
// if we need to put the caret at a position different then the end
// of the text
if (DefaultIndentationStrategy.getPairArrayParen()) {
futureCaretPosition = DefaultIndentationStrategy.getPairArrayOffset();
DefaultIndentationStrategy.unsetPairArrayParen();
}
if (futureCaretPosition != -1) {
// running the command ourself
document.replace(command.offset, command.length, command.text);
// consuming the command
command.length = 0;
command.text = ""; //$NON-NLS-1$
command.offset = futureCaretPosition;
// moving the caret position
document.getUndoManager().disableUndoManagement();
document.replace(command.offset, command.length, command.text);
document.getUndoManager().enableUndoManagement();
}
} catch (final BadLocationException exp) {
Logger.logException(exp);
}
}
@Override
public void customizeDocumentCommand(final IDocument document, final DocumentCommand command) {
if (command.text != null && TextUtilities.endsWith(document.getLegalLineDelimiters(), command.text) != -1) {
setIndentationObject(null); // reset
autoIndentAfterNewLine((IStructuredDocument) document, command);
}
}
private IAfterNewLineAutoEditStrategy getAfterNewLineAutoEditStrategy(final IStructuredDocument document,
final DocumentCommand command) throws BadLocationException {
if (command.length > 0)
return null;
final int offset = command.offset;
String currentState = FormatterUtils.getPartitionType(document, offset);
// fixed bug 186710
// scan for the first char that not equals to space or tab
int charPosition = offset;
char prevChar = '\0';
while (charPosition-- >= 0) {
prevChar = document.getChar(charPosition);
if (prevChar != ' ' && prevChar != '\t') {
break;
}
}
if (TypingPreferences.closeCurlyBracket && prevChar == '{') {
if (currentState == PHPPartitionTypes.PHP_DEFAULT)
return pairCurlyBracketAutoEditStrategy;
}
return null;
}
private IIndentationStrategy getAutoEditStrategy(final char insertionStrategyKey) {
switch (insertionStrategyKey) {
case '}':
return curlyCloseAutoEditStrategy;
default:
if (registry.hasExtensions()) {
return registry.getExtensions().get(0);
}
return this;
}
}
public void placeMatchingBlanks(final IStructuredDocument document, final StringBuilder result,
final int lineNumber, final DocumentCommand command) throws BadLocationException {
final int forOffset = command.offset;
final IRegion endLineInfo = document.getLineInformationOfOffset(forOffset + command.length);
// read the rest of the line
final String lineText = document.get(forOffset + command.length,
endLineInfo.getOffset() + endLineInfo.getLength() - (forOffset + command.length));
final String trimedText = lineText.trim();
final char insertionStrategyKey = trimedText.length() == 0 ? '{' : trimedText.charAt(0);
final IIndentationStrategy indentationStrategy = getAutoEditStrategy(insertionStrategyKey);
if (indentationStrategy instanceof IIndentationStrategyExtension1) {
((IIndentationStrategyExtension1) indentationStrategy).placeMatchingBlanks(document, result, lineNumber,
forOffset, getCurrentProgram(document));
} else {
indentationStrategy.placeMatchingBlanks(document, result, lineNumber, forOffset);
}
}
private Program getCurrentProgram(final IStructuredDocument document) {
final ISourceModule[] sourceModules = new ISourceModule[1];
// resolve current sourceModule
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
IWorkbenchPage page = PHPUiPlugin.getActivePage();
if (page != null) {
IEditorPart editor = page.getActiveEditor();
if (editor instanceof PHPStructuredEditor) {
PHPStructuredEditor phpStructuredEditor = (PHPStructuredEditor) editor;
if (phpStructuredEditor.getTextViewer() != null
&& phpStructuredEditor.getDocument() == document) {
sourceModules[0] = (ISourceModule) phpStructuredEditor.getModelElement();
}
}
}
}
});
// resolve AST
Program program = null;
if (sourceModules[0] != null) {
try {
program = SharedASTProvider.getAST(sourceModules[0], SharedASTProvider.WAIT_YES, null);
} catch (ModelException e) {
Logger.logException(e);
} catch (IOException e) {
Logger.logException(e);
}
}
return program;
}
}