blob: de3b80885d5800008b832bac416dab9ee66f138b [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2004 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.autoedit;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.wst.css.core.parser.CSSRegionContexts;
import org.eclipse.wst.css.core.preferences.CSSPreferenceHelper;
import org.eclipse.wst.css.core.util.RegionIterator;
import org.eclipse.wst.css.ui.internal.Logger;
import org.eclipse.wst.sse.core.text.IStructuredDocument;
import org.eclipse.wst.sse.core.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.text.ITextRegion;
import org.eclipse.wst.sse.ui.edit.util.BasicAutoEditStrategy;
public class StructuredAutoEditStrategyCSS extends BasicAutoEditStrategy {
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(CSSPreferenceHelper.getInstance().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);
}
}
//*/
}
/**
*/
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);
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;
}
}