blob: c213c879a4a090955a6546ea2867ac8d470fccd7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2009 Code 9 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:
* Code 9 Corporation - initial API and implementation
* Chris Aniszczyk <caniszczyk@gmail.com>
* Rafael Oliveira Nobrega <rafael.oliveira@gmail.com> - bug 242028
*******************************************************************************/
package org.eclipse.pde.internal.ds.ui.editor.contentassist;
import java.util.ArrayList;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ICompletionListener;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.pde.core.IBaseModel;
import org.eclipse.pde.internal.core.text.AbstractEditingModel;
import org.eclipse.pde.internal.core.text.IDocumentAttributeNode;
import org.eclipse.pde.internal.core.text.IDocumentElementNode;
import org.eclipse.pde.internal.core.text.IDocumentRange;
import org.eclipse.pde.internal.core.text.IDocumentTextNode;
import org.eclipse.pde.internal.core.text.IReconcilingParticipant;
import org.eclipse.pde.internal.ds.core.IDSComponent;
import org.eclipse.pde.internal.ds.core.IDSConstants;
import org.eclipse.pde.internal.ds.core.IDSModel;
import org.eclipse.pde.internal.ds.core.IDSObject;
import org.eclipse.pde.internal.ds.core.IDSProvide;
import org.eclipse.pde.internal.ds.core.IDSService;
import org.eclipse.pde.internal.ds.core.text.DSModel;
import org.eclipse.pde.internal.ui.editor.PDESourcePage;
public class DSContentAssistProcessor extends TypePackageCompletionProcessor
implements IContentAssistProcessor, ICompletionListener {
protected boolean fAssistSessionStarted;
private PDESourcePage fSourcePage;
private IDocumentRange fRange;
// proposal generation type
private static final int F_NO_ASSIST = 0, F_ADD_ATTRIB = 1,
F_ADD_CHILD = 2, F_OPEN_TAG = 3;
public DSContentAssistProcessor(PDESourcePage sourcePage) {
fSourcePage = sourcePage;
}
public void assistSessionEnded(ContentAssistEvent event) {
fRange = null;
}
public void assistSessionStarted(ContentAssistEvent event) {
fAssistSessionStarted = true;
}
public void selectionChanged(ICompletionProposal proposal,
boolean smartToggle) {
}
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
int offset) {
IDocument doc = viewer.getDocument();
IBaseModel model = fSourcePage.getInputContext().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
DSContentAssistText caText = DSContentAssistText.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);
}
return null;
}
private ICompletionProposal[] computeCATextProposal(IDocument doc,
int offset, DSContentAssistText 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;
// Element name
String element = guess[0];
// Attr name
String attribute = guess[1];
// Attr value
String attrValue = guess[2];
int attrValueLength = attrValue == null ? 0 : attrValue.length();
int startOffset = offests[2] + 1;
// Component
if (element != null
&& element.equals("scr:" + IDSConstants.ELEMENT_COMPONENT)) { //$NON-NLS-1$
boolean isAttrImmediate = attribute == null ? false : attribute
.equals(IDSConstants.ATTRIBUTE_COMPONENT_IMMEDIATE);
boolean isAttrEnabled = attribute == null ? false : attribute
.equals(IDSConstants.ATTRIBUTE_COMPONENT_ENABLED);
boolean isAttrConfigPolicy = attribute == null ? false : attribute
.equals(IDSConstants.ATTRIBUTE_COMPONENT_CONFIGURATION_POLICY);
if ((isAttrImmediate || isAttrEnabled)) {
return getCompletionBooleans(startOffset, attrValueLength);
}
if (isAttrConfigPolicy) {
return getConfigurationPolicyValues(attrValueLength,
startOffset);
}
// Service
} else if (element != null
&& element.equals(IDSConstants.ELEMENT_SERVICE)) {
boolean isAttrServFactory = attribute == null ? false : attribute
.equals(IDSConstants.ATTRIBUTE_SERVICE_FACTORY);
if (isAttrServFactory) {
return getCompletionBooleans(startOffset, attrValueLength);
}
// Reference
} else if (element != null
&& element.equals(IDSConstants.ELEMENT_REFERENCE)) {
boolean isAttrCardinality = attribute == null ? false : attribute
.equals(IDSConstants.ATTRIBUTE_REFERENCE_CARDINALITY);
boolean isAttrPolicy = attribute == null ? false : attribute
.equals(IDSConstants.ATTRIBUTE_REFERENCE_POLICY);
if (isAttrCardinality) {
return getReferenceCardinalityValues(attrValueLength,
startOffset);
} else if (isAttrPolicy) {
return getReferencePolicyValues(attrValueLength, startOffset);
}
// Property
} else if (element != null
&& element.equals(IDSConstants.ELEMENT_PROPERTY)) {
boolean isAttrType = attribute == null ? false : attribute
.equals(IDSConstants.ATTRIBUTE_PROPERTY_TYPE);
if (isAttrType) {
return getPropertyTypeValues(attrValueLength, startOffset);
}
}
return null;
}
/**
* Returns completion proposal with permitted values for Reference`s policy
* attribute
*
* @param attrValueLength
* @param startOffset
* @return ICompletionProposal array with completion proposals
*/
private ICompletionProposal[] getReferencePolicyValues(int attrValueLength,
int startOffset) {
return new ICompletionProposal[] {
new TypeCompletionProposal(
IDSConstants.VALUE_REFERENCE_POLICY_STATIC, null,
IDSConstants.VALUE_REFERENCE_POLICY_STATIC,
startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_REFERENCE_POLICY_DYNAMIC, null,
IDSConstants.VALUE_REFERENCE_POLICY_DYNAMIC,
startOffset, attrValueLength) };
}
private ICompletionProposal[] getConfigurationPolicyValues(
int attrValueLength, int startOffset) {
return new ICompletionProposal[] {
new TypeCompletionProposal(
IDSConstants.VALUE_CONFIGURATION_POLICY_IGNORE,
null,
IDSConstants.VALUE_CONFIGURATION_POLICY_IGNORE,
startOffset, attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_CONFIGURATION_POLICY_OPTIONAL, null,
IDSConstants.VALUE_CONFIGURATION_POLICY_OPTIONAL,
startOffset, attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_CONFIGURATION_POLICY_REQUIRE, null,
IDSConstants.VALUE_CONFIGURATION_POLICY_REQUIRE,
startOffset, attrValueLength) };
}
private ICompletionProposal[] getReferenceCardinalityValues(
int attrValueLength, int startOffset) {
return new ICompletionProposal[] {
new TypeCompletionProposal(
IDSConstants.VALUE_REFERENCE_CARDINALITY_ZERO_ONE,
null,
IDSConstants.VALUE_REFERENCE_CARDINALITY_ZERO_ONE,
startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_REFERENCE_CARDINALITY_ZERO_N, null,
IDSConstants.VALUE_REFERENCE_CARDINALITY_ZERO_N,
startOffset, attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_REFERENCE_CARDINALITY_ONE_ONE, null,
IDSConstants.VALUE_REFERENCE_CARDINALITY_ONE_ONE,
startOffset, attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_REFERENCE_CARDINALITY_ONE_N, null,
IDSConstants.VALUE_REFERENCE_CARDINALITY_ONE_N,
startOffset, attrValueLength) };
}
private ICompletionProposal[] getPropertyTypeValues(int attrValueLength,
int startOffset) {
return new ICompletionProposal[] {
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_STRING, null,
IDSConstants.VALUE_PROPERTY_TYPE_STRING, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_LONG, null,
IDSConstants.VALUE_PROPERTY_TYPE_LONG, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_DOUBLE, null,
IDSConstants.VALUE_PROPERTY_TYPE_DOUBLE, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_FLOAT, null,
IDSConstants.VALUE_PROPERTY_TYPE_FLOAT, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_INTEGER, null,
IDSConstants.VALUE_PROPERTY_TYPE_INTEGER, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_BYTE, null,
IDSConstants.VALUE_PROPERTY_TYPE_BYTE, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_CHAR, null,
IDSConstants.VALUE_PROPERTY_TYPE_CHAR, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_BOOLEAN, null,
IDSConstants.VALUE_PROPERTY_TYPE_BOOLEAN, startOffset,
attrValueLength),
new TypeCompletionProposal(
IDSConstants.VALUE_PROPERTY_TYPE_SHORT, null,
IDSConstants.VALUE_PROPERTY_TYPE_SHORT, startOffset,
attrValueLength) };
}
private ICompletionProposal[] getCompletionBooleans(int startOffset,
int attrValueLength) {
return new ICompletionProposal[] {
new TypeCompletionProposal(IDSConstants.VALUE_TRUE, null,
IDSConstants.VALUE_TRUE, startOffset, attrValueLength),
new TypeCompletionProposal(IDSConstants.VALUE_FALSE, null,
IDSConstants.VALUE_FALSE, startOffset, attrValueLength) };
}
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(node, offset, doc, null, node
.getXMLTagName());
// case F_OPEN_TAG:
// return new ICompletionProposal[] { new TypeCompletionProposal(
// "Open Tag", null, "Open Tag", offset, 0) };
case F_ADD_CHILD:
return computeAddChildProposal(node, offset, doc, null);
}
return null;
}
private ICompletionProposal[] computeAddAttributeProposal(
IDocumentElementNode node, int offset, IDocument doc,
String filter, String tag) {
ArrayList proposals = new ArrayList();
if (!(node instanceof IDSObject)) {
return null;
}
String[] attributesList = ((IDSObject) node).getAttributesNames();
if (attributesList == null || attributesList.length == 0) {
return null;
} else {
for (int i = 0; i < attributesList.length; i++) {
String attribute = attributesList[i];
// Lists all attributes already in use
IDocumentAttributeNode[] nodeAttributes = node
.getNodeAttributes();
boolean EqualToAnyItem = false;
for (int j = 0; j < nodeAttributes.length; j++) {
IDocumentAttributeNode documentAttributeNode = nodeAttributes[j];
EqualToAnyItem |= attribute.equals(documentAttributeNode
.getAttributeName());
}
// If the attribute is not in use, add it in the
// CompletionProposal
if (EqualToAnyItem == false) {
DSAttrCompletionProposal dsAttrCompletionProposal = new DSAttrCompletionProposal(
attributesList[i], offset, 0);
addFilteredProposal(offset, proposals,
dsAttrCompletionProposal, filter);
}
}
}
// cast the proposal elements to ICompletionProposal
if (proposals.size() > 0) {
ICompletionProposal proposalsArray[] = new ICompletionProposal[proposals
.size()];
for (int i = 0; i < proposals.size(); i++) {
proposalsArray[i] = (ICompletionProposal) proposals.get(i);
}
return proposalsArray;
} else {
return null;
}
}
private ICompletionProposal[] computeAddChildProposal(
IDocumentElementNode node, int offset, IDocument doc, String filter) {
if (node instanceof IDSComponent) {
return computeRootNodeProposals(node, offset, filter);
} else if (node instanceof IDSService) {
DSModel model = (DSModel) fSourcePage.getInputContext().getModel();
IDSProvide provide = model.getFactory().createProvide();
return new DSCompletionProposal[] { new DSCompletionProposal(
provide, offset) };
} else {
return null;
}
}
private ICompletionProposal[] computeRootNodeProposals(
IDocumentElementNode node, int offset, String filter) {
ArrayList proposals = new ArrayList();
IDSModel model = (DSModel) fSourcePage.getInputContext().getModel();
IDSComponent component = model.getDSComponent();
int length = filter != null ? filter.length() : 0;
addFilteredProposal(offset, proposals, new DSCompletionProposal(model
.getFactory().createProperty(), offset, length),
filter);
addFilteredProposal(offset, proposals, new DSCompletionProposal(model
.getFactory().createProperties(), offset, length),
filter);
addFilteredProposal(offset, proposals, new DSCompletionProposal(model
.getFactory().createReference(), offset, length),
filter);
boolean hasService = component.getService() != null;
if (!hasService) {
addFilteredProposal(offset, proposals, new DSCompletionProposal(
model.getFactory().createService(), offset, length), filter);
}
if (component.getImplementation() == null) {
addFilteredProposal(offset, proposals, new DSCompletionProposal(
model.getFactory().createImplementation(), offset, length), filter);
}
ICompletionProposal[] proposalsArray = new DSCompletionProposal[proposals
.size()];
for (int i = 0; i < proposalsArray.length; i++) {
proposalsArray[i] = (ICompletionProposal) proposals.get(i);
}
return proposalsArray;
}
private void addFilteredProposal(int offset, ArrayList proposals,
DSCompletionProposal proposal, String filter) {
if (filter == null || filter.length() == 0) {
proposals.add(proposal);
} else {
if (filter.regionMatches(true, 0, proposal.getDisplayString(), 0,
filter.length()))
proposals.add(proposal);
}
}
private void addFilteredProposal(int offset, ArrayList proposals,
DSAttrCompletionProposal proposal, String filter) {
if (filter == null || filter.length() == 0) {
proposals.add(proposal);
} else {
if (filter.regionMatches(true, 0, proposal.getDisplayString(), 0,
filter.length()))
proposals.add(proposal);
}
}
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 String[] guessContentRequest(int[] offset, IDocument doc,
boolean brokenModel) {
StringBuffer nodeBuffer = new StringBuffer();
StringBuffer attrBuffer = new StringBuffer();
StringBuffer attrValBuffer = new StringBuffer();
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 };
}
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();
}
}
public void dispose() {
}
protected ITextSelection getCurrentSelection() {
ISelection sel = fSourcePage.getSelectionProvider().getSelection();
if (sel instanceof ITextSelection)
return (ITextSelection) sel;
return null;
}
protected void flushDocument() {
fSourcePage.getInputContext().flushEditorInput();
}
}