diff options
author | Kris De Volder | 2019-07-02 17:55:55 +0000 |
---|---|---|
committer | Mickael Istria | 2019-07-29 07:18:54 +0000 |
commit | e8b733007b85e76ddab4836dad7175c5012cc000 (patch) | |
tree | 1ab085cb20c4b762ffb94347c3839e7916a1003c | |
parent | e3145df862180f7b4ffe6376008b92dca3632947 (diff) | |
download | eclipse.platform.text-e8b733007b85e76ddab4836dad7175c5012cc000.tar.gz eclipse.platform.text-e8b733007b85e76ddab4836dad7175c5012cc000.tar.xz eclipse.platform.text-e8b733007b85e76ddab4836dad7175c5012cc000.zip |
Bug 548518: STS Quicksearch ContributionY20190729-0900
Make copy of STS quicksearch code from https://github.com/spring-projects/eclipse-integration-commons/
Changes from original code:
- plugin ids changed to org.eclipse.text.quicksearch
and org.eclipse.text.quicksearch.tests
- command and action set ids renamed to org.eclipse.text.quicksearch.*
- about.html changed based on example from other platform bundles
- packages renamed to org.eclipse.text.*
- Preference page reimplemented using jface FieldEditors.
- EPL license headers v1.0 => v2.0
- Add some missing license headers
- Externalize strings from preferences page UI
- Various small code style improvements from feedback in gerrit
- Keybinding changed to CTRL+ALT+SHIFT+L
- add '.internal' to package names
- use 'x-internal=true' on all packages
- export all packages
- bundle and package version constraints
- trailing whitespace cleanups
- configure save actions to remove trailing whitespace in the future
- remove jgit dependency
- adopt ant 'TokenizedPattern' as replacement for jgit in ResourceMatcher
- add some basic regression tests for ResourceMatcher
- add 'Automatic-Module-Name' headers to plugin manifests
Change-Id: Id2c1e0fbd75e7635656e3801cf31af09e71858d5
Signed-off-by: Kris De Volder <kdevolder@pivotal.io>
47 files changed, 5271 insertions, 0 deletions
diff --git a/org.eclipse.text.quicksearch.tests/.classpath b/org.eclipse.text.quicksearch.tests/.classpath new file mode 100644 index 00000000000..3e5654f17eb --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/.classpath @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"> + <attributes> + <attribute name="test" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eclipse.text.quicksearch.tests/.project b/org.eclipse.text.quicksearch.tests/.project new file mode 100644 index 00000000000..b370cef0340 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.text.quicksearch.tests</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.text.quicksearch.tests/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.text.quicksearch.tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..0c68a61dca8 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/org.eclipse.text.quicksearch.tests/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.text.quicksearch.tests/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 00000000000..36d45928741 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,61 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=false +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=false +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/org.eclipse.text.quicksearch.tests/META-INF/MANIFEST.MF b/org.eclipse.text.quicksearch.tests/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..f9397634612 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/META-INF/MANIFEST.MF @@ -0,0 +1,14 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.eclipse.text.quicksearch.tests +Bundle-Version: 1.0.0.qualifier +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.text.quicksearch, + org.eclipse.core.resources, + org.junit;bundle-version="4.8.0" +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-Vendor: %providerName +Export-Package: org.eclipse.text.quicksearch.tests +Automatic-Module-Name: org.eclipse.text.quicksearch.tests diff --git a/org.eclipse.text.quicksearch.tests/about.html b/org.eclipse.text.quicksearch.tests/about.html new file mode 100644 index 00000000000..c3b1d8fd59c --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/about.html @@ -0,0 +1,36 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /> +<title>About</title> +</head> +<body lang="EN-US"> + <h2>About This Content</h2> + + <p>July 2, 2019</p> + <h3>License</h3> + + <p> + The Eclipse Foundation makes available all content in this plug-in + ("Content"). Unless otherwise indicated below, the Content + is provided to you under the terms and conditions of the Eclipse + Public License Version 2.0 ("EPL"). A copy of the EPL is + available at <a href="http://www.eclipse.org/legal/epl-2.0">http://www.eclipse.org/legal/epl-2.0</a>. + For purposes of the EPL, "Program" will mean the Content. + </p> + + <p> + If you did not receive this Content directly from the Eclipse + Foundation, the Content is being redistributed by another party + ("Redistributor") and different terms and conditions may + apply to your use of any object code in the Content. Check the + Redistributor's license that was provided with the Content. If no such + license exists, contact the Redistributor. Unless otherwise indicated + below, the terms and conditions of the EPL still apply to any source + code in the Content and such source code may be obtained at <a + href="http://www.eclipse.org/">http://www.eclipse.org</a>. + </p> + +</body> +</html>
\ No newline at end of file diff --git a/org.eclipse.text.quicksearch.tests/build.properties b/org.eclipse.text.quicksearch.tests/build.properties new file mode 100644 index 00000000000..8762079a174 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/build.properties @@ -0,0 +1,18 @@ +############################################################################### +# Copyright (c) 2013-2019 Pivotal Inc 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: +# Pivotal Inc - initial API and implementation +############################################################################### +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + about.html,\ + . diff --git a/org.eclipse.text.quicksearch.tests/plugin.properties b/org.eclipse.text.quicksearch.tests/plugin.properties new file mode 100644 index 00000000000..b399c2cf298 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/plugin.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2019 Pivotal Inc and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v2.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# Pivotal Inc - initial API and implementation +############################################################################### + +pluginName= Tests for org.eclipse.text.quicksearch Bundle +providerName= Eclipse.org diff --git a/org.eclipse.text.quicksearch.tests/pom.xml b/org.eclipse.text.quicksearch.tests/pom.xml new file mode 100644 index 00000000000..ef0c5fc224b --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (c) 2012, 2014. 2019 Eclipse Foundation and others. + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Distribution License v2.0 + which accompanies this distribution, and is available at + http://www.eclipse.org/org/documents/edl-v20.php + + SPDX-License-Identifier: EPL-2.0 + + Contributors: + Igor Fedorenko - initial implementation + Pivotal Inc - copy adapted for quicksearch +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>tests-pom</artifactId> + <groupId>eclipse.platform.text</groupId> + <version>4.13.0-SNAPSHOT</version> + <relativePath>../tests-pom/</relativePath> + </parent> + <groupId>org.eclipse.text</groupId> + <artifactId>org.eclipse.text.quicksearch.tests</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>eclipse-test-plugin</packaging> + <properties> + <testSuite>${project.artifactId}</testSuite> + <testClass>org.eclipse.text.quicksearch.tests.*Test</testClass> + <skipAPIAnalysis>true</skipAPIAnalysis> <!-- https://bugs.eclipse.org/bugs/show_bug.cgi?id=548518#c41 --> + </properties> +</project> diff --git a/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/MockResource.java b/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/MockResource.java new file mode 100644 index 00000000000..4c66bbbb113 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/MockResource.java @@ -0,0 +1,448 @@ +/******************************************************************************* + * Copyright (c) 2013-2019 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.tests; + +import java.net.URI; +import java.util.Map; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IPathVariableManager; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceProxy; +import org.eclipse.core.resources.IResourceProxyVisitor; +import org.eclipse.core.resources.IResourceVisitor; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourceAttributes; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +public class MockResource implements IResource { + + IPath fullPath; + + public MockResource(String pathStr) { + this.fullPath = new Path(pathStr); + } + + @Override + public String toString() { + return fullPath.toString(); + } + + @Override + public Object getAdapter(Class adapter) { + return null; + } + + @Override + public boolean contains(ISchedulingRule rule) { + throw new Error("Not implemented"); + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + throw new Error("Not implemented"); + } + + @Override + public void accept(IResourceProxyVisitor visitor, int memberFlags) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void accept(IResourceProxyVisitor visitor, int depth, int memberFlags) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void accept(IResourceVisitor visitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void accept(IResourceVisitor visitor, int depth, + boolean includePhantoms) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void accept(IResourceVisitor visitor, int depth, int memberFlags) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void clearHistory(IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void copy(IPath destination, boolean force, IProgressMonitor monitor) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void copy(IPath destination, int updateFlags, + IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void copy(IProjectDescription description, boolean force, + IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void copy(IProjectDescription description, int updateFlags, + IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public IMarker createMarker(String type) throws CoreException { + // TODO Auto-generated method stub + throw new Error("Not implemented"); + } + + @Override + public IResourceProxy createProxy() { + // TODO Auto-generated method stub + throw new Error("Not implemented"); + } + + @Override + public void delete(boolean force, IProgressMonitor monitor) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void delete(int updateFlags, IProgressMonitor monitor) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void deleteMarkers(String type, boolean includeSubtypes, int depth) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public IMarker findMarker(long id) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public IMarker[] findMarkers(String type, boolean includeSubtypes, int depth) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public int findMaxProblemSeverity(String type, boolean includeSubtypes, + int depth) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public String getFileExtension() { + return fullPath.getFileExtension(); + } + + @Override + public IPath getFullPath() { + return fullPath; + } + + @Override + public long getLocalTimeStamp() { + throw new Error("Not implemented"); + } + + @Override + public IPath getLocation() { + throw new Error("Not implemented"); + } + + @Override + public URI getLocationURI() { + throw new Error("Not implemented"); + } + + @Override + public IMarker getMarker(long id) { + throw new Error("Not implemented"); + } + + @Override + public long getModificationStamp() { + throw new Error("Not implemented"); + } + + @Override + public String getName() { + String name = fullPath.lastSegment(); + if (name!=null) { + return name; + } + return ""; + } + + @Override + public IPathVariableManager getPathVariableManager() { + throw new Error("Not implemented"); + } + + @Override + public IContainer getParent() { + throw new Error("Not implemented"); + } + + @Override + public Map<QualifiedName, String> getPersistentProperties() + throws CoreException { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getPersistentProperty(QualifiedName key) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public IProject getProject() { + throw new Error("Not implemented"); + } + + @Override + public IPath getProjectRelativePath() { + throw new Error("Not implemented"); + } + + @Override + public IPath getRawLocation() { + throw new Error("Not implemented"); + } + + @Override + public URI getRawLocationURI() { + throw new Error("Not implemented"); + } + + @Override + public ResourceAttributes getResourceAttributes() { + throw new Error("Not implemented"); + } + + @Override + public Map<QualifiedName, Object> getSessionProperties() + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public Object getSessionProperty(QualifiedName key) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public int getType() { + throw new Error("Not implemented"); + } + + @Override + public IWorkspace getWorkspace() { + throw new Error("Not implemented"); + } + + @Override + public boolean isAccessible() { + return true; + } + + @Override + public boolean isDerived() { + return false; + } + + @Override + public boolean isDerived(int options) { + return false; + } + + @Override + public boolean isHidden() { + return false; + } + + @Override + public boolean isHidden(int options) { + return false; + } + + @Override + public boolean isLinked() { + return false; + } + + @Override + public boolean isVirtual() { + return false; + } + + @Override + public boolean isLinked(int options) { + return false; + } + + @Override + public boolean isLocal(int depth) { + return false; + } + + @Override + public boolean isPhantom() { + return false; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public boolean isSynchronized(int depth) { + throw new Error("Not implemented"); + } + + @Override + public boolean isTeamPrivateMember() { + throw new Error("Not implemented"); + } + + @Override + public boolean isTeamPrivateMember(int options) { + throw new Error("Not implemented"); + } + + @Override + public void move(IPath destination, boolean force, IProgressMonitor monitor) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void move(IPath destination, int updateFlags, + IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void move(IProjectDescription description, boolean force, + boolean keepHistory, IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void move(IProjectDescription description, int updateFlags, + IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void refreshLocal(int depth, IProgressMonitor monitor) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void revertModificationStamp(long value) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setDerived(boolean isDerived) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setDerived(boolean isDerived, IProgressMonitor monitor) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setHidden(boolean isHidden) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setLocal(boolean flag, int depth, IProgressMonitor monitor) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public long setLocalTimeStamp(long value) throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setPersistentProperty(QualifiedName key, String value) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setReadOnly(boolean readOnly) { + throw new Error("Not implemented"); + } + + @Override + public void setResourceAttributes(ResourceAttributes attributes) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setSessionProperty(QualifiedName key, Object value) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void setTeamPrivateMember(boolean isTeamPrivate) + throws CoreException { + throw new Error("Not implemented"); + } + + @Override + public void touch(IProgressMonitor monitor) throws CoreException { + throw new Error("Not implemented"); + } + +} diff --git a/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/PrioriTreeTest.java b/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/PrioriTreeTest.java new file mode 100644 index 00000000000..ac972c420af --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/PrioriTreeTest.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2013-2019 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.tests; + +import static org.eclipse.text.quicksearch.internal.core.priority.PriorityFunction.PRIORITY_DEFAULT; +import static org.eclipse.text.quicksearch.internal.core.priority.PriorityFunction.PRIORITY_IGNORE; + +import org.eclipse.core.runtime.Path; +import org.eclipse.text.quicksearch.internal.core.priority.PrioriTree; + +import junit.framework.TestCase; + +@SuppressWarnings("restriction") +public class PrioriTreeTest extends TestCase { + + PrioriTree tree; + + @Override + protected void setUp() throws Exception { + super.setUp(); + tree = PrioriTree.create(); + } + + public void testWithEmptyTree() { + //In the empty tree most paths are assigned 'DEFAULT' priority. + checkPriority(PRIORITY_DEFAULT, "/"); + checkPriority(PRIORITY_DEFAULT, "/foo/bar/zor"); + + //Paths with ignored extensions should be 'ignored'. + checkPriority(PRIORITY_IGNORE, "/foo/muck.zip"); + checkPriority(PRIORITY_IGNORE, "/muck.jar"); + checkPriority(PRIORITY_IGNORE, "/images/muck.jpg"); + + //Names starting with ignored prefix should be ignored + checkPriority(PRIORITY_IGNORE, "/project/.git"); + + //Some specific names are also to be ignored + checkPriority(PRIORITY_IGNORE, "/project/target"); + checkPriority(PRIORITY_IGNORE, "/project/build"); + + } + + public void testSinglePathSet() { + setPriority("/foo/bar/zor", 100.0); + + //Path itself should have the set priority + checkPriority(100.0, "/foo/bar/zor"); + + //Also the parent paths should have been set automatically + checkPriority(100.0, "/foo/bar"); + checkPriority(100.0, "/foo"); + checkPriority(100.0, "/"); + + //Things not on the paths should still be 'default' + checkPriority(PRIORITY_DEFAULT, "/other/bar"); + checkPriority(PRIORITY_DEFAULT, "/other"); + + //The things nested underneath the set path also get assigned implicitly ... + checkPriority(100.0, "/foo/bar/zor/nested"); + checkPriority(100.0, "/foo/bar/zor/nested/deeper"); + // ... unless they are 'ignored'. Ignored paths are never converted to non-ignored. + + checkPriority(PRIORITY_IGNORE, "/foo/bar/zor/nested/big.zip"); + } + + public void testSetOverlappingPaths() { + setPriority("/shared/foo", 50.0); + setPriority("/shared/bar", 100.0); + + tree.dump(); + + checkPriority(50.0, "/shared/foo"); + checkPriority(100.0, "/shared/bar"); + + //Shared section of path should get highest priority of both + checkPriority(100.0, "/"); + checkPriority(100.0, "/shared"); + + //Disjoint paths remain default + checkPriority(PRIORITY_DEFAULT, "/other"); + } + + /** + * Similar to testSetOverlappingPaths but order of + * priority set operations is reversed. The result should + * be the same. + */ + public void testSetOverlappingPaths2() { + setPriority("/shared/bar", 100.0); + setPriority("/shared/foo", 50.0); + + checkPriority(50.0, "/shared/foo"); + checkPriority(100.0, "/shared/bar"); + + //Shared section of path should get highest priority of both + checkPriority(100.0, "/"); + checkPriority(100.0, "/shared"); + + //Disjoint paths remain default + checkPriority(PRIORITY_DEFAULT, "/other"); + } + + /** + * Need support for setting priority of an entire subtree. + */ + public void testSetTreePriority() { + setPriority("/promoted", 100.0); + + //Stuff not in the raised subtree should be unchanged + checkPriority(PRIORITY_DEFAULT, "/unrelated"); + + //Stuff in the raised subtree should be affected. + checkPriority(100.0, "/promoted"); + checkPriority(100.0, "/promoted/sub"); + checkPriority(100.0, "/promoted/sub/sub"); + + //But... ignored stuff should never be made searchable even in a raised subtree. + checkPriority(PRIORITY_IGNORE, "/promoted/big.zip"); + } + + /** + * Check that setting priotity of a tree raises children priority also if those + * children already had a priority assigned before. + */ + public void testSetTreePriority2() { + setPriority("/promoted/sub/sub", 50.0); + checkPriority(50.0, "/promoted"); + checkPriority(50.0, "/promoted/sub"); + checkPriority(50.0, "/promoted/sub/sub"); + checkPriority(PRIORITY_DEFAULT, "/promoted/other"); + + setPriority("/promoted", 100.0); + + //Stuff not in the raised subtree should be unchanged + checkPriority(PRIORITY_DEFAULT, "/unrelated"); + + //Stuff in the raised subtree should be affected. + checkPriority(100.0, "/promoted"); + checkPriority(100.0, "/promoted/sub"); + checkPriority(100.0, "/promoted/sub/sub"); + checkPriority(100.0, "/promoted/other"); + + //But... ignored stuff should never be made searchable even in a raised subtree. + checkPriority(PRIORITY_IGNORE, "/promoted/sub/big.zip"); + checkPriority(PRIORITY_IGNORE, "/promoted/other/big.zip"); + } + + private void setPriority(String pathStr, double pri) { + tree.setPriority(new Path(pathStr), pri); + } + + private void checkPriority(double expected, String pathStr) { + assertEquals(pathStr, + expected, tree.priority(new MockResource(pathStr))); + } + +} diff --git a/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/ResourceMatcherTest.java b/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/ResourceMatcherTest.java new file mode 100644 index 00000000000..4ecce5d0736 --- /dev/null +++ b/org.eclipse.text.quicksearch.tests/src/org/eclipse/text/quicksearch/tests/ResourceMatcherTest.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.eclipse.core.runtime.Path; +import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatcher; +import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatchers; +import org.junit.Test; + +@SuppressWarnings("restriction") +public class ResourceMatcherTest { + + @Test + public void simpleRelativePattern() throws Exception { + assertMatch(true, "*.java", "/myproject/something/nested/foo.java"); + assertMatch(false, "*.java", "/myproject/foo.class"); + } + + @Test + public void commaSeparatedPaths() throws Exception { + assertMatch(true, "*.java,*.properties", "/myproject/something/nested/foo.java"); + assertMatch(true, "*.java,*.properties", "/myproject/something/nested/application.properties"); + } + + @Test + public void complexRelativePattern() throws Exception { + assertMatch(true, "src/**/*.java", "/myproject/src/my/package/Foo.java"); + assertMatch(false, "src/**/*.java", "/myproject/resources/my/package/Foo.java"); + } + + @Test + public void absolutePath() throws Exception { + assertMatch(true, "/myproject/**/*.java", "/myproject/src/my/package/Foo.java"); + assertMatch(false, "/myproject/**/*.java", "/otherproject/src/my/package/Foo.java"); + } + + private void assertMatch(boolean expectedMatch, String patterns, String path) { + assertTrue(new Path(path).isAbsolute()); + ResourceMatcher matcher = ResourceMatchers.commaSeparatedPaths(patterns); + assertEquals(expectedMatch, matcher.matches(new MockResource(path))); + } +} diff --git a/org.eclipse.text.quicksearch/.classpath b/org.eclipse.text.quicksearch/.classpath new file mode 100644 index 00000000000..6726f63d347 --- /dev/null +++ b/org.eclipse.text.quicksearch/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry excluding="org/springsource/ide/eclipse/commons/quicksearch/ui/FileSearchViewer.java|org/springsource/ide/eclipse/commons/quicksearch/ui/QuickSearchResult.java" kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eclipse.text.quicksearch/.project b/org.eclipse.text.quicksearch/.project new file mode 100644 index 00000000000..f29a963e865 --- /dev/null +++ b/org.eclipse.text.quicksearch/.project @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.text.quicksearch</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> + <filteredResources> + <filter> + <id>1366673713400</id> + <name></name> + <type>10</type> + <matcher> + <id>org.eclipse.ui.ide.multiFilter</id> + <arguments>1.0-projectRelativePath-matches-false-false-target</arguments> + </matcher> + </filter> + </filteredResources> +</projectDescription> diff --git a/org.eclipse.text.quicksearch/.settings/org.eclipse.core.resources.prefs b/org.eclipse.text.quicksearch/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..da81118144d --- /dev/null +++ b/org.eclipse.text.quicksearch/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding//src/org/eclipse/text/quicksearch/internal/ui/messages.properties=ISO-8859-1 diff --git a/org.eclipse.text.quicksearch/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.text.quicksearch/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..0c68a61dca8 --- /dev/null +++ b/org.eclipse.text.quicksearch/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/org.eclipse.text.quicksearch/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.text.quicksearch/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 00000000000..c576259787c --- /dev/null +++ b/org.eclipse.text.quicksearch/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,61 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=false +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=false +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF b/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..dc662211d24 --- /dev/null +++ b/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF @@ -0,0 +1,30 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.eclipse.text.quicksearch;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: org.eclipse.text.quicksearch.internal.ui.QuickSearchActivator +Require-Bundle: org.eclipse.ui;bundle-version="[3.113.0,4.0.0)", + org.eclipse.core.resources;bundle-version="[3.13.0,4.0.0)", + org.eclipse.ui.ide;bundle-version="[3.16.0,4.0.0)", + org.eclipse.core.expressions;bundle-version="[3.6.0,4.0.0)", + org.eclipse.search;bundle-version="[3.11.0,4.0.0)", + org.eclipse.ui.editors;bundle-version="[3.11.0,4.0.0)", + org.eclipse.jface;bundle-version="[3.17.0,4.0.0)", + org.eclipse.jface.text;bundle-version="[3.15.0,4.0.0)", + org.apache.ant;bundle-version="[1.10.0,2.0.0)" +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Export-Package: org.eclipse.text.quicksearch.internal.core;x-internal:=true, + org.eclipse.text.quicksearch.internal.core.pathmatch;x-internal:=true, + org.eclipse.text.quicksearch.internal.core.preferences;x-internal:=true, + org.eclipse.text.quicksearch.internal.core.priority;x-internal:=true, + org.eclipse.text.quicksearch.internal.ui;x-internal:=true, + org.eclipse.text.quicksearch.internal.util;x-internal:=true +Import-Package: org.eclipse.core.runtime;version="3.5.0", + org.eclipse.core.runtime.jobs, + org.eclipse.osgi.util;version="1.1.0", + org.osgi.framework;version="1.9.0" +Automatic-Module-Name: org.eclipse.text.quicksearch diff --git a/org.eclipse.text.quicksearch/about.html b/org.eclipse.text.quicksearch/about.html new file mode 100644 index 00000000000..c3b1d8fd59c --- /dev/null +++ b/org.eclipse.text.quicksearch/about.html @@ -0,0 +1,36 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /> +<title>About</title> +</head> +<body lang="EN-US"> + <h2>About This Content</h2> + + <p>July 2, 2019</p> + <h3>License</h3> + + <p> + The Eclipse Foundation makes available all content in this plug-in + ("Content"). Unless otherwise indicated below, the Content + is provided to you under the terms and conditions of the Eclipse + Public License Version 2.0 ("EPL"). A copy of the EPL is + available at <a href="http://www.eclipse.org/legal/epl-2.0">http://www.eclipse.org/legal/epl-2.0</a>. + For purposes of the EPL, "Program" will mean the Content. + </p> + + <p> + If you did not receive this Content directly from the Eclipse + Foundation, the Content is being redistributed by another party + ("Redistributor") and different terms and conditions may + apply to your use of any object code in the Content. Check the + Redistributor's license that was provided with the Content. If no such + license exists, contact the Redistributor. Unless otherwise indicated + below, the terms and conditions of the EPL still apply to any source + code in the Content and such source code may be obtained at <a + href="http://www.eclipse.org/">http://www.eclipse.org</a>. + </p> + +</body> +</html>
\ No newline at end of file diff --git a/org.eclipse.text.quicksearch/build.properties b/org.eclipse.text.quicksearch/build.properties new file mode 100644 index 00000000000..31b9d2c0254 --- /dev/null +++ b/org.eclipse.text.quicksearch/build.properties @@ -0,0 +1,21 @@ +############################################################################### +# Copyright (c) 2013-2019 Pivotal Inc 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: +# Pivotal Inc - initial API and implementation +############################################################################### +source.. = src/ +output.. = bin/ +bin.includes = plugin.xml,\ + META-INF/,\ + .,\ + icons/,\ + about.html,\ + plugin.properties diff --git a/org.eclipse.text.quicksearch/plugin.properties b/org.eclipse.text.quicksearch/plugin.properties new file mode 100644 index 00000000000..a2292cbc515 --- /dev/null +++ b/org.eclipse.text.quicksearch/plugin.properties @@ -0,0 +1,16 @@ +############################################################################### +# Copyright (c) 2019 Pivotal Inc and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v2.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# Pivotal Inc - initial API and implementation +############################################################################### + +pluginName= Quick Search +providerName= Eclipse.org +searchMenu.label= Se&arch diff --git a/org.eclipse.text.quicksearch/plugin.xml b/org.eclipse.text.quicksearch/plugin.xml new file mode 100644 index 00000000000..6570905c1f7 --- /dev/null +++ b/org.eclipse.text.quicksearch/plugin.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (c) 2019 Pivotal Inc. and others. + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Distribution License v2.0 + which accompanies this distribution, and is available at + http://www.eclipse.org/org/documents/edl-v20.php + + SPDX-License-Identifier: EPL-2.0 + + Contributors: + Pivotal Inc - Initial API and implementation +--> +<?eclipse version="3.4"?> +<plugin> + + <extension + point="org.eclipse.ui.commands"> + <category + name="Quick Search Category" + id="org.eclipse.text.quicksearch.commands.category"> + </category> + <command + name="Quick Search Command" + categoryId="org.eclipse.text.quicksearch.commands.category" + id="org.eclipse.text.quicksearch.commands.quicksearchCommand"> + </command> + </extension> + + <!-- it seems that using the new and recommended way of contributing entries to existing menus + doesn't work for the search menu. + So we have to use the old mechanism using actionSets + See here https://stackoverflow.com/questions/7113380/how-to-extend-the-source-menu-in-eclipse-or-what-is-its-locationuri + --> + <extension + point="org.eclipse.ui.actionSets"> + + <actionSet + label="Quick Search ActionSet" + visible="true" + id="org.eclipse.text.quicksearch.actionSet"> + + + <!-- =================================================================== --> + <!-- Search Menu --> + <!-- =================================================================== --> + + <!-- weirdly, it seems we have to copy this code here because otherwise we + sometimes get an error that the menu does not exist. Presumably this + is because somehow our extensions are getting initialized before the + extensions in other plugins that define the menu --> + <menu + id="org.eclipse.search.menu" + label="%searchMenu.label" + path="navigate"> + <groupMarker name="internalDialogGroup"/> <!-- not to be used by clients --> + <groupMarker name="dialogGroup"/> <!-- to be used by clients --> + <separator name="fileSearchContextMenuActionsGroup"/> <!-- to be used by clients --> + <separator name="contextMenuActionsGroup"/> <!-- to be used by clients --> + <separator name="occurencesActionsGroup"/> <!-- to be used by clients --> + <separator name="extraSearchGroup"/> <!-- to be used by clients --> + </menu> + + <action + id="org.eclipse.text.quicksearch.commands.quicksearchAction" + class="org.eclipse.text.quicksearch.internal.ui.QuickSearchAction" + definitionId="org.eclipse.text.quicksearch.commands.quicksearchCommand" + label="Quick Search" + menubarPath="org.eclipse.search.menu/extraSearchGroup" + tooltip="Search for Text pattern in the workspace"> + </action> + </actionSet> + </extension> + +<!-- This doesn't work unfortunately... + <extension point="org.eclipse.ui.menus"> + <menuContribution locationURI="menu:navigate?after=additions"> + <command commandId="org.eclipse.text.quicksearch.commands.quicksearchCommand" + label="Quick Search" + mnemonic="Q"> + </command> + </menuContribution> + </extension> --> + + <!-- Allthough we have an action defined, it seems the action isn't always working. So for good measure + also define a handler --> + <extension + point="org.eclipse.ui.handlers"> + <handler + commandId="org.eclipse.text.quicksearch.commands.quicksearchCommand" + class="org.eclipse.text.quicksearch.internal.ui.QuickSearchHandler"> + </handler> + </extension> + +<!-- Define keybinding --> + <extension + point="org.eclipse.ui.bindings"> + <key + commandId="org.eclipse.text.quicksearch.commands.quicksearchCommand" + contextId="org.eclipse.ui.contexts.window" + sequence="M1+M2+M3+L" + schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"> + </key> + </extension> + +<extension + point = "org.eclipse.ui.preferencePages"> + <page id="org.eclipse.text.quicksearch.PreferencesPage" + class="org.eclipse.text.quicksearch.internal.ui.QuickSearchPreferencesPage" + name="Quick Search"> + </page> +</extension> + +</plugin> diff --git a/org.eclipse.text.quicksearch/pom.xml b/org.eclipse.text.quicksearch/pom.xml new file mode 100644 index 00000000000..6720f960ab2 --- /dev/null +++ b/org.eclipse.text.quicksearch/pom.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (c) 2012, 2014. 2019 Eclipse Foundation and others. All rights + reserved. This program and the accompanying materials are made available + under the terms of the Eclipse Distribution License v2.0 which accompanies + this distribution, and is available at http://www.eclipse.org/org/documents/edl-v20.php + + SPDX-License-Identifier: EPL-2.0 + + Contributors: Igor Fedorenko - initial implementation + Pivotal Inc - copy adapted for quicksearch bundle +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>eclipse.platform.text</artifactId> + <groupId>eclipse.platform.text</groupId> + <version>4.13.0-SNAPSHOT</version> + </parent> + <groupId>org.eclipse.core</groupId> + <artifactId>org.eclipse.text.quicksearch</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>eclipse-plugin</packaging> + + <properties> + <skipAPIAnalysis>true</skipAPIAnalysis> <!-- https://bugs.eclipse.org/bugs/show_bug.cgi?id=548518#c41 --> + </properties> +</project> diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/LineItem.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/LineItem.java new file mode 100644 index 00000000000..6867e96d96d --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/LineItem.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core; + +import org.eclipse.core.resources.IFile; +import org.eclipse.search.internal.ui.text.FileMatch; + +@SuppressWarnings("restriction") +public class LineItem { + + IFile f; + String line; + int lineNumber; + int lineOffset; + + public LineItem(IFile f, String line, int lineNumber, int lineOffset) { + this.f = f; + this.line = line; + this.lineNumber = lineNumber; + this.lineOffset = lineOffset; + } + + public LineItem(FileMatch match) { + this.f = match.getFile(); + this.line = match.getLineElement().getContents(); + this.lineNumber = match.getLineElement().getLine(); + this.lineOffset = match.getLineElement().getOffset(); + } + + @Override + public String toString() { + return lineNumber + ": " + line + " (" +f.getProjectRelativePath() + ")"; + } + + public String getText() { + return line; + } + + public int getLineNumber() { + return lineNumber; + } + + public IFile getFile() { + return this.f; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((f == null) ? 0 : f.hashCode()); + result = prime * result + lineNumber; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LineItem other = (LineItem) obj; + if (f == null) { + if (other.f != null) + return false; + } else if (!f.equals(other.f)) + return false; + if (lineNumber != other.lineNumber) + return false; + return true; + } + + public int getOffset() { + return lineOffset; + } + + + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextQuery.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextQuery.java new file mode 100644 index 00000000000..e6ce2edb826 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextQuery.java @@ -0,0 +1,229 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jface.text.IRegion; + +/** + * Represents something you can search for with a 'quick search' text searcher. + * + * @author Kris De Volder + */ +@SuppressWarnings("restriction") +public class QuickTextQuery { + + //TODO: delete and use jface Region class instead. + public class TextRange implements IRegion { + public final int start; + public final int len; + public TextRange(int start, int len) { + this.start = start; + this.len = len; + } + public int getLength() { + return len; + } + public int getOffset() { + return start; + } + } + + private boolean caseSensitive; + private String orgPattern; //Original pattern case preserved even if search is case insensitive. + private Pattern pattern; + + /** + * A query that matches anything. + */ + public QuickTextQuery() { + this("", true); + } + + public QuickTextQuery(String substring, boolean caseSensitive) { + this.orgPattern = substring; + this.caseSensitive = caseSensitive; + createMatcher(substring, caseSensitive); + } + + /** + * Compile a pattern string into an RegExp and create a Matcher for that + * regexp. This is so we can 'compile' the pattern once and then keep reusing + * the matcher or compiled pattern. + */ + private void createMatcher(String patString, boolean caseSensitive) { + StringBuilder segment = new StringBuilder(); //Accumulates text that needs to be 'quoted' + StringBuilder regexp = new StringBuilder(); //Accumulates 'compiled' pattern + int pos = 0, len = patString.length(); + while (pos<len) { + char c = patString.charAt(pos++); + switch (c) { + case '?': + appendSegment(segment, regexp); + regexp.append("."); + break; + case '*': + appendSegment(segment, regexp); + regexp.append(".*"); + break; + case '\\': + if (pos<len) { + char nextChar = patString.charAt(pos); + if (nextChar=='*' || nextChar=='?' || nextChar=='\\') { + segment.append(nextChar); + pos++; + break; + } + } + default: + //Char is 'nothing special'. Add it to segment that will be wrapped in 'quotes' + segment.append(c); + break; + } + } + //Don't forget to process that last segment. + appendSegment(segment, regexp); + + this.pattern = Pattern.compile(regexp.toString(), caseSensitive?0:Pattern.CASE_INSENSITIVE); +// this.matcher = pattern.matcher(""); + } + + private void appendSegment(StringBuilder segment, StringBuilder regexp) { + if (segment.length()>0) { + regexp.append(Pattern.quote(segment.toString())); + segment.setLength(0); //clear: ready for next segment + } + //else { + // nothing to append + //} + } + + public boolean equalsFilter(QuickTextQuery o) { + //TODO: actually for case insensitive matches we could relax this and treat patterns that + // differ only in case as the same. + return this.caseSensitive == o.caseSensitive && this.orgPattern.equals(o.orgPattern); + } + + /** + * Returns true if the other query is a specialisation of this query. I.e. any results matching the other + * query must also match this query. If this method returns true then we can optimise the search for other + * re-using already found results for this query. + * <p> + * If it is hard or impossible to decide whether other query is a specialisation of this query then this + * method is allowed to 'punt' and just return false. However, the consequence of this is that the query + * will be re-run instead of incrementally updated. + */ + public boolean isSubFilter(QuickTextQuery other) { + if (this.isTrivial()) { + return false; + } + if (this.caseSensitive==other.caseSensitive) { + boolean caseSensitive = this.caseSensitive; + String otherPat = normalize(other.orgPattern, caseSensitive); + String thisPat = normalize(this.orgPattern, caseSensitive); + return otherPat.contains(thisPat); + } + return false; + } + + /** + * Transforms a pattern string so we can use a simple 'substring' test to determine + * whether one pattern is sub-pattern of the other. + */ + private String normalize(String pat, boolean caseSensitive) { + if (pat.endsWith("\\")) { + pat = pat + "\\"; + } + if (!caseSensitive) { + pat = pat.toLowerCase(); + } + return pat; + } + + /** + * @return whether the LineItem text contains the search pattern. + */ + public boolean matchItem(LineItem item) { + return matchItem(item.getText()); + } + + /** + * Same as matchItem except only takes the text of the item. This can + * be useful for efficient processing. In particular to avoid creating + * LineItem instances for non-matching lines. + */ + public boolean matchItem(String item) { + //Alternate implementation. This is thread safe without synchronized, + // but it creates some garbage. + Matcher matcher = pattern.matcher(item); //Creating garbage here + return matcher.find(); + } + + /** + * A trivial query is one that either + * - matches anything + * - matches nothing + * In other words, if a query is 'trivial' then it returns either nothing or all the text in the scope + * of the search. + */ + public boolean isTrivial() { + return "".equals(this.orgPattern); + } + + @Override + public String toString() { + return "QTQuery("+orgPattern+", "+(caseSensitive?"caseSens":"caseInSens")+")"; + } + + public List<TextRange> findAll(String text) { + //alternate implementation without 'synchronized' but creates more garbage + if (isTrivial()) { + return Arrays.asList(); + } else { + List<TextRange> ranges = new ArrayList<QuickTextQuery.TextRange>(); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + ranges.add(new TextRange(start, end-start)); + } + return ranges; + } + } + + public TextRange findFirst(String str) { + //TODO: more efficient implementation, just search the first one + // no need to find all matches then toss away everything except the + // first one. + List<TextRange> all = findAll(str); + if (all!=null && !all.isEmpty()) { + return all.get(0); + } + return null; + } + + public String getPatternString() { + return orgPattern; + } + + public boolean isCaseSensitive() { + return caseSensitive; + } + + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextSearchRequestor.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextSearchRequestor.java new file mode 100644 index 00000000000..b46cd3dcb9f --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextSearchRequestor.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core; + +/** + * Plays a similar role than SearchReqeustor in eclipse Searches. I.e. a search requestor + * is some entity accepting the results of a search. Typically the requestor displays the + * result to the user. + * <p> + * This API differs a little from the Eclipse SearchRequestor in that searches are 'live'. + * I.e the results are updating while the user is typing the query. + * As the query is changing, this may cause results that were added earlier being changed or + * revoked. + * + * @author Kris De Volder + */ +public class QuickTextSearchRequestor { + + /** + * Called when a line of text containing the search text is found. + */ + public void add(LineItem match) {} + + /** + * Called when a previously added line of text needs to be redisplayed (this happens if + * the query has changed but still matches the line. I.e. the line is still a match, but + * the highlighting of the search term is different. + */ + public void update(LineItem match) {} + + /** + * Called when a line of text previously added is no longer a match for the current query. + * I.e. the line should no longer be displayed. + */ + public void revoke(LineItem line) {} + + /** + * Called when all previous results have become revoked at once. + * This happens when a query is changed in such a way that it can't be updated + * incrementally but needs to be completely restarted. + */ + public void clear() {} +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextSearcher.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextSearcher.java new file mode 100644 index 00000000000..d8a23e1cc9f --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextSearcher.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core; + +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatcher; +import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatchers; +import org.eclipse.text.quicksearch.internal.core.priority.PriorityFunction; +import org.eclipse.text.quicksearch.internal.util.LightSchedulingRule; +import org.eclipse.text.quicksearch.internal.util.LineReader; + +public class QuickTextSearcher { + private final QuickTextSearchRequestor requestor; + private QuickTextQuery query; + + /** + * Keeps track of currently found matches. Items are added as they are found and may also + * be removed when the query changed and they become invalid. + */ + private Set<LineItem> matches = new HashSet<LineItem>(2000); + + /** + * Scheduling rule used by Jobs that work on the matches collection. + */ + private ISchedulingRule matchesRule = new LightSchedulingRule("QuickSearchMatchesRule"); + + private SearchInFilesWalker walker = null; + private IncrementalUpdateJob incrementalUpdate; + + /** + * This field gets set to request a query change. The new query isn't stuffed directly + * into the query field because the query is responded to by the updater job which needs + * access to both the original query and the newQuery to decide on an efficient strategy. + */ + private QuickTextQuery newQuery; + + /** + * If number of accumulated results reaches maxResults the search will be suspended. + * <p> + * Note that more results may still arrive beyond the limit since the searcher does not (yet) have the + * capability to suspend/resume a search in the middle of a file. + */ + private int maxResults = 200; + + /** + * If a line of text is encountered longer than this, the searcher will stop searching + * that file (this rule avoids searching machine generated text files, like minified javascript). + */ + private int MAX_LINE_LEN; + + /** + * While searching in a file, this field will be set. This can be used to show the name + * of the 'current file' in the progress area of the quicksearch dialog. + */ + private IFile currentFile = null; + + /** + * Flag to disable incremental filtering logic based on incremental + * query updates. This forces a full refresh of the search results. + */ + private boolean forceRefresh = false; + private ResourceMatcher pathMatcher = ResourceMatchers.ANY; + + /** + * Retrieves the current result limit. + */ + public int getMaxResults() { + return maxResults; + } + + public void setMaxResults(int maxResults) { + this.maxResults = maxResults; + } + + public QuickTextSearcher(QuickTextQuery query, PriorityFunction priorities, int maxLineLen, QuickTextSearchRequestor requestor) { + this.MAX_LINE_LEN = maxLineLen; + this.requestor = requestor; + this.query = query; + this.walker = createWalker(new PriorityFunction() { + @Override + public double priority(IResource r) { + double basePriority = priorities.priority(r); + if (basePriority==PRIORITY_IGNORE) { + return basePriority; + } + if (r.getType()==IResource.FILE && !pathMatcher.matches(r)) { + return PRIORITY_IGNORE; + } + return basePriority; + } + }); + } + + private SearchInFilesWalker createWalker(PriorityFunction priorities) { + final SearchInFilesWalker job = new SearchInFilesWalker(); + job.setPriorityFun(priorities); + job.setRule(matchesRule); + job.schedule(); + return job; + } + + private final class SearchInFilesWalker extends ResourceWalker { + + + @Override + protected void visit(IFile f, IProgressMonitor mon) { + if (checkCanceled(mon)) { + return; + } + + + LineReader lr = null; + currentFile = f; + try { + lr = new LineReader(new InputStreamReader(f.getContents(true), f.getCharset()), MAX_LINE_LEN); + String line = null; + int lineIndex = 1; + while ((line = lr.readLine()) != null) { + int offset = lr.getLastLineOffset(); + if (checkCanceled(mon)) { + return; + } + + boolean found = query.matchItem(line); + if (found) { + LineItem lineItem = new LineItem(f, line, lineIndex, offset); + add(lineItem); + } + + lineIndex++; + } + } catch (Exception e) { + } finally { + currentFile = null; + if (lr != null) { + lr.close(); + } + } + } + + @Override + public void resume() { + //Only resume if we don't already exceed the maxResult limit. + if (matches.size()<maxResults) { + super.resume(); + } + } + + private boolean checkCanceled(IProgressMonitor mon) { + return mon.isCanceled(); + } + + public void requestMoreResults() { + int currentSize = matches.size(); + maxResults = Math.max(maxResults, currentSize + currentSize/10); + resume(); + } + + } + + /** + * This job updates already found matches when the query is changed. + * Both the walker job and this job share the same scheduling rule so + * only one of them can be executing at the same time. + * <p> + * This is to avoid problems with concurrent modification of the + * matches collection. + */ + private class IncrementalUpdateJob extends Job { + public IncrementalUpdateJob() { + super("Update matches"); + this.setRule(matchesRule); + //This job isn't started automatically. It should be schedule every time + // there's a 'newQuery' set by the user/client. + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + QuickTextQuery nq = newQuery; //Copy into local variable to avoid + // problems if another thread changes newQuery while we + // are still mucking with it. + if (!forceRefresh && query.isSubFilter(nq)) { + query = nq; + performIncrementalUpdate(monitor); + } else { + query = nq; + forceRefresh = false; + performRestart(monitor); + } + return monitor.isCanceled()?Status.CANCEL_STATUS:Status.OK_STATUS; + } + + private void performIncrementalUpdate(IProgressMonitor mon) { + Iterator<LineItem> items = matches.iterator(); + while (items.hasNext() && !mon.isCanceled()) { + + LineItem item = items.next(); + if (query.matchItem(item)) { + //Match still valid but may need updating highlighted text in the UI: + requestor.update(item); + } else { + items.remove(); + requestor.revoke(item); + } + } + if (!mon.isCanceled()) { + //Resume searching remaining files, if any. + walker.resume(); + } + } + + private void performRestart(IProgressMonitor mon) { + //walker may be null if dialog got closed already before we managed to + // 'performRestart'. + if (walker!=null) { + //since we are inside Job here that uses same scheduling rule as walker, we + //know walker is not currently executing. so walker cancel should be instantenous + matches.clear(); + requestor.clear(); + walker.cancel(); + if (!query.isTrivial()) { + walker.init(); //Reinitialize the walker work queue to its starting state + walker.resume(); //Allow walker to resume when we release the scheduling rule. + } + } + } + + } + + private void add(LineItem line) { + if (matches.add(line)) { + requestor.add(line); + if (matches.size() >= maxResults) { + walker.suspend(); + } + } + } + + public void setQuery(QuickTextQuery newQuery, boolean force) { + if (newQuery.equalsFilter(query) && !force) { + return; + } + this.newQuery = newQuery; + this.forceRefresh = true; + scheduleIncrementalUpdate(); + } + + public void setPathMatcher(ResourceMatcher pathMatcher) { + if (this.pathMatcher.equals(pathMatcher)) { + return; + } + this.pathMatcher = pathMatcher; + setQuery(query, true); + } + + public QuickTextQuery getQuery() { + //We return the newQuery as soon as it was set, even if it has not yet been effectively applied + // to previously found query results. Most logical since when you call 'setQuery' you would + // expect 'getQuery' to return the query you just set. + return newQuery!=null ? newQuery : query; + } + + private synchronized void scheduleIncrementalUpdate() { + walker.suspend(); //The walker must be suspended so the update job can run, they share scheduling rule + // so only one job can run at any time. + + //Any outstanding incremental update should be canceled since the query has changed again. + if (incrementalUpdate!=null) { + incrementalUpdate.cancel(); + } + incrementalUpdate = new IncrementalUpdateJob(); + incrementalUpdate.schedule(); + } + + public boolean isDone() { + //Walker can be null if job was canceled because dialog closed. But stuff like + //the job that shows 'Searching ...' doesn't instantly stop and may still + //be asking the incremental update job whether its done. + return walker!=null && walker.isDone(); + } + + public void requestMoreResults() { + if (walker!=null && !walker.isDone()) { + walker.requestMoreResults(); + } + } + + public void cancel() { + if (walker!=null) { + walker.cancel(); + walker = null; + } + } + + public IFile getCurrentFile() { + return currentFile; + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/ResourceWalker.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/ResourceWalker.java new file mode 100644 index 00000000000..e3535a01189 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/ResourceWalker.java @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core; + +import java.util.PriorityQueue; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.text.quicksearch.internal.core.priority.DefaultPriorityFunction; +import org.eclipse.text.quicksearch.internal.core.priority.PriorityFunction; +import org.eclipse.text.quicksearch.internal.ui.QuickSearchActivator; + +/** + * A Helper class that allows traversing all the resources in the workspace, assigning priorities + * to the resources to decide the ordering and completely ignore some resources. + * <p> + * The walker can also be paused and resumed. + * + * @author Kris De Volder + */ +public abstract class ResourceWalker extends Job { + + private class QItem implements Comparable<QItem> { + public final double priority; + public final IResource resource; + + public QItem(double p, IResource r) { + this.priority = p; + this.resource = r; + } + + public int compareTo(QItem other) { + return Double.compare(other.priority, this.priority); + } + } + + public ResourceWalker() { + super("QuickSearch"); + init(); + } + + protected void init() { + queue = new PriorityQueue<ResourceWalker.QItem>(); + queue.add(new QItem(0, ResourcesPlugin.getWorkspace().getRoot())); + } + + /** + * Queue of work to do. When all work is done this will be set to null. So it + * can also be used to determine 'done' status. + */ + private PriorityQueue<QItem> queue = null; + + /** + * Setting this to true will cause the ResourceWalker to stop walking. If the walker is running + * as a scheduled job, then this Job will terminate. However it is possible to 'resume' the + * later since pending list of workitems will be retained. + */ + private boolean suspend = false; + + private PriorityFunction prioritFun = new DefaultPriorityFunction(); + + public boolean isDone() { + return queue==null; + } + + /** + * Request that the walker stops walking at the next reasonable opportunity. + */ + public void suspend() { + this.suspend = true; + } + + /** + * Request the walker to be restarted... i.e. begin walking the resource tree from + * the initial state. + */ + + /** + * Request that the walker be resumed. This clears the 'supsend' state if it is set + * and ensures that the Job is scheduled. + */ + public void resume() { + if (isDone()) { + //Well... there's no work so don't bother with doing anything. + return; + } + this.suspend = false; + this.schedule(); + } + + public IStatus run(IProgressMonitor monitor) { + //TODO: progress reporting? + while (!suspend && queue!=null) { + if (monitor.isCanceled()) { + queue = null; + } else { + IResource r = getWork(); + if (r!=null) { + if (r instanceof IFile) { + IFile f = (IFile) r; + visit(f, monitor); + } else if (r instanceof IContainer) { + IContainer f = (IContainer) r; + if (f.isAccessible()) { + try { + for (IResource child : f.members()) { + enqueue(child); + } + } catch (CoreException e) { + QuickSearchActivator.log(e); + } + } + } + } else { + queue = null; + } + } + } + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } else { + return Status.OK_STATUS; + } + } + + /** + * Add a resource to the work queue taking account the priority of the resource. + */ + private void enqueue(IResource child) { + PriorityQueue<QItem> q = queue; + if (q!=null) { + double p = priority(child); + if (p==PriorityFunction.PRIORITY_IGNORE) { + return; + } + q.add(new QItem(p, child)); + } + } + + protected abstract void visit(IFile r, IProgressMonitor m); + + /** + * Assigns a priority to a given resource. This priority will affect the order in which + * resources get visited. Resources to be visited are tracked in a priority queue and + * at any time the resource with highest priority number is visited first. + * <p> + * Note that if a resource is a folder then lowering its priority implicitly reduces + * the priority of anything nested inside that folder because to visit the children + * one has to first visit the parent to reach them. + * <p> + * If the priority returned is PRIORITY_IGNORE then the resource will be ignored + * completely and not visited at all. + * + * @param r + * @return + */ + final double priority(IResource r) { + return prioritFun.priority(r); + } + + /** + * Set the priority function to use to determine walking order. For the function to + * take effect, it should be set before walking has started as the function is + * used when elements are added to the work queue during the walk. + * <p> + * Elements already in the work queue are not re-prioritized if a function is set + * in 'mid-run'. + */ + public void setPriorityFun(PriorityFunction f) { + Assert.isNotNull(f, "PriorityFunction should never be null"); + this.prioritFun = f; + } + + private IResource getWork() { + PriorityQueue<QItem> q = queue; + if (q!=null && !q.isEmpty()) { + return q.remove().resource; + } + return null; + } + + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/pathmatch/ResourceMatcher.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/pathmatch/ResourceMatcher.java new file mode 100644 index 00000000000..d45f11f21e2 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/pathmatch/ResourceMatcher.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core.pathmatch; + +import org.eclipse.core.resources.IResource; + +public interface ResourceMatcher { + + boolean matches(IResource resource); + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/pathmatch/ResourceMatchers.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/pathmatch/ResourceMatchers.java new file mode 100644 index 00000000000..ae1479f377a --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/pathmatch/ResourceMatchers.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core.pathmatch; + +import org.apache.tools.ant.types.selectors.TokenizedPath; +import org.apache.tools.ant.types.selectors.TokenizedPattern; +import org.eclipse.core.resources.IResource; + +@SuppressWarnings("restriction") +public class ResourceMatchers { + + public static ResourceMatcher ANY = new ResourceMatcher() { + @Override + public String toString() { + return "ResourceMatcher(ANY)"; + } + @Override + public boolean matches(IResource resource) { + return true; + } + }; + + public static ResourceMatcher commaSeparatedPaths(String text) { + text = text.trim(); + if (text.isEmpty()) { + return ANY; + } + String[] paths = text.split(","); + if (paths.length==1) { + return path(paths[0]); + } else { + ResourceMatcher[] matchers = new ResourceMatcher[paths.length]; + for (int i = 0; i < matchers.length; i++) { + matchers[i] = path(paths[i]); + } + return either(matchers); + } + } + + private static ResourceMatcher either(ResourceMatcher... matchers) { + return new ResourceMatcher() { + + @Override + public String toString() { + StringBuilder buf = new StringBuilder("ResourceMatcher("); + for (int i = 0; i < matchers.length; i++) { + if (i>0) { + buf.append(", "); + } + buf.append(matchers[i]); + } + buf.append(")"); + return buf.toString(); + } + + @Override + public boolean matches(IResource resource) { + for (ResourceMatcher m : matchers) { + if (m.matches(resource)) { + return true; + } + } + return false; + } + }; + } + + private static ResourceMatcher path(String _pat) { + if (!_pat.startsWith("/") && !_pat.startsWith("**/")) { + _pat = "**/"+_pat; + } + final String pat = _pat; + TokenizedPattern matcher = new TokenizedPattern(pat); + return new ResourceMatcher() { + + @Override + public String toString() { + return pat; + } + + @Override + public boolean matches(IResource resource) { + TokenizedPath path = new TokenizedPath(resource.getFullPath().toString()); + return matcher.matchPath(path, true); + } + }; + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/preferences/QuickSearchPreferences.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/preferences/QuickSearchPreferences.java new file mode 100644 index 00000000000..aff2ec9230a --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/preferences/QuickSearchPreferences.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core.preferences; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.text.quicksearch.internal.core.priority.DefaultPriorityFunction; +import org.eclipse.text.quicksearch.internal.ui.QuickSearchActivator; +import org.eclipse.text.quicksearch.internal.util.LineReader; + +/** + * Helper class to access the QuickSearch Preferences. + * + * @author Kris De Volder + */ +public class QuickSearchPreferences { + + //Keys used to fetch 'raw' preferences values from the preferences store. + public static final String IGNORED_EXTENSIONS = "ignored.extensions"; + public static final String IGNORED_NAMES = "ignored.names"; + public static final String IGNORED_PREFIXES = "ignored.prefixes"; + public static final String MAX_LINE_LEN = "LineReader.MAX_LINE_LEN"; + private static boolean initializedDefaults; + + private IPreferenceStore store; + + public QuickSearchPreferences(IPreferenceStore preferenceStore) { + this.store = preferenceStore; + initializeDefaults(); + } + + public String[] getIgnoredExtensions() { + return getAndParseStringList(IGNORED_EXTENSIONS); + } + + public String[] getIgnoredPrefixes() { + return getAndParseStringList(IGNORED_PREFIXES); + } + + public String[] getIgnoredNames() { + return getAndParseStringList(IGNORED_NAMES); + } + + public int getMaxLineLen() { + return store.getInt(MAX_LINE_LEN); + } + + private String[] getAndParseStringList(String key) { + String raw = store.getString(key); + if (raw!=null) { + return parseStringList(raw); + } + return null; + } + + /** + * Takes a raw string list as entered in the prefs page input field and parses it. + * <p> + * Commas and newline are treated as 'separators' between elements. Further, any trailing + * and leading whitespace is stripped from individual elements and empty strings are silently + * dropped. + */ + private String[] parseStringList(String raw) { + String[] elements = raw.split("[,\n]"); + List<String> list = new ArrayList<String>(elements.length); + for (String e : elements) { + e = e.trim(); + if (!"".equals(e)) { + list.add(e); + } + } + return list.toArray(new String[list.size()]); + } + + public static void initializeDefaults() { + if (!initializedDefaults) { + initializedDefaults = true; + IPreferenceStore store = QuickSearchActivator.getDefault().getPreferenceStore(); + store.setDefault(QuickSearchPreferences.MAX_LINE_LEN, LineReader.DEFAULT_MAX_LINE_LENGTH); + + DefaultPriorityFunction dpf = new DefaultPriorityFunction(); + store.setDefault(QuickSearchPreferences.IGNORED_EXTENSIONS, encode(dpf.ignoredExtensions)); + store.setDefault(QuickSearchPreferences.IGNORED_NAMES, encode(dpf.ignoredNames)); + store.setDefault(QuickSearchPreferences.IGNORED_PREFIXES, encode(dpf.ignoredPrefixes)); + } + } + + private static String encode(String[] strings) { + StringBuilder encoded = new StringBuilder(); + for (int i = 0; i < strings.length; i++) { + if (i>0) { + encoded.append(", "); + } + encoded.append(strings[i]); + } + return encoded.toString(); + } + + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/DefaultPriorityFunction.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/DefaultPriorityFunction.java new file mode 100644 index 00000000000..4d6c232646e --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/DefaultPriorityFunction.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core.priority; + +import java.net.URI; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.text.quicksearch.internal.core.preferences.QuickSearchPreferences; + +/** + * Default implementation of PriorityFunction. It doesn't de-emphasize anything but + * if has some list of extensions and names that are used to ignore some types of + * files and directories. + * <p> + * This class can be used as is and customised by changing the lists, or it can be + * subclassed and inherit some of the default behaviour by calling super.priority() + */ +public class DefaultPriorityFunction extends PriorityFunction { + + /** + * If true, any resources marked as 'derived' in the Eclipse workspace will + * be ignored. + */ + public boolean ignoreDerived = true; + + /** + * The default priority function causes any resources that end with these strings to + * be ignored. + */ + public String[] ignoredExtensions = { + ".class", ".gif", ".exe", ".png", ".jpg", ".zip", ".jar", ".svg", ".psd", "~", + ".CLASS", ".GIF", ".EXE", ".PNG", ".JPG", ".ZIP", ".JAR", ".SVG", ".PSD", + ".pdf", ".p12", ".odt", ".odp", ".doc", ".pptx", ".ppt", ".bin", ".docx", ".xls", ".xlsx", + ".PDF", ".P12", ".ODT", ".ODP", ".DOC", ".PPTX", ".PPT", ".BIN", ".DOCX", ".XLS", ".XLSX" + }; + + /** + * The default priority function causes any resource who's name (i.e last path segment) + * starts with any of these Strings to be ignored. + */ + public String[] ignoredPrefixes = { + "." + }; + + /** + * The default priority function causes any resources who's name equals any of these + * Strings to be ignored. + */ + public String[] ignoredNames = { + "bin", "target", "build" + }; + + public Set<IResource> ignoredResources = null; + + @Override + public double priority(IResource r) { + if (r!=null && r.isAccessible()) { + if (ignoreDerived && r.isDerived()) { + return PRIORITY_IGNORE; + } + if (ignoredResources!=null && ignoredResources.contains(r)) { + return PRIORITY_IGNORE; + } + String name = r.getName(); + for (String ext : ignoredExtensions) { + if (name.endsWith(ext)) { + return PRIORITY_IGNORE; + } + } + for (String pre : ignoredPrefixes) { + if (name.startsWith(pre)) { + return PRIORITY_IGNORE; + } + } + for (String n : ignoredNames) { + if (name.equals(n)) { + return PRIORITY_IGNORE; + } + } + return PRIORITY_DEFAULT; + } + return PRIORITY_IGNORE; + } + + /** + * Initialise some configurable settings from an instance of QuickSearchPreferences + */ + public void configure(QuickSearchPreferences preferences) { + String[] pref = preferences.getIgnoredExtensions(); + if (pref!=null) { + this.ignoredExtensions = pref; + } + pref = preferences.getIgnoredNames(); + if (pref!=null) { + this.ignoredNames = pref; + } + pref = preferences.getIgnoredPrefixes(); + if (pref!=null) { + this.ignoredPrefixes = pref; + } + computeIgnoredFolders(); + } + + /** + * We want to avoid searchin the same files / folders twice in cases where users have 'overlapping projects'. + * I.e a project contains folders that are actually correspond to other projects also imported in the workspace. + * <p> + * See https://issuetracker.springsource.com/browse/STS-3783 + * <p> + * This method computes a set of folders to ignore. + */ + private void computeIgnoredFolders() { + //TODO: Hopefully this won't take too long to compute. Otherwise we may need to look at ways of caching it. + // it probably doesn't change that often. + IProject[] allprojects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject p : allprojects) { + if (p.isAccessible()) { + URI location = p.getLocationURI(); + if (location!=null) { + IContainer[] containers = ResourcesPlugin.getWorkspace().getRoot().findContainersForLocationURI(location); + if (containers!=null) { + for (IContainer folder : containers) { + if (!folder.equals(p)) { + ignore(folder); + } + } + } + } + } + } + } + + private void ignore(IContainer folder) { + if (ignoredResources==null) { + ignoredResources = new HashSet<IResource>(); + } + ignoredResources.add(folder); + } +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/PrioriTree.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/PrioriTree.java new file mode 100644 index 00000000000..812cb795a1d --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/PrioriTree.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core.priority; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; + +/** + * A PrioriTree is an implementation of PriorityFunction that is based on assigning specific priorities + * to a finite set of paths. The paths are kept in a tree-like structure internally so that + * assigning a priority to a given path also implicitly forces all the children leading to that + * path to have a priority that is at least as high as that of the path itself. + * + * TODO: The priority function produced by this tree would probably be better if it also raised/affected + * the priority of nodes in a subtree of a node, but not by as much as the node itself. + * + * @author Kris De Volder + */ +public class PrioriTree extends DefaultPriorityFunction { + + private static final boolean DEBUG = false; //(""+Platform.getLocation()).contains("kdvolder"); + + /** + * Creates an empty PrioriTree. This tree assigns provided default priority to any path. + */ + public static PrioriTree create() { + return new PrioriTree(0, PRIORITY_DEFAULT); + } + + private PrioriTree(int level, double defaultPriority) { + this.level = level; + this.priority = defaultPriority; + this.childPriority = defaultPriority; + } + + private static void debug(String string) { + if (DEBUG) { + System.out.println(string); + } + } + + /** + * Level of this node in the tree. This corresponds to the length of path ending in this node. I.e. 0 for the root node, + * 1 for children of root, 2 for children of level 1 nodes etc. + * <p> + * Level is used to easily check whether a node lookup returned an exact matching node or an ancestor. + */ + private int level; + + /** + * Priority assigned to any path lookup that ends here. + */ + private double priority = PRIORITY_IGNORE; + //Must start out as low as possible because putting stuff into the tree will raise the priorities monotonically. + //Note that normally we will not create a PrioriTree node unless there is some reason to + // assign a priority. So this value is expected to be always overwritten shortly after + // a node is created. + + /** + * Default priority assigned to all children of this node + */ + private double childPriority = PRIORITY_IGNORE; + + /** + * Children indexed by first segment in the path. This is only initialised if there's at least one + * child. + */ + private Map<String, PrioriTree> children = null; + + /** + * Set the priority for a given path. Also forces an update of all 'ancestor' nodes in the + * tree leading to this path to ensure that a parent node always has a priority at least as high as any + * of its children. + * <p> + * Also assigns the same priority to any descendants of the target path (but not the descendants of + * implicitly set parent nodes. + * <p> + * THe picture below illustrates: # marks a target node * marks nodes that implicitly + * also get set. And '-' marks nodes that are unaffected. + * <p> + * <pre> + * * + * / \ + * - * + * / \ + * - # + * / \ + * * * + * /\ /\ + * * * * * + * </pre> + * <p> + * Note: this operation never reduces the priority of any path already in the tree. + * Thus if the same path gets assigned a priority more than once, only the highest priority will + * be retained in the tree node for that path. + */ + public void setPriority(IPath path, double priority) { + this.priority = Math.max(this.priority, priority); //Use Math.max, never reduce priorities! + if (path.segmentCount()>0) { + // path leads to a child node + PrioriTree child = ensureChild(path.segment(0)); + child.setPriority(path.removeFirstSegments(1), priority); + } else { + // path ends here + setChildPriority(priority); + } + } + + private void setChildPriority(double priority) { + double newChildPriority = Math.max(priority, childPriority); + if (newChildPriority!=childPriority) { + //Must update default child priority as well check if already created children priorities need + // to be raised. + this.childPriority = newChildPriority; + if (children!=null) { + for (PrioriTree child : children.values()) { + //TODO: remove children that became redundant because the increase of childPriority in parent + // makes them have no observable effect. + //Currently this cleanup is not happening. This is inefficient but not incorrect. + child.priority = Math.max(child.priority, newChildPriority); + child.setChildPriority(newChildPriority); + } + } + } + } + + /** + * Ensure that this node has a child for a given segment string. If no node exists yet, create it. + * @param segment + * @return {@link PrioriTree} the existing or newly created child, never null. + */ + private PrioriTree ensureChild(String segment) { + if (children==null) { + children = new HashMap<String, PrioriTree>(); + } + PrioriTree child = children.get(segment); + if (child==null) { + child = new PrioriTree(level+1, childPriority); + children.put(segment, child); + } + return child; + } + + @Override + public double priority(IResource r) { + double result = super.priority(r); + if (result==PRIORITY_IGNORE) { + //Ignored paths shouldn't be changed ... ever. + return PRIORITY_IGNORE; + } + IPath path = r.getFullPath(); + PrioriTree node = this.lookup(path); + if (node.level == path.segmentCount()) { + //exact node found + result = node.priority; + } else { + //ancestor node found + result = node.childPriority; + } + debug("Priority for "+r.getFullPath() + " = " + result); + return result; + } + + + /** + * Locate tree node corresponding to a given path. + * @param fullPath + * @return The node or null if no corresponding node exists in the tree. + */ + private PrioriTree lookup(IPath path) { + PrioriTree found = null; + if (path.segmentCount()>0) { + PrioriTree child = getChild(path.segment(0)); + if (child!=null) { + found = child.lookup(path.removeFirstSegments(1)); + } + } + return found==null?this:found; + } + + /** + * Fetch the child for the corresponding segment String. + * @param segment + * @return The child or null if there is no such child. + */ + private PrioriTree getChild(String segment) { + if (children!=null) { + return children.get(segment); + } + return null; + } + + /** + * For debugging purposes. Dumps tree data onto System.out + */ + public void dump() { + dump("/", 0); + } + + private void dump(String name, int indent) { + indent(indent); + System.out.println(name + " : " +priority); + if (children!=null) { + for (Entry<String, PrioriTree> c : children.entrySet()) { + c.getValue().dump(c.getKey(), indent+1); + } + } + } + + private void indent(int i) { + for (int j = 0; j < i; j++) { + System.out.print(" "); + } + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/PriorityFunction.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/PriorityFunction.java new file mode 100644 index 00000000000..8dc5b9001aa --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/priority/PriorityFunction.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.core.priority; + +import org.eclipse.core.resources.IResource; + +/** + * An instance implementing this interface can optionally be provided to influence + * the searching order. If it is not provided then the default will be used. + */ +public abstract class PriorityFunction { + + /** + * The highest priority. Any elements in the queue with this priority will be visited before + * any others in the queue. Be warned that assigning this priority to a deeply nested + * element in the tree alone doesn't guarantee it will be visited early on because in + * order to reach the element the parents have to be visited first. If the parent + * has a low priority... + */ + public static final double PRIORITY_HIGHEST = Double.POSITIVE_INFINITY; + + /** + * Priority indicating something that is moderately more interesting than the default. + * So it should be processed before default stuff but not before "VISIT_FIRST" priority. + */ + public static final double PRIORITY_INTERESTING = 100; + + /** + * A default priority value. Meant to be used for elements that are neither particularly + * interesting or particularly non-interesting. Use larger numbers to emphasize elements + * and lower numbers to de-emphasise them. Note that in order to emphasise an element + * globally it also necessary to raise the priority of their parents because children + * can't be reached without passing through their parent. + */ + public static final double PRIORITY_DEFAULT = 0; + + + /** + * A special priority that causes elements (and their children) to be completely ignored. + */ + public static final double PRIORITY_IGNORE = Double.NEGATIVE_INFINITY; + + + + + public abstract double priority(IResource r); +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/Messages.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/Messages.java new file mode 100644 index 00000000000..6eeb76f30f4 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/Messages.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.text.quicksearch.internal.ui.messages"; //$NON-NLS-1$ + public static String QuickSearchPreferencesPage_0; + public static String QuickSearchPreferencesPage_1; + public static String QuickSearchPreferencesPage_2; + public static String QuickSearchPreferencesPage_3; + public static String QuickSearchPreferencesPage_4; + public static String QuickSearchPreferencesPage_5; + public static String QuickSearchPreferencesPage_6; + public static String QuickSearchPreferencesPage_7; + public static String QuickSearchPreferencesPage_8; + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchAction.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchAction.java new file mode 100644 index 00000000000..48884c1e463 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchAction.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2013-2019 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.IWorkbenchWindowActionDelegate; + +public class QuickSearchAction implements IWorkbenchWindowActionDelegate { + + private IWorkbenchWindow window; + + public void run(IAction action) { + QuickSearchHandler.doQuickSearch(window); + } + + public void selectionChanged(IAction action, ISelection selection) { + } + + public void dispose() { + } + + public void init(IWorkbenchWindow window) { + this.window = window; + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java new file mode 100644 index 00000000000..58ff698a197 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2013-2019 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.text.quicksearch.internal.core.preferences.QuickSearchPreferences; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class QuickSearchActivator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eclipse.text.quicksearch"; //$NON-NLS-1$ + + // The shared instance + private static QuickSearchActivator plugin; + + private QuickSearchPreferences prefs = null; //Lazy initialized + + /** + * The constructor + */ + public QuickSearchActivator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static QuickSearchActivator getDefault() { + return plugin; + } + + /** + * Returns an image descriptor for the image file at the given + * plug-in relative path + * + * @param path the path + * @return the image descriptor + */ + public static ImageDescriptor getImageDescriptor(String path) { + return imageDescriptorFromPlugin(PLUGIN_ID, path); + } + + public static void log(Throwable exception) { + log(createErrorStatus(exception)); + } + + public static void log(IStatus status) { +// if (logger == null) { + getDefault().getLog().log(status); +// } +// else { +// logger.logEntry(status); +// } + } + + public static IStatus createErrorStatus(Throwable exception) { + return new Status(IStatus.ERROR, PLUGIN_ID, 0, exception.getMessage(), exception); + } + + public QuickSearchPreferences getPreferences() { + if (prefs==null) { + prefs = new QuickSearchPreferences(QuickSearchActivator.getDefault().getPreferenceStore()); + } + return prefs; + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchContext.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchContext.java new file mode 100644 index 00000000000..197342eeeb7 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchContext.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.text.quicksearch.internal.core.priority.DefaultPriorityFunction; +import org.eclipse.text.quicksearch.internal.core.priority.PrioriTree; +import org.eclipse.text.quicksearch.internal.core.priority.PriorityFunction; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; + +/** + * An instance of this class groups together some logic to inform the + * quicksearch dialog about 'search context'. I.e. things such as the + * current selection and open editors when the dialog is opened. + * <p> + * This contextual information is used to inform the PriorityFunction + * for the search process. + * + * @author Kris De Volder + */ +public class QuickSearchContext { + + /** + * We remember the last result of getOpenFiles in here. This is so that we can return this + * if we are having trouble to compute the open files. Sometimes we may not be able to + * access the active workbench page etc. In this case it is probably better to return + * a stale list of files than nothing at all. + */ + private static Collection<IFile> lastOpenFiles = Arrays.asList(); //Empty list to start with. + + private IWorkbenchWindow window; + + public QuickSearchContext(IWorkbenchWindow window) { + this.window = window; + } + + /** + * Create a walker priority function based on the current 'context'. + */ + public PriorityFunction createPriorityFun() { + PrioriTree priorities = PrioriTree.create(); + priorities.configure(QuickSearchActivator.getDefault().getPreferences()); + try { +// TODO: This is not working correctly right now, if the selected resources are containers / folders. +// The PrioriTree only assigns a priority to the folder itself, but not to its children. +// So open editors will automatically take priority over the children of selected projects. +// To fix, PrioriTree will need a mechanism to assign priorities to children. +// If doing so, care must be taken not to accidentally assign priorities to ignored +// resources. + + Collection<IResource> selectedResources = getSelectedResources(); + for (IResource r : selectedResources) { + priorities.setPriority(r.getFullPath(), 3*PriorityFunction.PRIORITY_INTERESTING); + } + + IFile currentFile = getActiveFile(); + if (currentFile!=null) { + //Current file is more interesting than other open files + priorities.setPriority(currentFile.getFullPath(), 2*PriorityFunction.PRIORITY_INTERESTING); + } + Collection<IFile> openFiles = getOpenFiles(); + for (IFile file : openFiles) { + priorities.setPriority(file.getFullPath(), PriorityFunction.PRIORITY_INTERESTING); + } + return priorities; + } catch (Throwable e) { + QuickSearchActivator.log(e); + } + return new DefaultPriorityFunction(); + } + + private Collection<IFile> getOpenFiles() { + try { + IWorkbenchPage page = window.getActivePage(); + if (page!=null) { + Collection<IFile> files = new ArrayList<IFile>(); + IEditorReference[] editors = page.getEditorReferences(); + if (editors!=null) { + for (IEditorReference editor : editors) { + try { + IEditorInput input = editor.getEditorInput(); + if (input!=null) { + IFile file = (IFile) input.getAdapter(IFile.class); + if (file != null) { + files.add(file); + } + } + } catch (PartInitException e) { + //Ignore silently. See: https://issuetracker.springsource.com/browse/STS-4156 + //Rationale: Whatever may be the reason we can't obtain a 'input' for the editor. + //It likely means there's no text to search in that editor, so it is safe to ignore + //without loss of functionality to the quicksearch engine. + + //QuickSearchActivator.log(e); + } + } + lastOpenFiles = files; + return files; + } + } + return lastOpenFiles; + } finally { + } + } + + /** + * Gets the IFile that is currently open in the active editor. + * @return IFile or null if there is no current editor or the editor isn't associated to a file. + */ + private IFile getActiveFile() { + IWorkbenchPage page = window.getActivePage(); + if (page!=null) { + IEditorPart editor = page.getActiveEditor(); + if (editor!=null) { + IEditorInput input = editor.getEditorInput(); + if (input!=null) { + return (IFile) input.getAdapter(IFile.class); + } + } + } + return null; + } + + /** + * Get a Collection of selected resources from the active selection if that selection is + * a Structured selection (e.g. in navigator or project/package explorer) + */ + private Collection<IResource> getSelectedResources() { + ISelection _s = window.getSelectionService().getSelection(); + if (_s!=null && _s instanceof IStructuredSelection) { + IStructuredSelection s = (IStructuredSelection) _s; + if (s!=null && !s.isEmpty()) { + Object[] elements = s.toArray(); + List<IResource> resources = new ArrayList<IResource>(elements.length); + for (Object e : elements) { + if (e instanceof IResource) { + resources.add((IResource) e); + } else if (e instanceof IAdaptable) { + IAdaptable ae = (IAdaptable) e; + IResource r = (IResource) ae.getAdapter(IResource.class); + if (r!=null) { + resources.add(r); + } + } + } + return resources; + } + } + return Collections.emptyList(); + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java new file mode 100644 index 00000000000..64a93292041 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -0,0 +1,1469 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011, 2013 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Willian Mitsuda <wmitsuda@gmail.com> + * - Fix for bug 196553 - [Dialogs] Support IColorProvider/IFontProvider in FilteredItemsSelectionDialog + * Peter Friese <peter.friese@gentleware.com> + * - Fix for bug 208602 - [Dialogs] Open Type dialog needs accessible labels + * Simon Muschel <smuschel@gmx.de> - bug 258493 + * Kris De Volder Copied and modified from org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog + * to create QuickSearchDialog + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import static org.eclipse.jface.resource.JFaceResources.TEXT_FONT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.LegacyActionTools; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.StyledString.Styler; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.search.internal.ui.text.EditorOpener; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.text.quicksearch.internal.core.LineItem; +import org.eclipse.text.quicksearch.internal.core.QuickTextQuery; +import org.eclipse.text.quicksearch.internal.core.QuickTextQuery.TextRange; +import org.eclipse.text.quicksearch.internal.core.QuickTextSearchRequestor; +import org.eclipse.text.quicksearch.internal.core.QuickTextSearcher; +import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatchers; +import org.eclipse.text.quicksearch.internal.util.DocumentFetcher; +import org.eclipse.ui.ActiveShellExpression; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.SelectionStatusDialog; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.internal.IWorkbenchGraphicConstants; +import org.eclipse.ui.internal.WorkbenchImages; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; +import org.eclipse.ui.progress.UIJob; + +/** + * Shows a list of items to the user with a text entry field for a string + * pattern used to filter the list of items. + * + * @since 3.3 + */ +@SuppressWarnings({ "rawtypes", "restriction", "unchecked" }) +public class QuickSearchDialog extends SelectionStatusDialog { + + private static final int GO_BUTTON_ID = IDialogConstants.CLIENT_ID + 1; + private static final int REFRESH_BUTTON_ID = IDialogConstants.CLIENT_ID + 2; + + public static final Styler HIGHLIGHT_STYLE = org.eclipse.search.internal.ui.text.DecoratingFileSearchLabelProvider.HIGHLIGHT_STYLE; + + private UIJob refreshJob = new UIJob("Refresh") { + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + refreshWidgets(); + return Status.OK_STATUS; + } + }; + + protected void openSelection() { + try { + LineItem item = (LineItem) this.getFirstResult(); + if (item!=null) { + QuickTextQuery q = this.getQuery(); + TextRange range = q.findFirst(item.getText()); + EditorOpener opener = new EditorOpener(); + IWorkbenchPage page = window.getActivePage(); + if (page!=null) { + opener.openAndSelect(page, item.getFile(), range.getOffset()+item.getOffset(), + range.getLength(), true); + } + } + } catch (PartInitException e) { + QuickSearchActivator.log(e); + } + } + + /** + * Job that shows a simple busy indicator while a search is active. + * The job must be scheduled when a search starts/resumes. + */ + private UIJob progressJob = new UIJob("Refresh") { + int animate = 0; // number of dots to display. + + protected String dots(int animate) { + char[] chars = new char[animate]; + for (int i = 0; i < chars.length; i++) { + chars[i] = '.'; + } + return new String(chars); + } + + protected String currentFileInfo(IFile currentFile, int animate) { + if (currentFile!=null) { + String path = currentFile.getFullPath().toString(); + if (path.length()<=30) { + return path; + } + return "..."+path.substring(path.length()-30); + } + return dots(animate); + } + + @Override + public IStatus runInUIThread(IProgressMonitor mon) { + if (!mon.isCanceled() && progressLabel!=null && !progressLabel.isDisposed()) { + if (searcher==null || searcher.isDone()) { + progressLabel.setText(""); + } else { + progressLabel.setText("Searching"+currentFileInfo(searcher.getCurrentFile(), animate)); + animate = (animate+1)%4; + this.schedule(333); + } + } + return Status.OK_STATUS; + } + }; + + public final StyledCellLabelProvider LINE_NUMBER_LABEL_PROVIDER = new StyledCellLabelProvider() { + @Override + public void update(ViewerCell cell) { + LineItem item = (LineItem) cell.getElement(); + if (item!=null) { + cell.setText(""+item.getLineNumber()); + } else { + cell.setText("?"); + } + cell.setImage(getBlankImage()); + }; + }; + + private static final Color GREY = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY); + + private final StyledCellLabelProvider LINE_TEXT_LABEL_PROVIDER = new StyledCellLabelProvider() { + + @Override + public void update(ViewerCell cell) { + LineItem item = (LineItem) cell.getElement(); + if (item!=null) { + StyledString text = highlightMatches(item.getText()); + cell.setText(text.getString()); + cell.setStyleRanges(text.getStyleRanges()); + } else { + cell.setText(""); + cell.setStyleRanges(null); + } + cell.setImage(getBlankImage()); + super.update(cell); + } + }; + + private Image blankImage; + + private Image getBlankImage() { + if (blankImage==null) { + blankImage = new Image(Display.getDefault(), 1, 1); +// GC gc = new GC(blankImage); +// gc.fillRectangle(0, 0, 16, 16); +// gc.dispose(); + } + return blankImage; + } + + private final StyledCellLabelProvider LINE_FILE_LABEL_PROVIDER = new StyledCellLabelProvider() { + + @Override + public void update(ViewerCell cell) { + LineItem item = (LineItem) cell.getElement(); + if (item!=null) { + IPath path = item.getFile().getFullPath(); + String name = path.lastSegment(); + String dir = path.removeLastSegments(1).toString(); + cell.setText(name + " - "+dir); + StyleRange[] styleRanges = new StyleRange[] { + new StyleRange(name.length(), dir.length()+3, GREY, null) + }; + cell.setStyleRanges(styleRanges); + } else { + cell.setText(""); + cell.setStyleRanges(null); + } + cell.setImage(getBlankImage()); + super.update(cell); + } + +// public String getToolTipText(Object element) { +// LineItem item = (LineItem) element; +// if (item!=null) { +// return ""+item.getFile().getFullPath(); +// } +// return ""; +// }; + +// public String getText(Object _item) { +// if (_item!=null) { +// LineItem item = (LineItem) _item; +// return item.getFile().getName().toString(); +// } +// return "?"; +// }; + }; + + private static final String DIALOG_SETTINGS = QuickSearchDialog.class.getName()+".DIALOG_SETTINGS"; + + private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$ + + private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$ + private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$ + private static final String DIALOG_COLUMNS = "COLUMN_WIDTHS"; + private static final String DIALOG_SASH_WEIGHTS = "SASH_WEIGHTS"; + + private static final String DIALOG_LAST_QUERY = "LAST_QUERY"; + private static final String DIALOG_PATH_FILTER = "PATH_FILTER"; + private static final String CASE_SENSITIVE = "CASE_SENSITIVE"; + private static final boolean CASE_SENSITIVE_DEFAULT = true; + + private static final String KEEP_OPEN = "KEEP_OPEN"; + private static final boolean KEEP_OPEN_DEFAULT = false; + + /** + * Represents an empty selection in the pattern input field (used only for + * initial pattern). + */ + public static final int NONE = 0; + + /** + * Pattern input field selection where caret is at the beginning (used only + * for initial pattern). + */ + public static final int CARET_BEGINNING = 1; + + /** + * Represents a full selection in the pattern input field (used only for + * initial pattern). + */ + public static final int FULL_SELECTION = 2; + + private Text pattern; + + private TableViewer list; + + private MenuManager menuManager; + + private MenuManager contextMenuManager; + + private boolean multi; + + private ToolBar toolBar; + + private ToolItem toolItem; + + private Label progressLabel; + + private ContentProvider contentProvider; + + private String initialPatternText; + + private int selectionMode; + + private static final String EMPTY_STRING = ""; //$NON-NLS-1$ + + private final int MAX_LINE_LEN; + + private IHandlerActivation showViewHandler; + + private QuickTextSearcher searcher; + + private StyledText details; + + private DocumentFetcher documents; + + + private ToggleCaseSensitiveAction toggleCaseSensitiveAction; + private ToggleKeepOpenAction toggleKeepOpenAction; + + + private QuickSearchContext context; + + + private SashForm sashForm; + + private Label headerLabel; + + private IWorkbenchWindow window; + private Text searchIn; + + /** + * Creates a new instance of the class. + * + * @param window.getShell() + * shell to parent the dialog on + * @param multi + * indicates whether dialog allows to select more than one + * position in its list of items + */ + public QuickSearchDialog(IWorkbenchWindow window) { + super(window.getShell()); + this.window = window; + setShellStyle(SWT.CLOSE | SWT.MODELESS | SWT.BORDER | SWT.TITLE | SWT.RESIZE); + setBlockOnOpen(false); + this.setTitle("Quick Text Search"); + this.context = new QuickSearchContext(window); + this.multi = false; + contentProvider = new ContentProvider(); + selectionMode = NONE; + MAX_LINE_LEN = QuickSearchActivator.getDefault().getPreferences().getMaxLineLen(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.window.Window#create() + */ + public void create() { + super.create(); + pattern.setFocus(); + } + + /** + * Restores dialog using persisted settings. + */ + protected void restoreDialog(IDialogSettings settings) { + try { + if (initialPatternText==null) { + String lastSearch = settings.get(DIALOG_LAST_QUERY); + if (lastSearch==null) { + lastSearch = ""; + } + pattern.setText(lastSearch); + pattern.setSelection(0, lastSearch.length()); + } + if (settings.get(DIALOG_PATH_FILTER)!=null) { + String filter = settings.get(DIALOG_PATH_FILTER); + searchIn.setText(filter); + } + + if (settings.getArray(DIALOG_COLUMNS)!=null) { + String[] columnWidths = settings.getArray(DIALOG_COLUMNS); + Table table = list.getTable(); + int cols = table.getColumnCount(); + for (int i = 0; i < cols; i++) { + TableColumn col = table.getColumn(i); + try { + if (col!=null) { + col.setWidth(Integer.valueOf(columnWidths[i])); + } + } catch (Throwable e) { + QuickSearchActivator.log(e); + } + } + } + + if (settings.getArray(DIALOG_SASH_WEIGHTS)!=null) { + String[] _weights = settings.getArray(DIALOG_SASH_WEIGHTS); + int[] weights = new int[_weights.length]; + for (int i = 0; i < weights.length; i++) { + weights[i] = Integer.valueOf(_weights[i]); + } + sashForm.setWeights(weights); + } + } catch (Throwable e) { + //None of this stuff is critical so shouldn't stop opening dialog if it fails! + QuickSearchActivator.log(e); + } + } + + private class ToggleKeepOpenAction extends Action { + public ToggleKeepOpenAction(IDialogSettings settings) { + super( + "Keep Open", + IAction.AS_CHECK_BOX + ); + if (settings.get(KEEP_OPEN)==null) { + setChecked(KEEP_OPEN_DEFAULT); + } else{ + setChecked(settings.getBoolean(KEEP_OPEN)); + } + } + + public void run() { + //setChecked(!isChecked()); + } + + } + + + private class ToggleCaseSensitiveAction extends Action { + + public ToggleCaseSensitiveAction(IDialogSettings settings) { + super( + "Case Sensitive", + IAction.AS_CHECK_BOX + ); + if (settings.get(CASE_SENSITIVE)==null) { + setChecked(CASE_SENSITIVE_DEFAULT); + } else{ + setChecked(settings.getBoolean(CASE_SENSITIVE)); + } + } + + public void run() { + //setChecked(!isChecked()); + refreshHeaderLabel(); + applyFilter(false); + } + } + + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.window.Window#close() + */ + public boolean close() { + this.progressJob.cancel(); + this.progressJob = null; +// this.refreshProgressMessageJob.cancel(); + if (showViewHandler != null) { + IHandlerService service = (IHandlerService) PlatformUI + .getWorkbench().getService(IHandlerService.class); + service.deactivateHandler(showViewHandler); + showViewHandler.getHandler().dispose(); + showViewHandler = null; + } + if (menuManager != null) + menuManager.dispose(); + if (contextMenuManager != null) + contextMenuManager.dispose(); + storeDialog(getDialogSettings()); + if (searcher!=null) { + searcher.cancel(); + } + return super.close(); + } + + /** + * Stores dialog settings. + * + * @param settings + * settings used to store dialog + */ + protected void storeDialog(IDialogSettings settings) { + String currentSearch = pattern.getText(); + settings.put(DIALOG_LAST_QUERY, currentSearch); + settings.put(DIALOG_PATH_FILTER, searchIn.getText()); + if (toggleCaseSensitiveAction!=null) { + settings.put(CASE_SENSITIVE, toggleCaseSensitiveAction.isChecked()); + } + if (toggleKeepOpenAction!=null) { + settings.put(KEEP_OPEN, toggleKeepOpenAction.isChecked()); + } + Table table = list.getTable(); + if (table.getColumnCount()>0) { + String[] columnWidths = new String[table.getColumnCount()]; + for (int i = 0; i < columnWidths.length; i++) { + columnWidths[i] = ""+table.getColumn(i).getWidth(); + } + settings.put(DIALOG_COLUMNS, columnWidths); + } + if (sashForm.getWeights()!=null) { + int[] w = sashForm.getWeights(); + String[] ws = new String[w.length]; + for (int i = 0; i < ws.length; i++) { + ws[i] = ""+w[i]; + } + settings.put(DIALOG_SASH_WEIGHTS, ws); + } + } + + /** + * Create a new header which is labelled by headerLabel. + * + * @param parent + * @return Label the label of the header + */ + private Label createHeader(Composite parent) { + Composite header = new Composite(parent, SWT.NONE); + + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 0; + layout.marginHeight = 0; + header.setLayout(layout); + + headerLabel = new Label(header, SWT.NONE); + headerLabel.addTraverseListener(new TraverseListener() { + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) { + e.detail = SWT.TRAVERSE_NONE; + pattern.setFocus(); + } + } + }); + + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + headerLabel.setLayoutData(gd); + + createViewMenu(header); + header.setLayoutData(gd); + + refreshHeaderLabel(); + return headerLabel; + } + + private void refreshHeaderLabel() { + String msg = toggleCaseSensitiveAction.isChecked() ? "Case SENSITIVE" : "Case INSENSITIVE"; + msg += " Pattern (? = any character, * = any string)"; + headerLabel.setText(msg); + } + + /** + * Create the labels for the list and the progress. Return the list label. + * + * @param parent + * @return Label + */ + private Label createLabels(Composite parent) { + Composite labels = new Composite(parent, SWT.NONE); + + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 0; + layout.marginHeight = 0; + labels.setLayout(layout); + + Label listLabel = new Label(labels, SWT.NONE); + listLabel + .setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel); + + listLabel.addTraverseListener(new TraverseListener() { + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) { + e.detail = SWT.TRAVERSE_NONE; + list.getTable().setFocus(); + } + } + }); + + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + listLabel.setLayoutData(gd); + + progressLabel = new Label(labels, SWT.RIGHT); + progressLabel.setLayoutData(gd); + + labels.setLayoutData(gd); + return listLabel; + } + + private void createViewMenu(Composite parent) { + toolBar = new ToolBar(parent, SWT.FLAT); + toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + + GridData data = new GridData(); + data.horizontalAlignment = GridData.END; + toolBar.setLayoutData(data); + + toolBar.addMouseListener(new MouseAdapter() { + public void mouseDown(MouseEvent e) { + showViewMenu(); + } + }); + + toolItem.setImage(WorkbenchImages + .getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU)); + toolItem + .setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu); + toolItem.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + showViewMenu(); + } + }); + + menuManager = new MenuManager(); + + fillViewMenu(menuManager); + + IHandlerService service = (IHandlerService) PlatformUI.getWorkbench() + .getService(IHandlerService.class); + IHandler handler = new AbstractHandler() { + public Object execute(ExecutionEvent event) { + showViewMenu(); + return null; + } + }; + showViewHandler = service.activateHandler( + IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler, + new ActiveShellExpression(getShell())); + } + + /** + * Fills the menu of the dialog. + * + * @param menuManager + * the menu manager + */ + protected void fillViewMenu(IMenuManager menuManager) { + IDialogSettings settings = getDialogSettings(); + toggleCaseSensitiveAction = new ToggleCaseSensitiveAction(settings); + menuManager.add(toggleCaseSensitiveAction); + toggleKeepOpenAction = new ToggleKeepOpenAction(settings); + menuManager.add(toggleKeepOpenAction); + } + + private void showViewMenu() { + Menu menu = menuManager.createContextMenu(getShell()); + Rectangle bounds = toolItem.getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = toolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + } + + /** + * Hook that allows to add actions to the context menu. + * <p> + * Subclasses may extend in order to add other actions.</p> + * + * @param menuManager the context menu manager + * @since 3.5 + */ + protected void fillContextMenu(IMenuManager menuManager) { + } + + private void createPopupMenu() { + + contextMenuManager = new MenuManager(); + contextMenuManager.setRemoveAllWhenShown(true); + contextMenuManager.addMenuListener(new IMenuListener() { + public void menuAboutToShow(IMenuManager manager) { + fillContextMenu(manager); + } + }); + + final Table table = list.getTable(); + Menu menu= contextMenuManager.createContextMenu(table); + table.setMenu(menu); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite dialogArea = (Composite) super.createDialogArea(parent); + + dialogArea.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + QuickSearchDialog.this.dispose(); + } + }); + + Composite content = createNestedComposite(dialogArea, 1, false); + GridData gd = new GridData(GridData.FILL_BOTH); + content.setLayoutData(gd); + + final Label headerLabel = createHeader(content); + + Composite inputRow = createNestedComposite(content, 10, true); + GridDataFactory.fillDefaults().grab(true, false).applyTo(inputRow); + pattern = new Text(inputRow, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL); + pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + e.result = LegacyActionTools.removeMnemonics(headerLabel + .getText()); + } + }); + GridDataFactory.fillDefaults().span(6,1).grab(true, false).applyTo(pattern); + + Composite searchInComposite = createNestedComposite(inputRow, 2, false); + GridDataFactory.fillDefaults().span(4,1).grab(true, false).applyTo(searchInComposite); + Label searchInLabel = new Label(searchInComposite, SWT.NONE); + searchInLabel.setText(" In: "); + searchIn = new Text(searchInComposite, SWT.SINGLE | SWT.BORDER | SWT.ICON_CANCEL); + searchIn.setToolTipText("Search in (comma-separated list of '.gitignore' style inclusion patterns)"); + GridDataFactory.fillDefaults().grab(true, false).applyTo(searchIn); + + final Label listLabel = createLabels(content); + + sashForm = new SashForm(content, SWT.VERTICAL); + GridDataFactory.fillDefaults().grab(true, true).applyTo(sashForm); + + list = new TableViewer(sashForm, (multi ? SWT.MULTI : SWT.SINGLE) | + SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL); +// ColumnViewerToolTipSupport.enableFor(list, ToolTip.NO_RECREATE); + + list.getTable().setHeaderVisible(true); + list.getTable().setLinesVisible(true); + list.getTable().getAccessible().addAccessibleListener( + new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + if (e.childID == ACC.CHILDID_SELF) { + e.result = LegacyActionTools + .removeMnemonics(listLabel.getText()); + } + } + }); + list.setContentProvider(contentProvider); +// new ScrollListener(list.getTable().getVerticalBar()); +// new SelectionChangedListener(list); + + TableViewerColumn col = new TableViewerColumn(list, SWT.RIGHT); + col.setLabelProvider(LINE_NUMBER_LABEL_PROVIDER); + col.getColumn().setText("Line"); + col.getColumn().setWidth(40); + col = new TableViewerColumn(list, SWT.LEFT); + col.getColumn().setText("Text"); + col.setLabelProvider(LINE_TEXT_LABEL_PROVIDER); + col.getColumn().setWidth(400); + col = new TableViewerColumn(list, SWT.LEFT); + col.getColumn().setText("Path"); + col.setLabelProvider(LINE_FILE_LABEL_PROVIDER); + col.getColumn().setWidth(150); + + new TableResizeHelper(list).enableResizing(); + + //list.setLabelProvider(getItemsListLabelProvider()); + list.setInput(new Object[0]); + list.setItemCount(contentProvider.getNumberOfElements()); + gd = new GridData(GridData.FILL_BOTH); + applyDialogFont(list.getTable()); + gd.heightHint= list.getTable().getItemHeight() * 15; + list.getTable().setLayoutData(gd); + + createPopupMenu(); + + pattern.addModifyListener(e -> { + applyFilter(false); + }); + + searchIn.addModifyListener(e -> { + applyPathMatcher(); + }); + + pattern.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.ARROW_DOWN) { + if (list.getTable().getItemCount() > 0) { + list.getTable().setFocus(); + list.getTable().select(0); + //programatic selection may not fire selection events so... + refreshDetails(); + } + } + } + }); + + list.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + StructuredSelection selection = (StructuredSelection) event + .getSelection(); + handleSelected(selection); + } + }); + + list.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + handleDoubleClick(); + } + }); + + list.getTable().addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + + if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) == 0 + && (e.stateMask & SWT.CTRL) == 0) { + StructuredSelection selection = (StructuredSelection) list + .getSelection(); + + if (selection.size() == 1) { + Object element = selection.getFirstElement(); + if (element.equals(list.getElementAt(0))) { + pattern.setFocus(); + } + list.getTable().notifyListeners(SWT.Selection, + new Event()); + + } + } + + if (e.keyCode == SWT.ARROW_DOWN + && (e.stateMask & SWT.SHIFT) != 0 + && (e.stateMask & SWT.CTRL) != 0) { + + list.getTable().notifyListeners(SWT.Selection, new Event()); + } + + } + }); + + createDetailsArea(sashForm); + sashForm.setWeights(new int[] {5,1}); + + applyDialogFont(content); + + restoreDialog(getDialogSettings()); + + if (initialPatternText != null) { + pattern.setText(initialPatternText); + } + + switch (selectionMode) { + case CARET_BEGINNING: + pattern.setSelection(0, 0); + break; + case FULL_SELECTION: + pattern.setSelection(0, initialPatternText.length()); + break; + } + + // apply filter even if pattern is empty (display history) + applyFilter(false); + + return dialogArea; + } + + private Composite createNestedComposite(Composite parent, int numRows, boolean equalRows) { + Composite nested = new Composite(parent, SWT.NONE); + { + GridLayout layout = new GridLayout(numRows, equalRows); + layout.marginWidth = 0; + layout.marginHeight = 0; + layout.marginLeft = 0; + layout.marginRight = 0; + layout.horizontalSpacing = 0; + nested.setLayout(layout); + } + GridDataFactory.fillDefaults().grab(true, false).applyTo(nested); + return nested; + } + + protected void dispose() { + if (blankImage!=null) { + blankImage.dispose(); + blankImage = null; + } + } + + private void createDetailsArea(Composite parent) { + details = new StyledText(parent, SWT.MULTI+SWT.READ_ONLY+SWT.BORDER+SWT.H_SCROLL); + details.setFont(JFaceResources.getFont(TEXT_FONT)); + + list.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + refreshDetails(); + } + }); + details.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + refreshDetails(); + } + }); + } + + + // Dumber version just using the a 'raw' StyledText widget. + private void refreshDetails() { + if (details!=null && list!=null && !list.getTable().isDisposed()) { + if (documents==null) { + documents = new DocumentFetcher(); + } + IStructuredSelection sel = (IStructuredSelection) list.getSelection(); + if (sel==null || sel.isEmpty()) { + details.setText(""); + } else { + //Not empty selection + int numLines = computeLines(); + if (numLines > 0) { + LineItem item = (LineItem) sel.getFirstElement(); + IDocument document = documents.getDocument(item.getFile()); + if (document!=null) { + try { + int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based. + int start = document.getLineOffset(Math.max(line-(numLines-1)/2, 0)); + int end = document.getLength(); + try { + end = document.getLineOffset(start+numLines); + } catch (BadLocationException e) { + //Presumably line number is past the end of document. + //ignore. + } + + StyledString styledString = highlightMatches(document.get(start, end-start)); + details.setText(styledString.getString()); + details.setStyleRanges(styledString.getStyleRanges()); + + return; + } catch (BadLocationException e) { + } + } + } + } + //empty selection or some error: + details.setText(""); + } + } + + /** + * Computes how much lines of text can be displayed in the details section based on + * its current height and font metrics. + */ + private int computeLines() { + if (details!=null && !details.isDisposed()) { + GC gc = new GC(details); + try { + FontMetrics fm = gc.getFontMetrics(); + int itemH = fm.getHeight(); + int areaH = details.getClientArea().height; + return (areaH+itemH-1) / itemH; + } finally { + gc.dispose(); + } + } + return 0; + } + + /** + * Helper function to highlight all the matches for the current query in a given piece + * of text. + * + * @return StyledString instance. + */ + private StyledString highlightMatches(String visibleText) { + StyledString styledText = new StyledString(visibleText); + List<TextRange> matches = getQuery().findAll(visibleText); + for (TextRange m : matches) { + styledText.setStyle(m.getOffset(), m.getLength(), HIGHLIGHT_STYLE); + } + return styledText; + } + +// Version using sourceviewer +// private void refreshDetails() { +// if (details!=null && list!=null && !list.getTable().isDisposed()) { +// if (documents==null) { +// documents = new DocumentFetcher(); +// } +// IStructuredSelection sel = (IStructuredSelection) list.getSelection(); +// if (sel!=null && !sel.isEmpty()) { +// //Not empty selection +// LineItem item = (LineItem) sel.getFirstElement(); +// IDocument document = documents.getDocument(item.getFile()); +// try { +// int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based. +// int start = document.getLineOffset(Math.max(line-2, 0)); +// int end = document.getLength(); +// try { +// end = document.getLineOffset(line+3); +// } catch (BadLocationException e) { +// //Presumably line number is past the end of document. +// //ignore. +// } +// details.setDocument(document, start, end-start); +// +// String visibleText = document.get(start, end-start); +// List<TextRange> matches = getQuery().findAll(visibleText); +// Region visibleRegion = new Region(start, end-start); +// TextPresentation presentation = new TextPresentation(visibleRegion, 20); +// presentation.setDefaultStyleRange(new StyleRange(0, document.getLength(), null, null)); +// for (TextRange m : matches) { +// presentation.addStyleRange(new StyleRange(m.start+start, m.len, null, YELLOW)); +// } +// details.changeTextPresentation(presentation, true); +// +// return; +// } catch (BadLocationException e) { +// } +// } +// details.setDocument(null); +// } +// } + + /** + * Handle selection in the items list by updating labels of selected and + * unselected items and refresh the details field using the selection. + * + * @param selection + * the new selection + */ + protected void handleSelected(StructuredSelection selection) { + IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, + IStatus.OK, EMPTY_STRING, null); + + updateStatus(status); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings() + */ + protected IDialogSettings getDialogBoundsSettings() { + IDialogSettings settings = getDialogSettings(); + IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS); + if (section == null) { + section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS); + section.put(DIALOG_HEIGHT, 500); + section.put(DIALOG_WIDTH, 600); + } + return section; + } + + /** + * Returns the dialog settings. Returned object can't be null. + * + * @return return dialog settings for this dialog + */ + protected IDialogSettings getDialogSettings() { + IDialogSettings settings = IDEWorkbenchPlugin.getDefault() + .getDialogSettings().getSection(DIALOG_SETTINGS); + + if (settings == null) { + settings = IDEWorkbenchPlugin.getDefault().getDialogSettings() + .addNewSection(DIALOG_SETTINGS); + } + + return settings; + } + + /** + * Has to be called in UI thread. + */ + public void refreshWidgets() { + if (list != null && !list.getTable().isDisposed()) { + int itemCount = contentProvider.getNumberOfElements(); + list.setItemCount(itemCount); + list.refresh(true, false); + Button goButton = getButton(GO_BUTTON_ID); + if (goButton!=null && !goButton.isDisposed()) { + //Even if no element is selected. The dialog should be have as if the first + //element in the list is selected. So the button is enabled if any + //element is available in the list. + goButton.setEnabled(itemCount>0); + } + + } + } + + /** + * Schedule refresh job. + */ + public void scheduleRefresh() { + refreshJob.schedule(); +// refreshCacheJob.cancelAll(); +// refreshCacheJob.schedule(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult() + */ + protected void computeResult() { + List objectsToReturn = ((StructuredSelection) list.getSelection()) + .toList(); + if (objectsToReturn.isEmpty()) { + //Pretend that the first element is selected. + Object first = list.getElementAt(0); + if (first!=null) { + objectsToReturn = Arrays.asList(first); + } + } + setResult(objectsToReturn); + } + + + + /** + * Handles double-click of items, but *also* by pressing the 'enter' key. + */ + protected void handleDoubleClick() { + goButtonPressed(); + } + + protected void refreshButtonPressed() { + applyFilter(true); + } + + /** + * Handles directly clicking the 'go' button. + */ + protected void goButtonPressed() { + computeResult(); + openSelection(); + if (!toggleKeepOpenAction.isChecked()) { + close(); + } + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + SelectionListener listener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + int buttonId = (int) ((Button)e.widget).getData(); + if (buttonId==GO_BUTTON_ID) { + goButtonPressed(); + } else if (buttonId==REFRESH_BUTTON_ID) { + refreshButtonPressed(); + } + } + }; + createButton(parent, GO_BUTTON_ID, "Go!", false).addSelectionListener(listener); + createButton(parent, REFRESH_BUTTON_ID, "Refresh", false).addSelectionListener(listener); + refreshWidgets(); + } + + /** + * Sets the initial pattern used by the filter. This text is copied into the + * selection input on the dialog. A full selection is used in the pattern + * input field. + * + * @param text + * initial pattern for the filter + * @see QuickSearchDialog#FULL_SELECTION + */ + public void setInitialPattern(String text) { + setInitialPattern(text, FULL_SELECTION); + } + + /** + * Sets the initial pattern used by the filter. This text is copied into the + * selection input on the dialog. The <code>selectionMode</code> is used + * to choose selection type for the input field. + * + * @param text + * initial pattern for the filter + * @param selectionMode + * one of: {@link QuickSearchDialog#NONE}, + * {@link QuickSearchDialog#CARET_BEGINNING}, + * {@link QuickSearchDialog#FULL_SELECTION} + */ + public void setInitialPattern(String text, int selectionMode) { + this.initialPatternText = text; + this.selectionMode = selectionMode; + } + + /** + * Gets initial pattern. + * + * @return initial pattern, or <code>null</code> if initial pattern is not + * set + */ + protected String getInitialPattern() { + return this.initialPatternText; + } + + /** + * Returns the current selection. + * + * @return the current selection + */ + protected StructuredSelection getSelectedItems() { + + StructuredSelection selection = (StructuredSelection) list + .getSelection(); + + List selectedItems = selection.toList(); + + return new StructuredSelection(selectedItems); + } + + /** + * Validates the item. When items on the items list are selected or + * deselected, it validates each item in the selection and the dialog status + * depends on all validations. + * + * @param item + * an item to be checked + * @return status of the dialog to be set + */ + protected IStatus validateItem(Object item) { + return new Status(IStatus.OK, QuickSearchActivator.PLUGIN_ID, "fine"); + } + + /** + * Creates an instance of a filter. + * + * @return a filter for items on the items list. Can be <code>null</code>, + * no filtering will be applied then, causing no item to be shown in + * the list. + */ + protected QuickTextQuery createFilter() { + return new QuickTextQuery(pattern.getText(), toggleCaseSensitiveAction.isChecked()); + } + + /** + * Applies the filter created by <code>createFilter()</code> method to the + * items list. When new filter is different than previous one it will cause + * refiltering. + * <p> + * The 'force' parameter forces a full refresh of the search results / filter even + * when the filter is unchanged, or when a incremenal filtering optimisation could be + * applied based on query structure. (The use case for this, is to trigger forced refresh + * because the underlying resources may have changed). + */ + protected void applyFilter(boolean force) { + QuickTextQuery newFilter = createFilter(); + if (this.searcher==null) { + if (!newFilter.isTrivial()) { + //Create the QuickTextSearcher with the inital query. + this.searcher = new QuickTextSearcher(newFilter, context.createPriorityFun(), MAX_LINE_LEN, new QuickTextSearchRequestor() { + @Override + public void add(LineItem match) { + contentProvider.add(match); + contentProvider.refresh(); + } + @Override + public void clear() { + contentProvider.reset(); + contentProvider.refresh(); + } + @Override + public void revoke(LineItem match) { + contentProvider.remove(match); + contentProvider.refresh(); + } + @Override + public void update(LineItem match) { + contentProvider.refresh(); + } + }); + applyPathMatcher(); + refreshWidgets(); + } +// this.list.setInput(input) + } else { + //The QuickTextSearcher is already active update the query + this.searcher.setQuery(newFilter, force); + } + if (progressJob!=null) { + progressJob.schedule(); + } + } + + private void applyPathMatcher() { + if (this.searcher!=null) { + this.searcher.setPathMatcher(ResourceMatchers.commaSeparatedPaths(searchIn.getText())); + } + } + + + + /** + * Returns name for then given object. + * + * @param item + * an object from the content provider. Subclasses should pay + * attention to the passed argument. They should either only pass + * objects of a known type (one used in content provider) or make + * sure that passed parameter is the expected one (by type + * checking like <code>instanceof</code> inside the method). + * @return name of the given item + */ + public String getElementName(Object item) { + return ""+item; +// return (String)item; // Assuming the items are strings for now + } + + /** + * Collects filtered elements. Contains one synchronized, sorted set for + * collecting filtered elements. + * Implementation of <code>ItemsFilter</code> is used to filter elements. + * The key function of filter used in to filtering is + * <code>matchElement(Object item)</code>. + * <p> + * The <code>ContentProvider</code> class also provides item filtering + * methods. The filtering has been moved from the standard TableView + * <code>getFilteredItems()</code> method to content provider, because + * <code>ILazyContentProvider</code> and virtual tables are used. This + * class is responsible for adding a separator below history items and + * marking each items as duplicate if its name repeats more than once on the + * filtered list. + */ + private class ContentProvider implements IStructuredContentProvider, ILazyContentProvider { + + private List items; + + /** + * Creates new instance of <code>ContentProvider</code>. + */ + public ContentProvider() { + this.items = Collections.synchronizedList(new ArrayList(2048)); +// this.duplicates = Collections.synchronizedSet(new HashSet(256)); +// this.lastSortedItems = Collections.synchronizedList(new ArrayList( +// 2048)); + } + + public void remove(LineItem match) { + this.items.remove(match); + } + + /** + * Removes all content items and resets progress message. + */ + public void reset() { + this.items.clear(); + } + + /** + * Adds filtered item. + * + * @param match + */ + public void add(LineItem match) { + this.items.add(match); + } + + /** + * Refresh dialog. + */ + public void refresh() { + scheduleRefresh(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object) + */ + public Object[] getElements(Object inputElement) { + return items.toArray(); + } + + public int getNumberOfElements() { + return items.size(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IContentProvider#dispose() + */ + public void dispose() { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.ILazyContentProvider#updateElement(int) + */ + public void updateElement(int index) { + + QuickSearchDialog.this.list.replace((items + .size() > index) ? items.get(index) : null, + index); + + } + + } + + /** + * Get the control where the search pattern is entered. Any filtering should + * be done using an {@link ItemsFilter}. This control should only be + * accessed for listeners that wish to handle events that do not affect + * filtering such as custom traversal. + * + * @return Control or <code>null</code> if the pattern control has not + * been created. + */ + public Control getPatternControl() { + return pattern; + } + + public QuickTextQuery getQuery() { + return searcher.getQuery(); + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchHandler.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchHandler.java new file mode 100644 index 00000000000..7f13c6b3714 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchHandler.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.ISelectionService; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Our sample handler extends AbstractHandler, an IHandler base class. + * @see org.eclipse.core.commands.IHandler + * @see org.eclipse.core.commands.AbstractHandler + */ +@SuppressWarnings("restriction") +public class QuickSearchHandler extends AbstractHandler { + /** + * The constructor. + */ + public QuickSearchHandler() { + } + + /** + * the command has been executed, so extract extract the needed information + * from the application context. + */ + public Object execute(ExecutionEvent event) throws ExecutionException { + IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event); + doQuickSearch(window); + return null; + } + + public static void doQuickSearch(IWorkbenchWindow window) { + QuickSearchDialog dialog = new QuickSearchDialog(window); + initializeFromSelection(window, dialog); + dialog.open(); + } + + /** + * Based on the current active selection initialize the priority function and/or + * the initial contents of the search box. + */ + static private void initializeFromSelection(IWorkbenchWindow workbench, QuickSearchDialog dialog) { + if (workbench!=null) { + ISelectionService selectionService = workbench.getSelectionService(); + ISelection selection = selectionService.getSelection(); + if (selection!=null && selection instanceof ITextSelection) { + //Use text selection to set initial search pattern. + String text = ((ITextSelection) selection).getText(); + if (text!=null && !"".equals(text)) { + dialog.setInitialPattern(text, QuickSearchDialog.FULL_SELECTION); + } + } + } +// IEditorPart editor = HandlerUtil.getActiveEditor(event); +// if (editor!=null && editor instanceof ITextEditor) { +// ITextEditor textEditor = (ITextEditor)editor; +// ISelection selection = textEditor.getSelectionProvider().getSelection(); +// if (selection!=null && selection instanceof ITextSelection) { +// String text = ((ITextSelection) selection).getText(); +// if (text!=null && !"".equals(text)) { +// dialog.setInitialPattern(text, QuickSearchDialog.FULL_SELECTION); +// } +// } +// } + } +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPreferencesPage.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPreferencesPage.java new file mode 100644 index 00000000000..d13e9606465 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPreferencesPage.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IntegerFieldEditor; +import org.eclipse.jface.preference.StringFieldEditor; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.text.quicksearch.internal.core.preferences.QuickSearchPreferences; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +public class QuickSearchPreferencesPage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + + public QuickSearchPreferencesPage() { + super(FieldEditorPreferencePage.GRID); + setPreferenceStore(QuickSearchActivator.getDefault().getPreferenceStore()); + QuickSearchPreferences.initializeDefaults(); + } + + @Override + public void init(IWorkbench arg0) { + } + + private static final String[] prefsKeys = { + QuickSearchPreferences.IGNORED_EXTENSIONS, + QuickSearchPreferences.IGNORED_PREFIXES, + QuickSearchPreferences.IGNORED_NAMES + }; + + private static final String[] fieldNames = { + Messages.QuickSearchPreferencesPage_0, Messages.QuickSearchPreferencesPage_1, Messages.QuickSearchPreferencesPage_2 + }; + + private static final String[] toolTips = { + Messages.QuickSearchPreferencesPage_3 + , + Messages.QuickSearchPreferencesPage_4 + , + Messages.QuickSearchPreferencesPage_5 + }; + + @Override + protected void createFieldEditors() { + IntegerFieldEditor field_maxLineLen = new IntegerFieldEditor(QuickSearchPreferences.MAX_LINE_LEN, Messages.QuickSearchPreferencesPage_6, getFieldEditorParent()); + field_maxLineLen.getTextControl(getFieldEditorParent()).setToolTipText( + Messages.QuickSearchPreferencesPage_7); + addField(field_maxLineLen); + + for (int i = 0; i < fieldNames.length; i++) { + final String tooltip = toolTips[i]; + StringFieldEditor field = new StringFieldEditor(prefsKeys[i], Messages.QuickSearchPreferencesPage_8+" "+fieldNames[i], 45, 5, StringFieldEditor.VALIDATE_ON_FOCUS_LOST, getFieldEditorParent()) { + @Override + protected Text createTextWidget(Composite parent) { + Text w = super.createTextWidget(parent); + w.setToolTipText(tooltip); + return w; + } + }; + addField(field); + } + } +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/TableResizeHelper.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/TableResizeHelper.java new file mode 100644 index 00000000000..2b9f2ff7ac9 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/TableResizeHelper.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +/** + * Resizes table columns such that columns fit the table width. The table is + * initially resized when the resize is enabled, and its columns continue being + * resized automatically as the user resizes the table. + */ +public class TableResizeHelper { + + private final TableViewer tableViewer; + + public TableResizeHelper(TableViewer tableViewer) { + this.tableViewer = tableViewer; + } + + public void enableResizing() { + ControlListener resizeListener = new ControlListener() { + public void controlResized(ControlEvent e) { + resizeTable(); + } + public void controlMoved(ControlEvent e) { + } + }; + tableViewer.getTable().addControlListener(resizeListener); + TableColumn[] cols = tableViewer.getTable().getColumns(); + if (cols!=null) { + for (int i = 0; i < cols.length-1; i++) { + cols[i].addControlListener(resizeListener); + } + } + + // Initial resize of the columns + resizeTable(); + + } + + protected void resizeTable() { + Composite tableComposite = tableViewer.getTable();//.getParent(); + Rectangle tableCompositeArea = tableComposite.getClientArea(); + int width = tableCompositeArea.width; +// ScrollBar sb = tableViewer.getTable().getVerticalBar(); +// if (sb!=null && sb.isVisible()) { +// width = width - sb.getSize().x; +// } + resizeTableColumns(width, tableViewer.getTable()); + } + + protected void resizeTableColumns(int tableWidth, Table table) { + TableColumn[] tableColumns = table.getColumns(); + + if (tableColumns.length == 0) { + return; + } + + int total = 0; + + for (TableColumn column : tableColumns) { + total += column.getWidth(); + } + + TableColumn lastColumn = tableColumns[tableColumns.length - 1]; + int newWidth = (tableWidth - total) + lastColumn.getWidth(); + if (newWidth>0) { + lastColumn.setWidth(newWidth); + } + + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/messages.properties b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/messages.properties new file mode 100644 index 00000000000..5d15d07ba64 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/messages.properties @@ -0,0 +1,9 @@ +QuickSearchPreferencesPage_0=Extensions +QuickSearchPreferencesPage_1=Prefixes +QuickSearchPreferencesPage_2=Names +QuickSearchPreferencesPage_3=Enter a list of file extensions. Elements in the list can be separated by commas or newlines. Any file or folder ending with one of the extensions will be ignored. +QuickSearchPreferencesPage_4=Enter a list of file prefixes. Elements in the list can be separated by commas or newlines. Any file or folder who's name begins with one of the extensions will be ignored. +QuickSearchPreferencesPage_5=Enter a list of file names. Elements in the list can be separated by commas or newlines. Any file or folder who's name equals one of the extensions will be ignored. +QuickSearchPreferencesPage_6=Max Line Length +QuickSearchPreferencesPage_7=When QuickSearch encounters a line of text longer than 'Max Line Length' it stops searching the current file. This is meant to avoid searching in machine generated text files, such as, minified javascript. +QuickSearchPreferencesPage_8=Ignore diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/DocumentFetcher.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/DocumentFetcher.java new file mode 100644 index 00000000000..52544d206b8 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/DocumentFetcher.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008, 2013-2019 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Pivotal Inc - Adapted for use in quicksearch + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.filebuffers.ITextFileBufferManager; +import org.eclipse.core.filebuffers.LocationKind; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.search.internal.ui.SearchPlugin; +import org.eclipse.text.quicksearch.internal.ui.QuickSearchActivator; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.editors.text.TextFileDocumentProvider; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.ITextEditor; + +/** + * Useful utilities (private methods) copied from org.eclipse.search.internal.core.text.TextSearchVisitor + * and rearanged / massaged to be a more reusable utility class. + * <p> + * These utilities allow us to access the contents of dirty editors so we can search/read in them as though they + * are already saved but without actually requiring the user to save them. + * + * @author Kris De Volder + */ +@SuppressWarnings("restriction") +public class DocumentFetcher { + + private Map<IFile, IDocument> dirtyEditors; + + //Simple cache remembers the last fetched file and document. + private IFile lastFile = null; + private IDocument lastDocument = null; + + IDocumentProvider provider = new TextFileDocumentProvider(); + + public DocumentFetcher() { + if (PlatformUI.isWorkbenchRunning()) { + dirtyEditors = evalNonFileBufferDocuments(); + } else { + dirtyEditors = Collections.emptyMap(); + } + } + + /** + * Obtains a {@link IDocument} containing the contents of a + * {@link IFile}. Two different scenarios are supported depending + * on whether or not the file is currently opened in a editor. + * <p> + * If the IFile is opened in an editor, then the document reflects + * the editor contents (including any not-yet saved edits). + * <p> + * If the file is not open, then the document just reflects the + * contents of the file. + * + * @param file + * @return Document containing the contents of the file or editor buffer, + * or null if the content can not be found (it exists neither as a editor + * buffer nor corresponds to an existing file in the workspace. + */ + public IDocument getDocument(IFile file) { + if (file==lastFile) { + return lastDocument; + } + lastFile = file; + lastDocument = dirtyEditors.get(file); + if (lastDocument==null) { + lastDocument = getOpenDocument(file); + if (lastDocument==null) { + lastDocument = getClosedDocument(file); + } + } + return lastDocument; + } + + private IDocument getOpenDocument(IFile file) { + ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager(); + ITextFileBuffer textFileBuffer= bufferManager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); + if (textFileBuffer != null) { + return textFileBuffer.getDocument(); + } + return null; + } + + private IDocument getClosedDocument(IFile file) { + //No in the manager yet. Try to create a temporary buffer then remove it again. + ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); + IPath location = file.getFullPath(); //Must use workspace location, not fs location for API below. + ITextFileBuffer buffer = null; + try { + bufferManager.connect(location, LocationKind.IFILE, new NullProgressMonitor()); + buffer = bufferManager.getTextFileBuffer(location, LocationKind.IFILE); + if (buffer!=null) { + return buffer.getDocument(); + } + } catch (Throwable e) { + QuickSearchActivator.log(e); + } finally { + try { + bufferManager.disconnect(location, LocationKind.IFILE, new NullProgressMonitor()); + } catch (CoreException e) { + } + } + return null; + } + + /** + * @return returns a map from IFile to IDocument for all open, dirty editors. + */ + private Map<IFile, IDocument> evalNonFileBufferDocuments() { + Map<IFile, IDocument> result= new HashMap<IFile, IDocument>(); + IWorkbench workbench= SearchPlugin.getDefault().getWorkbench(); + IWorkbenchWindow[] windows= workbench.getWorkbenchWindows(); + for (int i= 0; i < windows.length; i++) { + IWorkbenchPage[] pages= windows[i].getPages(); + for (int x= 0; x < pages.length; x++) { + IEditorReference[] editorRefs= pages[x].getEditorReferences(); + for (int z= 0; z < editorRefs.length; z++) { + IEditorPart ep= editorRefs[z].getEditor(false); + if (ep instanceof ITextEditor && ep.isDirty()) { // only dirty editors + evaluateTextEditor(result, ep); + } + } + } + } + return result; + } + + private void evaluateTextEditor(Map<IFile, IDocument> result, IEditorPart ep) { + IEditorInput input= ep.getEditorInput(); + if (input instanceof IFileEditorInput) { + IFile file= ((IFileEditorInput) input).getFile(); + if (!result.containsKey(file)) { // take the first editor found + ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager(); + ITextFileBuffer textFileBuffer= bufferManager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); + if (textFileBuffer != null) { + // file buffer has precedence + result.put(file, textFileBuffer.getDocument()); + } else { + // use document provider + IDocument document= ((ITextEditor) ep).getDocumentProvider().getDocument(input); + if (document != null) { + result.put(file, document); + } + } + } + } + } + +} diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/LightSchedulingRule.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/LightSchedulingRule.java new file mode 100644 index 00000000000..fa4b2af7e9f --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/LightSchedulingRule.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2013-2019 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.util; + +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * A scheduling rule that conflicts only with itself and only contains itself. + * <p> + * Note that every new instance of this rule is distinct. Different instances do not conflict + * with eachother (each instance only conflicts with itself. + * + * @author Kris De Volder + */ +public class LightSchedulingRule implements ISchedulingRule { + private final String name; + + /** + * Create a scheduling rule that conflicts only with itself and only contains itself. + * Runnables that want to have a 'light' impact on blocking other jobs + * but still some guarantee that they won't trample over other things that require + * access to some internal shared resource that only they can access can use this + * rule to protect the resource. + */ + public LightSchedulingRule(String name) { + this.name = name; + } + + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + public boolean isConflicting(ISchedulingRule rule) { + return rule == this || rule.contains(this); + } + + public String toString() { + return name; + } +}
\ No newline at end of file diff --git a/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/LineReader.java b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/LineReader.java new file mode 100644 index 00000000000..065076f0ae8 --- /dev/null +++ b/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/LineReader.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; + +/** + * Provides a helper to efficiently split a file into + * lines. Similar to BufferedReader.readline, but it also keeps + * track of character position while reading. This is needed to + * ease translation from line-relative offsets into stream-relative + * offsets. + * + * @author Kris De Volder + */ +public class LineReader { + + private static final int EXPECTED_LINE_LENGTH = 160; + public static final int DEFAULT_MAX_LINE_LENGTH = 1000; + + private BufferedReader input; + + //This simple implementation just wraps a BufferedReader and StringBuilder + //to do the buffering and String building. + //It may be more efficient to implement our own buffering like BufferedReader + //does. + + public LineReader(Reader reader) { + this(reader, DEFAULT_MAX_LINE_LENGTH); + } + + public LineReader(Reader reader, int maxLineLength) { + input = buffered(reader); + MAX_LINE_LENGTH = maxLineLength; + } + + + private StringBuilder line = new StringBuilder(EXPECTED_LINE_LENGTH); + + private final int MAX_LINE_LENGTH; + private int lineOffset = -1; //Start pos of last line read. + private int offset = 0; //position of next char in input. + private int mark = 0; //mark offset in underlying stream + + private BufferedReader buffered(Reader reader) { + //If already buffered don't wrap it again. + if (reader instanceof BufferedReader) { + return (BufferedReader) reader; + } else { + return new BufferedReader(reader); + } + } + + /** + * Close the underlying stream. Does nothing if already closed. + */ + public void close() { + BufferedReader toClose = null; + synchronized (input) { + if (input==null) { + return; + } + toClose = input; + input = null; + } + try { + toClose.close(); + } catch (IOException e) { + //Ignore. + } + } + + public String readLine() throws IOException { + lineOffset = offset; //remember start of line + int maxOffset = offset + MAX_LINE_LENGTH; + //Read text until we see either a CR, CR LF or LF. + int c = read(); + if (c==-1) { + return null; + } + //read until newline + while (c!='\r' && c!='\n' && c!=-1) { + line.append((char)c); + c = read(); + if (offset>maxOffset) { + throw new IOException("Very long lines of text. Minified file?"); + } + } + //Last char read was some kind of line terminator. But only read first char of it. + if (c=='\r') { + mark(); //next char may be part of next line. May need to 'unread' it. + int next = read(); + if (next == '\n') { + //skip + } else { + unread(); + } + } + try { + return line.toString(); + } finally { + line.setLength(0); + } + } + + private void unread() throws IOException { + offset = mark; + input.reset(); + } + + private void mark() throws IOException { + mark = offset; + input.mark(1); + } + + private int read() throws IOException { + try { + offset++; + return input.read(); + } catch (IOException e) { + //pretend errors are like EOF. + return -1; + } + } + + /** + * @return The offset of the start of the last line read relative to beginning of the stream; or -1 if + * no line has been read yet. + */ + public int getLastLineOffset() { + return lineOffset; + } + +} @@ -70,6 +70,9 @@ <module>org.eclipse.ui.genericeditor</module> <module>org.eclipse.ui.genericeditor.tests</module> <module>org.eclipse.ui.genericeditor.examples</module> + <!-- Quick Search --> + <module>org.eclipse.text.quicksearch</module> + <module>org.eclipse.text.quicksearch.tests</module> </modules> </project> |