| /******************************************************************************* |
| * 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(); |
| } |
| } |
| } |