support passing an argument to live template (WI-626)
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateSettings.java
bloba1f72df86d463ebd0f9fb66ab90a227561e08836
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.template.impl;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.template.Template;
21 import com.intellij.openapi.application.PathManager;
22 import com.intellij.openapi.application.ex.DecodeDefaultsUtil;
23 import com.intellij.openapi.components.*;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.extensions.Extensions;
26 import com.intellij.openapi.options.Scheme;
27 import com.intellij.openapi.options.SchemeProcessor;
28 import com.intellij.openapi.options.SchemesManager;
29 import com.intellij.openapi.options.SchemesManagerFactory;
30 import com.intellij.openapi.util.InvalidDataException;
31 import com.intellij.openapi.util.JDOMUtil;
32 import com.intellij.openapi.util.WriteExternalException;
33 import com.intellij.util.containers.MultiMap;
34 import org.jdom.Document;
35 import org.jdom.Element;
36 import org.jdom.JDOMException;
37 import org.jetbrains.annotations.NonNls;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
41 import java.io.File;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.util.*;
47 @State(
48 name="TemplateSettings",
49 storages= {
50 @Storage(
51 id="other",
52 file = "$APP_CONFIG$/other.xml"
55 public class TemplateSettings implements PersistentStateComponent<Element>, ExportableComponent {
57 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateSettings");
59 public @NonNls static final String USER_GROUP_NAME = "user";
60 private @NonNls static final String TEMPLATE_SET = "templateSet";
61 private @NonNls static final String GROUP = "group";
62 private @NonNls static final String TEMPLATE = "template";
64 private @NonNls static final String DELETED_TEMPLATES = "deleted_templates";
65 private final List<TemplateKey> myDeletedTemplates = new ArrayList<TemplateKey>();
67 public static final char SPACE_CHAR = ' ';
68 public static final char TAB_CHAR = '\t';
69 public static final char ENTER_CHAR = '\n';
70 public static final char DEFAULT_CHAR = 'D';
72 private static final @NonNls String SPACE = "SPACE";
73 private static final @NonNls String TAB = "TAB";
74 private static final @NonNls String ENTER = "ENTER";
76 private static final @NonNls String NAME = "name";
77 private static final @NonNls String VALUE = "value";
78 private static final @NonNls String DESCRIPTION = "description";
79 private static final @NonNls String SHORTCUT = "shortcut";
81 private static final @NonNls String VARIABLE = "variable";
82 private static final @NonNls String EXPRESSION = "expression";
83 private static final @NonNls String DEFAULT_VALUE = "defaultValue";
84 private static final @NonNls String ALWAYS_STOP_AT = "alwaysStopAt";
86 private static final @NonNls String CONTEXT = "context";
87 private static final @NonNls String TO_REFORMAT = "toReformat";
88 private static final @NonNls String TO_SHORTEN_FQ_NAMES = "toShortenFQNames";
90 private static final @NonNls String DEFAULT_SHORTCUT = "defaultShortcut";
91 private static final @NonNls String DEACTIVATED = "deactivated";
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";
97 private static final @NonNls String TEMPLATES_CONFIG_FOLDER = "templates";
99 private final List<TemplateImpl> myAllTemplates = new ArrayList<TemplateImpl>();
100 private final MultiMap<String,TemplateImpl> myTemplates = new MultiMap<String,TemplateImpl>();
101 private final Map<String,Template> myTemplatesById = new LinkedHashMap<String,Template>();
102 private final Map<String,TemplateImpl> myDefaultTemplates = new LinkedHashMap<String, TemplateImpl>();
104 private int myMaxKeyLength = 0;
105 private char myDefaultShortcutChar = TAB_CHAR;
106 private String myLastSelectedTemplateKey;
107 @NonNls
108 public static final String XML_EXTENSION = ".xml";
109 private final SchemesManager<TemplateGroup, TemplateGroup> mySchemesManager;
110 private final SchemeProcessor<TemplateGroup> myProcessor;
111 private static final String FILE_SPEC = "$ROOT_CONFIG$/templates";
113 private static class TemplateKey {
114 final String groupName;
115 final String key;
117 private TemplateKey(String groupName, String key) {
118 this.groupName = groupName;
119 this.key = key;
122 public static TemplateKey keyOf(TemplateImpl template) {
123 return new TemplateKey(template.getGroupName(), template.getKey());
126 public boolean equals(Object o) {
127 if (this == o) return true;
128 if (o == null || getClass() != o.getClass()) return false;
130 TemplateKey that = (TemplateKey)o;
132 if (groupName != null ? !groupName.equals(that.groupName) : that.groupName != null) return false;
133 if (key != null ? !key.equals(that.key) : that.key != null) return false;
135 return true;
138 public int hashCode() {
139 int result = groupName != null ? groupName.hashCode() : 0;
140 result = 31 * result + (key != null ? key.hashCode() : 0);
141 return result;
145 public TemplateSettings(SchemesManagerFactory schemesManagerFactory) {
148 myProcessor = new SchemeProcessor<TemplateGroup>() {
149 public TemplateGroup readScheme(final Document schemeContent)
150 throws InvalidDataException, IOException, JDOMException {
151 return readTemplateFile(schemeContent, schemeContent.getRootElement().getAttributeValue("group"), false, false,
152 getClass().getClassLoader());
156 public boolean shouldBeSaved(final TemplateGroup template) {
157 for (TemplateImpl t : template.getElements()) {
158 if (differsFromDefault(t)) {
159 return true;
162 return false;
165 public Document writeScheme(final TemplateGroup template) throws WriteExternalException {
166 Element templateSetElement = new Element(TEMPLATE_SET);
167 templateSetElement.setAttribute(GROUP, template.getName());
169 for (TemplateImpl t : template.getElements()) {
170 if (differsFromDefault(t)) {
171 saveTemplate(t, templateSetElement);
175 return new Document(templateSetElement);
178 public void initScheme(final TemplateGroup scheme) {
179 Collection<TemplateImpl> templates = scheme.getElements();
181 for (TemplateImpl template : templates) {
182 addTemplateImpl(template);
186 public void onSchemeAdded(final TemplateGroup scheme) {
187 for (TemplateImpl template : scheme.getElements()) {
188 addTemplateImpl(template);
192 public void onSchemeDeleted(final TemplateGroup scheme) {
193 for (TemplateImpl template : scheme.getElements()) {
194 removeTemplate(template);
198 public void onCurrentSchemeChanged(final Scheme newCurrentScheme) {
203 mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, myProcessor, RoamingType.PER_USER);
205 loadTemplates();
208 private boolean differsFromDefault(TemplateImpl t) {
209 TemplateImpl def = myDefaultTemplates.get(t.getKey());
210 if (def == null) return true;
211 return !t.equals(def) || !t.contextsEqual(def);
214 @NotNull
215 public File[] getExportFiles() {
216 return new File[]{getTemplateDirectory(true),PathManager.getDefaultOptionsFile()};
219 @NotNull
220 public String getPresentableName() {
221 return CodeInsightBundle.message("templates.export.display.name");
224 public static TemplateSettings getInstance() {
225 return ServiceManager.getService(TemplateSettings.class);
228 public void loadState(Element parentNode) {
229 Element element = parentNode.getChild(DEFAULT_SHORTCUT);
230 if (element != null) {
231 String shortcut = element.getAttributeValue(SHORTCUT);
232 if (TAB.equals(shortcut)) {
233 myDefaultShortcutChar = TAB_CHAR;
234 } else if (ENTER.equals(shortcut)) {
235 myDefaultShortcutChar = ENTER_CHAR;
236 } else {
237 myDefaultShortcutChar = SPACE_CHAR;
241 Element deleted = parentNode.getChild(DELETED_TEMPLATES);
242 if (deleted != null) {
243 List children = deleted.getChildren();
244 for (final Object aChildren : children) {
245 Element child = (Element)aChildren;
246 myDeletedTemplates.add(new TemplateKey(child.getAttributeValue(GROUP), child.getAttributeValue(NAME)));
250 for (TemplateKey templateKey : myDeletedTemplates) {
251 if (templateKey.groupName == null) {
252 final Collection<TemplateImpl> templates = myTemplates.get(templateKey.key);
253 for (TemplateImpl template : templates) {
254 removeTemplate(template);
257 else {
258 final TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName);
259 if (toDelete != null) {
260 removeTemplate(toDelete);
265 //TODO lesya reload schemes
268 public Element getState() {
269 Element parentNode = new Element("TemplateSettings");
270 Element element = new Element(DEFAULT_SHORTCUT);
271 if (myDefaultShortcutChar == TAB_CHAR) {
272 element.setAttribute(SHORTCUT, TAB);
273 } else if (myDefaultShortcutChar == ENTER_CHAR) {
274 element.setAttribute(SHORTCUT, ENTER);
275 } else {
276 element.setAttribute(SHORTCUT, SPACE);
278 parentNode.addContent(element);
280 if (myDeletedTemplates.size() > 0) {
281 Element deleted = new Element(DELETED_TEMPLATES);
282 for (final TemplateKey deletedTemplate : myDeletedTemplates) {
283 if (deletedTemplate.key != null) {
284 Element template = new Element(TEMPLATE);
285 template.setAttribute(NAME, deletedTemplate.key);
286 if (deletedTemplate.groupName != null) {
287 template.setAttribute(GROUP, deletedTemplate.groupName);
289 deleted.addContent(template);
292 parentNode.addContent(deleted);
294 return parentNode;
297 public String getLastSelectedTemplateKey() {
298 return myLastSelectedTemplateKey;
301 public void setLastSelectedTemplateKey(String key) {
302 myLastSelectedTemplateKey = key;
305 public TemplateImpl[] getTemplates() {
306 return myAllTemplates.toArray(new TemplateImpl[myAllTemplates.size()]);
309 public char getDefaultShortcutChar() {
310 return myDefaultShortcutChar;
313 public void setDefaultShortcutChar(char defaultShortcutChar) {
314 myDefaultShortcutChar = defaultShortcutChar;
317 public Collection<TemplateImpl> getTemplates(@NonNls String key) {
318 return myTemplates.get(key);
321 public TemplateImpl getTemplate(@NonNls String key, String group) {
322 final Collection<TemplateImpl> templates = myTemplates.get(key);
323 for (TemplateImpl template : templates) {
324 if (template.getGroupName().equals(group)) {
325 return template;
328 return null;
331 public Template getTemplateById(@NonNls String id) {
332 return myTemplatesById.get(id);
335 public int getMaxKeyLength() {
336 return myMaxKeyLength;
339 public void addTemplate(Template template) {
340 clearPreviouslyRegistered(template);
341 addTemplateImpl(template);
343 TemplateImpl templateImpl = (TemplateImpl)template;
344 String groupName = templateImpl.getGroupName();
345 TemplateGroup group = mySchemesManager.findSchemeByName(groupName);
346 if (group == null) {
347 group = new TemplateGroup(groupName);
348 mySchemesManager.addNewScheme(group, true);
350 group.addElement(templateImpl);
353 private void clearPreviouslyRegistered(final Template template) {
354 TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName());
355 if (existing != null) {
356 LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered");
357 TemplateGroup group = mySchemesManager.findSchemeByName(existing.getGroupName());
358 if (group != null) {
359 group.removeElement(existing);
360 if (group.isEmpty()) {
361 mySchemesManager.removeScheme(group);
364 myTemplates.removeValue(template.getKey(), existing);
368 private void addTemplateImpl(Template template) {
369 final TemplateImpl templateImpl = (TemplateImpl)template;
370 if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) {
371 myTemplates.putValue(template.getKey(), templateImpl);
372 myAllTemplates.add(templateImpl);
375 myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length());
376 myDeletedTemplates.remove(TemplateKey.keyOf((TemplateImpl)template));
380 private void addTemplateById(Template template) {
381 if (!myTemplatesById.containsKey(template.getId())) {
382 final String id = template.getId();
383 if (id != null) {
384 myTemplatesById.put(id, template);
389 public void removeTemplate(Template template) {
390 myTemplates.removeValue(template.getKey(), (TemplateImpl )template);
392 TemplateImpl templateImpl = (TemplateImpl)template;
393 myAllTemplates.remove(templateImpl);
394 String groupName = templateImpl.getGroupName();
395 TemplateGroup group = mySchemesManager.findSchemeByName(groupName);
397 if (group != null) {
398 group.removeElement((TemplateImpl)template);
399 if (group.isEmpty()) {
400 mySchemesManager.removeScheme(group);
406 private TemplateImpl addTemplate(String key, String string, String group, String description, String shortcut, boolean isDefault,
407 final String id) {
408 TemplateImpl template = new TemplateImpl(key, string, group);
409 template.setId(id);
410 template.setDescription(description);
411 if (TAB.equals(shortcut)) {
412 template.setShortcutChar(TAB_CHAR);
413 } else if (ENTER.equals(shortcut)) {
414 template.setShortcutChar(ENTER_CHAR);
415 } else if (SPACE.equals(shortcut)) {
416 template.setShortcutChar(SPACE_CHAR);
417 } else {
418 template.setShortcutChar(DEFAULT_CHAR);
420 if (isDefault) {
421 myDefaultTemplates.put(key, template);
423 return template;
426 @Nullable
427 private static File getTemplateDirectory(boolean toCreate) {
428 String directoryPath = PathManager.getConfigPath() + File.separator + TEMPLATES_CONFIG_FOLDER;
429 File directory = new File(directoryPath);
430 if (!directory.exists()) {
431 if (!toCreate) {
432 return null;
434 if (!directory.mkdir()) {
435 if (LOG.isDebugEnabled()) {
436 LOG.debug("cannot create directory: " + directory.getAbsolutePath());
438 return null;
441 return directory;
444 private void loadTemplates() {
446 Collection<TemplateGroup> loaded = mySchemesManager.loadSchemes();
447 for (TemplateGroup group : loaded) {
448 Collection<TemplateImpl> templates = group.getElements();
450 for (TemplateImpl template : templates) {
451 addTemplateImpl(template);
457 try {
458 for(DefaultLiveTemplatesProvider provider: Extensions.getExtensions(DefaultLiveTemplatesProvider.EP_NAME)) {
459 for (String defTemplate : provider.getDefaultLiveTemplateFiles()) {
460 String templateName = getDefaultTemplateName(defTemplate);
461 InputStream inputStream = DecodeDefaultsUtil.getDefaultsInputStream(provider, defTemplate);
462 if (inputStream != null) {
463 readDefTemplateFile(inputStream, templateName, provider.getClass().getClassLoader());
467 } catch (Exception e) {
468 LOG.error(e);
472 public static String getDefaultTemplateName(String defTemplate) {
473 return defTemplate.substring(defTemplate.lastIndexOf("/") + 1);
476 public void readDefTemplateFile(InputStream inputStream, String defGroupName) throws JDOMException, InvalidDataException, IOException {
477 readDefTemplateFile(inputStream, defGroupName, getClass().getClassLoader());
480 public void readDefTemplateFile(InputStream inputStream, String defGroupName, ClassLoader classLoader) throws JDOMException, InvalidDataException, IOException {
481 readTemplateFile(JDOMUtil.loadDocument(inputStream), defGroupName, true, true, classLoader);
484 @Nullable
485 public TemplateGroup readTemplateFile(Document document, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate) throws InvalidDataException {
486 return readTemplateFile(document, defGroupName, isDefault, registerTemplate, getClass().getClassLoader() );
489 @Nullable
490 public TemplateGroup readTemplateFile(Document document, @NonNls String defGroupName, boolean isDefault, boolean registerTemplate, ClassLoader classLoader) throws InvalidDataException {
491 if (document == null) {
492 throw new InvalidDataException();
494 Element root = document.getRootElement();
495 if (root == null || !TEMPLATE_SET.equals(root.getName())) {
496 throw new InvalidDataException();
499 String groupName = root.getAttributeValue(GROUP);
500 if (groupName == null || groupName.length() == 0) groupName = defGroupName;
502 TemplateGroup result = new TemplateGroup(groupName);
504 Map<String, TemplateImpl> created = new LinkedHashMap<String, TemplateImpl>();
506 for (final Object o1 : root.getChildren(TEMPLATE)) {
507 Element element = (Element)o1;
509 TemplateImpl template = readTemplateFromElement(isDefault, groupName, element, classLoader);
510 boolean doNotRegister = isDefault && (myDeletedTemplates.contains(TemplateKey.keyOf(template)) || myTemplatesById.containsKey(template.getId()));
512 if(!doNotRegister) {
513 created.put(template.getKey(), template);
517 if (registerTemplate) {
518 TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName());
519 if (existingScheme != null) {
520 result = existingScheme;
524 for (TemplateImpl template : created.values()) {
525 if (registerTemplate) {
526 clearPreviouslyRegistered(template);
527 addTemplateImpl(template);
530 result.addElement(template);
533 if (registerTemplate) {
534 TemplateGroup existingScheme = mySchemesManager.findSchemeByName(result.getName());
535 if (existingScheme == null && !result.isEmpty()) {
536 mySchemesManager.addNewScheme(result, false);
540 return result.isEmpty() ? null : result;
544 private TemplateImpl readTemplateFromElement(final boolean isDefault,
545 final String groupName,
546 final Element element,
547 ClassLoader classLoader) throws InvalidDataException {
548 String name = element.getAttributeValue(NAME);
549 String value = element.getAttributeValue(VALUE);
550 String description;
551 String resourceBundle = element.getAttributeValue(RESOURCE_BUNDLE);
552 String key = element.getAttributeValue(KEY);
553 String id = element.getAttributeValue(ID);
554 if (resourceBundle != null && key != null) {
555 if (classLoader == null) {
556 classLoader = getClass().getClassLoader();
558 ResourceBundle bundle = ResourceBundle.getBundle(resourceBundle, Locale.getDefault(), classLoader);
559 description = bundle.getString(key);
561 else {
562 description = element.getAttributeValue(DESCRIPTION);
564 String shortcut = element.getAttributeValue(SHORTCUT);
565 TemplateImpl template = addTemplate(name, value, groupName, description, shortcut, isDefault, id);
567 template.setToReformat(Boolean.parseBoolean(element.getAttributeValue(TO_REFORMAT)));
568 template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES)));
569 template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED)));
572 for (final Object o : element.getChildren(VARIABLE)) {
573 Element e = (Element)o;
574 String variableName = e.getAttributeValue(NAME);
575 String expression = e.getAttributeValue(EXPRESSION);
576 String defaultValue = e.getAttributeValue(DEFAULT_VALUE);
577 boolean isAlwaysStopAt = Boolean.parseBoolean(e.getAttributeValue(ALWAYS_STOP_AT));
578 template.addVariable(variableName, expression, defaultValue, isAlwaysStopAt);
581 Element context = element.getChild(CONTEXT);
582 if (context != null) {
583 template.getTemplateContext().readExternal(context);
586 return template;
589 public void readHiddenTemplateFile(Document document) throws InvalidDataException {
590 if (document == null) {
591 throw new InvalidDataException();
593 Element root = document.getRootElement();
594 if (root == null || !TEMPLATE_SET.equals(root.getName())) {
595 throw new InvalidDataException();
598 for (final Object o1 : root.getChildren(TEMPLATE)) {
600 addTemplateById(readTemplateFromElement(false, null, (Element)o1, getClass().getClassLoader()));
606 private static void saveTemplate(TemplateImpl template, Element templateSetElement) {
607 Element element = new Element(TEMPLATE);
608 final String id = template.getId();
609 if (id != null) {
610 element.setAttribute(ID, id);
612 element.setAttribute(NAME, template.getKey());
613 element.setAttribute(VALUE, template.getString());
614 if (template.getShortcutChar() == TAB_CHAR) {
615 element.setAttribute(SHORTCUT, TAB);
616 } else if (template.getShortcutChar() == ENTER_CHAR) {
617 element.setAttribute(SHORTCUT, ENTER);
618 } else if (template.getShortcutChar() == SPACE_CHAR) {
619 element.setAttribute(SHORTCUT, SPACE);
621 if (template.getDescription() != null) {
622 element.setAttribute(DESCRIPTION, template.getDescription());
624 element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat()));
625 element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames()));
626 if (template.isDeactivated()) {
627 element.setAttribute(DEACTIVATED, Boolean.toString(true));
630 for (int i = 0; i < template.getVariableCount(); i++) {
631 Element variableElement = new Element(VARIABLE);
632 variableElement.setAttribute(NAME, template.getVariableNameAt(i));
633 variableElement.setAttribute(EXPRESSION, template.getExpressionStringAt(i));
634 variableElement.setAttribute(DEFAULT_VALUE, template.getDefaultValueStringAt(i));
635 variableElement.setAttribute(ALWAYS_STOP_AT, Boolean.toString(template.isAlwaysStopAt(i)));
636 element.addContent(variableElement);
639 try {
640 Element contextElement = new Element(CONTEXT);
641 template.getTemplateContext().writeExternal(contextElement);
642 element.addContent(contextElement);
643 } catch (WriteExternalException e) {
645 templateSetElement.addContent(element);
648 public void setTemplates(List<TemplateGroup> newGroups) {
649 myTemplates.clear();
650 myAllTemplates.clear();
651 myDeletedTemplates.clear();
652 for (TemplateImpl template : myDefaultTemplates.values()) {
653 myDeletedTemplates.add(TemplateKey.keyOf(template));
655 mySchemesManager.clearAllSchemes();
656 myMaxKeyLength = 0;
657 for (TemplateGroup group : newGroups) {
658 if (!group.isEmpty()) {
659 mySchemesManager.addNewScheme(group, true);
660 for (TemplateImpl template : group.getElements()) {
661 clearPreviouslyRegistered(template);
662 addTemplateImpl(template);
668 public SchemesManager<TemplateGroup,TemplateGroup> getSchemesManager() {
669 return mySchemesManager;
672 public List<TemplateGroup> getTemplateGroups() {
673 return mySchemesManager.getAllSchemes();
676 public List<TemplateImpl> collectMatchingCandidates(String key, char shortcutChar, boolean hasArgument) {
677 final Collection<TemplateImpl> templates = getTemplates(key);
678 List<TemplateImpl> candidates = new ArrayList<TemplateImpl>();
679 for (TemplateImpl template : templates) {
680 if (template.isDeactivated()) {
681 continue;
683 if (getShortcutChar(template) != shortcutChar) {
684 continue;
686 if (template.isSelectionTemplate()) {
687 continue;
689 if (hasArgument && !template.hasArgument()) {
690 continue;
692 candidates.add(template);
694 return candidates;
697 private char getShortcutChar(TemplateImpl template) {
698 char c = template.getShortcutChar();
699 if (c == DEFAULT_CHAR) {
700 return getDefaultShortcutChar();
702 else {
703 return c;