/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.omni.elements; import com.google.dart.server.utilities.instrumentation.Instrumentation; import com.google.dart.server.utilities.instrumentation.InstrumentationBuilder; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.core.pub.PubCacheManager_NEW; import com.google.dart.tools.search.ui.text.TextSearchScopeFilter; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.omni.OmniBoxMessages; import com.google.dart.tools.ui.omni.OmniElement; import com.google.dart.tools.ui.omni.OmniProposalProvider; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceProxy; import org.eclipse.core.resources.IResourceProxyVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.ui.dialogs.SearchPattern; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Provider for files. */ public class FileProvider extends OmniProposalProvider { private class FileCollector implements IResourceProxyVisitor { private final IProgressMonitor progressMonitor; private final List<OmniElement> matches = new ArrayList<OmniElement>(); private final List<IResource> projects; private final boolean showDerived = false; private final int filterTypeMask = IResource.FILE; public FileCollector(IProgressMonitor progressMonitor) throws CoreException { this.progressMonitor = progressMonitor; IResource[] resources = container.members(); this.projects = new ArrayList<IResource>(Arrays.asList(resources)); if (progressMonitor != null) { progressMonitor.beginTask(OmniBoxMessages.TextSearch_taskName, projects.size()); } } public OmniElement[] getFiles() { return matches.toArray(EMPTY_ARRAY); } /** * @param item Must be instance of IFile, otherwise <code>false</code> will be returned. * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.ItemsFilter#matchItem(java.lang.Object) */ public boolean matchItem(Object item) { if (!(item instanceof IFile)) { return false; } IFile resource = (IFile) item; if (isFiltered(resource)) { return false; } if (!isProviderResource(resource)) { return false; } String name = resource.getName(); IPath path = resource.getFullPath(); //exclude .project && .children files //TODO (pquitslund): consider centralizing this filter when core search is integrated if (name.equals(".project") || name.equals(".children")) { //$NON-NLS-1$ //$NON-NLS-2$ // e.g., /MyProject/.project if (path.segmentCount() == 2) { return false; } } // exclude files in packages in example, bin etc folders String pathString = path.toString(); for (String packagePath : PUB_DIRECTORY_PATHS) { if (pathString.contains(packagePath)) { return false; } } if (nameMatches(name)) { if (containerPattern != null) { // match full container path: String containerPath = resource.getParent().getFullPath().toString(); if (containerPattern.matches(containerPath)) { return true; } // match path relative to current selection: if (relativeContainerPattern != null) { return relativeContainerPattern.matches(containerPath); } return false; } return true; } return false; } @Override public boolean visit(IResourceProxy proxy) { if (progressMonitor.isCanceled()) { return false; } IResource resource = proxy.requestResource(); if (this.projects.remove((resource.getProject())) || this.projects.remove((resource))) { progressMonitor.worked(1); } if (matchItem(resource)) { matches.add(new FileElement(FileProvider.this, (IFile) resource)); } if (resource.getType() == IResource.FOLDER && !shouldTraverseFolder(resource)) { return false; } if (resource.getType() == IResource.FILE) { return false; } return true; } /** * Matches text with filter. * * @param text the text to match with the filter * @return <code>true</code> if text matches with filter pattern, <code>false</code> otherwise */ protected boolean matches(String text) { return patternMatcher.matches(text); } private boolean isFiltered(IFile resource) { return !DartCore.isAnalyzed(resource) || (!this.showDerived && resource.isDerived()) || ((this.filterTypeMask & resource.getType()) == 0) || TextSearchScopeFilter.isSelfLinkedPackageResource(resource); } private boolean nameMatches(String name) { if (namePattern != null) { // fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=212565 int lastDot = name.lastIndexOf('.'); if (lastDot != -1 && namePattern.matches(name.substring(0, lastDot)) && extensionPattern.matches(name.substring(lastDot + 1))) { return true; } } return matches(name); } private boolean shouldTraverseFolder(IResource resource) { if (resource.isDerived()) { return false; } String name = resource.getName(); return name != null && !name.startsWith("."); } } /** * The directories in the pub white list, ones which have symlinks to the packages directory */ // (TODO:keertip) Make sure this stays in sync with documentation // http://pub.dartlang.org/doc/pub-install.html private static final List<String> PUB_DIRECTORY_PATHS = Arrays.asList( "/web/packages/", "/example/packages/", "/test/packages/", "/bin/packages/"); public static FileProvider packageFiles(IProgressMonitor pm) { return new FileProvider(pm) { @Override public String getName() { return "Package Files"; } @Override protected boolean isProviderResource(IResource resource) { return PubCacheManager_NEW.isPubCacheResource(resource); } }; } public static FileProvider projectFiles(IProgressMonitor pm) { return new FileProvider(pm) { @Override public String getName() { return "Project Files"; } @Override protected boolean isProviderResource(IResource resource) { return !PubCacheManager_NEW.isPubCacheResource(resource); } }; } /** * The base outer-container which will be used to search for resources. This is the root of the * tree that spans the search space. Often, this is the workspace root. */ private final IContainer container; private final SearchPattern patternMatcher; /** * Container path pattern. Is <code>null</code> when only a file name pattern is used. */ private SearchPattern containerPattern; /** * Container path pattern, relative to the current searchContainer. Is <code>null</code> if * there's no search container. */ private SearchPattern relativeContainerPattern; /** * Camel case pattern for the name part of the file name (without extension). Is <code>null</code> * if there's no extension. */ private SearchPattern namePattern; /** * Camel case pattern for the file extension. Is <code>null</code> if there's no extension. */ private SearchPattern extensionPattern; private final IProgressMonitor progressMonitor; private static OmniElement[] EMPTY_ARRAY = new OmniElement[0]; public FileProvider(IProgressMonitor progressMonitor) { this.progressMonitor = progressMonitor; this.container = ResourcesPlugin.getWorkspace().getRoot(); this.patternMatcher = new SearchPattern(); } @Override public OmniElement getElementForId(String id) { //TODO (pquitslund): this singelton match concept needs a rethink OmniElement[] elements = getElements(id); if (elements.length == 0) { return null; } return elements[0]; } @Override public OmniElement[] getElements(String stringPattern) { InstrumentationBuilder instrumentation = Instrumentation.builder("Omni-FileProvider.doSearch"); try { String filenamePattern; int sep = stringPattern.lastIndexOf(IPath.SEPARATOR); if (sep != -1) { filenamePattern = stringPattern.substring(sep + 1, stringPattern.length()); if ("*".equals(filenamePattern)) { filenamePattern = "**"; //$NON-NLS-1$ } if (sep > 0) { if (filenamePattern.length() == 0) { filenamePattern = "**"; //$NON-NLS-1$ } String containerPattern = stringPattern.substring(0, sep); if (container != null) { relativeContainerPattern = new SearchPattern(SearchPattern.RULE_EXACT_MATCH | SearchPattern.RULE_PATTERN_MATCH); relativeContainerPattern.setPattern(container.getFullPath().append(containerPattern).toString()); } if (!containerPattern.startsWith("" + IPath.SEPARATOR)) { containerPattern = IPath.SEPARATOR + containerPattern; } this.containerPattern = new SearchPattern(SearchPattern.RULE_EXACT_MATCH | SearchPattern.RULE_PREFIX_MATCH | SearchPattern.RULE_PATTERN_MATCH); this.containerPattern.setPattern(containerPattern); } patternMatcher.setPattern(filenamePattern); } else { filenamePattern = stringPattern; patternMatcher.setPattern(stringPattern); } int lastPatternDot = filenamePattern.lastIndexOf('.'); if (lastPatternDot != -1) { char last = filenamePattern.charAt(filenamePattern.length() - 1); if (last != ' ' && last != '<' && getMatchRule() != SearchPattern.RULE_EXACT_MATCH) { namePattern = new SearchPattern(); namePattern.setPattern(filenamePattern.substring(0, lastPatternDot)); extensionPattern = new SearchPattern(); extensionPattern.setPattern(filenamePattern.substring(lastPatternDot + 1)); } } try { FileCollector collector = new FileCollector(progressMonitor); container.accept(collector, IResource.NONE); return collector.getFiles(); } catch (CoreException e) { DartToolsPlugin.log(e); } return EMPTY_ARRAY; } finally { instrumentation.log(); } } @Override public String getId() { return "com.google.dart.tools.ui.files"; //$NON-NLS-1$ } @Override public String getName() { return OmniBoxMessages.OmniBox_Files; } protected boolean isProviderResource(IResource resource) { return true; } /** * Returns the rule to apply for matching keys. * * @return an implementation-specific match rule * @see SearchPattern#getMatchRule() for match rules returned by the default implementation */ private int getMatchRule() { return patternMatcher.getMatchRule(); } }