From 814a79afdea6fc6a7db787b13d531d9dcc69b1ce Mon Sep 17 00:00:00 2001 From: Alexander Zolotov Date: Wed, 16 Apr 2014 18:41:53 +0400 Subject: [PATCH] Unify prefix calculation rules for live templates --- .../PostfixTemplateLookupElement.java | 5 +- .../templates/PostfixLiveTemplate.java | 8 +- .../list/ComplexKeyWithFullMatchPrefix.java | 5 + .../ComplexKeyWithFullMatchPrefix_after.java | 5 + .../list/ComplexKeyWithNotMatchedPrefix.java | 5 + ...NotMatchedPrefixAfterNonJavaCharacter.java | 5 + ...chedPrefixAfterNonJavaCharacter_after.java | 5 + .../ComplexKeyWithNotMatchedPrefix_after.java | 5 + .../list/ComplexKeyWithPartialPrefix.java | 5 + .../ComplexKeyWithPartialPrefix_after.java | 5 + .../list/ComplexKeyWithoutPrefix.java | 5 + .../list/ComplexKeyWithoutPrefix_after.java | 5 + .../template/list/FullMatchPrefix.java | 5 + .../template/list/FullMatchPrefix_after.java | 5 + .../list/MatchingByTemplateDescription.java | 5 + .../MatchingByTemplateDescription_after.java | 5 + .../template/list/NotMatchedPrefix.java | 5 + ...NotMatchedPrefixAfterNonJavaCharacter.java | 5 + ...chedPrefixAfterNonJavaCharacter_after.java | 5 + .../template/list/NotMatchedPrefix_after.java | 5 + .../template/list/PartialPrefix.java | 5 + .../template/list/PartialPrefix_after.java | 5 + .../template/list/WithoutPrefix.java | 5 + .../template/list/WithoutPrefix_after.java | 5 + .../template/ListTemplateActionTest.java | 114 ++++++++++++++++++ .../CompletionProgressIndicator.java | 4 +- .../actions/SurroundWithAction.java | 6 +- .../surroundWith/SurroundWithHandler.java | 6 +- .../lookup/impl/actions/ChooseItemAction.java | 46 ++----- .../template/CustomTemplateCallback.java | 30 +++-- .../actions/SaveAsTemplateAction.java | 9 +- .../template/impl/ListTemplatesHandler.java | 101 ++++++++++------ .../LiveTemplateCompletionContributor.java | 107 ++++++++-------- .../impl/SurroundWithTemplateHandler.java | 48 +------- .../template/impl/TemplateManagerImpl.java | 33 ++++- .../src/messages/CodeInsightBundle.properties | 1 - .../template/emmet/ZenCodingTemplate.java | 27 ++++- 37 files changed, 444 insertions(+), 211 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/FullMatchPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/FullMatchPrefix_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription.java create mode 100644 java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter.java create mode 100644 java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/PartialPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/PartialPrefix_after.java create mode 100644 java/java-tests/testData/codeInsight/template/list/WithoutPrefix.java create mode 100644 java/java-tests/testData/codeInsight/template/list/WithoutPrefix_after.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/template/ListTemplateActionTest.java diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateLookupElement.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateLookupElement.java index 0aeba2baaced..73bbf697f633 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateLookupElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateLookupElement.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import com.intellij.codeInsight.template.impl.CustomLiveTemplateLookupElement; import com.intellij.codeInsight.template.postfix.templates.PostfixLiveTemplate; import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; public class PostfixTemplateLookupElement extends CustomLiveTemplateLookupElement { @@ -30,7 +31,7 @@ public class PostfixTemplateLookupElement extends CustomLiveTemplateLookupElemen @NotNull PostfixTemplate postfixTemplate, @NotNull String templateKey, boolean sudden) { - super(liveTemplate, templateKey, postfixTemplate.getPresentableName(), postfixTemplate.getDescription(), sudden, true); + super(liveTemplate, templateKey, StringUtil.trimStart(templateKey, "."), postfixTemplate.getDescription(), sudden, true); myTemplate = postfixTemplate; } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixLiveTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixLiveTemplate.java index a165dcb0ed17..d738fb8d227b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixLiveTemplate.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixLiveTemplate.java @@ -180,15 +180,15 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase { public Collection getLookupElements(@NotNull PsiFile file, @NotNull Editor editor, int offset) { String key = computeTemplateKeyWithoutContextChecking(editor.getDocument().getCharsSequence(), offset); if (key != null && editor.getCaretModel().getCaretCount() == 1) { - Map result = ContainerUtil.newHashMap(); + Collection result = ContainerUtil.newHashSet(); Condition isApplicationTemplateFunction = createIsApplicationTemplateFunction(key, file, editor); for (Map.Entry entry : myTemplates.entrySet()) { PostfixTemplate postfixTemplate = entry.getValue(); - if (entry.getKey().startsWith(key) && isApplicationTemplateFunction.value(postfixTemplate)) { - result.put(postfixTemplate.getKey(), new PostfixTemplateLookupElement(this, postfixTemplate, entry.getKey(), false)); + if (isApplicationTemplateFunction.value(postfixTemplate)) { + result.add(new PostfixTemplateLookupElement(this, postfixTemplate, entry.getKey(), false)); } } - return result.values(); + return result; } return super.getLookupElements(file, editor, offset); } diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix.java new file mode 100644 index 000000000000..9da7f6849348 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + complex key + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix_after.java new file mode 100644 index 000000000000..63e36d31c7a8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithFullMatchPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + complex template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix.java new file mode 100644 index 000000000000..6cfe6e4d504c --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefixcomplex key + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter.java new file mode 100644 index 000000000000..fdad1b280958 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefix:comple + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter_after.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter_after.java new file mode 100644 index 000000000000..e6ad365b3ae9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefix:complex template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix_after.java new file mode 100644 index 000000000000..f52e550bf162 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithNotMatchedPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefixcomplex keycomplex template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix.java new file mode 100644 index 000000000000..abd92e0d9531 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + complex k + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix_after.java new file mode 100644 index 000000000000..63e36d31c7a8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithPartialPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + complex template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix.java new file mode 100644 index 000000000000..10fc39c14455 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix_after.java new file mode 100644 index 000000000000..63e36d31c7a8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/ComplexKeyWithoutPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + complex template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/FullMatchPrefix.java b/java/java-tests/testData/codeInsight/template/list/FullMatchPrefix.java new file mode 100644 index 000000000000..cd1ce95251b7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/FullMatchPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + simple + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/FullMatchPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/FullMatchPrefix_after.java new file mode 100644 index 000000000000..d5540c1e7a01 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/FullMatchPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + simple template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription.java b/java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription.java new file mode 100644 index 000000000000..2a382dc87345 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + descri + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription_after.java b/java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription_after.java new file mode 100644 index 000000000000..d5540c1e7a01 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/MatchingByTemplateDescription_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + simple template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix.java b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix.java new file mode 100644 index 000000000000..fa0b3363d465 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefixsimpl + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter.java b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter.java new file mode 100644 index 000000000000..192539c1277b --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefix:simp + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter_after.java b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter_after.java new file mode 100644 index 000000000000..7e7699d1c99d --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefixAfterNonJavaCharacter_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefix:simple template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix_after.java new file mode 100644 index 000000000000..252a61762201 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/NotMatchedPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + prefixsimplsimple template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/PartialPrefix.java b/java/java-tests/testData/codeInsight/template/list/PartialPrefix.java new file mode 100644 index 000000000000..76f88e6eb4ff --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/PartialPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + simp + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/PartialPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/PartialPrefix_after.java new file mode 100644 index 000000000000..d5540c1e7a01 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/PartialPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + simple template text + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/WithoutPrefix.java b/java/java-tests/testData/codeInsight/template/list/WithoutPrefix.java new file mode 100644 index 000000000000..10fc39c14455 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/WithoutPrefix.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/list/WithoutPrefix_after.java b/java/java-tests/testData/codeInsight/template/list/WithoutPrefix_after.java new file mode 100644 index 000000000000..d5540c1e7a01 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/list/WithoutPrefix_after.java @@ -0,0 +1,5 @@ +class A { + public static void main() { + simple template text + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/ListTemplateActionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/template/ListTemplateActionTest.java new file mode 100644 index 000000000000..6c06350b059d --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/ListTemplateActionTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.template; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.lookup.Lookup; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.actions.ListTemplatesAction; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +public class ListTemplateActionTest extends LightCodeInsightFixtureTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + TemplateManagerImpl.setTemplateTesting(getProject(), getTestRootDisposable()); + addTemplate("simple", "simple template text", "description"); + addTemplate("complex key", "complex template text", ""); + } + + private void addTemplate(String key, String text, String description) { + TemplateManager manager = TemplateManager.getInstance(getProject()); + TemplateImpl template = (TemplateImpl)manager.createTemplate(key, "test", text); + template.setDescription(description); + TemplateContextType contextType = ContainerUtil.findInstance(TemplateContextType.EP_NAME.getExtensions(), JavaCodeContextType.class); + template.getTemplateContext().setEnabled(contextType, true); + LiveTemplateTest.addTemplate(template, getTestRootDisposable()); + } + + @Override + protected String getBasePath() { + return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/template/list/"; + } + + public void testWithoutPrefix() { + doTest("simple"); + } + + public void testPartialPrefix() { + doTest("simple"); + } + + public void testFullMatchPrefix() { + doTest("simple"); + } + + public void testNotMatchedPrefix() { + doTest("simple"); + } + + public void testNotMatchedPrefixAfterNonJavaCharacter() { + doTest("simple"); + } + + public void testMatchingByTemplateDescription() { + doTest("simple"); + } + + public void testComplexKeyWithoutPrefix() { + doTest("complex key"); + } + + public void testComplexKeyWithPartialPrefix() { + doTest("complex key"); + } + + public void testComplexKeyWithFullMatchPrefix() { + doTest("complex key"); + } + + public void testComplexKeyWithNotMatchedPrefix() { + doTest("complex key"); + } + + public void testComplexKeyWithNotMatchedPrefixAfterNonJavaCharacter() { + doTest("complex key"); + } + + private void doTest(@NotNull String lookupText) { + myFixture.configureByFile(getTestName(false) + ".java"); + new ListTemplatesAction().actionPerformedImpl(myFixture.getProject(), myFixture.getEditor()); + + LookupElement[] elements = myFixture.getLookupElements(); + assertNotNull(elements); + for (LookupElement element : elements) { + if (lookupText.equals(element.getLookupString())) { + myFixture.getLookup().setCurrentItem(element); + myFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR); + myFixture.checkResultByFile(getTestName(false) + "_after.java"); + return; + } + } + //noinspection ConstantConditions + fail("Lookup element with text '" + lookupText + "' not found:\n" + StringUtil.join(myFixture.getLookupElementStrings(), "\n")); + } +} + \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index c24fb9c1f6d0..e064ddc5187d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -601,7 +601,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement final CharSequence text = myEditor.getDocument().getCharsSequence(); for (Pair> pair : myRestartingPrefixConditions) { int start = pair.first; - if (caretOffset >= start) { + if (caretOffset >= start && start >= 0) { final String newPrefix = text.subSequence(start, caretOffset).toString(); if (pair.second.accepts(newPrefix)) { scheduleRestart(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/SurroundWithAction.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/SurroundWithAction.java index 6f0d6dfe561d..53a0fe15b7bf 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/SurroundWithAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/SurroundWithAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,9 @@ package com.intellij.codeInsight.generation.actions; import com.intellij.codeInsight.CodeInsightActionHandler; -import com.intellij.codeInsight.template.impl.SurroundWithTemplateHandler; import com.intellij.codeInsight.actions.BaseCodeInsightAction; import com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.lang.Language; import com.intellij.lang.LanguageSurrounders; import com.intellij.openapi.editor.Editor; @@ -50,7 +50,7 @@ public class SurroundWithAction extends BaseCodeInsightAction{ return true; } - if (!SurroundWithTemplateHandler.getApplicableTemplates(editor, file, true).isEmpty()) { + if (!TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, true).isEmpty()) { return true; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/surroundWith/SurroundWithHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/surroundWith/SurroundWithHandler.java index a461ab9d61b1..1fd552db96d2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/surroundWith/SurroundWithHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/surroundWith/SurroundWithHandler.java @@ -23,8 +23,8 @@ import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.template.CustomLiveTemplate; import com.intellij.codeInsight.template.TemplateManager; import com.intellij.codeInsight.template.impl.InvokeTemplateAction; -import com.intellij.codeInsight.template.impl.SurroundWithTemplateHandler; import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.WrapWithCustomTemplateAction; import com.intellij.ide.DataManager; import com.intellij.lang.Language; @@ -247,8 +247,8 @@ public class SurroundWithHandler implements CodeInsightActionHandler { } } - List customTemplates = SurroundWithTemplateHandler.getApplicableCustomTemplates(editor, file); - List templates = SurroundWithTemplateHandler.getApplicableTemplates(editor, file, true); + List customTemplates = TemplateManagerImpl.listApplicableCustomTemplates(editor, file, true); + List templates = TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, true); if (!templates.isEmpty() || !customTemplates.isEmpty()) { applicable.add(new Separator("Live templates")); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java index 903f2cb88f30..ebd20cf98202 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java @@ -22,23 +22,21 @@ import com.intellij.codeInsight.completion.CompletionService; import com.intellij.codeInsight.lookup.Lookup; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.lookup.impl.LookupImpl; -import com.intellij.codeInsight.template.CustomLiveTemplate; -import com.intellij.codeInsight.template.CustomLiveTemplateBase; -import com.intellij.codeInsight.template.CustomTemplateCallback; import com.intellij.codeInsight.template.impl.*; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorAction; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; -import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import java.util.List; + public abstract class ChooseItemAction extends EditorAction { - public ChooseItemAction(Handler handler){ + public ChooseItemAction(Handler handler) { super(handler); } @@ -90,7 +88,6 @@ public abstract class ChooseItemAction extends EditorAction { return true; } - } public static boolean hasTemplatePrefix(LookupImpl lookup, char shortcutChar) { @@ -109,6 +106,7 @@ public abstract class ChooseItemAction extends EditorAction { if (file == null) return false; final Editor editor = lookup.getEditor(); + final int offset = editor.getCaretModel().getOffset(); PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument()); final LiveTemplateLookupElement liveTemplateLookup = ContainerUtil.findInstance(lookup.getItems(), LiveTemplateLookupElement.class); @@ -118,34 +116,14 @@ public abstract class ChooseItemAction extends EditorAction { // in this case we should find live template with appropriate prefix (custom live templates doesn't participate in this action). // - completion provider worked too long: // in this case we should check custom templates that provides completion lookup. - - final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file, false); - for (CustomLiveTemplate customLiveTemplate : CustomLiveTemplate.EP_NAME.getExtensions()) { - if (customLiveTemplate instanceof CustomLiveTemplateBase) { - final int offset = editor.getCaretModel().getOffset(); - if (customLiveTemplate.getShortcut() == shortcutChar - && TemplateManagerImpl.isApplicable(customLiveTemplate, editor, file) - && ((CustomLiveTemplateBase)customLiveTemplate).hasCompletionItem(file, offset)) { - return customLiveTemplate.computeTemplateKey(callback) != null; - } - } + if (LiveTemplateCompletionContributor.customTemplateAvailableAndHasCompletionItem(shortcutChar, editor, file, offset)) { + return true; } - - final int end = editor.getCaretModel().getOffset(); - final int start = lookup.getLookupStart(); - final String prefix = !lookup.getItems().isEmpty() - ? editor.getDocument().getText(TextRange.create(start, end)) - : ListTemplatesHandler.getPrefix(editor.getDocument(), end, false); - - if (TemplateSettings.getInstance().getTemplates(prefix).isEmpty()) { - return false; - } - - for (TemplateImpl template : SurroundWithTemplateHandler.getApplicableTemplates(editor, file, false)) { - if (prefix.equals(template.getKey()) && shortcutChar == TemplateSettings.getInstance().getShortcutChar(template)) { - return true; - } + List templates = TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, false); + TemplateImpl template = LiveTemplateCompletionContributor.findFullMatchedApplicableTemplate(editor, offset, templates); + if (template != null && shortcutChar == TemplateSettings.getInstance().getShortcutChar(template)) { + return true; } return false; } @@ -158,20 +136,22 @@ public abstract class ChooseItemAction extends EditorAction { super(new Handler(true, Lookup.NORMAL_SELECT_CHAR)); } } + public static class Replacing extends ChooseItemAction { public Replacing() { super(new Handler(false, Lookup.REPLACE_SELECT_CHAR)); } } + public static class CompletingStatement extends ChooseItemAction { public CompletingStatement() { super(new Handler(true, Lookup.COMPLETE_STATEMENT_SELECT_CHAR)); } } + public static class ChooseWithDot extends ChooseItemAction { public ChooseWithDot() { super(new Handler(false, '.')); } } - } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java index e03ae577ffca..896dc823ec78 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java @@ -39,10 +39,10 @@ import java.util.Set; */ public class CustomTemplateCallback { private final TemplateManager myTemplateManager; - private final Editor myEditor; - private final PsiFile myFile; + @NotNull private final Editor myEditor; + @NotNull private final PsiFile myFile; private final int myOffset; - private final Project myProject; + @NotNull private final Project myProject; private final boolean myInInjectedFragment; private Set myApplicableContextTypes; @@ -58,6 +58,11 @@ public class CustomTemplateCallback { myEditor = myInInjectedFragment ? InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, file, myOffset) : editor; } + public TemplateManager getTemplateManager() { + return myTemplateManager; + } + + @NotNull public PsiFile getFile() { return myFile; } @@ -71,7 +76,7 @@ public class CustomTemplateCallback { return myOffset; } - private static int getOffset(boolean wrapping, Editor editor) { + private static int getOffset(boolean wrapping, @NotNull Editor editor) { if (wrapping) { return editor.getSelectionModel().getSelectionStart(); } @@ -98,29 +103,21 @@ public class CustomTemplateCallback { return result; } - private boolean isAvailableTemplate(TemplateImpl template) { + private boolean isAvailableTemplate(@NotNull TemplateImpl template) { if (myApplicableContextTypes == null) { myApplicableContextTypes = TemplateManagerImpl.getApplicableContextTypes(myFile, myOffset); } return !template.isDeactivated() && TemplateManagerImpl.isApplicable(template, myApplicableContextTypes); } - public void startTemplate(Template template, Map predefinedValues, TemplateEditingListener listener) { + public void startTemplate(@NotNull Template template, Map predefinedValues, TemplateEditingListener listener) { if(myInInjectedFragment) { template.setToReformat(false); } myTemplateManager.startTemplate(myEditor, template, false, predefinedValues, listener); } - public void startTemplate() { - Map template2Argument = - ((TemplateManagerImpl)myTemplateManager).findMatchingTemplates(myFile, myEditor, null, TemplateSettings.getInstance()); - Runnable runnable = ((TemplateManagerImpl)myTemplateManager).startNonCustomTemplates(template2Argument, myEditor, null); - if (runnable != null) { - runnable.run(); - } - } - + @NotNull private static List getMatchingTemplates(@NotNull String templateKey) { TemplateSettings settings = TemplateSettings.getInstance(); List candidates = new ArrayList(); @@ -142,11 +139,12 @@ public class CustomTemplateCallback { return myFile.getFileType(); } + @NotNull public Project getProject() { return myProject; } - public void deleteTemplateKey(String key) { + public void deleteTemplateKey(@NotNull String key) { int caretAt = myEditor.getCaretModel().getOffset(); myEditor.getDocument().deleteString(caretAt - key.length(), caretAt); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/actions/SaveAsTemplateAction.java b/platform/lang-impl/src/com/intellij/codeInsight/template/actions/SaveAsTemplateAction.java index 5048a0849695..0feed37aab89 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/actions/SaveAsTemplateAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/actions/SaveAsTemplateAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,10 @@ package com.intellij.codeInsight.template.actions; import com.intellij.codeInsight.template.TemplateContextType; import com.intellij.codeInsight.template.impl.*; import com.intellij.lang.StdLanguages; -import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.WriteCommandAction; @@ -130,7 +133,7 @@ public class SaveAsTemplateAction extends AnAction { PsiFile copy; AccessToken token = WriteAction.start(); try { - copy = SurroundWithTemplateHandler.insertDummyIdentifier(editor, file); + copy = TemplateManagerImpl.insertDummyIdentifier(editor, file); } finally { token.finish(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java index 45e2e2c08a85..798e82ca4ed8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java @@ -36,6 +36,7 @@ import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.util.containers.ContainerUtil; @@ -57,50 +58,80 @@ public class ListTemplatesHandler implements CodeInsightActionHandler { PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); int offset = editor.getCaretModel().getOffset(); - String prefix = getPrefix(editor.getDocument(), offset, false); - String prefixWithoutDots = getPrefix(editor.getDocument(), offset, true); + List applicableTemplates = TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, false); - List matchingTemplates = new ArrayList(); - ArrayList applicableTemplates = SurroundWithTemplateHandler.getApplicableTemplates(editor, file, false); - final Pattern prefixSearchPattern = Pattern.compile(".*\\b" + prefixWithoutDots + ".*"); - for (TemplateImpl template : applicableTemplates) { - final String templateDescription = template.getDescription(); - if (template.getKey().startsWith(prefix) || - !prefixWithoutDots.isEmpty() && templateDescription != null && prefixSearchPattern.matcher(templateDescription).matches()) { - matchingTemplates.add(template); + Map matchingTemplates = filterTemplatesByPrefix(applicableTemplates, editor, offset, false, true); + MultiMap customTemplatesLookupElements = getCustomTemplatesLookupItems(editor, file, offset); + + if (matchingTemplates.isEmpty()) { + for (TemplateImpl template : applicableTemplates) { + matchingTemplates.put(template, null); } } - MultiMap customTemplatesLookupElements = listApplicableCustomTemplates(editor, file, offset); - - if (matchingTemplates.isEmpty()) { - matchingTemplates.addAll(applicableTemplates); - prefixWithoutDots = ""; - } - if (matchingTemplates.isEmpty() && customTemplatesLookupElements.isEmpty()) { - String text = prefixWithoutDots.length() == 0 - ? CodeInsightBundle.message("templates.no.defined") - : CodeInsightBundle.message("templates.no.defined.with.prefix", prefix); - HintManager.getInstance().showErrorHint(editor, text); + HintManager.getInstance().showErrorHint(editor, CodeInsightBundle.message("templates.no.defined")); return; } - Collections.sort(matchingTemplates, TemplateListPanel.TEMPLATE_COMPARATOR); - showTemplatesLookup(project, editor, file, prefixWithoutDots, matchingTemplates, customTemplatesLookupElements); + showTemplatesLookup(project, editor, file, matchingTemplates, customTemplatesLookupElements); + } + + public static Map filterTemplatesByPrefix(@NotNull Collection templates, @NotNull Editor editor, + int offset, boolean fullMatch, boolean searchInDescription) { + CharSequence documentText = editor.getDocument().getCharsSequence().subSequence(0, offset); + + String prefixWithoutDots = computeDescriptionMatchingPrefix(editor.getDocument(), offset); + Pattern prefixSearchPattern = Pattern.compile(".*\\b" + prefixWithoutDots + ".*"); + + Map matchingTemplates = new TreeMap(TemplateListPanel.TEMPLATE_COMPARATOR); + for (TemplateImpl template : templates) { + String templateKey = template.getKey(); + if (fullMatch) { + int startOffset = documentText.length() - templateKey.length(); + if (startOffset <= 0 || !Character.isJavaIdentifierPart(documentText.charAt(startOffset - 1))) { + // after non-identifier + if (StringUtil.endsWith(documentText, templateKey)) { + matchingTemplates.put(template, templateKey); + } + } + } + else { + for (int i = templateKey.length(); i > 0; i--) { + String prefix = templateKey.substring(0, i); + int startOffset = documentText.length() - i; + if (startOffset > 0 && Character.isJavaIdentifierPart(documentText.charAt(startOffset - 1))) { + // after java identifier + continue; + } + if (StringUtil.endsWith(documentText, prefix)) { + matchingTemplates.put(template, prefix); + break; + } + } + } + + if (searchInDescription) { + String templateDescription = template.getDescription(); + if (!prefixWithoutDots.isEmpty() && templateDescription != null && prefixSearchPattern.matcher(templateDescription).matches()) { + matchingTemplates.put(template, prefixWithoutDots); + } + } + } + + return matchingTemplates; } private static void showTemplatesLookup(final Project project, final Editor editor, final PsiFile file, - @NotNull String prefix, - @NotNull List matchingTemplates, + @NotNull Map matchingTemplates, @NotNull MultiMap customTemplatesLookupElements) { - final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, LookupElement.EMPTY_ARRAY, prefix, - new TemplatesArranger()); - for (TemplateImpl template : matchingTemplates) { - lookup.addItem(createTemplateElement(template), new PlainPrefixMatcher(prefix)); + LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, LookupElement.EMPTY_ARRAY, "", new TemplatesArranger()); + for (Map.Entry entry : matchingTemplates.entrySet()) { + TemplateImpl template = entry.getKey(); + lookup.addItem(createTemplateElement(template), new PlainPrefixMatcher(StringUtil.notNullize(entry.getValue()))); } for (Map.Entry> entry : customTemplatesLookupElements.entrySet()) { @@ -112,11 +143,13 @@ public class ListTemplatesHandler implements CodeInsightActionHandler { showLookup(lookup, file); } - private static MultiMap listApplicableCustomTemplates(@NotNull Editor editor, @NotNull PsiFile file, int offset) { + public static MultiMap getCustomTemplatesLookupItems(@NotNull Editor editor, + @NotNull PsiFile file, + int offset) { final MultiMap result = MultiMap.create(); CustomTemplateCallback customTemplateCallback = new CustomTemplateCallback(editor, file, false); - for (CustomLiveTemplate customLiveTemplate : CustomLiveTemplate.EP_NAME.getExtensions()) { - if (customLiveTemplate instanceof CustomLiveTemplateBase && TemplateManagerImpl.isApplicable(customLiveTemplate, editor, file)) { + for (CustomLiveTemplate customLiveTemplate : TemplateManagerImpl.listApplicableCustomTemplates(editor, file, false)) { + if (customLiveTemplate instanceof CustomLiveTemplateBase) { String customTemplatePrefix = ((CustomLiveTemplateBase)customLiveTemplate).computeTemplateKeyWithoutContextChecking(customTemplateCallback); if (customTemplatePrefix != null) { result.putValues(customTemplatePrefix, ((CustomLiveTemplateBase)customLiveTemplate).getLookupElements(file, editor, offset)); @@ -182,13 +215,13 @@ public class ListTemplatesHandler implements CodeInsightActionHandler { return true; } - public static String getPrefix(Document document, int offset, boolean lettersOnly) { + private static String computeDescriptionMatchingPrefix(Document document, int offset) { CharSequence chars = document.getCharsSequence(); int start = offset; while (true) { if (start == 0) break; char c = chars.charAt(start - 1); - if (!(Character.isJavaIdentifierPart(c) || !lettersOnly && c == '.')) break; + if (!(Character.isJavaIdentifierPart(c))) break; start--; } return chars.subSequence(start, offset).toString(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java index 0bbe11f748d1..d9fec79a1e12 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateCompletionContributor.java @@ -20,11 +20,10 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.template.CustomLiveTemplate; import com.intellij.codeInsight.template.CustomLiveTemplateBase; import com.intellij.codeInsight.template.CustomTemplateCallback; -import com.intellij.codeInsight.template.TemplateContextType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.patterns.PlatformPatterns; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiPlainTextFile; @@ -35,11 +34,13 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Set; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import static com.intellij.codeInsight.template.impl.ListTemplatesHandler.filterTemplatesByPrefix; + /** * @author peter */ @@ -52,7 +53,7 @@ public class LiveTemplateCompletionContributor extends CompletionContributor { } return Registry.is("show.live.templates.in.completion"); } - + public LiveTemplateCompletionContributor() { extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new CompletionProvider() { @Override @@ -60,14 +61,14 @@ public class LiveTemplateCompletionContributor extends CompletionContributor { ProcessingContext context, @NotNull CompletionResultSet result) { final PsiFile file = parameters.getPosition().getContainingFile(); - if (file instanceof PsiPlainTextFile && - parameters.getEditor().getComponent().getParent() instanceof EditorTextField) { + if (file instanceof PsiPlainTextFile && parameters.getEditor().getComponent().getParent() instanceof EditorTextField) { return; } - final int offset = parameters.getOffset(); - final List templates = listApplicableTemplates(file, offset); + int offset = parameters.getOffset(); + final List availableTemplates = TemplateManagerImpl.listApplicableTemplates(file, offset, false); Editor editor = parameters.getEditor(); + final Map templates = filterTemplatesByPrefix(availableTemplates, editor, offset, false, false); if (showAllTemplates()) { final AtomicBoolean templatesShown = new AtomicBoolean(false); final CompletionResultSet finalResult = result; @@ -85,75 +86,74 @@ public class LiveTemplateCompletionContributor extends CompletionContributor { if (parameters.getInvocationCount() > 0) return; //only in autopopups for now - String templatePrefix = findLiveTemplatePrefix(file, editor, result.getPrefixMatcher().getPrefix()); - final TemplateImpl template = findApplicableTemplate(file, offset, templatePrefix); - if (template != null) { - result = result.withPrefixMatcher(template.getKey()); - result.addElement(new LiveTemplateLookupElementImpl(template, true)); + // custom templates should handle this situation by itself (return true from hasCompletionItems() and provide lookup element) + // regular templates won't be shown in this case + if (!customTemplateAvailableAndHasCompletionItem(null, editor, file, offset)) { + TemplateImpl template = findFullMatchedApplicableTemplate(editor, offset, availableTemplates); + if (template != null) { + result.withPrefixMatcher(result.getPrefixMatcher().cloneWithPrefix(template.getKey())) + .addElement(new LiveTemplateLookupElementImpl(template, true)); + } } - for (final TemplateImpl possible : templates) { - result.restartCompletionOnPrefixChange(possible.getKey()); + + for (Map.Entry possible : templates.entrySet()) { + String templateKey = possible.getKey().getKey(); + String currentPrefix = possible.getValue(); + result.withPrefixMatcher(result.getPrefixMatcher().cloneWithPrefix(currentPrefix)) + .restartCompletionOnPrefixChange(templateKey); } } }); } + public static boolean customTemplateAvailableAndHasCompletionItem(@Nullable Character shortcutChar, @NotNull Editor editor, @NotNull PsiFile file, int offset) { + CustomTemplateCallback callback = new CustomTemplateCallback(editor, file, false); + for (CustomLiveTemplate customLiveTemplate : TemplateManagerImpl.listApplicableCustomTemplates(editor, file, false)) { + if (customLiveTemplate instanceof CustomLiveTemplateBase) { + if ((shortcutChar == null || customLiveTemplate.getShortcut() == shortcutChar.charValue()) + && ((CustomLiveTemplateBase)customLiveTemplate).hasCompletionItem(file, offset)) { + return customLiveTemplate.computeTemplateKey(callback) != null; + } + } + } + return false; + } + @SuppressWarnings("MethodMayBeStatic") //for Kotlin protected boolean showAllTemplates() { return shouldShowAllTemplates(); } - private static void ensureTemplatesShown(AtomicBoolean templatesShown, - List templates, - CompletionParameters parameters, - CompletionResultSet result) { + private static void ensureTemplatesShown(AtomicBoolean templatesShown, Map templates, + CompletionParameters parameters, CompletionResultSet result) { if (!templatesShown.getAndSet(true)) { - for (final TemplateImpl possible : templates) { - result.addElement(new LiveTemplateLookupElementImpl(possible, false)); + for (final Map.Entry entry : templates.entrySet()) { + result.withPrefixMatcher(result.getPrefixMatcher().cloneWithPrefix(StringUtil.notNullize(entry.getValue()))) + .addElement(new LiveTemplateLookupElementImpl(entry.getKey(), false)); } PsiFile file = parameters.getPosition().getContainingFile(); Editor editor = parameters.getEditor(); - for (CustomLiveTemplate customLiveTemplate : CustomLiveTemplate.EP_NAME.getExtensions()) { - if (customLiveTemplate instanceof CustomLiveTemplateBase && TemplateManagerImpl.isApplicable(customLiveTemplate, editor, file)) { + for (CustomLiveTemplate customLiveTemplate : TemplateManagerImpl.listApplicableCustomTemplates(editor, file, false)) { + if (customLiveTemplate instanceof CustomLiveTemplateBase) { ((CustomLiveTemplateBase)customLiveTemplate).addCompletions(parameters, result); } } } } - private static List listApplicableTemplates(PsiFile file, int offset) { - Set contextTypes = TemplateManagerImpl.getApplicableContextTypes(file, offset); - - final ArrayList result = ContainerUtil.newArrayList(); - for (final TemplateImpl template : TemplateSettings.getInstance().getTemplates()) { - if (!template.isDeactivated() && TemplateManagerImpl.isApplicable(template, contextTypes)) { - result.add(template); - } - } - return result; - } - @Nullable - public static TemplateImpl findApplicableTemplate(final PsiFile file, int offset, @NotNull final String possiblePrefix) { - return ContainerUtil.find(listApplicableTemplates(file, offset), new Condition() { - @Override - public boolean value(TemplateImpl template) { - return possiblePrefix.equals(template.getKey()); - } - }); - } - - @NotNull - public static String findLiveTemplatePrefix(@NotNull PsiFile file, @NotNull Editor editor, @NotNull String defaultValue) { - final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file, false); - for (CustomLiveTemplate customLiveTemplate : CustomLiveTemplate.EP_NAME.getExtensions()) { - final String customKey = customLiveTemplate.computeTemplateKey(callback); - if (customKey != null) { - return customKey; + public static TemplateImpl findFullMatchedApplicableTemplate(@NotNull Editor editor, + int offset, + @NotNull Collection availableTemplates) { + Map templates = filterTemplatesByPrefix(availableTemplates, editor, offset, true, false); + if (templates.size() == 1) { + TemplateImpl template = ContainerUtil.getFirstItem(templates.keySet()); + if (template != null) { + return template; } } - return defaultValue; + return null; } public static class Skipper extends CompletionPreselectSkipper { @@ -163,5 +163,4 @@ public class LiveTemplateCompletionContributor extends CompletionContributor { return element instanceof LiveTemplateLookupElement && ((LiveTemplateLookupElement)element).sudden && !Registry.is("ide.completion.autopopup.select.live.templates"); } } - } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/SurroundWithTemplateHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/SurroundWithTemplateHandler.java index 08034b248fe9..edfb37a700e2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/SurroundWithTemplateHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/SurroundWithTemplateHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -63,8 +62,8 @@ public class SurroundWithTemplateHandler implements CodeInsightActionHandler { if (!editor.getSelectionModel().hasSelection()) return null; } PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); - List customTemplates = getApplicableCustomTemplates(editor, file); - ArrayList templates = getApplicableTemplates(editor, file, true); + List customTemplates = TemplateManagerImpl.listApplicableCustomTemplates(editor, file, true); + List templates = TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, true); if (templates.isEmpty() && customTemplates.isEmpty()) { HintManager.getInstance().showErrorHint(editor, CodeInsightBundle.message("templates.surround.no.defined")); return null; @@ -89,45 +88,4 @@ public class SurroundWithTemplateHandler implements CodeInsightActionHandler { public boolean startInWriteAction() { return true; } - - public static List getApplicableCustomTemplates(Editor editor, PsiFile file) { - List result = new ArrayList(); - for (CustomLiveTemplate template : CustomLiveTemplate.EP_NAME.getExtensions()) { - if (template.supportsWrapping() && isApplicable(template, editor, file)) { - result.add(template); - } - } - return result; - } - - public static boolean isApplicable(CustomLiveTemplate template, Editor editor, PsiFile file) { - return template.isApplicable(file, editor.getSelectionModel().getSelectionStart(), true); - } - - public static ArrayList getApplicableTemplates(Editor editor, PsiFile file, boolean selectionOnly) { - - int startOffset = editor.getCaretModel().getOffset(); - if (editor.getSelectionModel().hasSelection()) { - startOffset = editor.getSelectionModel().getSelectionStart(); - } - - file = insertDummyIdentifier(editor, file); - - ArrayList list = new ArrayList(); - for (TemplateImpl template : TemplateSettings.getInstance().getTemplates()) { - if (!template.isDeactivated() && - (!selectionOnly || template.isSelectionTemplate()) && - TemplateManagerImpl.isApplicable(file, startOffset, template)) { - list.add(template); - } - } - return list; - } - - public static PsiFile insertDummyIdentifier(final Editor editor, PsiFile file) { - boolean selection = editor.getSelectionModel().hasSelection(); - final int startOffset = selection ? editor.getSelectionModel().getSelectionStart() : editor.getCaretModel().getOffset(); - final int endOffset = selection ? editor.getSelectionModel().getSelectionEnd() : startOffset; - return TemplateManagerImpl.insertDummyIdentifier(file, startOffset, endOffset); - } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java index 28600b79fdcf..366fe62d97f2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java @@ -312,9 +312,8 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo return !(customLiveTemplate instanceof CustomLiveTemplateBase) || ((CustomLiveTemplateBase)customLiveTemplate).supportsMultiCaret(); } - public static boolean isApplicable(CustomLiveTemplate customLiveTemplate, Editor editor, PsiFile file) { - int caretOffset = editor.getCaretModel().getOffset(); - return customLiveTemplate.isApplicable(file, caretOffset > 0 ? caretOffset - 1 : 0, false); + public static boolean isApplicable(@NotNull CustomLiveTemplate customLiveTemplate, @NotNull Editor editor, @NotNull PsiFile file) { + return customLiveTemplate.isApplicable(file, Math.max(0, editor.getSelectionModel().getSelectionStart() - 1), false); } private static int getArgumentOffset(int caretOffset, String argument, CharSequence text) { @@ -567,12 +566,29 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo return false; } - public static List listApplicableTemplates(PsiFile file, int offset) { + public static List listApplicableTemplates(PsiFile file, int offset, boolean selectionOnly) { Set contextTypes = getApplicableContextTypes(file, offset); final ArrayList result = ContainerUtil.newArrayList(); for (final TemplateImpl template : TemplateSettings.getInstance().getTemplates()) { - if (!template.isDeactivated() && isApplicable(template, contextTypes)) { + if (!template.isDeactivated() && (!selectionOnly || template.isSelectionTemplate()) && isApplicable(template, contextTypes)) { + result.add(template); + } + } + return result; + } + + public static List listApplicableTemplateWithInsertingDummyIdentifier(Editor editor, PsiFile file, boolean selectionOnly) { + int startOffset = editor.getSelectionModel().getSelectionStart(); + file = insertDummyIdentifier(editor, file); + + return listApplicableTemplates(file, startOffset, selectionOnly); + } + + public static List listApplicableCustomTemplates(@NotNull Editor editor, @NotNull PsiFile file, boolean selectionOnly) { + List result = new ArrayList(); + for (CustomLiveTemplate template : CustomLiveTemplate.EP_NAME.getExtensions()) { + if ((!selectionOnly || template.supportsWrapping()) && isApplicable(template, editor, file)) { result.add(template); } } @@ -602,6 +618,13 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo return result; } + + public static PsiFile insertDummyIdentifier(final Editor editor, PsiFile file) { + boolean selection = editor.getSelectionModel().hasSelection(); + final int startOffset = selection ? editor.getSelectionModel().getSelectionStart() : editor.getCaretModel().getOffset(); + final int endOffset = selection ? editor.getSelectionModel().getSelectionEnd() : startOffset; + return insertDummyIdentifier(file, startOffset, endOffset); + } public static PsiFile insertDummyIdentifier(PsiFile file, final int startOffset, final int endOffset) { file = (PsiFile)file.copy(); diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index b7a94b297c23..6ef2d157b144 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -286,7 +286,6 @@ dialog.edit.live.template.title=Edit Live Template dialog.add.live.template.title=Add Live Template templates.no.defined=No templates defined in this context templates.surround.no.defined=No surround templates defined in this context -templates.no.defined.with.prefix=No templates starting with ''{0}'' defined in this context templates.settings.page.title=Live Templates templates.select.template.chooser.title=Select Template templates.export.display.name=Live templates diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/ZenCodingTemplate.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/ZenCodingTemplate.java index c0ef08b1eb42..5b519a61de41 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/ZenCodingTemplate.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/ZenCodingTemplate.java @@ -28,8 +28,9 @@ import com.intellij.codeInsight.template.emmet.nodes.*; import com.intellij.codeInsight.template.emmet.tokens.TemplateToken; import com.intellij.codeInsight.template.emmet.tokens.TextToken; import com.intellij.codeInsight.template.emmet.tokens.ZenCodingToken; -import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor; import com.intellij.codeInsight.template.impl.TemplateImpl; +import com.intellij.codeInsight.template.impl.TemplateManagerImpl; +import com.intellij.codeInsight.template.impl.TemplateSettings; import com.intellij.codeInsight.template.impl.TemplateState; import com.intellij.diagnostic.AttachmentFactory; import com.intellij.ide.IdeEventQueue; @@ -43,6 +44,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.JBPopupListener; import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.ui.popup.util.PopupUtil; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; @@ -188,7 +190,12 @@ public class ZenCodingTemplate extends CustomLiveTemplateBase { } if (surroundedText == null && node instanceof TemplateNode) { if (key.equals(((TemplateNode)node).getTemplateToken().getKey()) && callback.findApplicableTemplates(key).size() > 1) { - callback.startTemplate(); + TemplateManagerImpl templateManager = (TemplateManagerImpl)callback.getTemplateManager(); + Map template2Argument = templateManager.findMatchingTemplates(callback.getFile(), callback.getEditor(), null, TemplateSettings.getInstance()); + Runnable runnable = templateManager.startNonCustomTemplates(template2Argument, callback.getEditor(), null); + if (runnable != null) { + runnable.run(); + } return; } } @@ -472,21 +479,29 @@ public class ZenCodingTemplate extends CustomLiveTemplateBase { final Ref generatedTemplate = new Ref(); final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file, false) { @Override - public void deleteTemplateKey(String key) { + public void deleteTemplateKey(@NotNull String key) { } @Override - public void startTemplate(Template template, Map predefinedValues, TemplateEditingListener listener) { + public void startTemplate(@NotNull Template template, Map predefinedValues, TemplateEditingListener listener) { if (template instanceof TemplateImpl && !((TemplateImpl)template).isDeactivated()) { generatedTemplate.set((TemplateImpl)template); } } }; - String templatePrefix = computeTemplateKeyWithoutContextChecking(callback); + final String templatePrefix = computeTemplateKeyWithoutContextChecking(callback); if (templatePrefix != null) { - if (LiveTemplateCompletionContributor.findApplicableTemplate(file, offset, templatePrefix) == null) { + List regularTemplates = TemplateManagerImpl.listApplicableTemplates(file, offset, false); + boolean regularTemplateWithSamePrefixExists = !ContainerUtil.filter(regularTemplates, new Condition() { + @Override + public boolean value(TemplateImpl template) { + return templatePrefix.equals(template.getKey()); + } + }).isEmpty(); + + if (!regularTemplateWithSamePrefixExists) { // exclude perfect matches with existing templates because LiveTemplateCompletionContributor handles it final Collection extraFilters = ContainerUtil.newLinkedList(new SingleLineEmmetFilter()); expand(templatePrefix, callback, null, generator, extraFilters, false);