blob: 3155c07390cb984aba9c6b836bff208106cefe2a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2015 GEBIT Gesellschaft fuer EDV-Beratung
* und Informatik-Technologien mbH,
* Berlin, Duesseldorf, Frankfurt (Germany) 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:
* GEBIT Gesellschaft fuer EDV-Beratung und Informatik-Technologien mbH - initial API and implementation
* IBM Corporation - bug fixes
* John-Mason P. Shackelford (john-mason.shackelford@pearson.com) - bug 49383, 56299, 59024
* Brock Janiczak (brockj_eclipse@ihug.com.au ) - bug 78028, 78030
* Remy Chi Jian Suen - bug 277587
*******************************************************************************/
package org.eclipse.ant.internal.ui.editor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.tools.ant.AntTypeDefinition;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.ComponentHelper;
import org.apache.tools.ant.ExtensionPoint;
import org.apache.tools.ant.IntrospectionHelper;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.taskdefs.MacroDef;
import org.apache.tools.ant.taskdefs.MacroDef.Attribute;
import org.apache.tools.ant.taskdefs.MacroDef.TemplateElement;
import org.apache.tools.ant.taskdefs.MacroInstance;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.Reference;
import org.eclipse.ant.internal.core.IAntCoreConstants;
import org.eclipse.ant.internal.ui.AntUIImages;
import org.eclipse.ant.internal.ui.AntUIPlugin;
import org.eclipse.ant.internal.ui.IAntUIConstants;
import org.eclipse.ant.internal.ui.dtd.IAttribute;
import org.eclipse.ant.internal.ui.dtd.IDfm;
import org.eclipse.ant.internal.ui.dtd.IElement;
import org.eclipse.ant.internal.ui.dtd.ISchema;
import org.eclipse.ant.internal.ui.dtd.ParseError;
import org.eclipse.ant.internal.ui.dtd.Parser;
import org.eclipse.ant.internal.ui.editor.TaskDescriptionProvider.ProposalNode;
import org.eclipse.ant.internal.ui.editor.templates.AntContext;
import org.eclipse.ant.internal.ui.editor.templates.AntTemplateAccess;
import org.eclipse.ant.internal.ui.editor.templates.AntTemplateInformationControlCreator;
import org.eclipse.ant.internal.ui.editor.templates.AntTemplateProposal;
import org.eclipse.ant.internal.ui.editor.templates.BuildFileContextType;
import org.eclipse.ant.internal.ui.editor.templates.TargetContextType;
import org.eclipse.ant.internal.ui.editor.templates.TaskContextType;
import org.eclipse.ant.internal.ui.model.AntDefiningTaskNode;
import org.eclipse.ant.internal.ui.model.AntElementNode;
import org.eclipse.ant.internal.ui.model.AntModel;
import org.eclipse.ant.internal.ui.model.AntProjectNode;
import org.eclipse.ant.internal.ui.model.AntTargetNode;
import org.eclipse.ant.internal.ui.model.AntTaskNode;
import org.eclipse.ant.internal.ui.model.IAntElement;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
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.IContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistantExtension2;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateCompletionProcessor;
import org.eclipse.jface.text.templates.TemplateContext;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.progress.IProgressService;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.ibm.icu.text.MessageFormat;
/**
* The completion processor for the Ant Editor.
*/
public class AntEditorCompletionProcessor extends TemplateCompletionProcessor implements ICompletionListener {
private Comparator<ICompletionProposal> proposalComparator = new Comparator<ICompletionProposal>() {
@Override
public int compare(ICompletionProposal o1, ICompletionProposal o2) {
int type1 = getProposalType(o1);
int type2 = getProposalType(o2);
if (type1 != type2) {
if (type1 > type2) {
return 1;
}
return -1;
}
String string1 = o1.getDisplayString();
String string2 = o2.getDisplayString();
return string1.compareToIgnoreCase(string2);
}
private int getProposalType(Object o) {
if (o instanceof AntCompletionProposal) {
return ((AntCompletionProposal) o).getType();
}
return AntCompletionProposal.TASK_PROPOSAL;
}
};
protected final static int PROPOSAL_MODE_NONE = 0;
protected final static int PROPOSAL_MODE_BUILDFILE = 1;
protected final static int PROPOSAL_MODE_TASK_PROPOSAL = 2;
protected final static int PROPOSAL_MODE_PROPERTY_PROPOSAL = 3;
protected final static int PROPOSAL_MODE_ATTRIBUTE_PROPOSAL = 4;
protected final static int PROPOSAL_MODE_TASK_PROPOSAL_CLOSING = 5;
protected final static int PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL = 6;
protected final static int PROPOSAL_MODE_NESTED_ELEMENT_PROPOSAL = 7;
private final static ICompletionProposal[] NO_PROPOSALS = new ICompletionProposal[0];
/**
* The fully qualified name of the {@link MacroInstance} class
*
* @since 3.5.500
*/
private static final String MACROINSTANCE_NAME = "org.apache.tools.ant.taskdefs.MacroInstance"; //$NON-NLS-1$
/**
* The line where the cursor sits now.
* <P>
* The first line has index '1'.
*/
protected int lineNumber = -1;
/**
* The startingColumn where the cursor sits now.
* <P>
* The first startingColumn has index '1'.
*/
protected int columnNumber = -1;
/**
* The additional offset required from a required attribute to place the cursor for the current proposal
*/
private int additionalProposalOffset = -1;
private static final String ANT_DTD_FILENAME = "/org/eclipse/ant/internal/ui/editor/ant1.6.2.dtd"; //$NON-NLS-1$
/**
* The DTD.
*/
private static ISchema fgDtd;
/**
* Cursor position, counted from the beginning of the document.
* <P>
* The first position has index '0'.
*/
protected int cursorPosition = -1;
/**
* The text viewer.
*/
private ITextViewer viewer;
/**
* The set of characters that will trigger the activation of the completion proposal computation.
*/
private char[] autoActivationChars = null;
private String errorMessage;
protected AntModel antModel;
/**
* The proposal mode for the current content assist
*
* @see #determineProposalMode(IDocument, int, String)
*/
private int currentProposalMode = -1;
/**
* The prefix for the current content assist
*/
protected String currentPrefix = null;
/**
* The current task string for content assist
*
* @see #determineProposalMode(IDocument, int, String)
*/
protected String currentTaskString = null;
private boolean fTemplatesOnly = false;
protected IContentAssistantExtension2 fContentAssistant;
public AntEditorCompletionProcessor(AntModel model) {
super();
antModel = model;
}
/**
* Parses the dtd.
*/
private ISchema parseDtd() throws ParseError, IOException {
try (InputStream stream = getClass().getResourceAsStream(ANT_DTD_FILENAME); Reader reader = new InputStreamReader(stream, "UTF-8");) {//$NON-NLS-1$
Parser parser = new Parser();
ISchema schema = parser.parseDTD(reader, "project"); //$NON-NLS-1$
return schema;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
*/
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer refViewer, int documentOffset) {
this.viewer = refViewer;
try {
if (fTemplatesOnly) {
fContentAssistant.setStatusMessage(getIterationGestureMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.0"))); //$NON-NLS-1$
fContentAssistant.setEmptyMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.60")); //$NON-NLS-1$
ICompletionProposal[] templates = determineTemplateProposals(refViewer, documentOffset);
Arrays.sort(templates, proposalComparator);
return templates;
}
fContentAssistant.setStatusMessage(getIterationGestureMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.61"))); //$NON-NLS-1$
fContentAssistant.setEmptyMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.62")); //$NON-NLS-1$
ICompletionProposal[] matchingProposals = determineProposals();
ICompletionProposal[] matchingTemplateProposals = determineTemplateProposals(refViewer, documentOffset);
return mergeProposals(matchingProposals, matchingTemplateProposals);
}
finally {
currentPrefix = null;
currentProposalMode = -1;
fTemplatesOnly = !fTemplatesOnly;
}
}
protected ICompletionProposal[] determineTemplateProposals(ITextViewer refViewer, int documentOffset) {
this.viewer = refViewer;
String prefix = getCurrentPrefix();
ICompletionProposal[] matchingTemplateProposals;
if (prefix.length() == 0) {
matchingTemplateProposals = determineTemplateProposalsForContext(documentOffset);
} else {
ICompletionProposal[] templateProposals = determineTemplateProposalsForContext(documentOffset);
List<ICompletionProposal> templateProposalList = new ArrayList<>(templateProposals.length);
for (int i = 0; i < templateProposals.length; i++) {
if (templateProposals[i].getDisplayString().toLowerCase().startsWith(prefix)) {
templateProposalList.add(templateProposals[i]);
}
}
matchingTemplateProposals = templateProposalList.toArray(new ICompletionProposal[templateProposalList.size()]);
}
return matchingTemplateProposals;
}
// essentially a copy of super.computeCompletionProposals but we need to have both context types work
// for target (task and target) context type in a backwards compatible way
private ICompletionProposal[] determineTemplateProposalsForContext(int offset) {
ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();
// adjust offset to end of normalized selection
int newoffset = offset;
if (selection.getOffset() == newoffset) {
newoffset = selection.getOffset() + selection.getLength();
}
String prefix = extractPrefix(viewer, newoffset);
Region region = new Region(newoffset - prefix.length(), prefix.length());
TemplateContext context = createContext(viewer, region);
if (context == null) {
return new ICompletionProposal[0];
}
context.setVariable("selection", selection.getText()); // name of the selection variables {line, word}_selection //$NON-NLS-1$
Template[] templates;
String contextTypeId = context.getContextType().getId();
boolean isTargetContextType = contextTypeId.equals(TargetContextType.TARGET_CONTEXT_TYPE);
if (isTargetContextType) {
Template[] tasks = AntTemplateAccess.getDefault().getTemplateStore().getTemplates(TaskContextType.TASK_CONTEXT_TYPE);
Template[] targets = getTemplates(contextTypeId);
templates = new Template[tasks.length + targets.length];
System.arraycopy(tasks, 0, templates, 0, tasks.length);
System.arraycopy(targets, 0, templates, tasks.length, targets.length);
} else {
templates = getTemplates(contextTypeId);
}
List<ICompletionProposal> matches = new ArrayList<>();
for (int i = 0; i < templates.length; i++) {
Template template = templates[i];
try {
context.getContextType().validate(template.getPattern());
}
catch (TemplateException e) {
continue;
}
if (template.matches(prefix, contextTypeId) || (isTargetContextType && template.matches(prefix, TaskContextType.TASK_CONTEXT_TYPE))) {
matches.add(createProposal(template, context, (IRegion) region, getRelevance(template, prefix)));
}
}
Collections.sort(matches, proposalComparator);
return matches.toArray(new ICompletionProposal[matches.size()]);
}
private ICompletionProposal[] mergeProposals(ICompletionProposal[] proposals1, ICompletionProposal[] proposals2) {
ICompletionProposal[] combinedProposals = new ICompletionProposal[proposals1.length + proposals2.length];
System.arraycopy(proposals1, 0, combinedProposals, 0, proposals1.length);
System.arraycopy(proposals2, 0, combinedProposals, proposals1.length, proposals2.length);
Arrays.sort(combinedProposals, proposalComparator);
return combinedProposals;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(ITextViewer, int)
*/
@Override
public IContextInformation[] computeContextInformation(ITextViewer refViewer, int documentOffset) {
return new IContextInformation[0];
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
*/
@Override
public char[] getCompletionProposalAutoActivationCharacters() {
return autoActivationChars;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters()
*/
@Override
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator()
*/
@Override
public IContextInformationValidator getContextInformationValidator() {
return null;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage()
*/
@Override
public String getErrorMessage() {
return errorMessage;
}
/**
* Returns the new determined proposals.
*/
private ICompletionProposal[] determineProposals() {
ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();
cursorPosition = selection.getOffset() + selection.getLength();
IDocument doc = viewer.getDocument();
try {
lineNumber = doc.getLineOfOffset(cursorPosition);
columnNumber = cursorPosition - doc.getLineOffset(lineNumber);
}
catch (BadLocationException e) {
AntUIPlugin.log(e);
}
String prefix = getCurrentPrefix();
if (prefix == null || cursorPosition == -1) {
return NO_PROPOSALS;
}
ICompletionProposal[] proposals = getProposalsFromDocument(doc, prefix);
currentTaskString = null;
return proposals;
}
/**
* Returns the proposals for the specified document.
*/
protected ICompletionProposal[] getProposalsFromDocument(IDocument document, String prefix) {
ICompletionProposal[] proposals = null;
currentProposalMode = determineProposalMode(document, cursorPosition, prefix);
switch (currentProposalMode) {
case PROPOSAL_MODE_ATTRIBUTE_PROPOSAL:
proposals = getAttributeProposals(currentTaskString, prefix);
if (proposals.length == 0) {
errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.28"); //$NON-NLS-1$
}
break;
case PROPOSAL_MODE_TASK_PROPOSAL:
String parentName = getParentName(document, lineNumber, columnNumber);
if (parentName == null || parentName.length() == 0) { // outside of any parent element
proposals = NO_PROPOSALS;
currentProposalMode = PROPOSAL_MODE_NONE;
} else {
proposals = getTaskProposals(document, parentName, prefix);
}
if (proposals.length == 0) {
errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.29"); //$NON-NLS-1$
}
break;
case PROPOSAL_MODE_BUILDFILE:
proposals = getBuildFileProposals(document, prefix);
if (proposals.length == 0) {
errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.29"); //$NON-NLS-1$
}
break;
case PROPOSAL_MODE_TASK_PROPOSAL_CLOSING:
ICompletionProposal proposal = getClosingTaskProposal(getOpenElementName(), prefix, true);
if (proposal == null) {
errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.30"); //$NON-NLS-1$
proposals = NO_PROPOSALS;
} else {
proposals = new ICompletionProposal[] { proposal };
}
break;
case PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL:
String textToSearch = document.get().substring(0, cursorPosition - prefix.length());
String attributeString = getAttributeStringFromDocumentStringToPrefix(textToSearch);
if ("target".equalsIgnoreCase(currentTaskString) || "extension-point".equalsIgnoreCase(currentTaskString)) { //$NON-NLS-1$ //$NON-NLS-2$
proposals = getTargetAttributeValueProposals(document, textToSearch, prefix, attributeString);
} else if ("antcall".equalsIgnoreCase(currentTaskString)) { //$NON-NLS-1$
proposals = getAntCallAttributeValueProposals(document, prefix, attributeString);
} else if ("project".equalsIgnoreCase(currentTaskString)) { //$NON-NLS-1$
proposals = getProjectAttributeValueProposals(prefix, attributeString);
} else if ("refid".equalsIgnoreCase(attributeString) || "classpathref".equalsIgnoreCase(attributeString) //$NON-NLS-1$//$NON-NLS-2$
|| "sourcepathref".equalsIgnoreCase(attributeString) || "bootpathref".equalsIgnoreCase(attributeString)) { //$NON-NLS-1$ //$NON-NLS-2$
proposals = getReferencesValueProposals(prefix);
} else {
proposals = getAttributeValueProposals(currentTaskString, attributeString, prefix);
}
if (proposals.length == 0) {
errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.31"); //$NON-NLS-1$
}
break;
case PROPOSAL_MODE_PROPERTY_PROPOSAL:
proposals = getPropertyProposals(document, prefix, cursorPosition);
if (proposals.length == 0) {
errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.32"); //$NON-NLS-1$
}
break;
case PROPOSAL_MODE_NONE:
default:
proposals = NO_PROPOSALS;
errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.33"); //$NON-NLS-1$
}
if (proposals.length > 0) {
errorMessage = IAntCoreConstants.EMPTY_STRING;
}
return proposals;
}
private ICompletionProposal[] getProjectAttributeValueProposals(String prefix, String attributeName) {
if (attributeName.equalsIgnoreCase(IAntCoreConstants.DEFAULT)) {
return getDefaultValueProposals(prefix);
}
return NO_PROPOSALS;
}
private ICompletionProposal[] getDefaultValueProposals(String prefix) {
Map<String, Target> targets = getTargets();
List<AntCompletionProposal> defaultProposals = new ArrayList<>(targets.size());
Iterator<Target> itr = targets.values().iterator();
Target target;
String targetName;
while (itr.hasNext()) {
target = itr.next();
targetName = target.getName();
if (targetName.toLowerCase().startsWith(prefix) && targetName.length() > 0) {
defaultProposals.add(new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, target.getDescription(), AntCompletionProposal.TASK_PROPOSAL));
}
}
ICompletionProposal[] proposals = new ICompletionProposal[defaultProposals.size()];
return defaultProposals.toArray(proposals);
}
private ICompletionProposal[] getReferencesValueProposals(String prefix) {
Project project = antModel.getProjectNode().getProject();
Map<String, Object> references = project.getReferences();
if (references.isEmpty()) {
return NO_PROPOSALS;
}
IAntElement node = antModel.getNode(cursorPosition, false);
if (node == null) {
return NO_PROPOSALS;
}
while (node.getParentNode() instanceof AntTaskNode) {
node = node.getParentNode();
}
String id = null;
if (node instanceof AntTaskNode) {
id = ((AntTaskNode) node).getId();
}
Set<String> refIds = references.keySet();
List<ICompletionProposal> proposals = new ArrayList<>(refIds.size());
String refId;
ICompletionProposal proposal;
int prefixLength = prefix.length();
int replacementOffset = cursorPosition - prefixLength;
Iterator<String> iter = refIds.iterator();
while (iter.hasNext()) {
refId = iter.next();
if (!refId.equals(id) && (prefixLength == 0 || refId.toLowerCase().startsWith(prefix))) {
proposal = new AntCompletionProposal(refId, replacementOffset, prefixLength, refId.length(), null, refId, null, AntCompletionProposal.TASK_PROPOSAL);
proposals.add(proposal);
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
protected ICompletionProposal[] getTargetAttributeValueProposals(IDocument document, String textToSearch, String prefix, String attributeName) {
if (attributeName.equalsIgnoreCase("depends")) { //$NON-NLS-1$
return getDependsValueProposals(document, prefix);
} else if (attributeName.equalsIgnoreCase("if") || attributeName.equalsIgnoreCase("unless")) { //$NON-NLS-1$ //$NON-NLS-2$
if (!textToSearch.trim().endsWith(",")) { //$NON-NLS-1$
return getPropertyProposals(document, prefix, cursorPosition);
}
} else if (attributeName.equalsIgnoreCase("extensionOf")) {//$NON-NLS-1$
return getExtensionOfValueProposals(prefix);
}
return NO_PROPOSALS;
}
private ICompletionProposal[] getExtensionOfValueProposals(String prefix) {
List<ICompletionProposal> extensions = new ArrayList<>();
Map<String, Target> targets = getTargets();
Set<String> targetNames = targets.keySet();
for (String targetName : targetNames) {
Target currentTarget = targets.get(targetName);
if (currentTarget instanceof ExtensionPoint) {
extensions.add(new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, targets.get(targetName).getDescription(), AntCompletionProposal.TASK_PROPOSAL));
}
}
return extensions.toArray(new ICompletionProposal[extensions.size()]);
}
protected ICompletionProposal[] getAntCallAttributeValueProposals(IDocument document, String prefix, String attributeName) {
if (attributeName.equalsIgnoreCase("target")) { //$NON-NLS-1$
return getTargetProposals(document, prefix);
}
return NO_PROPOSALS;
}
private ICompletionProposal[] getTargetProposals(IDocument document, String prefix) {
String currentTargetName = getEnclosingTargetName(document, lineNumber, columnNumber);
if (currentTargetName == null) {
return NO_PROPOSALS;
}
Map<String, Target> targets = getTargets();
Set<String> targetNames = targets.keySet();
List<ICompletionProposal> proposals = new ArrayList<>(targets.size() - 2); // current target and implicit target
for (String targetName : targetNames) {
if (targetName.equals(currentTargetName)) {
continue;
}
if (targetName.toLowerCase().startsWith(prefix) && targetName.length() > 0) {
proposals.add(new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, targets.get(targetName).getDescription(), AntCompletionProposal.TASK_PROPOSAL));
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
/**
* Retrieves the representative image of a target of the given name. If the target cannot be found, <code>null</code> will be returned.
*
* @param targetName
* the target's name
* @return an image suitable for representing the target, or <code>null</code> if the target cannot be found
* @since 3.6
*/
private Image getTargetImage(String targetName) {
AntTargetNode targetNode = antModel.getTargetNode(targetName);
if (targetNode == null) {
return null;
} else if (targetNode.isInternal()) {
return AntUIImages.getImage(IAntUIConstants.IMG_ANT_TARGET_INTERNAL);
} else if (targetNode.isDefaultTarget()) {
return AntUIImages.getImage(IAntUIConstants.IMG_ANT_DEFAULT_TARGET);
} else {
return AntUIImages.getImage(IAntUIConstants.IMG_ANT_TARGET);
}
}
private ICompletionProposal[] getDependsValueProposals(IDocument document, String prefix) {
List<String> possibleDependencies = new ArrayList<>();
String currentTargetName = getEnclosingTargetName(document, lineNumber, columnNumber);
if (currentTargetName == null) {
return NO_PROPOSALS;
}
Map<String, Target> targets = getTargets();
Set<String> targetNames = targets.keySet();
Enumeration<String> dependencies = null;
for (String targetName : targetNames) {
if (targetName.equals(currentTargetName)) {
Target currentTarget = targets.get(targetName);
dependencies = currentTarget.getDependencies();
continue;
}
if (targetName.toLowerCase().startsWith(prefix) && targetName.length() > 0) {
possibleDependencies.add(targetName);
}
}
if (dependencies != null) {
while (dependencies.hasMoreElements()) {
possibleDependencies.remove(dependencies.nextElement());
}
}
ArrayList<ICompletionProposal> proposals = new ArrayList<>(possibleDependencies.size());
for (String targetName : possibleDependencies) {
ICompletionProposal proposal = new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, targets.get(targetName).getDescription(), AntCompletionProposal.TASK_PROPOSAL);
proposals.add(proposal);
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
/**
* Returns all possible attributes for the specified task.
*
* @param taskName
* the name of the task for that the attribute shall be completed
* @param prefix
* prefix, that all proposals should start with. The prefix may be an empty string.
*/
protected ICompletionProposal[] getAttributeProposals(String taskName, String prefix) {
List<ICompletionProposal> proposals = new ArrayList<>();
IElement element = getDtd().getElement(taskName);
if (element != null) {
Iterator<String> keys = element.getAttributes().keySet().iterator();
while (keys.hasNext()) {
String attrName = keys.next();
if (prefix.length() == 0 || attrName.toLowerCase().startsWith(prefix)) {
IAttribute dtdAttributes = element.getAttributes().get(attrName);
String replacementString = attrName + "=\"\""; //$NON-NLS-1$
String displayString = attrName;
String[] items = dtdAttributes.getEnum();
if (items != null) {
if (items.length > 1) {
displayString += " - ("; //$NON-NLS-1$
}
for (int i = 0; i < items.length; i++) {
displayString += items[i];
if (i + 1 < items.length) {
displayString += " | "; //$NON-NLS-1$
} else {
displayString += ")"; //$NON-NLS-1$
}
}
}
addAttributeProposal(taskName, prefix, proposals, attrName, replacementString, displayString, true);
}
}
} else { // possibly a user defined task or type
AntTypeDefinition taskClass = getTaskClass(taskName);
if (taskClass != null) {
if (MACROINSTANCE_NAME.equals(taskClass.getClassName())) {
addMacroDefAttributeProposals(taskName, prefix, proposals);
} else {
IntrospectionHelper helper = getIntrospectionHelper(taskClass);
if (helper != null) {
addAttributeProposals(helper, taskName, prefix, proposals);
}
}
} else { // nested user defined element
AntTypeDefinition nestedType = getNestedType();
if (nestedType != null) {
IntrospectionHelper helper = getIntrospectionHelper(nestedType);
if (helper != null) {
addAttributeProposals(helper, taskName, prefix, proposals);
}
}
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
private void addAttributeProposals(IntrospectionHelper helper, String taskName, String prefix, List<ICompletionProposal> proposals) {
Enumeration<String> attributes = helper.getAttributes();
while (attributes.hasMoreElements()) {
String attribute = attributes.nextElement();
if (prefix.length() == 0 || attribute.toLowerCase().startsWith(prefix)) {
String replacementString = attribute + "=\"\""; //$NON-NLS-1$
addAttributeProposal(taskName, prefix, proposals, attribute, replacementString, attribute, false);
}
}
}
private AntTypeDefinition getNestedType() {
AntElementNode currentNode = antModel.getNode(cursorPosition, false);
if (currentNode == null) {
return null;
}
IAntElement parent = currentNode.getParentNode();
if (parent instanceof AntTaskNode) {
String parentName = parent.getName();
if (hasNestedElements(parentName)) {
AntTypeDefinition taskClass = getTaskClass(parentName);
if (taskClass != null) {
IntrospectionHelper helper = getIntrospectionHelper(taskClass);
if (helper != null) {
try {
Class<?> nestedType = helper.getElementType(currentNode.getName());
// TODO probably a much better way to find the definition of a nested type than this
AntTypeDefinition def = new AntTypeDefinition();
def.setClass(nestedType);
def.setClassName(nestedType.getName());
return def;
}
catch (BuildException be) {
// do nothing
}
}
}
}
}
return null;
}
private IntrospectionHelper getIntrospectionHelper(AntTypeDefinition taskClass) {
Project p = antModel.getProjectNode().getProject();
Class<?> clazz = taskClass.getExposedClass(p);
if (clazz != null) {
IntrospectionHelper helper = null;
try {
helper = IntrospectionHelper.getHelper(antModel.getProjectNode().getProject(), clazz);
}
catch (NoClassDefFoundError e) {
// ignore as a task may require additional classpath components
}
return helper;
}
return null;
}
private void addMacroDefAttributeProposals(String taskName, String prefix, List<ICompletionProposal> proposals) {
currentProposalMode = PROPOSAL_MODE_ATTRIBUTE_PROPOSAL;
AntDefiningTaskNode node = antModel.getDefininingTaskNode(taskName);
Object task = node.getRealTask();
if (!(task instanceof MacroDef)) {
return;
}
List<Attribute> attributes = ((MacroDef) task).getAttributes();
Iterator<Attribute> itr = attributes.iterator();
while (itr.hasNext()) {
MacroDef.Attribute attribute = itr.next();
String attributeName = attribute.getName();
if (!(prefix.length() == 0 || attributeName.toLowerCase().startsWith(prefix))) {
continue;
}
String replacementString = attributeName + "=\"\""; //$NON-NLS-1$
String proposalInfo = null;
String description = attribute.getDescription();
if (description != null) {
proposalInfo = description;
}
String deflt = attribute.getDefault();
if (deflt != null && deflt.length() > 0) {
proposalInfo = (proposalInfo == null ? "<BR><BR>" : (proposalInfo += "<BR><BR>")); //$NON-NLS-1$ //$NON-NLS-2$
proposalInfo += MessageFormat.format(AntEditorMessages.getString("AntEditorCompletionProcessor.59"), new Object[] { deflt }); //$NON-NLS-1$
}
ICompletionProposal proposal = new AntCompletionProposal(replacementString, cursorPosition - prefix.length(), prefix.length(), attributeName.length() + 2, null, attributeName, proposalInfo, AntCompletionProposal.TASK_PROPOSAL);
proposals.add(proposal);
}
}
private void addMacroDefElementProposals(String taskName, String prefix, List<ICompletionProposal> proposals) {
AntDefiningTaskNode node = antModel.getDefininingTaskNode(taskName);
Object task = node.getRealTask();
if (!(task instanceof MacroDef)) {
return;
}
Map<String, TemplateElement> elements = ((MacroDef) task).getElements();
Iterator<String> itr = elements.keySet().iterator();
int prefixLength = prefix.length();
int replacementOffset = cursorPosition - prefixLength;
while (itr.hasNext()) {
String elementName = itr.next();
if (!(prefixLength == 0 || elementName.toLowerCase().startsWith(prefix))) {
continue;
}
MacroDef.TemplateElement element = elements.get(elementName);
String replacementString = MessageFormat.format("<{0}>\n</{1}>", new Object[] { elementName, elementName }); //$NON-NLS-1$
String proposalInfo = null;
String description = element.getDescription();
if (description != null) {
proposalInfo = description;
}
proposalInfo = (proposalInfo == null ? "<BR><BR>" : (proposalInfo += "<BR><BR>")); //$NON-NLS-1$ //$NON-NLS-2$
if (element.isOptional()) {
proposalInfo += AntEditorMessages.getString("AntEditorCompletionProcessor.1"); //$NON-NLS-1$
} else {
proposalInfo += AntEditorMessages.getString("AntEditorCompletionProcessor.2"); //$NON-NLS-1$
}
ICompletionProposal proposal = new AntCompletionProposal(replacementString, replacementOffset, prefixLength, elementName.length() + 2, null, elementName, proposalInfo, AntCompletionProposal.TASK_PROPOSAL);
proposals.add(proposal);
}
}
private void addAttributeProposal(String taskName, String prefix, List<ICompletionProposal> proposals, String attrName, String replacementString, String displayString, boolean lookupDescription) {
String proposalInfo = null;
if (lookupDescription) {
String required = getDescriptionProvider().getRequiredAttributeForTaskAttribute(taskName, attrName);
if (required != null && required.length() > 0) {
proposalInfo = AntEditorMessages.getString("AntEditorCompletionProcessor.Required___4") + required; //$NON-NLS-1$
proposalInfo += "<BR><BR>"; //$NON-NLS-1$
}
String description = getDescriptionProvider().getDescriptionForTaskAttribute(taskName, attrName);
if (description != null) {
proposalInfo = (proposalInfo == null ? IAntCoreConstants.EMPTY_STRING : proposalInfo);
proposalInfo += description;
}
}
ICompletionProposal proposal = new AntCompletionProposal(replacementString, cursorPosition - prefix.length(), prefix.length(), attrName.length() + 2, null, displayString, proposalInfo, AntCompletionProposal.TASK_PROPOSAL);
proposals.add(proposal);
}
/**
* Returns all possible values for the specified attribute of the specified task.
*
* @param aTaskName
* the name of the task that the specified attribute belongs to.
*
* @param anAttributeName
* the name of the attribute for that the value shall be completed
*
* @param prefix
* the prefix that all proposals should start with. The prefix may be an empty string.
*/
private ICompletionProposal[] getAttributeValueProposals(String taskName, String attributeName, String prefix) {
List<ICompletionProposal> proposals = new ArrayList<>();
IElement taskElement = getDtd().getElement(taskName);
if (taskElement != null) {
IAttribute attribute = taskElement.getAttributes().get(attributeName);
if (attribute != null) {
String[] items = attribute.getEnum();
if (items != null) {
String item;
for (int i = 0; i < items.length; i++) {
item = items[i];
if (prefix.length() == 0 || item.toLowerCase().startsWith(prefix)) {
proposals.add(new AntCompletionProposal(item, cursorPosition - prefix.length(), prefix.length(), item.length(), null, item, null, AntCompletionProposal.TASK_PROPOSAL));
}
}
}
}
} else { // possibly a user defined task or type
AntTypeDefinition taskClass = getTaskClass(taskName);
if (taskClass != null) {
IntrospectionHelper helper = getIntrospectionHelper(taskClass);
if (helper != null) {
addAttributeValueProposals(helper, attributeName, prefix, proposals);
}
} else { // nested user defined element
AntTypeDefinition nestedType = getNestedType();
if (nestedType != null) {
IntrospectionHelper helper = getIntrospectionHelper(nestedType);
if (helper != null) {
addAttributeValueProposals(helper, attributeName, prefix, proposals);
}
}
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
private void addAttributeValueProposals(IntrospectionHelper helper, String attributeName, String prefix, List<ICompletionProposal> proposals) {
Enumeration<String> attributes = helper.getAttributes();
while (attributes.hasMoreElements()) {
String attribute = attributes.nextElement();
if (attribute.equals(attributeName)) {
Class<?> attributeType = helper.getAttributeType(attribute);
addAttributeValueProposalsForAttributeType(attributeType, prefix, proposals);
break;
}
}
}
private void addAttributeValueProposalsForAttributeType(Class<?> attributeType, String prefix, List<ICompletionProposal> proposals) {
if ((attributeType == Boolean.TYPE || attributeType == Boolean.class) && prefix.length() <= 5) {
addBooleanAttributeValueProposals(prefix, proposals);
} else if (EnumeratedAttribute.class.isAssignableFrom(attributeType)) {
try {
addEnumeratedAttributeValueProposals(attributeType, prefix, proposals);
}
catch (InstantiationException e) {
// do nothing
}
catch (IllegalAccessException e) {
// do nothing
}
} else if (Reference.class == attributeType) {
proposals.addAll(Arrays.asList(getReferencesValueProposals(prefix)));
}
}
private void addEnumeratedAttributeValueProposals(Class<?> type, String prefix, List<ICompletionProposal> proposals) throws InstantiationException, IllegalAccessException {
EnumeratedAttribute ea = (EnumeratedAttribute) type.newInstance();
String[] values = ea.getValues();
String enumerated;
for (int i = 0; i < values.length; i++) {
enumerated = values[i].toLowerCase();
if (prefix.length() == 0 || enumerated.startsWith(prefix)) {
proposals.add(new AntCompletionProposal(enumerated, cursorPosition - prefix.length(), prefix.length(), enumerated.length(), null, enumerated, null, AntCompletionProposal.TASK_PROPOSAL));
}
}
}
private void addBooleanAttributeValueProposals(String prefix, List<ICompletionProposal> proposals) {
String[] booleanValues = new String[] { "true", "false", "on", "off", "yes", "no" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
String booleanAssist;
for (int i = 0; i < booleanValues.length; i++) {
booleanAssist = booleanValues[i].toLowerCase();
if (prefix.length() == 0 || booleanAssist.startsWith(prefix)) {
proposals.add(new AntCompletionProposal(booleanAssist, cursorPosition - prefix.length(), prefix.length(), booleanAssist.length(), null, booleanAssist, null, AntCompletionProposal.TASK_PROPOSAL));
}
}
}
/**
* Returns all possible properties for the specified prefix.
* <P>
* Note that the completion mode must be property mode, otherwise it is not safe to call this method.
*/
protected ICompletionProposal[] getPropertyProposals(IDocument document, String prefix, int aCursorPosition) {
List<ICompletionProposal> proposals = new ArrayList<>();
Map<String, ICompletionProposal> displayStringToProposals = new HashMap<>();
Map<String, Object> properties = findPropertiesFromDocument();
Image image = AntUIImages.getImage(IAntUIConstants.IMG_PROPERTY);
// Determine replacement length and offset
// String from beginning to the beginning of the prefix
int replacementLength = prefix.length();
int replacementOffset = 0;
String text = document.get();
String stringToPrefix = text.substring(0, aCursorPosition - prefix.length());
// Property proposal
String lastTwoCharacters = stringToPrefix.substring(stringToPrefix.length() - 2, stringToPrefix.length());
boolean appendBraces = true;
if (lastTwoCharacters.equals("${")) { //$NON-NLS-1$
replacementLength += 2;
replacementOffset = aCursorPosition - prefix.length() - 2;
} else if (lastTwoCharacters.endsWith("$")) { //$NON-NLS-1$
replacementLength += 1;
replacementOffset = aCursorPosition - prefix.length() - 1;
} else {
// support for property proposals for the if/unless attributes of targets
replacementOffset = aCursorPosition - prefix.length();
appendBraces = false;
}
if (text.length() > aCursorPosition && text.charAt(aCursorPosition) == '}') {
replacementLength += 1;
}
String propertyName;
for (Iterator<String> i = properties.keySet().iterator(); i.hasNext();) {
propertyName = i.next();
if (prefix.length() == 0 || propertyName.toLowerCase().startsWith(prefix)) {
String additionalPropertyInfo = (String) properties.get(propertyName);
StringBuffer replacementString = new StringBuffer();
if (appendBraces) {
replacementString.append("${"); //$NON-NLS-1$
}
replacementString.append(propertyName);
if (appendBraces) {
replacementString.append('}');
}
if (displayStringToProposals.get(propertyName) == null) {
ICompletionProposal proposal = new AntCompletionProposal(replacementString.toString(), replacementOffset, replacementLength, replacementString.length(), image, propertyName, additionalPropertyInfo, AntCompletionProposal.PROPERTY_PROPOSAL);
proposals.add(proposal);
displayStringToProposals.put(propertyName, proposal);
}
}
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
/**
* Returns all possible proposals for the specified parent name.
* <P>
* No completions will be returned if <code>parentName</code> is not known.
*
* @param document
* the entire document
* @param parentName
* name of the parent (surrounding) element.
* @param prefix
* the prefix that all proposals should start with. The prefix may be an empty string.
*/
protected ICompletionProposal[] getTaskProposals(IDocument document, String parentName, String prefix) {
List<ICompletionProposal> proposals = new ArrayList<>(250);
ICompletionProposal proposal;
if (areTasksOrTypesValidChildren(parentName)) {
// use the definitions in the project as that includes more than what is defined in the DTD
Project project = antModel.getProjectNode().getProject();
Map<String, AntTypeDefinition> tasksAndTypes = ComponentHelper.getComponentHelper(project).getAntTypeTable();
createProposals(document, prefix, proposals, tasksAndTypes);
if (parentName.equals("project")) { //$NON-NLS-1$
if ("target".startsWith(prefix)) { //$NON-NLS-1$
proposals.add(newCompletionProposal(document, prefix, "target")); //$NON-NLS-1$
}
if ("extension-point".startsWith(prefix)) { //$NON-NLS-1$
proposals.add(newCompletionProposal(document, prefix, "extension-point")); //$NON-NLS-1$
}
}
} else {
IElement parent = getDtd().getElement(parentName);
if (parent != null) {
IDfm dfm = parent.getDfm();
String[] accepts = dfm.getAccepts();
if (accepts.length == 0) {
currentProposalMode = PROPOSAL_MODE_NONE;
}
String elementName;
for (int i = 0; i < accepts.length; i++) {
elementName = accepts[i];
if (prefix.length() == 0 || elementName.toLowerCase().startsWith(prefix)) {
proposal = newCompletionProposal(document, prefix, elementName);
proposals.add(proposal);
}
}
} else {
// a nested element of a user defined task/type?
AntTypeDefinition taskClass = getTaskClass(parentName);
if (taskClass != null) {
if (MACROINSTANCE_NAME.equals(taskClass.getClassName())) {
currentProposalMode = PROPOSAL_MODE_ATTRIBUTE_PROPOSAL;
addMacroDefElementProposals(parentName, prefix, proposals);
} else {
currentProposalMode = PROPOSAL_MODE_NESTED_ELEMENT_PROPOSAL;
IntrospectionHelper helper = getIntrospectionHelper(taskClass);
if (helper != null) {
Enumeration<String> nested = helper.getNestedElements();
String nestedElement;
while (nested.hasMoreElements()) {
nestedElement = nested.nextElement();
if (prefix.length() == 0 || nestedElement.toLowerCase().startsWith(prefix)) {
proposal = newCompletionProposal(document, prefix, nestedElement);
proposals.add(proposal);
}
}
}
}
}
}
}
proposal = getClosingTaskProposal(getOpenElementName(), prefix, false);
if (proposal != null) {
proposals.add(proposal);
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
private boolean areTasksOrTypesValidChildren(String parentName) {
return parentName == "project" || parentName == "target" || parentName == "sequential" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|| parentName == "presetdef" || parentName == "parallel" || parentName == "daemons" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|| parentName == "extension-point"; //$NON-NLS-1$
}
/**
* Returns proposals that define the structure of a build file.
*
* Note that template proposals which define the structure of a build file are handled by {@link #determineTemplateProposals(ITextViewer, int)}
* which limits proposals by context type.
*
* @param document
* the entire document
* @param prefix
* the prefix that all proposals should start with. The prefix may be an empty string.
*/
protected ICompletionProposal[] getBuildFileProposals(IDocument document, String prefix) {
String rootElementName = "project"; //$NON-NLS-1$
IElement rootElement = getDtd().getElement(rootElementName);
if (rootElement != null && rootElementName.toLowerCase().startsWith(prefix)) {
ICompletionProposal proposal = newCompletionProposal(document, prefix, rootElementName);
return new ICompletionProposal[] { proposal };
}
return NO_PROPOSALS;
}
private void createProposals(IDocument document, String prefix, List<ICompletionProposal> proposals, Map<String, AntTypeDefinition> tasks) {
Iterator<String> keys = tasks.keySet().iterator();
ICompletionProposal proposal;
String key;
while (keys.hasNext()) {
key = antModel.getUserNamespaceCorrectName(keys.next());
if (prefix.length() == 0 || key.toLowerCase().startsWith(prefix)) {
proposal = newCompletionProposal(document, prefix, key);
proposals.add(proposal);
}
}
}
private ICompletionProposal newCompletionProposal(IDocument document, String aPrefix, String elementName) {
additionalProposalOffset = 0;
Image proposalImage = AntUIImages.getImage(IAntUIConstants.IMG_TASK_PROPOSAL);
String proposalInfo = getDescriptionProvider().getDescriptionForTask(elementName);
boolean hasNestedElements = hasNestedElements(elementName);
String replacementString = getTaskProposalReplacementString(elementName, hasNestedElements);
int replacementOffset = cursorPosition - aPrefix.length();
int replacementLength = aPrefix.length();
if (replacementOffset > 0 && document.get().charAt(replacementOffset - 1) == '<') {
replacementOffset--;
replacementLength++;
}
int proposalCursorPosition;
if (hasNestedElements) {
proposalCursorPosition = elementName.length() + 2 + additionalProposalOffset;
} else {
if (additionalProposalOffset > 0) {
additionalProposalOffset += 2; // <antstructure output="|"/>
} else {
additionalProposalOffset += 1; // <arg|/>
}
proposalCursorPosition = elementName.length() + additionalProposalOffset;
}
return new AntCompletionProposal(replacementString, replacementOffset, replacementLength, proposalCursorPosition, proposalImage, elementName, proposalInfo, AntCompletionProposal.TASK_PROPOSAL);
}
/**
* Returns the one possible completion for the specified unclosed task .
*
* @param openElementName
* the task that hasn't been closed last
* @param prefix
* The prefix that the one possible proposal should start with. The prefix may be an empty string.
* @return the proposal or <code>null</code> if no closing proposal available
*/
private ICompletionProposal getClosingTaskProposal(String openElementName, String prefix, boolean closingMode) {
char previousChar = getPreviousChar();
ICompletionProposal proposal = null;
if (openElementName != null) {
if (prefix.length() == 0 || openElementName.toLowerCase().startsWith(prefix)) {
StringBuffer replaceString = new StringBuffer();
if (!closingMode) {
if (previousChar != '/') {
if (previousChar != '<') {
replaceString.append('<');
}
replaceString.append('/');
}
}
replaceString.append(openElementName);
replaceString.append('>');
StringBuffer displayString = new StringBuffer("</"); //$NON-NLS-1$
displayString.append(openElementName);
displayString.append('>');
proposal = new AntCompletionProposal(replaceString.toString(), cursorPosition - prefix.length(), prefix.length(), replaceString.length(), null, displayString.toString(), AntEditorMessages.getString("AntEditorCompletionProcessor.39"), AntCompletionProposal.TAG_CLOSING_PROPOSAL); //$NON-NLS-1$
}
}
return proposal;
}
protected char getPreviousChar() {
ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();
int offset = selection.getOffset();
char previousChar = '?';
try {
previousChar = viewer.getDocument().getChar(offset - 1);
}
catch (BadLocationException e) {
// do nothing
}
return previousChar;
}
/**
* Returns the replacement string for the specified task name.
*/
private String getTaskProposalReplacementString(String aTaskName, boolean hasNested) {
StringBuffer replacement = new StringBuffer("<"); //$NON-NLS-1$
replacement.append(aTaskName);
ProposalNode task = getDescriptionProvider().getTaskNode(aTaskName);
if (task != null) {
appendRequiredAttributes(replacement, task);
}
if (hasNested) {
replacement.append("></"); //$NON-NLS-1$
replacement.append(aTaskName);
replacement.append('>');
} else {
replacement.append("/>"); //$NON-NLS-1$
}
return replacement.toString();
}
private void appendRequiredAttributes(StringBuffer replacement, ProposalNode task) {
if (task.nodes != null) {
boolean requiredAdded = false;
Entry<String, ProposalNode> entry = null;
for (Iterator<Entry<String, ProposalNode>> i = task.nodes.entrySet().iterator(); i.hasNext();) {
entry = i.next();
String name = entry.getKey();
ProposalNode att = entry.getValue();
if ("yes".equalsIgnoreCase(att.required)) { //$NON-NLS-1$
replacement.append(' ');
replacement.append(name);
replacement.append("=\"\""); //$NON-NLS-1$
if (!requiredAdded) {
additionalProposalOffset = name.length() + 2;
requiredAdded = true;
}
}
}
}
}
/**
* Returns whether the named element supports nested elements.
*/
private boolean hasNestedElements(String elementName) {
IElement element = getDtd().getElement(elementName);
if (element != null) {
return !element.isEmpty();
}
AntTypeDefinition taskClass = getTaskClass(elementName);
if (taskClass != null) {
IntrospectionHelper helper = getIntrospectionHelper(taskClass);
if (helper != null) {
Enumeration<String> nested = helper.getNestedElements();
return nested.hasMoreElements();
}
}
return false;
}
/**
* Finds a direct child element with <code>aChildElementName</code> of <code>anElement</code>.
* <P>
* The child will not be searched for in the whole hierarchy but only in the hierarchy step below.
*
* @return the found child or <code>null</code> if not found.
*/
protected Element findChildElementNamedOf(Element anElement, String aChildElementName) {
NodeList nodeList = anElement.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
if (childNode.getNodeName().equals(aChildElementName)) {
return (Element) childNode;
}
}
}
return null;
}
/**
* Determines the current prefix that should be used for completion.
*/
private String getCurrentPrefix() {
if (currentPrefix != null) {
return currentPrefix;
}
ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();
IDocument doc = viewer.getDocument();
return getPrefixFromDocument(doc.get(), selection.getOffset() + selection.getLength()).toLowerCase();
}
/**
* Returns the prefix in the specified document text with respect to the specified offset.
*
* @param aDocumentText
* the whole content of the edited file as String
* @param anOffset
* the cursor position
*/
protected String getPrefixFromDocument(String aDocumentText, int anOffset) {
if (currentPrefix != null) {
return currentPrefix;
}
int startOfWordToken = anOffset;
char token = 'a';
if (startOfWordToken > 0) {
token = aDocumentText.charAt(startOfWordToken - 1);
}
while (startOfWordToken > 0 && (Character.isJavaIdentifierPart(token) || '.' == token || '-' == token || ';' == token) && !('$' == token)) {
startOfWordToken--;
if (startOfWordToken == 0) {
break; // word goes right to the beginning of the doc
}
token = aDocumentText.charAt(startOfWordToken - 1);
}
if (startOfWordToken != anOffset) {
currentPrefix = aDocumentText.substring(startOfWordToken, anOffset).toLowerCase();
} else {
currentPrefix = IAntCoreConstants.EMPTY_STRING;
}
return currentPrefix;
}
/**
* Returns the current proposal mode.
*/
protected int determineProposalMode(IDocument document, int aCursorPosition, String aPrefix) {
if (currentProposalMode != -1) {
return currentProposalMode;
}
if (document.getLength() == 0 || (document.getLength() == 1 && document.get().equals("<"))) { //$NON-NLS-1$
return PROPOSAL_MODE_BUILDFILE;
}
// String from beginning of document to the beginning of the prefix
String text = document.get();
String stringToPrefix = text.substring(0, aCursorPosition - aPrefix.length());
if (stringToPrefix.length() == 0) {
return PROPOSAL_MODE_BUILDFILE;
}
// Is trimmable from behind
String trimmedString = stringToPrefix.trim();
if (antModel != null && antModel.getProjectNode() == null) {
currentTaskString = getTaskStringFromDocumentStringToPrefix(trimmedString);
if ("project".equals(currentTaskString)) { //$NON-NLS-1$
return PROPOSAL_MODE_ATTRIBUTE_PROPOSAL;
}
return PROPOSAL_MODE_BUILDFILE;
}
char lastChar = 0;
if (trimmedString.length() > 0) {
lastChar = trimmedString.charAt(trimmedString.length() - 1);
} else {
return PROPOSAL_MODE_TASK_PROPOSAL;
}
if (stringToPrefix.charAt(stringToPrefix.length() - 1) != lastChar && lastChar != '>' && lastChar != ',') {
/*
* Substring must be trimmable from behind in case of attribute proposal because a space or a new line must be used as delimiter between
* task name and attribute or attribute and attribute. Example: '<property id="bla" name="hups"'
*/
// Attribute proposal
if (lastChar != '>' && lastChar != '<') {
currentTaskString = getTaskStringFromDocumentStringToPrefix(trimmedString);
if (currentTaskString != null && isKnownElement(currentTaskString)) {
return PROPOSAL_MODE_ATTRIBUTE_PROPOSAL;
}
}
} else if (stringToPrefix.charAt(stringToPrefix.length() - 1) == '"' || trimmedString.charAt(trimmedString.length() - 1) == ',') {
// Attribute value proposal
currentTaskString = getTaskStringFromDocumentStringToPrefix(trimmedString);
if (currentTaskString != null && isKnownElement(currentTaskString)) {
return PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL;
}
} else { // Possibly a Task proposal
int spaceIndex = stringToPrefix.lastIndexOf(' ');
int lessThanIndex = stringToPrefix.lastIndexOf('<');
int greaterThanIndex = stringToPrefix.lastIndexOf('>');
// Task proposal
if (greaterThanIndex < lessThanIndex) {
// we are inside an open element
if (lastChar == '$') {
return PROPOSAL_MODE_PROPERTY_PROPOSAL;
}
if (lessThanIndex > spaceIndex) {
int slashIndex = stringToPrefix.lastIndexOf('/');
if (slashIndex == lessThanIndex + 1) {
return PROPOSAL_MODE_TASK_PROPOSAL_CLOSING; // ... </
}
return PROPOSAL_MODE_TASK_PROPOSAL;
}
}
if (lessThanIndex < greaterThanIndex) {
if (isPropertyProposalMode(stringToPrefix)) {
return PROPOSAL_MODE_PROPERTY_PROPOSAL;
}
return PROPOSAL_MODE_TASK_PROPOSAL;
}
}
// Property proposal
if (isPropertyProposalMode(stringToPrefix)) {
return PROPOSAL_MODE_PROPERTY_PROPOSAL;
}
return PROPOSAL_MODE_NONE;
}
private boolean isPropertyProposalMode(String stringToPrefix) {
if (stringToPrefix.length() >= 2) {
String lastTwoChars = stringToPrefix.substring(stringToPrefix.length() - 2, stringToPrefix.length());
if (lastTwoChars.equals("${") || //$NON-NLS-1$
stringToPrefix.charAt(stringToPrefix.length() - 1) == '$') {
return true;
}
}
return false;
}
/**
* Returns the last occurring task string in the specified string.
* <P>
* The returned string must not necessarily be a valid Ant task string. This can be tested with the method <code>inNamedTaskKnown(String)</code>
* after invoking this method.
*
* @param aDocumentStringToPrefix
* the String that contains the whole string of the currently edited file from the beginning up to the prefix for code completion.
* Example: '<project default="name"><property '.
*
* @return the extracted task string or <code>null</code> if no string could be extracted.
*/
private String getTaskStringFromDocumentStringToPrefix(String aDocumentStringToPrefix) {
int lessThanIndex = aDocumentStringToPrefix.lastIndexOf('<');
if (lessThanIndex > -1) {
String taskString = aDocumentStringToPrefix.trim();
taskString = taskString.substring(lessThanIndex + 1, taskString.length());
int index = taskString.indexOf(' ');
if (index > 0) {
taskString = taskString.substring(0, index);
}
index = taskString.indexOf('\n');
if (index > 0) {
taskString = taskString.substring(0, index);
}
index = taskString.indexOf('\r');
if (index > 0) {
taskString = taskString.substring(0, index);
}
return taskString;
}
return null;
}
/**
* Returns the last occurring attribute string in the specified string. <code>null</code> is returned if no attribute string is available.
* <P>
* Calling this method is only safe if the current proposal mode is really <code>PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL</code>.
*/
public static String getAttributeStringFromDocumentStringToPrefix(String docStringToPrefix) {
int index = docStringToPrefix.lastIndexOf('=');
if (index == -1) {
return null;
}
String subString = docStringToPrefix.substring(0, index);
subString = subString.trim();
index = subString.lastIndexOf(' ');
if (index > 0) {
subString = subString.substring(index + 1, subString.length());
}
index = subString.lastIndexOf('\n');
if (index > 0) {
subString = subString.substring(index + 1, subString.length());
}
index = subString.lastIndexOf('\r');
if (index > 0) {
subString = subString.substring(index + 1, subString.length());
}
return trimBeginning(subString);
}
private static String trimBeginning(String toBeTrimmed) {
int i = 0;
while ((i != toBeTrimmed.length()) && Character.isWhitespace(toBeTrimmed.charAt(i))) {
i++;
}
return toBeTrimmed.substring(i);
}
/**
* Returns whether the specified element name is known
*/
protected boolean isKnownElement(String elementName) {
if (elementName.equals("target") || elementName.equals("project") || elementName.equals("extension-point")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return true;
}
AntProjectNode node = antModel.getProjectNode();
if (node != null) {
Project antProject = node.getProject();
ComponentHelper helper = ComponentHelper.getComponentHelper(antProject);
if (helper.getDefinition(elementName) != null) {
return true;
}
if (helper.getDefinition(antModel.getNamespaceCorrectName(elementName)) != null) {
return true;
}
// not everything is a task or type (nested elements)
if (getDtd().getElement(elementName) != null) {
return true;
}
if (getNestedType() != null) {
return true;
}
}
return false;
}
private AntTypeDefinition getTaskClass(String taskName) {
AntTypeDefinition clss = null;
AntProjectNode node = antModel.getProjectNode();
if (node != null) {
Project antProject = node.getProject();
Map<String, AntTypeDefinition> tasksAndTypes = ComponentHelper.getComponentHelper(antProject).getAntTypeTable();
clss = tasksAndTypes.get(taskName);
if (clss == null) {
clss = tasksAndTypes.get(antModel.getNamespaceCorrectName(taskName));
}
}
return clss;
}
/**
* Finds the parent task element in respect to the cursor position.
*
* @return the parent task element or <code>null</code> if not found.
*/
protected String getParentName(IDocument document, int aLineNumber, int aColumnNumber) {
if (document.getLength() == 0) {
return null;
}
AntProjectNode project = antModel.getProjectNode();
if (project == null) {
return null;
}
int offset = getOffset(document, aLineNumber, aColumnNumber);
if (offset == -1) {
return null;
}
IAntElement node = project.getNode(offset);
if (node == null) {
node = antModel.getOpenElement();
}
if (node == null) {
return IAntCoreConstants.EMPTY_STRING;
} else if (node instanceof AntTaskNode) {
String name = node.getName();
if (offset <= node.getOffset() + name.length() - 1) {
// not really the enclosing node as the offset is within the name of the node
node = node.getParentNode();
} else {
return name;
}
}
if (node instanceof AntTaskNode) {
return node.getName();
} else if (node instanceof AntTargetNode) {
return "target"; //$NON-NLS-1$
} else {
return "project"; //$NON-NLS-1$
}
}
/**
* Return the properties as defined in the entire buildfile
*
* @return a map with all the found properties
*/
private Map<String, Object> findPropertiesFromDocument() {
Project project = antModel.getProjectNode().getProject();
return project.getProperties();
}
private Map<String, Target> getTargets() {
Project project = antModel.getProjectNode().getProject();
return project.getTargets();
}
protected File getEditedFile() {
IWorkbenchPage page = AntUIPlugin.getActivePage();
if (page == null) {
return null;
}
IEditorPart editor = page.getActiveEditor();
if (editor == null) {
return null;
}
FileEditorInput input = (FileEditorInput) editor.getEditorInput();
String projectPath = input.getFile().getProject().getLocation().toFile().getAbsolutePath();
String projectRelativeFilePath = input.getFile().getFullPath().removeFirstSegments(1).makeRelative().toString();
return new File(projectPath + File.separator + projectRelativeFilePath);
}
private String getOpenElementName() {
AntElementNode node = antModel.getOpenElement();
if (node == null) {
return null;
}
return node.getName();
}
/**
* Finds the enclosing target in respect to the cursor position and returns its name
*
* @return the name of the enclosing target or <code>null</code> if not found or the element is not contained in a target.
*/
private String getEnclosingTargetName(IDocument document, int aLineNumber, int aColumnNumber) {
AntProjectNode project = antModel.getProjectNode();
int offset = getOffset(document, aLineNumber, aColumnNumber);
if (offset == -1) {
return null;
}
IAntElement node = project.getNode(offset);
if (node instanceof AntTaskNode) {
node = node.getParentNode();
if (!(node instanceof AntTargetNode)) {
// top level task
node = null;
}
} else if (node instanceof AntProjectNode) {
node = null;
}
String targetName = null;
if (node == null || (targetName = ((AntTargetNode) node).getTarget().getName()) == null || targetName.length() == 0) {
return null;
}
return targetName;
}
private int getOffset(IDocument document, int line, int column) {
try {
return document.getLineOffset(line) + column - 1;
}
catch (BadLocationException e) {
return -1;
}
}
/**
* Sets this processor's set of characters triggering the activation of the completion proposal computation.
*
* @param activationSet
* the activation set
*/
public void setCompletionProposalAutoActivationCharacters(char[] activationSet) {
autoActivationChars = activationSet;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#extractPrefix(org.eclipse.jface.text.ITextViewer, int)
*/
@Override
protected String extractPrefix(ITextViewer textViewer, int offset) {
return getPrefixFromDocument(textViewer.getDocument().get(), offset);
}
/**
* Cut out angular brackets for relevance sorting, since the template name does not contain the brackets.
*/
@Override
protected int getRelevance(Template template, String prefix) {
String newprefix = prefix;
if (newprefix.startsWith("<")) {//$NON-NLS-1$
newprefix = prefix.substring(1);
}
if (template.getName().startsWith(newprefix)) {
return 90;
}
return 0;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getTemplates(java.lang.String)
*/
@Override
protected Template[] getTemplates(String contextTypeId) {
return AntTemplateAccess.getDefault().getTemplateStore().getTemplates(contextTypeId);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getContextType(org.eclipse.jface.text.ITextViewer,
* org.eclipse.jface.text.IRegion)
*/
@Override
protected TemplateContextType getContextType(ITextViewer textViewer, IRegion region) {
switch (determineProposalMode(textViewer.getDocument(), cursorPosition, getCurrentPrefix())) {
case PROPOSAL_MODE_TASK_PROPOSAL:
if (getEnclosingTargetName(textViewer.getDocument(), lineNumber, columnNumber) == null) {
return AntTemplateAccess.getDefault().getContextTypeRegistry().getContextType(TargetContextType.TARGET_CONTEXT_TYPE);
}
return AntTemplateAccess.getDefault().getContextTypeRegistry().getContextType(TaskContextType.TASK_CONTEXT_TYPE);
case PROPOSAL_MODE_BUILDFILE:
return AntTemplateAccess.getDefault().getContextTypeRegistry().getContextType(BuildFileContextType.BUILDFILE_CONTEXT_TYPE);
case PROPOSAL_MODE_NONE:
case PROPOSAL_MODE_ATTRIBUTE_PROPOSAL:
case PROPOSAL_MODE_TASK_PROPOSAL_CLOSING:
case PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL:
case PROPOSAL_MODE_PROPERTY_PROPOSAL:
case PROPOSAL_MODE_NESTED_ELEMENT_PROPOSAL:
default:
return null;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getImage(org.eclipse.jface.text.templates.Template)
*/
@Override
protected Image getImage(Template template) {
return AntUIImages.getImage(IAntUIConstants.IMG_TEMPLATE_PROPOSAL);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#createContext(org.eclipse.jface.text.ITextViewer,
* org.eclipse.jface.text.IRegion)
*/
@Override
protected TemplateContext createContext(ITextViewer contextViewer, IRegion region) {
TemplateContextType contextType = getContextType(contextViewer, region);
if (contextType != null) {
Point selection = contextViewer.getSelectedRange();
Position position;
if (selection.y > 0) {
position = new Position(selection.x, selection.y);
} else {
position = new Position(region.getOffset(), region.getLength());
}
IDocument document = contextViewer.getDocument();
return new AntContext(contextType, document, antModel, position);
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#createProposal(org.eclipse.jface.text.templates.Template,
* org.eclipse.jface.text.templates.TemplateContext, org.eclipse.jface.text.Region, int)
*/
@Override
protected ICompletionProposal createProposal(Template template, TemplateContext context, IRegion region, int relevance) {
AntTemplateProposal proposal = new AntTemplateProposal(template, context, region, getImage(template), relevance);
proposal.setInformationControlCreator(new AntTemplateInformationControlCreator());
return proposal;
}
protected ISchema getDtd() {
if (fgDtd == null) {
IRunnableWithProgress runnable = new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
try {
fgDtd = parseDtd();
}
catch (IOException e) {
AntUIPlugin.log(e);
}
catch (ParseError e) {
AntUIPlugin.log(e);
}
}
};
IProgressService service = PlatformUI.getWorkbench().getProgressService();
try {
service.busyCursorWhile(runnable);
}
catch (InvocationTargetException e) {
// do nothing
}
catch (InterruptedException e) {
// do nothing
}
}
return fgDtd;
}
/**
* The provider for all task and attribute descriptions.
*/
private TaskDescriptionProvider getDescriptionProvider() {
return TaskDescriptionProvider.getDefault();
}
protected static void resetCodeCompletionDataStructures() {
fgDtd = null;
TaskDescriptionProvider.reset();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionStarted(org.eclipse.jface.text.contentassist.ContentAssistEvent)
*/
@Override
public void assistSessionStarted(ContentAssistEvent event) {
IContentAssistant assistant = event.assistant;
if (assistant instanceof IContentAssistantExtension2) {
fContentAssistant = (IContentAssistantExtension2) assistant;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionEnded(org.eclipse.jface.text.contentassist.ContentAssistEvent)
*/
@Override
public void assistSessionEnded(ContentAssistEvent event) {
fContentAssistant = null;
fTemplatesOnly = false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.contentassist.ICompletionListener#selectionChanged(org.eclipse.jface.text.contentassist.ICompletionProposal,
* boolean)
*/
@Override
public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
// do nothing
}
private String getIterationGestureMessage(String showMessage) {
final IBindingService bindingSvc = PlatformUI.getWorkbench().getAdapter(IBindingService.class);
TriggerSequence[] triggers = bindingSvc.getActiveBindingsFor(getContentAssistCommand());
String message;
if (triggers.length > 0) {
message = MessageFormat.format(AntEditorMessages.getString("AntEditorCompletionProcessor.63"), new Object[] { triggers[0].format(), showMessage }); //$NON-NLS-1$
} else {
message = MessageFormat.format(AntEditorMessages.getString("AntEditorCompletionProcessor.64"), new Object[] { showMessage }); //$NON-NLS-1$
}
return message;
}
private ParameterizedCommand getContentAssistCommand() {
final ICommandService commandSvc = PlatformUI.getWorkbench().getAdapter(ICommandService.class);
final Command command = commandSvc.getCommand(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
ParameterizedCommand pCmd = new ParameterizedCommand(command, null);
return pCmd;
}
}