allow using multiple live templates with the same abbreviation (as long as they're in different groups)

This commit is contained in:
Dmitry Jemerov
2009-10-08 18:11:45 +04:00
parent b2ee8d31fe
commit a35afb71e6
9 changed files with 170 additions and 100 deletions
@@ -533,19 +533,19 @@ public class EditTemplateDialog extends DialogWrapper {
protected void doOKAction() {
String key = myKeyField.getText().trim();
final String newGroup = (String)myGroupCombo.getSelectedItem();
for (TemplateGroup templateGroup : myTemplateGroups) {
for (TemplateImpl template : templateGroup.getElements()) {
if (template.getKey().equals(key) && myTemplate != template) {
Messages.showMessageDialog(
getContentPane(),
CodeInsightBundle.message("dialog.edit.template.error.already.exists", key, template.getGroupName()),
CodeInsightBundle.message("dialog.edit.template.error.title"),
Messages.getErrorIcon()
);
return;
if (templateGroup.getName().equals(newGroup)) {
for (TemplateImpl template : templateGroup.getElements()) {
if (template.getKey().equals(key) && myTemplate != template) {
Messages.showMessageDialog(getContentPane(),
CodeInsightBundle.message("dialog.edit.template.error.already.exists", key, template.getGroupName()),
CodeInsightBundle.message("dialog.edit.template.error.title"), Messages.getErrorIcon());
return;
}
}
}
}
if (!TemplateImplUtil.validateTemplateText(myTemplateEditor.getDocument().getText())) {
@@ -559,7 +559,7 @@ public class EditTemplateDialog extends DialogWrapper {
}
SchemesManager<TemplateGroup, TemplateGroup> schemesManager = TemplateSettings.getInstance().getSchemesManager();
TemplateGroup group = schemesManager.findSchemeByName((String)myGroupCombo.getSelectedItem());
TemplateGroup group = schemesManager.findSchemeByName(newGroup);
if (group != null && schemesManager.isShared(group)) {
Messages.showMessageDialog (
getContentPane(),
@@ -18,6 +18,7 @@ import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class ListTemplatesHandler implements CodeInsightActionHandler{
public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile file) {
@@ -27,17 +28,15 @@ public class ListTemplatesHandler implements CodeInsightActionHandler{
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
int offset = editor.getCaretModel().getOffset();
String prefix = getPrefix(editor.getDocument(), offset);
ArrayList<LookupItem> array = new ArrayList<LookupItem>();
List<TemplateImpl> matchingTemplates = new ArrayList<TemplateImpl>();
for (TemplateImpl template : SurroundWithTemplateHandler.getApplicableTemplates(editor, file, false)) {
String key = template.getKey();
if (key.startsWith(prefix)) {
array.add(new LookupItem(template, key));
if (template.getKey().startsWith(prefix)) {
matchingTemplates.add(template);
}
}
LookupElement[] items = array.toArray(new LookupElement[array.size()]);
if (items.length == 0){
if (matchingTemplates.size() == 0) {
String text = prefix.length() == 0
? CodeInsightBundle.message("templates.no.defined")
: CodeInsightBundle.message("templates.no.defined.with.prefix", prefix);
@@ -45,13 +44,24 @@ public class ListTemplatesHandler implements CodeInsightActionHandler{
return;
}
showTemplatesLookup(project, editor, prefix, matchingTemplates);
}
public static void showTemplatesLookup(final Project project, final Editor editor, String prefix, List<TemplateImpl> matchingTemplates) {
ArrayList<LookupItem> array = new ArrayList<LookupItem>();
for (TemplateImpl template: matchingTemplates) {
array.add(new LookupItem(template, template.getKey()));
}
LookupElement[] items = array.toArray(new LookupElement[array.size()]);
final LookupImpl lookup = (LookupImpl) LookupManager.getInstance(project).createLookup(editor, items, prefix, LookupArranger.DEFAULT);
lookup.addLookupListener(
new LookupAdapter() {
public void itemSelected(LookupEvent event) {
final TemplateImpl template = (TemplateImpl)event.getItem().getObject();
new WriteCommandAction(project) {
protected void run(Result result) throws Throwable {
TemplateManager.getInstance(project).startTemplate(editor, '\0');
((TemplateManagerImpl) TemplateManager.getInstance(project)).startTemplateWithPrefix(editor, template, null);
}
}.execute();
}
@@ -23,9 +23,7 @@ import com.intellij.util.PairProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
public class TemplateManagerImpl extends TemplateManager implements ProjectComponent {
protected Project myProject;
@@ -108,7 +106,7 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
}
public boolean startTemplate(@NotNull Editor editor, char shortcutChar) {
return startTemplate(this, editor, shortcutChar, null);
return startTemplate(editor, shortcutChar, null);
}
public void startTemplate(@NotNull final Editor editor, @NotNull Template template) {
@@ -164,11 +162,7 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
startTemplate(editor, null, template, listener, null);
}
public boolean startTemplate(TemplateManagerImpl templateManager, final Editor editor, char shortcutChar) {
return startTemplate(templateManager, editor, shortcutChar, null);
}
public boolean startTemplate(TemplateManagerImpl templateManager, final Editor editor, char shortcutChar, final PairProcessor<String, String> processor) {
public boolean startTemplate(final Editor editor, char shortcutChar, final PairProcessor<String, String> processor) {
final Document document = editor.getDocument();
PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, myProject);
if (file == null) return false;
@@ -176,36 +170,25 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
TemplateSettings templateSettings = TemplateSettings.getInstance();
CharSequence text = document.getCharsSequence();
final int caretOffset = editor.getCaretModel().getOffset();
TemplateImpl template = null;
int wordStart = 0;
String key = null;
List<TemplateImpl> candidates = Collections.emptyList();
for (int i = templateSettings.getMaxKeyLength(); i >= 1 ; i--) {
wordStart = caretOffset - i;
int wordStart = caretOffset - i;
if (wordStart < 0) {
continue;
}
String key = text.subSequence(wordStart, caretOffset).toString();
template = templateSettings.getTemplate(key);
if (template != null && template.isDeactivated()) {
template = null;
}
if (template != null) {
if (Character.isJavaIdentifierStart(key.charAt(0))) {
if (wordStart > 0 && Character.isJavaIdentifierPart(text.charAt(wordStart - 1))) {
template = null;
continue;
}
key = text.subSequence(wordStart, caretOffset).toString();
if (Character.isJavaIdentifierStart(key.charAt(0))) {
if (wordStart > 0 && Character.isJavaIdentifierPart(text.charAt(wordStart - 1))) {
continue;
}
break;
}
candidates = templateSettings.collectMatchingCandidates(key, shortcutChar);
if (!candidates.isEmpty()) break;
}
if (template == null) return false;
if (shortcutChar != 0 && getShortcutChar(template) != shortcutChar) {
return false;
}
if (template.isSelectionTemplate()) return false;
if (candidates.isEmpty()) return false;
CommandProcessor.getInstance().executeCommand(
myProject, new Runnable() {
@@ -215,39 +198,53 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
},
"", null
);
if (!isApplicable(file, caretOffset - template.getKey().length(), template)) {
candidates = filterApplicableCandidates(file, caretOffset - key.length(), candidates);
if (candidates.isEmpty()) {
return false;
}
if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), myProject)) {
return false;
return false;
}
final int wordStart0 = wordStart;
final TemplateImpl template0 = template;
final TemplateState templateState0 = templateManager.initTemplateState(editor);
if (candidates.size() == 1) {
TemplateImpl template = candidates.get(0);
startTemplateWithPrefix(editor, template, processor);
}
else {
ListTemplatesHandler.showTemplatesLookup(myProject, editor, key, candidates);
}
return true;
}
public void startTemplateWithPrefix(final Editor editor, final TemplateImpl template, @Nullable final PairProcessor<String, String> processor) {
final int caretOffset = editor.getCaretModel().getOffset();
final int wordStart = caretOffset - template.getKey().length();
final TemplateState templateState = initTemplateState(editor);
CommandProcessor commandProcessor = CommandProcessor.getInstance();
commandProcessor.executeCommand(
myProject, new Runnable() {
public void run() {
editor.getDocument().deleteString(wordStart0, caretOffset);
editor.getCaretModel().moveToOffset(wordStart0);
editor.getDocument().deleteString(wordStart, caretOffset);
editor.getCaretModel().moveToOffset(wordStart);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().removeSelection();
templateState0.start(template0, processor);
templateState.start(template, processor);
}
},
CodeInsightBundle.message("insert.code.template.command"), null
);
return true;
}
private static char getShortcutChar(TemplateImpl template) {
char c = template.getShortcutChar();
if (c == TemplateSettings.DEFAULT_CHAR) {
return TemplateSettings.getInstance().getDefaultShortcutChar();
}
else {
return c;
private static List<TemplateImpl> filterApplicableCandidates(PsiFile file, int offset, List<TemplateImpl> candidates) {
List<TemplateImpl> result = new ArrayList<TemplateImpl>();
for (TemplateImpl candidate : candidates) {
if (isApplicable(file, offset, candidate)) {
result.add(candidate);
}
}
return result;
}
public TemplateContextType getContextType(@NotNull PsiFile file, int offset) {
@@ -14,6 +14,7 @@ import com.intellij.openapi.options.SchemesManagerFactory;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.util.containers.MultiMap;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
@@ -45,7 +46,7 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
private @NonNls static final String TEMPLATE = "template";
private @NonNls static final String DELETED_TEMPLATES = "deleted_templates";
private final List<String> myDeletedTemplates = new ArrayList<String>();
private final List<TemplateKey> myDeletedTemplates = new ArrayList<TemplateKey>();
public static final char SPACE_CHAR = ' ';
public static final char TAB_CHAR = '\t';
@@ -79,7 +80,8 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
private static final @NonNls String TEMPLATES_CONFIG_FOLDER = "templates";
private final Map<String,Template> myTemplates = new LinkedHashMap<String,Template>();
private final List<TemplateImpl> myAllTemplates = new ArrayList<TemplateImpl>();
private final MultiMap<String,TemplateImpl> myTemplates = new MultiMap<String,TemplateImpl>();
private final Map<String,Template> myTemplatesById = new LinkedHashMap<String,Template>();
private final Map<String,TemplateImpl> myDefaultTemplates = new LinkedHashMap<String, TemplateImpl>();
@@ -92,6 +94,20 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
private final SchemeProcessor<TemplateGroup> myProcessor;
private static final String FILE_SPEC = "$ROOT_CONFIG$/templates";
private static class TemplateKey {
final String groupName;
final String key;
private TemplateKey(String groupName, String key) {
this.groupName = groupName;
this.key = key;
}
public static TemplateKey keyOf(TemplateImpl template) {
return new TemplateKey(template.getGroupName(), template.getKey());
}
}
public TemplateSettings(SchemesManagerFactory schemesManagerFactory) {
@@ -192,14 +208,22 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
List children = deleted.getChildren();
for (final Object aChildren : children) {
Element child = (Element)aChildren;
myDeletedTemplates.add(child.getAttributeValue(NAME));
myDeletedTemplates.add(new TemplateKey(child.getAttributeValue(NAME), child.getAttributeValue(GROUP)));
}
}
for (String name : myDeletedTemplates) {
Template toDelete = myTemplates.get(name);
if (toDelete != null) {
removeTemplate(toDelete);
for (TemplateKey templateKey : myDeletedTemplates) {
if (templateKey.groupName == null) {
final Collection<TemplateImpl> templates = myTemplates.get(templateKey.key);
for (TemplateImpl template : templates) {
removeTemplate(template);
}
}
else {
final TemplateImpl toDelete = getTemplate(templateKey.key, templateKey.groupName);
if (toDelete != null) {
removeTemplate(toDelete);
}
}
}
@@ -220,9 +244,10 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
if (myDeletedTemplates.size() > 0) {
Element deleted = new Element(DELETED_TEMPLATES);
for (final String myDeletedTemplate : myDeletedTemplates) {
for (final TemplateKey deletedTemplate : myDeletedTemplates) {
Element template = new Element(TEMPLATE);
template.setAttribute(NAME, myDeletedTemplate);
template.setAttribute(NAME, deletedTemplate.key);
template.setAttribute(GROUP, deletedTemplate.groupName);
deleted.addContent(template);
}
@@ -240,7 +265,7 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
}
public TemplateImpl[] getTemplates() {
return myTemplates.values().toArray(new TemplateImpl[myTemplates.size()]);
return myAllTemplates.toArray(new TemplateImpl[myAllTemplates.size()]);
}
public char getDefaultShortcutChar() {
@@ -251,8 +276,18 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
myDefaultShortcutChar = defaultShortcutChar;
}
public TemplateImpl getTemplate(@NonNls String key) {
return (TemplateImpl) myTemplates.get(key);
public Collection<TemplateImpl> getTemplates(@NonNls String key) {
return myTemplates.get(key);
}
public TemplateImpl getTemplate(@NonNls String key, String group) {
final Collection<TemplateImpl> templates = myTemplates.get(key);
for (TemplateImpl template : templates) {
if (template.getGroupName().equals(group)) {
return template;
}
}
return null;
}
public Template getTemplateById(@NonNls String id) {
@@ -278,7 +313,7 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
}
private void clearPreviouslyRegistered(final Template template) {
TemplateImpl existing = getTemplate(template.getKey());
TemplateImpl existing = getTemplate(template.getKey(), ((TemplateImpl) template).getGroupName());
if (existing != null) {
LOG.info("Template with key " + template.getKey() + " and id " + template.getId() + " already registered");
TemplateGroup group = mySchemesManager.findSchemeByName(existing.getGroupName());
@@ -288,17 +323,19 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
mySchemesManager.removeScheme(group);
}
}
myTemplates.remove(template.getKey());
myTemplates.removeValue(template.getKey(), existing);
}
}
private void addTemplateImpl(Template template) {
if (!myTemplates.containsKey(template.getKey())) {
myTemplates.put(template.getKey(), template);
myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length());
final TemplateImpl templateImpl = (TemplateImpl)template;
if (getTemplate(templateImpl.getKey(), templateImpl.getGroupName()) == null) {
myTemplates.putValue(template.getKey(), templateImpl);
myAllTemplates.add(templateImpl);
}
myDeletedTemplates.remove(template.getKey());
myMaxKeyLength = Math.max(myMaxKeyLength, template.getKey().length());
myDeletedTemplates.remove(TemplateKey.keyOf((TemplateImpl)template));
}
@@ -312,10 +349,11 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
}
public void removeTemplate(Template template) {
myTemplates.remove(template.getKey());
myTemplates.removeValue(template.getKey(), (TemplateImpl )template);
TemplateImpl templImpl = (TemplateImpl)template;
String groupName = templImpl.getGroupName();
TemplateImpl templateImpl = (TemplateImpl)template;
myAllTemplates.remove(templateImpl);
String groupName = templateImpl.getGroupName();
TemplateGroup group = mySchemesManager.findSchemeByName(groupName);
if (group != null) {
@@ -422,14 +460,11 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
Element element = (Element)o1;
TemplateImpl template = readTemplateFromElement(isDefault, groupName, element);
boolean doNotRegister = isDefault && (myDeletedTemplates.contains(template.getKey()) || myTemplates.containsKey(template.getKey()));
boolean doNotRegister = isDefault && (myDeletedTemplates.contains(TemplateKey.keyOf(template)) || myTemplates.containsKey(template.getKey()));
if(!doNotRegister) {
created.put(template.getKey(), template);
}
}
if (registerTemplate) {
@@ -562,7 +597,7 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
myTemplates.clear();
myDeletedTemplates.clear();
for (TemplateImpl template : myDefaultTemplates.values()) {
myDeletedTemplates.add(template.getKey());
myDeletedTemplates.add(TemplateKey.keyOf(template));
}
mySchemesManager.clearAllSchemes();
myMaxKeyLength = 0;
@@ -584,4 +619,32 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
public List<TemplateGroup> getTemplateGroups() {
return mySchemesManager.getAllSchemes();
}
public List<TemplateImpl> collectMatchingCandidates(String key, char shortcutChar) {
final Collection<TemplateImpl> templates = getTemplates(key);
List<TemplateImpl> candidates = new ArrayList<TemplateImpl>();
for (TemplateImpl template : templates) {
if (template.isDeactivated()) {
continue;
}
if (getShortcutChar(template) != shortcutChar) {
continue;
}
if (template.isSelectionTemplate()) {
continue;
}
candidates.add(template);
}
return candidates;
}
private char getShortcutChar(TemplateImpl template) {
char c = template.getShortcutChar();
if (c == DEFAULT_CHAR) {
return getDefaultShortcutChar();
}
else {
return c;
}
}
}
@@ -73,7 +73,7 @@ public class TemplateState implements Disposable {
private boolean myTemplateIndented = false;
private Document myDocument;
private boolean myFinished;
private PairProcessor<String, String> myProcessor;
@Nullable private PairProcessor<String, String> myProcessor;
public TemplateState(@NotNull Project project, final Editor editor) {
myProject = project;
@@ -214,7 +214,7 @@ public class TemplateState implements Disposable {
}
}
public void start(TemplateImpl template, final PairProcessor<String, String> processor) {
public void start(TemplateImpl template, @Nullable final PairProcessor<String, String> processor) {
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
myProcessor = processor;
@@ -31,7 +31,7 @@ public class EnterHandler extends BaseEnterHandler {
TemplateManagerImpl templateManager = (TemplateManagerImpl)TemplateManager.getInstance(project);
if (!templateManager.startTemplate(templateManager, editor, TemplateSettings.ENTER_CHAR)) {
if (!templateManager.startTemplate(editor, TemplateSettings.ENTER_CHAR)) {
myOriginalHandler.execute(editor, dataContext);
}
}
@@ -29,7 +29,7 @@ public class SpaceHandler implements TypedActionHandler {
}
TemplateManagerImpl templateManager = (TemplateManagerImpl) TemplateManagerImpl.getInstance(project);
if (!templateManager.startTemplate(templateManager, editor, TemplateSettings.SPACE_CHAR)) {
if (!templateManager.startTemplate(editor, TemplateSettings.SPACE_CHAR)) {
myOriginalHandler.execute(editor, charTyped, dataContext);
}
}
@@ -26,7 +26,7 @@ public class TabHandler extends EditorWriteActionHandler {
TemplateManagerImpl templateManager = (TemplateManagerImpl) TemplateManagerImpl.getInstance(project);
if (!templateManager.startTemplate(templateManager, editor, TemplateSettings.TAB_CHAR)) {
if (!templateManager.startTemplate(editor, TemplateSettings.TAB_CHAR)) {
myOriginalHandler.execute(editor, dataContext);
}
}
@@ -295,7 +295,7 @@ dialog.edit.template.checkbox.smart.type.completion=Smart type c&ompletion
dialog.edit.template.checkbox.other=O&ther
dialog.edit.template.error.title=Cannot Save
dialog.edit.template.error.malformed.abbreviation=Cannot save the template.\nTemplate abbreviation should contain only letters, digits, dots and hyphens.
dialog.edit.template.error.already.exists=Cannot save the template.\nTemplate with the abbreviation \"{0}\"\nalready exists in group \"{1}\".\nTry another abbreviation.
dialog.edit.template.error.already.exists=Cannot save the template.\nTemplate with the abbreviation \"{0}\"\nalready exists in group \"{1}\".\nPlease choose a different abbreviation or group.
dialog.edit.template.error.malformed.template=Error parsing the template
finish.template.command=Finish Template
insert.code.template.command=Insert Code Template