protected List<IRegion> getAttributeValueRegions(ITextViewer viewer) {
    List<IRegion> regions = new ArrayList<IRegion>();
    IDocument document = viewer.getDocument();
    int startOffset = 0;
    int endOffset = document.getLength();

    IStructuredDocumentRegion sdRegion = null;

    while (startOffset < endOffset
        && (sdRegion = ContentAssistUtils.getStructuredDocumentRegion(viewer, startOffset))
            != null) {
      ITextRegionList list = sdRegion.getRegions();

      for (int i = 0; list != null && i < list.size(); i++) {
        ITextRegion region = list.get(i);
        if (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE) {

          final int regionOffset = sdRegion.getStartOffset() + region.getStart();
          final int regionLength = region.getTextLength();

          regions.add(new Region(regionOffset, regionLength));
        }
      }
      startOffset += sdRegion.getLength();
    }

    return regions;
  }
  /**
   * Returns a pair of (tag-balance,bracket-balance) for the range textStart to offset.
   *
   * @param doc the document
   * @param start the offset of the starting character (inclusive)
   * @param end the offset of the ending character (exclusive)
   * @return the balance of tags and brackets
   */
  private static Pair<Integer, Integer> getBalance(IStructuredDocument doc, int start, int end) {
    // Balance of open and closing tags
    // <foo></foo> has tagBalance = 0, <foo> has tagBalance = 1
    int tagBalance = 0;
    // Balance of open and closing brackets
    // <foo attr1="value1"> has bracketBalance = 1, <foo has bracketBalance = 1
    int bracketBalance = 0;
    IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(start);

    if (region != null) {
      boolean inOpenTag = true;
      while (region != null && region.getStartOffset() < end) {
        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 || subRegionStart >= end) {
            continue;
          }
          String type = subRegion.getType();

          if (XML_TAG_OPEN.equals(type)) {
            bracketBalance++;
            inOpenTag = true;
          } else if (XML_TAG_CLOSE.equals(type)) {
            bracketBalance--;
            if (inOpenTag) {
              tagBalance++;
            } else {
              tagBalance--;
            }
          } else if (XML_END_TAG_OPEN.equals(type)) {
            bracketBalance++;
            inOpenTag = false;
          } else if (XML_EMPTY_TAG_CLOSE.equals(type)) {
            bracketBalance--;
          }
        }

        region = region.getNext();
      }
    }

    return Pair.of(tagBalance, bracketBalance);
  }
  private boolean prepareTwigRegions(
      Collection<StyleRange> holdResults,
      ITwigScriptRegion region,
      int regionStart,
      int partitionStartOffset,
      int partitionLength) {

    assert (region.getType() == TwigRegionContext.TWIG_CONTENT
        || region.getType() == TwigRegionContext.TWIG_COMMENT);

    StyleRange styleRange = null;
    TextAttribute attr;
    TextAttribute previousAttr = null;

    ITextRegion[] twigTokens = null;
    try {

      int from;
      int length;
      if (partitionStartOffset < regionStart) {
        from = 0;
        length = partitionLength - (regionStart - partitionStartOffset);
      } else {
        from = partitionStartOffset - regionStart;
        length = partitionLength;
      }
      twigTokens = region.getTwigTokens(from, Math.min(length, region.getLength()));
      ITextRegion prevElement = null;
      for (int i = 0; i < twigTokens.length; i++) {

        ITextRegion element = twigTokens[i];
        attr = getAttributeFor(element);
        // Check that the elements are different - otherwise the
        // coloring is not valid
        if (prevElement == element || attr == null) {
          continue;
        }
        if ((styleRange != null)
            && (previousAttr != null)
            && (previousAttr.equals(attr))
            && prevElement != null
            && prevElement.getLength() == prevElement.getLength()) {
          // extends the prev styleRange with the current element
          // length
          styleRange.length += element.getLength();
          if (styleRange.start + styleRange.length > partitionStartOffset + partitionLength) {
            styleRange.length -=
                (styleRange.start + styleRange.length) - (partitionStartOffset + partitionLength);
          }
        } else {
          // create new styleRange
          int styleStart = regionStart + element.getStart();
          int styleLength = element.getLength();
          if (styleStart + styleLength < partitionStartOffset) { // if
            // the
            // range
            // ends
            // before
            // the
            // requested
            // starting
            // position
            // -
            // ignoring
            // it
            continue;
          }
          if (styleStart < partitionStartOffset) { // if the region
            // starts before
            // the requested
            // starting
            // position -
            // adjusting the
            // style start
            // position
            styleLength -= (partitionStartOffset - styleStart);
            styleStart = partitionStartOffset;
          }
          if (styleStart > partitionStartOffset + partitionLength) {
            // if the region ends after the requested end position -
            // making it shorter
            styleLength -= styleStart - (partitionStartOffset + partitionLength);
          }
          if (attr.getBackground() != null && element.getTextEnd() != element.getEnd()) { // in
            // case
            // of
            // background
            // color
            // make
            // sure
            // the
            // highlighting
            // will
            // not
            // paint
            // the
            // whitespaces
            // applying style to the region w/o the whitespace
            styleRange =
                new StyleRange(
                    styleStart,
                    styleLength - (element.getEnd() - element.getTextEnd()),
                    attr.getForeground(),
                    attr.getBackground(),
                    attr.getStyle());
            if ((attr.getStyle() & TextAttribute.UNDERLINE) != 0) {
              styleRange.underline = true;
              styleRange.fontStyle &= ~TextAttribute.UNDERLINE;
            }
            if ((attr.getStyle() & TextAttribute.STRIKETHROUGH) != 0) {
              styleRange.strikeout = true;
              styleRange.fontStyle &= ~TextAttribute.STRIKETHROUGH;
            }
            holdResults.add(styleRange);
            // applying style to the whitespace (important for the
            // refresh of the specific range
            styleRange =
                new StyleRange(
                    regionStart + element.getTextEnd(),
                    element.getEnd() - element.getTextEnd(),
                    attr.getForeground(),
                    null,
                    attr.getStyle());
            holdResults.add(styleRange);
            previousAttr = null;
          } else {
            styleRange =
                new StyleRange(
                    styleStart,
                    styleLength,
                    attr.getForeground(),
                    attr.getBackground(),
                    attr.getStyle());
            if ((attr.getStyle() & TextAttribute.UNDERLINE) != 0) {
              styleRange.underline = true;
              styleRange.fontStyle &= ~TextAttribute.UNDERLINE;
            }
            if ((attr.getStyle() & TextAttribute.STRIKETHROUGH) != 0) {
              styleRange.strikeout = true;
              styleRange.fontStyle &= ~TextAttribute.STRIKETHROUGH;
            }
            holdResults.add(styleRange);
            // technically speaking, we don't need to update
            // previousAttr
            // in the other case, because the other case is when
            // it hasn't changed
            previousAttr = attr;
          }
        }
        prevElement = element;
      }
      return true;
    } catch (BadLocationException e) {
      Logger.logException(e);
      return false;
    }
  }
  public void customizeDocumentCommand(IDocument document, DocumentCommand c) {
    if (!isSmartInsertMode()) {
      return;
    }

    if (!(document instanceof IStructuredDocument)) {
      // This shouldn't happen unless this strategy is used on an invalid document
      return;
    }
    IStructuredDocument doc = (IStructuredDocument) document;

    // Handle newlines/indentation
    if (c.length == 0
        && c.text != null
        && TextUtilities.endsWith(doc.getLegalLineDelimiters(), c.text) != -1) {

      IModelManager modelManager = StructuredModelManager.getModelManager();
      IStructuredModel model = modelManager.getModelForRead(doc);
      if (model != null) {
        try {
          final int offset = c.offset;
          int lineStart = findLineStart(doc, offset);
          int textStart = findTextStart(doc, lineStart, offset);

          IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(textStart);
          if (region != null && region.getType().equals(XML_TAG_NAME)) {
            Pair<Integer, Integer> balance = getBalance(doc, textStart, offset);
            int tagBalance = balance.getFirst();
            int bracketBalance = balance.getSecond();

            String lineIndent = ""; // $NON-NLS-1$
            if (textStart > lineStart) {
              lineIndent = doc.get(lineStart, textStart - lineStart);
            }

            // We only care if tag or bracket balance is greater than 0;
            // we never *dedent* on negative balances
            boolean addIndent = false;
            if (bracketBalance < 0) {
              // Handle
              //    <foo
              //        ></foo>^
              // and
              //    <foo
              //        />^
              ITextRegion left = getRegionAt(doc, offset, true /*biasLeft*/);
              if (left != null
                  && (left.getType().equals(XML_TAG_CLOSE)
                      || left.getType().equals(XML_EMPTY_TAG_CLOSE))) {

                // Find the corresponding open tag...
                // The org.eclipse.wst.xml.ui.gotoMatchingTag frequently
                // doesn't work, it just says "No matching brace found"
                // (or I would use that here).

                int targetBalance = 0;
                ITextRegion right = getRegionAt(doc, offset, false /*biasLeft*/);
                if (right != null && right.getType().equals(XML_END_TAG_OPEN)) {
                  targetBalance = -1;
                }
                int openTag =
                    AndroidXmlCharacterMatcher.findTagBackwards(doc, offset, targetBalance);
                if (openTag != -1) {
                  // Look up the indentation of the given line
                  lineIndent = AndroidXmlEditor.getIndentAtOffset(doc, openTag);
                }
              }
            } else if (tagBalance > 0 || bracketBalance > 0) {
              // Add indentation
              addIndent = true;
            }

            StringBuilder sb = new StringBuilder(c.text);
            sb.append(lineIndent);
            String oneIndentUnit = XmlFormatPreferences.create().getOneIndentUnit();
            if (addIndent) {
              sb.append(oneIndentUnit);
            }

            // Handle
            //     <foo>^</foo>
            // turning into
            //     <foo>
            //         ^
            //     </foo>
            ITextRegion left = getRegionAt(doc, offset, true /*biasLeft*/);
            ITextRegion right = getRegionAt(doc, offset, false /*biasLeft*/);
            if (left != null
                && right != null
                && left.getType().equals(XML_TAG_CLOSE)
                && right.getType().equals(XML_END_TAG_OPEN)) {
              // Move end tag
              if (tagBalance > 0 && bracketBalance < 0) {
                sb.append(oneIndentUnit);
              }
              c.caretOffset = offset + sb.length();
              c.shiftsCaret = false;
              sb.append(TextUtilities.getDefaultLineDelimiter(doc));
              sb.append(lineIndent);
            }
            c.text = sb.toString();
          } else if (region != null && region.getType().equals(XML_CONTENT)) {
            // Indenting in text content. If you're in the middle of editing
            // text, just copy the current line indentation.
            // However, if you're editing in leading whitespace (e.g. you press
            // newline on a blank line following say an element) then figure
            // out the indentation as if the newline had been pressed at the
            // end of the element, and insert that amount of indentation.
            // In this case we need to also make sure to subtract any existing
            // whitespace on the current line such that if we have
            //
            // <foo>
            // ^   <bar/>
            // </foo>
            //
            // you end up with
            //
            // <foo>
            //
            //    ^<bar/>
            // </foo>
            //
            String text = region.getText();
            int regionStart = region.getStartOffset();
            int delta = offset - regionStart;
            boolean inWhitespacePrefix = true;
            for (int i = 0, n = Math.min(delta, text.length()); i < n; i++) {
              char ch = text.charAt(i);
              if (!Character.isWhitespace(ch)) {
                inWhitespacePrefix = false;
                break;
              }
            }
            if (inWhitespacePrefix) {
              IStructuredDocumentRegion previous = region.getPrevious();
              if (previous != null && previous.getType() == XML_TAG_NAME) {
                ITextRegionList subRegions = previous.getRegions();
                ITextRegion last = subRegions.get(subRegions.size() - 1);
                if (last.getType() == XML_TAG_CLOSE || last.getType() == XML_EMPTY_TAG_CLOSE) {
                  int begin =
                      AndroidXmlCharacterMatcher.findTagBackwards(
                          doc, previous.getStartOffset() + last.getStart(), 0);
                  int prevLineStart = findLineStart(doc, begin);
                  int prevTextStart = findTextStart(doc, prevLineStart, begin);

                  String lineIndent = ""; // $NON-NLS-1$
                  if (prevTextStart > prevLineStart) {
                    lineIndent = doc.get(prevLineStart, prevTextStart - prevLineStart);
                  }
                  StringBuilder sb = new StringBuilder(c.text);
                  sb.append(lineIndent);
                  String oneIndentUnit = XmlFormatPreferences.create().getOneIndentUnit();

                  // See if there is whitespace on the insert line that
                  // we should also remove
                  for (int i = delta, n = text.length(); i < n; i++) {
                    char ch = text.charAt(i);
                    if (ch == ' ') {
                      c.length++;
                    } else {
                      break;
                    }
                  }
                  boolean onClosingTagLine = false;
                  if (text.indexOf('\n', delta) == -1) {
                    IStructuredDocumentRegion next = region.getNext();
                    if (next != null && next.getType() == XML_TAG_NAME) {
                      String nextType = next.getRegions().get(0).getType();
                      if (nextType == XML_END_TAG_OPEN) {
                        onClosingTagLine = true;
                      }
                    }
                  }

                  boolean addIndent = (last.getType() == XML_TAG_CLOSE) && !onClosingTagLine;
                  if (addIndent) {
                    sb.append(oneIndentUnit);
                  }
                  c.text = sb.toString();

                  return;
                }
              }
            }
            copyPreviousLineIndentation(doc, c);
          } else {
            copyPreviousLineIndentation(doc, c);
          }
        } catch (BadLocationException e) {
          AdtPlugin.log(e, null);
        } finally {
          model.releaseFromRead();
        }
      }
    }
  }
 public void equatePositions(ITextRegion region) {
   fStart = region.getStart();
   fLength = region.getLength();
   fTextLength = region.getTextLength();
 }
 protected int getOffset(ITextRegion valueRegion, IDOMNode beanNode) {
   return valueRegion.getStart() + beanNode.getStartOffset() + 1;
 }