From 9833c3024a8a742ea0c1c94366889915d7361850 Mon Sep 17 00:00:00 2001 From: azerr Date: Tue, 13 Jun 2023 08:00:30 +0200 Subject: [PATCH] fix: HTML completion / autoclose is broken when offset is inside a Qute section (#787) Fixes #787 Signed-off-by: azerr --- .../lsp4ij/internal/SupportedFeatures.java | 24 +- .../LSIncompleteCompletionProposal.java | 2 +- .../completion/LSPCompletionConfidence.java | 31 ++ .../highlight/LSPHighlightPsiElement.java | 102 +++++ .../highlight/LSPHighlightUsagesHandler.java | 24 +- .../LSPHighlightUsagesHandlerFactory.java | 17 +- .../devtools/intellij/qute/QuteBundle.java | 45 +++ .../intellij/qute/lang/QuteASTFactory.java | 39 ++ .../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 ----- .../intellij/qute/lang/QuteParser.java | 39 -- .../qute/lang/QuteParserDefinition.java | 13 +- .../format/QuteFileIndentOptionsProvider.java | 37 ++ .../format/QuteFormattingModelBuilder.java | 354 ++++++++++++++++++ .../qute/lang/highlighter/QuteColorsPage.java | 93 +++++ .../highlighter/QuteEditorHighlighter.java | 39 ++ .../QuteEditorHighlighterProvider.java | 30 ++ .../highlighter/QuteHighlighterColors.java | 39 ++ .../highlighter/QuteSyntaxHighlighter.java | 84 +++++ .../QuteSyntaxHighlighterFactory.java | 29 ++ .../qute/lang/psi/AbstractQuteSubLexer.java | 59 +++ .../intellij/qute/lang/psi/QuteElement.java | 11 + .../qute/lang/{ => psi}/QuteElementType.java | 3 +- .../qute/lang/{ => psi}/QuteElementTypes.java | 45 ++- .../intellij/qute/lang/psi/QuteLexer.java | 219 +++++++++++ .../qute/lang/psi/QuteLexerForExpression.java | 52 +++ .../psi/QuteLexerForExpressionContent.java | 170 +++++++++ ...QuteLexerForExpressionMethodParameter.java | 31 ++ .../psi/QuteLexerForExpressionParameter.java | 33 ++ .../psi/QuteLexerForMethodParameters.java | 109 ++++++ .../qute/lang/psi/QuteLexerForStartTag.java | 153 ++++++++ .../intellij/qute/lang/psi/QuteParser.java | 37 ++ .../intellij/qute/lang/psi/QuteParsing.java | 206 ++++++++++ .../qute/lang/{ => psi}/QutePsiFile.java | 7 +- .../qute/lang/psi/QutePsiReference.java | 79 ++++ .../intellij/qute/lang/psi/QuteToken.java | 47 +++ .../intellij/qute/lang/psi/QuteTokenType.java | 53 +++ src/main/resources/META-INF/lsp4ij-qute.xml | 1 + src/main/resources/META-INF/plugin.xml | 8 +- .../messages/LanguageServerBundle.properties | 3 +- .../resources/messages/QuteBundle.properties | 22 ++ 46 files changed, 2340 insertions(+), 269 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/QuteBundle.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 delete mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParser.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/QuteColorsPage.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/QuteHighlighterColors.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 rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi}/QuteElementType.java (89%) rename src/main/java/com/redhat/devtools/intellij/qute/lang/{ => psi}/QuteElementTypes.java (57%) 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/QuteLexerForExpressionContent.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionMethodParameter.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionParameter.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForMethodParameters.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForStartTag.java create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java 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%) 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 create mode 100644 src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteTokenType.java create mode 100644 src/main/resources/messages/QuteBundle.properties 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/LSIncompleteCompletionProposal.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSIncompleteCompletionProposal.java index 28df8a2c5..d8d702b5a 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSIncompleteCompletionProposal.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSIncompleteCompletionProposal.java @@ -173,7 +173,7 @@ public int getPrefixCompletionStart(Document document, int completionOffset) { @NotNull @Override public String getLookupString() { - String lookup = StringUtils.isNotBlank(item.getSortText())?item.getSortText():item.getLabel(); + String lookup = StringUtils.isNotBlank(item.getFilterText())?item.getFilterText():item.getLabel(); if (lookup.charAt(0) == '@') { return lookup.substring(1); } 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..01ce243b4 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/completion/LSPCompletionConfidence.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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; + +/** + * Provides the capability to open completion anywhere. + */ +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..394caf7d6 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/operations/highlight/LSPHighlightPsiElement.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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; + +/** + * Implement a fake {@link PsiElement} which stores the required text edit (coming from Language server) to highlight. + * + * This class provides the capability to highlight part of code by using Language server TextEdit and not by using the PsiElement. + */ +public class LSPHighlightPsiElement extends PsiElementBase { + + private final TextRange textRange; + private final DocumentHighlightKind kind; + + public LSPHighlightPsiElement(TextRange textRange, DocumentHighlightKind kind) { + this.textRange = textRange; + this.kind = kind; + } + + 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..ad04201d6 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 @@ -15,7 +15,9 @@ import com.intellij.codeInsight.highlighting.HighlightUsagesHandlerFactory; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.progress.ProcessCanceledException; 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 +45,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,13 +78,14 @@ 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); - PsiElement element = file.findElementAt(highlightOffset); - if (element != null) { - elements.add(element); + TextRange textRange = LSPIJUtils.toTextRange(highlight.getRange(), document); + if (textRange != null) { + elements.add(new LSPHighlightPsiElement(textRange, highlight.getKind())); } } } + } catch (ProcessCanceledException cancellation){ + throw cancellation; } catch (InterruptedException e) { LOGGER.log(Level.WARNING, e, e::getLocalizedMessage); } diff --git a/src/main/java/com/redhat/devtools/intellij/qute/QuteBundle.java b/src/main/java/com/redhat/devtools/intellij/qute/QuteBundle.java new file mode 100644 index 000000000..9bc320f52 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/QuteBundle.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute; + +import com.intellij.DynamicBundle; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.PropertyKey; + +import java.util.function.Supplier; + +/** + * Qute messages bundle. + */ +public final class QuteBundle extends DynamicBundle { + + @NonNls public static final String BUNDLE = "messages.QuteBundle"; + private static final QuteBundle INSTANCE = new QuteBundle(); + + private QuteBundle() { + super(BUNDLE); + } + + @NotNull + public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { + return INSTANCE.getMessage(key, params); + } + + @NotNull + public static Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { + return INSTANCE.getLazyMessage(key, params); + } +} \ No newline at end of file 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..cec379d1a --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteASTFactory.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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.QuteElementTypes; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Qute AST factory. + */ +public class QuteASTFactory extends ASTFactory { + + @Override + public @Nullable LeafElement createLeaf(@NotNull IElementType type, @NotNull CharSequence text) { + if (type == QuteElementTypes.QUTE_OUTER_ELEMENT_TYPE) { + // HTML, YAML, etc content + return new OuterLanguageElementImpl(type, text); + } + // Qute content + 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..07abb40f4 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.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/QuteParser.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParser.java deleted file mode 100644 index 7fde3252f..000000000 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteParser.java +++ /dev/null @@ -1,39 +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.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.psi.tree.IElementType; -import com.intellij.psi.tree.ILazyParseableElementType; -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 - * 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 { - @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; - } -} 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..0538dd871 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 @@ -21,9 +21,17 @@ 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.QuteElementTypes; import org.jetbrains.annotations.NotNull; +/** + * Qute parser definition. + */ public class QuteParserDefinition implements ParserDefinition { + @Override public @NotNull Lexer createLexer(Project project) { return new QuteLexer(); @@ -41,16 +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) { + // This method seems never called, since whe have defined a QuteASTFactory. return new ASTWrapperPsiElement(node); } 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..59dc7c671 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/format/QuteFileIndentOptionsProvider.java @@ -0,0 +1,37 @@ +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; + +/** + * This class is a copy/paste from https://github.com/JetBrains/intellij-plugins/blob/master/handlebars/src/com/dmarcotte/handlebars/format/HbFileIndentOptionsProvider.java adapted for Qute. + */ +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..63000c2f5 --- /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.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.QuteElementTypes; +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 Qute syntax and delegates formatting + * for the templated language to that languages formatter + * + * This class is a copy/paste from https://github.com/JetBrains/intellij-plugins/blob/master/handlebars/src/com/dmarcotte/handlebars/format/HbFormattingModelBuilder.java adapted for Qute. + */ +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/QuteColorsPage.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteColorsPage.java new file mode 100644 index 000000000..c9e05fbfd --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteColorsPage.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.options.colors.AttributesDescriptor; +import com.intellij.openapi.options.colors.ColorDescriptor; +import com.intellij.openapi.options.colors.ColorSettingsPage; +import com.redhat.devtools.intellij.qute.QuteBundle; +import com.redhat.devtools.intellij.qute.lang.QuteFileType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Map; + +/** + * Qute color settings page. + */ +public class QuteColorsPage implements ColorSettingsPage { + + private static final AttributesDescriptor[] ATTRS = new AttributesDescriptor[]{ + new AttributesDescriptor(QuteBundle.message("options.qute.attribute.descriptor.comment"), QuteHighlighterColors.COMMENT), + new AttributesDescriptor(QuteBundle.message("options.qute.attribute.descriptor.edge"), QuteHighlighterColors.QUTE_EDGE), + new AttributesDescriptor(QuteBundle.message("options.qute.attribute.descriptor.tag"), QuteHighlighterColors.SECTION_TAG_NAME), + new AttributesDescriptor(QuteBundle.message("options.qute.attribute.descriptor.string"), QuteHighlighterColors.STRING), + new AttributesDescriptor(QuteBundle.message("options.qute.attribute.descriptor.numeric"), QuteHighlighterColors.NUMERIC), + new AttributesDescriptor(QuteBundle.message("options.qute.attribute.descriptor.boolean"), QuteHighlighterColors.BOOLEAN), + new AttributesDescriptor(QuteBundle.message("options.qute.attribute.descriptor.keyword"), QuteHighlighterColors.KEYWORD) + }; + + @Override + @NotNull + public String getDisplayName() { + return QuteBundle.message("options.qute.display.name"); + } + + @Override + public Icon getIcon() { + return QuteFileType.QUTE.getIcon(); + } + + @Override + public AttributesDescriptor @NotNull [] getAttributeDescriptors() { + return ATTRS; + } + + @Override + public ColorDescriptor @NotNull [] getColorDescriptors() { + return ColorDescriptor.EMPTY_ARRAY; + } + + @Override + @NotNull + public SyntaxHighlighter getHighlighter() { + return new QuteSyntaxHighlighter(); + } + + @Override + @NotNull + public String getDemoText() { + return "{! See following sample at https://quarkus.io/guides/qute-reference#expression_resolution !}\n" + + "\n" + + "{item.name} \n" + + "

    \n" + + "{#for item in item.derivedItems} \n" + + "
  • \n" + + " {item.name} \n" + + " is derived from\n" + + " {data:item.name} \n" + + "
  • \n" + + "{/for}\n" + + "
\n" + + ""; + } + + @Override + public @Nullable Map getAdditionalHighlightingTagToDescriptorMap() { + return null; + } +} 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..25b0f3ac7 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighter.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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.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.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..6e38053fc --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteEditorHighlighterProvider.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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/QuteHighlighterColors.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteHighlighterColors.java new file mode 100644 index 000000000..27fff2215 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteHighlighterColors.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.XmlHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; + +/** + * Qute higlighter colors constants. + */ +public class QuteHighlighterColors { + + public static final TextAttributesKey COMMENT = TextAttributesKey.createTextAttributesKey("COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT); + + public static final TextAttributesKey QUTE_EDGE = TextAttributesKey.createTextAttributesKey("EXPRESSION", XmlHighlighterColors.XML_TAG); + + public static final TextAttributesKey SECTION_TAG_NAME = TextAttributesKey.createTextAttributesKey("SECTION_TAG_NAME", XmlHighlighterColors.XML_TAG_NAME); + + public static final TextAttributesKey STRING = TextAttributesKey.createTextAttributesKey("STRING", XmlHighlighterColors.XML_ATTRIBUTE_VALUE); + + public static final TextAttributesKey NUMERIC = TextAttributesKey.createTextAttributesKey("NUMERIC", DefaultLanguageHighlighterColors.NUMBER); + + public static final TextAttributesKey BOOLEAN = TextAttributesKey.createTextAttributesKey("BOOLEAN", DefaultLanguageHighlighterColors.KEYWORD); + + public static final TextAttributesKey KEYWORD = TextAttributesKey.createTextAttributesKey("KEYWORD", DefaultLanguageHighlighterColors.KEYWORD); + +} 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..629c0cf85 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighter.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.highlighter; + +import com.intellij.lexer.Lexer; +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.QuteElementTypes; +import com.redhat.devtools.intellij.qute.lang.psi.QuteTokenType; +import org.jetbrains.annotations.NotNull; + +import static com.redhat.devtools.intellij.qute.lang.highlighter.QuteHighlighterColors.*; + +/** + * Qyte syntax highlighter. + */ +public class QuteSyntaxHighlighter extends SyntaxHighlighterBase { + + 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 (ex: {foo} + ourMap.putValue(QuteTokenType.QUTE_START_EXPRESSION, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_END_EXPRESSION, QUTE_EDGE); + + // String + ourMap.putValue(QuteTokenType.QUTE_EXPRESSION_START_STRING, STRING); + ourMap.putValue(QuteTokenType.QUTE_EXPRESSION_STRING, STRING); + ourMap.putValue(QuteTokenType.QUTE_EXPRESSION_END_STRING, STRING); + ourMap.putValue(QuteElementTypes.QUTE_STRING, STRING); + + // Numeric + ourMap.putValue(QuteElementTypes.QUTE_NUMERIC, NUMERIC); + + // Boolean + ourMap.putValue(QuteElementTypes.QUTE_BOOLEAN, BOOLEAN); + + // Keyword (ex : null) + ourMap.putValue(QuteElementTypes.QUTE_KEYWORD, KEYWORD); + + // Qute section (ex: {#let name='foo'} + ourMap.putValue(QuteTokenType.QUTE_START_TAG_OPEN, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_START_TAG_CLOSE, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_START_TAG_SELF_CLOSE, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_END_TAG_OPEN, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_END_TAG_CLOSE, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_END_TAG_SELF_CLOSE, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_START_TAG, SECTION_TAG_NAME); + ourMap.putValue(QuteTokenType.QUTE_END_TAG, SECTION_TAG_NAME); + + // Qute parameter declaration (ex: {@java.lang.String foo} + ourMap.putValue(QuteTokenType.QUTE_START_PARAMETER_DECLARATION, QUTE_EDGE); + ourMap.putValue(QuteTokenType.QUTE_END_PARAMETER_DECLARATION, QUTE_EDGE); + } + + @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..0fc2fbf07 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/highlighter/QuteSyntaxHighlighterFactory.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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; + +/** + * Qyte syntax highlighter factory. + */ +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..27d06760a --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/AbstractQuteSubLexer.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.tree.IElementType; + +/** + * Abstract class for sub lexer. + */ +public abstract class AbstractQuteSubLexer { + + protected IElementType myTokenType; + protected int myTokenStart; + protected int myTokenEnd; + protected int myState; + protected boolean myFailed; + + public int getState() { + locateToken(); + return myState; + } + + public IElementType getTokenType() { + locateToken(); + return myTokenType; + } + + 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/QuteElementType.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteElementType.java similarity index 89% 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/QuteElementType.java index 753aa4377..f1c4bbcd2 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementType.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/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; 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/QuteElementTypes.java similarity index 57% 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/QuteElementTypes.java index 4107666c6..eb771a891 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/lang/QuteElementTypes.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteElementTypes.java @@ -8,51 +8,48 @@ * 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.psi.templateLanguages.TemplateDataElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IFileElementType; import com.intellij.psi.tree.OuterLanguageElementType; -import com.redhat.qute.parser.template.Node; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import com.redhat.devtools.intellij.qute.lang.QuteLanguage; +import com.redhat.devtools.intellij.qute.lang.psi.QuteElementType; 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_NUMERIC = new QuteElementType("QUTE_NUMERIC"); + + public static final IElementType QUTE_BOOLEAN = new QuteElementType("QUTE_BOOLEAN"); + + public static final IElementType QUTE_KEYWORD = new QuteElementType("QUTE_KEYWORD"); + + 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 IElementType fromNode(Node node) { - return types.computeIfAbsent(node.getNodeName(), s -> new QuteElementType(s)); - } + public static final IElementType QUTE_FILE_DATA = new TemplateDataElementType("QUTE_FILE_DATA", QuteLanguage.INSTANCE, QUTE_TEXT, QUTE_OUTER_ELEMENT_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..b13653243 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexer.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * 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.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 to parse Qute template. + */ +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) { + // create a sub lexer to parse content of Qute expression (ex: {|foo.bar(0)|}) + currentSubLexer = new QuteLexerForExpression(myText.toString(), scanner, startExpressionOffset); + } else if (startTagOpenOffset != -1 && currentSubLexer == null) { + // create a sub lexer to parse content of Qute start section (ex: {#let |name='foo'|}{/let}) + currentSubLexer = new QuteLexerForStartTag(myText.toString(), scanner, startTagOpenOffset); + } + boolean continueToScanTemplate = currentSubLexer == null; + if (currentSubLexer != null) { + // parse content of expression or content of Qute start section + myTokenType = currentSubLexer.getTokenType(); + if (myTokenType == null) { + // The parse of the content is fisnished, we can continue to parse another tokens of the template. + continueToScanTemplate = true; + startExpressionOffset = -1; + startTagOpenOffset = -1; + currentSubLexer = null; + } else { + // collect token from the sub lexer + myState = currentSubLexer.getState(); + myTokenEnd = currentSubLexer.getTokenEnd(); + currentSubLexer.advance(); + } + } + + if (continueToScanTemplate) { + // Parse tokens from the template + 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) { + // It is a start expression (ex : |{|foo}, + // We will return this token and for the next iteration we will use a sub lexer + // to parse expression content + startExpressionOffset = scanner.getTokenEnd(); + } else if (myTokenType == QuteTokenType.QUTE_START_TAG) { + // It is a start tag (ex : |{#|let ...}, + // We will return this token and for the next iteration we will use a sub lexer + // to parse start tag content + 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; + } + } + + /** + * Returns the IJ {@link IElementType} from the given Qute parser token type. + * + * @param tokenType the Qute parser token type. + * + * @return the IJ {@link IElementType} from the given Qute parser token type. + */ + 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 ParameterDeclaration: + return QuteTokenType.QUTE_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..bec661f9c --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpression.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.redhat.qute.parser.expression.scanner.ExpressionScanner; +import com.redhat.qute.parser.template.scanner.TemplateScanner; +import com.redhat.qute.parser.template.scanner.TokenType; + +/** + * Qute lexer to parse Qute expression content. + * + * + * {|foo.bar(0)|} + * + */ +public class QuteLexerForExpression extends QuteLexerForExpressionContent { + + QuteLexerForExpression(String text, TemplateScanner templateScanner, int startExpressionOffset) { + super(text); + // Get the token end of the expression --> {foo.bar(0)|}| + boolean isClosed = false; + TokenType tokenType = templateScanner.scan(); + while (tokenType != TokenType.EOS) { + if (tokenType == TokenType.EndExpression) { + isClosed = true; + break; + } + tokenType = templateScanner.scan(); + } + if (isClosed) { + // The expression is closed, stores state, token type and end position of the end expression '}' + myLastState = QuteLexer.getStateAsInt(templateScanner.getScannerState()); + myLastTokenType = QuteLexer.getTokenType(templateScanner.getTokenType()); + myLastTokenEnd = templateScanner.getTokenEnd(); + } + // Initialize the expression scanner to parse the expression content + int endExpressionOffset = templateScanner.getTokenOffset(); + initialize(ExpressionScanner.createScanner(text, true, startExpressionOffset, endExpressionOffset)); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionContent.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionContent.java new file mode 100644 index 000000000..4d039f876 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionContent.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.tree.IElementType; +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.LiteralSupport; + +/** + * Base class to parse expression content. + */ +public abstract class QuteLexerForExpressionContent extends AbstractQuteSubLexer { + + private static final String NULL_TYPE = "null"; + private static final String STRING_TYPE = "java.lang.String"; + private static final String BOOLEAN_TYPE = "java.lang.Boolean"; + private static final String DOUBLE_TYPE = "java.lang.Double"; + private static final String FLOAT_TYPE = "java.lang.Float"; + private static final String INTEGER_TYPE = "java.lang.Integer"; + private static final String LONG_TYPE = "java.lang.Long"; + + private final String text; + private ExpressionScanner scanner; + protected int myLastState; + protected IElementType myLastTokenType; + protected int myLastTokenEnd; + private AbstractQuteSubLexer currentSubLexer; + private int startOpenBracketOffset; + + QuteLexerForExpressionContent(String text) { + this.text = text; + } + + public void initialize(ExpressionScanner scanner) { + this.scanner = scanner; + this.startOpenBracketOffset = -1; + } + + @Override + protected void doLocateToken() { + if (startOpenBracketOffset != -1 && currentSubLexer == null) { + currentSubLexer = new QuteLexerForMethodParameters(text, scanner, startOpenBracketOffset); + } + boolean continueToScanTemplate = currentSubLexer == null; + if (currentSubLexer != null) { + myTokenType = currentSubLexer.getTokenType(); + if (myTokenType == null) { + continueToScanTemplate = true; + startOpenBracketOffset = -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_EXPRESSION_OPEN_BRACKET) { + // Current token is an open bracket of the method + // we stores this offset to parse in thenext iteration method parameters. + startOpenBracketOffset = scanner.getTokenEnd(); + } else if (myTokenType == QuteTokenType.QUTE_EXPRESSION_OBJECT_PART || myTokenType == QuteTokenType.QUTE_EXPRESSION_INFIX_PARAMETER){ + String text = scanner.getTokenText(); + String javaType = LiteralSupport.getLiteralJavaType(text); + if (javaType != null && !javaType.isEmpty()) { + // It is an object part or an infix parameter + // Instead of returning this token, we try to return the best type (String,Number, Boolean, Token) + switch(javaType) { + case STRING_TYPE: + myTokenType = QuteElementTypes.QUTE_STRING; + break; + case INTEGER_TYPE: + case LONG_TYPE: + case DOUBLE_TYPE: + case FLOAT_TYPE: + myTokenType = QuteElementTypes.QUTE_NUMERIC; + break; + case BOOLEAN_TYPE: + myTokenType = QuteElementTypes.QUTE_BOOLEAN; + break; + case NULL_TYPE: + myTokenType = QuteElementTypes.QUTE_KEYWORD; + break; + } + } + } + break; + } + tokenType = scanner.scan(); + } + if (tokenType == TokenType.EOS) { + if (myLastTokenType != null) { + myState = myLastState; + myTokenType = myLastTokenType; + myTokenEnd = myLastTokenEnd; + myLastTokenType = null; + } else { + myTokenType = null; + } + } + } + } + + /** + * Returns the IJ {@link IElementType} from the given Qute parser token type. + * + * @param tokenType the Qute parser token type. + * + * @return the IJ {@link IElementType} from the given Qute parser token type. + */ + public 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; + } + + public static int getStateAsInt(ScannerState state) { + return 20 + state.ordinal(); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionMethodParameter.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionMethodParameter.java new file mode 100644 index 000000000..e552785e1 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionMethodParameter.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.redhat.qute.parser.expression.scanner.ExpressionScanner; + +/** + * Qute lexer to parse a given Qute method parameter content. + * + * + * {foo.bar(|'foo'|,true)} + * + */ +public class QuteLexerForExpressionMethodParameter extends QuteLexerForExpressionContent { + + QuteLexerForExpressionMethodParameter(String text, int startExpressionOffset, int endExpressionOffset) { + super(text); + initialize(ExpressionScanner.createScanner(text, false, startExpressionOffset, endExpressionOffset)); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionParameter.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionParameter.java new file mode 100644 index 000000000..d7b2df681 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForExpressionParameter.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.redhat.qute.parser.expression.scanner.ExpressionScanner; +import com.redhat.qute.parser.parameter.scanner.ParameterScanner; +import com.redhat.qute.parser.template.scanner.TemplateScanner; + +/** + * Qute lexer to parse a given an expression parameter content. + * + * + * {foo ?: |bar|} + * + */ +public class QuteLexerForExpressionParameter extends QuteLexerForExpressionContent { + + QuteLexerForExpressionParameter(String text, int startExpressionOffset, int endExpressionOffset) { + super(text); + initialize(ExpressionScanner.createScanner(text, true, startExpressionOffset, endExpressionOffset)); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForMethodParameters.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForMethodParameters.java new file mode 100644 index 000000000..f636a352b --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForMethodParameters.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.tree.IElementType; +import com.redhat.qute.parser.expression.scanner.ExpressionScanner; +import com.redhat.qute.parser.parameter.scanner.TokenType; +import com.redhat.qute.parser.parameter.scanner.ParameterScanner; + +/** + * Qute lexer to parse Qute method parameters content. + * + * + * {foo.bar(|'foo',true|)} + * + */ +public class QuteLexerForMethodParameters extends AbstractQuteSubLexer { + + private final ParameterScanner scanner; + private final String text; + protected int myLastState; + protected IElementType myLastTokenType; + protected int myLastTokenEnd; + private AbstractQuteSubLexer currentSubLexer; + + public QuteLexerForMethodParameters(String text, ExpressionScanner expressionScanner, int startExpressionOffset) { + this.text = text; + boolean isClosed = false; + // Get the token end of the method parameter --> {foo.bar('foo',true|)|} + com.redhat.qute.parser.expression.scanner.TokenType tokenType = expressionScanner.scan(); + while (tokenType != com.redhat.qute.parser.expression.scanner.TokenType.EOS) { + if (tokenType == com.redhat.qute.parser.expression.scanner.TokenType.CloseBracket) { + isClosed = true; + break; + } + tokenType = expressionScanner.scan(); + } + if (isClosed) { + // The method parameters is closed, stores state, token type and end position of the end ')' + myLastState = QuteLexerForExpressionContent.getStateAsInt(expressionScanner.getScannerState()); + myLastTokenType = QuteLexerForExpressionContent.getTokenType(expressionScanner.getTokenType()); + myLastTokenEnd = expressionScanner.getTokenEnd(); + } + // Initialize the parameter scanner to parse the method parameters content + int endExpressionOffset = expressionScanner.getTokenOffset(); + scanner = ParameterScanner.createScanner(text, startExpressionOffset, endExpressionOffset, true, false); + } + + @Override + protected void doLocateToken() { + boolean continueToScanTemplate = currentSubLexer == null; + if (currentSubLexer != null) { + myTokenType = currentSubLexer.getTokenType(); + if (myTokenType == null) { + continueToScanTemplate = true; + currentSubLexer = null; + } else { + myState = currentSubLexer.getState(); + myTokenEnd = currentSubLexer.getTokenEnd(); + currentSubLexer.advance(); + } + } + + if (continueToScanTemplate) { + TokenType tokenType = scanner.scan(); + while (tokenType != TokenType.EOS) { + IElementType elementType = QuteLexerForStartTag.getTokenType(tokenType); + if (elementType != null) { + myState = QuteLexerForStartTag.getStateAsInt(scanner.getScannerState()); + myTokenType = elementType; + myTokenEnd = scanner.getTokenEnd(); + if (myTokenType == QuteTokenType.QUTE_PARAMETER_NAME) { + // Parse parameter name as expression + int startExpressionOffset = scanner.getTokenOffset(); + int endExpressionOffset = scanner.getTokenEnd(); + myTokenType = null; + myTokenEnd = startExpressionOffset; + currentSubLexer = new QuteLexerForExpressionMethodParameter(text.toString(), startExpressionOffset, endExpressionOffset); + locateToken(); + return; + } + break; + } + tokenType = scanner.scan(); + } + if (tokenType == TokenType.EOS) { + if (myLastTokenType != null) { + myState = myLastState; + myTokenType = myLastTokenType; + myTokenEnd = myLastTokenEnd; + myLastTokenType = null; + } else { + myTokenType = null; + } + } + } + } +} 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..ccd4fe597 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteLexerForStartTag.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.psi.tree.IElementType; +import com.redhat.qute.parser.expression.scanner.ExpressionScanner; +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.parameter.scanner.TokenType; + +/** + * Qute lexer to parse Qute start tag content. + * + * + * {#let |name='foo' bar=true|} + * + */ +public class QuteLexerForStartTag extends AbstractQuteSubLexer { + + private final String text; + + private final ParameterScanner scanner; + private int myLastState; + private IElementType myLastTokenType; + private int myLastTokenEnd; + private ExpressionScanner expressionScanner; + + private AbstractQuteSubLexer currentSubLexer; + + public QuteLexerForStartTag(String text, TemplateScanner templateScanner, int startTagOpenOffset) { + this.text = text; + boolean isClosed = false; + // Get the token end of the start section --> {#let name='foo' bar=true|}| + 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.StartTagClose || + tokenType == com.redhat.qute.parser.template.scanner.TokenType.StartTagSelfClose) { + isClosed = true; + break; + } + tokenType = templateScanner.scan(); + } + if (isClosed) { + // The start section is closed, stores state, token type and end position of the end '}' or '/}' + myLastState = QuteLexer.getStateAsInt(templateScanner.getScannerState()); + myLastTokenType = QuteLexer.getTokenType(templateScanner.getTokenType()); + myLastTokenEnd = templateScanner.getTokenEnd(); + } + // Initialize the parameter scanner to parse the start section content + int endTagOpenOffset = templateScanner.getTokenOffset(); + scanner = ParameterScanner.createScanner(text, startTagOpenOffset, endTagOpenOffset, false, true); + } + + @Override + protected void doLocateToken() { + if (myTokenType != null) return; + + myTokenStart = myTokenEnd; + if (myFailed) return; + + try { + boolean continueToScanTemplate = currentSubLexer == null; + if (currentSubLexer != null) { + myTokenType = currentSubLexer.getTokenType(); + if (myTokenType == null) { + continueToScanTemplate = true; + 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_PARAMETER_NAME || myTokenType == QuteTokenType.QUTE_PARAMETER_VALUE) { + // Parse parameter name as expression + int startExpressionOffset = scanner.getTokenOffset(); + int endExpressionOffset = scanner.getTokenEnd(); + myTokenType = null; + myTokenEnd = startExpressionOffset; + currentSubLexer = new QuteLexerForExpressionParameter(text.toString(), startExpressionOffset, endExpressionOffset); + locateToken(); + return; + } + 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; + } + } + } + } catch (ProcessCanceledException e) { + throw e; + } catch (Throwable e) { + myFailed = true; + myTokenType = com.intellij.psi.TokenType.BAD_CHARACTER; + } + } + + /** + * Returns the IJ {@link IElementType} from the given Qute parser token type. + * + * @param tokenType the Qute parser token type. + * + * @return the IJ {@link IElementType} from the given Qute parser token type. + */ + public static IElementType getTokenType(com.redhat.qute.parser.parameter.scanner.TokenType tokenType) { + switch (tokenType) { + case ParameterName: + return QuteTokenType.QUTE_PARAMETER_NAME; + case Assign: + return QuteTokenType.QUTE_PARAMETER_ASSIGN; + case ParameterValue: + return QuteTokenType.QUTE_PARAMETER_VALUE; + case Whitespace: + return QuteTokenType.QUTE_EXPRESSION_WHITESPACE; + } + return null; + } + + public static int getStateAsInt(ScannerState state) { + return 20 + state.ordinal(); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java new file mode 100644 index 000000000..4c8a28787 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParser.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.lang.*; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.NotNull; + +/** + * Qute parser. Required because some features are related to special + * PSI nodes thus we need to implement them. + */ +public class QuteParser implements PsiParser, LightPsiParser { + + @Override + public @NotNull ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) { + 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..a8fbaa6a2 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteParsing.java @@ -0,0 +1,206 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.lang.PsiBuilder; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.Nullable; + +import static com.redhat.devtools.intellij.qute.lang.psi.QuteElementTypes.*; +import static com.redhat.devtools.intellij.qute.lang.psi.QuteTokenType.*; + +/** + * Qute parsing used to build a Qute AST from the IJ lexer element type. + */ +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_STRING) { + final PsiBuilder.Marker string = mark(); + advance(); + string.done(QUTE_STRING); + continue; + } else if (tt == QUTE_EXPRESSION_NAMESPACE_PART) { + final PsiBuilder.Marker namespacePart = mark(); + advance(); + namespacePart.done(QUTE_EXPRESSION_NAMESPACE_PART); + continue; + } 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_STRING) { + final PsiBuilder.Marker string = mark(); + advance(); + string.done(QUTE_STRING); + continue; + } else if (tt == QUTE_EXPRESSION_NAMESPACE_PART) { + final PsiBuilder.Marker namespacePart = mark(); + advance(); + namespacePart.done(QUTE_EXPRESSION_NAMESPACE_PART); + continue; + } 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/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..7358043ce --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QutePsiReference.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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; + +/** + * Implement a fake {@link PsiReference} which is required for hover and avoid having this kind of bug + * https://github.com/redhat-developer/intellij-quarkus/issues/944 + */ +public class QutePsiReference implements PsiReference { + + private final ASTNode node; + + private final TextRange rangeInElement; + + public QutePsiReference(ASTNode node) { + this.node = node; + this.rangeInElement = new TextRange(0, node.getPsi().getTextRange().getLength()); + } + + @Override + public @NotNull PsiElement getElement() { + return node.getPsi(); + } + + @Override + public @NotNull TextRange getRangeInElement() { + return rangeInElement; + } + + @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..9e242945d --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteToken.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +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; + +/** + * Qute token. + * + */ +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) { + 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/psi/QuteTokenType.java b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteTokenType.java new file mode 100644 index 000000000..0144fef83 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/qute/lang/psi/QuteTokenType.java @@ -0,0 +1,53 @@ +package com.redhat.devtools.intellij.qute.lang.psi; + +import com.intellij.psi.tree.IElementType; + +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_PARAMETER_DECLARATION = new QuteElementType("QUTE_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"); + + // Parameters + IElementType QUTE_PARAMETER_NAME = new QuteElementType("QUTE_PARAMETER_NAME"); + + IElementType QUTE_PARAMETER_ASSIGN = new QuteElementType("QUTE_PARAMETER_ASSIGN"); + + IElementType QUTE_PARAMETER_VALUE = new QuteElementType("QUTE_PARAMETER_VALUE"); +} 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..1d70d3f8a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -284,8 +284,14 @@ implementationClass="com.redhat.devtools.intellij.qute.lang.QuteFileType"/> + - + + + + diff --git a/src/main/resources/messages/LanguageServerBundle.properties b/src/main/resources/messages/LanguageServerBundle.properties index cf6b7fca0..5bc6bf19d 100644 --- a/src/main/resources/messages/LanguageServerBundle.properties +++ b/src/main/resources/messages/LanguageServerBundle.properties @@ -27,5 +27,4 @@ lsp.console.actions.folding=Collapse/Expand All lsp.create.file.confirm.dialog.title=Create file? lsp.create.file.confirm.dialog.message=Unable to open file ''{0}''. Do you want to create it? lsp.create.file.error.dialog.title=Create file problem -lsp.create.file.error.dialog.message=Error while creating file ''{0}'' : ''{1}''. - +lsp.create.file.error.dialog.message=Error while creating file ''{0}'' : ''{1}''. \ No newline at end of file diff --git a/src/main/resources/messages/QuteBundle.properties b/src/main/resources/messages/QuteBundle.properties new file mode 100644 index 000000000..95fb60633 --- /dev/null +++ b/src/main/resources/messages/QuteBundle.properties @@ -0,0 +1,22 @@ +############################################################################### +# Copyright (c) 2023 Red Hat Inc. and others. +# All rights reserved. This program and the accompanying materials +# are 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 +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# Red Hat Inc. - initial API and implementation +############################################################################### + +## Qute colors settings page +options.qute.display.name=Qute +options.qute.attribute.descriptor.comment=Comment +options.qute.attribute.descriptor.edge=Edge +options.qute.attribute.descriptor.tag=Tag +options.qute.attribute.descriptor.string=String +options.qute.attribute.descriptor.numeric=Numeric +options.qute.attribute.descriptor.boolean=Boolean +options.qute.attribute.descriptor.keyword=Keyword