95f1883f5f554e83214b3d15be15e964a913eb07
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateSettings.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.codeInsight.template.impl;
17
18 import com.intellij.AbstractBundle;
19 import com.intellij.codeInsight.template.Template;
20 import com.intellij.openapi.application.ex.DecodeDefaultsUtil;
21 import com.intellij.openapi.components.PersistentStateComponent;
22 import com.intellij.openapi.components.ServiceManager;
23 import com.intellij.openapi.components.State;
24 import com.intellij.openapi.components.Storage;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.options.BaseSchemeProcessor;
27 import com.intellij.openapi.options.SchemeManager;
28 import com.intellij.openapi.options.SchemeManagerFactory;
29 import com.intellij.openapi.options.SchemeState;
30 import com.intellij.openapi.util.Comparing;
31 import com.intellij.openapi.util.InvalidDataException;
32 import com.intellij.openapi.util.JDOMUtil;
33 import com.intellij.openapi.util.WriteExternalException;
34 import com.intellij.util.JdomKt;
35 import com.intellij.util.SmartList;
36 import com.intellij.util.containers.MultiMap;
37 import com.intellij.util.xmlb.Converter;
38 import com.intellij.util.xmlb.annotations.OptionTag;
39 import org.jdom.Element;
40 import org.jdom.JDOMException;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.util.*;
48
49 @State(
50   name = "TemplateSettings",
51   storages = {
52     @Storage("templates.xml"),
53     @Storage(value = "other.xml", deprecated = true)
54   },
55   additionalExportFile = TemplateSettings.TEMPLATES_DIR_PATH
56 )
57 public class TemplateSettings implements PersistentStateComponent<TemplateSettings.State> {
58   private static final Logger LOG = Logger.getInstance(TemplateSettings.class);
59
60   @NonNls public static final String USER_GROUP_NAME = "user";
61   @NonNls private static final String TEMPLATE_SET = "templateSet";
62   @NonNls private static final String GROUP = "group";
63   @NonNls static final String TEMPLATE = "template";
64
65   public static final char SPACE_CHAR = ' ';
66   public static final char TAB_CHAR = '\t';
67   public static final char ENTER_CHAR = '\n';
68   public static final char DEFAULT_CHAR = 'D';
69   public static final char CUSTOM_CHAR = 'C';
70
71   @NonNls private static final String SPACE = "SPACE";
72   @NonNls private static final String TAB = "TAB";
73   @NonNls private static final String ENTER = "ENTER";
74   @NonNls private static final String CUSTOM = "CUSTOM";
75
76   @NonNls private static final String NAME = "name";
77   @NonNls private static final String VALUE = "value";
78   @NonNls private static final String DESCRIPTION = "description";
79   @NonNls private static final String SHORTCUT = "shortcut";
80
81   @NonNls private static final String VARIABLE = "variable";
82   @NonNls private static final String EXPRESSION = "expression";
83   @NonNls private static final String DEFAULT_VALUE = "defaultValue";
84   @NonNls private static final String ALWAYS_STOP_AT = "alwaysStopAt";
85
86   @NonNls private static final String CONTEXT = "context";
87   @NonNls private static final String TO_REFORMAT = "toReformat";
88   @NonNls private static final String TO_SHORTEN_FQ_NAMES = "toShortenFQNames";
89   @NonNls private static final String USE_STATIC_IMPORT = "useStaticImport";
90
91   @NonNls private static final String DEACTIVATED = "deactivated";
92
93   @NonNls private static final String RESOURCE_BUNDLE = "resource-bundle";
94   @NonNls private static final String KEY = "key";
95   @NonNls private static final String ID = "id";
96
97   static final String TEMPLATES_DIR_PATH = "templates";
98
99   private final MultiMap<String, TemplateImpl> myTemplates = MultiMap.createLinked();
100
101   private final Map<String, Template> myTemplatesById = new LinkedHashMap<>();
102   private final Map<TemplateKey, TemplateImpl> myDefaultTemplates = new LinkedHashMap<>();
103
104   private int myMaxKeyLength = 0;
105   private final SchemeManager<TemplateGroup> mySchemeManager;
106
107   private State myState = new State();
108
109   static final class ShortcutConverter extends Converter<Character> {
110     @Nullable
111     @Override
112     public Character fromString(@NotNull String shortcut) {
113       return TAB.equals(shortcut) ? TAB_CHAR :
114              ENTER.equals(shortcut) ? ENTER_CHAR :
115              CUSTOM.equals(shortcut) ? CUSTOM_CHAR :
116              SPACE_CHAR;
117     }
118
119     @NotNull
120     @Override
121     public String toString(@NotNull Character shortcut) {
122       return shortcut == TAB_CHAR ? TAB :
123              shortcut == ENTER_CHAR ? ENTER :
124              shortcut == CUSTOM_CHAR ? CUSTOM :
125              SPACE;
126     }
127   }
128
129   final static class State {
130     @OptionTag(nameAttribute = "", valueAttribute = "shortcut", converter = ShortcutConverter.class)
131     public char defaultShortcut = TAB_CHAR;
132
133     public List<TemplateSettings.TemplateKey> deletedKeys = new SmartList<>();
134   }
135
136   public static class TemplateKey {
137     private String groupName;
138     private String key;
139
140     @SuppressWarnings("UnusedDeclaration")
141     public TemplateKey() {}
142
143     private TemplateKey(String groupName, String key) {
144       this.groupName = groupName;
145       this.key = key;
146     }
147
148     public static TemplateKey keyOf(TemplateImpl template) {
149       return new TemplateKey(template.getGroupName(), template.getKey());
150     }
151
152     public boolean equals(Object o) {
153       if (this == o) return true;
154       if (o == null || getClass() != o.getClass()) return false;
155
156       TemplateKey that = (TemplateKey)o;
157       return Comparing.equal(groupName, that.groupName) && Comparing.equal(key, that.key);
158     }
159
160     public int hashCode() {
161       int result = groupName != null ? groupName.hashCode() : 0;
162       result = 31 * result + (key != null ? key.hashCode() : 0);
163       return result;
164     }
165
166     public String getGroupName() {
167       return groupName;
168     }
169
170     @SuppressWarnings("UnusedDeclaration")
171     public void setGroupName(String groupName) {
172       this.groupName = groupName;
173     }
174
175     public String getKey() {
176       return key;
177     }
178
179     public void setKey(String key) {
180       this.key = key;
181     }
182
183     @Override
184     public String toString() {
185       return getKey() + "@" + getGroupName();
186     }
187   }
188
189   private TemplateKey myLastSelectedTemplate;
190
191   public TemplateSettings(@NotNull SchemeManagerFactory schemeManagerFactory) {
192     mySchemeManager = schemeManagerFactory.create(TEMPLATES_DIR_PATH, new BaseSchemeProcessor<TemplateGroup, TemplateGroup>() {
193       @Nullable
194       @Override
195       public TemplateGroup readScheme(@NotNull Element element, boolean duringLoad) {
196         return readTemplateFile(element, element.getAttributeValue("group"), false, false, getClass().getClassLoader());
197       }
198
199       @NotNull
200       @Override
201       public SchemeState getState(@NotNull TemplateGroup template) {
202         for (TemplateImpl t : template.getElements()) {
203           if (differsFromDefault(t)) {
204             return SchemeState.POSSIBLY_CHANGED;
205           }
206         }
207         return SchemeState.NON_PERSISTENT;
208       }
209
210       @NotNull
211       @Override
212       public Element writeScheme(@NotNull TemplateGroup template) {
213         Element templateSetElement = new Element(TEMPLATE_SET);
214         templateSetElement.setAttribute(GROUP, template.getName());
215
216         for (TemplateImpl t : template.getElements()) {
217           if (differsFromDefault(t)) {
218             templateSetElement.addContent(serializeTemplate(t));
219           }
220         }
221
222         return templateSetElement;
223       }
224
225       @Override
226       public void initScheme(@NotNull final TemplateGroup scheme) {
227         for (TemplateImpl template : scheme.getElements()) {
228           addTemplateImpl(template);
229         }
230       }
231
232       @Override
233       public void onSchemeAdded(@NotNull final TemplateGroup scheme) {
234         for (TemplateImpl template : scheme.getElements()) {
235           addTemplateImpl(template);
236         }
237       }
238
239       @Override
240       public void onSchemeDeleted(@NotNull final TemplateGroup scheme) {
241         for (TemplateImpl template : scheme.getElements()) {
242           removeTemplate(template);
243         }
244       }
245     });
246
247     for (TemplateGroup group : mySchemeManager.loadSchemes()) {
248       for (TemplateImpl template : group.getElements()) {
249         addTemplateImpl(template);
250       }
251     }
252
253     loadDefaultLiveTemplates();
254   }
255
256   public static TemplateSettings getInstance() {
257     return ServiceManager.getService(TemplateSettings.class);
258   }
259
260   private boolean differsFromDefault(TemplateImpl t) {
261     TemplateImpl def = getDefaultTemplate(t);
262     return def == null || !t.equals(def) || !t.contextsEqual(def);
263   }
264
265   @Nullable
266   public TemplateImpl getDefaultTemplate(TemplateImpl t) {
267     return myDefaultTemplates.get(TemplateKey.keyOf(t));
268   }
269
270   @Override
271   public State getState() {
272     return myState;
273   }
274
275   @Override
276   public void loadState(State state) {
277     myState = state;
278
279     applyNewDeletedTemplates();
280   }
281
282   void applyNewDeletedTemplates() {
283     for (TemplateKey templateKey : myState.deletedKeys) {
284       if (templateKey.groupName == null) {
285         for (TemplateImpl template : new ArrayList<>(myTemplates.get(templateKey.key))) {
286           removeTemplate(template);
287         }
288       }
289       else {
290         TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName);
291         if (toDelete != null) {
292           removeTemplate(toDelete);
293         }
294       }
295     }
296   }
297
298   @Nullable
299   public String getLastSelectedTemplateKey() {
300     return myLastSelectedTemplate != null ? myLastSelectedTemplate.key : null;
301   }
302
303   @Nullable
304   public String getLastSelectedTemplateGroup() {
305     return myLastSelectedTemplate != null ? myLastSelectedTemplate.groupName : null;
306   }
307
308   public void setLastSelectedTemplate(@Nullable String group, @Nullable String key) {
309     myLastSelectedTemplate = group == null ? null : new TemplateKey(group, key);
310   }
311
312   @SuppressWarnings("unused")
313   public Collection<? extends TemplateImpl> getTemplatesAsList() {
314     return myTemplates.values();
315   }
316
317   public TemplateImpl[] getTemplates() {
318     final Collection<? extends TemplateImpl> all = myTemplates.values();
319     return all.toArray(new TemplateImpl[all.size()]);
320   }
321
322   public char getDefaultShortcutChar() {
323     return myState.defaultShortcut;
324   }
325
326   public void setDefaultShortcutChar(char defaultShortcutChar) {
327     myState.defaultShortcut = defaultShortcutChar;
328   }
329
330   public Collection<TemplateImpl> getTemplates(@NonNls String key) {
331     return myTemplates.get(key);
332   }
333
334   @Nullable
335   public TemplateImpl getTemplate(@NonNls String key, String group) {
336     final Collection<TemplateImpl> templates = myTemplates.get(key);
337     for (TemplateImpl template : templates) {
338       if (template.getGroupName().equals(group)) {
339         return template;
340       }
341     }
342     return null;
343   }
344
345   public Template getTemplateById(@NonNls String id) {
346     return myTemplatesById.get(id);
347   }
348
349   public int getMaxKeyLength() {
350     return myMaxKeyLength;
351   }
352
353   public void addTemplate(Template template) {
354     clearPreviouslyRegistered(template);
355     addTemplateImpl(template);
356
357     TemplateImpl templateImpl = (TemplateImpl)template;
358     String groupName = templateImpl.getGroupName();
359     TemplateGroup group = mySchemeManager.findSchemeByName(groupName);
360     if (group == null) {
361       group = new TemplateGroup(groupName);
362       mySchemeManager.addScheme(group);
363     }
364     group.addElement(templateImpl);
365   }
366
367   private void clearPreviouslyRegistered(final Template template) {
368     TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName());
369     if (existing != null) {
370       LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered");
371       TemplateGroup group = mySchemeManager.findSchemeByName(existing.getGroupName());
372       if (group != null) {
373         group.removeElement(existing);
374         if (group.isEmpty()) {
375           mySchemeManager.removeScheme(group);
376         }
377       }
378       myTemplates.remove(template.getKey(), existing);
379     }
380   }
381
382   private void addTemplateImpl(@NotNull Template template) {
383     TemplateImpl templateImpl = (TemplateImpl)template;
384     if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) {
385       myTemplates.putValue(template.getKey(), templateImpl);
386     }
387
388     myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length());
389     myState.deletedKeys.remove(TemplateKey.keyOf((TemplateImpl)template));
390   }
391
392   private void addTemplateById(Template template) {
393     if (!myTemplatesById.containsKey(template.getId())) {
394       final String id = template.getId();
395       if (id != null) {
396         myTemplatesById.put(id, template);
397       }
398     }
399   }
400
401   public void removeTemplate(@NotNull Template template) {
402     myTemplates.remove(template.getKey(), (TemplateImpl)template);
403
404     TemplateGroup group = mySchemeManager.findSchemeByName(((TemplateImpl)template).getGroupName());
405     if (group != null) {
406       group.removeElement((TemplateImpl)template);
407       if (group.isEmpty()) {
408         mySchemeManager.removeScheme(group);
409       }
410     }
411   }
412
413   private static TemplateImpl createTemplate(String key, String string, String group, String description, String shortcut, String id) {
414     TemplateImpl template = new TemplateImpl(key, string, group);
415     template.setId(id);
416     template.setDescription(description);
417     if (TAB.equals(shortcut)) {
418       template.setShortcutChar(TAB_CHAR);
419     }
420     else if (ENTER.equals(shortcut)) {
421       template.setShortcutChar(ENTER_CHAR);
422     }
423     else if (SPACE.equals(shortcut)) {
424       template.setShortcutChar(SPACE_CHAR);
425     }
426     else {
427       template.setShortcutChar(DEFAULT_CHAR);
428     }
429     return template;
430   }
431
432   private void loadDefaultLiveTemplates() {
433     try {
434       for (DefaultLiveTemplatesProvider provider : DefaultLiveTemplatesProvider.EP_NAME.getExtensions()) {
435         for (String defTemplate : provider.getDefaultLiveTemplateFiles()) {
436           readDefTemplate(provider, defTemplate, true);
437         }
438         try {
439           String[] hidden = provider.getHiddenLiveTemplateFiles();
440           if (hidden != null) {
441             for (String s : hidden) {
442               readDefTemplate(provider, s, false);
443             }
444           }
445         }
446         catch (AbstractMethodError ignore) {
447         }
448       }
449     }
450     catch (Exception e) {
451       LOG.error(e);
452     }
453   }
454
455   private void readDefTemplate(DefaultLiveTemplatesProvider provider, String defTemplate, boolean registerTemplate) throws JDOMException, InvalidDataException, IOException {
456     InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate);
457     if (inputStream != null) {
458       TemplateGroup group = readTemplateFile(JdomKt.loadElement(inputStream), getDefaultTemplateName(defTemplate), true, registerTemplate, provider.getClass().getClassLoader());
459       if (group != null && group.getReplace() != null) {
460         for (TemplateImpl template : myTemplates.get(group.getReplace())) {
461           removeTemplate(template);
462         }
463       }
464     }
465   }
466
467   private static String getDefaultTemplateName(String defTemplate) {
468     return defTemplate.substring(defTemplate.lastIndexOf('/') + 1);
469   }
470
471   @Nullable
472   private TemplateGroup readTemplateFile(@NotNull Element element, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate, @NotNull ClassLoader classLoader) {
473     if (!TEMPLATE_SET.equals(element.getName())) {
474       LOG.error("Ignore invalid template scheme: " + JDOMUtil.writeElement(element));
475       return null;
476     }
477
478     String groupName = element.getAttributeValue(GROUP);
479     if (groupName == null || groupName.isEmpty()) groupName = defGroupName;
480
481     TemplateGroup result = new TemplateGroup(groupName, element.getAttributeValue("REPLACE"));
482
483     Map<String, TemplateImpl> created = new LinkedHashMap<>();
484
485     for (Element child : element.getChildren(TEMPLATE)) {
486       TemplateImpl template;
487       try {
488         template = readTemplateFromElement(groupName, child, classLoader);
489       }
490       catch (Exception e) {
491         LOG.info("failed to load template " + element.getAttributeValue(NAME), e);
492         continue;
493       }
494
495       if (isDefault) {
496         myDefaultTemplates.put(TemplateKey.keyOf(template), template);
497       }
498       TemplateImpl existing = getTemplate(template.getKey(), template.getGroupName());
499       boolean defaultTemplateModified = isDefault && (myState.deletedKeys.contains(TemplateKey.keyOf(template)) ||
500                                                       myTemplatesById.containsKey(template.getId()) ||
501                                                       existing != null);
502
503       if(!defaultTemplateModified) {
504         created.put(template.getKey(), template);
505       }
506       if (isDefault && existing != null) {
507         existing.getTemplateContext().setDefaultContext(template.getTemplateContext());
508       }
509     }
510
511     if (registerTemplate) {
512       TemplateGroup existingScheme = mySchemeManager.findSchemeByName(result.getName());
513       if (existingScheme != null) {
514         result = existingScheme;
515       }
516     }
517
518     for (TemplateImpl template : created.values()) {
519       if (registerTemplate) {
520         clearPreviouslyRegistered(template);
521         addTemplateImpl(template);
522       }
523       addTemplateById(template);
524
525       result.addElement(template);
526     }
527
528     if (registerTemplate) {
529       TemplateGroup existingScheme = mySchemeManager.findSchemeByName(result.getName());
530       if (existingScheme == null && !result.isEmpty()) {
531         mySchemeManager.addNewScheme(result, false);
532       }
533     }
534
535     return result.isEmpty() ? null : result;
536   }
537
538   static TemplateImpl readTemplateFromElement(final String groupName, @NotNull Element element, @NotNull ClassLoader classLoader) {
539     String name = element.getAttributeValue(NAME);
540     String value = element.getAttributeValue(VALUE);
541     String description;
542     String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE);
543     String key = element.getAttributeValue(KEY);
544     String id = element.getAttributeValue(ID);
545     if (resourceBundle != null && key != null) {
546       ResourceBundle bundle = AbstractBundle.getResourceBundle(resourceBundle, classLoader);
547       description = bundle.getString(key);
548     }
549     else {
550       description = element.getAttributeValue(DESCRIPTION);
551     }
552     String shortcut = element.getAttributeValue(SHORTCUT);
553     TemplateImpl template = createTemplate(name, value, groupName, description, shortcut, id);
554
555     template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT)));
556     template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES)));
557     template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED)));
558
559     String useStaticImport = element.getAttributeValue(USE_STATIC_IMPORT);
560     if (useStaticImport != null) {
561       template.setValue(TemplateImpl.Property.USE_STATIC_IMPORT_IF_POSSIBLE, Boolean.parseBoolean(useStaticImport));
562     }
563
564     for (Element e : element.getChildren(VARIABLE)) {
565       String variableName = e.getAttributeValue(NAME);
566       String expression = e.getAttributeValue(EXPRESSION);
567       String defaultValue = e.getAttributeValue(DEFAULT_VALUE);
568       boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT));
569       template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt);
570     }
571
572     Element context = element.getChild(CONTEXT);
573     if (context != null) {
574       template.getTemplateContext().readTemplateContext(context);
575     }
576
577     return template;
578   }
579
580   @NotNull
581   static Element serializeTemplate(@NotNull TemplateImpl template) {
582     Element element = new Element(TEMPLATE);
583     final String id = template.getId();
584     if (id != null) {
585       element.setAttribute(ID, id);
586     }
587     element.setAttribute(NAME, template.getKey());
588     element.setAttribute(VALUE, template.getString());
589     if (template.getShortcutChar() == TAB_CHAR) {
590       element.setAttribute(SHORTCUT, TAB);
591     } else if (template.getShortcutChar() == ENTER_CHAR) {
592       element.setAttribute(SHORTCUT, ENTER);
593     } else if (template.getShortcutChar() == SPACE_CHAR) {
594       element.setAttribute(SHORTCUT, SPACE);
595     }
596     if (template.getDescription() != null) {
597       element.setAttribute(DESCRIPTION, template.getDescription());
598     }
599     element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat()));
600     element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames()));
601     if (template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)
602         != Template.getDefaultValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE))
603     {
604       element.setAttribute(USE_STATIC_IMPORT, Boolean.toString(template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)));
605     }
606     if (template.isDeactivated()) {
607       element.setAttribute(DEACTIVATED, Boolean.toString(true));
608     }
609
610     for (int i = 0; i < template.getVariableCount(); i++) {
611       Element variableElement = new Element(VARIABLE);
612       variableElement.setAttribute(NAME, template.getVariableNameAt(i));
613       variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i));
614       variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i));
615       variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i)));
616       element.addContent(variableElement);
617     }
618
619     try {
620       Element contextElement = new Element(CONTEXT);
621       template.getTemplateContext().writeTemplateContext(contextElement);
622       element.addContent(contextElement);
623     } catch (WriteExternalException ignore) {
624     }
625     return element;
626   }
627
628   public void setTemplates(@NotNull List<TemplateGroup> newGroups) {
629     myTemplates.clear();
630     myState.deletedKeys.clear();
631     for (TemplateImpl template : myDefaultTemplates.values()) {
632       myState.deletedKeys.add(TemplateKey.keyOf(template));
633     }
634     myMaxKeyLength = 0;
635     List<TemplateGroup> schemes = new SmartList<>();
636     for (TemplateGroup group : newGroups) {
637       if (!group.isEmpty()) {
638         schemes.add(group);
639         for (TemplateImpl template : group.getElements()) {
640           clearPreviouslyRegistered(template);
641           addTemplateImpl(template);
642         }
643       }
644     }
645     mySchemeManager.setSchemes(schemes);
646   }
647
648   public List<TemplateGroup> getTemplateGroups() {
649     return mySchemeManager.getAllSchemes();
650   }
651
652   public List<TemplateImpl> collectMatchingCandidates(String key, @Nullable Character shortcutChar, boolean hasArgument) {
653     final Collection<TemplateImpl> templates = getTemplates(key);
654     List<TemplateImpl> candidates = new ArrayList<>();
655     for (TemplateImpl template : templates) {
656       if (template.isDeactivated()) {
657         continue;
658       }
659       if (shortcutChar != null && getShortcutChar(template) != shortcutChar) {
660         continue;
661       }
662       if (hasArgument && !template.hasArgument()) {
663         continue;
664       }
665       candidates.add(template);
666     }
667     return candidates;
668   }
669
670   public char getShortcutChar(TemplateImpl template) {
671     char c = template.getShortcutChar();
672     return c == DEFAULT_CHAR ? getDefaultShortcutChar() : c;
673   }
674
675   public List<TemplateKey> getDeletedTemplates() {
676     return myState.deletedKeys;
677   }
678
679   public void reset() {
680     myState.deletedKeys.clear();
681     loadDefaultLiveTemplates();
682   }
683 }