IDEA-80690 Live Template with matched prefix should be at top of popup window
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / ListTemplatesHandler.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.codeInsight.template.impl;
18
19 import com.intellij.codeInsight.CodeInsightActionHandler;
20 import com.intellij.codeInsight.CodeInsightBundle;
21 import com.intellij.codeInsight.completion.PlainPrefixMatcher;
22 import com.intellij.codeInsight.hint.HintManager;
23 import com.intellij.codeInsight.lookup.*;
24 import com.intellij.codeInsight.lookup.impl.LookupImpl;
25 import com.intellij.codeInsight.template.TemplateManager;
26 import com.intellij.featureStatistics.FeatureUsageTracker;
27 import com.intellij.openapi.application.Result;
28 import com.intellij.openapi.command.WriteCommandAction;
29 import com.intellij.openapi.editor.Document;
30 import com.intellij.openapi.editor.Editor;
31 import com.intellij.openapi.editor.ex.util.EditorUtil;
32 import com.intellij.openapi.fileEditor.FileDocumentManager;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.psi.PsiDocumentManager;
35 import com.intellij.psi.PsiFile;
36 import com.intellij.util.containers.CollectionFactory;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import java.util.*;
41
42 public class ListTemplatesHandler implements CodeInsightActionHandler {
43   public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile file) {
44     if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
45       return;
46     }
47     EditorUtil.fillVirtualSpaceUntilCaret(editor);
48
49     PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
50     int offset = editor.getCaretModel().getOffset();
51     String prefix = getPrefix(editor.getDocument(), offset);
52
53     List<TemplateImpl> matchingTemplates = new ArrayList<TemplateImpl>();
54     ArrayList<TemplateImpl> applicableTemplates = SurroundWithTemplateHandler.getApplicableTemplates(editor, file, false);
55     for (TemplateImpl template : applicableTemplates) {
56       if (template.getKey().startsWith(prefix)) {
57         matchingTemplates.add(template);
58       }
59     }
60
61     if (matchingTemplates.isEmpty()) {
62       matchingTemplates.addAll(applicableTemplates);
63       prefix = "";
64     }
65
66     if (matchingTemplates.size() == 0) {
67       String text = prefix.length() == 0
68                     ? CodeInsightBundle.message("templates.no.defined")
69                     : CodeInsightBundle.message("templates.no.defined.with.prefix", prefix);
70       HintManager.getInstance().showErrorHint(editor, text);
71       return;
72     }
73
74     Collections.sort(matchingTemplates, TemplateListPanel.TEMPLATE_COMPARATOR);
75     showTemplatesLookup(project, editor, prefix, matchingTemplates);
76   }
77
78   public static void showTemplatesLookup(final Project project, final Editor editor,
79                                          @NotNull String prefix, List<TemplateImpl> matchingTemplates) {
80
81     final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, LookupElement.EMPTY_ARRAY, prefix, LookupArranger.DEFAULT);
82     lookup.setArranger(new LookupArranger() {
83       /*
84       @Override
85       public Comparator<LookupElement> getItemComparator() {
86         return new Comparator<LookupElement>() {
87           @Override
88           public int compare(LookupElement o1, LookupElement o2) {
89             return o1.getLookupString().compareToIgnoreCase(o2.getLookupString());
90           }
91         };
92       }
93       */
94
95       @Override
96       public Classifier<LookupElement> createRelevanceClassifier() {
97         return new ComparingClassifier<LookupElement>(ClassifierFactory.<LookupElement>listClassifier(), "preferPrefix") {
98           @NotNull
99           @Override
100           public Comparable getWeight(LookupElement element) {
101             return !element.getLookupString().startsWith(lookup.itemPattern(element));
102           }
103         };
104       }
105     });
106     for (TemplateImpl template : matchingTemplates) {
107       lookup.addItem(createTemplateElement(template), new PlainPrefixMatcher(prefix));
108     }
109     
110     showLookup(lookup, null);
111   }
112
113   private static LiveTemplateLookupElement createTemplateElement(final TemplateImpl template) {
114     return new LiveTemplateLookupElement(template, false) {
115       @Override
116       public Set<String> getAllLookupStrings() {
117         String description = template.getDescription();
118         if (description == null) {
119           return super.getAllLookupStrings();
120         }
121         return CollectionFactory.newSet(getLookupString(), description);
122       }
123     };
124   }
125
126   private static String computePrefix(TemplateImpl template, String argument) {
127     String key = template.getKey();
128     if (argument == null) {
129       return key;
130     }
131     if (key.length() > 0 && Character.isJavaIdentifierPart(key.charAt(key.length() - 1))) {
132       return key + ' ' + argument;
133     }
134     return key + argument;
135   }
136
137   public static void showTemplatesLookup(final Project project, final Editor editor, Map<TemplateImpl, String> template2Argument) {
138     final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, LookupElement.EMPTY_ARRAY, "", LookupArranger.DEFAULT);
139     for (TemplateImpl template : template2Argument.keySet()) {
140       String prefix = computePrefix(template, template2Argument.get(template));
141       lookup.addItem(createTemplateElement(template), new PlainPrefixMatcher(prefix));
142     }
143
144     showLookup(lookup, template2Argument);
145   }
146
147   private static void showLookup(LookupImpl lookup, @Nullable Map<TemplateImpl, String> template2Argument) {
148     Editor editor = lookup.getEditor();
149     Project project = editor.getProject();
150     lookup.addLookupListener(new MyLookupAdapter(project, editor, template2Argument));
151     lookup.refreshUi(false);
152     lookup.showLookup();
153   }
154
155   public boolean startInWriteAction() {
156     return true;
157   }
158
159   public static String getPrefix(Document document, int offset) {
160     CharSequence chars = document.getCharsSequence();
161     int start = offset;
162     while (true) {
163       if (start == 0) break;
164       char c = chars.charAt(start - 1);
165       if (!isInPrefix(c)) break;
166       start--;
167     }
168     return chars.subSequence(start, offset).toString();
169   }
170
171   private static boolean isInPrefix(final char c) {
172     return Character.isJavaIdentifierPart(c) || c == '.';
173   }
174
175   private static class MyLookupAdapter extends LookupAdapter {
176     private final Project myProject;
177     private final Editor myEditor;
178     private final Map<TemplateImpl, String> myTemplate2Argument;
179
180     public MyLookupAdapter(Project project, Editor editor, Map<TemplateImpl, String> template2Argument) {
181       myProject = project;
182       myEditor = editor;
183       myTemplate2Argument = template2Argument;
184     }
185
186     public void itemSelected(LookupEvent event) {
187       FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.liveTemplates");
188       LookupElement item = event.getItem();
189       if (item instanceof LiveTemplateLookupElement) {
190         final TemplateImpl template = ((LiveTemplateLookupElement)item).getTemplate();
191         final String argument = myTemplate2Argument != null ? myTemplate2Argument.get(template) : null;
192         new WriteCommandAction(myProject) {
193           protected void run(Result result) throws Throwable {
194             ((TemplateManagerImpl)TemplateManager.getInstance(myProject)).startTemplateWithPrefix(myEditor, template, null, argument);
195           }
196         }.execute();
197       }
198     }
199   }
200 }