| /******************************************************************************* |
| * 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.core.internal.text; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.wst.css.core.internal.Logger; |
| import org.eclipse.wst.css.core.internal.parser.CSSRegionUtil; |
| import org.eclipse.wst.css.core.internal.parserz.CSSRegionContexts; |
| import org.eclipse.wst.css.core.internal.util.CSSUtil; |
| import org.eclipse.wst.css.core.internal.util.RegionIterator; |
| import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser; |
| import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; |
| import org.eclipse.wst.sse.core.internal.text.StructuredDocumentReParser; |
| |
| |
| /** |
| * This class provides a centralized place to put "reparsing" logic. This is |
| * the logic that reparses the text incrementally, as a user types in new |
| * characters, or DOM nodes are inserted or deleted. Note: it is not a thread |
| * safe class. |
| */ |
| public class CSSStructuredDocumentReParser extends StructuredDocumentReParser { |
| |
| class ReparseRange { |
| ReparseRange() { |
| reset(); |
| } |
| |
| ReparseRange(int start, int end) { |
| fRangeStart = start; |
| fRangeEnd = end; |
| } |
| |
| void setStart(int start) { |
| fRangeStart = start; |
| } |
| |
| void setEnd(int end) { |
| fRangeEnd = end; |
| } |
| |
| int getStart() { |
| return fRangeStart; |
| } |
| |
| int getEnd() { |
| return fRangeEnd; |
| } |
| |
| boolean isValid() { |
| return (0 < fRangeEnd - fRangeStart) ? true : false; |
| } |
| |
| void reset() { |
| fRangeStart = Integer.MAX_VALUE; |
| fRangeEnd = 0; |
| } |
| |
| void expand(ReparseRange range) { |
| if (range == null || !range.isValid()) { |
| return; |
| } |
| int start = range.getStart(); |
| if (start < fRangeStart) { |
| fRangeStart = start; |
| } |
| int end = range.getEnd(); |
| if (fRangeEnd < end) { |
| fRangeEnd = end; |
| } |
| } |
| |
| private int fRangeStart; |
| private int fRangeEnd; |
| } |
| |
| /** |
| * |
| */ |
| public CSSStructuredDocumentReParser() { |
| super(); |
| } |
| |
| /** |
| * |
| */ |
| // public StructuredDocumentEvent |
| // checkForCrossStructuredDocumentRegionBoundryCases2() { |
| // StructuredDocumentEvent result = specialPositionUpdate(fStart, fStart + |
| // fLengthToReplace - 1); |
| // if (result == null) { |
| // result = checkInsideBrace(); |
| // } |
| // return result; |
| // } |
| protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionSyntax() { |
| int checkStart = fStart; |
| int checkEnd = fStart + fLengthToReplace - 1; |
| IStructuredDocumentRegion endRegion = fStructuredDocument.getRegionAtCharacterOffset(checkEnd); |
| if (endRegion != null) { |
| checkEnd = endRegion.getEndOffset(); |
| } |
| |
| ReparseRange range = new ReparseRange(checkStart, checkEnd); |
| |
| range.expand(getUpdateRangeForDelimiter(range.getStart(), range.getEnd())); |
| range.expand(getUpdateRangeForUnknownRegion(range.getStart(), range.getEnd())); |
| |
| range.expand(getUpdateRangeForQuotes(range.getStart(), range.getEnd())); |
| range.expand(getUpdateRangeForComments(range.getStart(), range.getEnd())); |
| range.expand(getUpdateRangeForBraces(range.getStart(), range.getEnd())); |
| |
| StructuredDocumentEvent result; |
| |
| result = checkInsideBrace(range.getStart(), range.getEnd()); |
| |
| if (result == null) { |
| result = reparse(range.getStart(), range.getEnd()); |
| } |
| |
| return result; |
| } |
| |
| private ReparseRange getUpdateRangeForUnknownRegion(int start, int end) { |
| int newStart = start; |
| RegionIterator iterator = new RegionIterator(fStructuredDocument, start - 1); |
| if (iterator.hasPrev()) { |
| iterator.prev(); // skip myself |
| } |
| while (iterator.hasPrev()) { |
| ITextRegion region = iterator.prev(); |
| if (region != null && region.getType() == CSSRegionContexts.CSS_UNKNOWN) { |
| newStart = iterator.getStructuredDocumentRegion().getStartOffset(region); |
| } |
| else { |
| break; |
| } |
| } |
| |
| if (start != newStart) { |
| return new ReparseRange(newStart, end); |
| } |
| |
| return null; |
| } |
| |
| private ReparseRange getUpdateRangeForDelimiter(int start, int end) { |
| if (dirtyStart != null && dirtyStart.getStart() < start) { |
| start = dirtyStart.getStart(); |
| } |
| IStructuredDocumentRegion docRegion = fStructuredDocument.getRegionAtCharacterOffset(start); |
| if (docRegion == null) { |
| return null; |
| } |
| if (docRegion.getType() == CSSRegionContexts.CSS_DELIMITER) { |
| IStructuredDocumentRegion prevRegion = docRegion.getPrevious(); |
| if (prevRegion != null) { |
| return new ReparseRange(prevRegion.getStart(), end); |
| } |
| } |
| |
| return null; |
| } |
| |
| private ReparseRange getUpdateRangeForQuotes(int start, int end) { |
| ReparseRange range = new ReparseRange(); |
| |
| range.expand(getUpdateRangeForPair(start, end, "\"", "\"")); //$NON-NLS-2$//$NON-NLS-1$ |
| range.expand(getUpdateRangeForPair(start, end, "\'", "\'")); //$NON-NLS-2$//$NON-NLS-1$ |
| |
| return (range.isValid()) ? range : null; |
| } |
| |
| private ReparseRange getUpdateRangeForComments(int start, int end) { |
| ReparseRange range = new ReparseRange(); |
| |
| range.expand(getUpdateRangeForPair(start, end, "/*", "*/")); //$NON-NLS-2$//$NON-NLS-1$ |
| range.expand(getUpdateRangeForPair(start, end, "<%", "%>")); //$NON-NLS-2$//$NON-NLS-1$ |
| |
| return (range.isValid()) ? range : null; |
| } |
| |
| private ReparseRange getUpdateRangeForBraces(int start, int end) { |
| ReparseRange range = new ReparseRange(); |
| |
| range.expand(getUpdateRangeForPair(start, end, "[", "]")); //$NON-NLS-2$//$NON-NLS-1$ |
| range.expand(getUpdateRangeForPair(start, end, "(", ")")); //$NON-NLS-2$//$NON-NLS-1$ |
| range.expand(getUpdateRangeForPair(start, end, "{", "}")); //$NON-NLS-2$//$NON-NLS-1$ |
| |
| return (range.isValid()) ? range : null; |
| } |
| |
| private StructuredDocumentEvent checkInsideBrace(int start, int end) { |
| StructuredDocumentEvent result = null; |
| IStructuredDocumentRegion region = fStructuredDocument.getRegionAtCharacterOffset(start); |
| if (region == null) { |
| return null; |
| } |
| boolean bDeclaration = false; |
| String type = region.getType(); |
| if (CSSRegionUtil.isDeclarationType(type) || type == CSSRegionContexts.CSS_LBRACE || type == CSSRegionContexts.CSS_RBRACE) { |
| bDeclaration = true; |
| } |
| if (!bDeclaration) { |
| IStructuredDocumentRegion prevRegion = CSSUtil.findPreviousSignificantNode(region); |
| if (prevRegion != null) { |
| type = prevRegion.getType(); |
| if (CSSRegionUtil.isDeclarationType(type) || type == CSSRegionContexts.CSS_LBRACE) { |
| bDeclaration = true; |
| } |
| } |
| } |
| if (!bDeclaration) { |
| IStructuredDocumentRegion nextRegion = CSSUtil.findNextSignificantNode(region); |
| if (nextRegion != null) { |
| type = nextRegion.getType(); |
| if (CSSRegionUtil.isDeclarationType(type) || type == CSSRegionContexts.CSS_RBRACE) { |
| bDeclaration = true; |
| } |
| } |
| } |
| |
| if (bDeclaration) { |
| IStructuredDocumentRegion startRegion = findRuleStart(region); |
| if (startRegion != null) { |
| IStructuredDocumentRegion endRegion = fStructuredDocument.getRegionAtCharacterOffset(end); |
| if (endRegion != null) { |
| result = reparse(startRegion, endRegion); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private IStructuredDocumentRegion findRuleStart(IStructuredDocumentRegion startRegion) { |
| IStructuredDocumentRegion region = startRegion; |
| // find '{' |
| while (region != null && region.getType() != CSSRegionContexts.CSS_LBRACE) { |
| region = region.getPrevious(); |
| } |
| // if (region == startRegion) { |
| // return null; |
| // } |
| // else |
| if (region != null) { // '{' is found |
| region = region.getPrevious(); |
| if (isLeadingDeclarationType(region.getType())) { |
| return region; |
| } |
| } |
| return null; |
| } |
| |
| private boolean isLeadingDeclarationType(String type) { |
| return (type == CSSRegionContexts.CSS_PAGE || type == CSSRegionContexts.CSS_FONT_FACE || CSSRegionUtil.isSelectorType(type)); |
| } |
| |
| // public StructuredDocumentEvent |
| // checkForCrossStructuredDocumentRegionBoundryCases() { |
| // return specialPositionUpdate(fStart, fStart + fLengthToReplace - 1); |
| // } |
| /** |
| * |
| */ |
| private ReparseRange getUpdateRangeForPair(int start, int end, String opener, String closer) { |
| StringBuffer deletionBuf = new StringBuffer(); |
| StringBuffer insertionBuf = new StringBuffer(); |
| int quoteLen = Math.max(opener.length(), closer.length()); |
| if (1 < quoteLen) { |
| /** |
| * ex) opener = "ABC", closer = "XYZ" model: ABCDEF.......UVWXYZ |
| * deletion: CDEF.......UVWX pre/post: AB / YZ |
| */ |
| int addStart = start - (quoteLen - 1); |
| if (0 <= addStart) { |
| String addStr = fStructuredDocument.get(addStart, quoteLen - 1); |
| deletionBuf.append(addStr); |
| insertionBuf.append(addStr); |
| } |
| deletionBuf.append(fDeletedText); |
| insertionBuf.append(fChanges); |
| if (end + (quoteLen - 1) < fStructuredDocument.getLength()) { |
| String addStr = fStructuredDocument.get(end + 1, quoteLen - 1); |
| deletionBuf.append(addStr); |
| insertionBuf.append(addStr); |
| } |
| } |
| else { |
| deletionBuf.append(fDeletedText); |
| insertionBuf.append(fChanges); |
| } |
| String deletion = deletionBuf.toString(); |
| String insertion = insertionBuf.toString(); |
| |
| int rangeStart = start; |
| int rangeEnd = end; |
| |
| if (0 <= deletion.indexOf(opener) || 0 <= deletion.indexOf(closer) || 0 <= insertion.indexOf(opener) || 0 <= insertion.indexOf(closer)) { |
| int s, e; |
| try { |
| if (start <= fStructuredDocument.getLength()) { |
| IRegion startRegion = getFindReplaceDocumentAdapter().find(start - 1, opener, false, false, false, false); |
| if (startRegion != null) { |
| s = startRegion.getOffset(); |
| } |
| else { |
| s = -1; |
| } |
| } |
| else { |
| s = -1; |
| } |
| if (end < fStructuredDocument.getLength() - 1) { |
| IRegion endRegion = getFindReplaceDocumentAdapter().find(end + 1, closer, true, false, false, false); |
| if (endRegion != null) { |
| e = endRegion.getOffset(); |
| } |
| else { |
| e = -1; |
| } |
| } |
| else { |
| e = -1; |
| } |
| } |
| catch (BadLocationException ex) { |
| Logger.logException(ex); |
| return null; |
| } |
| if (0 <= s) { |
| rangeStart = Math.min(rangeStart, s); |
| } |
| if (0 <= e) { |
| rangeEnd = Math.max(rangeEnd, e); |
| } |
| } |
| |
| if (rangeStart < start || end < rangeEnd) { |
| return new ReparseRange(rangeStart, rangeEnd); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| public IStructuredTextReParser newInstance() { |
| return new CSSStructuredDocumentReParser(); |
| } |
| } |