All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.rsta.ac.xml.XmlCompletionProvider Maven / Gradle / Ivy

Go to download

A library adding code completion and other advanced features for Java, JavaScript, Perl, and other languages to RSyntaxTextArea.

The newest version!
/*
 * 10/02/2014
 *
 * XmlCompletionProvider.java - Returns XML tag names or attributes that are
 * seen elsewhere in the document.
 *
 * This library is distributed under a modified BSD license.  See the included
 * RSyntaxTextArea.License.txt file for details.
 */
package org.fife.rsta.ac.xml;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.UIManager;
import javax.swing.text.JTextComponent;

import org.fife.rsta.ac.html.AttributeCompletion;
import org.fife.ui.autocomplete.Completion;
import org.fife.ui.autocomplete.DefaultCompletionProvider;
import org.fife.ui.autocomplete.MarkupTagCompletion;
import org.fife.ui.autocomplete.ParameterizedCompletion;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenImpl;
import org.fife.ui.rsyntaxtextarea.TokenTypes;


/**
 * A completion provider that, in the absence of a DTD or XML schema, makes a
 * best guess at what completion choices the user might want for XML.  It does
 * this by looking at what XML element names and attributes have been used
 * elsewhere in the document:

* *

    *
  • If the caret is not in an XML tag, no completion choices are * suggested. *
  • If the caret is in an XML tag name, other tag names in the document * that start with that prefix are suggested. *
  • If the caret is in an XML tag, but not in the tag's name, the * document is parsed for other instances of that tag. If any are * found, all attributes used for that tag elsewhere are suggested. *
* * @author Robert Futrell * @version 1.0 */ class XmlCompletionProvider extends DefaultCompletionProvider { private static final char[] TAG_SELF_CLOSE = { '/', '>' }; XmlCompletionProvider() { setAutoActivationRules(false, "<"); } /** * Creates a Completion of the proper type and adds it * to the completions list. * * @param word The text of the completion. * @param desiredType The completion type we're collecting. */ private void addCompletionImpl(String word, int desiredType) { Completion c; if (desiredType==TokenTypes.MARKUP_TAG_NAME) { c = new MarkupTagCompletion(this, word); } else { ParameterizedCompletion.Parameter param = new ParameterizedCompletion.Parameter(null, word); c = new AttributeCompletion(this, param); } completions.add(c); } /** * Returns the list of attribute names to offer for completion choices for * a given tag. * * @param doc The document being parsed. * @param inTag The XML tag whose attribute is being completed. * @param currentWordStart The start of the current word. * @return The set of completion choices. This will never be * null. */ private Set collectCompletionWordsAttribute(RSyntaxDocument doc, Token inTag, int currentWordStart) { Set possibleAttrs = new HashSet<>(); Set attrs = new HashSet<>(); Set attrsAlreadySpecified = new HashSet<>(); String desiredTagName = inTag.getLexeme(); boolean collectAttrs = false; boolean inCurTag = false; for (Token t2 : doc) { int type = t2.getType(); if (type==TokenTypes.MARKUP_TAG_NAME) { collectAttrs = desiredTagName.equals(t2.getLexeme()); inCurTag = t2.getOffset()==inTag.getOffset(); if (!attrs.isEmpty()) { possibleAttrs.addAll(attrs); attrs.clear(); } } else if (type==TokenTypes.MARKUP_TAG_ATTRIBUTE && collectAttrs) { if (t2.getOffset()!=currentWordStart) { String word = t2.getLexeme(); if (inCurTag) { if (word.indexOf('<')>-1) { collectAttrs = false; attrs.clear(); // Keep attrs already specified up to the element // start //attrsAlreadySpecified.clear(); } else { attrsAlreadySpecified.add(word); } } else { // This is a hack to work around the fact that RSTA will // identify e.g. "-1) { // Prevent other attributes from being added collectAttrs = false; attrs.clear(); attrsAlreadySpecified.clear(); } else { attrs.add(word); } } } } } if (!attrs.isEmpty()) { possibleAttrs.addAll(attrs); } possibleAttrs.removeAll(attrsAlreadySpecified); return possibleAttrs; } /** * Returns the list of tag names to offer for completion choices. * * @param doc The document being parsed. * @param currentWordStart The start of the current word. * @return The set of completion choices. This will never be * null. */ private Set collectCompletionWordsTag(RSyntaxDocument doc, int currentWordStart) { Set words = new HashSet<>(); for (Token t2 : doc) { if (t2.getType()==TokenTypes.MARKUP_TAG_NAME && t2.getOffset()!=currentWordStart) { words.add(t2.getLexeme()); } } return words; } @Override protected List getCompletionsImpl(JTextComponent comp) { completions.clear(); String text = getAlreadyEnteredText(comp); if (text==null) { return completions; } RSyntaxTextArea textArea = (RSyntaxTextArea)comp; int dot = textArea.getCaretPosition(); RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); Token t = RSyntaxUtilities.getPreviousImportantTokenFromOffs(doc, dot); if (t==null) { UIManager.getLookAndFeel().provideErrorFeedback(textArea); return completions; } int desiredType = getDesiredTokenType(t, dot); if (desiredType==TokenTypes.NULL) { UIManager.getLookAndFeel().provideErrorFeedback(textArea); return completions; } int currentWordStart = dot - text.length(); Set words; if (desiredType==TokenTypes.MARKUP_TAG_NAME) { words = collectCompletionWordsTag(doc, currentWordStart); } else { Token tagNameToken = getTagNameTokenForCaretOffset(textArea); if (tagNameToken!=null) { tagNameToken = new TokenImpl(tagNameToken); words = collectCompletionWordsAttribute(doc, tagNameToken, currentWordStart); } else { UIManager.getLookAndFeel().provideErrorFeedback(textArea); return completions; } } for (String word : words) { addCompletionImpl(word, desiredType); } Collections.sort(completions); return super.getCompletionsImpl(comp); } /** * Returns the type of token to return as completion choices, based on * the current caret position. * * @param t The previous "important" (e.g., non-whitespace) token. * @param dot The caret position. * @return The token type, or {@link TokenTypes#NULL} if no completion * choices should be suggested for the current caret position. */ private static int getDesiredTokenType(Token t, int dot) { switch (t.getType()) { case TokenTypes.MARKUP_TAG_NAME: if (t.containsPosition(dot-1)) { return t.getType(); } return TokenTypes.MARKUP_TAG_ATTRIBUTE; case TokenTypes.MARKUP_TAG_ATTRIBUTE: return t.getType(); case TokenTypes.MARKUP_TAG_ATTRIBUTE_VALUE: if (t.containsPosition(dot)) { return TokenTypes.NULL; // In the attribute itself } return TokenTypes.MARKUP_TAG_ATTRIBUTE; case TokenTypes.MARKUP_TAG_DELIMITER: if (t.isSingleChar('<')) { return TokenTypes.MARKUP_TAG_NAME; } return TokenTypes.NULL; default: return TokenTypes.NULL; } } /** * If the caret is inside a tag, this method returns the token * representing the tag name; otherwise, null is returned. * * @param textArea The text area. * @return The token representing the tag name, or null if it * could not be found. */ public static final Token getTagNameTokenForCaretOffset( RSyntaxTextArea textArea) { int dot = textArea.getCaretPosition(); int line = textArea.getCaretLineNumber(); Token toMark = null; do { Token t = textArea.getTokenListForLine(line); while (t!=null && t.isPaintable()) { if (t.getType()==Token.MARKUP_TAG_NAME) { toMark = t; } if (t.getEndOffset()==dot || t.containsPosition(dot)) { break; } if (t.getType()==Token.MARKUP_TAG_DELIMITER) { if (t.isSingleChar('>') || t.is(TAG_SELF_CLOSE)) { toMark = null; } } t = t.getNextToken(); } } while (toMark==null && --line>=0); return toMark; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy