an API to contribute searchable options from plugins

This commit is contained in:
peter
2015-01-12 20:07:51 +01:00
parent bf3d8bcd86
commit 404c80311c
10 changed files with 176 additions and 50 deletions
@@ -25,9 +25,7 @@ package com.intellij.codeInspection.ex;
import com.intellij.codeInspection.*;
import com.intellij.icons.AllIcons;
import com.intellij.ide.impl.ContentManagerWatcher;
import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
import com.intellij.lang.Language;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NotNullLazyValue;
@@ -35,30 +33,23 @@ import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.profile.codeInspection.ui.header.InspectionToolsConfigurable;
import com.intellij.psi.PsiElement;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.TabbedPaneContentUI;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
public class InspectionManagerEx extends InspectionManagerBase {
private static final Pattern HTML_PATTERN = Pattern.compile("<[^<>]*>");
private final NotNullLazyValue<ContentManager> myContentManager;
private final Set<GlobalInspectionContextImpl> myRunningContexts = new HashSet<GlobalInspectionContextImpl>();
private final AtomicBoolean myToolsAreInitialized = new AtomicBoolean(false);
private GlobalInspectionContextImpl myGlobalInspectionContext;
public InspectionManagerEx(final Project project) {
super(project);
if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
@@ -114,15 +105,6 @@ public class InspectionManagerEx extends InspectionManagerBase {
});
}
private static void processText(@NotNull @NonNls String descriptionText,
@NotNull InspectionToolWrapper tool,
@NotNull SearchableOptionsRegistrar myOptionsRegistrar) {
if (ApplicationManager.getApplication().isDisposed()) return;
final Set<String> words = myOptionsRegistrar.getProcessedWordsWithoutStemming(descriptionText);
for (String word : words) {
myOptionsRegistrar.addOption(word, tool.getShortName(), tool.getDisplayName(), InspectionToolsConfigurable.ID, InspectionToolsConfigurable.DISPLAY_NAME);
}
}
@NotNull
public ProblemDescriptor createProblemDescriptor(@NotNull final PsiElement psiElement,
@@ -182,28 +164,4 @@ public class InspectionManagerEx extends InspectionManagerBase {
return myContentManager;
}
public void buildInspectionSearchIndexIfNecessary() {
if (!myToolsAreInitialized.getAndSet(true)) {
final SearchableOptionsRegistrar myOptionsRegistrar = SearchableOptionsRegistrar.getInstance();
final InspectionToolRegistrar toolRegistrar = InspectionToolRegistrar.getInstance();
final Application app = ApplicationManager.getApplication();
if (app.isUnitTestMode() || app.isHeadlessEnvironment()) return;
app.executeOnPooledThread(new Runnable(){
@Override
public void run() {
List<InspectionToolWrapper> tools = toolRegistrar.createTools();
for (InspectionToolWrapper toolWrapper : tools) {
processText(toolWrapper.getDisplayName().toLowerCase(), toolWrapper, myOptionsRegistrar);
final String description = toolWrapper.loadDescription();
if (description != null) {
@NonNls String descriptionText = HTML_PATTERN.matcher(description).replaceAll(" ");
processText(descriptionText, toolWrapper, myOptionsRegistrar);
}
}
}
});
}
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.ex;
import com.intellij.ide.ui.search.SearchableOptionContributor;
import com.intellij.ide.ui.search.SearchableOptionProcessor;
import com.intellij.profile.codeInspection.ui.header.InspectionToolsConfigurable;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Pattern;
/**
* @author peter
*/
public class InspectionSearchableOptionContributor extends SearchableOptionContributor {
private static final Pattern HTML_PATTERN = Pattern.compile("<[^<>]*>");
@Override
public void processOptions(@NotNull SearchableOptionProcessor processor) {
for (InspectionToolWrapper toolWrapper : InspectionToolRegistrar.getInstance().createTools()) {
String hit = toolWrapper.getDisplayName();
processor.addOptions(toolWrapper.getDisplayName(), toolWrapper.getShortName(), hit,
InspectionToolsConfigurable.ID,
InspectionToolsConfigurable.DISPLAY_NAME, false);
final String description = toolWrapper.loadDescription();
if (description != null) {
@NonNls String descriptionText = HTML_PATTERN.matcher(description).replaceAll(" ");
processor.addOptions(descriptionText, toolWrapper.getShortName(), hit, InspectionToolsConfigurable.ID,
InspectionToolsConfigurable.DISPLAY_NAME, false);
}
}
}
}
@@ -24,9 +24,7 @@ package com.intellij.profile.codeInspection.ui.header;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.ModifiableModel;
import com.intellij.codeInspection.ex.InspectionManagerEx;
import com.intellij.codeInspection.ex.InspectionProfileImpl;
import com.intellij.codeInspection.ex.InspectionToolRegistrar;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
@@ -57,13 +55,10 @@ import com.intellij.profile.codeInspection.InspectionProfileManager;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.profile.codeInspection.ui.ErrorsConfigurable;
import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel;
import com.intellij.ui.JBColor;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import org.jdom.Document;
import org.jdom.Element;
@@ -388,8 +383,6 @@ public abstract class InspectionToolsConfigurable extends BaseConfigurable
toolbar.add(myAuxiliaryRightPanel.getHintLabel(), new GridBagConstraints(3, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.VERTICAL, new Insets(0, 15, 6, 0), 0, 0));
toolbar.add(myAuxiliaryRightPanel, new GridBagConstraints(3, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 15, 0, 0), 0, 0));
((InspectionManagerEx)InspectionManager.getInstance(projectProfileManager.getProject())).buildInspectionSearchIndexIfNecessary();
myProjectProfileManager = projectProfileManager;
myProfileManager = profileManager;
}
@@ -0,0 +1,31 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide.ui.search;
import com.intellij.openapi.extensions.ExtensionPointName;
import org.jetbrains.annotations.NotNull;
/**
* An extension allowing plugins to provide the data at runtime for the setting search to work on.
*
* @author peter
*/
public abstract class SearchableOptionContributor {
public static final ExtensionPointName<SearchableOptionContributor> EP_NAME = ExtensionPointName.create("com.intellij.search.optionContributor");
public abstract void processOptions(@NotNull SearchableOptionProcessor processor);
}
@@ -0,0 +1,51 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide.ui.search;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.SearchableConfigurable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/**
* A place for {@link SearchableOptionContributor} implementations to feed the searchable options to.
*
* @author peter
*/
public abstract class SearchableOptionProcessor {
/**
* Take text that can be found on a setting page, split it into words and add them to the internal setting search index.
*
* @param text the text that appears on a setting page and can be searched for
* @param path for complex settings pages, identifies the subpage where the option is to be found.
* For example, it can be the name of tab on the settings page that should be opened when showing search results.
* Can be null for simple configurables.
* @param hit the string that's presented to the user when showing found results in a list, e.g. in Goto Action.
* @param configurableId the id of the topmost configurable containing the search result. See {@link SearchableConfigurable#getId()}
* @param configurableDisplayName display name of the configurable containing the search result
* @param applyStemming whether only word stems should be indexed or the full words. Porter stemmer is used.
*/
public abstract void addOptions(@NotNull String text,
@Nullable String path,
@Nullable String hit,
@NotNull final String configurableId,
@Nullable final String configurableDisplayName,
boolean applyStemming);
}
@@ -16,6 +16,7 @@
package com.intellij.openapi.options;
import com.intellij.ide.ui.search.SearchableOptionContributor;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -26,11 +27,18 @@ import javax.swing.JComponent;
/**
* SearchableConfigurable instances would be instantiated on buildSearchableOptions step during Installer's build to index of all available options.
* {@link #com.intellij.ide.ui.search.TraverseUIStarter}
*
* @see SearchableOptionContributor
*/
public interface SearchableConfigurable extends Configurable {
@NotNull
@NonNls String getId();
/**
* @param option setting search query
* @return an action to perform when this configurable is opened when a search filter query is entered by the user in setting dialog.
* This action, for example, can select something in a tree or a list embedded in this setting page that matches the query.
*/
@Nullable Runnable enableSearch(String option);
interface Parent extends SearchableConfigurable, Composite {
@@ -87,6 +87,36 @@ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar {
catch (IOException e) {
LOG.error(e);
}
loadExtensions();
}
private void loadExtensions() {
final SearchableOptionProcessor processor = new SearchableOptionProcessor() {
@Override
public void addOptions(@NotNull String text,
@Nullable String path,
@Nullable String hit,
@NotNull String configurableId,
@Nullable String configurableDisplayName,
boolean applyStemming) {
Set<String> words = applyStemming ? getProcessedWords(text) : getProcessedWordsWithoutStemming(text);
for (String word : words) {
addOption(word, path, hit, configurableId, configurableDisplayName);
}
}
};
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
//todo what if application is disposed?
for (SearchableOptionContributor contributor : SearchableOptionContributor.EP_NAME.getExtensions()) {
contributor.processOptions(processor);
}
}
});
}
private void loadHugeFilesIfNecessary() {
@@ -180,7 +210,7 @@ public class SearchableOptionsRegistrarImpl extends SearchableOptionsRegistrar {
}
}
private synchronized void putOptionWithHelpId(String option, final String id, final String groupName, String hit, final String path) {
private synchronized void putOptionWithHelpId(@NotNull String option, @NotNull final String id, @Nullable final String groupName, @Nullable String hit, @Nullable final String path) {
if (isStopWord(option)) return;
String stopWord = PorterStemmerUtil.stem(option);
if (stopWord == null) return;
@@ -23,6 +23,7 @@ import com.intellij.ide.DataManager;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
import com.intellij.idea.IdeaLogger;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
@@ -1279,6 +1280,7 @@ public final class ActionManagerImpl extends ActionManagerEx implements Applicat
@Override
public void run() {
try {
SearchableOptionsRegistrar.getInstance(); // load inspection descriptions etc. to be used in Goto Action, Search Everywhere
doPreloadActions();
} catch (RuntimeInterruptedException ignore) {
}
@@ -891,6 +891,8 @@
<previewPanelProvider implementation="com.intellij.find.UsagesPreviewPanelProvider"/>
<projectService serviceInterface="com.intellij.openapi.preview.PreviewManager"
serviceImplementation="com.intellij.openapi.preview.impl.PreviewManagerImpl"/>
<search.optionContributor implementation="com.intellij.codeInspection.ex.InspectionSearchableOptionContributor"/>
<fileIndentOptionsProvider implementation="com.intellij.psi.codeStyle.autodetect.DetectableIndentOptionsProvider" order="last"/>
</extensions>
@@ -217,6 +217,8 @@
<extensionPoint name="search.topHitProvider" interface="com.intellij.ide.SearchTopHitProvider"/>
<extensionPoint name="search.optionContributor" interface="com.intellij.ide.ui.search.SearchableOptionContributor"/>
<extensionPoint name="schemeImporter" beanClass="com.intellij.openapi.options.SchemeImporterEP">
<with attribute="schemeClass" implements="com.intellij.openapi.options.Scheme"/>
<with attribute="implementationClass" implements="com.intellij.openapi.options.SchemeImporter"/>