diff options
author | Anton Tanasenko | 2016-03-17 23:58:36 +0000 |
---|---|---|
committer | Anton Tanasenko | 2016-03-20 19:48:58 +0000 |
commit | fdc97365308e3422a055311b43e1e5dde78ffeba (patch) | |
tree | 390fb09c036d37881fb35ace1a98d76e04b8afc0 /org.eclipse.m2e.editor.xml | |
parent | 89cd40ba907e178d523f40ac22f7eac0291de37b (diff) | |
download | m2e-core-fdc97365308e3422a055311b43e1e5dde78ffeba.tar.gz m2e-core-fdc97365308e3422a055311b43e1e5dde78ffeba.tar.xz m2e-core-fdc97365308e3422a055311b43e1e5dde78ffeba.zip |
489755 Provide content assist on resources' <directory> nodes
Change-Id: Iaaf18f7763420ec422f7f0013383b9122aa1d130
Signed-off-by: Anton Tanasenko <atg.sleepless@gmail.com>
Diffstat (limited to 'org.eclipse.m2e.editor.xml')
11 files changed, 669 insertions, 148 deletions
diff --git a/org.eclipse.m2e.editor.xml/OSGI-INF/l10n/bundle.properties b/org.eclipse.m2e.editor.xml/OSGI-INF/l10n/bundle.properties index eb4580ca..c7bc3d4c 100644 --- a/org.eclipse.m2e.editor.xml/OSGI-INF/l10n/bundle.properties +++ b/org.eclipse.m2e.editor.xml/OSGI-INF/l10n/bundle.properties @@ -25,7 +25,9 @@ contextType.packaging = Packaging contextType.scope = Scope contextType.phase = Phase contextType.goal = Goal +contextType.modules = Modules contextType.module = Module +contextType.file = File or directory template.project.description = New project element template.project.name = project template.parent.description = New parent element diff --git a/org.eclipse.m2e.editor.xml/plugin.xml b/org.eclipse.m2e.editor.xml/plugin.xml index c6535a4d..29a73566 100644 --- a/org.eclipse.m2e.editor.xml/plugin.xml +++ b/org.eclipse.m2e.editor.xml/plugin.xml @@ -97,49 +97,57 @@ id="org.eclipse.m2e.editor.xml.templates.contextType.repositories"/> <contextType name="%contextType.groupid" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.groupId"/> <contextType name="%contextType.artifactid" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.artifactId"/> <contextType name="%contextType.version" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.version"/> <contextType name="%contextType.classifier" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.classifier"/> <contextType name="%contextType.type" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.type"/> <contextType name="%contextType.systemPath" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.systemPath"/> <contextType name="%contextType.packaging" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.packaging"/> <contextType name="%contextType.scope" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.scope"/> <contextType name="%contextType.phase" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.phase"/> <contextType name="%contextType.goal" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.goal"/> + <contextType name="%contextType.modules" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" + id="org.eclipse.m2e.editor.xml.templates.contextType.modules"/> + <contextType name="%contextType.module" - class="org.eclipse.jface.text.templates.TemplateContextType" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" id="org.eclipse.m2e.editor.xml.templates.contextType.module"/> + <contextType name="%contextType.file" + class="org.eclipse.m2e.editor.xml.PomTemplateContextType" + id="org.eclipse.m2e.editor.xml.templates.contextType.file"/> + <template id="org.eclipse.m2e.editor.xml.templates.project.skeleton" contextTypeId="org.eclipse.m2e.editor.xml.templates.contextType.#document" name="%template.project.name" diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/InsertExpressionProposal.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/InsertExpressionProposal.java index 973c6837..6dedd533 100644 --- a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/InsertExpressionProposal.java +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/InsertExpressionProposal.java @@ -24,6 +24,7 @@ import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; +import org.eclipse.wst.sse.ui.internal.contentassist.IRelevanceCompletionProposal; import org.apache.maven.model.InputLocation; import org.apache.maven.model.InputSource; @@ -38,7 +39,8 @@ import org.eclipse.m2e.editor.xml.internal.Messages; * * @author mkleint */ -public class InsertExpressionProposal implements ICompletionProposal, ICompletionProposalExtension5 { +public class InsertExpressionProposal + implements ICompletionProposal, ICompletionProposalExtension5, IRelevanceCompletionProposal { private static final Logger log = LoggerFactory.getLogger(InsertExpressionProposal.class); private MavenProject project; @@ -124,4 +126,8 @@ public class InsertExpressionProposal implements ICompletionProposal, ICompletio return null; } + public int getRelevance() { + return 2000; + } + } diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnImages.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnImages.java index 417f7b4b..d1fbb471 100644 --- a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnImages.java +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnImages.java @@ -12,6 +12,9 @@ package org.eclipse.m2e.editor.xml; +import java.util.HashMap; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +46,8 @@ public class MvnImages { public static final ImageDescriptor IMGD_EXECUTION = create("execution_obj.gif"); //$NON-NLS-1$ + public static final Image IMG_DISCOVERY = createImage("insp_sbook.gif"); //$NON-NLS-1$ + public static final Image IMG_EXECUTION = createImage("execution_obj.gif"); //$NON-NLS-1$ public static final Image IMG_GOAL = createImage("goal_obj.gif"); //$NON-NLS-1$ @@ -87,6 +92,8 @@ public class MvnImages { public static final ImageDescriptor IMGD_WARNINGS = create("warnings.png"); //$NON-NLS-1$ + private static Map<ImageDescriptor, Image> customImages = new HashMap<>(); + private static ImageDescriptor create(String key) { try { ImageDescriptor imageDescriptor = createDescriptor(key); @@ -122,4 +129,22 @@ public class MvnImages { return AbstractUIPlugin.imageDescriptorFromPlugin(MvnIndexPlugin.PLUGIN_ID, "icons/" + image); //$NON-NLS-1$ } + public static Image getImage(ImageDescriptor imageDescriptor) { + Image image = customImages.get(imageDescriptor); + if(image != null) { + return image; + } + image = imageDescriptor.createImage(); + if(image != null) { + customImages.put(imageDescriptor, image); + } + return image; + } + + static void disposeCustomImages() { + for(Image img : customImages.values()) { + img.dispose(); + } + customImages.clear(); + } } diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnIndexPlugin.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnIndexPlugin.java index 707f188b..131e666e 100644 --- a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnIndexPlugin.java +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/MvnIndexPlugin.java @@ -49,6 +49,7 @@ public class MvnIndexPlugin extends AbstractUIPlugin { @Override public void stop(BundleContext context) throws Exception { super.stop(context); + MvnImages.disposeCustomImages(); defaultInstance = null; } diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomContentAssistProcessor.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomContentAssistProcessor.java index 5df52b75..9c33d4c0 100644 --- a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomContentAssistProcessor.java +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomContentAssistProcessor.java @@ -12,6 +12,8 @@ package org.eclipse.m2e.editor.xml; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Properties; @@ -26,7 +28,6 @@ import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; @@ -49,7 +50,6 @@ import org.eclipse.ui.PlatformUI; import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.core.utils.StringUtils; import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext; -import org.eclipse.wst.sse.ui.internal.contentassist.IRelevanceCompletionProposal; import org.eclipse.wst.xml.ui.internal.contentassist.ContentAssistRequest; import org.eclipse.wst.xml.ui.internal.contentassist.DefaultXMLCompletionProposalComputer; @@ -76,9 +76,18 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu PomTemplateContext.CLASSIFIER, PomTemplateContext.SCOPE, PomTemplateContext.SYSTEM_PATH, // PomTemplateContext.PROPERTIES, PomTemplateContext.MODULE, // PomTemplateContext.PHASE, PomTemplateContext.GOAL, PomTemplateContext.CONFIGURATION, // + PomTemplateContext.SOURCEDIRECTORY, PomTemplateContext.SCRIPTSOURCEDIRECTORY, + PomTemplateContext.TESTSOURCEDIRECTORY, // + PomTemplateContext.OUTPUTDIRECTORY, PomTemplateContext.TESTOUTPUTDIRECTORY, // + PomTemplateContext.DIRECTORY, PomTemplateContext.FILTER, // //this one is both important and troubling.. but having a context for everything is weird. PomTemplateContext.UNKNOWN); + private static List<String> hardwiredProperties = Collections.unmodifiableList(Arrays.asList( // + "basedir", "project.basedir", // + "project.version", "project.groupId", "project.artifactId", "project.version", "project.name", // + "project.build.directory", "project.build.outputDirectory")); + protected void addTagNameProposals(ContentAssistRequest contentAssistRequest, int childPosition, CompletionProposalInvocationContext ctx) { PomTemplateContext context = PomTemplateContext.fromNode(contentAssistRequest.getParent()); @@ -110,7 +119,7 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu * @param request * @param context * @param currentNode - * @param prefix + * @param prefixPath */ private void addExpressionProposals(ContentAssistRequest request, PomTemplateContext context, ITextViewer sourceViewer) { @@ -129,11 +138,23 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu MavenProject prj = XmlUtils.extractMavenProject(sourceViewer); Region region = new Region(request.getReplacementBeginPosition() + exprStart, realExpressionPrefix.length()); Set<String> collect = new TreeSet<String>(); + + String currentProp = null; + Node node = request.getParent(); + if(PomTemplateContext.getAncestor(node, "properties", "project") != null + || PomTemplateContext.getAncestor(node, "properties", "profile", "profiles", "project") != null) { + currentProp = node.getLocalName(); + } + if(prj != null) { Properties props = prj.getProperties(); if(props != null) { for(Object key : props.keySet()) { String keyString = key.toString(); + if(keyString.equals(currentProp)) { + // do not allow recursive property usage + continue; + } if(("${" + keyString).startsWith(realExpressionPrefix)) { //$NON-NLS-1$ collect.add(keyString); } @@ -142,20 +163,10 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu } //add a few hardwired values as well - if("${basedir}".startsWith(realExpressionPrefix)) { //$NON-NLS-1$ - collect.add("basedir"); //$NON-NLS-1$ - } - if("${project.version}".startsWith(realExpressionPrefix)) { //$NON-NLS-1$ - collect.add("project.version"); //$NON-NLS-1$ - } - if("${project.groupId}".startsWith(realExpressionPrefix)) { //$NON-NLS-1$ - collect.add("project.groupId"); //$NON-NLS-1$ - } - if("${project.artifactId}".startsWith(realExpressionPrefix)) { //$NON-NLS-1$ - collect.add("project.artifactId"); //$NON-NLS-1$ - } - if("${project.build.directory}".startsWith(realExpressionPrefix)) { //$NON-NLS-1$ - collect.add("project.build.directory"); //$NON-NLS-1$ + for(String prop : hardwiredProperties) { + if(("${" + prop).startsWith(realExpressionPrefix)) { //$NON-NLS-1$ + collect.add(prop); + } } for(String key : collect) { request.addProposal(new InsertExpressionProposal(region, key, prj)); @@ -301,6 +312,22 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu String prefix = request.getMatchString(); int len = prefix.length(); + // replace text until the next whitespace or tag end + IndexedRegion ir = (IndexedRegion) request.getNode(); + if(ir instanceof Text) { + IDocument document = sourceViewer.getDocument(); + for(int i = offset + len; i < ir.getEndOffset(); i++ ) { + try { + if(Character.isWhitespace(document.getChar(i))) { + break; + } + } catch(BadLocationException e) { + break; + } + len++ ; + } + } + // also replace opening '<' if(tagProposals) { offset-- ; @@ -318,28 +345,31 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu // add the user defined templates - separate them from the rest of the templates // so that we know what they are and can assign proper icon to them. - Image image = MvnImages.IMG_USER_TEMPLATE; List<TemplateProposal> matches = new ArrayList<TemplateProposal>(); TemplateStore store = MvnIndexPlugin.getDefault().getTemplateStore(); if(store != null) { Template[] templates = store.getTemplates(context.getContextTypeId()); for(Template template : templates) { - TemplateProposal proposal = createProposalForTemplate(prefix, region, templateContext, image, template, true); + TemplateProposal proposal = createProposalForTemplate(prefix, region, templateContext, + MvnImages.IMG_USER_TEMPLATE, template, true); if(proposal != null) { matches.add(proposal); } } } - if(context == PomTemplateContext.CONFIGURATION) { - image = MvnImages.IMG_PARAMETER; - } else { - //other suggestions from the templatecontext are to be text inside the element, not actual - //elements.. - image = null; - } Template[] templates = context.getTemplates(prj, eclipseprj, parentNode, prefix); for(Template template : templates) { + Image image = null; + + if(template instanceof PomTemplate) { + image = ((PomTemplate) template).getImage(); + } + + if(image == null && context == PomTemplateContext.CONFIGURATION) { + image = MvnImages.IMG_PARAMETER; + } + TemplateProposal proposal = createProposalForTemplate(prefix, region, templateContext, image, template, false); if(proposal != null) { matches.add(proposal); @@ -392,6 +422,12 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu //TODO we should have different relevance for user defined templates and generated proposals.. protected int getRelevance(Template template, String prefix) { + if(template instanceof PomTemplate) { + int rel = ((PomTemplate) template).getRelevance(); + if(rel != -1) + return rel; + } + if(template.getName().startsWith(prefix)) return 1900; return 1500; @@ -426,28 +462,4 @@ public class PomContentAssistProcessor extends DefaultXMLCompletionProposalCompu } } - private static class PomTemplateProposal extends TemplateProposal implements IRelevanceCompletionProposal { - - public PomTemplateProposal(Template template, TemplateContext context, IRegion region, Image image, int relevance) { - super(template, context, region, image, relevance); - } - - @Override - public boolean validate(IDocument document, int offset, DocumentEvent event) { - try { - int replaceOffset = getReplaceOffset(); - if(offset >= replaceOffset) { - String content = document.get(replaceOffset, offset - replaceOffset); - if(!content.isEmpty() && content.charAt(0) == '<') { - content = content.substring(1); - } - return getTemplate().getName().toLowerCase().startsWith(content.toLowerCase()); - } - } catch(BadLocationException e) { - // concurrent modification - ignore - } - return false; - } - } - } diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplate.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplate.java new file mode 100644 index 00000000..66b5c214 --- /dev/null +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplate.java @@ -0,0 +1,73 @@ +/*******************************************************************************
+ * Copyright (c) 2016 Anton Tanasenko
+ * 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:
+ * Anton Tanasenko - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.m2e.editor.xml;
+
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.swt.graphics.Image;
+
+
+/**
+ * PomTemplate
+ *
+ * @author atanasenko
+ */
+public class PomTemplate extends Template {
+
+ private Image image;
+
+ private int relevance;
+
+ private String matchValue;
+
+ private boolean retriggerOnApply;
+
+ public PomTemplate(String name, String description, String contextTypeId, String pattern, boolean isAutoInsertable) {
+ super(name, description, contextTypeId, pattern, isAutoInsertable);
+ }
+
+ public Image getImage() {
+ return this.image;
+ }
+
+ public PomTemplate image(Image image) {
+ this.image = image;
+ return this;
+ }
+
+ public int getRelevance() {
+ return this.relevance;
+ }
+
+ public PomTemplate relevance(int relevance) {
+ this.relevance = relevance;
+ return this;
+ }
+
+ public String getMatchValue() {
+ return this.matchValue;
+ }
+
+ public PomTemplate matchValue(String matchValue) {
+ this.matchValue = matchValue;
+ return this;
+ }
+
+ public boolean isRetriggerOnApply() {
+ return this.retriggerOnApply;
+ }
+
+ public PomTemplate retriggerOnApply(boolean retriggerOnApply) {
+ this.retriggerOnApply = retriggerOnApply;
+ return this;
+ }
+
+}
diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplateContext.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplateContext.java index a700d5c6..2f45c17b 100644 --- a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplateContext.java +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplateContext.java @@ -12,12 +12,22 @@ package org.eclipse.m2e.editor.xml; import java.io.File; -import java.io.FileFilter; +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Properties; import java.util.Set; @@ -30,15 +40,22 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.templates.Template; import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.IWorkbenchAdapter; import org.codehaus.plexus.interpolation.InterpolationException; import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; import org.codehaus.plexus.interpolation.PropertiesBasedValueSource; import org.codehaus.plexus.interpolation.RegexBasedInterpolator; -import org.codehaus.plexus.util.StringUtils; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; @@ -81,8 +98,6 @@ public enum PomTemplateContext { RELATIVE_PATH("relativePath"), // //$NON-NLS-1$ - MODULES("modules"), // //$NON-NLS-1$ - PROPERTIES("properties"), // //$NON-NLS-1$ DEPENDENCIES("dependencies"), // //$NON-NLS-1$ @@ -192,7 +207,8 @@ public enum PomTemplateContext { MojoParameter param = result.getContainer(configPath); if(param != null) { - for(MojoParameter parameter : param.getNestedParameters()) { + List<MojoParameter> nestedParameters = param.getNestedParameters(); + for(MojoParameter parameter : nestedParameters) { String name = parameter.getName(); if(name.startsWith(prefix)) { @@ -229,6 +245,25 @@ public enum PomTemplateContext { } + if(nestedParameters.size() == 1) { + MojoParameter nestedParam = nestedParameters.get(0); + if(nestedParam.isMultiple()) { + boolean containsFiles = File.class.getSimpleName().equals(nestedParam.getType()) + || PomTemplateContext.fromNodeName(nestedParam.getName()).handlesFiles(); + if(containsFiles) { + addFileTemplates(project, eclipseprj, proposals, node, prefix, + param.getName().toLowerCase().endsWith("directory"), nestedParam.getName()); + } + } + } + + boolean containsFiles = File.class.getSimpleName().equals(param.getType()) + || PomTemplateContext.fromNodeName(param.getName()).handlesFiles(); + if(containsFiles) { + addFileTemplates(project, eclipseprj, proposals, node, prefix, + param.getName().toLowerCase().endsWith("directory"), null); + } + } } }, @@ -392,7 +427,7 @@ public enum PomTemplateContext { checkAndAdd(proposals, prefix, "runtime"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "system"); //$NON-NLS-1$ - if(checkAncestors(node.getParentNode(), "dependency", "dependencies", "dependencyManagement")) {// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + if(getAncestor(node, "dependency", "dependencies", "dependencyManagement") != null) {// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ checkAndAdd(proposals, prefix, "import"); //$NON-NLS-1$ } } @@ -484,85 +519,31 @@ public enum PomTemplateContext { } }, - MODULE("module") { //$NON-NLS-1$ + MODULES("modules") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) { - if(project == null) { - //shall not happen just double check. - return; - } - //MNGECLIPSE-2204 collect the existing values from the surrounding xml content only.. - List<String> existings = new ArrayList<String>(); - Node moduleNode = node; - if(moduleNode != null) { - Node modulesNode = moduleNode.getParentNode(); - if(modulesNode != null) { - for(Element el : XmlUtils.findChilds((Element) modulesNode, "module")) { - if(el != moduleNode) { - String val = XmlUtils.getTextValue(el); - if(val != null) { - existings.add(val); - } - } - } - } - } + addModuleTemplates(project, eclipseprj, proposals, node, prefix, true); + } + }, - File directory = new File(eclipseprj.getLocationURI()); - final File currentPom = new File(directory, "pom.xml"); - String path = prefix; - boolean endingSlash = path.endsWith("/"); //$NON-NLS-1$ - String[] elems = StringUtils.split(path, "/"); //$NON-NLS-1$ - String lastElement = null; - for(int i = 0; i < elems.length; i++ ) { - if("..".equals(elems[i])) { //$NON-NLS-1$ - directory = directory != null ? directory.getParentFile() : null; - } else if(i < elems.length - (endingSlash ? 0 : 1)) { - directory = directory != null ? new File(directory, elems[i]) : null; - } else { - lastElement = elems[i]; - } - } - path = lastElement != null ? path.substring(0, path.length() - lastElement.length()) : path; - FileFilter filter = new FileFilter() { - public boolean accept(File pathname) { - if(pathname.isDirectory()) { - File pom = new File(pathname, "pom.xml"); //$NON-NLS-1$ - //TODO shall also handle polyglot maven :) - return pom.exists() && pom.isFile() && !pom.equals(currentPom); - } - return false; - } - }; - if(directory != null && directory.exists() && directory.isDirectory()) { - File[] offerings = directory.listFiles(filter); - for(File candidate : offerings) { - if(lastElement == null || candidate.getName().startsWith(lastElement)) { - String val = path + candidate.getName(); - if(!existings.contains(val)) { //only those not already being added in the surrounding area - checkAndAdd(proposals, prefix, val, NLS.bind(Messages.PomTemplateContext_candidate, candidate)); - } - } - } - if(path.length() == 0 && directory.equals(currentPom.getParentFile())) { - //for the empty value, when searching in current directory, propose also stuff one level up. - File currentParent = directory.getParentFile(); - if(currentParent != null && currentParent.exists()) { - offerings = currentParent.listFiles(filter); - for(File candidate : offerings) { - String val = "../" + candidate.getName(); - if(!existings.contains(val)) { //only those not already being added in the surrounding area - checkAndAdd(proposals, prefix, val, NLS.bind(Messages.PomTemplateContext_candidate, candidate)); - } - } - } - } - } + MODULE("module") { //$NON-NLS-1$ + @Override + public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, + String prefix) { + addModuleTemplates(project, eclipseprj, proposals, node, prefix, false); } }, - LICENSES("licenses"); + SOURCEDIRECTORY("sourceDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ + SCRIPTSOURCEDIRECTORY("scriptSourceDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ + TESTSOURCEDIRECTORY("testSourceDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ + OUTPUTDIRECTORY("outputDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ + TESTOUTPUTDIRECTORY("testOutputDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ + DIRECTORY("directory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ + FILTER("filter", "file"), //$NON-NLS-1$ //$NON-NLS-2$ + + LICENSES("licenses"); //$NON-NLS-1$ private static final Logger log = LoggerFactory.getLogger(PomTemplateContext.class); @@ -570,14 +551,25 @@ public enum PomTemplateContext { private final String nodeName; + private final String contextSuffix; + private PomTemplateContext(String nodeName) { + this(nodeName, nodeName); + } + + private PomTemplateContext(String nodeName, String contextSuffix) { this.nodeName = nodeName; + this.contextSuffix = contextSuffix; } public boolean handlesSubtree() { return false; } + public boolean handlesFiles() { + return "file".equals(contextSuffix); //$NON-NLS-1$ + } + /** * Return templates depending on the context type. */ @@ -601,6 +593,301 @@ public enum PomTemplateContext { */ protected void addTemplates(MavenProject project, IProject eclipsePrj, Collection<Template> templates, Node currentNode, String prefix) throws CoreException { + if(handlesFiles()) { + addFileTemplates(project, eclipsePrj, templates, currentNode, prefix, name().toLowerCase().endsWith("directory"), + null); + } + } + + protected FileProposalContext getFileProposalContext(MavenProject project, IProject eclipsePrj, String prefix) { + if(project == null && eclipsePrj == null) { + return null; + } + + File projectDir; + if(project != null) { + projectDir = project.getFile().getParentFile(); + } else { + projectDir = new File(eclipsePrj.getLocationURI()); + } + + String parentPath; + String prefixPath; + int lastSep = prefix.lastIndexOf('/'); + if(lastSep != -1) { + prefixPath = prefix.substring(0, lastSep) + '/'; + prefix = prefix.substring(lastSep + 1); + } else { + prefixPath = ""; + } + String interpolated = simpleInterpolate(project, prefixPath); + parentPath = interpolated == null ? prefixPath : interpolated; + + File parentDir; + if(!new File(parentPath).isAbsolute()) { + parentDir = new File(projectDir, parentPath); + } else { + parentDir = new File(parentPath); + } + if(!parentDir.isDirectory()) { + return null; + } + return new FileProposalContext(projectDir, parentDir, prefixPath, prefix); + } + + protected void addFileTemplates(MavenProject project, IProject eclipsePrj, Collection<Template> templates, + Node currentNode, String prefix, boolean dirsOnly, String wrapperNode) { + + FileProposalContext pctx = getFileProposalContext(project, eclipsePrj, prefix); + if(pctx == null) { + return; + } + + List<File> files = Arrays.asList(pctx.parentDir.listFiles()); + Collections.sort(files, Comparator.<File, Integer> comparing(r -> r.isDirectory() ? 0 : 1) + .thenComparing(Comparator.comparing(r -> r.getName()))); + + int rel = 4000; + for(File f : files) { + if(f.getName().startsWith(pctx.prefix)) { + String value = pctx.prefixPath + f.getName(); + String template = value; + boolean retrigger = false; + + if(f.isDirectory()) { + if(hasContents(f, dirsOnly)) { + template += '/'; + retrigger = true; + } + } else if(dirsOnly) { + continue; + } + template = template.replace("$", "$$"); + + if(wrapperNode != null) { + template = '<' + wrapperNode + '>' + template + "${cursor}</" + wrapperNode + '>'; + } + + templates.add(new PomTemplate(f.getName(), "", getContextTypeId(), template, false).image(getFileIcon(f)) + .matchValue(value).relevance(rel-- ).retriggerOnApply(retrigger)); + } + } + } + + private boolean hasContents(File f, boolean dirsOnly) { + // using nio is faster for large dirs compared to File#listFiles() + boolean[] res = new boolean[] {false}; + Path thisPath = f.toPath(); + try { + Files.walkFileTree(thisPath, new SimpleFileVisitor<Path>() { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + if(thisPath.equals(dir)) { + return FileVisitResult.CONTINUE; + } + res[0] = true; + return FileVisitResult.TERMINATE; + } + + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if(dirsOnly) { + return FileVisitResult.CONTINUE; + } + res[0] = true; + return FileVisitResult.TERMINATE; + } + }); + } catch(IOException ex) { + } + return res[0]; + } + + protected void addModuleTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, + Node node, String prefix, boolean wrap) { + if(project == null) { + //shall not happen just double check. + return; + } + FileProposalContext pctx = getFileProposalContext(project, eclipseprj, prefix); + if(pctx == null) { + return; + } + + //MNGECLIPSE-2204 collect the existing values from the surrounding xml content only.. + // also, if it's a profile modules list, consider main modules as well + Set<String> existings = new HashSet<>(); + Node moduleNode = node; + if(moduleNode != null) { + Node modulesNode; + if(moduleNode.getLocalName().equals("modules")) { + modulesNode = moduleNode; + } else { + modulesNode = moduleNode.getParentNode(); + } + while(modulesNode != null) { + for(Element el : XmlUtils.findChilds((Element) modulesNode, "module")) { + if(el != moduleNode) { + String val = XmlUtils.getTextValue(el); + if(val != null) { + existings.add(val); + } + } + } + Node profileProjectNode = getAncestor(modulesNode, "profile", "profiles", "project"); + if(profileProjectNode != null) { + modulesNode = getChildWithName(profileProjectNode, "modules"); + } else { + modulesNode = null; + } + } + } + + Set<String> subProjects = new LinkedHashSet<>(); + try { + Path projectPath = pctx.projectDir.toPath().toRealPath(); + Path parentPath = pctx.parentDir.toPath().toRealPath(); + + FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() { + + boolean submodulesSearch; + + boolean submodulesFound; + + Path submodulesSearchBase; + + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + + if(submodulesSearch && submodulesFound) { + // drop out quickly if we were searching for submodules and at least one was already found + return FileVisitResult.SKIP_SIBLINGS; + } + + String name = dir.getFileName().toString(); + if(name.startsWith(".")) { + // skip dotfiles + return FileVisitResult.SKIP_SUBTREE; + } + + if(parentPath.equals(dir)) { + // don't propose the dir we are looking under + return FileVisitResult.CONTINUE; + } + + if(projectPath.equals(dir) && pctx.prefixPath.startsWith("../")) { + // skip this project's dir entirely when looking for modules in parent dir + return FileVisitResult.SKIP_SUBTREE; + } + + if(projectPath.startsWith(dir)) { + // skip ancestors of current dir + return FileVisitResult.CONTINUE; + } + + //TODO polyglot? + if(Arrays.asList("src", "target", "bin").contains(name) && dir.resolve("../pom.xml").toFile().exists()) { + // skip recursing into certain the project dirs + return FileVisitResult.SKIP_SUBTREE; + } + + if(dir.resolve("pom.xml").toFile().exists()) { + + if(submodulesSearch) { + // we were looking for submodules and found at least one + submodulesFound = true; + return FileVisitResult.SKIP_SIBLINGS; + } + + // found a candidate + String path = projectPath.relativize(dir).toString().replace('\\', '/'); + if(!existings.contains(path)) { + subProjects.add(path); + } + + // now we need to check for submodules, and if there are any, add a <path>/ proposal + submodulesSearch = true; + submodulesSearchBase = dir; + submodulesFound = false; + } + return FileVisitResult.CONTINUE; + } + + public FileVisitResult postVisitDirectory(Path dir, IOException e) { + if(submodulesSearch && dir.equals(submodulesSearchBase)) { + + // finish search for submodules + if(submodulesFound) { + String path = projectPath.relativize(dir).toString().replace('\\', '/'); + subProjects.add(path + '/'); + } + + submodulesSearch = false; + submodulesSearchBase = null; + submodulesFound = false; + } + + return FileVisitResult.CONTINUE; + } + }; + + Files.walkFileTree(parentPath, EnumSet.of(FileVisitOption.FOLLOW_LINKS), 5, visitor); + } catch(IOException e) { + } + + subProjects.removeAll(existings); + + int moduleRel = 8000; + int submoduleRel = 4000; + for(String path : subProjects) { + if(path.startsWith(prefix)) { + String value = path; + String template = value; + template = template.replace("$", "$$"); + + Image image; + String description; + int rel; + boolean retrigger = false; + if(path.endsWith("/")) { + image = MvnImages.IMG_DISCOVERY; + description = NLS.bind(Messages.PomTemplateContext_submodules, path); + rel = submoduleRel-- ; + retrigger = true; + } else { + image = getFileIcon(new File(pctx.projectDir, path)); + description = NLS.bind(Messages.PomTemplateContext_module, path); + rel = moduleRel-- ; + } + if(wrap) { + template = "<module>" + template + "${cursor}</module>"; + } + + proposals.add(new PomTemplate(value, description, getContextTypeId(), template, false).image(image) + .relevance(rel).retriggerOnApply(retrigger)); + } + } + } + + protected static Image getFileIcon(File f) { + IWorkspaceRoot wroot = ResourcesPlugin.getWorkspace().getRoot(); + IResource[] resources; + if(f.isDirectory()) { + resources = wroot.findContainersForLocationURI(f.toURI()); + } else { + resources = wroot.findFilesForLocationURI(f.toURI()); + } + IResource res = resources.length > 0 ? resources[0] : null; + if(res != null) { + IWorkbenchAdapter wbAdapter = res.getAdapter(IWorkbenchAdapter.class); + if(wbAdapter == null) { + return null; + } + ImageDescriptor id = wbAdapter.getImageDescriptor(res); + return id != null ? MvnImages.getImage(id) : null; + } + + if(f.isDirectory()) { + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); + } + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); } protected String getNodeName() { @@ -608,7 +895,7 @@ public enum PomTemplateContext { } public String getContextTypeId() { - return PREFIX + nodeName; + return PREFIX + contextSuffix; } public static PomTemplateContext fromId(String contextTypeId) { @@ -718,8 +1005,12 @@ public enum PomTemplateContext { } protected void checkAndAdd(Collection<Template> proposals, String prefix, String name, String description) { + checkAndAdd(proposals, prefix, name, name, -1); + } + + protected void checkAndAdd(Collection<Template> proposals, String prefix, String name, String description, int rel) { if(name.startsWith(prefix)) { - proposals.add(new Template(name, description, getContextTypeId(), name, false)); + proposals.add(new PomTemplate(name, description, getContextTypeId(), name, false).relevance(rel)); } } @@ -858,13 +1149,32 @@ public enum PomTemplateContext { return null; } - protected static boolean checkAncestors(Node n, String... names) { + protected static Node getAncestor(Node node, String... names) { int i = 0; - while(n != null && i < names.length) { - if(!names[i++ ].equals(n.getNodeName())) - return false; - n = n.getParentNode(); + for(; i < names.length; i++ ) { + Node parent = node.getParentNode(); + if(parent == null || !names[i].equals(parent.getNodeName())) + return null; + node = parent; } - return i == names.length; + return i == names.length ? node : null; + } + + private static class FileProposalContext { + final File projectDir; + + final File parentDir; + + final String prefixPath; + + final String prefix; + + FileProposalContext(File projectDir, File parentDir, String prefixPath, String prefix) { + this.projectDir = projectDir; + this.parentDir = parentDir; + this.prefixPath = prefixPath; + this.prefix = prefix; + } + } } diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplateProposal.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplateProposal.java new file mode 100644 index 00000000..5cc3fc56 --- /dev/null +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/PomTemplateProposal.java @@ -0,0 +1,78 @@ +/*******************************************************************************
+ * Copyright (c) 2008-2010 Sonatype, Inc.
+ * 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:
+ * Sonatype, Inc. - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.m2e.editor.xml;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextOperationTarget;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.text.templates.TemplateContext;
+import org.eclipse.jface.text.templates.TemplateProposal;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wst.sse.ui.internal.contentassist.IRelevanceCompletionProposal;
+
+
+@SuppressWarnings("restriction")
+public class PomTemplateProposal extends TemplateProposal implements IRelevanceCompletionProposal {
+
+ public PomTemplateProposal(Template template, TemplateContext context, IRegion region, Image image, int relevance) {
+ super(template, context, region, image, relevance);
+ }
+
+ @Override
+ public boolean validate(IDocument document, int offset, DocumentEvent event) {
+ try {
+ int replaceOffset = getReplaceOffset();
+ if(offset >= replaceOffset) {
+ String content = document.get(replaceOffset, offset - replaceOffset);
+ if(!content.isEmpty() && content.charAt(0) == '<') {
+ content = content.substring(1);
+ }
+ return getMatchValue().toLowerCase().startsWith(content.toLowerCase());
+ }
+ } catch(BadLocationException e) {
+ // concurrent modification - ignore
+ }
+ return false;
+ }
+
+ public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) {
+ super.apply(viewer, trigger, stateMask, offset);
+ if(retriggerOnApply()) {
+ Display.getDefault()
+ .asyncExec(() -> ((ITextOperationTarget) viewer).doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS));
+ }
+ }
+
+ private boolean retriggerOnApply() {
+ if(getTemplate() instanceof PomTemplate) {
+ return ((PomTemplate) getTemplate()).isRetriggerOnApply();
+ }
+ return false;
+ }
+
+ private String getMatchValue() {
+ String matchValue = null;
+ if(getTemplate() instanceof PomTemplate) {
+ matchValue = ((PomTemplate) getTemplate()).getMatchValue();
+ }
+ if(matchValue == null) {
+ return getTemplate().getName();
+ }
+ return matchValue;
+ }
+}
diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/Messages.java b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/Messages.java index cd41239d..1ab91673 100644 --- a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/Messages.java +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/Messages.java @@ -118,6 +118,8 @@ public class Messages extends NLS { public static String PomTemplateContext_insertParameter; + public static String PomTemplateContext_module; + public static String PomTemplateContext_resolvingPlugin; public static String PomTemplateContext_package; @@ -160,6 +162,8 @@ public class Messages extends NLS { public static String PomTemplateContext_sitedeploy; + public static String PomTemplateContext_submodules; + public static String PomTemplateContext_test; public static String PomTemplateContext_testcompile; diff --git a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/messages.properties b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/messages.properties index e6005218..7e60e908 100644 --- a/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/messages.properties +++ b/org.eclipse.m2e.editor.xml/src/main/java/org/eclipse/m2e/editor/xml/internal/messages.properties @@ -48,6 +48,7 @@ PomTemplateContext_generatetestsources=Generate any test source code for inclusi PomTemplateContext_install=Install the package into the local repository, for use as a dependency in other projects locally PomTemplateContext_integrationtest=Process and deploy the package if necessary into an environment where integration tests can be run PomTemplateContext_insertParameter=Insert parameter {0} +PomTemplateContext_module=Module {0} PomTemplateContext_package=Take the compiled code and package it in its distributable format, such as a JAR PomTemplateContext_param=<b>required:</b> {0}<br><b>type:</b> {1}<br> PomTemplateContext_param_def=default: {0}<br> @@ -69,6 +70,7 @@ PomTemplateContext_project_version_hint=For projects developed in sync only. PomTemplateContext_resolvingPlugin=Resolving plugin PomTemplateContext_site=Generates the project's site documentation PomTemplateContext_sitedeploy=Deploys the generated site documentation to the specified web server +PomTemplateContext_submodules=Potential submodules exist under {0} PomTemplateContext_test=Run tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed PomTemplateContext_testcompile=Compile the test source code into the test destination directory PomTemplateContext_validate=Validate the project is correct and all necessary information is available |