blob: 531b0ca80e03280b8d2fba269a3d470b1a7d4e6d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2005 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
*******************************************************************************/
package org.eclipse.wst.css.ui.internal.autoedit;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.wst.css.core.internal.CSSCorePlugin;
import org.eclipse.wst.css.core.internal.parserz.CSSRegionContexts;
import org.eclipse.wst.css.core.internal.preferences.CSSCorePreferenceNames;
import org.eclipse.wst.css.core.internal.util.RegionIterator;
import org.eclipse.wst.css.ui.internal.Logger;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
public class StructuredAutoEditStrategyCSS implements IAutoEditStrategy {
protected IStructuredDocument structuredDocument = null;
class CompoundRegion {
CompoundRegion(IStructuredDocumentRegion documentRegion, ITextRegion textRegion) {
super();
this.documentRegion = documentRegion;
this.textRegion = textRegion;
}
IStructuredDocumentRegion getDocumentRegion() {
return documentRegion;
}
ITextRegion getTextRegion() {
return textRegion;
}
int getStart() {
return textRegion.getStart();
}
int getEnd() {
return textRegion.getEnd();
}
String getType() {
return textRegion.getType();
}
String getText() {
return documentRegion.getText(textRegion);
}
int getStartOffset() {
return documentRegion.getStartOffset(textRegion);
}
int getEndOffset() {
return documentRegion.getEndOffset(textRegion);
}
private IStructuredDocumentRegion documentRegion;
private ITextRegion textRegion;
}
/**
*/
protected void autoIndentAfterClose(DocumentCommand command, String regionType) {
if (!setRangeForClose(command))
return;
int position = command.offset + command.length;
if (position == -1 || structuredDocument.getLength() == 0) {
return;
}
// get open brace region
CompoundRegion region = prevCorrespondence(position, regionType);
// get indentation
String str = getIndentFor(region, false);
// append to input
if (str != null)
command.text = str + command.text;
}
/**
* Copies the indentation of the previous line.
*/
protected void autoIndentAfterNewLine(DocumentCommand command) {
// select nearest white spaces to replace with new-line
setRangeForNewLine(command);
// get position
int position = command.offset;
if (position == -1 || structuredDocument.getLength() == 0) {
return;
}
// get current region
CompoundRegion currentRegion = getRegion(command.offset + command.length);
// get key region
CompoundRegion keyRegion = getPrevKeyRegion(position, currentRegion);
// get indent string
String str = getIndentFor(keyRegion, true);
// check another indentation
int shift = needShift(keyRegion, command.offset + command.length);
// create text to replace
StringBuffer buf = new StringBuffer(command.text);
if (str != null)
buf.append(str);
while (shift-- != 0)
buf.append(getIndentString());
command.text = buf.toString();
}
/**
*/
public void customizeDocumentCommand(IDocument document, DocumentCommand command) {
Object textEditor = getActiveTextEditor();
if (!(textEditor instanceof ITextEditorExtension3 && ((ITextEditorExtension3) textEditor).getInsertMode() == ITextEditorExtension3.SMART_INSERT))
return;
// return;
// /*
structuredDocument = (IStructuredDocument) document;
if (command.length == 0 && command.text != null) {
if (endsWith(document.getLegalLineDelimiters(), command.text) != -1) {
autoIndentAfterNewLine(command);
} else if (command.text.equals("}")) {//$NON-NLS-1$
autoIndentAfterClose(command, CSSRegionContexts.CSS_RBRACE);
} else if (command.text.equals("]")) {//$NON-NLS-1$
autoIndentAfterClose(command, CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_END);
} else if (command.text.equals(")")) {//$NON-NLS-1$
autoIndentAfterClose(command, CSSRegionContexts.CSS_DECLARATION_VALUE_PARENTHESIS_CLOSE);
}
}
// */
// spaces for tab character
if (command.text != null && command.text.length() > 0 && command.text.charAt(0) == '\t')
smartInsertForTab(command, document);
}
/**
*/
protected String getIndentFor(CompoundRegion region, boolean indentForNextRegion) {
if (region == null)
return null;
IStructuredDocumentRegion flatNode = region.getDocumentRegion();
if (flatNode == null)
return null;
try {
if (region.getType() == CSSRegionContexts.CSS_LBRACE || region.getType() == CSSRegionContexts.CSS_DELIMITER || region.getType() == CSSRegionContexts.CSS_DECLARATION_DELIMITER) {
// get meanful flat node
RegionIterator it = new RegionIterator(flatNode, region.getTextRegion());
it.prev();
while (it.hasPrev()) {
ITextRegion r = it.prev();
region = new CompoundRegion(it.getStructuredDocumentRegion(), r);
if (region.getType() != CSSRegionContexts.CSS_S)
break;
}
flatNode = region.getDocumentRegion();
// get indent string
int position = flatNode.getStart();
int line = structuredDocument.getLineOfOffset(position);
int start = structuredDocument.getLineOffset(line);
int end = findEndOfWhiteSpace(structuredDocument, start, position);
return structuredDocument.get(start, end - start);
} else if (region.getType() == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_START ||
// region.getType() == CSSRegionContexts.CSS_PARENTHESIS_OPEN ||
region.getType() == CSSRegionContexts.CSS_DECLARATION_VALUE_FUNCTION || region.getType() == CSSRegionContexts.CSS_DECLARATION_SEPARATOR) {
int position = flatNode.getStart() + region.getStart();
int line = structuredDocument.getLineOfOffset(position);
int start = structuredDocument.getLineOffset(line);
int end = findEndOfWhiteSpace(structuredDocument, start, position);
StringBuffer buf = new StringBuffer(structuredDocument.get(start, end - start));
position += region.getText().length();
if (indentForNextRegion) {
int tokenStart = findEndOfWhiteSpace(structuredDocument, position, structuredDocument.getLineOffset(line) + structuredDocument.getLineLength(line) - 1);
if (tokenStart < structuredDocument.getLineOffset(line) + structuredDocument.getLineLength(line) - 1) {
position = tokenStart;
}
}
while (position - end > 0) {
buf.append(" ");//$NON-NLS-1$
end++;
}
return buf.toString();
} else
return "";//$NON-NLS-1$
} catch (BadLocationException excp) {
Logger.logException(excp);
}
return null;
}
/**
*/
protected CompoundRegion getPrevKeyRegion(int position, CompoundRegion currentRegion) {
if (currentRegion == null) {
if (structuredDocument.getLastStructuredDocumentRegion() == null)
return null;
}
if (currentRegion != null && (currentRegion.getType() == CSSRegionContexts.CSS_RBRACE || currentRegion.getType() == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_END || currentRegion.getType() == CSSRegionContexts.CSS_DECLARATION_VALUE_PARENTHESIS_CLOSE)) {
return prevCorrespondence(currentRegion);
}
RegionIterator it = new RegionIterator(structuredDocument, position - 1);
while (it.hasPrev()) {
ITextRegion r = it.prev();
CompoundRegion region = new CompoundRegion(it.getStructuredDocumentRegion(), r);
if (region.getType() == CSSRegionContexts.CSS_LBRACE ||
// region.getType() == CSSRegionContexts.CSS_RBRACE ||
region.getType() == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_START ||
// region.getType() ==
// CSSRegionContexts.CSS_BRACKET_CLOSE ||
// // region.getType() ==
// CSSRegionContexts.CSS_PARENTHESIS_OPEN ||
// region.getType() ==
// CSSRegionContexts.CSS_PARENTHESIS_CLOSE ||
region.getType() == CSSRegionContexts.CSS_DELIMITER || region.getType() == CSSRegionContexts.CSS_DECLARATION_DELIMITER ||
// region.getType() == CSSRegionContexts.CSS_COLON ||
// region.getType() == CSSRegionContexts.CSS_COMMENT
// ||
region.getType() == CSSRegionContexts.CSS_DECLARATION_VALUE_FUNCTION) {
return region;
} else if (region.getType() == CSSRegionContexts.CSS_RBRACE || region.getType() == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_END || region.getType() == CSSRegionContexts.CSS_DECLARATION_VALUE_PARENTHESIS_CLOSE) {
// skip to LBRACE
CompoundRegion pc = prevCorrespondence(region);
// guard for NPE
//https://bugs.eclipse.org/bugs/show_bug.cgi?id=111318
if (pc == null) break;
it.reset(pc.getDocumentRegion(), pc.getTextRegion());
it.prev();
} else if (region.getType() == CSSRegionContexts.CSS_STRING) {
RegionIterator itTmp = new RegionIterator(structuredDocument, position);
if (region == itTmp.prev())
return region; // position is inside of string
} else if (region.getType() == CSSRegionContexts.CSS_COMMENT) {
RegionIterator itTmp = new RegionIterator(structuredDocument, position);
if (region == itTmp.prev())
return region; // position is inside of comment
} else if (region.getType() == CSSRegionContexts.CSS_UNKNOWN) {
String str = region.getText();
if (str.charAt(str.length() - 1) == '\\')
return region;
} else if (region.getType() == CSSRegionContexts.CSS_DECLARATION_SEPARATOR) {
RegionIterator itPrev = new RegionIterator(region.getDocumentRegion(), region.getTextRegion());
while (itPrev.hasPrev()) {
ITextRegion regionPrev = itPrev.prev();
if (regionPrev.getType() == CSSRegionContexts.CSS_RBRACE) {
break;
} else if (regionPrev.getType() == CSSRegionContexts.CSS_DELIMITER || regionPrev.getType() == CSSRegionContexts.CSS_DECLARATION_DELIMITER) {
return region;
} else if (regionPrev.getType() == CSSRegionContexts.CSS_LBRACE) {
while (itPrev.hasPrev()) {
regionPrev = itPrev.prev();
if (regionPrev.getType() == CSSRegionContexts.CSS_MEDIA)
break;
if (regionPrev.getType() == CSSRegionContexts.CSS_LBRACE || regionPrev.getType() == CSSRegionContexts.CSS_RBRACE || regionPrev.getType() == CSSRegionContexts.CSS_DELIMITER || regionPrev.getType() == CSSRegionContexts.CSS_DECLARATION_DELIMITER)
return region;
}
if (regionPrev.getType() == CSSRegionContexts.CSS_MEDIA)
break;
else
return region;
}
}
}
}
return null;
}
/**
*/
protected CompoundRegion getRegion(int position) {
IStructuredDocumentRegion flatNode = structuredDocument.getRegionAtCharacterOffset(position);
if (flatNode != null)
return new CompoundRegion(flatNode, flatNode.getRegionAtCharacterOffset(position));
return null;
}
/**
*/
protected int needShift(CompoundRegion region, int position) {
int shift = 0;
if (region == null || region.getType() == CSSRegionContexts.CSS_DELIMITER || region.getType() == CSSRegionContexts.CSS_DECLARATION_DELIMITER || region.getType() == CSSRegionContexts.CSS_LBRACE) {
// get non space region
CompoundRegion cr = getRegion(position - 1);
RegionIterator it = new RegionIterator(cr.getDocumentRegion(), cr.getTextRegion());
ITextRegion nearestRegion = null;
while (it.hasPrev()) {
nearestRegion = it.prev();
if (nearestRegion.getType() != CSSRegionContexts.CSS_S && nearestRegion.getType() != CSSRegionContexts.CSS_COMMENT)
break;
}
if (nearestRegion != null && (nearestRegion.getType() == CSSRegionContexts.CSS_LBRACE || nearestRegion.getType() == CSSRegionContexts.CSS_RBRACE || nearestRegion.getType() == CSSRegionContexts.CSS_DELIMITER || nearestRegion.getType() == CSSRegionContexts.CSS_DECLARATION_DELIMITER))
shift--;
else if (region == null)
shift--;
shift++;
}
if (region != null && region.getType() == CSSRegionContexts.CSS_LBRACE) {
RegionIterator it = new RegionIterator(structuredDocument, position);
if (!it.hasPrev() || it.prev().getType() != CSSRegionContexts.CSS_RBRACE)
shift++;
else
shift = 0;
}
return shift;
}
/**
*/
protected CompoundRegion prevCorrespondence(int position, String regionType) {
RegionIterator it = new RegionIterator(structuredDocument, position - 1);
ITextRegion region = null;
int nest = 1;
if (regionType == CSSRegionContexts.CSS_RBRACE) {
// skip to LBRACE
while (it.hasPrev()) {
region = it.prev();
if (region.getType() == CSSRegionContexts.CSS_LBRACE)
nest--;
else if (region.getType() == CSSRegionContexts.CSS_RBRACE)
nest++;
if (nest <= 0)
break;
}
if (nest == 0)
return new CompoundRegion(it.getStructuredDocumentRegion(), region);
}
if (regionType == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_END) {
// skip to BRACKET_OPEN
while (it.hasPrev()) {
region = it.prev();
if (region.getType() == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_START)
nest--;
else if (region.getType() == CSSRegionContexts.CSS_SELECTOR_ATTRIBUTE_END)
nest++;
if (nest <= 0)
break;
}
if (nest == 0)
return new CompoundRegion(it.getStructuredDocumentRegion(), region);
}
if (regionType == CSSRegionContexts.CSS_DECLARATION_VALUE_PARENTHESIS_CLOSE) {
// skip to PARENTHESIS_OPEN
while (it.hasPrev()) {
region = it.prev();
if (// region.getType() ==
// CSSRegionContexts.CSS_PARENTHESIS_OPEN ||
region.getType() == CSSRegionContexts.CSS_DECLARATION_VALUE_FUNCTION)
nest--;
else if (region.getType() == CSSRegionContexts.CSS_DECLARATION_VALUE_PARENTHESIS_CLOSE)
nest++;
if (nest <= 0)
break;
}
if (nest == 0)
return new CompoundRegion(it.getStructuredDocumentRegion(), region);
}
return null;
}
/**
*/
protected CompoundRegion prevCorrespondence(CompoundRegion region) {
if (region == null)
return null;
IStructuredDocumentRegion flatNode = region.getDocumentRegion();
int position = flatNode.getStart() + region.getStart();
return prevCorrespondence(position, region.getType());
}
/**
* Insert the method's description here.
*
* @return boolean
* @param command
* org.eclipse.jface.text.DocumentCommand
*/
protected boolean setRangeForClose(DocumentCommand command) {
int position = command.offset;
if (position == -1 || structuredDocument.getLength() == 0) {
return false;
}
try {
// find start of line
int p = (position == structuredDocument.getLength() ? position - 1 : position);
int line = structuredDocument.getLineOfOffset(p);
int start = structuredDocument.getLineOffset(line);
RegionIterator it = new RegionIterator(structuredDocument, start);
boolean allWhiteSpace = false;
// check whether the text from lStart to position is white space
// or not
while (it.hasNext()) {
ITextRegion region = it.next();
if (region.getType() != CSSRegionContexts.CSS_S)
break;
if (it.getStructuredDocumentRegion().getEndOffset(region) > p) {
allWhiteSpace = true;
break;
}
}
if (allWhiteSpace) {
command.length = command.length - (start - command.offset);
command.offset = start;
return true;
}
} catch (BadLocationException excp) {
Logger.logException(excp);
}
return false;
}
/**
*/
protected void setRangeForNewLine(DocumentCommand command) {
int position = command.offset;
if (position == -1 || structuredDocument.getLength() == 0) {
return;
}
try {
// add pre-nearest white spaces to replace target
if (position > 0) {
IStructuredDocumentRegion flatNode = structuredDocument.getRegionAtCharacterOffset(position - 1);
if (flatNode != null) {
ITextRegion region = flatNode.getRegionAtCharacterOffset(position - 1);
if (region.getType() == CSSRegionContexts.CSS_S) {
int end = command.offset + command.length;
int nLine = structuredDocument.getLineOfOffset(position);
int nStartPos = structuredDocument.getLineOffset(nLine);
if (nStartPos < flatNode.getStartOffset(region))
nStartPos = flatNode.getStartOffset(region);
command.offset = nStartPos;
command.length = end - command.offset;
}
}
}
// add post-nearest white spaces to replace target
if (position < structuredDocument.getLength()) {
IStructuredDocumentRegion flatNode = structuredDocument.getRegionAtCharacterOffset(position);
if (flatNode != null) {
ITextRegion region = flatNode.getRegionAtCharacterOffset(position);
if (region.getType() == CSSRegionContexts.CSS_S) {
int nLine = structuredDocument.getLineOfOffset(position);
String currentLineDelim = structuredDocument.getLineDelimiter(nLine);
int nEndPos = structuredDocument.getLineOffset(nLine) + structuredDocument.getLineLength(nLine) - ((currentLineDelim != null) ? currentLineDelim.length() : 0);
if (nEndPos > flatNode.getEndOffset(region))
nEndPos = flatNode.getEndOffset(region);
command.length = nEndPos - command.offset;
}
}
}
} catch (BadLocationException e) {
// do not customize command
}
}
private static int endsWith(String[] searchStrings, String text) {
for (int i = 0; i < searchStrings.length; i++) {
if (text.endsWith(searchStrings[i]))
return i;
}
return -1;
}
private static int findEndOfWhiteSpace(IDocument document, int offset, int end) throws BadLocationException {
while (offset < end) {
char c = document.getChar(offset);
if (c != ' ' && c != '\t') {
return offset;
}
offset++;
}
return end;
}
private String getIndentString() {
StringBuffer indent = new StringBuffer();
Preferences preferences = CSSCorePlugin.getDefault().getPluginPreferences();
if (preferences != null) {
char indentChar = ' ';
String indentCharPref = preferences.getString(CSSCorePreferenceNames.INDENTATION_CHAR);
if (CSSCorePreferenceNames.TAB.equals(indentCharPref)) {
indentChar = '\t';
}
int indentationWidth = preferences.getInt(CSSCorePreferenceNames.INDENTATION_SIZE);
for (int i = 0; i < indentationWidth; i++) {
indent.append(indentChar);
}
}
return indent.toString();
}
/**
* Return the active text editor if possible, otherwise the active editor
* part.
*
* @return
*/
private Object getActiveTextEditor() {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window != null) {
IWorkbenchPage page = window.getActivePage();
if (page != null) {
IEditorPart editor = page.getActiveEditor();
if (editor != null) {
if (editor instanceof ITextEditor)
return editor;
ITextEditor textEditor = (ITextEditor) editor.getAdapter(ITextEditor.class);
if (textEditor != null)
return textEditor;
return editor;
}
}
}
return null;
}
/**
* Insert spaces for tabs
*
* @param command
*/
private void smartInsertForTab(DocumentCommand command, IDocument document) {
// tab key was pressed. now check preferences to see if need to insert
// spaces instead of tab
Preferences preferences = CSSCorePlugin.getDefault().getPluginPreferences();
if (CSSCorePreferenceNames.SPACE.equals(preferences.getString(CSSCorePreferenceNames.INDENTATION_CHAR))) {
int indentationWidth = preferences.getInt(CSSCorePreferenceNames.INDENTATION_SIZE);
StringBuffer indent = new StringBuffer();
if (indentationWidth != 0) {
int indentSize = indentationWidth;
try {
IRegion firstLine = document.getLineInformationOfOffset(command.offset);
int offsetInLine = command.offset - firstLine.getOffset();
int remainder = offsetInLine % indentationWidth;
indentSize = indentationWidth - remainder;
} catch (BadLocationException e) {
Logger.log(Logger.WARNING_DEBUG, e.getMessage(), e);
}
for (int i = 0; i < indentSize; i++)
indent.append(' ');
}
// replace \t characters with spaces
command.text = indent.toString();
}
}
}