blob: a94410cfba6ce40ad803e75022ee2262ad1785b9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Remy Chi Jian Suen <remy.suen@gmail.com> - bug 201566
* Benjamin Cabe <benjamin.cabe@anyware-tech.com> - bug 227105
*******************************************************************************/
package org.eclipse.pde.internal.ui.editor.contentassist;
import java.util.*;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jface.text.*;
import org.eclipse.jface.text.contentassist.*;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.pde.core.IBaseModel;
import org.eclipse.pde.core.IIdentifiable;
import org.eclipse.pde.core.plugin.*;
import org.eclipse.pde.internal.core.ischema.*;
import org.eclipse.pde.internal.core.text.*;
import org.eclipse.pde.internal.core.text.plugin.PluginModelBase;
import org.eclipse.pde.internal.core.util.IdUtil;
import org.eclipse.pde.internal.core.util.PDESchemaHelper;
import org.eclipse.pde.internal.ui.PDEPluginImages;
import org.eclipse.pde.internal.ui.PDEUIMessages;
import org.eclipse.pde.internal.ui.editor.PDEFormEditor;
import org.eclipse.pde.internal.ui.editor.PDESourcePage;
import org.eclipse.pde.internal.ui.editor.text.XMLUtil;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.forms.editor.FormEditor;
public class XMLContentAssistProcessor extends TypePackageCompletionProcessor implements ICompletionListener {
protected boolean fAssistSessionStarted;
// Specific assist types
protected static final int F_INFER_BY_OBJECT = -1;
protected static final int F_EXTENSION_POINT = 0;
protected static final int F_EXTENSION = 1;
protected static final int F_ELEMENT = 2;
protected static final int F_ATTRIBUTE = 3;
protected static final int F_CLOSE_TAG = 4;
protected static final int F_ATTRIBUTE_VALUE = 5;
protected static final int F_EXTENSION_ATTRIBUTE_POINT_VALUE = 6;
protected static final int F_EXTENSION_POINT_AND_VALUE = 7;
protected static final int F_ATTRIBUTE_ID_VALUE = 8;
protected static final int F_ATTRIBUTE_BOOLEAN_VALUE = 9;
protected static final int F_TOTAL_TYPES = 10;
// proposal generation type
private static final int F_NO_ASSIST = 0, F_ADD_ATTRIB = 1, F_ADD_CHILD = 2, F_OPEN_TAG = 3;
private static final ArrayList<VirtualSchemaObject> F_V_BOOLS = new ArrayList<>();
static {
F_V_BOOLS.add(new VirtualSchemaObject("true", null, F_ATTRIBUTE_BOOLEAN_VALUE)); //$NON-NLS-1$
F_V_BOOLS.add(new VirtualSchemaObject("false", null, F_ATTRIBUTE_BOOLEAN_VALUE)); //$NON-NLS-1$
}
private static final String F_STR_EXT_PT = "extension-point"; //$NON-NLS-1$
private static final String F_STR_EXT = "extension"; //$NON-NLS-1$
private PDESourcePage fSourcePage;
private final Image[] fImages = new Image[F_TOTAL_TYPES];
// TODO add a listener to add/remove extension points as they are added/removed from working models
private IDocumentRange fRange;
private int fDocLen = -1;
/** All external plug-in extension points */
private ArrayList<ISchemaObject> fExternalExtPoints;
/** All internal plug-in extension points */
private ArrayList<VirtualSchemaObject> fInternalExtPoints;
/** All external and internal plug-in extension points */
private ArrayList<ISchemaObject> fAllExtPoints;
public XMLContentAssistProcessor(PDESourcePage sourcePage) {
fSourcePage = sourcePage;
}
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
IDocument doc = viewer.getDocument();
int docLen = doc.getLength();
if (docLen == fDocLen)
return null; // left/right cursor has been pressed - cancel content assist
fDocLen = docLen;
IBaseModel model = getModel();
if (model instanceof AbstractEditingModel && fSourcePage.isDirty() && ((AbstractEditingModel) model).isStale() && fRange == null) {
((AbstractEditingModel) model).reconciled(doc);
} else if (fAssistSessionStarted) {
// Always reconcile when content assist is first invoked
// Fix Bug # 149478
((AbstractEditingModel) model).reconciled(doc);
fAssistSessionStarted = false;
}
if (fRange == null) {
assignRange(offset);
} else {
// TODO - we may be looking at the wrong fRange
// when this happens --> reset it and reconcile
// how can we tell if we are looking at the wrong one... ?
boolean resetAndReconcile = false;
if (!(fRange instanceof IDocumentAttributeNode))
// too easy to reconcile.. this is temporary
resetAndReconcile = true;
if (resetAndReconcile) {
fRange = null;
if (model instanceof IReconcilingParticipant)
((IReconcilingParticipant) model).reconciled(doc);
}
}
// Get content assist text if any
XMLContentAssistText caText = XMLContentAssistText.parse(offset, doc);
if (caText != null) {
return computeCATextProposal(doc, offset, caText);
} else if (fRange instanceof IDocumentAttributeNode) {
return computeCompletionProposal((IDocumentAttributeNode) fRange, offset, doc);
} else if (fRange instanceof IDocumentElementNode) {
return computeCompletionProposal((IDocumentElementNode) fRange, offset, doc);
} else if (fRange instanceof IDocumentTextNode) {
return null;
} else if (model instanceof PluginModelBase) {
// broken model - infer from text content
return computeBrokenModelProposal(((PluginModelBase) model).getLastErrorNode(), offset, doc);
}
return null;
}
protected ICompletionProposal[] debugPrintProposals(ICompletionProposal[] proposals, String id, boolean print) {
if (proposals == null) {
System.out.println("[0] " + id); //$NON-NLS-1$
return proposals;
}
System.out.println("[" + proposals.length + "] " + id); //$NON-NLS-1$ //$NON-NLS-2$
if (print == false) {
return proposals;
}
for (ICompletionProposal proposal : proposals) {
System.out.println(proposal.getDisplayString());
}
return proposals;
}
private void assignRange(int offset) {
fRange = fSourcePage.getRangeElement(offset, true);
if (fRange == null)
return;
// if we are rigth AT (cursor before) the range, we want to contribute
// to its parent
if (fRange instanceof IDocumentAttributeNode) {
if (((IDocumentAttributeNode) fRange).getNameOffset() == offset)
fRange = ((IDocumentAttributeNode) fRange).getEnclosingElement();
} else if (fRange instanceof IDocumentElementNode) {
if (((IDocumentElementNode) fRange).getOffset() == offset)
fRange = ((IDocumentElementNode) fRange).getParentNode();
} else if (fRange instanceof IDocumentTextNode) {
if (((IDocumentTextNode) fRange).getOffset() == offset)
fRange = ((IDocumentTextNode) fRange).getEnclosingElement();
}
}
private ICompletionProposal[] computeCATextProposal(IDocument doc, int offset, XMLContentAssistText caText) {
fRange = fSourcePage.getRangeElement(offset, true);
if ((fRange != null) && (fRange instanceof IDocumentTextNode)) {
// We have a text node.
// Get its parent element
fRange = ((IDocumentTextNode) fRange).getEnclosingElement();
}
if ((fRange != null) && (fRange instanceof IDocumentElementNode)) {
return computeAddChildProposal((IDocumentElementNode) fRange, caText.getStartOffset(), doc, caText.getText());
}
return null;
}
private ICompletionProposal[] computeCompletionProposal(IDocumentAttributeNode attr, int offset, IDocument doc) {
if (offset < attr.getValueOffset())
return null;
int[] offests = new int[] {offset, offset, offset};
String[] guess = guessContentRequest(offests, doc, false);
if (guess == null)
return null;
// String element = guess[0];
// String attribute = guess[1];
String attrValue = guess[2];
IPluginObject obj = XMLUtil.getTopLevelParent(attr);
if (obj instanceof IPluginExtension) {
if (attr.getAttributeName().equals(IPluginExtension.P_POINT) && offset >= attr.getValueOffset()) {
return computeExtPointAttrProposals(attr, offset, attrValue);
}
ISchemaAttribute sAttr = XMLUtil.getSchemaAttribute(attr, ((IPluginExtension) obj).getPoint());
if (sAttr == null)
return null;
if (sAttr.getKind() == IMetaAttribute.JAVA) {
IResource resource = obj.getModel().getUnderlyingResource();
if (resource == null)
return null;
// Revisit: NEW CODE HERE
ArrayList<Object> list = new ArrayList<>();
ICompletionProposal[] proposals = null;
generateTypePackageProposals(attrValue, resource.getProject(), list, offset - attrValue.length(), IJavaSearchConstants.CLASS_AND_INTERFACE);
if (!list.isEmpty()) {
// Convert the results array list into an array of completion
// proposals
proposals = list.toArray(new ICompletionProposal[list.size()]);
sortCompletions(proposals);
return proposals;
}
return null;
} else if (sAttr.getKind() == IMetaAttribute.RESOURCE) {
// provide proposals with all resources in current plugin?
} else if (sAttr.getKind() == IMetaAttribute.IDENTIFIER) {
String[] validAttributes = PDESchemaHelper.getValidAttributes(sAttr).keySet().toArray(new String[0]);
Arrays.sort(validAttributes);
ArrayList<VirtualSchemaObject> objs = new ArrayList<>(validAttributes.length);
for (String validAttribute : validAttributes)
objs.add(new VirtualSchemaObject(validAttribute, null, F_ATTRIBUTE_ID_VALUE));
return computeAttributeProposal(attr, offset, attrValue, objs);
} else { // we have an IMetaAttribute.STRING kind
if (sAttr.getType() == null)
return null;
ISchemaRestriction sRestr = (sAttr.getType()).getRestriction();
ArrayList<VirtualSchemaObject> objs = new ArrayList<>();
if (sRestr == null) {
ISchemaSimpleType type = sAttr.getType();
if (type != null && type.getName().equals("boolean")) //$NON-NLS-1$
objs = F_V_BOOLS;
} else {
Object[] restrictions = sRestr.getChildren();
for (Object restriction : restrictions)
if (restriction instanceof ISchemaObject)
objs.add(new VirtualSchemaObject(((ISchemaObject) restriction).getName(), null, F_ATTRIBUTE_VALUE));
}
return computeAttributeProposal(attr, offset, attrValue, objs);
}
} else if (obj instanceof IPluginExtensionPoint) {
if (attr.getAttributeValue().equals(IPluginExtensionPoint.P_SCHEMA)) {
// provide proposals with all schema files in current plugin?
}
}
return null;
}
private ICompletionProposal[] computeExtPointAttrProposals(IDocumentAttributeNode attribute, int offset, String currentAttributeValue) {
// Get all the applicable extension points
ArrayList<ISchemaObject> allExtensionPoints = getAllExtensionPoints(F_EXTENSION_ATTRIBUTE_POINT_VALUE);
// Ensure we found extension points
if ((allExtensionPoints == null) || (allExtensionPoints.isEmpty())) {
return null;
}
// If there is no current attribute value, then the applicable extension
// points do not need to be filtered. Return the list as is
if ((currentAttributeValue == null) || (currentAttributeValue.length() == 0)) {
return convertListToProposal(allExtensionPoints, attribute, offset);
}
// Create the filtered proposal list
ArrayList<ISchemaObject> filteredProposalList = new ArrayList<>();
// Filter the applicable extension points by the current attribute
// value
filterExtPointAttrProposals(filteredProposalList, allExtensionPoints, currentAttributeValue);
// Convert into a digestable list of proposals
return convertListToProposal(filteredProposalList, attribute, offset);
}
private ICompletionProposal[] computeRootNodeProposals(IDocumentElementNode node, int offset, String filter) {
// Create the filtered proposal list
ArrayList<ISchemaObject> filteredProposalList = new ArrayList<>();
// Add extension to the list
addToList(filteredProposalList, filter, new VirtualSchemaObject(F_STR_EXT, PDEUIMessages.XMLContentAssistProcessor_extensions, F_EXTENSION));
// Add extension point to the list
addToList(filteredProposalList, filter, new VirtualSchemaObject(F_STR_EXT_PT, PDEUIMessages.XMLContentAssistProcessor_extensionPoints, F_EXTENSION_POINT));
// Get all avaliable extensions with point attribute values
ArrayList<ISchemaObject> allExtensionPoints = getAllExtensionPoints(F_EXTENSION_POINT_AND_VALUE);
// Ensure we found extension points
if ((allExtensionPoints == null) || (allExtensionPoints.isEmpty())) {
// Return the current proposal list without extension points
return convertListToProposal(filteredProposalList, node, offset);
}
// If there is no current value, then the applicable extension
// points do not need to be filtered. Merge the list as is with the
// existing proposals
if ((filter == null) || (filter.length() == 0)) {
filteredProposalList.addAll(allExtensionPoints);
return convertListToProposal(filteredProposalList, node, offset);
}
// Filter the applicable extension points by the current value
filterExtPointAttrProposals(filteredProposalList, allExtensionPoints, filter);
// Convert into a digestable list of proposals
return convertListToProposal(filteredProposalList, node, offset);
}
/**
* @param filteredProposalList - Must not be NULL
* @param allExtensionPoints - Must not be NULL
* @param filter - Must not be NULL
*/
private void filterExtPointAttrProposals(ArrayList<ISchemaObject> filteredProposalList, ArrayList<ISchemaObject> allExtensionPoints, String filter) {
// Create a regex pattern out of the current attribute value
// Ignore case
String patternString = "(?i)" + filter; //$NON-NLS-1$
// Compile the pattern
Pattern pattern = Pattern.compile(patternString);
// Iterate over the applicable extension points and add extension points
// matching the pattern to the filtered proposal list
Iterator<ISchemaObject> iterator = allExtensionPoints.iterator();
while (iterator.hasNext()) {
// Get the schema object
ISchemaObject schemaObject = iterator.next();
// Ensure the schema object is defined
if (schemaObject == null) {
continue;
}
// Get the name of the schema object
String name = schemaObject.getName();
// If the current attribute value matches some part of the name
// add it to the list
if (pattern.matcher(name).find()) {
filteredProposalList.add(schemaObject);
}
}
}
private ICompletionProposal[] computeAttributeProposal(IDocumentAttributeNode attr, int offset, String currValue, List<VirtualSchemaObject> validValues) {
if (validValues == null)
return null;
ArrayList<ISchemaObject> list = new ArrayList<>();
for (int i = 0; i < validValues.size(); i++)
addToList(list, currValue, validValues.get(i));
return convertListToProposal(list, attr, offset);
}
private ICompletionProposal[] computeCompletionProposal(IDocumentElementNode node, int offset, IDocument doc) {
int prop_type = determineAssistType(node, doc, offset);
switch (prop_type) {
case F_ADD_ATTRIB :
return computeAddAttributeProposal(F_INFER_BY_OBJECT, node, offset, doc, null, node.getXMLTagName());
case F_OPEN_TAG :
return computeOpenTagProposal(node, offset, doc);
case F_ADD_CHILD :
return computeAddChildProposal(node, offset, doc, null);
}
return null;
}
private int determineAssistType(IDocumentElementNode node, IDocument doc, int offset) {
int len = node.getLength();
int off = node.getOffset();
if (len == -1 || off == -1)
return F_NO_ASSIST;
offset = offset - off; // look locally
if (offset > node.getXMLTagName().length() + 1) {
try {
String eleValue = doc.get(off, len);
int ind = eleValue.indexOf('>');
if (ind > 0 && eleValue.charAt(ind - 1) == '/')
ind -= 1;
if (offset <= ind) {
if (canInsertAttrib(eleValue, offset))
return F_ADD_ATTRIB;
return F_NO_ASSIST;
}
ind = eleValue.lastIndexOf('<');
if (ind == 0 && offset == len - 1)
return F_OPEN_TAG; // childless node - check if it can be cracked open
if (ind + 1 < len && eleValue.charAt(ind + 1) == '/' && offset <= ind)
return F_ADD_CHILD;
} catch (BadLocationException e) {
}
}
return F_NO_ASSIST;
}
private boolean canInsertAttrib(String eleValue, int offset) {
// character before offset must be whitespace
// character on offset must be whitespace, '/' or '>'
char c = eleValue.charAt(offset);
return offset - 1 >= 0 && Character.isWhitespace(eleValue.charAt(offset - 1)) && (Character.isWhitespace(c) || c == '/' || c == '>');
}
private ICompletionProposal[] computeAddChildProposal(IDocumentElementNode node, int offset, IDocument doc, String filter) {
ArrayList<ISchemaObject> propList = new ArrayList<>();
if (node instanceof IPluginBase) {
return computeRootNodeProposals(node, offset, filter);
} else if (node instanceof IPluginExtensionPoint) {
return null;
} else {
IPluginObject obj = XMLUtil.getTopLevelParent(node);
if (obj instanceof IPluginExtension) {
ISchemaElement sElement = XMLUtil.getSchemaElement(node, ((IPluginExtension) obj).getPoint());
if ((sElement != null) && (sElement.getType() instanceof ISchemaComplexType)) {
// We have a schema complex type. Either the element has attributes
// or the element has children.
// Generate the list of element proposals
Set<ISchemaElement> elementSet = XMLElementProposalComputer.computeElementProposal(sElement, node);
// Filter the list of element proposals
Iterator<ISchemaElement> iterator = elementSet.iterator();
while (iterator.hasNext()) {
addToList(propList, filter, iterator.next());
}
} else {
return null;
}
}
}
return convertListToProposal(propList, node, offset);
}
private ICompletionProposal[] computeOpenTagProposal(IDocumentElementNode node, int offset, IDocument doc) {
IPluginObject obj = XMLUtil.getTopLevelParent(node);
if (obj instanceof IPluginExtension) {
ISchemaElement sElem = XMLUtil.getSchemaElement(node, ((IPluginExtension) obj).getPoint());
if (sElem == null)
return null;
ISchemaCompositor comp = ((ISchemaComplexType) sElem.getType()).getCompositor();
if (comp != null)
return new ICompletionProposal[] {new XMLCompletionProposal(node, null, offset, this)};
}
return null;
}
private ICompletionProposal[] computeAddAttributeProposal(int type, IDocumentElementNode node, int offset, IDocument doc, String filter, String tag) {
String nodeName = tag;
if (nodeName == null && node != null)
nodeName = node.getXMLTagName();
if (type == F_EXTENSION || node instanceof IPluginExtension) {
ISchemaElement sElem = XMLUtil.getSchemaElement(node, node != null ? ((IPluginExtension) node).getPoint() : null);
ISchemaObject[] sAttrs = sElem != null ? sElem.getAttributes() : new ISchemaObject[] {new VirtualSchemaObject(IIdentifiable.P_ID, PDEUIMessages.XMLContentAssistProcessor_extId, F_ATTRIBUTE), new VirtualSchemaObject(IPluginObject.P_NAME, PDEUIMessages.XMLContentAssistProcessor_extName, F_ATTRIBUTE), new VirtualSchemaObject(IPluginExtension.P_POINT, PDEUIMessages.XMLContentAssistProcessor_extPoint, F_ATTRIBUTE)};
return computeAttributeProposals(sAttrs, node, offset, filter, nodeName);
} else if (type == F_EXTENSION_POINT || node instanceof IPluginExtensionPoint) {
ISchemaObject[] sAttrs = new ISchemaObject[] {new VirtualSchemaObject(IIdentifiable.P_ID, PDEUIMessages.XMLContentAssistProcessor_extPointId, F_ATTRIBUTE), new VirtualSchemaObject(IPluginObject.P_NAME, PDEUIMessages.XMLContentAssistProcessor_extPointName, F_ATTRIBUTE), new VirtualSchemaObject(IPluginExtensionPoint.P_SCHEMA, PDEUIMessages.XMLContentAssistProcessor_schemaLocation, F_ATTRIBUTE)};
return computeAttributeProposals(sAttrs, node, offset, filter, nodeName);
} else {
IPluginObject obj = XMLUtil.getTopLevelParent(node);
if (obj instanceof IPluginExtension) {
ISchemaElement sElem = XMLUtil.getSchemaElement(node, node != null ? ((IPluginExtension) obj).getPoint() : null);
ISchemaObject[] sAttrs = sElem != null ? sElem.getAttributes() : null;
return computeAttributeProposals(sAttrs, node, offset, filter, nodeName);
}
}
return null;
}
private void addToList(ArrayList<ISchemaObject> list, String filter, ISchemaObject object) {
if (object == null)
return;
if (filter == null || filter.length() == 0)
list.add(object);
else {
String name = object.getName();
if (filter.regionMatches(true, 0, name, 0, filter.length()))
list.add(object);
}
}
private ICompletionProposal[] computeBrokenModelProposal(IDocumentElementNode parent, int offset, IDocument doc) {
if (parent == null)
return null;
int[] offArr = new int[] {offset, offset, offset};
String[] guess = guessContentRequest(offArr, doc, true);
if (guess == null)
return null;
int elRepOffset = offArr[0];
int atRepOffset = offArr[1];
int atValRepOffest = offArr[2];
String element = guess[0];
String attr = guess[1];
String attVal = guess[2];
IPluginObject obj = XMLUtil.getTopLevelParent(parent);
if (obj instanceof IPluginExtension) {
String point = ((IPluginExtension) obj).getPoint();
if (attr == null)
// search for element proposals
return computeAddChildProposal(parent, elRepOffset, doc, element);
ISchemaElement sEle = XMLUtil.getSchemaElement(parent, point);
if (sEle == null)
return null;
sEle = sEle.getSchema().findElement(element);
if (sEle == null)
return null;
if (attr.indexOf('=') != -1)
// search for attribute content proposals
return computeBrokenModelAttributeContentProposal(parent, atValRepOffest, element, attr, attVal);
// search for attribute proposals
return computeAttributeProposals(sEle.getAttributes(), null, atRepOffset, attr, element);
} else if (parent instanceof IPluginBase) {
if (attr == null)
return computeAddChildProposal(parent, elRepOffset, doc, element);
if (element.equalsIgnoreCase(F_STR_EXT))
return computeAddAttributeProposal(F_EXTENSION, null, atRepOffset, doc, attr, F_STR_EXT);
if (element.equalsIgnoreCase(F_STR_EXT_PT))
return computeAddAttributeProposal(F_EXTENSION_POINT, null, atRepOffset, doc, attr, F_STR_EXT_PT);
}
return null;
}
private ICompletionProposal[] computeBrokenModelAttributeContentProposal(IDocumentElementNode parent, int offset, String element, String attr, String filter) {
// TODO use computeCompletionProposal(IDocumentAttributeNode attr, int offset) if possible
// or refactor above to be used here
// CURRENTLY: attribute completion only works in non-broken models
return null;
}
private String[] guessContentRequest(int[] offset, IDocument doc, boolean brokenModel) {
StringBuilder nodeBuffer = new StringBuilder();
StringBuilder attrBuffer = new StringBuilder();
StringBuilder attrValBuffer = new StringBuilder();
String node = null;
String attr = null;
String attVal = null;
int quoteCount = 0;
try {
while (--offset[0] >= 0) {
char c = doc.getChar(offset[0]);
if (c == '"') {
quoteCount += 1;
nodeBuffer.setLength(0);
attrBuffer.setLength(0);
if (attVal != null) // ran into 2nd quotation mark, we are out of range
continue;
offset[2] = offset[0];
attVal = attrValBuffer.toString();
} else if (Character.isWhitespace(c)) {
nodeBuffer.setLength(0);
if (attr == null) {
offset[1] = offset[0];
int attBuffLen = attrBuffer.length();
if (attBuffLen > 0 && attrBuffer.charAt(attBuffLen - 1) == '=')
attrBuffer.setLength(attBuffLen - 1);
attr = attrBuffer.toString();
}
} else if (c == '<') {
node = nodeBuffer.toString();
break;
} else if (c == '>') {
// only enable content assist if user is inside an open tag
return null;
} else {
attrValBuffer.insert(0, c);
attrBuffer.insert(0, c);
nodeBuffer.insert(0, c);
}
}
} catch (BadLocationException e) {
}
if (node == null)
return null;
if (quoteCount % 2 == 0)
attVal = null;
else if (brokenModel)
return null; // open quotes - don't provide assist
return new String[] {node, attr, attVal};
}
protected IBaseModel getModel() {
return fSourcePage.getInputContext().getModel();
}
protected ITextSelection getCurrentSelection() {
ISelection sel = fSourcePage.getSelectionProvider().getSelection();
if (sel instanceof ITextSelection)
return (ITextSelection) sel;
return null;
}
protected void flushDocument() {
fSourcePage.getInputContext().flushEditorInput();
}
private ICompletionProposal[] computeAttributeProposals(ISchemaObject[] sAttrs, IDocumentElementNode node, int offset, String filter, String parentName) {
if (sAttrs == null || sAttrs.length == 0)
return null;
IDocumentAttributeNode[] attrs = node != null ? node.getNodeAttributes() : new IDocumentAttributeNode[0];
ArrayList<ISchemaObject> list = new ArrayList<>();
for (ISchemaObject attr : sAttrs) {
int k; // if we break early we wont add
for (k = 0; k < attrs.length; k++)
if (attrs[k].getAttributeName().equals(attr.getName()))
break;
if (k == attrs.length)
addToList(list, filter, attr);
}
if (filter != null && filter.length() == 0)
list.add(0, new VirtualSchemaObject(parentName, null, F_CLOSE_TAG));
return convertListToProposal(list, node, offset);
}
private ICompletionProposal[] convertListToProposal(ArrayList<ISchemaObject> list, IDocumentRange range, int offset) {
ICompletionProposal[] proposals = new ICompletionProposal[list.size()];
if (proposals.length == 0)
return null;
for (int i = 0; i < proposals.length; i++)
proposals[i] = new XMLCompletionProposal(range, list.get(i), offset, this);
return proposals;
}
@Override
public void assistSessionEnded(ContentAssistEvent event) {
fRange = null;
// Reset cached internal and external extension point proposals
fAllExtPoints = null;
// Reset cached internal point proposals
// Note: Not resetting cached external point proposals
// Assumption is that the users workspace can change; but, not the
// platform including all the external plugins
fInternalExtPoints = null;
fDocLen = -1;
}
@Override
public void assistSessionStarted(ContentAssistEvent event) {
fAssistSessionStarted = true;
}
@Override
public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
}
@SuppressWarnings("unchecked")
private ArrayList<ISchemaObject> getAllExtensionPoints(int vSchemaType) {
// Return the previous extension points if defined
if (fAllExtPoints != null) {
return fAllExtPoints;
}
// Get the plugin model base
IPluginModelBase model = getPluginModelBase();
// Note: All plug-in extension points are cached except the
// extension points defined by the plugin.xml we are currently
// editing. This means if a plug-in in the workspace defines a new
// extension point and the plugin.xml editor is still open, the
// new extension point will not show up as a proposal because it is
// using a cached list of extension points.
// External extensions points are all extension points not defined by
// the plugin currently being edited - opposed to non-workpspace
// plugins. Internal extension points are the extension points defined
// by the plugin currently being edited. May want to modify this
// behaviour in the future.
// Add all external extension point proposals to the list
fAllExtPoints = (ArrayList<ISchemaObject>) getExternalExtensionPoints(model, vSchemaType).clone();
// Add all internal extension point proposals to the list
fAllExtPoints.addAll(getInternalExtensionPoints(model, vSchemaType));
return fAllExtPoints;
}
private ArrayList<ISchemaObject> getExternalExtensionPoints(IPluginModelBase model, int vSchemaType) {
// Return the previous external extension points if defined
if (fExternalExtPoints != null) {
updateExternalExtPointTypes(vSchemaType);
return fExternalExtPoints;
}
// Query for all external extension points
fExternalExtPoints = new ArrayList<>();
// Get all plug-ins in the workspace
IPluginModelBase[] plugins = PluginRegistry.getActiveModels();
// Process each plugin
for (IPluginModelBase plugin : plugins) {
// Make sure this plugin is not the one we are currently
// editing which defines internal extension points.
// We don't want to cache internal extension points because the
// workspace can change.
if (plugin.getPluginBase().getId().equals(model.getPluginBase().getId())) {
// Skip this plugin
continue;
}
// Get all extension points defined by this plugin
IPluginExtensionPoint[] points = plugin.getPluginBase().getExtensionPoints();
// Process each extension point
for (IPluginExtensionPoint point : points) {
VirtualSchemaObject vObject = new VirtualSchemaObject(IdUtil.getFullId(point, model), point, vSchemaType);
// Add the proposal to the list
fExternalExtPoints.add(vObject);
}
}
return fExternalExtPoints;
}
/**
* Handles edge case.
* External extension points are cached.
* As a result, these types persist over each other depending on what
* context content assist is invoked first:
* F_EXTENSION_ATTRIBUTE_POINT_VALUE
* F_EXTENSION_POINT_AND_VALUE
* @param newVType
*/
private void updateExternalExtPointTypes(int newVType) {
// Ensure we have proposals
if (fExternalExtPoints.isEmpty()) {
return;
}
// Get the first proposal
VirtualSchemaObject vObject = (VirtualSchemaObject) fExternalExtPoints.get(0);
// If the first proposals type is the same as the new type, then we
// do not have to do the update. That is because all the proposals
// have the same type.
if (vObject.getVType() == newVType) {
return;
}
// Update all the proposals with the new type
Iterator<ISchemaObject> iterator = fExternalExtPoints.iterator();
while (iterator.hasNext()) {
((VirtualSchemaObject) iterator.next()).setVType(newVType);
}
}
private ArrayList<VirtualSchemaObject> getInternalExtensionPoints(IPluginModelBase model, int vSchemaType) {
// Return the previous internal extension points if defined
if (fInternalExtPoints != null) {
// Realistically, this line should never be hit
return fInternalExtPoints;
}
fInternalExtPoints = new ArrayList<>();
// Get all extension points defined by this plugin
IPluginExtensionPoint[] points = model.getPluginBase().getExtensionPoints();
// Process each extension point
for (IPluginExtensionPoint point : points) {
VirtualSchemaObject vObject = new VirtualSchemaObject(IdUtil.getFullId(point, model), point, vSchemaType);
// Add the proposal to the list
fInternalExtPoints.add(vObject);
}
return fInternalExtPoints;
}
/**
* Returns a BundlePluginModel which has a getId() method that works.
* getModel() method returns a PluginModel whose getId() method does not
* work.
*/
private IPluginModelBase getPluginModelBase() {
FormEditor formEditor = fSourcePage.getEditor();
if ((formEditor instanceof PDEFormEditor) == false) {
return null;
}
IBaseModel bModel = ((PDEFormEditor) formEditor).getAggregateModel();
if ((bModel instanceof IPluginModelBase) == false) {
return null;
}
return (IPluginModelBase) bModel;
}
public Image getImage(int type) {
if (fImages[type] == null) {
switch (type) {
case F_EXTENSION_POINT :
case F_EXTENSION_ATTRIBUTE_POINT_VALUE :
return fImages[type] = PDEPluginImages.DESC_EXT_POINT_OBJ.createImage();
case F_EXTENSION_POINT_AND_VALUE :
case F_EXTENSION :
return fImages[type] = PDEPluginImages.DESC_EXTENSION_OBJ.createImage();
case F_ELEMENT :
case F_CLOSE_TAG :
return fImages[type] = PDEPluginImages.DESC_XML_ELEMENT_OBJ.createImage();
case F_ATTRIBUTE :
case F_ATTRIBUTE_VALUE :
return fImages[type] = PDEPluginImages.DESC_ATT_URI_OBJ.createImage();
case F_ATTRIBUTE_ID_VALUE :
return fImages[type] = PDEPluginImages.DESC_ATT_ID_OBJ.createImage();
case F_ATTRIBUTE_BOOLEAN_VALUE :
return fImages[type] = PDEPluginImages.DESC_ATT_BOOLEAN_OBJ.createImage();
}
}
return fImages[type];
}
public void dispose() {
for (int i = 0; i < fImages.length; i++)
if (fImages[i] != null && !fImages[i].isDisposed())
fImages[i].dispose();
}
public PDESourcePage getSourcePage() {
return fSourcePage;
}
}