IDEA-CR-15780 do not write default context values
[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 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           TemplateImpl defaultTemplate = getDefaultTemplate(t);
218           if (defaultTemplate == null || !t.equals(defaultTemplate) || !t.contextsEqual(defaultTemplate)) {
219             templateSetElement.addContent(serializeTemplate(t, defaultTemplate));
220           }
221         }
222
223         return templateSetElement;
224       }
225
226       @Override
227       public void initScheme(@NotNull final TemplateGroup scheme) {
228         for (TemplateImpl template : scheme.getElements()) {
229           addTemplateImpl(template);
230         }
231       }
232
233       @Override
234       public void onSchemeAdded(@NotNull final TemplateGroup scheme) {
235         for (TemplateImpl template : scheme.getElements()) {
236           addTemplateImpl(template);
237         }
238       }
239
240       @Override
241       public void onSchemeDeleted(@NotNull final TemplateGroup scheme) {
242         for (TemplateImpl template : scheme.getElements()) {
243           removeTemplate(template);
244         }
245       }
246     });
247
248     for (TemplateGroup group : mySchemeManager.loadSchemes()) {
249       for (TemplateImpl template : group.getElements()) {
250         addTemplateImpl(template);
251       }
252     }
253
254     loadDefaultLiveTemplates();
255   }
256
257   public static TemplateSettings getInstance() {
258     return ServiceManager.getService(TemplateSettings.class);
259   }
260
261   private boolean differsFromDefault(TemplateImpl t) {
262     TemplateImpl def = getDefaultTemplate(t);
263     return def == null || !t.equals(def) || !t.contextsEqual(def);
264   }
265
266   @Nullable
267   public TemplateImpl getDefaultTemplate(TemplateImpl t) {
268     return myDefaultTemplates.get(TemplateKey.keyOf(t));
269   }
270
271   @Override
272   public State getState() {
273     return myState;
274   }
275
276   @Override
277   public void loadState(State state) {
278     myState = state;
279
280     applyNewDeletedTemplates();
281   }
282
283   void applyNewDeletedTemplates() {
284     for (TemplateKey templateKey : myState.deletedKeys) {
285       if (templateKey.groupName == null) {
286         for (TemplateImpl template : new ArrayList<>(myTemplates.get(templateKey.key))) {
287           removeTemplate(template);
288         }
289       }
290       else {
291         TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName);
292         if (toDelete != null) {
293           removeTemplate(toDelete);
294         }
295       }
296     }
297   }
298
299   @Nullable
300   public String getLastSelectedTemplateKey() {
301     return myLastSelectedTemplate != null ? myLastSelectedTemplate.key : null;
302   }
303
304   @Nullable
305   public String getLastSelectedTemplateGroup() {
306     return myLastSelectedTemplate != null ? myLastSelectedTemplate.groupName : null;
307   }
308
309   public void setLastSelectedTemplate(@Nullable String group, @Nullable String key) {
310     myLastSelectedTemplate = group == null ? null : new TemplateKey(group, key);
311   }
312
313   @SuppressWarnings("unused")
314   public Collection<? extends TemplateImpl> getTemplatesAsList() {
315     return myTemplates.values();
316   }
317
318   public TemplateImpl[] getTemplates() {
319     final Collection<? extends TemplateImpl> all = myTemplates.values();
320     return all.toArray(new TemplateImpl[all.size()]);
321   }
322
323   public char getDefaultShortcutChar() {
324     return myState.defaultShortcut;
325   }
326
327   public void setDefaultShortcutChar(char defaultShortcutChar) {
328     myState.defaultShortcut = defaultShortcutChar;
329   }
330
331   public Collection<TemplateImpl> getTemplates(@NonNls String key) {
332     return myTemplates.get(key);
333   }
334
335   @Nullable
336   public TemplateImpl getTemplate(@NonNls String key, String group) {
337     final Collection<TemplateImpl> templates = myTemplates.get(key);
338     for (TemplateImpl template : templates) {
339       if (template.getGroupName().equals(group)) {
340         return template;
341       }
342     }
343     return null;
344   }
345
346   public Template getTemplateById(@NonNls String id) {
347     return myTemplatesById.get(id);
348   }
349
350   public int getMaxKeyLength() {
351     return myMaxKeyLength;
352   }
353
354   public void addTemplate(Template template) {
355     clearPreviouslyRegistered(template);
356     addTemplateImpl(template);
357
358     TemplateImpl templateImpl = (TemplateImpl)template;
359     String groupName = templateImpl.getGroupName();
360     TemplateGroup group = mySchemeManager.findSchemeByName(groupName);
361     if (group == null) {
362       group = new TemplateGroup(groupName);
363       mySchemeManager.addScheme(group);
364     }
365     group.addElement(templateImpl);
366   }
367
368   private void clearPreviouslyRegistered(final Template template) {
369     TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName());
370     if (existing != null) {
371       LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered");
372       TemplateGroup group = mySchemeManager.findSchemeByName(existing.getGroupName());
373       if (group != null) {
374         group.removeElement(existing);
375         if (group.isEmpty()) {
376           mySchemeManager.removeScheme(group);
377         }
378       }
379       myTemplates.remove(template.getKey(), existing);
380     }
381   }
382
383   private void addTemplateImpl(@NotNull Template template) {
384     TemplateImpl templateImpl = (TemplateImpl)template;
385     if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) {
386       myTemplates.putValue(template.getKey(), templateImpl);
387     }
388
389     myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length());
390     myState.deletedKeys.remove(TemplateKey.keyOf((TemplateImpl)template));
391   }
392
393   private void addTemplateById(Template template) {
394     if (!myTemplatesById.containsKey(template.getId())) {
395       final String id = template.getId();
396       if (id != null) {
397         myTemplatesById.put(id, template);
398       }
399     }
400   }
401
402   public void removeTemplate(@NotNull Template template) {
403     myTemplates.remove(template.getKey(), (TemplateImpl)template);
404
405     TemplateGroup group = mySchemeManager.findSchemeByName(((TemplateImpl)template).getGroupName());
406     if (group != null) {
407       group.removeElement((TemplateImpl)template);
408       if (group.isEmpty()) {
409         mySchemeManager.removeScheme(group);
410       }
411     }
412   }
413
414   @NotNull
415   private static TemplateImpl createTemplate(@NotNull String key, String string, @NotNull String group, String description, @Nullable String shortcut, String id) {
416     TemplateImpl template = new TemplateImpl(key, string, group);
417     template.setId(id);
418     template.setDescription(description);
419     if (TAB.equals(shortcut)) {
420       template.setShortcutChar(TAB_CHAR);
421     }
422     else if (ENTER.equals(shortcut)) {
423       template.setShortcutChar(ENTER_CHAR);
424     }
425     else if (SPACE.equals(shortcut)) {
426       template.setShortcutChar(SPACE_CHAR);
427     }
428     else {
429       template.setShortcutChar(DEFAULT_CHAR);
430     }
431     return template;
432   }
433
434   private void loadDefaultLiveTemplates() {
435     try {
436       for (DefaultLiveTemplatesProvider provider : DefaultLiveTemplatesProvider.EP_NAME.getExtensions()) {
437         for (String defTemplate : provider.getDefaultLiveTemplateFiles()) {
438           readDefTemplate(provider, defTemplate, true);
439         }
440         try {
441           String[] hidden = provider.getHiddenLiveTemplateFiles();
442           if (hidden != null) {
443             for (String s : hidden) {
444               readDefTemplate(provider, s, false);
445             }
446           }
447         }
448         catch (AbstractMethodError ignore) {
449         }
450       }
451     }
452     catch (Exception e) {
453       LOG.error(e);
454     }
455   }
456
457   private void readDefTemplate(DefaultLiveTemplatesProvider provider, String defTemplate, boolean registerTemplate) throws JDOMException, InvalidDataException, IOException {
458     InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate);
459     if (inputStream != null) {
460       TemplateGroup group = readTemplateFile(JdomKt.loadElement(inputStream), getDefaultTemplateName(defTemplate), true, registerTemplate, provider.getClass().getClassLoader());
461       if (group != null && group.getReplace() != null) {
462         for (TemplateImpl template : myTemplates.get(group.getReplace())) {
463           removeTemplate(template);
464         }
465       }
466     }
467   }
468
469   private static String getDefaultTemplateName(String defTemplate) {
470     return defTemplate.substring(defTemplate.lastIndexOf('/') + 1);
471   }
472
473   @Nullable
474   private TemplateGroup readTemplateFile(@NotNull Element element, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate, @NotNull ClassLoader classLoader) {
475     if (!TEMPLATE_SET.equals(element.getName())) {
476       LOG.error("Ignore invalid template scheme: " + JDOMUtil.writeElement(element));
477       return null;
478     }
479
480     String groupName = element.getAttributeValue(GROUP);
481     if (StringUtil.isEmpty(groupName)) {
482       groupName = defGroupName;
483     }
484
485     TemplateGroup result = new TemplateGroup(groupName, element.getAttributeValue("REPLACE"));
486
487     Map<String, TemplateImpl> created = new LinkedHashMap<>();
488
489     for (Element child : element.getChildren(TEMPLATE)) {
490       TemplateImpl template;
491       try {
492         template = readTemplateFromElement(groupName, child, classLoader);
493       }
494       catch (Exception e) {
495         LOG.warn("failed to load template " + element.getAttributeValue(NAME), e);
496         continue;
497       }
498
499       if (isDefault) {
500         myDefaultTemplates.put(TemplateKey.keyOf(template), template);
501       }
502       TemplateImpl existing = getTemplate(template.getKey(), template.getGroupName());
503       boolean defaultTemplateModified = isDefault && (myState.deletedKeys.contains(TemplateKey.keyOf(template)) ||
504                                                       myTemplatesById.containsKey(template.getId()) ||
505                                                       existing != null);
506
507       if(!defaultTemplateModified) {
508         created.put(template.getKey(), template);
509       }
510       if (isDefault && existing != null) {
511         existing.getTemplateContext().setDefaultContext(template.getTemplateContext());
512       }
513     }
514
515     if (registerTemplate) {
516       TemplateGroup existingScheme = mySchemeManager.findSchemeByName(result.getName());
517       if (existingScheme != null) {
518         result = existingScheme;
519       }
520     }
521
522     for (TemplateImpl template : created.values()) {
523       if (registerTemplate) {
524         clearPreviouslyRegistered(template);
525         addTemplateImpl(template);
526       }
527       addTemplateById(template);
528
529       result.addElement(template);
530     }
531
532     if (registerTemplate) {
533       TemplateGroup existingScheme = mySchemeManager.findSchemeByName(result.getName());
534       if (existingScheme == null && !result.isEmpty()) {
535         mySchemeManager.addNewScheme(result, false);
536       }
537     }
538
539     return result.isEmpty() ? null : result;
540   }
541
542   static TemplateImpl readTemplateFromElement(final String groupName, @NotNull Element element, @NotNull ClassLoader classLoader) {
543     String name = element.getAttributeValue(NAME);
544     String value = element.getAttributeValue(VALUE);
545     String description;
546     String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE);
547     String key = element.getAttributeValue(KEY);
548     String id = element.getAttributeValue(ID);
549     if (resourceBundle != null && key != null) {
550       ResourceBundle bundle = AbstractBundle.getResourceBundle(resourceBundle, classLoader);
551       description = bundle.getString(key);
552     }
553     else {
554       description = element.getAttributeValue(DESCRIPTION);
555     }
556
557     String shortcut = element.getAttributeValue(SHORTCUT);
558     TemplateImpl template = createTemplate(name, value, groupName, description, shortcut, id);
559
560     template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT)));
561     template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES)));
562     template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED)));
563
564     String useStaticImport = element.getAttributeValue(USE_STATIC_IMPORT);
565     if (useStaticImport != null) {
566       template.setValue(TemplateImpl.Property.USE_STATIC_IMPORT_IF_POSSIBLE, Boolean.parseBoolean(useStaticImport));
567     }
568
569     for (Element e : element.getChildren(VARIABLE)) {
570       String variableName = e.getAttributeValue(NAME);
571       String expression = e.getAttributeValue(EXPRESSION);
572       String defaultValue = e.getAttributeValue(DEFAULT_VALUE);
573       boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT));
574       template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt);
575     }
576
577     Element context = element.getChild(CONTEXT);
578     if (context != null) {
579       template.getTemplateContext().readTemplateContext(context);
580     }
581
582     return template;
583   }
584
585   @NotNull
586   static Element serializeTemplate(@NotNull TemplateImpl template, @Nullable TemplateImpl defaultTemplate) {
587     Element element = new Element(TEMPLATE);
588     final String id = template.getId();
589     if (id != null) {
590       element.setAttribute(ID, id);
591     }
592     element.setAttribute(NAME, template.getKey());
593     element.setAttribute(VALUE, template.getString());
594     if (template.getShortcutChar() == TAB_CHAR) {
595       element.setAttribute(SHORTCUT, TAB);
596     }
597     else if (template.getShortcutChar() == ENTER_CHAR) {
598       element.setAttribute(SHORTCUT, ENTER);
599     }
600     else if (template.getShortcutChar() == SPACE_CHAR) {
601       element.setAttribute(SHORTCUT, SPACE);
602     }
603     if (template.getDescription() != null) {
604       element.setAttribute(DESCRIPTION, template.getDescription());
605     }
606     element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat()));
607     element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames()));
608     if (template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)
609         != Template.getDefaultValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)) {
610       element.setAttribute(USE_STATIC_IMPORT, Boolean.toString(template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)));
611     }
612     if (template.isDeactivated()) {
613       element.setAttribute(DEACTIVATED, Boolean.toString(true));
614     }
615
616     for (int i = 0; i < template.getVariableCount(); i++) {
617       Element variableElement = new Element(VARIABLE);
618       variableElement.setAttribute(NAME, template.getVariableNameAt(i));
619       variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i));
620       variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i));
621       variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i)));
622       element.addContent(variableElement);
623     }
624
625     Element contextElement = template.getTemplateContext().writeTemplateContext(defaultTemplate == null ? null : defaultTemplate.getTemplateContext());
626     if (contextElement != null) {
627       element.addContent(contextElement);
628     }
629     return element;
630   }
631
632   public void setTemplates(@NotNull List<TemplateGroup> newGroups) {
633     myTemplates.clear();
634     myState.deletedKeys.clear();
635     for (TemplateImpl template : myDefaultTemplates.values()) {
636       myState.deletedKeys.add(TemplateKey.keyOf(template));
637     }
638     myMaxKeyLength = 0;
639     List<TemplateGroup> schemes = new SmartList<>();
640     for (TemplateGroup group : newGroups) {
641       if (!group.isEmpty()) {
642         schemes.add(group);
643         for (TemplateImpl template : group.getElements()) {
644           clearPreviouslyRegistered(template);
645           addTemplateImpl(template);
646         }
647       }
648     }
649     mySchemeManager.setSchemes(schemes);
650   }
651
652   public List<TemplateGroup> getTemplateGroups() {
653     return mySchemeManager.getAllSchemes();
654   }
655
656   public List<TemplateImpl> collectMatchingCandidates(String key, @Nullable Character shortcutChar, boolean hasArgument) {
657     final Collection<TemplateImpl> templates = getTemplates(key);
658     List<TemplateImpl> candidates = new ArrayList<>();
659     for (TemplateImpl template : templates) {
660       if (template.isDeactivated()) {
661         continue;
662       }
663       if (shortcutChar != null && getShortcutChar(template) != shortcutChar) {
664         continue;
665       }
666       if (hasArgument && !template.hasArgument()) {
667         continue;
668       }
669       candidates.add(template);
670     }
671     return candidates;
672   }
673
674   public char getShortcutChar(TemplateImpl template) {
675     char c = template.getShortcutChar();
676     return c == DEFAULT_CHAR ? getDefaultShortcutChar() : c;
677   }
678
679   public List<TemplateKey> getDeletedTemplates() {
680     return myState.deletedKeys;
681   }
682
683   public void reset() {
684     myState.deletedKeys.clear();
685     loadDefaultLiveTemplates();
686   }
687 }