WEB-8934 Preview for Emmet live templates

This commit is contained in:
Alexander Zolotov
2013-10-15 20:29:21 +04:00
parent 04097311e2
commit d08ee7aa99
12 changed files with 229 additions and 39 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2013 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,7 +20,9 @@ import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInsight.lookup.RealLookupElementPresentation;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.event.KeyEvent;
@@ -30,15 +32,17 @@ import java.awt.event.KeyEvent;
public class LiveTemplateLookupElement extends LookupElement {
private final String myPrefix;
private final TemplateImpl myTemplate;
private final String myLookupString;
public final boolean sudden;
private final boolean myWorthShowingInAutoPopup;
public LiveTemplateLookupElement(TemplateImpl template, boolean sudden) {
this(template, sudden, false);
this(template, null, sudden, false);
}
public LiveTemplateLookupElement(TemplateImpl template, boolean sudden, boolean worthShowingInAutoPopup) {
public LiveTemplateLookupElement(TemplateImpl template, @Nullable String lookupString, boolean sudden, boolean worthShowingInAutoPopup) {
this.sudden = sudden;
myLookupString = lookupString;
myPrefix = template.getKey();
myTemplate = template;
myWorthShowingInAutoPopup = worthShowingInAutoPopup;
@@ -56,6 +60,7 @@ public class LiveTemplateLookupElement extends LookupElement {
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setItemText(StringUtil.notNullize(myLookupString, myPrefix));
if (sudden) {
presentation.setItemTextBold(true);
if (!presentation.isReal() || !((RealLookupElementPresentation)presentation).isLookupSelectionTouched()) {
@@ -71,7 +76,6 @@ public class LiveTemplateLookupElement extends LookupElement {
}
} else {
presentation.setTypeText(myTemplate.getDescription());
}
}
@@ -238,6 +238,7 @@ zen.coding.title=Emmet (XML Zen Coding)
emmet.configuration.title=Emmet (Zen Coding)
emmet.enable.label=&Enable XML Emmet
emmet.enable.bem.filter=Enable &BEM filter by default
emmet.enable.preview=Enable &abbreviation preview
emmet.expand.abbreviation.with=Expand &abbreviation with
zen.coding.enter.abbreviation.dialog.label=Please enter an abbreviation
zen.coding.incorrect.abbreviation.error=Incorrect abbreviation
@@ -44,6 +44,7 @@ public class EmmetOptions implements PersistentStateComponent<EmmetOptions>, Exp
private int myEmmetExpandShortcut = WebEditorOptions.getInstance().getZenCodingExpandShortcut();
private boolean myFuzzySearchEnabled = true;
private boolean myAutoInsertCssPrefixedEnabled = true;
private boolean myPreviewEnabled = false;
@NotNull
private Map<String, Integer> prefixes = ContainerUtil.newHashMap();
@@ -64,6 +65,14 @@ public class EmmetOptions implements PersistentStateComponent<EmmetOptions>, Exp
return myEmmetExpandShortcut;
}
public boolean isPreviewEnabled() {
return myPreviewEnabled;
}
public void setPreviewEnabled(boolean previewEnabled) {
myPreviewEnabled = previewEnabled;
}
public boolean isEmmetEnabled() {
return myEmmetEnabled;
}
@@ -8,7 +8,7 @@
<properties/>
<border type="none"/>
<children>
<grid id="369dc" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="369dc" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -35,6 +35,14 @@
<text resource-bundle="messages/XmlBundle" key="emmet.enable.label"/>
</properties>
</component>
<component id="c52c1" class="com.intellij.ui.components.JBCheckBox" binding="myEnablePreviewJBCheckBox">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/XmlBundle" key="emmet.enable.preview"/>
</properties>
</component>
</children>
</grid>
<vspacer id="27c08">
@@ -34,6 +34,7 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
private JBCheckBox myEnableBEMFilterJBCheckBox;
private JPanel myPanel;
private JBCheckBox myEnableEmmetJBCheckBox;
private JBCheckBox myEnablePreviewJBCheckBox;
public XmlEmmetConfigurable() {
myEnableEmmetJBCheckBox.addActionListener(new ActionListener() {
@@ -41,6 +42,7 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
public void actionPerformed(ActionEvent e) {
boolean selected = myEnableEmmetJBCheckBox.isSelected();
myEnableBEMFilterJBCheckBox.setEnabled(selected);
myEnablePreviewJBCheckBox.setEnabled(selected);
}
});
}
@@ -59,6 +61,7 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
public boolean isModified() {
EmmetOptions emmetOptions = EmmetOptions.getInstance();
return emmetOptions.isEmmetEnabled() != myEnableEmmetJBCheckBox.isSelected() ||
emmetOptions.isPreviewEnabled() != myEnablePreviewJBCheckBox.isSelected() ||
emmetOptions.isBemFilterEnabledByDefault() != myEnableBEMFilterJBCheckBox.isSelected();
}
@@ -68,6 +71,7 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
EmmetOptions emmetOptions = EmmetOptions.getInstance();
emmetOptions.setEmmetEnabled(myEnableEmmetJBCheckBox.isSelected());
emmetOptions.setBemFilterEnabledByDefault(myEnableBEMFilterJBCheckBox.isSelected());
emmetOptions.setPreviewEnabled(myEnablePreviewJBCheckBox.isSelected());
}
@Override
@@ -76,6 +80,8 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
myEnableEmmetJBCheckBox.setSelected(emmetOptions.isEmmetEnabled());
myEnableBEMFilterJBCheckBox.setEnabled(emmetOptions.isEmmetEnabled());
myEnableBEMFilterJBCheckBox.setSelected(emmetOptions.isBemFilterEnabledByDefault());
myEnablePreviewJBCheckBox.setEnabled(emmetOptions.isEmmetEnabled());
myEnablePreviewJBCheckBox.setSelected(emmetOptions.isPreviewEnabled());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2013 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.
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.lookup.InsertHandlerDecorator;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.template.emmet.completion.XmlEmmetAbbreviationCompletionProvider;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.actionSystem.IdeActions;
@@ -79,6 +80,7 @@ public class XmlCompletionContributor extends CompletionContributor {
};
public XmlCompletionContributor() {
extend(CompletionType.BASIC, psiElement().inside(XmlPatterns.xmlFile()), new XmlEmmetAbbreviationCompletionProvider());
extend(CompletionType.BASIC,
psiElement().inside(XmlPatterns.xmlAttributeValue()),
new CompletionProvider<CompletionParameters>() {
@@ -44,6 +44,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -66,8 +67,9 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
@Nullable
public static ZenCodingNode parse(@NotNull String text,
@NotNull CustomTemplateCallback callback,
@NotNull ZenCodingGenerator generator, @Nullable String surroundedText) {
@NotNull CustomTemplateCallback callback,
@NotNull ZenCodingGenerator generator,
@Nullable String surroundedText) {
List<ZenCodingToken> tokens = new EmmetLexer().lex(text);
if (tokens == null) {
return null;
@@ -99,7 +101,7 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
public void expand(String key, @NotNull CustomTemplateCallback callback) {
ZenCodingGenerator defaultGenerator = findApplicableDefaultGenerator(callback.getContext(), false);
assert defaultGenerator != null;
expand(key, callback, null, defaultGenerator);
expand(key, callback, null, defaultGenerator, Collections.<ZenCodingFilter>emptyList(), true);
}
@Nullable
@@ -154,18 +156,17 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
}
public static void expand(String key,
@NotNull CustomTemplateCallback callback,
String surroundedText,
@NotNull ZenCodingGenerator defaultGenerator) {
ZenCodingNode node = parse(key, callback, defaultGenerator, surroundedText);
public static void expand(@NotNull String key, @NotNull CustomTemplateCallback callback, @Nullable String surroundedText,
@NotNull ZenCodingGenerator defaultGenerator,
@NotNull Collection<? extends ZenCodingFilter> extraFilters,
boolean expandPrimitiveAbbreviations) {
final ZenCodingNode node = parse(key, callback, defaultGenerator, surroundedText);
if (node == null) {
return;
}
if (surroundedText == null) {
if (node instanceof TemplateNode) {
if (key.equals(((TemplateNode)node).getTemplateToken().getKey()) &&
callback.findApplicableTemplates(key).size() > 1) {
if (key.equals(((TemplateNode)node).getTemplateToken().getKey()) && callback.findApplicableTemplates(key).size() > 1) {
callback.startTemplate();
return;
}
@@ -176,15 +177,16 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
PsiElement context = callback.getContext();
ZenCodingGenerator generator = findApplicableGenerator(node, context, false);
List<ZenCodingFilter> filters = getFilters(node, context);
filters.addAll(extraFilters);
expand(node, generator, filters, surroundedText, callback);
expand(node, generator, filters, surroundedText, callback, expandPrimitiveAbbreviations);
}
private static void expand(ZenCodingNode node,
ZenCodingGenerator generator,
List<ZenCodingFilter> filters,
String surroundedText,
CustomTemplateCallback callback) {
CustomTemplateCallback callback, boolean expandPrimitiveAbbreviations) {
if (surroundedText != null) {
surroundedText = surroundedText.trim();
}
@@ -192,6 +194,12 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
GenerationNode fakeParentNode = new GenerationNode(TemplateToken.EMPTY_TEMPLATE_TOKEN, -1, 1, surroundedText, true, null);
node.expand(-1, 1, surroundedText, callback, true, fakeParentNode);
if (!expandPrimitiveAbbreviations) {
if (isPrimitiveNode(node)) {
return;
}
}
List<GenerationNode> genNodes = fakeParentNode.getChildren();
LiveTemplateBuilder builder = new LiveTemplateBuilder();
int end = -1;
@@ -248,6 +256,16 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
});
}
private static boolean isPrimitiveNode(@NotNull ZenCodingNode node) {
if (node instanceof TemplateNode) {
final TemplateToken token = ((TemplateNode)node).getTemplateToken();
if (token != null && token.getAttribute2Value().isEmpty()) {
return true;
}
}
return false;
}
public void wrap(final String selection, @NotNull final CustomTemplateCallback callback) {
InputValidatorEx validator = new InputValidatorEx() {
public String getErrorText(String inputString) {
@@ -289,9 +307,7 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
return applicableGenerator != null && applicableGenerator.isEnabled();
}
public static void doWrap(final String selection,
final String abbreviation,
final CustomTemplateCallback callback) {
public static void doWrap(final String selection, final String abbreviation, final CustomTemplateCallback callback) {
final ZenCodingGenerator defaultGenerator = findApplicableDefaultGenerator(callback.getContext(), true);
assert defaultGenerator != null;
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -308,7 +324,7 @@ public class ZenCodingTemplate implements CustomLiveTemplate {
EditorModificationUtil.deleteSelectedText(callback.getEditor());
PsiDocumentManager.getInstance(callback.getProject()).commitAllDocuments();
expand(node, generator, filters, selection, callback);
expand(node, generator, filters, selection, callback, true);
}
}, CodeInsightBundle.message("insert.code.template.command"), null);
}
@@ -0,0 +1,101 @@
/*
* Copyright 2000-2013 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.emmet.completion;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.template.CustomTemplateCallback;
import com.intellij.codeInsight.template.Template;
import com.intellij.codeInsight.template.TemplateEditingListener;
import com.intellij.codeInsight.template.emmet.ZenCodingTemplate;
import com.intellij.codeInsight.template.emmet.filters.SingleLineEmmetFilter;
import com.intellij.codeInsight.template.emmet.generators.ZenCodingGenerator;
import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor;
import com.intellij.codeInsight.template.impl.LiveTemplateLookupElement;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Ref;
import com.intellij.patterns.StandardPatterns;
import com.intellij.psi.PsiFile;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Map;
/**
* User: zolotov
* Date: 8/9/13
*/
abstract public class EmmetAbbreviationCompletionProvider extends CompletionProvider<CompletionParameters> {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
if (!isAvailable(parameters)) {
return;
}
final PsiFile file = parameters.getPosition().getContainingFile();
final Editor editor = parameters.getEditor();
final String completionPrefix = result.getPrefixMatcher().getPrefix();
final String templatePrefix = LiveTemplateCompletionContributor.findLiveTemplatePrefix(file, editor, completionPrefix);
if (LiveTemplateCompletionContributor.findApplicableTemplate(file, parameters.getOffset(), templatePrefix) != null) {
// exclude perfect matches with existing templates because LiveTemplateCompletionContributor handles it
result.restartCompletionOnPrefixChange(StandardPatterns.string().startsWith(templatePrefix));
return;
}
final Ref<TemplateImpl> generatedTemplate = new Ref<TemplateImpl>();
final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file, false) {
@Override
public void deleteTemplateKey(String key) {
}
@Override
public void startTemplate(Template template, Map<String, String> predefinedValues, TemplateEditingListener listener) {
if (template instanceof TemplateImpl && !((TemplateImpl)template).isDeactivated()) {
generatedTemplate.set((TemplateImpl)template);
}
}
};
final Collection<SingleLineEmmetFilter> extraFilters = ContainerUtil.newLinkedList(new SingleLineEmmetFilter());
ZenCodingTemplate.expand(templatePrefix, callback, null, getGenerator(), extraFilters, false);
if (!generatedTemplate.isNull()) {
result = result.withPrefixMatcher(templatePrefix);
final TemplateImpl template = generatedTemplate.get();
template.setKey(templatePrefix);
template.setDescription(template.getTemplateText());
result.addElement(createLookupElement(template));
result.restartCompletionOnPrefixChange(StandardPatterns.string().startsWith(templatePrefix));
}
}
protected LiveTemplateLookupElement createLookupElement(TemplateImpl template) {
return new LiveTemplateLookupElement(template, null, true, true);
}
protected abstract ZenCodingGenerator getGenerator();
protected boolean isAvailable(CompletionParameters parameters) {
return parameters.getInvocationCount() == 0;
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2000-2013 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.emmet.completion;
import com.intellij.application.options.emmet.EmmetOptions;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.template.emmet.generators.XmlZenCodingGeneratorImpl;
import com.intellij.codeInsight.template.emmet.generators.ZenCodingGenerator;
import com.intellij.codeInsight.template.impl.LiveTemplateLookupElement;
import com.intellij.codeInsight.template.impl.TemplateImpl;
/**
* User: zolotov
* Date: 8/9/13
*/
public class XmlEmmetAbbreviationCompletionProvider extends EmmetAbbreviationCompletionProvider {
@Override
protected boolean isAvailable(CompletionParameters parameters) {
final EmmetOptions emmetOptions = EmmetOptions.getInstance();
return super.isAvailable(parameters) && emmetOptions.isEmmetEnabled() && emmetOptions.isPreviewEnabled();
}
@Override
protected LiveTemplateLookupElement createLookupElement(final TemplateImpl template) {
final String description = template.getDescription();
template.setDescription(null);
return new LiveTemplateLookupElement(template, description, false, true);
}
@Override
protected ZenCodingGenerator getGenerator() {
return XmlZenCodingGeneratorImpl.INSTANCE;
}
}
@@ -119,7 +119,7 @@ public class XmlZenCodingGeneratorImpl extends XmlZenCodingGenerator {
}
}
});
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
for (final SmartPsiElementPointer<XmlTag> pointer : tagToClose) {
final XmlTag tag = pointer.getElement();
if (tag != null) {
@@ -133,13 +133,13 @@ public class XmlZenCodingGeneratorImpl extends XmlZenCodingGenerator {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
document.replaceString(offset, tag.getTextRange().getEndOffset(), "/>");
documentManager.commitDocument(document);
}
});
}
}
}
}
documentManager.commitAllDocuments();
}
private static boolean isTagClosed(@NotNull XmlTag tag) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2013 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.
@@ -28,7 +28,6 @@ import com.intellij.codeInsight.template.emmet.tokens.TemplateToken;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.injected.editor.DocumentWindowImpl;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.StdFileTypes;
@@ -272,23 +271,19 @@ public class GenerationNode extends UserDataHolderBase {
assert template != null;
final XmlFile xmlFile = token.getFile();
XmlDocument document = xmlFile.getDocument();
if (document != null) {
final XmlTag tag = document.getRootTag();
PsiFileFactory fileFactory = PsiFileFactory.getInstance(xmlFile.getProject());
XmlFile dummyFile = (XmlFile)fileFactory.createFileFromText("dummy.xml", StdFileTypes.XML, xmlFile.getText());
final XmlTag tag = dummyFile.getRootTag();
if (tag != null) {
for (Pair<String, String> pair : attr2value) {
if (Strings.isNullOrEmpty(pair.second)) {
template.addVariable(pair.first, "", "", true);
}
}
if (tag != null) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
XmlTag tag1 = hasChildren ? expandEmptyTagIfNeccessary(tag) : tag;
setAttributeValues(tag1, attr2value);
token.setFile((XmlFile)tag1.getContainingFile());
}
});
}
XmlTag tag1 = hasChildren ? expandEmptyTagIfNeccessary(tag) : tag;
setAttributeValues(tag1, attr2value);
token.setFile((XmlFile)tag1.getContainingFile());
}
ZenCodingGenerator zenCodingGenerator = generator != null ? generator : XmlZenCodingGeneratorImpl.INSTANCE;
template = zenCodingGenerator.generateTemplate(token, hasChildren, callback.getContext());
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2013 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.
@@ -65,6 +65,7 @@ public class TemplateNode extends ZenCodingNode {
TemplateImpl template = myGenerator.createTemplateByKey(newTemplateKey);
if (template != null) {
template.setDeactivated(true);
newTemplateToken.setTemplate(template, callback);
templateToken = newTemplateToken;
}