diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java new file mode 100644 index 000000000..8a12fe03b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.editors; + +import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_EMPTY_TAG_CLOSE; +import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_END_TAG_OPEN; +import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG_CLOSE; +import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG_NAME; +import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG_OPEN; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +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.core.internal.provisional.text.ITextRegionList; +import org.eclipse.wst.xml.ui.internal.text.XMLDocumentRegionEdgeMatcher; + +/** + * Custom version of the character matcher for XML files which adds the ability to + * jump between open and close tags in the XML file. + */ +@SuppressWarnings("restriction") +public class AndroidXmlCharacterMatcher extends XMLDocumentRegionEdgeMatcher { + /** + * Constructs a new character matcher for Android XML files + */ + public AndroidXmlCharacterMatcher() { + } + + @Override + public IRegion match(IDocument doc, int offset) { + if (offset < 0 || offset >= doc.getLength()) { + return null; + } + + IRegion match = findOppositeTag(doc, offset); + if (match != null) { + return match; + } + + return super.match(doc, offset); + } + + private IRegion findOppositeTag(IDocument document, int offset) { + if (!(document instanceof IStructuredDocument)) { + return null; + } + IStructuredDocument doc = (IStructuredDocument) document; + + IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); + if (region == null) { + return null; + } + + ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); + if (subRegion == null) { + return null; + } + ITextRegionList subRegions = region.getRegions(); + int index = subRegions.indexOf(subRegion); + + String type = subRegion.getType(); + boolean isOpenTag = false; + boolean isCloseTag = false; + + if (type.equals(XML_TAG_OPEN)) { + isOpenTag = true; + } else if (type.equals(XML_END_TAG_OPEN)) { + isCloseTag = true; + } else if (!(type.equals(XML_TAG_CLOSE) || type.equals(XML_TAG_NAME)) && + (subRegion.getStart() + region.getStartOffset() == offset)) { + // Look to the left one character; we may have the case where you're + // pointing to the right of a tag, e.g. + // <foo>^text + offset--; + region = doc.getRegionAtCharacterOffset(offset); + if (region == null) { + return null; + } + subRegion = region.getRegionAtCharacterOffset(offset); + if (subRegion == null) { + return null; + } + type = subRegion.getType(); + + subRegions = region.getRegions(); + index = subRegions.indexOf(subRegion); + } + + if (type.equals(XML_TAG_CLOSE) || type.equals(XML_TAG_NAME)) { + for (int i = index; i >= 0; i--) { + subRegion = subRegions.get(i); + type = subRegion.getType(); + if (type.equals(XML_TAG_OPEN)) { + isOpenTag = true; + break; + } else if (type.equals(XML_END_TAG_OPEN)) { + isCloseTag = true; + break; + } + } + } + + if (isOpenTag) { + // Find closing tag + int target = findTagForwards(doc, subRegion.getStart() + region.getStartOffset(), 0); + // Note - there is no point in looking up the whole region for the matching + // tag, because even if you pass a length greater than 1 here, the paint highlighter + // will only highlight a single character -- the *last* character of the region, + // not the whole region itself. + return new Region(target, 1); + } else if (isCloseTag) { + // Find open tag + int target = findTagBackwards(doc, subRegion.getStart() + region.getStartOffset(), -1); + return new Region(target, 1); + } + + return null; + } + + /** + * Finds the corresponding open tag by searching backwards until the tag balance + * reaches a given target. + * + * @param doc the document + * @param offset the ending offset (where the search begins searching backwards from) + * @param targetTagBalance the balance to end the search at + * @return the offset of the beginning of the open tag + */ + public static int findTagBackwards(IStructuredDocument doc, int offset, int targetTagBalance) { + // Balance of open and closing tags + int tagBalance = 0; + // Balance of open and closing brackets + IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); + if (region != null) { + boolean inEmptyTag = true; + + while (region != null) { + int regionStart = region.getStartOffset(); + ITextRegionList subRegions = region.getRegions(); + for (int i = subRegions.size() - 1; i >= 0; i--) { + ITextRegion subRegion = subRegions.get(i); + int subRegionStart = regionStart + subRegion.getStart(); + if (subRegionStart >= offset) { + continue; + } + String type = subRegion.getType(); + + // Iterate backwards and keep track of the tag balance such that + // we can find the corresponding opening tag + + if (XML_TAG_OPEN.equals(type)) { + if (!inEmptyTag) { + tagBalance--; + } + if (tagBalance == targetTagBalance) { + return subRegionStart; + } + } else if (XML_END_TAG_OPEN.equals(type)) { + tagBalance++; + } else if (XML_EMPTY_TAG_CLOSE.equals(type)) { + inEmptyTag = true; + } else if (XML_TAG_CLOSE.equals(type)) { + inEmptyTag = false; + } + } + + region = region.getPrevious(); + } + } + + return -1; + } + + /** + * Finds the corresponding closing tag by searching forwards until the tag balance + * reaches a given target. + * + * @param doc the document + * @param start the starting offset (where the search begins searching forwards from) + * @param targetTagBalance the balance to end the search at + * @return the offset of the beginning of the closing tag + */ + public static int findTagForwards(IStructuredDocument doc, int start, int targetTagBalance) { + int tagBalance = 0; + IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(start); + + if (region != null) { + while (region != null) { + int regionStart = region.getStartOffset(); + ITextRegionList subRegions = region.getRegions(); + for (int i = 0, n = subRegions.size(); i < n; i++) { + ITextRegion subRegion = subRegions.get(i); + int subRegionStart = regionStart + subRegion.getStart(); + int subRegionEnd = regionStart + subRegion.getEnd(); + if (subRegionEnd < start) { + continue; + } + String type = subRegion.getType(); + + if (XML_TAG_OPEN.equals(type)) { + tagBalance++; + } else if (XML_END_TAG_OPEN.equals(type)) { + tagBalance--; + if (tagBalance == targetTagBalance) { + return subRegionStart; + } + } else if (XML_EMPTY_TAG_CLOSE.equals(type)) { + tagBalance--; + if (tagBalance == targetTagBalance) { + // We don't jump to matching tags within a self-closed tag + return -1; + } + } + } + + region = region.getNext(); + } + } + + return -1; + } +} |