7594043c034ed252abef4ea5c458d52e1fc0ce6a
[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.text.StringUtil;
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   @NotNull
414   private static TemplateImpl createTemplate(@NotNull String key, String string, @NotNull String group, String description, @Nullable String shortcut, String id) {
415     TemplateImpl template = new TemplateImpl(key, string, group);
416     template.setId(id);
417     template.setDescription(description);
418     if (TAB.equals(shortcut)) {
419       template.setShortcutChar(TAB_CHAR);
420     }
421     else if (ENTER.equals(shortcut)) {
422       template.setShortcutChar(ENTER_CHAR);
423     }
424     else if (SPACE.equals(shortcut)) {
425       template.setShortcutChar(SPACE_CHAR);
426     }
427     else {
428       template.setShortcutChar(DEFAULT_CHAR);
429     }
430     return template;
431   }
432
433   private void loadDefaultLiveTemplates() {
434     try {
435       for (DefaultLiveTemplatesProvider provider : DefaultLiveTemplatesProvider.EP_NAME.getExtensions()) {
436         for (String defTemplate : provider.getDefaultLiveTemplateFiles()) {
437           readDefTemplate(provider, defTemplate, true);
438         }
439         try {
440           String[] hidden = provider.getHiddenLiveTemplateFiles();
441           if (hidden != null) {
442             for (String s : hidden) {
443               readDefTemplate(provider, s, false);
444             }
445           }
446         }
447         catch (AbstractMethodError ignore) {
448         }
449       }
450     }
451     catch (Exception e) {
452       LOG.error(e);
453     }
454   }
455
456   private void readDefTemplate(DefaultLiveTemplatesProvider provider, String defTemplate, boolean registerTemplate) throws JDOMException, InvalidDataException, IOException {
457     InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate);
458     if (inputStream != null) {
459       TemplateGroup group = readTemplateFile(JdomKt.loadElement(inputStream), getDefaultTemplateName(defTemplate), true, registerTemplate, provider.getClass().getClassLoader());
460       if (group != null && group.getReplace() != null) {
461         for (TemplateImpl template : myTemplates.get(group.getReplace())) {
462           removeTemplate(template);
463         }
464       }
465     }
466   }
467
468   private static String getDefaultTemplateName(String defTemplate) {
469     return defTemplate.substring(defTemplate.lastIndexOf('/') + 1);
470   }
471
472   @Nullable
473   private TemplateGroup readTemplateFile(@NotNull Element element, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate, @NotNull ClassLoader classLoader) {
474     if (!TEMPLATE_SET.equals(element.getName())) {
475       LOG.error("Ignore invalid template scheme: " + JDOMUtil.writeElement(element));
476       return null;
477     }
478
479     String groupName = element.getAttributeValue(GROUP);
480     if (StringUtil.isEmpty(groupName)) {
481       groupName = defGroupName;
482     }
483
484     TemplateGroup result = new TemplateGroup(groupName, element.getAttributeValue("REPLACE"));
485
486     Map<String, TemplateImpl> created = new LinkedHashMap<>();
487
488     for (Element child : element.getChildren(TEMPLATE)) {
489       TemplateImpl template;
490       try {
491         template = readTemplateFromElement(groupName, child, classLoader);
492       }
493       catch (Exception e) {
494         LOG.warn("failed to load template " + element.getAttributeValue(NAME), e);
495         continue;
496       }
497
498       if (isDefault) {
499         myDefaultTemplates.put(TemplateKey.keyOf(template), template);
500       }
501       TemplateImpl existing = getTemplate(template.getKey(), template.getGroupName());
502       boolean defaultTemplateModified = isDefault && (myState.deletedKeys.contains(TemplateKey.keyOf(template)) ||
503                                                       myTemplatesById.containsKey(template.getId()) ||
504                                                       existing != null);
505
506       if(!defaultTemplateModified) {
507         created.put(template.getKey(), template);
508       }
509       if (isDefault && existing != null) {
510         existing.getTemplateContext().setDefaultContext(template.getTemplateContext());
511       }
512     }
513
514     if (registerTemplate) {
515       TemplateGroup existingScheme = mySchemeManager.findSchemeByName(result.getName());
516       if (existingScheme != null) {
517         result = existingScheme;
518       }
519     }
520
521     for (TemplateImpl template : created.values()) {
522       if (registerTemplate) {
523         clearPreviouslyRegistered(template);
524         addTemplateImpl(template);
525       }
526       addTemplateById(template);
527
528       result.addElement(template);
529     }
530
531     if (registerTemplate) {
532       TemplateGroup existingScheme = mySchemeManager.findSchemeByName(result.getName());
533       if (existingScheme == null && !result.isEmpty()) {
534         mySchemeManager.addNewScheme(result, false);
535       }
536     }
537
538     return result.isEmpty() ? null : result;
539   }
540
541   static TemplateImpl readTemplateFromElement(final String groupName, @NotNull Element element, @NotNull ClassLoader classLoader) {
542     String name = element.getAttributeValue(NAME);
543     String value = element.getAttributeValue(VALUE);
544     String description;
545     String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE);
546     String key = element.getAttributeValue(KEY);
547     String id = element.getAttributeValue(ID);
548     if (resourceBundle != null && key != null) {
549       ResourceBundle bundle = AbstractBundle.getResourceBundle(resourceBundle, classLoader);
550       description = bundle.getString(key);
551     }
552     else {
553       description = element.getAttributeValue(DESCRIPTION);
554     }
555
556     String shortcut = element.getAttributeValue(SHORTCUT);
557     TemplateImpl template = createTemplate(name, value, groupName, description, shortcut, id);
558
559     template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT)));
560     template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES)));
561     template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED)));
562
563     String useStaticImport = element.getAttributeValue(USE_STATIC_IMPORT);
564     if (useStaticImport != null) {
565       template.setValue(TemplateImpl.Property.USE_STATIC_IMPORT_IF_POSSIBLE, Boolean.parseBoolean(useStaticImport));
566     }
567
568     for (Element e : element.getChildren(VARIABLE)) {
569       String variableName = e.getAttributeValue(NAME);
570       String expression = e.getAttributeValue(EXPRESSION);
571       String defaultValue = e.getAttributeValue(DEFAULT_VALUE);
572       boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT));
573       template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt);
574     }
575
576     Element context = element.getChild(CONTEXT);
577     if (context != null) {
578       template.getTemplateContext().readTemplateContext(context);
579     }
580
581     return template;
582   }
583
584   @NotNull
585   static Element serializeTemplate(@NotNull TemplateImpl template) {
586     Element element = new Element(TEMPLATE);
587     final String id = template.getId();
588     if (id != null) {
589       element.setAttribute(ID, id);
590     }
591     element.setAttribute(NAME, template.getKey());
592     element.setAttribute(VALUE, template.getString());
593     if (template.getShortcutChar() == TAB_CHAR) {
594       element.setAttribute(SHORTCUT, TAB);
595     }
596     else if (template.getShortcutChar() == ENTER_CHAR) {
597       element.setAttribute(SHORTCUT, ENTER);
598     }
599     else if (template.getShortcutChar() == SPACE_CHAR) {
600       element.setAttribute(SHORTCUT, SPACE);
601     }
602     if (template.getDescription() != null) {
603       element.setAttribute(DESCRIPTION, template.getDescription());
604     }
605     element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat()));
606     element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames()));
607     if (template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)
608         != Template.getDefaultValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)) {
609       element.setAttribute(USE_STATIC_IMPORT, Boolean.toString(template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)));
610     }
611     if (template.isDeactivated()) {
612       element.setAttribute(DEACTIVATED, Boolean.toString(true));
613     }
614
615     for (int i = 0; i < template.getVariableCount(); i++) {
616       Element variableElement = new Element(VARIABLE);
617       variableElement.setAttribute(NAME, template.getVariableNameAt(i));
618       variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i));
619       variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i));
620       variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i)));
621       element.addContent(variableElement);
622     }
623
624     Element contextElement = new Element(CONTEXT);
625     template.getTemplateContext().writeTemplateContext(contextElement);
626     element.addContent(contextElement);
627     return element;
628   }
629
630   public void setTemplates(@NotNull List<TemplateGroup> newGroups) {
631     myTemplates.clear();
632     myState.deletedKeys.clear();
633     for (TemplateImpl template : myDefaultTemplates.values()) {
634       myState.deletedKeys.add(TemplateKey.keyOf(template));
635     }
636     myMaxKeyLength = 0;
637     List<TemplateGroup> schemes = new SmartList<>();
638     for (TemplateGroup group : newGroups) {
639       if (!group.isEmpty()) {
640         schemes.add(group);
641         for (TemplateImpl template : group.getElements()) {
642           clearPreviouslyRegistered(template);
643           addTemplateImpl(template);
644         }
645       }
646     }
647     mySchemeManager.setSchemes(schemes);
648   }
649
650   public List<TemplateGroup> getTemplateGroups() {
651     return mySchemeManager.getAllSchemes();
652   }
653
654   public List<TemplateImpl> collectMatchingCandidates(String key, @Nullable Character shortcutChar, boolean hasArgument) {
655     final Collection<TemplateImpl> templates = getTemplates(key);
656     List<TemplateImpl> candidates = new ArrayList<>();
657     for (TemplateImpl template : templates) {
658       if (template.isDeactivated()) {
659         continue;
660       }
661       if (shortcutChar != null && getShortcutChar(template) != shortcutChar) {
662         continue;
663       }
664       if (hasArgument && !template.hasArgument()) {
665         continue;
666       }
667       candidates.add(template);
668     }
669     return candidates;
670   }
671
672   public char getShortcutChar(TemplateImpl template) {
673     char c = template.getShortcutChar();
674     return c == DEFAULT_CHAR ? getDefaultShortcutChar() : c;
675   }
676
677   public List<TemplateKey> getDeletedTemplates() {
678     return myState.deletedKeys;
679   }
680
681   public void reset() {
682     myState.deletedKeys.clear();
683     loadDefaultLiveTemplates();
684   }
685 }