file structure dialog speed search match highlighting
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / ui / search / SearchUtil.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.ide.ui.search;
18
19 import com.intellij.openapi.options.Configurable;
20 import com.intellij.openapi.options.ConfigurableGroup;
21 import com.intellij.openapi.options.MasterDetails;
22 import com.intellij.openapi.options.SearchableConfigurable;
23 import com.intellij.openapi.options.ex.GlassPanel;
24 import com.intellij.openapi.options.ex.IdeConfigurablesGroup;
25 import com.intellij.openapi.options.ex.ProjectConfigurablesGroup;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.ui.popup.JBPopup;
28 import com.intellij.openapi.ui.popup.JBPopupFactory;
29 import com.intellij.openapi.util.Pair;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.ui.*;
32 import com.intellij.ui.components.JBList;
33 import com.intellij.util.Alarm;
34 import com.intellij.util.Consumer;
35 import com.intellij.util.Processor;
36 import com.intellij.util.containers.ContainerUtil;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40
41 import javax.swing.*;
42 import javax.swing.border.Border;
43 import javax.swing.border.TitledBorder;
44 import java.awt.*;
45 import java.awt.event.ActionEvent;
46 import java.awt.event.ActionListener;
47 import java.awt.event.KeyAdapter;
48 import java.awt.event.KeyEvent;
49 import java.util.*;
50 import java.util.List;
51 import java.util.regex.Matcher;
52 import java.util.regex.Pattern;
53
54 /**
55  * User: anna
56  * Date: 07-Feb-2006
57  */
58 public class SearchUtil {
59   private static final Pattern HTML_PATTERN = Pattern.compile("<[^<>]*>");
60   private static final Pattern QUOTED = Pattern.compile("\\\"([^\\\"]+)\\\"");
61
62   public static final String HIGHLIGHT_WITH_BORDER = "searchUtil.highlightWithBorder";
63
64   private SearchUtil() {
65   }
66
67   public static void processProjectConfigurables(Project project, HashMap<SearchableConfigurable, TreeSet<OptionDescription>> options) {
68     processConfigurables(new ProjectConfigurablesGroup(project).getConfigurables(), options);
69     processConfigurables(new IdeConfigurablesGroup().getConfigurables(), options);
70   }
71
72   private static void processConfigurables(final Configurable[] configurables,
73                                            final HashMap<SearchableConfigurable, TreeSet<OptionDescription>> options) {
74     for (Configurable configurable : configurables) {
75       if (configurable instanceof SearchableConfigurable) {
76         TreeSet<OptionDescription> configurableOptions = new TreeSet<OptionDescription>();
77         options.put((SearchableConfigurable)configurable, configurableOptions);
78         if (configurable instanceof Configurable.Composite) {
79           final Configurable[] children = ((Configurable.Composite)configurable).getConfigurables();
80           processConfigurables(children, options);
81         }
82
83         if (configurable instanceof MasterDetails) {
84           final MasterDetails md = (MasterDetails)configurable;
85           md.initUi();
86           _processComponent(configurable, configurableOptions, md.getMaster());
87           _processComponent(configurable, configurableOptions, md.getDetails().getComponent());
88         }
89         else {
90           _processComponent(configurable, configurableOptions, configurable.createComponent());
91         }
92       }
93     }
94   }
95
96   private static void _processComponent(final Configurable configurable, final TreeSet<OptionDescription> configurableOptions,
97                                         final JComponent component) {
98
99     if (component == null) return;
100
101     processUILabel(configurable.getDisplayName(), configurableOptions, null);
102     processComponent(component, configurableOptions, null);
103   }
104
105   public static void processComponent(final JComponent component, final Set<OptionDescription> configurableOptions, @NonNls String path) {
106     final Border border = component.getBorder();
107     if (border instanceof TitledBorder) {
108       final TitledBorder titledBorder = (TitledBorder)border;
109       final String title = titledBorder.getTitle();
110       if (title != null) {
111         processUILabel(title, configurableOptions, path);
112       }
113     }
114     if (component instanceof JLabel) {
115       final String label = ((JLabel)component).getText();
116       if (label != null) {
117         processUILabel(label, configurableOptions, path);
118       }
119     }
120     else if (component instanceof JCheckBox) {
121       @NonNls final String checkBoxTitle = ((JCheckBox)component).getText();
122       if (checkBoxTitle != null) {
123         processUILabel(checkBoxTitle, configurableOptions, path);
124       }
125     }
126     else if (component instanceof JRadioButton) {
127       @NonNls final String radioButtonTitle = ((JRadioButton)component).getText();
128       if (radioButtonTitle != null) {
129         processUILabel(radioButtonTitle, configurableOptions, path);
130       }
131     }
132     else if (component instanceof JButton) {
133       @NonNls final String buttonTitle = ((JButton)component).getText();
134       if (buttonTitle != null) {
135         processUILabel(buttonTitle, configurableOptions, path);
136       }
137     }
138     if (component instanceof JTabbedPane) {
139       final JTabbedPane tabbedPane = (JTabbedPane)component;
140       final int tabCount = tabbedPane.getTabCount();
141       for (int i = 0; i < tabCount; i++) {
142         final String title = path != null ? path + '.' + tabbedPane.getTitleAt(i) : tabbedPane.getTitleAt(i);
143         processUILabel(title, configurableOptions, title);
144         final Component tabComponent = tabbedPane.getComponentAt(i);
145         if (tabComponent instanceof JComponent) {
146           processComponent((JComponent)tabComponent, configurableOptions, title);
147         }
148       }
149     }
150     else {
151       final Component[] components = component.getComponents();
152       if (components != null) {
153         for (Component child : components) {
154           if (child instanceof JComponent) {
155             processComponent((JComponent)child, configurableOptions, path);
156           }
157         }
158       }
159     }
160   }
161
162   private static void processUILabel(@NonNls final String title, final Set<OptionDescription> configurableOptions, String path) {
163     final Set<String> words = SearchableOptionsRegistrar.getInstance().getProcessedWordsWithoutStemming(title);
164     @NonNls final String regex = "[\\W&&[^\\p{Punct}\\p{Blank}]]";
165     for (String option : words) {
166       configurableOptions.add(new OptionDescription(option, HTML_PATTERN.matcher(title).replaceAll(" ").replaceAll(regex, " "), path));
167     }
168   }
169
170   public static Runnable lightOptions(final SearchableConfigurable configurable,
171                                       final JComponent component,
172                                       final String option,
173                                       final GlassPanel glassPanel) {
174     return new Runnable() {
175       public void run() {
176         if (!SearchUtil.traverseComponentsTree(configurable, glassPanel, component, option, true)) {
177           SearchUtil.traverseComponentsTree(configurable, glassPanel, component, option, false);
178         }
179       }
180     };
181   }
182
183   public static int getSelection(String tabIdx, final JTabbedPane tabbedPane) {
184     SearchableOptionsRegistrar searchableOptionsRegistrar = SearchableOptionsRegistrar.getInstance();
185     for (int i = 0; i < tabbedPane.getTabCount(); i++) {
186       final Set<String> pathWords = searchableOptionsRegistrar.getProcessedWords(tabIdx);
187       final String title = tabbedPane.getTitleAt(i);
188       final Set<String> titleWords = searchableOptionsRegistrar.getProcessedWords(title);
189       pathWords.removeAll(titleWords);
190       if (pathWords.isEmpty()) return i;
191     }
192     return -1;
193   }
194
195   public static int getSelection(String tabIdx, final TabbedPaneWrapper tabbedPane) {
196     SearchableOptionsRegistrar searchableOptionsRegistrar = SearchableOptionsRegistrar.getInstance();
197     for (int i = 0; i < tabbedPane.getTabCount(); i++) {
198       final Set<String> pathWords = searchableOptionsRegistrar.getProcessedWords(tabIdx);
199       final String title = tabbedPane.getTitleAt(i);
200       final Set<String> titleWords = searchableOptionsRegistrar.getProcessedWords(title);
201       pathWords.removeAll(titleWords);
202       if (pathWords.isEmpty()) return i;
203     }
204     return -1;
205   }
206
207   private static boolean traverseComponentsTree(final SearchableConfigurable configurable,
208                                                 GlassPanel glassPanel,
209                                                 JComponent rootComponent,
210                                                 String option,
211                                                 boolean force) {
212
213     rootComponent.putClientProperty(HIGHLIGHT_WITH_BORDER, null);
214
215     if (option == null || option.trim().length() == 0) return false;
216     boolean highlight = false;
217     if (rootComponent instanceof JCheckBox) {
218       final JCheckBox checkBox = ((JCheckBox)rootComponent);
219       if (isComponentHighlighted(checkBox.getText(), option, force, configurable)) {
220         highlight = true;
221         glassPanel.addSpotlight(checkBox);
222       }
223     }
224     else if (rootComponent instanceof JRadioButton) {
225       final JRadioButton radioButton = ((JRadioButton)rootComponent);
226       if (isComponentHighlighted(radioButton.getText(), option, force, configurable)) {
227         highlight = true;
228         glassPanel.addSpotlight(radioButton);
229       }
230     }
231     else if (rootComponent instanceof JLabel) {
232       final JLabel label = ((JLabel)rootComponent);
233       if (isComponentHighlighted(label.getText(), option, force, configurable)) {
234         highlight = true;
235         glassPanel.addSpotlight(label);
236       }
237     }
238     else if (rootComponent instanceof JButton) {
239       final JButton button = ((JButton)rootComponent);
240       if (isComponentHighlighted(button.getText(), option, force, configurable)) {
241         highlight = true;
242         glassPanel.addSpotlight(button);
243       }
244     }
245     else if (rootComponent instanceof JTabbedPane) {
246       final JTabbedPane tabbedPane = (JTabbedPane)rootComponent;
247       final String path = SearchableOptionsRegistrarImpl.getInstance().getInnerPath(configurable, option);
248       if (path != null) {
249         final int index = SearchUtil.getSelection(path, tabbedPane);
250         if (index > -1 && index < tabbedPane.getTabCount()) {
251           tabbedPane.setSelectedIndex(index);
252         }
253       }
254     }
255
256
257     final Component[] components = rootComponent.getComponents();
258     for (Component component : components) {
259       if (component instanceof JComponent) {
260         final boolean innerHighlight = traverseComponentsTree(configurable, glassPanel, (JComponent)component, option, force);
261
262         if (!highlight && !innerHighlight) {
263           final Border border = rootComponent.getBorder();
264           if (border instanceof TitledBorder) {
265             final String title = ((TitledBorder)border).getTitle();
266             if (isComponentHighlighted(title, option, force, configurable)) {
267               highlight = true;
268               glassPanel.addSpotlight(rootComponent);
269               rootComponent.putClientProperty(HIGHLIGHT_WITH_BORDER, Boolean.TRUE);
270             }
271           }
272         }
273
274
275         if (innerHighlight) {
276           highlight = true;
277         }
278       }
279     }
280     return highlight;
281   }
282
283   public static boolean isComponentHighlighted(String text, String option, final boolean force, final SearchableConfigurable configurable) {
284     if (text == null || option == null || option.length() == 0) return false;
285     final SearchableOptionsRegistrar searchableOptionsRegistrar = SearchableOptionsRegistrar.getInstance();
286     final Set<String> words = searchableOptionsRegistrar.getProcessedWords(option);
287     final Set<String> options =
288       configurable != null ? searchableOptionsRegistrar.replaceSynonyms(words, configurable) : words;
289     if (options == null || options.isEmpty()) {
290       return text.toLowerCase().indexOf(option.toLowerCase()) != -1;
291     }
292     final Set<String> tokens = searchableOptionsRegistrar.getProcessedWords(text);
293     if (!force) {
294       options.retainAll(tokens);
295       final boolean highlight = !options.isEmpty();
296       return highlight || text.toLowerCase().indexOf(option.toLowerCase()) != -1;
297     }
298     else {
299       options.removeAll(tokens);
300       return options.isEmpty();
301     }
302   }
303
304   public static Runnable lightOptions(final SearchableConfigurable configurable,
305                                       final JComponent component,
306                                       final String option,
307                                       final GlassPanel glassPanel,
308                                       final boolean forceSelect) {
309     return new Runnable() {
310       public void run() {
311         SearchUtil.traverseComponentsTree(configurable, glassPanel, component, option, forceSelect);
312       }
313     };
314   }
315
316   public static String markup(@NonNls @NotNull String textToMarkup, String filter) {
317     if (filter == null || filter.length() == 0) {
318       return textToMarkup;
319     }
320     final Pattern insideHtmlTagPattern = Pattern.compile("[<[^<>]*>]*<[^<>]*");
321     final SearchableOptionsRegistrar registrar = SearchableOptionsRegistrar.getInstance();
322     final HashSet<String> quoted = new HashSet<String>();
323     filter = processFilter(quoteStrictOccurences(textToMarkup, filter), quoted);
324     final Set<String> options = registrar.getProcessedWords(filter);
325     final Set<String> words = registrar.getProcessedWords(textToMarkup);
326     for (String option : options) {
327       if (words.contains(option)) {
328         textToMarkup = markup(textToMarkup, insideHtmlTagPattern, option);
329       }
330     }
331     for (String stripped : quoted) {
332       textToMarkup = markup(textToMarkup, insideHtmlTagPattern, stripped);
333     }
334     return textToMarkup;
335   }
336
337   private static String quoteStrictOccurences(final String textToMarkup, final String filter) {
338     String cur = "";
339     final String s = textToMarkup.toLowerCase();
340     for (String part : filter.split(" ")) {
341       if (s.indexOf(part) != -1) {
342         cur += "\"" + part + "\" ";
343       }
344       else {
345         cur += part + " ";
346       }
347     }
348     return cur;
349   }
350
351   private static String markup(@NonNls String textToMarkup, final Pattern insideHtmlTagPattern, final String option) {
352     @NonNls String result = "";
353     int beg = 0;
354     int idx;
355     while ((idx = StringUtil.indexOfIgnoreCase(textToMarkup, option, beg)) != -1) {
356       final String prefix = textToMarkup.substring(beg, idx);
357       final String toMark = textToMarkup.substring(idx, idx + option.length());
358       if (insideHtmlTagPattern.matcher(prefix).matches()) {
359         result += prefix + toMark;
360       }
361       else {
362         result += prefix + "<font color='#ffffff' bgColor='#1d5da7'>" + toMark + "</font>";
363       }
364       beg = idx + option.length();
365     }
366     result += textToMarkup.substring(beg);
367     return result;
368   }
369
370   public static void appendFragmentsStrict(@NonNls final String text, @NotNull final List<Pair<String, Integer>> toHighlight,
371                                            final int style, final Color foreground,
372                                            final Color background, final SimpleColoredComponent c) {
373     if (text == null) return;
374     final SimpleTextAttributes plainAttributes = new SimpleTextAttributes(style, foreground);
375
376     final int[] lastOffset = {0};
377     ContainerUtil.process(toHighlight, new Processor<Pair<String, Integer>>() {
378       @Override
379       public boolean process(Pair<String, Integer> pair) {
380         if (pair.second > lastOffset[0]) {
381           c.append(text.substring(lastOffset[0], pair.second), new SimpleTextAttributes(style, foreground));
382         }
383
384         c.append(text.substring(pair.second, pair.second + pair.first.length()), new SimpleTextAttributes(background,
385                                                                                                           foreground, null,
386                                                                                                           style |
387                                                                                                           SimpleTextAttributes.STYLE_SEARCH_MATCH));
388         lastOffset[0] = pair.second + pair.first.length();
389         return true;
390       }
391     });
392
393     if (lastOffset[0] < text.length()) {
394       c.append(text.substring(lastOffset[0]), plainAttributes);
395     }
396   }
397
398   public static void appendFragments(String filter,
399                                      @NonNls String text,
400                                      final int style,
401                                      final Color foreground,
402                                      final Color background,
403                                      final SimpleColoredComponent textRenderer) {
404     if (text == null) return;
405     if (filter == null || filter.length() == 0) {
406       textRenderer.append(text, new SimpleTextAttributes(style, foreground));
407     }
408     else { //markup
409       final HashSet<String> quoted = new HashSet<String>();
410       filter = processFilter(quoteStrictOccurences(text, filter), quoted);
411       final TreeMap<Integer, String> indx = new TreeMap<Integer, String>();
412       for (String stripped : quoted) {
413         int beg = 0;
414         int idx;
415         while ((idx = StringUtil.indexOfIgnoreCase(text, stripped, beg)) != -1) {
416           indx.put(idx, text.substring(idx, idx + stripped.length()));
417           beg = idx + stripped.length();
418         }
419       }
420
421       final List<String> selectedWords = new ArrayList<String>();
422       int pos = 0;
423       for (Integer index : indx.keySet()) {
424         final String stripped = indx.get(index);
425         final int start = index.intValue();
426         if (pos > start) {
427           final String highlighted = selectedWords.get(selectedWords.size() - 1);
428           if (highlighted.length() < stripped.length()){
429             selectedWords.remove(highlighted);
430           } else {
431             continue;
432           }
433         }
434         appendSelectedWords(text, selectedWords, pos, start, filter);
435         selectedWords.add(stripped);
436         pos = start + stripped.length();
437       }
438       appendSelectedWords(text, selectedWords, pos, text.length(), filter);
439
440       int idx = 0;
441       for (String word : selectedWords) {
442         text = text.substring(idx);
443         final String before = text.substring(0, text.indexOf(word));
444         if (before.length() > 0) textRenderer.append(before, new SimpleTextAttributes(background, foreground, null, style));
445         idx = text.indexOf(word) + word.length();
446         textRenderer.append(text.substring(idx - word.length(), idx), new SimpleTextAttributes(background,
447                                                                                                foreground, null,
448                                                                                                style |
449                                                                                                SimpleTextAttributes.STYLE_SEARCH_MATCH));
450       }
451       final String after = text.substring(idx, text.length());
452       if (after.length() > 0) textRenderer.append(after, new SimpleTextAttributes(background, foreground, null, style));
453     }
454   }
455
456   private static void appendSelectedWords(final String text,
457                                           final List<String> selectedWords,
458                                           final int pos,
459                                           int end,
460                                           final String filter) {
461     if (pos < end) {
462       final Set<String> filters = SearchableOptionsRegistrar.getInstance().getProcessedWords(filter);
463       final String[] words = text.substring(pos, end).split("[\\W&&[^_-]]");
464       for (String word : words) {
465         if (filters.contains(PorterStemmerUtil.stem(word.toLowerCase()))) {
466           selectedWords.add(word);
467         }
468       }
469     }
470   }
471
472   @Nullable
473   private static JBPopup createPopup(final ConfigurableSearchTextField searchField,
474                                      final JBPopup[] activePopup,
475                                      final Alarm showHintAlarm,
476                                      final Consumer<String> selectConfigurable,
477                                      final Project project,
478                                      final int down) {
479
480     final String filter = searchField.getText();
481     if (filter == null || filter.length() == 0) return null;
482     final Map<String, Set<String>> hints = SearchableOptionsRegistrar.getInstance().findPossibleExtension(filter, project);
483     final DefaultListModel model = new DefaultListModel();
484     final JList list = new JBList(model);
485     for (String groupName : hints.keySet()) {
486       model.addElement(groupName);
487       final Set<String> descriptions = hints.get(groupName);
488       if (descriptions != null) {
489         for (String hit : descriptions) {
490           if (hit == null) continue;
491           model.addElement(new OptionDescription(null, groupName, hit, null));
492         }
493       }
494     }
495     ListScrollingUtil.installActions(list);
496     list.setCellRenderer(new DefaultListCellRenderer() {
497       public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
498         final Component rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
499         if (value instanceof String) {
500           setText("------ " + value + " ------");
501         }
502         else if (value instanceof OptionDescription) {
503           setText(((OptionDescription)value).getHit());
504         }
505         return rendererComponent;
506       }
507     });
508
509
510     if (model.size() > 0) {
511       final Runnable onChosen = new Runnable() {
512         public void run() {
513           final Object selectedValue = list.getSelectedValue();
514           if (selectedValue instanceof OptionDescription) {
515             final OptionDescription description = ((OptionDescription)selectedValue);
516             searchField.setText(description.getHit());
517             searchField.addCurrentTextToHistory();
518             SwingUtilities.invokeLater(new Runnable() {
519               public void run() {     //do not show look up again
520                 showHintAlarm.cancelAllRequests();
521                 selectConfigurable.consume(description.getConfigurableId());
522               }
523             });
524           }
525         }
526       };
527       final JBPopup popup = JBPopupFactory.getInstance()
528         .createListPopupBuilder(list)
529         .setItemChoosenCallback(onChosen)
530         .setRequestFocus(down != 0)
531         .createPopup();
532       list.addKeyListener(new KeyAdapter() {
533         public void keyPressed(final KeyEvent e) {
534           if (e.getKeyCode() != KeyEvent.VK_ENTER && e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_DOWN &&
535               e.getKeyCode() != KeyEvent.VK_PAGE_UP && e.getKeyCode() != KeyEvent.VK_PAGE_DOWN) {
536             searchField.requestFocusInWindow();
537             if (cancelPopups(activePopup) && e.getKeyCode() == KeyEvent.VK_ESCAPE) {
538               return;
539             }
540             if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
541               searchField.process(
542                 new KeyEvent(searchField, KeyEvent.KEY_TYPED, e.getWhen(), e.getModifiers(), KeyEvent.VK_UNDEFINED, e.getKeyChar()));
543             }
544           }
545         }
546
547       });
548       if (down > 0) {
549         if (list.getSelectedIndex() < list.getModel().getSize() - 1) {
550           list.setSelectedIndex(list.getSelectedIndex() + 1);
551         }
552       }
553       else if (down < 0) {
554         if (list.getSelectedIndex() > 0) {
555           list.setSelectedIndex(list.getSelectedIndex() - 1);
556         }
557       }
558       return popup;
559     }
560     return null;
561   }
562
563   public static void showHintPopup(final ConfigurableSearchTextField searchField,
564                                    final JBPopup[] activePopup,
565                                    final Alarm showHintAlarm,
566                                    final Consumer<String> selectConfigurable,
567                                    final Project project) {
568     for (JBPopup aPopup : activePopup) {
569       if (aPopup != null) {
570         aPopup.cancel();
571       }
572     }
573
574     final JBPopup popup = createPopup(searchField, activePopup, showHintAlarm, selectConfigurable, project, 0); //no selection
575     if (popup != null) {
576       popup.showUnderneathOf(searchField);
577       searchField.requestFocusInWindow();
578     }
579
580     activePopup[0] = popup;
581     activePopup[1] = null;
582   }
583
584
585   public static void registerKeyboardNavigation(final ConfigurableSearchTextField searchField,
586                                                 final JBPopup[] activePopup,
587                                                 final Alarm showHintAlarm,
588                                                 final Consumer<String> selectConfigurable,
589                                                 final Project project) {
590     final Consumer<Integer> shower = new Consumer<Integer>() {
591       public void consume(final Integer direction) {
592         if (activePopup[0] != null) {
593           activePopup[0].cancel();
594         }
595
596         if (activePopup[1] != null && activePopup[1].isVisible()) {
597           return;
598         }
599
600         final JBPopup popup = createPopup(searchField, activePopup, showHintAlarm, selectConfigurable, project, direction.intValue());
601         if (popup != null) {
602           popup.showUnderneathOf(searchField);
603         }
604         activePopup[0] = null;
605         activePopup[1] = popup;
606       }
607     };
608     searchField.registerKeyboardAction(new ActionListener() {
609       public void actionPerformed(ActionEvent e) {
610         shower.consume(1);
611       }
612     }, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
613     searchField.registerKeyboardAction(new ActionListener() {
614       public void actionPerformed(ActionEvent e) {
615         shower.consume(-1);
616       }
617     }, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
618
619     searchField.addKeyboardListener(new KeyAdapter() {
620       public void keyPressed(KeyEvent e) {
621         if (e.getKeyCode() == KeyEvent.VK_ESCAPE && searchField.getText().length() > 0) {
622           e.consume();
623           if (cancelPopups(activePopup)) return;
624           searchField.setText("");
625         }
626         else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
627           searchField.addCurrentTextToHistory();
628           cancelPopups(activePopup);
629           if (e.getModifiers() == 0) {
630             e.consume();
631           }
632         }
633       }
634     });
635   }
636
637   private static boolean cancelPopups(final JBPopup[] activePopup) {
638     for (JBPopup popup : activePopup) {
639       if (popup != null && popup.isVisible()) {
640         popup.cancel();
641         return true;
642       }
643     }
644     return false;
645   }
646
647   public static List<Set<String>> findKeys(String filter, Set<String> quoted) {
648     filter = processFilter(filter.toLowerCase(), quoted);
649     final List<Set<String>> keySetList = new ArrayList<Set<String>>();
650     final SearchableOptionsRegistrar optionsRegistrar = SearchableOptionsRegistrar.getInstance();
651     final Set<String> words = optionsRegistrar.getProcessedWords(filter);
652     for (String word : words) {
653       final Set<OptionDescription> descriptions = ((SearchableOptionsRegistrarImpl)optionsRegistrar).getAcceptableDescriptions(word);
654       Set<String> keySet = new HashSet<String>();
655       if (descriptions != null) {
656         for (OptionDescription description : descriptions) {
657           keySet.add(description.getPath());
658         }
659       }
660       keySetList.add(keySet);
661     }
662     return keySetList;
663   }
664
665   public static String processFilter(String filter, Set<String> quoted) {
666     String withoutQuoted = "";
667     int beg = 0;
668     final Matcher matcher = QUOTED.matcher(filter);
669     while (matcher.find()) {
670       final int start = matcher.start(1);
671       withoutQuoted += " " + filter.substring(beg, start);
672       beg = matcher.end(1);
673       final String trimmed = filter.substring(start, beg).trim();
674       if (trimmed.length() > 0) {
675         quoted.add(trimmed);
676       }
677     }
678     return withoutQuoted + " " + filter.substring(beg);
679   }
680
681   //to process event
682   public static class ConfigurableSearchTextField extends SearchTextFieldWithStoredHistory {
683     public ConfigurableSearchTextField() {
684       super("ALL_CONFIGURABLES_PANEL_SEARCH_HISTORY");
685     }
686
687     public void process(final KeyEvent e) {
688       ((TextFieldWithProcessing)getTextEditor()).processKeyEvent(e);
689     }
690   }
691
692   public static List<Configurable> expand(ConfigurableGroup[] groups) {
693     final ArrayList<Configurable> result = new ArrayList<Configurable>();
694     for (ConfigurableGroup eachGroup : groups) {
695       result.addAll(expandGroup(eachGroup));
696     }
697     return result;
698   }
699
700   public static List<Configurable> expandGroup(final ConfigurableGroup group) {
701     final Configurable[] configurables = group.getConfigurables();
702     ArrayList<Configurable> result = new ArrayList<Configurable>();
703     ContainerUtil.addAll(result, configurables);
704     for (Configurable each : configurables) {
705       addChildren(each, result);
706     }
707     return result;
708   }
709
710   private static void addChildren(Configurable configurable, ArrayList<Configurable> list) {
711     if (configurable instanceof Configurable.Composite) {
712       final Configurable[] kids = ((Configurable.Composite)configurable).getConfigurables();
713       for (Configurable eachKid : kids) {
714         list.add(eachKid);
715         addChildren(eachKid, list);
716       }
717     }
718   }
719
720 }