From 201775a5dbd46c71995961d5a6a6730fcb25350a Mon Sep 17 00:00:00 2001 From: azerr Date: Tue, 13 Jun 2023 08:00:30 +0200 Subject: [PATCH] Improve completion --- .../lsp4ij/internal/SupportedFeatures.java | 24 +-- .../completion/LSPCompletionConfidence.java | 15 ++ .../intellij/qute/lang/QuteASTNode.java | 17 +- .../intellij/qute/lang/QuteFileType.java | 4 + .../qute/lang/QuteFileViewProvider.java | 9 +- .../lang/QuteFileViewProviderFactory.java | 3 + .../intellij/qute/lang/QuteLanguage.java | 5 + .../qute/lang/QuteLanguageSubstitutor.java | 14 +- .../intellij/qute/lang/QuteLexer.java | 98 ---------- .../qute/lang/QuteParserDefinition.java | 12 +- .../intellij/qute/lang/QuteToken.java | 42 +++++ .../highlighter/QuteSyntaxHighlighter.java | 33 ++++ .../QuteSyntaxHighlighterFactory.java | 13 ++ .../intellij/qute/lang/psi/MyASTVisitor.java | 12 ++ .../qute/lang/psi/PsiQuteExpression.java | 12 ++ .../intellij/qute/lang/psi/QuteLexer.java | 167 ++++++++++++++++++ .../qute/lang/{ => psi}/QuteParser.java | 30 ++-- .../intellij/qute/lang/psi/QuteParsing.java | 66 +++++++ .../qute/lang/{ => psi}/QutePsiFile.java | 7 +- .../lang/{ => psi/tree}/QuteElementType.java | 3 +- .../lang/{ => psi/tree}/QuteElementTypes.java | 5 +- .../lang/psi/tree/QutePsiElementFactory.java | 11 ++ .../qute/lang/psi/tree/QuteTokenType.java | 29 +++ src/main/resources/META-INF/lsp4ij-qute.xml | 1 + src/main/resources/META-INF/plugin.xml | 2 + 25 files changed, 496 insertions(+), 138 deletions(-) create mode 100644 src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionConfidence.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/QuteToken.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/MyASTVisitor.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/PsiQuteExpression.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexer.java rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi}/QuteParser.java (63%) create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParsing.java rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi}/QutePsiFile.java (82%) 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 (92%) 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/qute/lang/QuteASTNode.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTNode.java index 011336178..0ff6a9764 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTNode.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTNode.java @@ -11,10 +11,12 @@ 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.intellij.psi.templateLanguages.OuterLanguageElementImpl; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes; import com.redhat.qute.parser.template.ASTVisitor; import com.redhat.qute.parser.template.Node; +import com.redhat.qute.parser.template.NodeKind; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -29,19 +31,26 @@ public QuteASTNode(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(new QuteToken(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()))); + rawAddChildrenWithoutNotifications(new QuteToken(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); + if (child.getKind() == NodeKind.Text) { + return new OuterLanguageElementImpl(QuteElementTypes.fromNode(child), child.getOwnerTemplate().getText(child.getStart(), child.getEnd())); + } + if (child.getKind() == NodeKind.ExpressionPart) { + + } + return getChildren(child).isEmpty()?new QuteToken(QuteElementTypes.fromNode(child), child.getOwnerTemplate().getText(child.getStart(), child.getEnd())) + :new QuteASTNode(child); } private static List getChildren(Node node) { 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..111cfa8ef 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 @@ -20,12 +20,15 @@ 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; @@ -58,8 +61,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 +73,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..ce91c98c9 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 @@ -20,6 +20,9 @@ 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) { 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..96f1e38a4 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(); @@ -51,7 +59,7 @@ public IFileElementType getFileNodeType() { @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/QuteToken.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteToken.java new file mode 100644 index 000000000..bfa87c2e6 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteToken.java @@ -0,0 +1,42 @@ +package com.redhat.devtools.intellij.qute.lang; + +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import com.intellij.psi.impl.source.xml.TagNameReference; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.xml.XmlToken; +import com.intellij.psi.xml.XmlTokenType; +import org.jetbrains.annotations.NotNull; + +public class QuteToken extends LeafPsiElement { + + 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(); + + if (elementType == XmlTokenType.XML_DATA_CHARACTERS || + elementType == XmlTokenType.XML_CHAR_ENTITY_REF) { + return ReferenceProvidersRegistry.getReferencesFromProviders(this, hints); + } else if (elementType == XmlTokenType.XML_NAME && getParent() instanceof PsiErrorElement) { + final PsiElement element = getPrevSibling(); + + if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) { + return new PsiReference[] {TagNameReference.createTagNameReference(this, getNode(), false)}; + } + } + + return super.getReferences();*/ + return null; + } + +} 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..47a7814da --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighter.java @@ -0,0 +1,33 @@ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.lexer.Lexer; +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; +import com.intellij.psi.tree.IElementType; +import com.redhat.devtools.intellij.qute.lang.psi.QuteLexer; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public class QuteSyntaxHighlighter extends SyntaxHighlighterBase { + + private static final TextAttributesKey COMMENT = TextAttributesKey.createTextAttributesKey("COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); + + private static final Map ATTRIBUTES = new HashMap<>(); + + static { + ATTRIBUTES.put("comment", COMMENT); + } + + @Override + public @NotNull Lexer getHighlightingLexer() { + return new QuteLexer(); + } + + @Override + public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) { + return new TextAttributesKey[] {ATTRIBUTES.get(tokenType.getDebugName().toString())}; + } +} 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/MyASTVisitor.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/MyASTVisitor.java new file mode 100644 index 000000000..e146c6f7e --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/MyASTVisitor.java @@ -0,0 +1,12 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.redhat.qute.parser.template.ASTVisitor; +import com.redhat.qute.parser.template.sections.ForSection; + +public class MyASTVisitor extends ASTVisitor { + + @Override + public boolean visit(ForSection node) { + return super.visit(node); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/PsiQuteExpression.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/PsiQuteExpression.java new file mode 100644 index 000000000..8cc414c77 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/PsiQuteExpression.java @@ -0,0 +1,12 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.extapi.psi.ASTWrapperPsiElement; +import com.intellij.lang.ASTNode; +import org.jetbrains.annotations.NotNull; + +public class PsiQuteExpression extends ASTWrapperPsiElement { + + public PsiQuteExpression(@NotNull ASTNode node) { + super(node); + } +} 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..e3e067f37 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexer.java @@ -0,0 +1,167 @@ +/******************************************************************************* + * 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.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; + + @Override + public void start(@NotNull CharSequence buffer, int startOffset, int endOffset, int initialState) { + myText = buffer; + myTokenStart = myTokenEnd = startOffset; + myBufferEnd = endOffset; + //myFlex.reset(myText, startOffset, endOffset, initialState); + scanner = (TemplateScanner) TemplateScanner.createScanner(buffer.subSequence(startOffset,endOffset).toString(), 0); + 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 { + 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(); + } + } + 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); + } + } + + private static IElementType getTokenType(TokenType tokenType) { + switch(tokenType) { + case StartComment: + return QuteTokenType.QUTE_COMMENT_START; + 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 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; + case ParameterTag: + return QuteTokenType.QUTE_PARAMETER_TAG; + } + return QuteElementTypes.QUTE_CONTENT; + } + + private 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/QuteParser.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java similarity index 63% 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..f85d6129b 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,40 @@ * 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.lang.*; import com.intellij.psi.impl.source.tree.LazyParseableElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.ILazyParseableElementType; +import com.intellij.psi.tree.TokenSet; +import com.redhat.devtools.intellij.qute.lang.QuteASTNode; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementType; +import com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType; import com.redhat.qute.parser.template.Template; import com.redhat.qute.parser.template.TemplateParser; 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..161afa6d4 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParsing.java @@ -0,0 +1,66 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import static com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes.QUTE_COMMENT; +import static com.redhat.devtools.intellij.qute.lang.psi.tree.QuteElementTypes.QUTE_TEXT; +import static com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType.QUTE_COMMENT_END; +import static com.redhat.devtools.intellij.qute.lang.psi.tree.QuteTokenType.QUTE_COMMENT_START; +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 document = mark(); + + while (isCommentToken(token())) { + parseComment(); + } + } + + private void parseComment() { + final PsiBuilder.Marker comment = mark(); + advance(); + while (true) { + final IElementType tt = token(); + if (tt == QUTE_TEXT) { + advance(); + } else if (tt == QUTE_COMMENT_END) { + advance(); + } else { + final PsiBuilder.Marker error = mark(); + advance(); + error.error("BAD comments!"); + continue; + } + break; + } + comment.done(QUTE_COMMENT); + } + + protected boolean isCommentToken(final IElementType tt) { + 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/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/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 92% 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..04beb0e1f 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,15 +8,16 @@ * 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.devtools.intellij.qute.lang.psi.tree.QuteElementType; import com.redhat.qute.parser.template.Node; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; 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..98a56c0d9 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/tree/QuteTokenType.java @@ -0,0 +1,29 @@ +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_WHITESPACE = new QuteElementType("QUTE_WHITESPACE"); + IElementType QUTE_PARAMETER_TAG = new QuteElementType("QUTE_PARAMETER_TAG"); +} 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..13cd033ec 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -285,6 +285,8 @@ +