/******************************************************************************* * Copyright (c) 2004, 2012 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.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.TextUtilities; 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; import org.eclipse.wst.sse.ui.internal.text.StructuredAutoEditStrategy; public class StructuredAutoEditStrategyCSS extends StructuredAutoEditStrategy { protected IStructuredDocument structuredDocument = null; class CompoundRegion { /* textRegion can be null if the offset is at the end of the document */ CompoundRegion(IStructuredDocumentRegion documentRegion, ITextRegion textRegion) { super(); this.documentRegion = documentRegion; this.textRegion = textRegion; } IStructuredDocumentRegion getDocumentRegion() { return documentRegion; } ITextRegion getTextRegion() { return textRegion; } int getStart() { return (textRegion != null) ? textRegion.getStart() : documentRegion.getStart(); } int getEnd() { return (textRegion != null) ? textRegion.getEnd() : documentRegion.getEnd(); } String getType() { return (textRegion != null) ? textRegion.getType() : CSSRegionContexts.CSS_UNKNOWN; } String getText() { return (textRegion != null) ? documentRegion.getText(textRegion) : ""; //$NON-NLS-1$ } int getStartOffset() { return (textRegion != null) ? documentRegion.getStartOffset(textRegion) : documentRegion.getStartOffset(); } int getEndOffset() { return (textRegion != null) ? documentRegion.getEndOffset(textRegion) : documentRegion.getEndOffset(); } 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) { // get position int position = command.offset; if (position == -1 || structuredDocument.getLength() == 0) { return; } IStructuredDocumentRegion prev = getPreviousRegion(position); if (prev == null || !CSSRegionContexts.CSS_LBRACE.equals(prev.getType())) return; // select nearest white spaces to replace with new-line setRangeForNewLine(command); position = command.offset; IStructuredDocumentRegion next = prev.getNext(); // create text to replace StringBuffer buf = new StringBuffer(command.text); try { IRegion line = structuredDocument.getLineInformationOfOffset(position); int contentStart = findEndOfWhiteSpace(structuredDocument, position, line.getOffset() + line.getLength()); command.length = Math.max(contentStart - position, 0); buf.append(getIndentString()); if (next != null && CSSRegionContexts.CSS_RBRACE.equals(next.getType()) && !isOneLine(prev)) { command.shiftsCaret = false; command.caretOffset = contentStart + buf.length(); buf.append(TextUtilities.getDefaultLineDelimiter(structuredDocument)); } command.text = buf.toString(); } catch (BadLocationException e) { } } private boolean isOneLine(IStructuredDocumentRegion prev) { return endsWith(structuredDocument.getLegalLineDelimiters(), prev.getFullText()) != -1; } private IStructuredDocumentRegion getPreviousRegion(int offset) { IStructuredDocumentRegion prev = null; if (offset > 0) prev = structuredDocument.getRegionAtCharacterOffset(offset - 1); return prev; } /** */ public void customizeDocumentCommand(IDocument document, DocumentCommand command) { if (!supportsSmartInsert(document)) { 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(); } /** * 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(); } } }