Unify prefix calculation rules for live templates

This commit is contained in:
Alexander Zolotov
2014-04-17 16:02:35 +04:00
parent f447531dc9
commit 814a79afde
37 changed files with 444 additions and 211 deletions
@@ -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;
}
@@ -180,15 +180,15 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase {
public Collection<? extends CustomLiveTemplateLookupElement> getLookupElements(@NotNull PsiFile file, @NotNull Editor editor, int offset) {
String key = computeTemplateKeyWithoutContextChecking(editor.getDocument().getCharsSequence(), offset);
if (key != null && editor.getCaretModel().getCaretCount() == 1) {
Map<String, CustomLiveTemplateLookupElement> result = ContainerUtil.newHashMap();
Collection<CustomLiveTemplateLookupElement> result = ContainerUtil.newHashSet();
Condition<PostfixTemplate> isApplicationTemplateFunction = createIsApplicationTemplateFunction(key, file, editor);
for (Map.Entry<String, PostfixTemplate> 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);
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
complex key<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
complex template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefixcomplex key<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefix:comple<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefix:complex template text<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefixcomplex keycomplex template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
complex k<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
complex template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
complex template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
simple<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
simple template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
descri<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
simple template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefixsimpl<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefix:simp<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefix:simple template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
prefixsimplsimple template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
simp<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
simple template text
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
<caret>
}
}
@@ -0,0 +1,5 @@
class A {
public static void main() {
simple template text
}
}
@@ -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"));
}
}
@@ -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<Integer, ElementPattern<String>> 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();
@@ -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;
}
@@ -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<CustomLiveTemplate> customTemplates = SurroundWithTemplateHandler.getApplicableCustomTemplates(editor, file);
List<TemplateImpl> templates = SurroundWithTemplateHandler.getApplicableTemplates(editor, file, true);
List<CustomLiveTemplate> customTemplates = TemplateManagerImpl.listApplicableCustomTemplates(editor, file, true);
List<TemplateImpl> templates = TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, true);
if (!templates.isEmpty() || !customTemplates.isEmpty()) {
applicable.add(new Separator("Live templates"));
@@ -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<TemplateImpl> 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, '.'));
}
}
}
@@ -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<TemplateContextType> 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<String, String> predefinedValues, TemplateEditingListener listener) {
public void startTemplate(@NotNull Template template, Map<String, String> predefinedValues, TemplateEditingListener listener) {
if(myInInjectedFragment) {
template.setToReformat(false);
}
myTemplateManager.startTemplate(myEditor, template, false, predefinedValues, listener);
}
public void startTemplate() {
Map<TemplateImpl, String> 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<TemplateImpl> getMatchingTemplates(@NotNull String templateKey) {
TemplateSettings settings = TemplateSettings.getInstance();
List<TemplateImpl> candidates = new ArrayList<TemplateImpl>();
@@ -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);
}
@@ -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();
@@ -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<TemplateImpl> applicableTemplates = TemplateManagerImpl.listApplicableTemplateWithInsertingDummyIdentifier(editor, file, false);
List<TemplateImpl> matchingTemplates = new ArrayList<TemplateImpl>();
ArrayList<TemplateImpl> 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<TemplateImpl, String> matchingTemplates = filterTemplatesByPrefix(applicableTemplates, editor, offset, false, true);
MultiMap<String, CustomLiveTemplateLookupElement> customTemplatesLookupElements = getCustomTemplatesLookupItems(editor, file, offset);
if (matchingTemplates.isEmpty()) {
for (TemplateImpl template : applicableTemplates) {
matchingTemplates.put(template, null);
}
}
MultiMap<String,CustomLiveTemplateLookupElement> 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<TemplateImpl, String> filterTemplatesByPrefix(@NotNull Collection<TemplateImpl> 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<TemplateImpl, String> matchingTemplates = new TreeMap<TemplateImpl, String>(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<TemplateImpl> matchingTemplates,
@NotNull Map<TemplateImpl, String> matchingTemplates,
@NotNull MultiMap<String, CustomLiveTemplateLookupElement> 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<TemplateImpl, String> entry : matchingTemplates.entrySet()) {
TemplateImpl template = entry.getKey();
lookup.addItem(createTemplateElement(template), new PlainPrefixMatcher(StringUtil.notNullize(entry.getValue())));
}
for (Map.Entry<String, Collection<CustomLiveTemplateLookupElement>> entry : customTemplatesLookupElements.entrySet()) {
@@ -112,11 +143,13 @@ public class ListTemplatesHandler implements CodeInsightActionHandler {
showLookup(lookup, file);
}
private static MultiMap<String, CustomLiveTemplateLookupElement> listApplicableCustomTemplates(@NotNull Editor editor, @NotNull PsiFile file, int offset) {
public static MultiMap<String, CustomLiveTemplateLookupElement> getCustomTemplatesLookupItems(@NotNull Editor editor,
@NotNull PsiFile file,
int offset) {
final MultiMap<String, CustomLiveTemplateLookupElement> 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();
@@ -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<CompletionParameters>() {
@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<TemplateImpl> templates = listApplicableTemplates(file, offset);
int offset = parameters.getOffset();
final List<TemplateImpl> availableTemplates = TemplateManagerImpl.listApplicableTemplates(file, offset, false);
Editor editor = parameters.getEditor();
final Map<TemplateImpl, String> 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<TemplateImpl, String> 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<TemplateImpl> templates,
CompletionParameters parameters,
CompletionResultSet result) {
private static void ensureTemplatesShown(AtomicBoolean templatesShown, Map<TemplateImpl, String> templates,
CompletionParameters parameters, CompletionResultSet result) {
if (!templatesShown.getAndSet(true)) {
for (final TemplateImpl possible : templates) {
result.addElement(new LiveTemplateLookupElementImpl(possible, false));
for (final Map.Entry<TemplateImpl, String> 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<TemplateImpl> listApplicableTemplates(PsiFile file, int offset) {
Set<TemplateContextType> contextTypes = TemplateManagerImpl.getApplicableContextTypes(file, offset);
final ArrayList<TemplateImpl> 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<TemplateImpl>() {
@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<TemplateImpl> availableTemplates) {
Map<TemplateImpl, String> 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");
}
}
}
@@ -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<CustomLiveTemplate> customTemplates = getApplicableCustomTemplates(editor, file);
ArrayList<TemplateImpl> templates = getApplicableTemplates(editor, file, true);
List<CustomLiveTemplate> customTemplates = TemplateManagerImpl.listApplicableCustomTemplates(editor, file, true);
List<TemplateImpl> 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<CustomLiveTemplate> getApplicableCustomTemplates(Editor editor, PsiFile file) {
List<CustomLiveTemplate> result = new ArrayList<CustomLiveTemplate>();
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<TemplateImpl> 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<TemplateImpl> list = new ArrayList<TemplateImpl>();
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);
}
}
@@ -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<TemplateImpl> listApplicableTemplates(PsiFile file, int offset) {
public static List<TemplateImpl> listApplicableTemplates(PsiFile file, int offset, boolean selectionOnly) {
Set<TemplateContextType> contextTypes = getApplicableContextTypes(file, offset);
final ArrayList<TemplateImpl> 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<TemplateImpl> listApplicableTemplateWithInsertingDummyIdentifier(Editor editor, PsiFile file, boolean selectionOnly) {
int startOffset = editor.getSelectionModel().getSelectionStart();
file = insertDummyIdentifier(editor, file);
return listApplicableTemplates(file, startOffset, selectionOnly);
}
public static List<CustomLiveTemplate> listApplicableCustomTemplates(@NotNull Editor editor, @NotNull PsiFile file, boolean selectionOnly) {
List<CustomLiveTemplate> result = new ArrayList<CustomLiveTemplate>();
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();
@@ -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
@@ -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<TemplateImpl, String> 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<TemplateImpl> generatedTemplate = new Ref<TemplateImpl>();
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<String, String> predefinedValues, TemplateEditingListener listener) {
public void startTemplate(@NotNull Template template, Map<String, String> 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<TemplateImpl> regularTemplates = TemplateManagerImpl.listApplicableTemplates(file, offset, false);
boolean regularTemplateWithSamePrefixExists = !ContainerUtil.filter(regularTemplates, new Condition<TemplateImpl>() {
@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<SingleLineEmmetFilter> extraFilters = ContainerUtil.newLinkedList(new SingleLineEmmetFilter());
expand(templatePrefix, callback, null, generator, extraFilters, false);