diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateCompletionContributor.java index b6a7b5f687ce..eb6b585f0ec1 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplateCompletionContributor.java @@ -21,34 +21,19 @@ import com.intellij.codeInsight.template.CustomLiveTemplate; import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.postfix.templates.PostfixLiveTemplate; import com.intellij.openapi.editor.Editor; -import com.intellij.patterns.ElementPattern; -import com.intellij.psi.JavaTokenType; -import com.intellij.psi.PsiElement; +import com.intellij.patterns.PlatformPatterns; import com.intellij.psi.PsiFile; -import com.intellij.psi.impl.source.tree.ElementType; -import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static com.intellij.patterns.PsiJavaPatterns.psiElement; -import static com.intellij.patterns.StandardPatterns.string; - public class PostfixTemplateCompletionContributor extends CompletionContributor { - private static final TokenSet SUITABLE_ELEMENTS = TokenSet.orSet(ElementType.KEYWORD_BIT_SET, - ElementType.LITERAL_BIT_SET, - TokenSet.create(JavaTokenType.IDENTIFIER)); - public PostfixTemplateCompletionContributor() { - extend(CompletionType.BASIC, identifierAfterDot(), new PostfixTemplatesCompletionProvider()); + extend(CompletionType.BASIC, PlatformPatterns.psiElement(), new PostfixTemplatesCompletionProvider()); } @Nullable - public static PostfixLiveTemplate getPostfixLiveTemplate(@NotNull PsiFile file, @NotNull Editor editor) { + public static PostfixLiveTemplate getPostfixLiveTemplate(@NotNull PsiFile file, @NotNull Editor editor) { PostfixLiveTemplate postfixLiveTemplate = CustomLiveTemplate.EP_NAME.findExtension(PostfixLiveTemplate.class); return postfixLiveTemplate != null && TemplateManagerImpl.isApplicable(postfixLiveTemplate, editor, file) ? postfixLiveTemplate : null; } - - private static ElementPattern identifierAfterDot() { - return psiElement().withElementType(SUITABLE_ELEMENTS).afterLeaf(psiElement().withText(string().contains("."))); - } } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplatesCompletionProvider.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplatesCompletionProvider.java index db253a6808b1..3e99fbf66db2 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplatesCompletionProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/completion/PostfixTemplatesCompletionProvider.java @@ -42,11 +42,12 @@ class PostfixTemplatesCompletionProvider extends CompletionProvider templates = Arrays.asList(PostfixTemplate.EP_NAME.getExtensions()); + + LanguageExtensionPoint[] extensions = new ExtensionPointName(LanguagePostfixTemplate.EP_NAME).getExtensions(); + + List templates = ContainerUtil.newArrayList(); + for (LanguageExtensionPoint extension : extensions) { + templates.addAll(((PostfixTemplateProvider)extension.getInstance()).getTemplates()); + } + ContainerUtil.sort(templates, new Comparator() { @Override public int compare(PostfixTemplate o1, PostfixTemplate o2) { @@ -184,7 +196,7 @@ public class PostfixTemplatesConfigurable implements SearchableConfigurable, Edi private static String shortcutToString(char shortcut) { if (shortcut == TemplateSettings.SPACE_CHAR) { return SPACE; - } + } if (shortcut == TemplateSettings.ENTER_CHAR) { return ENTER; } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/JavaPostfixTemplateProvider.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/JavaPostfixTemplateProvider.java new file mode 100644 index 000000000000..cb628dd2c276 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/JavaPostfixTemplateProvider.java @@ -0,0 +1,167 @@ +/* + * 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.postfix.templates; + +import com.intellij.codeInsight.completion.CompletionInitializationContext; +import com.intellij.codeInsight.completion.JavaCompletionContributor; +import com.intellij.codeInsight.template.CustomTemplateCallback; +import com.intellij.codeInsight.template.postfix.util.Aliases; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Set; + + +public class JavaPostfixTemplateProvider implements PostfixTemplateProvider { + + private static final Logger LOG = Logger.getInstance(JavaPostfixTemplateProvider.class); + + private final Map myMapTemplates; + + public JavaPostfixTemplateProvider() { + myMapTemplates = ContainerUtil.newHashMap(); + for (PostfixTemplate template : getInitializeTemplateSet()) { + register(template.getKey(), template); + Aliases aliases = template.getClass().getAnnotation(Aliases.class); + if (aliases != null) { + for (String key : aliases.value()) { + register(key, template); + } + } + } + } + + @NotNull + protected Set getInitializeTemplateSet() { + return ContainerUtil.newHashSet(new AssertStatementPostfixTemplate(), + new CastExpressionPostfixTemplate(), + new ElseStatementPostfixTemplate(), + new ForAscendingPostfixTemplate(), + new ForDescendingPostfixTemplate(), + new ForeachPostfixTemplate(), + new FormatPostfixTemplate(), + new IfStatementPostfixTemplate(), + new InstanceofExpressionPostfixTemplate(), + new IntroduceFieldPostfixTemplate(), + new IntroduceVariablePostfixTemplate(), + new IsNullCheckPostfixTemplate(), + new NotExpressionPostfixTemplate(), + new NotNullCheckPostfixTemplate(), + new ParenthesizedExpressionPostfixTemplate(), + new ReturnStatementPostfixTemplate(), + new SoutPostfixTemplate(), + new SwitchStatementPostfixTemplate(), + new SynchronizedStatementPostfixTemplate(), + new ThrowExceptionPostfixTemplate(), + new TryStatementPostfixTemplate(), + new TryWithResourcesPostfixTemplate(), + new WhileStatementPostfixTemplate()); + } + + @NotNull + @Override + public Set getTemplates() { + return ContainerUtil.newHashSet(myMapTemplates.values()); + } + + @NotNull + @Override + public Set getKeys() { + return myMapTemplates.keySet(); + } + + + @Nullable + @Override + public PostfixTemplate get(@Nullable String key) { + return myMapTemplates.get(key); + } + + @Override + public boolean isTerminalSymbol(char currentChar) { + return currentChar == '.' || currentChar == '!'; + } + + @NotNull + @Override + public PsiElement preExpand(@NotNull Editor editor, @NotNull PsiElement context, int offset, @NotNull final String key) { + + return addSemicolonIfNeeded(editor, editor.getDocument(), context, offset - key.length()); + } + + @NotNull + @Override + public PsiFile preCheck(@NotNull Editor editor, @NotNull PsiFile file, int currentOffset) { + Document document = file.getViewProvider().getDocument(); + assert document != null; + CharSequence sequence = document.getCharsSequence(); + StringBuilder fileContentWithoutKey = new StringBuilder(sequence); + if (isSemicolonNeeded(file, editor)) { + fileContentWithoutKey.insert(currentOffset, ';'); + file = PostfixLiveTemplate.copyFile(file, fileContentWithoutKey); + } + + return file; + } + + private void register(@NotNull String key, @NotNull PostfixTemplate template) { + PostfixTemplate registered = myMapTemplates.put(key, template); + if (registered != null) { + LOG.error("Can't register postfix template. Duplicated key: " + template.getKey()); + } + } + + @NotNull + private static PsiElement addSemicolonIfNeeded(@NotNull final Editor editor, + @NotNull final Document document, + @NotNull final PsiElement context, + final int offset) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + final Ref newContext = Ref.create(context); + final PsiFile file = context.getContainingFile(); + if (isSemicolonNeeded(file, editor)) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + public void run() { + document.insertString(offset, ";"); + PsiDocumentManager.getInstance(context.getProject()).commitDocument(document); + newContext.set(CustomTemplateCallback.getContext(file, offset - 1)); + } + }); + } + }); + } + return newContext.get(); + } + + private static boolean isSemicolonNeeded(@NotNull PsiFile file, @NotNull Editor editor) { + return JavaCompletionContributor.semicolonNeeded(editor, file, CompletionInitializationContext.calcStartOffset(editor)); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/LanguagePostfixTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/LanguagePostfixTemplate.java new file mode 100644 index 000000000000..d3780e1f3f12 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/LanguagePostfixTemplate.java @@ -0,0 +1,27 @@ +/* + * 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.postfix.templates; + +import com.intellij.lang.LanguageExtension; + +public class LanguagePostfixTemplate extends LanguageExtension { + public static final LanguagePostfixTemplate INSTANCE = new LanguagePostfixTemplate(); + public static final String EP_NAME = "com.intellij.codeInsight.template.postfixTemplateProvider"; + + private LanguagePostfixTemplate() { + super(EP_NAME); + } +} 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 d738fb8d227b..349eeda3ffb7 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 @@ -15,17 +15,15 @@ */ package com.intellij.codeInsight.template.postfix.templates; -import com.intellij.codeInsight.completion.CompletionInitializationContext; -import com.intellij.codeInsight.completion.JavaCompletionContributor; +import com.google.common.collect.Sets; import com.intellij.codeInsight.template.CustomLiveTemplateBase; import com.intellij.codeInsight.template.CustomTemplateCallback; import com.intellij.codeInsight.template.impl.CustomLiveTemplateLookupElement; import com.intellij.codeInsight.template.impl.TemplateSettings; import com.intellij.codeInsight.template.postfix.completion.PostfixTemplateLookupElement; import com.intellij.codeInsight.template.postfix.settings.PostfixTemplatesSettings; -import com.intellij.codeInsight.template.postfix.util.Aliases; import com.intellij.featureStatistics.FeatureUsageTracker; -import com.intellij.lang.java.JavaLanguage; +import com.intellij.lang.Language; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.undo.UndoConstants; @@ -33,7 +31,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; @@ -42,71 +39,53 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.Collections; import java.util.Set; public class PostfixLiveTemplate extends CustomLiveTemplateBase { public static final String POSTFIX_TEMPLATE_ID = "POSTFIX_TEMPLATE_ID"; - private static final Logger LOG = Logger.getInstance(PostfixLiveTemplate.class); - private final HashMap myTemplates = ContainerUtil.newHashMap(); + private static final LanguagePostfixTemplate templates = LanguagePostfixTemplate.INSTANCE; - public PostfixLiveTemplate() { - for (PostfixTemplate template : PostfixTemplate.EP_NAME.getExtensions()) { - register(template.getKey(), template); - Aliases aliases = template.getClass().getAnnotation(Aliases.class); - if (aliases != null) { - for (String key : aliases.value()) { - register(key, template); - } + @NotNull + public Set getAllTemplateKeys(PsiFile file, int offset) { + Set keys = Sets.newHashSet(); + Language language = PsiUtilCore.getLanguageAtOffset(file, offset); + + for (PostfixTemplateProvider provider : templates.allForLanguage(language)) { + keys.addAll(provider.getKeys()); + } + return keys; + } + + public boolean hasNotEmptyKey(PsiFile file, int offset) { + Language language = PsiUtilCore.getLanguageAtOffset(file, offset); + for (PostfixTemplateProvider provider : templates.allForLanguage(language)) { + if (StringUtil + .isNotEmpty(computeTemplateKeyWithoutContextChecking(provider, file.getText(), offset + 1))) { + return true; } } - } - - private void register(@NotNull String key, @NotNull PostfixTemplate template) { - PostfixTemplate registered = myTemplates.put(key, template); - if (registered != null) { - LOG.error("Can't register postfix template. Duplicated key: " + template.getKey()); - } - } - - @Nullable - @Override - public String computeTemplateKey(@NotNull CustomTemplateCallback callback) { - Editor editor = callback.getEditor(); - String key = computeTemplateKeyWithoutContextChecking(editor.getDocument().getCharsSequence(), editor.getCaretModel().getOffset()); - if (key == null) return null; - return isApplicableTemplate(getTemplateByKey(key), key, callback.getContext().getContainingFile(), editor) ? key : null; - } - - @Nullable - @Override - public String computeTemplateKeyWithoutContextChecking(@NotNull CustomTemplateCallback callback) { - Editor editor = callback.getEditor(); - return computeTemplateKeyWithoutContextChecking(editor.getDocument().getCharsSequence(), editor.getCaretModel().getOffset()); - } - - @Override - public boolean supportsMultiCaret() { return false; } @Nullable - public String computeTemplateKeyWithoutContextChecking(@NotNull CharSequence documentContent, int currentOffset) { + public String computeTemplateKeyWithoutContextChecking(PostfixTemplateProvider provider, + @NotNull CharSequence documentContent, + int currentOffset) { int startOffset = currentOffset; if (documentContent.length() < startOffset) { return null; } + while (startOffset > 0) { char currentChar = documentContent.charAt(startOffset - 1); if (!Character.isJavaIdentifierPart(currentChar)) { - if (currentChar != '.' && currentChar != '!') { + if (!provider.isTerminalSymbol(currentChar)) { return null; } startOffset--; @@ -117,19 +96,82 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase { return String.valueOf(documentContent.subSequence(startOffset, currentOffset)); } + @Nullable + @Override + public String computeTemplateKey(@NotNull CustomTemplateCallback callback) { + Editor editor = callback.getEditor(); + CharSequence charsSequence = editor.getDocument().getCharsSequence(); + int offset = editor.getCaretModel().getOffset(); + Language language = getLanguage(callback); + for (PostfixTemplateProvider provider : templates.allForLanguage(language)) { + String key = computeTemplateKeyWithoutContextChecking(provider, charsSequence, offset); + if (key != null && isApplicableTemplate(provider, key, callback.getFile(), editor)) { + return key; + } + } + + return null; + } + + @Nullable + @Override + public String computeTemplateKeyWithoutContextChecking(@NotNull CustomTemplateCallback callback) { + Editor editor = callback.getEditor(); + return computeTemplateKeyWithoutContextChecking(callback.getFile(), editor, editor.getCaretModel().getOffset()); + } + + @Override + public boolean supportsMultiCaret() { + return false; + } + + @Nullable + public String computeTemplateKeyWithoutContextChecking(PsiFile file, Editor editor, int currentOffset) { + Language language = PsiUtilCore.getLanguageAtOffset(file, currentOffset); + for (PostfixTemplateProvider provider : templates.allForLanguage(language)) { + String key = computeTemplateKeyWithoutContextChecking(provider, editor.getDocument().getCharsSequence(), currentOffset); + if (key != null) return key; + } + return null; + } + @Override public void expand(@NotNull final String key, @NotNull final CustomTemplateCallback callback) { ApplicationManager.getApplication().assertIsDispatchThread(); - FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.postfix"); - final PostfixTemplate template = getTemplateByKey(key); + Editor editor = callback.getEditor(); + Language language = getLanguage(callback); + for (PostfixTemplateProvider provider : templates.allForLanguage(language)) { + PostfixTemplate postfixTemplate = provider.get(key); + if (postfixTemplate != null) { + expandForProvider(provider, key, callback); + return; + } + } + + // don't care about errors in multiCaret mode + if (editor.getCaretModel().getAllCarets().size() == 1) { + LOG.error("Template not found by key: " + key); + } + } + + private static Language getLanguage(CustomTemplateCallback callback) { + return PsiUtilCore.getLanguageAtOffset(callback.getFile(), callback.getEditor().getCaretModel().getOffset()); + } + + public void expandForProvider( + @NotNull PostfixTemplateProvider provider, + @NotNull final String key, + @NotNull final CustomTemplateCallback callback) { final Editor editor = callback.getEditor(); final PsiFile file = callback.getContext().getContainingFile(); - if (isApplicableTemplate(template, key, file, editor)) { + if (isApplicableTemplate(provider, key, file, editor)) { int currentOffset = editor.getCaretModel().getOffset(); PsiElement newContext = deleteTemplateKey(file, editor.getDocument(), currentOffset, key); - newContext = addSemicolonIfNeeded(editor, editor.getDocument(), newContext, currentOffset - key.length()); + newContext = provider.preExpand(editor, newContext, currentOffset, key); + PostfixTemplate template = provider.get(key); + assert template != null; expandTemplate(template, editor, newContext); } // don't care about errors in multiCaret mode @@ -141,11 +183,10 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase { @Override public boolean isApplicable(PsiFile file, int offset, boolean wrapping) { PostfixTemplatesSettings settings = PostfixTemplatesSettings.getInstance(); - if (wrapping || file == null || settings == null || !settings.isPostfixTemplatesEnabled() || - PsiUtilCore.getLanguageAtOffset(file, offset) != JavaLanguage.INSTANCE) { + if (wrapping || file == null || settings == null || !settings.isPostfixTemplatesEnabled()) { return false; } - return StringUtil.isNotEmpty(computeTemplateKeyWithoutContextChecking(file.getText(), offset + 1)); + return hasNotEmptyKey(file, offset); } @Override @@ -177,35 +218,46 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase { @NotNull @Override - public Collection getLookupElements(@NotNull PsiFile file, @NotNull Editor editor, int offset) { - String key = computeTemplateKeyWithoutContextChecking(editor.getDocument().getCharsSequence(), offset); + public Collection getLookupElements(@NotNull PsiFile file, + @NotNull Editor editor, + int offset) { + Collection result = ContainerUtil.newHashSet(); + Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + for (PostfixTemplateProvider provider : templates.allForLanguage(language)) { + result.addAll(getLookupElementsForProvider(provider, file, editor, offset)); + } + + return result; + } + + @NotNull + private Collection getLookupElementsForProvider( + @NotNull PostfixTemplateProvider provider, + @NotNull PsiFile file, + @NotNull Editor editor, + int offset) { + String key = computeTemplateKeyWithoutContextChecking(file, editor, offset); if (key != null && editor.getCaretModel().getCaretCount() == 1) { Collection result = ContainerUtil.newHashSet(); - Condition isApplicationTemplateFunction = createIsApplicationTemplateFunction(key, file, editor); - for (Map.Entry entry : myTemplates.entrySet()) { - PostfixTemplate postfixTemplate = entry.getValue(); + + Condition isApplicationTemplateFunction = createIsApplicationTemplateFunction(provider, key, file, editor); + for (String postfixKey : provider.getKeys()) { + PostfixTemplate postfixTemplate = provider.get(postfixKey); + assert postfixTemplate != null; if (isApplicationTemplateFunction.value(postfixTemplate)) { - result.add(new PostfixTemplateLookupElement(this, postfixTemplate, entry.getKey(), false)); + result.add(new PostfixTemplateLookupElement(this, postfixTemplate, postfixKey, false)); } } return result; } - return super.getLookupElements(file, editor, offset); + return Collections.emptyList(); } - @NotNull - public Set getAllTemplateKeys() { - return myTemplates.keySet(); - } - @Nullable - public PostfixTemplate getTemplateByKey(@NotNull String key) { - return myTemplates.get(key); - } - - private static void expandTemplate(@NotNull final PostfixTemplate template, - @NotNull final Editor editor, - @NotNull final PsiElement context) { + private static void expandTemplate( + @NotNull final PostfixTemplate template, + @NotNull final Editor editor, + @NotNull final PsiElement context) { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { @@ -218,56 +270,6 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase { }); } - @Contract("null, _, _, _ -> false") - private static boolean isApplicableTemplate(@Nullable PostfixTemplate template, @NotNull String key, @NotNull PsiFile file, @NotNull Editor editor) { - return createIsApplicationTemplateFunction(key, file, editor).value(template); - } - - private static Condition createIsApplicationTemplateFunction(@NotNull String key, @NotNull PsiFile file, @NotNull Editor editor) { - int currentOffset = editor.getCaretModel().getOffset(); - final int newOffset = currentOffset - key.length(); - CharSequence fileContent = editor.getDocument().getCharsSequence(); - - StringBuilder fileContentWithoutKey = new StringBuilder(); - fileContentWithoutKey.append(fileContent.subSequence(0, newOffset)); - fileContentWithoutKey.append(fileContent.subSequence(currentOffset, fileContent.length())); - PsiFile copyFile = copyFile(file, fileContentWithoutKey); - Document copyDocument = copyFile.getViewProvider().getDocument(); - if (copyDocument == null) { - //noinspection unchecked - return Condition.FALSE; - } - - if (isSemicolonNeeded(copyFile, editor)) { - fileContentWithoutKey.insert(newOffset, ';'); - copyFile = copyFile(file, fileContentWithoutKey); - copyDocument = copyFile.getViewProvider().getDocument(); - if (copyDocument == null) { - //noinspection unchecked - return Condition.FALSE; - } - } - - final PsiElement context = CustomTemplateCallback.getContext(copyFile, newOffset > 0 ? newOffset - 1 : newOffset); - final Document finalCopyDocument = copyDocument; - return new Condition() { - @Override - public boolean value(PostfixTemplate template) { - return template != null && template.isEnabled() && template.isApplicable(context, finalCopyDocument, newOffset); - } - }; - } - - @NotNull - private static PsiFile copyFile(@NotNull PsiFile file, @NotNull StringBuilder fileContentWithoutKey) { - final PsiFileFactory psiFileFactory = PsiFileFactory.getInstance(file.getProject()); - PsiFile copy = psiFileFactory.createFileFromText(file.getName(), file.getFileType(), fileContentWithoutKey); - VirtualFile vFile = copy.getVirtualFile(); - if (vFile != null) { - vFile.putUserData(UndoConstants.DONT_RECORD_UNDO, Boolean.TRUE); - } - return copy; - } @NotNull private static PsiElement deleteTemplateKey(@NotNull final PsiFile file, @@ -291,33 +293,58 @@ public class PostfixLiveTemplate extends CustomLiveTemplateBase { return CustomTemplateCallback.getContext(file, startOffset > 0 ? startOffset - 1 : startOffset); } - @NotNull - private static PsiElement addSemicolonIfNeeded(@NotNull final Editor editor, - @NotNull final Document document, - @NotNull final PsiElement context, - final int offset) { - ApplicationManager.getApplication().assertIsDispatchThread(); - - final Ref newContext = Ref.create(context); - final PsiFile file = context.getContainingFile(); - if (isSemicolonNeeded(file, editor)) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { - public void run() { - document.insertString(offset, ";"); - PsiDocumentManager.getInstance(context.getProject()).commitDocument(document); - newContext.set(CustomTemplateCallback.getContext(file, offset - 1)); - } - }); - } - }); + private static Condition createIsApplicationTemplateFunction( + @NotNull PostfixTemplateProvider provider, + @NotNull String key, + @NotNull PsiFile file, + @NotNull Editor editor) { + int currentOffset = editor.getCaretModel().getOffset(); + final int newOffset = currentOffset - key.length(); + CharSequence fileContent = editor.getDocument().getCharsSequence(); + StringBuilder fileContentWithoutKey = new StringBuilder(); + fileContentWithoutKey.append(fileContent.subSequence(0, newOffset)); + fileContentWithoutKey.append(fileContent.subSequence(currentOffset, fileContent.length())); + PsiFile copyFile = copyFile(file, fileContentWithoutKey); + Document copyDocument = copyFile.getViewProvider().getDocument(); + if (copyDocument == null) { + //noinspection unchecked + return Condition.FALSE; } - return newContext.get(); + + copyFile = provider.preCheck(editor, copyFile, newOffset); + copyDocument = copyFile.getViewProvider().getDocument(); + if (copyDocument == null) { + //noinspection unchecked + return Condition.FALSE; + } + + final PsiElement context = CustomTemplateCallback.getContext(copyFile, newOffset > 0 ? newOffset - 1 : newOffset); + final Document finalCopyDocument = copyDocument; + return new Condition() { + @Override + public boolean value(PostfixTemplate template) { + return template != null && template.isEnabled() && template.isApplicable(context, finalCopyDocument, newOffset); + } + }; } - private static boolean isSemicolonNeeded(@NotNull PsiFile file, @NotNull Editor editor) { - return JavaCompletionContributor.semicolonNeeded(editor, file, CompletionInitializationContext.calcStartOffset(editor)); + + @NotNull + public static PsiFile copyFile(@NotNull PsiFile file, @NotNull StringBuilder fileContentWithoutKey) { + final PsiFileFactory psiFileFactory = PsiFileFactory.getInstance(file.getProject()); + PsiFile copy = psiFileFactory.createFileFromText(file.getName(), file.getFileType(), fileContentWithoutKey); + VirtualFile vFile = copy.getVirtualFile(); + if (vFile != null) { + vFile.putUserData(UndoConstants.DONT_RECORD_UNDO, Boolean.TRUE); + } + return copy; + } + + public static boolean isApplicableTemplate( + @NotNull PostfixTemplateProvider provider, + @NotNull String key, + @NotNull PsiFile file, + @NotNull Editor editor) { + return createIsApplicationTemplateFunction(provider, key, file, editor).value(provider.get(key)); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplate.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplate.java index 80a68ab26650..4131dd907d32 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplate.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplate.java @@ -18,7 +18,6 @@ package com.intellij.codeInsight.template.postfix.templates; import com.intellij.codeInsight.template.postfix.settings.PostfixTemplatesSettings; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionStatement; @@ -32,9 +31,6 @@ public abstract class PostfixTemplate { @NotNull private final String myDescription; @NotNull private final String myExample; - @NotNull - public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.postfixTemplate"); - protected PostfixTemplate(@NotNull String name, @NotNull String description, @NotNull String example) { this(name, "." + name, description, example); } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateProvider.java b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateProvider.java new file mode 100644 index 000000000000..b74beba50598 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/template/postfix/templates/PostfixTemplateProvider.java @@ -0,0 +1,64 @@ +/* + * 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.postfix.templates; + + +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +public interface PostfixTemplateProvider { + + /** + * Return all templates registered in the provider + */ + @NotNull + Set getTemplates(); + + /** + * Return all keys registered in the provider + */ + @NotNull + Set getKeys(); + + /** + * Return postfix template for key + */ + @Nullable + PostfixTemplate get(@Nullable String key); + + /** + * Check symbol can separate template keys + */ + boolean isTerminalSymbol(char currentChar); + + /** + * Prepare original file content for template expanding + */ + @NotNull + PsiElement preExpand(@NotNull Editor editor, @NotNull PsiElement context, int currentOffset, @NotNull String key); + + /** + * Do some actions with the file content before check applicable. + * Return new copy of file because we should not change original file + */ + @NotNull + PsiFile preCheck(@NotNull Editor editor, @NotNull PsiFile file, int currentOffset); +} diff --git a/resources/src/META-INF/PostfixTemplates.xml b/resources/src/META-INF/PostfixTemplates.xml index 90294e0d619c..2fe4cd381876 100644 --- a/resources/src/META-INF/PostfixTemplates.xml +++ b/resources/src/META-INF/PostfixTemplates.xml @@ -1,41 +1,23 @@ - + + + + - - - - - - - - - - - - - - - - - - - - - - - + - + - + \ No newline at end of file