From 95a83c2c36ad32af0f48fca25152031d05c1f63d Mon Sep 17 00:00:00 2001 From: azerr Date: Tue, 13 Jun 2023 08:00:30 +0200 Subject: [PATCH] Improve completion Signed-off-by: azerr --- .../lsp4ij/internal/SupportedFeatures.java | 24 +- .../completion/LSPCompletionConfidence.java | 15 + .../highlight/LSPHighlightPsiElement.java | 84 +++++ .../highlight/LSPHighlightUsagesHandler.java | 24 +- .../LSPHighlightUsagesHandlerFactory.java | 12 +- .../intellij/qute/lang/QuteASTFactory.java | 22 ++ .../intellij/qute/lang/QuteASTNode.java | 60 --- .../intellij/qute/lang/QuteFileType.java | 4 + .../qute/lang/QuteFileViewProvider.java | 30 +- .../lang/QuteFileViewProviderFactory.java | 9 +- .../intellij/qute/lang/QuteLanguage.java | 5 + .../qute/lang/QuteLanguageSubstitutor.java | 14 +- .../intellij/qute/lang/QuteLexer.java | 98 ----- .../qute/lang/QuteParserDefinition.java | 16 +- .../format/QuteFileIndentOptionsProvider.java | 34 ++ .../format/QuteFormattingModelBuilder.java | 354 ++++++++++++++++++ .../highlighter/QuteEditorHighlighter.java | 27 ++ .../QuteEditorHighlighterProvider.java | 17 + .../highlighter/QuteSyntaxHighlighter.java | 62 +++ .../QuteSyntaxHighlighterFactory.java | 13 + .../qute/lang/psi/AbstractQuteSubLexer.java | 54 +++ .../intellij/qute/lang/psi/QuteElement.java | 11 + .../intellij/qute/lang/psi/QuteLexer.java | 203 ++++++++++ .../qute/lang/psi/QuteLexerForExpression.java | 101 +++++ .../qute/lang/psi/QuteLexerForStartTag.java | 81 ++++ .../qute/lang/{ => psi}/QuteParser.java | 31 +- .../intellij/qute/lang/psi/QuteParsing.java | 170 +++++++++ .../qute/lang/psi/QutePsiExpression.java | 4 + .../qute/lang/{ => psi}/QutePsiFile.java | 7 +- .../qute/lang/psi/QutePsiReference.java | 59 +++ .../intellij/qute/lang/psi/QuteToken.java | 32 ++ .../lang/{ => psi/tree}/QuteElementType.java | 3 +- .../lang/{ => psi/tree}/QuteElementTypes.java | 33 +- .../lang/psi/tree/QutePsiElementFactory.java | 11 + .../qute/lang/psi/tree/QuteTokenType.java | 46 +++ src/main/resources/META-INF/lsp4ij-qute.xml | 1 + src/main/resources/META-INF/plugin.xml | 7 +- 37 files changed, 1541 insertions(+), 237 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionConfidence.java create mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightPsiElement.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTFactory.java delete mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTNode.java delete mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLexer.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFileIndentOptionsProvider.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFormattingModelBuilder.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighter.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighterProvider.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighter.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighterFactory.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/AbstractQuteSubLexer.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteElement.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexer.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpression.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForStartTag.java rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi}/QuteParser.java (57%) create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParsing.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiExpression.java rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi}/QutePsiFile.java (82%) create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiReference.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteToken.java rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi/tree}/QuteElementType.java (88%) rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi/tree}/QuteElementTypes.java (67%) create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QutePsiElementFactory.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteTokenType.java diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java index deea74ff8..24fa8c63e 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/internal/SupportedFeatures.java @@ -78,11 +78,11 @@ public class SupportedFeatures { textDocumentClientCapabilities.setInlayHint(new InlayHintCapabilities()); // TODO : support textDocument/colorPresentation // textDocumentClientCapabilities.setColorProvider(new ColorProviderCapabilities()); - final var completionItemCapabilities = new CompletionItemCapabilities(Boolean.TRUE); + final var completionItemCapabilities = new CompletionItemCapabilities(Boolean.FALSE); completionItemCapabilities .setDocumentationFormat(Arrays.asList(MarkupKind.MARKDOWN, MarkupKind.PLAINTEXT)); - completionItemCapabilities.setInsertTextModeSupport(new CompletionItemInsertTextModeSupportCapabilities(List.of(InsertTextMode.AsIs, InsertTextMode.AdjustIndentation))); - completionItemCapabilities.setResolveSupport(new CompletionItemResolveSupportCapabilities(List.of("documentation", "detail", "additionalTextEdits"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + // completionItemCapabilities.setInsertTextModeSupport(new CompletionItemInsertTextModeSupportCapabilities(List.of(InsertTextMode.AsIs, InsertTextMode.AdjustIndentation))); + // completionItemCapabilities.setResolveSupport(new CompletionItemResolveSupportCapabilities(List.of("documentation", "detail", "additionalTextEdits"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ textDocumentClientCapabilities.setCompletion(new CompletionCapabilities(completionItemCapabilities)); final var definitionCapabilities = new DefinitionCapabilities(); definitionCapabilities.setLinkSupport(Boolean.TRUE); @@ -115,11 +115,13 @@ public class SupportedFeatures { textDocumentClientCapabilities.setOnTypeFormatting(null); // TODO // TODO : support textDocument/rangeFormatting // textDocumentClientCapabilities.setRangeFormatting(new RangeFormattingCapabilities()); - textDocumentClientCapabilities.setReferences(new ReferencesCapabilities()); - final var renameCapabilities = new RenameCapabilities(); - renameCapabilities.setPrepareSupport(true); - textDocumentClientCapabilities.setRename(renameCapabilities); - // TODO + // TODO : support textDocument/references + // textDocumentClientCapabilities.setReferences(new ReferencesCapabilities()); + // TODO : support textDocument/rename + //final var renameCapabilities = new RenameCapabilities(); + //renameCapabilities.setPrepareSupport(true); + //textDocumentClientCapabilities.setRename(renameCapabilities); + // TODO : support textDocument/signatureHelp // textDocumentClientCapabilities.setSignatureHelp(new SignatureHelpCapabilities()); textDocumentClientCapabilities .setSynchronization(new SynchronizationCapabilities(Boolean.TRUE, Boolean.TRUE, Boolean.TRUE)); @@ -151,9 +153,9 @@ public class SupportedFeatures { public static WindowClientCapabilities getWindowClientCapabilities() { final var windowClientCapabilities = new WindowClientCapabilities(); - windowClientCapabilities.setShowDocument(new ShowDocumentCapabilities(true)); - windowClientCapabilities.setWorkDoneProgress(true); - windowClientCapabilities.setShowMessage(new WindowShowMessageRequestCapabilities()); + //windowClientCapabilities.setShowDocument(new ShowDocumentCapabilities(true)); + //windowClientCapabilities.setWorkDoneProgress(true); + //windowClientCapabilities.setShowMessage(new WindowShowMessageRequestCapabilities()); return windowClientCapabilities; } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionConfidence.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionConfidence.java new file mode 100644 index 000000000..81307a0b9 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionConfidence.java @@ -0,0 +1,15 @@ +package com.redhat.devtools.intellij.lsp4ij.operations.completion; + +import com.intellij.codeInsight.completion.CompletionConfidence; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.ThreeState; +import org.jetbrains.annotations.NotNull; + +public class LSPCompletionConfidence extends CompletionConfidence { + + @Override + public @NotNull ThreeState shouldSkipAutopopup(@NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) { + return ThreeState.NO; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightPsiElement.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightPsiElement.java new file mode 100644 index 000000000..516e46259 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightPsiElement.java @@ -0,0 +1,84 @@ +package com.redhat.devtools.intellij.lsp4ij.operations.highlight; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.PsiElementBase; +import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; +import org.eclipse.lsp4j.DocumentHighlight; +import org.eclipse.lsp4j.DocumentHighlightKind; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class LSPHighlightPsiElement extends PsiElementBase { + + private final TextRange textRange; + private final DocumentHighlightKind kind; + + public LSPHighlightPsiElement(DocumentHighlight highlight, Document document) { + this.textRange = LSPIJUtils.toTextRange(highlight.getRange(), document); + this.kind = highlight.getKind(); + } + + public DocumentHighlightKind getKind() { + return kind; + } + + @Override + public @NotNull Language getLanguage() { + return null; + } + + @Override + public PsiElement @NotNull [] getChildren() { + return new PsiElement[0]; + } + + @Override + public PsiElement getParent() { + return null; + } + + @Override + public TextRange getTextRange() { + return textRange; + } + + @Override + public int getStartOffsetInParent() { + return 0; + } + + @Override + public int getTextLength() { + return 0; + } + + @Override + public @Nullable PsiElement findElementAt(int offset) { + return null; + } + + @Override + public int getTextOffset() { + return 0; + } + + @Override + public @NlsSafe String getText() { + return null; + } + + @Override + public char @NotNull [] textToCharArray() { + return new char[0]; + } + + @Override + public ASTNode getNode() { + return null; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java index 64e3caa30..909c57ca0 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandler.java @@ -15,33 +15,41 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.Consumer; +import org.eclipse.lsp4j.DocumentHighlightKind; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.logging.Logger; -public class LSPHighlightUsagesHandler extends HighlightUsagesHandlerBase { +public class LSPHighlightUsagesHandler extends HighlightUsagesHandlerBase { private static final Logger LOGGER = Logger.getLogger(LSPHighlightUsagesHandler.class.getName()); - private final List targets; + private final List targets; - public LSPHighlightUsagesHandler(Editor editor, PsiFile file, List targets) { + public LSPHighlightUsagesHandler(Editor editor, PsiFile file, List targets) { super(editor, file); this.targets = targets; } @Override - public @NotNull List getTargets() { + public @NotNull List getTargets() { return targets; } @Override - protected void selectTargets(@NotNull List targets, - @NotNull Consumer> selectionConsumer) { + protected void selectTargets(@NotNull List targets, + @NotNull Consumer> selectionConsumer) { selectionConsumer.consume(targets); } @Override - public void computeUsages(@NotNull List targets) { - targets.forEach(target -> myReadUsages.add(target.getTextRange())); + public void computeUsages(@NotNull List targets) { + targets.forEach(target -> + { + if (target.getKind() == DocumentHighlightKind.Read) { + myReadUsages.add(target.getTextRange()); + } else { + myWriteUsages.add(target.getTextRange()); + } + }); } } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java index 041c69fb0..5d901ae39 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightUsagesHandlerFactory.java @@ -16,6 +16,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; @@ -43,12 +44,12 @@ public class LSPHighlightUsagesHandlerFactory implements HighlightUsagesHandlerF @Override public @Nullable HighlightUsagesHandlerBase createHighlightUsagesHandler(@NotNull Editor editor, @NotNull PsiFile file) { - List targets = getTargets(editor, file); + List targets = getTargets(editor, file); return targets.isEmpty()?null:new LSPHighlightUsagesHandler(editor, file, targets); } - private List getTargets(Editor editor, PsiFile file) { - List elements = new ArrayList<>(); + private List getTargets(Editor editor, PsiFile file) { + List elements = new ArrayList<>(); try { int offset = TargetElementUtil.adjustOffset(file, editor.getDocument(), editor.getCaretModel().getOffset()); Document document = editor.getDocument(); @@ -76,11 +77,12 @@ private List getTargets(Editor editor, PsiFile file) { ProgressManager.checkCanceled(); DocumentHighlight highlight = highlights.poll(25, TimeUnit.MILLISECONDS); if (highlight != null) { - int highlightOffset = LSPIJUtils.toOffset(highlight.getRange().getStart(), document); + elements.add(new LSPHighlightPsiElement(highlight, document)); + /*int highlightOffset = LSPIJUtils.toOffset(highlight.getRange().getStart(), document); PsiElement element = file.findElementAt(highlightOffset); if (element != null) { elements.add(element); - } + }*/ } } } catch (InterruptedException e) { diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTFactory.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTFactory.java new file mode 100644 index 000000000..edf416bea --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTFactory.java @@ -0,0 +1,22 @@ +package com.redhat.devtools.intellij.qute.lang; + +import com.intellij.lang.ASTFactory; +import com.intellij.psi.impl.source.tree.LeafElement; +import com.intellij.psi.templateLanguages.OuterLanguageElementImpl; +import com.intellij.psi.tree.IElementType; +import com.redhat.devtools.intellij.qute.lang.psi.QuteToken; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementType; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class QuteASTFactory extends ASTFactory { + + @Override + public @Nullable LeafElement createLeaf(@NotNull IElementType type, @NotNull CharSequence text) { + if (type == QuteElementTypes.QUTE_OUTER_ELEMENT_TYPE) { + return new OuterLanguageElementImpl(type, text); + } + return new QuteToken(type, text); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTNode.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTNode.java deleted file mode 100644 index 011336178..000000000 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTNode.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 Red Hat, Inc. - * Distributed under license by Red Hat, Inc. All rights reserved. - * This program is made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package com.redhat.devtools.intellij.qute.lang; - -import com.intellij.psi.impl.source.tree.CompositeElement; -import com.intellij.psi.impl.source.tree.LeafPsiElement; -import com.intellij.psi.impl.source.tree.TreeElement; -import com.redhat.qute.parser.template.ASTVisitor; -import com.redhat.qute.parser.template.Node; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class QuteASTNode extends CompositeElement { - private final Node node; - - public QuteASTNode(Node node) { - super(QuteElementTypes.fromNode(node)); - this.node = node; - int end = node.getStart(); - for(Node child : getChildren(node)) { - if (end != -1 && end < child.getStart()) { - rawAddChildrenWithoutNotifications(new LeafPsiElement(QuteElementTypes.QUTE_TEXT, child.getOwnerTemplate().getText(end, child.getStart()))); - } - rawAddChildrenWithoutNotifications(getNode(child)); - end = child.getEnd(); - } - if (end < node.getEnd()) { - rawAddChildrenWithoutNotifications(new LeafPsiElement(QuteElementTypes.QUTE_CONTENT, node.getOwnerTemplate().getText(end, node.getEnd()))); - } - } - - @NotNull - public static TreeElement getNode(Node child) { - return getChildren(child).isEmpty()?new LeafPsiElement(QuteElementTypes.fromNode(child), child.getOwnerTemplate().getText(child.getStart(), child.getEnd())):new QuteASTNode(child); - } - - private static List getChildren(Node node) { - List children = new ArrayList<>(); - node.accept(new ASTVisitor() { - @Override - public boolean preVisit2(Node child) { - if (child.getParent() == node) { - children.add(child); - } - return child.getParent() == node || child == node; - } - }); - return children; - } -} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileType.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileType.java index 455011a38..3d175ab24 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileType.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileType.java @@ -32,9 +32,13 @@ import javax.swing.Icon; +/** + * Qute language file type. + */ public class QuteFileType extends LanguageFileType { private static final Icon QUARKUS_ICON = IconLoader.findIcon("/quarkus_icon_rgb_16px_default.png", QuarkusIconProvider.class); + @NotNull public static final QuteFileType QUTE = new QuteFileType(); private QuteFileType() { diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProvider.java index 5cb510550..2cb6a6ee9 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProvider.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProvider.java @@ -13,6 +13,9 @@ import com.intellij.lang.Language; import com.intellij.lang.LanguageParserDefinitions; import com.intellij.lang.ParserDefinition; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.MultiplePsiFilesPerDocumentFileViewProvider; import com.intellij.psi.PsiFile; @@ -20,22 +23,41 @@ import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider; import com.intellij.psi.tree.IElementType; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; import org.jetbrains.annotations.NotNull; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; +/** + * Qute file view provider. + */ public class QuteFileViewProvider extends MultiplePsiFilesPerDocumentFileViewProvider implements TemplateLanguageFileViewProvider { private final Language language; private final Language templateLanguage; - public QuteFileViewProvider(VirtualFile file, Language language, Language templateLanguage, PsiManager manager, boolean eventSystemEnabled) { + public QuteFileViewProvider(VirtualFile file, Language language, PsiManager manager, boolean eventSystemEnabled) { + this(file, language, getTemplateLanguage(file), manager, eventSystemEnabled); + } + + private QuteFileViewProvider(VirtualFile file, Language language, Language templateLanguage, PsiManager manager, boolean eventSystemEnabled) { super(manager, file, eventSystemEnabled); this.language = language; this.templateLanguage = templateLanguage; } + /** + * Returns the template language of the given file (ex : "HTML", "YAML", language etc) and the "Qute_" language otherwise. + * + * @param file the virtual file. + * + * @return the template language of the given file (ex : "HTML", "YAML", language etc) and the "Qute_" language otherwise. + */ + public static Language getTemplateLanguage(VirtualFile file) { + FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(file.getExtension()); + return fileType instanceof LanguageFileType ? ((LanguageFileType) fileType).getLanguage() : QuteLanguage.INSTANCE; + } + protected PsiFile createFile(@NotNull Language lang) { if (lang == getTemplateDataLanguage()) { final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang); @@ -58,8 +80,8 @@ protected PsiFile createFile(@NotNull Language lang) { @Override public @NotNull Set getLanguages() { Set languages = new LinkedHashSet<>(); - languages.add(getTemplateDataLanguage()); languages.add(getBaseLanguage()); + languages.add(getTemplateDataLanguage()); return languages; } @@ -70,7 +92,7 @@ protected PsiFile createFile(@NotNull Language lang) { @Override public IElementType getContentElementType(@NotNull Language language) { - return language == getTemplateDataLanguage()?QuteElementTypes.QUTE_FILE_DATA:null; + return language == getTemplateDataLanguage() ? QuteElementTypes.QUTE_FILE_DATA : null; } @Override diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProviderFactory.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProviderFactory.java index 8a484fe1a..cd1def295 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProviderFactory.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteFileViewProviderFactory.java @@ -11,19 +11,18 @@ package com.redhat.devtools.intellij.qute.lang; import com.intellij.lang.Language; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.FileViewProvider; import com.intellij.psi.FileViewProviderFactory; import com.intellij.psi.PsiManager; import org.jetbrains.annotations.NotNull; +/** + * Qute file view provider factory. + */ public class QuteFileViewProviderFactory implements FileViewProviderFactory { @Override public @NotNull FileViewProvider createFileViewProvider(@NotNull VirtualFile file, Language language, @NotNull PsiManager manager, boolean eventSystemEnabled) { - FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(file.getExtension()); - return new QuteFileViewProvider(file, language, fileType instanceof LanguageFileType?((LanguageFileType) fileType).getLanguage():language, manager, eventSystemEnabled); + return new QuteFileViewProvider(file, language, manager, eventSystemEnabled); } } diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguage.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguage.java index ffccfb105..97f0ea97a 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguage.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguage.java @@ -16,7 +16,12 @@ import com.intellij.psi.templateLanguages.TemplateLanguage; import org.jetbrains.annotations.NotNull; +/** + * Qute language. + */ public class QuteLanguage extends Language implements TemplateLanguage, InjectableLanguage { + + @NotNull public static final QuteLanguage INSTANCE = new QuteLanguage(); private QuteLanguage() { diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java index f37d1892d..6769bb165 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLanguageSubstitutor.java @@ -11,8 +11,6 @@ package com.redhat.devtools.intellij.qute.lang; import com.intellij.lang.Language; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleUtilCore; @@ -27,7 +25,13 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; - +/** + * Qute language substitutor to force some language file (ex:HTML, YAML, etc) to "_Qute" language when: + *
    + *
  • the HTML, YAML, etc file is hosted in a Qute project.
  • + *
  • the HTML, YAML, etc file is hosted in the src/main/resources/templates folder.
  • + *
+ */ public class QuteLanguageSubstitutor extends LanguageSubstitutor { protected boolean isTemplate(VirtualFile file, Module module) { return file.getPath().contains("templates") && ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(file); @@ -51,8 +55,8 @@ public static boolean isQuteLibrary(@NotNull LibraryOrderEntry libraryOrderEntry } private Module findModule(VirtualFile file) { - for(Project project : ProjectManager.getInstance().getOpenProjects()) { - for(Module module : ModuleManager.getInstance(project).getModules()) { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + for (Module module : ModuleManager.getInstance(project).getModules()) { if (ModuleUtilCore.moduleContainsFile(module, file, false)) { return module; } diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLexer.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLexer.java deleted file mode 100644 index a5e19e233..000000000 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteLexer.java +++ /dev/null @@ -1,98 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 Red Hat, Inc. - * Distributed under license by Red Hat, Inc. All rights reserved. - * This program is made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package com.redhat.devtools.intellij.qute.lang; - -import com.intellij.lexer.Lexer; -import com.intellij.lexer.LexerPosition; -import com.intellij.psi.tree.IElementType; -import com.redhat.qute.parser.template.Node; -import com.redhat.qute.parser.template.Template; -import com.redhat.qute.parser.template.TemplateParser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class QuteLexer extends Lexer { - Template template; - private CharSequence buffer; - private int endOffset; - - private int index; - - @Override - public void start(@NotNull CharSequence buffer, int startOffset, int endOffset, int initialState) { - this.buffer = buffer; - this.endOffset = endOffset; - template = TemplateParser.parse(buffer.subSequence(startOffset, endOffset).toString(), ""); - this.index = initialState; - } - - @Override - public int getState() { - return index; - } - - private Node getToken() { - return index< template.getChildCount()?template.getChild(index):null; - } - @Override - public @Nullable IElementType getTokenType() { - Node node = getToken(); - return node!=null?QuteElementTypes.fromNode(node):null; - } - - @Override - public int getTokenStart() { - Node node = getToken(); - return node!=null?node.getStart():-1; - } - - @Override - public int getTokenEnd() { - Node node = getToken(); - return node!=null?node.getEnd():-1; - - } - - @Override - public void advance() { - ++index; - } - - @Override - public @NotNull LexerPosition getCurrentPosition() { - return new LexerPosition() { - @Override - public int getOffset() { - return getTokenStart(); - } - - @Override - public int getState() { - return getState(); - } - }; - } - - @Override - public void restore(@NotNull LexerPosition position) { - index = position.getState(); - } - - @Override - public @NotNull CharSequence getBufferSequence() { - return buffer; - } - - @Override - public int getBufferEnd() { - return endOffset; - } -} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParserDefinition.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParserDefinition.java index 91ce0c345..5d8d283f0 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParserDefinition.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParserDefinition.java @@ -10,7 +10,6 @@ ******************************************************************************/ package com.redhat.devtools.intellij.qute.lang; -import com.intellij.extapi.psi.ASTWrapperPsiElement; import com.intellij.lang.ASTNode; import com.intellij.lang.ParserDefinition; import com.intellij.lang.PsiParser; @@ -21,9 +20,18 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.TokenSet; +import com.redhat.devtools.intellij.qute.lang.psi.QuteLexer; +import com.redhat.devtools.intellij.qute.lang.psi.QuteParser; +import com.redhat.devtools.intellij.qute.lang.psi.QutePsiFile; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QutePsiElementFactory; import org.jetbrains.annotations.NotNull; +/** + * Qute parser definition. + */ public class QuteParserDefinition implements ParserDefinition { + @Override public @NotNull Lexer createLexer(Project project) { return new QuteLexer(); @@ -41,17 +49,17 @@ public IFileElementType getFileNodeType() { @Override public @NotNull TokenSet getCommentTokens() { - return TokenSet.create(QuteElementTypes.QUTE_CONTENT, QuteElementTypes.QUTE_COMMENT); + return TokenSet.create(QuteElementTypes.QUTE_COMMENT); } @Override public @NotNull TokenSet getStringLiteralElements() { - return TokenSet.EMPTY; + return TokenSet.create(QuteElementTypes.QUTE_STRING); } @Override public @NotNull PsiElement createElement(ASTNode node) { - return new ASTWrapperPsiElement(node); + return QutePsiElementFactory.createPsiElement(node); } @Override diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFileIndentOptionsProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFileIndentOptionsProvider.java new file mode 100644 index 000000000..5e73abf09 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFileIndentOptionsProvider.java @@ -0,0 +1,34 @@ +package com.redhat.devtools.intellij.qute.lang.format; + +import com.intellij.lang.Language; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.psi.codeStyle.FileIndentOptionsProvider; +import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider; +import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils; +import com.redhat.devtools.intellij.qute.lang.QuteFileType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class QuteFileIndentOptionsProvider extends FileIndentOptionsProvider { + + @Nullable + @Override + public CommonCodeStyleSettings.@Nullable IndentOptions getIndentOptions(@NotNull CodeStyleSettings settings, @NotNull PsiFile file) { + if (file.getFileType().equals(QuteFileType.QUTE)) { + VirtualFile virtualFile = file.getVirtualFile(); + Project project = LSPIJUtils.getProject(virtualFile).getProject(); + FileViewProvider provider = PsiManagerEx.getInstanceEx(project).findViewProvider(virtualFile); + if (provider instanceof TemplateLanguageFileViewProvider) { + Language language = ((TemplateLanguageFileViewProvider)provider).getTemplateDataLanguage(); + return settings.getCommonSettings(language).getIndentOptions(); + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFormattingModelBuilder.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFormattingModelBuilder.java new file mode 100644 index 000000000..5f86900c2 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFormattingModelBuilder.java @@ -0,0 +1,354 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.redhat.devtools.intellij.qute.lang.format; + +import com.intellij.formatting.*; +import com.intellij.formatting.templateLanguages.*; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiErrorElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.formatter.DocumentBasedFormattingModel; +import com.intellij.psi.formatter.FormattingDocumentModelImpl; +import com.intellij.psi.formatter.common.AbstractBlock; +import com.intellij.psi.formatter.xml.HtmlPolicy; +import com.intellij.psi.formatter.xml.SyntheticBlock; +import com.intellij.psi.templateLanguages.SimpleTemplateLanguageFormattingModelBuilder; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.xml.XmlTag; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static com.intellij.psi.formatter.WrappingUtil.getWrapType; + +/** + * Template aware formatter which provides formatting for Handlebars/Mustache syntax and delegates formatting + * for the templated language to that languages formatter + */ +public class QuteFormattingModelBuilder extends TemplateLanguageFormattingModelBuilder { + + + @Override + public TemplateLanguageBlock createTemplateLanguageBlock(@NotNull ASTNode node, + @Nullable Wrap wrap, + @Nullable Alignment alignment, + @Nullable List foreignChildren, + @NotNull CodeStyleSettings codeStyleSettings) { + final FormattingDocumentModelImpl documentModel = FormattingDocumentModelImpl.createOn(node.getPsi().getContainingFile()); + HtmlPolicy policy = new HtmlPolicy(codeStyleSettings, documentModel); + /* XXX return HbTokenTypes.TAGS.contains(node.getElementType()) ? + new HandlebarsTagBlock(node, wrap, alignment, this, codeStyleSettings, foreignChildren, policy) : + new HandlebarsBlock(node, wrap, alignment, this, codeStyleSettings, foreignChildren, policy);*/ + return new HandlebarsBlock(node, wrap, alignment, this, codeStyleSettings, foreignChildren, policy); + } + + /** + * We have to override {@link TemplateLanguageFormattingModelBuilder#createModel} + * since after we delegate to some templated languages, those languages (xml/html for sure, potentially others) + * delegate right back to us to format the HbTokenTypes.OUTER_ELEMENT_TYPE token we tell them to ignore, + * causing a stack-overflowing loop of polite format-delegation. + */ + @Override + public @NotNull FormattingModel createModel(@NotNull FormattingContext formattingContext) { + /*if (!HbConfig.isFormattingEnabled()) { + // formatting is disabled, return the no-op formatter (note that this still delegates formatting + // to the templated language, which lets the users manage that separately) + return new SimpleTemplateLanguageFormattingModelBuilder().createModel(formattingContext); + } +*/ + final PsiFile file = formattingContext.getContainingFile(); + Block rootBlock; + + ASTNode node = formattingContext.getNode(); + + if (node.getElementType() == QuteElementTypes.QUTE_OUTER_ELEMENT_TYPE) { + // If we're looking at a HbTokenTypes.OUTER_ELEMENT_TYPE element, then we've been invoked by our templated + // language. Make a dummy block to allow that formatter to continue + return new SimpleTemplateLanguageFormattingModelBuilder().createModel(formattingContext); + } + else { + rootBlock = getRootBlock(file, file.getViewProvider(), formattingContext.getCodeStyleSettings()); + } + return new DocumentBasedFormattingModel( + rootBlock, formattingContext.getProject(), formattingContext.getCodeStyleSettings(), file.getFileType(), file); + } + + /** + * Do format my model! + * + * @return false all the time to tell the {@link TemplateLanguageFormattingModelBuilder} + * to not-not format our model (i.e. yes please! Format away!) + */ + @Override + public boolean dontFormatMyModel() { + return false; + } + + private static class HandlebarsTagBlock extends HandlebarsBlock { + @NotNull + private final Alignment myChildAttributeAlignment; + + + HandlebarsTagBlock(@NotNull ASTNode node, + Wrap wrap, + Alignment alignment, + @NotNull TemplateLanguageBlockFactory blockFactory, + @NotNull CodeStyleSettings settings, + @Nullable List foreignChildren, + HtmlPolicy htmlPolicy) { + super(node, wrap, alignment, blockFactory, settings, foreignChildren, htmlPolicy); + + myChildAttributeAlignment = Alignment.createAlignment(); + } + + @NotNull + @Override + public ChildAttributes getChildAttributes(int newChildIndex) { + if (newChildIndex > 0) { + List blocks = getSubBlocks(); + if (blocks.size() > newChildIndex - 1) { + Block prevBlock = blocks.get(newChildIndex - 1); + if (prevBlock instanceof AbstractBlock) { + ASTNode node = ((AbstractBlock)prevBlock).getNode(); + /* XXX if (isAttribute(node) || + node.getElementType() == HbTokenTypes.MUSTACHE_NAME) { + return new ChildAttributes(null, prevBlock.getAlignment()); + }*/ + } + } + } + + return super.getChildAttributes(newChildIndex); + } + + @Override + protected Alignment createChildAlignment(ASTNode child) { + if (isAttribute(child)) { + return myChildAttributeAlignment; + } + return super.createChildAlignment(child); + } + + @Override + protected Wrap createChildWrap(ASTNode child) { + if (isAttribute(child)) { + return Wrap.createWrap(getWrapType(myHtmlPolicy.getAttributesWrap()), false); + } + return null; + } + } + + private static boolean isAttribute(ASTNode child) { + IElementType type = child.getElementType(); + return false; // XXX type == HbTokenTypes.PARAM || type == HbTokenTypes.HASH; + } + + private static class HandlebarsBlock extends TemplateLanguageBlock { + + @NotNull + protected final HtmlPolicy myHtmlPolicy; + + + HandlebarsBlock(@NotNull ASTNode node, + Wrap wrap, + Alignment alignment, + @NotNull TemplateLanguageBlockFactory blockFactory, + @NotNull CodeStyleSettings settings, + @Nullable List foreignChildren, + @NotNull HtmlPolicy htmlPolicy) { + super(node, wrap, alignment, blockFactory, settings, foreignChildren); + myHtmlPolicy = htmlPolicy; + } + + /** + * We indented the code in the following manner, playing nice with the formatting from the language + * we're templating: + *
+     *   * Block expressions:
+     *      {{#foo}}
+     *          INDENTED_CONTENT
+     *      {{/foo}}
+     *   * Inverse block expressions:
+     *      {{^bar}}
+     *          INDENTED_CONTENT
+     *      {{/bar}}
+     *   * Conditional expressions using the "else" syntax:
+     *      {{#if test}}
+     *          INDENTED_CONTENT
+     *      {{else}}
+     *          INDENTED_CONTENT
+     *      {{/if}}
+     *   * Conditional expressions using the "^" syntax:
+     *      {{#if test}}
+     *          INDENTED_CONTENT
+     *      {{^}}
+     *          INDENTED_CONTENT
+     *      {{/if}}
+     * 
+ *

+ * This naturally maps to any "statements" expression in the grammar which is not a child of the + * root "program" element. See {@link com.dmarcotte.handlebars.parsing.HbParsing#parseProgram} and + * {@link com.dmarcotte.handlebars.parsing.HbParsing#parseStatement(com.intellij.lang.PsiBuilder)} for the + * relevant parts of the parser. + *

+ * To understand the approach in this method, consider the following: + *

+     * {{#foo}}
+     * BEGIN_STATEMENTS
+     * TEMPLATE_STUFF
+     * END_STATEMENTS
+     * {{/foo}}
+     * 
+ *

+ * then formatting looks easy. Simply apply an indent (represented here by "[hb_indent]") to the STATEMENTS and call it a day: + *

+     * {{#foo}}
+     * [hb_indent]BEGIN_STATEMENTS
+     * [hb_indent]TEMPLATE_STUFF
+     * [hb_indent]END_STATEMENTS
+     * {{/foo}}
+     * 
+ *

+ * However, if we're contained in templated language block, it's going to provide some indents of its own + * (call them "[tl_indent]") which quickly leads to undesirable double-indenting: + *

+ *

+     * <div>
+     * [tl_indent]{{#foo}}
+     *            [hb_indent]BEGIN_STATEMENTS
+     *            [tl_indent][hb_indent]TEMPLATE_STUFF
+     *            [hb_indent]END_STATEMENTS
+     * [tl_indent]{{/foo}}
+     * </div>
+     * 
+ * So to behave correctly in both situations, we indent STATEMENTS from the "outside" anytime we're not wrapped + * in a templated language block, and we indent STATEMENTS from the "inside" (i.e. apply an indent to each non-template + * language STATEMENT inside the STATEMENTS) to interleave nicely with templated-language provided indents. + */ + @Override + public Indent getIndent() { + // ignore whitespace + if (myNode.getText().trim().length() == 0) { + return Indent.getNoneIndent(); + } + + if (isAttribute(myNode)) { + return null; + } + + if (false) { // XXX HbPsiUtil.isNonRootStatementsElement(myNode.getPsi())) { + // we're computing the indent for a non-root STATEMENTS: + // if it's not contained in a foreign block, indent! + DataLanguageBlockWrapper foreignBlockParent = getForeignBlockParent(false); + if (foreignBlockParent == null) { + return Indent.getNormalIndent(); + } + + // otherwise, only indent if our foreign parent isn't indenting us + if (foreignBlockParent.getNode() instanceof XmlTag) { + if (!myHtmlPolicy.indentChildrenOf((XmlTag)foreignBlockParent.getNode())) { + // no indent from xml parent, add our own + return Indent.getNormalIndent(); + } + } + + return Indent.getNoneIndent(); + } + + + if (myNode.getTreeParent() != null + && false) { // XXX HbPsiUtil.isNonRootStatementsElement(myNode.getTreeParent().getPsi())) { + // we're computing the indent for a direct descendant of a non-root STATEMENTS: + // if its Block parent (i.e. not HB AST Tree parent) is a Handlebars block + // which has NOT been indented, then have the element provide the indent itself + if (getParent() instanceof HandlebarsBlock + && ((HandlebarsBlock)getParent()).getIndent() == Indent.getNoneIndent()) { + return Indent.getNormalIndent(); + } + } + + // any element that is the direct descendant of a foreign block gets an indent + // (unless that foreign element has been configured to not indent its children) + DataLanguageBlockWrapper foreignParent = getForeignBlockParent(true); + if (foreignParent != null) { + if (foreignParent.getNode() instanceof XmlTag + && !myHtmlPolicy.indentChildrenOf((XmlTag)foreignParent.getNode())) { + return Indent.getNoneIndent(); + } + return Indent.getNormalIndent(); + } + + return Indent.getNoneIndent(); + } + + @Override + protected IElementType getTemplateTextElementType() { + // we ignore CONTENT tokens since they get formatted by the templated language + return QuteElementTypes.QUTE_TEXT; //HbTokenTypes.CONTENT; + } + + @Override + public boolean isRequiredRange(TextRange range) { + // seems our approach doesn't require us to insert any custom DataLanguageBlockFragmentWrapper blocks + return false; + } + + /** + *

+ * This method handles indent and alignment on Enter. + */ + @NotNull + @Override + public ChildAttributes getChildAttributes(int newChildIndex) { + /* + * We indent if we're in a BLOCK_WRAPPER (note that this works nicely since Enter can only be invoked + * INSIDE a block (i.e. after the open block 'stache). + * + * Also indent if we are wrapped in a block created by the templated language + */ + /* XXX if (myNode.getElementType() == HbTokenTypes.BLOCK_WRAPPER + || (getParent() instanceof DataLanguageBlockWrapper + // hack alert: the following check opportunistically fixes com.dmarcotte.handlebars.format.HbFormatOnEnterTest#testSimpleBlockInDiv8 + // and com.dmarcotte.handlebars.format.HbFormatOnEnterTest#testSimpleBlockInDiv8 + // but isn't really based on solid logic (why do these checks work?), so when there's inevitably a + // format-on-enter bug, this is the first bit of code to be suspicious of + && + (myNode.getElementType() != HbTokenTypes.STATEMENTS + || newChildIndex != 0 + || myNode.getTreeNext() instanceof PsiErrorElement))) { + return new ChildAttributes(Indent.getNormalIndent(), null); + }*/ + + return new ChildAttributes(Indent.getNoneIndent(), null); + } + + + /** + * Returns this block's first "real" foreign block parent if it exists, and null otherwise. (By "real" here, we mean that this method + * skips SyntheticBlock blocks inserted by the template formatter) + * + * @param immediate Pass true to only check for an immediate foreign parent, false to look up the hierarchy. + */ + private DataLanguageBlockWrapper getForeignBlockParent(boolean immediate) { + DataLanguageBlockWrapper foreignBlockParent = null; + BlockWithParent parent = getParent(); + + while (parent != null) { + if (parent instanceof DataLanguageBlockWrapper && !(((DataLanguageBlockWrapper)parent).getOriginal() instanceof SyntheticBlock)) { + foreignBlockParent = (DataLanguageBlockWrapper)parent; + break; + } + else if (immediate && parent instanceof HandlebarsBlock) { + break; + } + parent = parent.getParent(); + } + + return foreignBlockParent; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighter.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighter.java new file mode 100644 index 000000000..48de29540 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighter.java @@ -0,0 +1,27 @@ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.lang.Language; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.ex.util.LayerDescriptor; +import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.redhat.devtools.intellij.qute.lang.QuteFileViewProvider; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class QuteEditorHighlighter extends LayeredLexerEditorHighlighter { + + public QuteEditorHighlighter(@Nullable Project project, @Nullable VirtualFile virtualFile, @NotNull EditorColorsScheme scheme) { + super(new QuteSyntaxHighlighter(), scheme); + if (virtualFile != null) { + Language templateLanguage = QuteFileViewProvider.getTemplateLanguage(virtualFile); + this.registerLayer(QuteElementTypes.QUTE_TEXT, + new LayerDescriptor( + SyntaxHighlighterFactory.getSyntaxHighlighter(templateLanguage, project, virtualFile),"")); + } + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighterProvider.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighterProvider.java new file mode 100644 index 000000000..67419efd9 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighterProvider.java @@ -0,0 +1,17 @@ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.fileTypes.EditorHighlighterProvider; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class QuteEditorHighlighterProvider implements EditorHighlighterProvider { + @Override + public EditorHighlighter getEditorHighlighter(@Nullable Project project, @NotNull FileType fileType, @Nullable VirtualFile virtualFile, @NotNull EditorColorsScheme colors) { + return new QuteEditorHighlighter(project, virtualFile, colors); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighter.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighter.java new file mode 100644 index 000000000..71d2a3c7e --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighter.java @@ -0,0 +1,62 @@ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.XmlHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.containers.MultiMap; +import com.redhat.devtools.intellij.qute.lang.psi.QuteLexer; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType; +import org.jetbrains.annotations.NotNull; + +public class QuteSyntaxHighlighter extends SyntaxHighlighterBase { + + private static final TextAttributesKey COMMENT = TextAttributesKey.createTextAttributesKey("COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); + + private static final TextAttributesKey TAG_BRACE = TextAttributesKey.createTextAttributesKey("EXPRESSION", XmlHighlighterColors.XML_TAG); + + private static final TextAttributesKey SECTION_TAG_NAME = TextAttributesKey.createTextAttributesKey("SECTION_TAG_NAME", XmlHighlighterColors.XML_TAG_NAME); + + private static final TextAttributesKey STRING = TextAttributesKey.createTextAttributesKey("STRING", XmlHighlighterColors.XML_ATTRIBUTE_VALUE); + + private static final MultiMap ourMap = MultiMap.create(); + + static { + // Qute Comments + ourMap.putValue(QuteTokenType.QUTE_COMMENT_START, COMMENT); + ourMap.putValue(QuteTokenType.QUTE_COMMENT_END, COMMENT); + ourMap.putValue(QuteElementTypes.QUTE_COMMENT, COMMENT); + + // Qute expression + ourMap.putValue(QuteTokenType.QUTE_START_EXPRESSION, TAG_BRACE); + ourMap.putValue(QuteTokenType.QUTE_END_EXPRESSION, TAG_BRACE); + + // String + ourMap.putValue(QuteTokenType.QUTE_EXPRESSION_START_STRING, STRING); + ourMap.putValue(QuteTokenType.QUTE_EXPRESSION_STRING, STRING); + ourMap.putValue(QuteTokenType.QUTE_EXPRESSION_END_STRING, STRING); + + // Qute section + ourMap.putValue(QuteTokenType.QUTE_START_TAG_OPEN, TAG_BRACE); + ourMap.putValue(QuteTokenType.QUTE_START_TAG_CLOSE, TAG_BRACE); + ourMap.putValue(QuteTokenType.QUTE_START_TAG_SELF_CLOSE, TAG_BRACE); + ourMap.putValue(QuteTokenType.QUTE_END_TAG_OPEN, TAG_BRACE); + ourMap.putValue(QuteTokenType.QUTE_END_TAG_CLOSE, TAG_BRACE); + ourMap.putValue(QuteTokenType.QUTE_END_TAG_SELF_CLOSE, TAG_BRACE); + ourMap.putValue(QuteTokenType.QUTE_START_TAG, SECTION_TAG_NAME); + ourMap.putValue(QuteTokenType.QUTE_END_TAG, SECTION_TAG_NAME); + } + + @Override + public @NotNull Lexer getHighlightingLexer() { + return new QuteLexer(); + } + + @Override + public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) { + return ourMap.get(tokenType).toArray(TextAttributesKey.EMPTY_ARRAY); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighterFactory.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighterFactory.java new file mode 100644 index 000000000..3d61baa01 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighterFactory.java @@ -0,0 +1,13 @@ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.openapi.fileTypes.SingleLazyInstanceSyntaxHighlighterFactory; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import org.jetbrains.annotations.NotNull; + +public class QuteSyntaxHighlighterFactory extends SingleLazyInstanceSyntaxHighlighterFactory { + @Override + @NotNull + protected SyntaxHighlighter createHighlighter() { + return new QuteSyntaxHighlighter(); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/AbstractQuteSubLexer.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/AbstractQuteSubLexer.java new file mode 100644 index 000000000..5726f22ae --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/AbstractQuteSubLexer.java @@ -0,0 +1,54 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.tree.IElementType; + +public abstract class AbstractQuteSubLexer { + + protected IElementType myTokenType; + protected String myText; + + protected int myTokenStart; + protected int myTokenEnd; + + protected int myBufferEnd; + protected int myState; + + protected boolean myFailed; + + public int getState() { + locateToken(); + return myState; + } + + public IElementType getTokenType() { + locateToken(); + return myTokenType; + } + + public int getTokenStart() { + locateToken(); + return myTokenStart; + } + + public int getTokenEnd() { + locateToken(); + return myTokenEnd; + } + + public void advance() { + locateToken(); + myTokenType = null; + } + + + public void locateToken() { + if (myTokenType != null) return; + + myTokenStart = myTokenEnd; + if (myFailed) return; + + doLocateToken(); + } + + protected abstract void doLocateToken(); +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteElement.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteElement.java new file mode 100644 index 000000000..a6f71478f --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteElement.java @@ -0,0 +1,11 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.impl.source.tree.CompositePsiElement; +import com.intellij.psi.tree.IElementType; + +public class QuteElement extends CompositePsiElement { + + protected QuteElement(IElementType type) { + super(type); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexer.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexer.java new file mode 100644 index 000000000..1275d0962 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexer.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2022 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.lexer.LexerBase; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.psi.tree.IElementType; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType; +import com.redhat.qute.parser.expression.scanner.ExpressionScanner; +import com.redhat.qute.parser.template.scanner.ScannerState; +import com.redhat.qute.parser.template.scanner.TemplateScanner; +import com.redhat.qute.parser.template.scanner.TokenType; +import org.jetbrains.annotations.NotNull; + +/** + * Qute lexer based on the Qute LS scanner. + */ +public class QuteLexer extends LexerBase { + + private IElementType myTokenType; + private CharSequence myText; + + private int myTokenStart; + private int myTokenEnd; + + private int myBufferEnd; + private int myState; + + private boolean myFailed; + + private TemplateScanner scanner; + + private AbstractQuteSubLexer currentSubLexer; + private int startExpressionOffset = -1; + + private int startTagOpenOffset; + + @Override + public void start(@NotNull CharSequence buffer, int startOffset, int endOffset, int initialState) { + myText = buffer; + myTokenStart = myTokenEnd = startOffset; + myBufferEnd = endOffset; + startTagOpenOffset = -1; + startExpressionOffset = -1; + currentSubLexer = null; + scanner = (TemplateScanner) TemplateScanner.createScanner(buffer.subSequence(0, endOffset).toString(), startOffset); + myTokenType = null; + } + + @Override + public int getState() { + locateToken(); + return myState; + } + + @Override + public IElementType getTokenType() { + locateToken(); + return myTokenType; + } + + @Override + public int getTokenStart() { + locateToken(); + return myTokenStart; + } + + @Override + public int getTokenEnd() { + locateToken(); + return myTokenEnd; + } + + @Override + public void advance() { + locateToken(); + myTokenType = null; + } + + @NotNull + @Override + public CharSequence getBufferSequence() { + return myText; + } + + @Override + public int getBufferEnd() { + return myBufferEnd; + } + + protected void locateToken() { + if (myTokenType != null) return; + + myTokenStart = myTokenEnd; + if (myFailed) return; + + try { + + if (startExpressionOffset != -1 && currentSubLexer == null) { + currentSubLexer = new QuteLexerForExpression(myText.toString(), scanner, startExpressionOffset); + } else if (startTagOpenOffset != -1 && currentSubLexer == null) { + currentSubLexer = new QuteLexerForStartTag(myText.toString(), scanner, startTagOpenOffset); + } + boolean continueToScanTemplate = currentSubLexer == null; + if (currentSubLexer != null) { + myTokenType = currentSubLexer.getTokenType(); + if (myTokenType == null) { + continueToScanTemplate = true; + startExpressionOffset = -1; + startTagOpenOffset = -1; + currentSubLexer = null; + } else { + myState = currentSubLexer.getState(); + myTokenEnd = currentSubLexer.getTokenEnd(); + currentSubLexer.advance(); + } + } + + if (continueToScanTemplate) { + TokenType tokenType = scanner.scan(); + while (tokenType != TokenType.EOS) { + IElementType elementType = getTokenType(tokenType); + if (elementType != null) { + myState = getStateAsInt(scanner.getScannerState()); + myTokenType = elementType; + myTokenEnd = scanner.getTokenEnd(); + if (myTokenType == QuteTokenType.QUTE_START_EXPRESSION) { + startExpressionOffset = scanner.getTokenEnd(); + } else if (myTokenType == QuteTokenType.QUTE_START_TAG) { + startTagOpenOffset = scanner.getTokenEnd(); + } + break; + } + tokenType = scanner.scan(); + } + } + } catch (ProcessCanceledException e) { + throw e; + } catch (Throwable e) { + myFailed = true; + myTokenType = com.intellij.psi.TokenType.BAD_CHARACTER; + myTokenEnd = myBufferEnd; + //LOG.warn(myFlex.getClass().getName(), e); + } + } + + static IElementType getTokenType(TokenType tokenType) { + switch (tokenType) { + case StartComment: + return QuteTokenType.QUTE_COMMENT_START; + case Comment: + return QuteElementTypes.QUTE_COMMENT; + case EndComment: + return QuteTokenType.QUTE_COMMENT_END; + case StartParameterDeclaration: + return QuteTokenType.QUTE_START_PARAMETER_DECLARATION; + case EndParameterDeclaration: + return QuteTokenType.QUTE_END_PARAMETER_DECLARATION; + case StartExpression: + return QuteTokenType.QUTE_START_EXPRESSION; + case EndExpression: + return QuteTokenType.QUTE_END_EXPRESSION; + case StartTagOpen: + return QuteTokenType.QUTE_START_TAG_OPEN; + case StartTagClose: + return QuteTokenType.QUTE_START_TAG_CLOSE; + case StartTagSelfClose: + return QuteTokenType.QUTE_START_TAG_SELF_CLOSE; + case StartTag: + return QuteTokenType.QUTE_START_TAG; + case EndTag: + return QuteTokenType.QUTE_END_TAG; + case EndTagSelfClose: + return QuteTokenType.QUTE_END_TAG_SELF_CLOSE; + case EndTagOpen: + return QuteTokenType.QUTE_END_TAG_OPEN; + case EndTagClose: + return QuteTokenType.QUTE_END_TAG_CLOSE; + case Whitespace: + return QuteTokenType.QUTE_WHITESPACE; + } + return QuteElementTypes.QUTE_TEXT; + } + + static int getStateAsInt(ScannerState state) { + return state.ordinal(); + } + + @Override + public String toString() { + return "QuteLexer for " + scanner.getClass().getName(); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpression.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpression.java new file mode 100644 index 000000000..c82cfe24b --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpression.java @@ -0,0 +1,101 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.tree.IElementType; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType; +import com.redhat.qute.parser.expression.scanner.ExpressionScanner; +import com.redhat.qute.parser.expression.scanner.ScannerState; +import com.redhat.qute.parser.expression.scanner.TokenType; +import com.redhat.qute.parser.template.scanner.TemplateScanner; + +public class QuteLexerForExpression extends AbstractQuteSubLexer { + + private final ExpressionScanner scanner; + private int myLastState; + private IElementType myLastTokenType; + private int myLastTokenEnd; + private int endExpressionOffset; + + QuteLexerForExpression(String text, TemplateScanner templateScanner, int startExpressionOffset) { + boolean isClosed = false; + com.redhat.qute.parser.template.scanner.TokenType tokenType = templateScanner.scan(); + while (tokenType != com.redhat.qute.parser.template.scanner.TokenType.EOS) { + if (tokenType == com.redhat.qute.parser.template.scanner.TokenType.EndExpression) { + isClosed = true; + break; + } + tokenType = templateScanner.scan(); + } + if (isClosed) { + myLastState = QuteLexer.getStateAsInt(templateScanner.getScannerState()); + myLastTokenType = QuteLexer.getTokenType(templateScanner.getTokenType()); + myLastTokenEnd = templateScanner.getTokenEnd(); + } + endExpressionOffset = templateScanner.getTokenOffset(); + scanner = ExpressionScanner.createScanner(text, true, startExpressionOffset, endExpressionOffset); + } + + @Override + protected void doLocateToken() { + TokenType tokenType = scanner.scan(); + while (tokenType != TokenType.EOS) { + IElementType elementType = getTokenType(tokenType); + if (elementType != null) { + myState = getStateAsInt(scanner.getScannerState()); + myTokenType = elementType; + myTokenEnd = scanner.getTokenEnd(); + break; + } + tokenType = scanner.scan(); + } + if (tokenType == com.redhat.qute.parser.expression.scanner.TokenType.EOS) { + if (myLastTokenType != null) { + myState = myLastState; + myTokenType = myLastTokenType; + myTokenEnd = myLastTokenEnd; + myLastTokenType = null; + } else { + myTokenType = null; + } + endExpressionOffset = -1; + } + } + + private static IElementType getTokenType(TokenType tokenType) { + switch (tokenType) { + case NamespacePart: + return QuteTokenType.QUTE_EXPRESSION_NAMESPACE_PART; + case ObjectPart: + return QuteTokenType.QUTE_EXPRESSION_OBJECT_PART; + case PropertyPart: + return QuteTokenType.QUTE_EXPRESSION_PROPERTY_PART; + case MethodPart: + return QuteTokenType.QUTE_EXPRESSION_METHOD_PART; + case OpenBracket: + return QuteTokenType.QUTE_EXPRESSION_OPEN_BRACKET; + case CloseBracket: + return QuteTokenType.QUTE_EXPRESSION_CLOSE_BRACKET; + case InfixMethodPart: + return QuteTokenType.QUTE_EXPRESSION_INFIX_METHOD_PART; + case InfixParameter: + return QuteTokenType.QUTE_EXPRESSION_INFIX_PARAMETER; + case Dot: + return QuteTokenType.QUTE_EXPRESSION_DOT; + case ColonSpace: + return QuteTokenType.QUTE_EXPRESSION_COLON_SPACE; + case StartString: + return QuteTokenType.QUTE_EXPRESSION_START_STRING; + case String: + return QuteTokenType.QUTE_EXPRESSION_STRING; + case EndString: + return QuteTokenType.QUTE_EXPRESSION_END_STRING; + case Whitespace: + return QuteTokenType.QUTE_EXPRESSION_WHITESPACE; + } + return null; + } + + private static int getStateAsInt(ScannerState state) { + return 10 + state.ordinal(); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForStartTag.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForStartTag.java new file mode 100644 index 000000000..9041a66b9 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForStartTag.java @@ -0,0 +1,81 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.tree.IElementType; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType; +import com.redhat.qute.parser.parameter.scanner.ParameterScanner; +import com.redhat.qute.parser.parameter.scanner.ScannerState; +import com.redhat.qute.parser.template.scanner.TemplateScanner; +import com.redhat.qute.parser.template.scanner.TokenType; + +public class QuteLexerForStartTag extends AbstractQuteSubLexer { + + private final int endTagOpenOffset; + private final ParameterScanner scanner; + + private int myLastState; + private IElementType myLastTokenType; + private int myLastTokenEnd; + + public QuteLexerForStartTag(String text, TemplateScanner templateScanner, int startTagOpenOffset) { + boolean isClosed = false; + com.redhat.qute.parser.template.scanner.TokenType tokenType = templateScanner.scan(); + while (tokenType != com.redhat.qute.parser.template.scanner.TokenType.EOS) { + if (tokenType == TokenType.StartTagClose || tokenType == TokenType.StartTagSelfClose) { + isClosed = true; + break; + } + tokenType = templateScanner.scan(); + } + if (isClosed) { + myLastState = QuteLexer.getStateAsInt(templateScanner.getScannerState()); + myLastTokenType = QuteLexer.getTokenType(templateScanner.getTokenType()); + myLastTokenEnd = templateScanner.getTokenEnd(); + } + endTagOpenOffset = templateScanner.getTokenOffset(); + scanner = ParameterScanner.createScanner(text, startTagOpenOffset, endTagOpenOffset, false, true); + } + + + @Override + protected void doLocateToken() { + com.redhat.qute.parser.parameter.scanner.TokenType tokenType = scanner.scan(); + while (tokenType != com.redhat.qute.parser.parameter.scanner.TokenType.EOS) { + IElementType elementType = getTokenType(tokenType); + if (elementType != null) { + myState = getStateAsInt(scanner.getScannerState()); + myTokenType = elementType; + myTokenEnd = scanner.getTokenEnd(); + break; + } + tokenType = scanner.scan(); + } + if (tokenType == com.redhat.qute.parser.parameter.scanner.TokenType.EOS) { + if (myLastTokenType != null) { + myState = myLastState; + myTokenType = myLastTokenType; + myTokenEnd = myLastTokenEnd; + myLastTokenType = null; + } else { + myTokenType = null; + } + } + } + + private static IElementType getTokenType(com.redhat.qute.parser.parameter.scanner.TokenType tokenType) { + switch (tokenType) { + case Assign: + return QuteTokenType.QUTE_EXPRESSION_NAMESPACE_PART; + case ParameterName: + return QuteTokenType.QUTE_EXPRESSION_OBJECT_PART; + case ParameterValue: + return QuteTokenType.QUTE_EXPRESSION_METHOD_PART; + case Whitespace: + return QuteTokenType.QUTE_EXPRESSION_WHITESPACE; + } + return null; + } + + private static int getStateAsInt(ScannerState state) { + return 20 + state.ordinal(); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParser.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java similarity index 57% rename from src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParser.java rename to src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java index 7fde3252f..178dd348f 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParser.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java @@ -8,32 +8,33 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.qute.lang; +package com.redhat.devtools.intellij.qute.lang.psi; -import com.intellij.lang.ASTFactory; -import com.intellij.lang.ASTNode; -import com.intellij.lang.PsiBuilder; -import com.intellij.lang.PsiParser; -import com.intellij.psi.impl.source.tree.LazyParseableElement; +import com.intellij.lang.*; import com.intellij.psi.tree.IElementType; -import com.intellij.psi.tree.ILazyParseableElementType; -import com.redhat.qute.parser.template.Template; -import com.redhat.qute.parser.template.TemplateParser; +import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; /** - * Qute parser. Reqyired because some features are related to special + * Qute parser. Required because some features are related to special * PSI nodes thus we need to implement them. * Inspired by TemplateParser as there seem no way to reuse it. * * @see https://github.com/redhat-developer/quarkus-ls/blob/HEAD/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/TemplateParser.java */ -public class QuteParser implements PsiParser { +public class QuteParser implements PsiParser, LightPsiParser { + @Override public @NotNull ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) { - Template template = TemplateParser.parse(builder.getOriginalText().toString(), ""); - LazyParseableElement rootNode = ASTFactory.lazy((ILazyParseableElementType) root, null); - template.getChildren().forEach(child -> rootNode.rawAddChildrenWithoutNotifications(QuteASTNode.getNode(child))); - return rootNode; + this.parseLight(root, builder); + return builder.getTreeBuilt(); + } + + @Override + public void parseLight(IElementType root, PsiBuilder builder) { + builder.enforceCommentTokens(TokenSet.EMPTY); + final PsiBuilder.Marker file = builder.mark(); + new QuteParsing(builder).parseTemplate(); + file.done(root); } } diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParsing.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParsing.java new file mode 100644 index 000000000..b731e29e3 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParsing.java @@ -0,0 +1,170 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import static com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes.*; +import static com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType.*; + +import com.intellij.lang.PsiBuilder; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.Nullable; + +public class QuteParsing { + + private final PsiBuilder myBuilder; + + public QuteParsing(PsiBuilder builder) { + myBuilder = builder; + } + + public void parseTemplate() { + final PsiBuilder.Marker template = mark(); + + while (isCommentToken(token())) { + parseComment(); + } + + while (!eof()) { + final IElementType tt = token(); + if (tt == QUTE_START_EXPRESSION) { + parseExpression(); + } else if (tt == QUTE_START_TAG_OPEN) { + parseStartSection(); + } else if (isCommentToken(tt)) { + parseComment(); + } else if (tt == QUTE_TEXT) { + parseText(); + } else { + advance(); + } + } + + template.done(QUTE_CONTENT); + } + + private void parseStartSection() { + final PsiBuilder.Marker startSection = mark(); + advance(); + + while (true) { + final IElementType tt = token(); + if (tt == null) { + break; + } else if (tt == QUTE_EXPRESSION) { + // Comment content : foo in {! foo !} + advance(); + continue; + } else if (tt == QUTE_END_EXPRESSION) { + // End expression: } + advance(); + } else if (tt == QUTE_EXPRESSION_OBJECT_PART) { + final PsiBuilder.Marker objectPart = mark(); + advance(); + objectPart.done(QUTE_EXPRESSION_OBJECT_PART); + continue; + }else if (tt == QUTE_EXPRESSION_PROPERTY_PART) { + final PsiBuilder.Marker propertyPart = mark(); + advance(); + propertyPart.done(QUTE_EXPRESSION_PROPERTY_PART); + continue; + } else { + //final PsiBuilder.Marker error = mark(); + advance(); + // error.error("BAD comments!"); + continue; + } + break; + } + startSection.done(QUTE_START_SECTION); + } + + private void parseText() { + final PsiBuilder.Marker text = mark(); + advance(); + text.done(QUTE_TEXT); + } + + private void parseExpression() { + final PsiBuilder.Marker expression = mark(); + advance(); + + while (true) { + final IElementType tt = token(); + if (tt == null) { + break; + } else if (tt == QUTE_EXPRESSION) { + // Comment content : foo in {! foo !} + advance(); + continue; + } else if (tt == QUTE_END_EXPRESSION) { + // End expression: } + advance(); + } else if (tt == QUTE_EXPRESSION_OBJECT_PART) { + final PsiBuilder.Marker objectPart = mark(); + advance(); + objectPart.done(QUTE_EXPRESSION_OBJECT_PART); + continue; + }else if (tt == QUTE_EXPRESSION_PROPERTY_PART) { + final PsiBuilder.Marker propertyPart = mark(); + advance(); + propertyPart.done(QUTE_EXPRESSION_PROPERTY_PART); + continue; + } else { + //final PsiBuilder.Marker error = mark(); + advance(); + // error.error("BAD comments!"); + continue; + } + break; + } + expression.done(QUTE_EXPRESSION); + } + + + private void parseComment() { + final PsiBuilder.Marker comment = mark(); + advance(); + while (true) { + final IElementType tt = token(); + if (tt == null) { + // Case when comment is not closed. + // ex : {! foo + break; + } else if (tt == QUTE_COMMENT) { + // Comment content : foo in {! foo !} + advance(); + continue; + } else if (tt == QUTE_COMMENT_END) { + // End comment: !} + advance(); + } else { + //final PsiBuilder.Marker error = mark(); + advance(); + // error.error("BAD comments!"); + continue; + } + break; + } + comment.done(QUTE_COMMENT); + } + + protected boolean isCommentToken(final IElementType tt) { + // Start comment: {! + return tt == QUTE_COMMENT_START; + } + + protected final PsiBuilder.Marker mark() { + return myBuilder.mark(); + } + + @Nullable + protected final IElementType token() { + return myBuilder.getTokenType(); + } + + protected final boolean eof() { + return myBuilder.eof(); + } + + protected final void advance() { + myBuilder.advanceLexer(); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiExpression.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiExpression.java new file mode 100644 index 000000000..3ff84de03 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiExpression.java @@ -0,0 +1,4 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +public class QutePsiExpression { +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QutePsiFile.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiFile.java similarity index 82% rename from src/main/java/com/redhat/devtools/intellij/qute/lang/QutePsiFile.java rename to src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiFile.java index cc942b627..8f14193e3 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QutePsiFile.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiFile.java @@ -8,13 +8,18 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.qute.lang; +package com.redhat.devtools.intellij.qute.lang.psi; import com.intellij.extapi.psi.PsiFileBase; import com.intellij.openapi.fileTypes.FileType; import com.intellij.psi.FileViewProvider; +import com.redhat.devtools.intellij.qute.lang.QuteFileType; +import com.redhat.devtools.intellij.qute.lang.QuteLanguage; import org.jetbrains.annotations.NotNull; +/** + * Qute Psi file. + */ public class QutePsiFile extends PsiFileBase { public QutePsiFile(FileViewProvider viewProvider) { super(viewProvider, QuteLanguage.INSTANCE); diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiReference.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiReference.java new file mode 100644 index 000000000..9d65dd7e1 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiReference.java @@ -0,0 +1,59 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.NlsSafe; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class QutePsiReference implements PsiReference { + + private final ASTNode node; + + public QutePsiReference(ASTNode node) { + this.node = node; + } + + @Override + public @NotNull PsiElement getElement() { + return node.getPsi(); + } + + @Override + public @NotNull TextRange getRangeInElement() { + return new TextRange(0, node.getPsi().getTextRange().getLength()); + } + + @Override + public @Nullable PsiElement resolve() { + return node.getPsi(); + } + + @Override + public @NotNull @NlsSafe String getCanonicalText() { + return node.getPsi().getText(); + } + + @Override + public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException { + return null; + } + + @Override + public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException { + return null; + } + + @Override + public boolean isReferenceTo(@NotNull PsiElement element) { + return false; + } + + @Override + public boolean isSoft() { + return false; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteToken.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteToken.java new file mode 100644 index 000000000..60103921e --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteToken.java @@ -0,0 +1,32 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.HintedReferenceHost; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiReferenceService; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; + +public class QuteToken extends LeafPsiElement implements HintedReferenceHost { + + public QuteToken(@NotNull IElementType type, CharSequence text) { + super(type, text); + } + + @Override + public PsiReference @NotNull [] getReferences() { + return getReferences(PsiReferenceService.Hints.NO_HINTS); + } + + @Override + public PsiReference @NotNull [] getReferences(PsiReferenceService.@NotNull Hints hints) { + final IElementType elementType = getElementType(); + + return new PsiReference[] {new QutePsiReference(getNode())}; + } + + @Override + public boolean shouldAskParentForReferences(PsiReferenceService.@NotNull Hints hints) { + return false; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementType.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteElementType.java similarity index 88% rename from src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementType.java rename to src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteElementType.java index 753aa4377..e1718c05d 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementType.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteElementType.java @@ -8,9 +8,10 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.qute.lang; +package com.redhat.devtools.intellij.qute.lang.psi.tree; import com.intellij.psi.tree.IElementType; +import com.redhat.devtools.intellij.qute.lang.QuteLanguage; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementTypes.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteElementTypes.java similarity index 67% rename from src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementTypes.java rename to src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteElementTypes.java index 4107666c6..ceeb62ed5 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementTypes.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteElementTypes.java @@ -8,51 +8,46 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.qute.lang; +package com.redhat.devtools.intellij.qute.lang.psi.tree; import com.intellij.psi.templateLanguages.TemplateDataElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.OuterLanguageElementType; +import com.redhat.devtools.intellij.qute.lang.QuteLanguage; import com.redhat.qute.parser.template.Node; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class QuteElementTypes { + public static final IFileElementType QUTE_FILE = new IFileElementType("QUTE_FILE", QuteLanguage.INSTANCE); /* Non Qute content in Qute PSI file */ - public static final IElementType QUTE_CONTENT = new QuteElementType("#text"); + public static final IElementType QUTE_CONTENT = new QuteElementType("QUTE_CONTENT"); /* Qute comment */ - public static final IElementType QUTE_COMMENT = new QuteElementType("#comment"); + public static final IElementType QUTE_COMMENT = new QuteElementType("QUTE_COMMENT"); - public static final IElementType QUTE_TEXT = new QuteElementType("TEXT"); + public static final IElementType QUTE_EXPRESSION = new QuteElementType("QUTE_EXPRESSION"); - /* - Qute block in non Qute PSI file - */ - public static final IElementType QUTE_BLOCK = new OuterLanguageElementType("QUTE-BLOCK", QuteLanguage.INSTANCE); + public static final IElementType QUTE_START_SECTION = new QuteElementType("QUTE_START_SECTION"); + + public static final IElementType QUTE_STRING = new QuteElementType("QUTE_STRING"); + + public static final IElementType QUTE_TEXT = new OuterLanguageElementType("QUTE_TEXT", QuteLanguage.INSTANCE); + + public static final IElementType QUTE_OUTER_ELEMENT_TYPE = new OuterLanguageElementType("QUTE_OUTER_ELEMENT_TYPE", QuteLanguage.INSTANCE); /* This is the pseudo type that will be assigned to the Qute PSI file so that Qute blocks in non Qute PSI file is assigned the OuterLanguageElement so that it is not used by completion,... */ - public static final IElementType QUTE_FILE_DATA = new TemplateDataElementType("QUTE_FILE_DATA", QuteLanguage.INSTANCE, QUTE_CONTENT, QUTE_BLOCK); - private static final Map types = new ConcurrentHashMap<>(); - - static { - types.put("#text", QUTE_CONTENT); - types.put("#comment", QUTE_COMMENT); - } + public static final IElementType QUTE_FILE_DATA = new TemplateDataElementType("QUTE_FILE_DATA", QuteLanguage.INSTANCE, QUTE_TEXT, QUTE_OUTER_ELEMENT_TYPE); - public static IElementType fromNode(Node node) { - return types.computeIfAbsent(node.getNodeName(), s -> new QuteElementType(s)); - } } diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QutePsiElementFactory.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QutePsiElementFactory.java new file mode 100644 index 000000000..109dc5a87 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QutePsiElementFactory.java @@ -0,0 +1,11 @@ +package com.redhat.devtools.intellij.qute.lang.psi.tree; + +import com.intellij.extapi.psi.ASTWrapperPsiElement; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; + +public class QutePsiElementFactory { + public static PsiElement createPsiElement(ASTNode node) { + return new ASTWrapperPsiElement(node); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteTokenType.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteTokenType.java new file mode 100644 index 000000000..a06a17785 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteTokenType.java @@ -0,0 +1,46 @@ +package com.redhat.devtools.intellij.qute.lang.psi.tree; + +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.xml.IXmlLeafElementType; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementType; + +public interface QuteTokenType { + + IElementType QUTE_COMMENT_START = new QuteElementType("QUTE_COMMENT_START"); + + IElementType QUTE_COMMENT_END = new QuteElementType("QUTE_COMMENT_END"); + + IElementType QUTE_START_PARAMETER_DECLARATION = new QuteElementType("QUTE_START_PARAMETER_DECLARATION"); + + IElementType QUTE_END_PARAMETER_DECLARATION = new QuteElementType("QUTE_END_PARAMETER_DECLARATION"); + + IElementType QUTE_START_EXPRESSION = new QuteElementType("QUTE_START_EXPRESSION"); + + IElementType QUTE_END_EXPRESSION = new QuteElementType("QUTE_END_EXPRESSION"); + + IElementType QUTE_START_TAG = new QuteElementType("QUTE_START_TAG"); + IElementType QUTE_END_TAG = new QuteElementType("QUTE_END_TAG"); + IElementType QUTE_END_TAG_SELF_CLOSE = new QuteElementType("QUTE_END_TAG_SELF_CLOSE"); + IElementType QUTE_END_TAG_OPEN = new QuteElementType("QUTE_END_TAG_OPEN"); + IElementType QUTE_END_TAG_CLOSE = new QuteElementType("QUTE_END_TAG_CLOSE"); + IElementType QUTE_START_TAG_OPEN = new QuteElementType("QUTE_START_TAG_OPEN"); + IElementType QUTE_START_TAG_CLOSE = new QuteElementType("QUTE_START_TAG_CLOSE"); + IElementType QUTE_START_TAG_SELF_CLOSE = new QuteElementType("QUTE_START_TAG_SELF_CLOSE"); + IElementType QUTE_WHITESPACE = new QuteElementType("QUTE_WHITESPACE"); + + // Qute expression + IElementType QUTE_EXPRESSION_NAMESPACE_PART = new QuteElementType("QUTE_EXPRESSION_NAMESPACE_PART"); + IElementType QUTE_EXPRESSION_OBJECT_PART = new QuteElementType("QUTE_EXPRESSION_OBJECT_PART"); + IElementType QUTE_EXPRESSION_PROPERTY_PART = new QuteElementType("QUTE_EXPRESSION_PROPERTY_PART"); + IElementType QUTE_EXPRESSION_METHOD_PART = new QuteElementType("QUTE_EXPRESSION_METHOD_PART"); + IElementType QUTE_EXPRESSION_OPEN_BRACKET = new QuteElementType("QUTE_EXPRESSION_OPEN_BRACKET"); + IElementType QUTE_EXPRESSION_CLOSE_BRACKET = new QuteElementType("QUTE_EXPRESSION_CLOSE_BRACKET"); + IElementType QUTE_EXPRESSION_INFIX_METHOD_PART = new QuteElementType("QUTE_EXPRESSION_INFIX_METHOD_PART"); + IElementType QUTE_EXPRESSION_INFIX_PARAMETER = new QuteElementType("QUTE_EXPRESSION_INFIX_PARAMETER"); + IElementType QUTE_EXPRESSION_DOT = new QuteElementType("QUTE_EXPRESSION_DOT"); + IElementType QUTE_EXPRESSION_COLON_SPACE = new QuteElementType("QUTE_EXPRESSION_COLON_SPACE"); + IElementType QUTE_EXPRESSION_START_STRING = new QuteElementType("QUTE_EXPRESSION_START_STRING"); + IElementType QUTE_EXPRESSION_STRING = new QuteElementType("QUTE_EXPRESSION_STRING"); + IElementType QUTE_EXPRESSION_END_STRING = new QuteElementType("QUTE_EXPRESSION_END_STRING"); + IElementType QUTE_EXPRESSION_WHITESPACE = new QuteElementType("QUTE_EXPRESSION_WHITESPACE"); +} diff --git a/src/main/resources/META-INF/lsp4ij-qute.xml b/src/main/resources/META-INF/lsp4ij-qute.xml index 926de215b..8dd6166ac 100644 --- a/src/main/resources/META-INF/lsp4ij-qute.xml +++ b/src/main/resources/META-INF/lsp4ij-qute.xml @@ -42,6 +42,7 @@ implementationClass="com.redhat.devtools.intellij.lsp4ij.operations.diagnostics.LSPDiagnosticAnnotator"/> + diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index a4bf1ab23..6c25fe282 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -284,8 +284,13 @@ implementationClass="com.redhat.devtools.intellij.qute.lang.QuteFileType"/> + - + + +