Extract Java live postfix templates provider

This commit is contained in:
Andrey Starovoyt
2014-04-22 11:06:29 +04:00
parent e5eb7aa43a
commit 89ca1576e0
9 changed files with 463 additions and 202 deletions
@@ -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<? extends PsiElement> identifierAfterDot() {
return psiElement().withElementType(SUITABLE_ELEMENTS).afterLeaf(psiElement().withText(string().contains(".")));
}
}
@@ -42,11 +42,12 @@ class PostfixTemplatesCompletionProvider extends CompletionProvider<CompletionPa
PostfixLiveTemplate postfixLiveTemplate = getPostfixLiveTemplate(parameters.getOriginalFile(), parameters.getEditor());
if (postfixLiveTemplate != null) {
postfixLiveTemplate.addCompletions(parameters, result.withPrefixMatcher(new MyPrefixMatcher(result.getPrefixMatcher().getPrefix())));
CharSequence documentContent = parameters.getEditor().getDocument().getCharsSequence();
String possibleKey = postfixLiveTemplate.computeTemplateKeyWithoutContextChecking(documentContent, parameters.getOffset());
String possibleKey = postfixLiveTemplate
.computeTemplateKeyWithoutContextChecking(parameters.getOriginalFile(), parameters.getEditor(), parameters.getOffset());
if (possibleKey != null) {
result = result.withPrefixMatcher(possibleKey);
result.restartCompletionOnPrefixChange(StandardPatterns.string().oneOf(postfixLiveTemplate.getAllTemplateKeys()));
result.restartCompletionOnPrefixChange(
StandardPatterns.string().oneOf(postfixLiveTemplate.getAllTemplateKeys(parameters.getOriginalFile(), parameters.getOffset())));
}
}
}
@@ -19,7 +19,11 @@ import com.intellij.application.options.editor.EditorOptionsProvider;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor;
import com.intellij.codeInsight.template.impl.TemplateSettings;
import com.intellij.codeInsight.template.postfix.templates.LanguagePostfixTemplate;
import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate;
import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
import com.intellij.lang.LanguageExtensionPoint;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
@@ -34,8 +38,9 @@ import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.util.*;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class PostfixTemplatesConfigurable implements SearchableConfigurable, EditorOptionsProvider, Configurable.NoScroll {
@Nullable
@@ -61,7 +66,14 @@ public class PostfixTemplatesConfigurable implements SearchableConfigurable, Edi
}
myTemplatesSettings = settings;
List<PostfixTemplate> templates = Arrays.asList(PostfixTemplate.EP_NAME.getExtensions());
LanguageExtensionPoint[] extensions = new ExtensionPointName<LanguageExtensionPoint>(LanguagePostfixTemplate.EP_NAME).getExtensions();
List<PostfixTemplate> templates = ContainerUtil.newArrayList();
for (LanguageExtensionPoint extension : extensions) {
templates.addAll(((PostfixTemplateProvider)extension.getInstance()).getTemplates());
}
ContainerUtil.sort(templates, new Comparator<PostfixTemplate>() {
@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;
}
@@ -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<String, PostfixTemplate> 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<PostfixTemplate> 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<PostfixTemplate> getTemplates() {
return ContainerUtil.newHashSet(myMapTemplates.values());
}
@NotNull
@Override
public Set<String> 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<PsiElement> 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));
}
}
@@ -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<PostfixTemplateProvider> {
public static final LanguagePostfixTemplate INSTANCE = new LanguagePostfixTemplate();
public static final String EP_NAME = "com.intellij.codeInsight.template.postfixTemplateProvider";
private LanguagePostfixTemplate() {
super(EP_NAME);
}
}
@@ -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<String, PostfixTemplate> 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<String> getAllTemplateKeys(PsiFile file, int offset) {
Set<String> 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<? extends CustomLiveTemplateLookupElement> getLookupElements(@NotNull PsiFile file, @NotNull Editor editor, int offset) {
String key = computeTemplateKeyWithoutContextChecking(editor.getDocument().getCharsSequence(), offset);
public Collection<? extends CustomLiveTemplateLookupElement> getLookupElements(@NotNull PsiFile file,
@NotNull Editor editor,
int offset) {
Collection<CustomLiveTemplateLookupElement> 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<? extends CustomLiveTemplateLookupElement> 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<CustomLiveTemplateLookupElement> result = ContainerUtil.newHashSet();
Condition<PostfixTemplate> isApplicationTemplateFunction = createIsApplicationTemplateFunction(key, file, editor);
for (Map.Entry<String, PostfixTemplate> entry : myTemplates.entrySet()) {
PostfixTemplate postfixTemplate = entry.getValue();
Condition<PostfixTemplate> 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<String> 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<PostfixTemplate> 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<PostfixTemplate>() {
@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<PsiElement> 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<PostfixTemplate> 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<PostfixTemplate>() {
@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));
}
}
@@ -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<PostfixTemplate> EP_NAME = ExtensionPointName.create("com.intellij.postfixTemplate");
protected PostfixTemplate(@NotNull String name, @NotNull String description, @NotNull String example) {
this(name, "." + name, description, example);
}
@@ -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<PostfixTemplate> getTemplates();
/**
* Return all keys registered in the provider
*/
@NotNull
Set<String> 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);
}
+8 -26
View File
@@ -1,41 +1,23 @@
<idea-plugin version="2">
<extensionPoints>
<extensionPoint name="postfixTemplate" interface="com.intellij.codeInsight.template.postfix.templates.PostfixTemplate"/>
<extensionPoint name="codeInsight.template.postfixTemplateProvider"
beanClass="com.intellij.lang.LanguageExtensionPoint">
<with attribute="implementationClass" implements="com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider"/>
</extensionPoint>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.ElseStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.IntroduceVariablePostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.NotNullCheckPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.IsNullCheckPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.NotExpressionPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.ForeachPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.WhileStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.CastExpressionPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.IfStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.ForAscendingPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.ForDescendingPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.IntroduceFieldPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.ParenthesizedExpressionPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.ReturnStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.SwitchStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.ThrowExceptionPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.AssertStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.SynchronizedStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.InstanceofExpressionPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.SoutPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.FormatPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.TryStatementPostfixTemplate"/>
<postfixTemplate implementation="com.intellij.codeInsight.template.postfix.templates.TryWithResourcesPostfixTemplate"/>
<codeInsight.template.postfixTemplateProvider language="JAVA" implementationClass="com.intellij.codeInsight.template.postfix.templates.JavaPostfixTemplateProvider"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<applicationService id="postfixCompletionSettings" serviceImplementation="com.intellij.codeInsight.template.postfix.settings.PostfixTemplatesSettings"/>
<customLiveTemplate implementation="com.intellij.codeInsight.template.postfix.templates.PostfixLiveTemplate"/>
<completion.contributor language="JAVA" implementationClass="com.intellij.codeInsight.template.postfix.completion.PostfixTemplateCompletionContributor"/>
<lookup.actionProvider implementation="com.intellij.codeInsight.template.postfix.completion.PostfixTemplateLookupActionProvider"/>
<editorOptionsProvider id="editing.postfixCompletion" instance="com.intellij.codeInsight.template.postfix.settings.PostfixTemplatesConfigurable"/>
</extensions>
</idea-plugin>