Skip to content

Commit

Permalink
Close matching symbols automatically (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjansen committed Sep 15, 2020
1 parent 35597f6 commit 65df391
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 37 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ intellij {
pluginName 'jetbrains-plugin-st4'
downloadSources true
updateSinceUntilBuild false
plugins 'java', 'IntelliLang'
}

group 'antlr'
Expand Down
1 change: 1 addition & 0 deletions src/main/antlr/STLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ LBRACE : LBrace { startSubTemplate(); } ;
RDELIM : . { isRDelim() }? -> mode(DEFAULT_MODE) ;

STRING : DQuoteLiteral ;
EOF_STRING : DQuote ( EscSeq | ~["\r\n\\] )* ;
IF : 'if' ;
ELSEIF : 'elseif' ;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.antlr.jetbrains.st4plugin.editor;

import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ReflectionUtil;
import org.antlr.intellij.adaptor.lexer.ANTLRLexerAdaptor;
import org.antlr.jetbrains.st4plugin.parsing.STGLexer;
import org.antlr.jetbrains.st4plugin.parsing.STLexer;
import org.antlr.jetbrains.st4plugin.psi.STFile;
import org.antlr.jetbrains.st4plugin.psi.STGroupFile;
import org.antlr.jetbrains.st4plugin.psi.STTokenTypes;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.List;

import static org.antlr.jetbrains.st4plugin.psi.STGroupTokenTypes.getTokenElementType;

/**
* Automatically closes tag delimiters, template delimiters etc.
*/
public class AutoCloseTypedHandler extends TypedHandlerDelegate {

@Override
public @NotNull Result beforeCharTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, @NotNull FileType fileType) {
List<IElementType> validBeforeLDelim = Arrays.asList(
STTokenTypes.getTokenElementType(STLexer.TEXT),
STTokenTypes.getTokenElementType(STLexer.RDELIM),
STTokenTypes.getTokenElementType(STLexer.HORZ_WS)
);

if (file instanceof STFile) {
int offset = editor.getCaretModel().getOffset();

EditorHighlighter highlighter = ((EditorEx) editor).getHighlighter();
if (highlighter instanceof LexerEditorHighlighter) {
Lexer lexer = ReflectionUtil.getField(LexerEditorHighlighter.class, highlighter, Lexer.class, "myLexer");
org.antlr.v4.runtime.Lexer antlrLexer = ReflectionUtil.getField(ANTLRLexerAdaptor.class, lexer, org.antlr.v4.runtime.Lexer.class, "lexer");

if (antlrLexer instanceof STLexer) {
char ldelim = ((STLexer) antlrLexer).getlDelim();
char rdelim = ((STLexer) antlrLexer).getrDelim();

if (c == ldelim) {
IElementType previousToken = offset == 0 ? null : highlighter.createIterator(offset - 1).getTokenType();

if (offset == 0 || validBeforeLDelim.contains(previousToken)) {
EditorModificationUtil.insertStringAtCaret(editor, "" + c + rdelim, false, 1);
return Result.STOP;
}
}
}
}
}

return super.beforeCharTyped(c, project, editor, file, fileType);
}

@Override
public @NotNull Result charTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
if (file instanceof STGroupFile) {
int offset = editor.getCaretModel().getOffset();

if ((c == '<' ||c == '%') && offset > 1) {
autoCloseTemplate(c, editor, file, offset);
}

// TODO we could let IntelliJ handle this automatically if anonymous templates were parsed as `LBRACE ANON_TEMPLATE_CONTENT RBRACE`
// => we'd also have braces matching
if (c == '{') {
PsiElement previousElement = file.findElementAt(offset - 1);
previousElement = previousElement == null ? null : PsiTreeUtil.prevVisibleLeaf(previousElement);

if (previousElement != null && previousElement.getNode().getElementType() == getTokenElementType(STGLexer.ASSIGN)) {
EditorModificationUtil.insertStringAtCaret(editor, "}", false, 0);
}
}
}

return super.charTyped(c, project, editor, file);
}

private void autoCloseTemplate(char c, @NotNull Editor editor, @NotNull PsiFile file, int offset) {
char previous = editor.getDocument().getCharsSequence().charAt(offset - 2);

if (previous == '<') {
PsiElement previousElement = file.findElementAt(offset - 2);
previousElement = previousElement == null ? null : PsiTreeUtil.prevVisibleLeaf(previousElement);

if (previousElement != null && previousElement.getNode().getElementType() == getTokenElementType(STGLexer.TMPL_ASSIGN)) {
String closing = c == '<' ? ">>" : "%>";
EditorModificationUtil.insertStringAtCaret(editor, closing, false, 0);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.antlr.jetbrains.st4plugin.editor;

import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler;
import org.antlr.jetbrains.st4plugin.STGroupLanguage;
import org.antlr.jetbrains.st4plugin.parsing.STGLexer;

import static org.antlr.intellij.adaptor.lexer.PSIElementTypeFactory.createTokenSet;

/**
* Automatically closes {@link STGLexer#STRING} quotes.
*/
public class STGroupQuoteHandler extends SimpleTokenSetQuoteHandler {

public STGroupQuoteHandler() {
super(createTokenSet(STGroupLanguage.INSTANCE, STGLexer.STRING, STGLexer.EOF_STRING));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.antlr.jetbrains.st4plugin.editor;

import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler;
import org.antlr.jetbrains.st4plugin.STLanguage;
import org.antlr.jetbrains.st4plugin.parsing.STLexer;

import static org.antlr.intellij.adaptor.lexer.PSIElementTypeFactory.createTokenSet;

/**
* Automatically closes {@link STLexer#STRING} quotes.
*/
public class STQuoteHandler extends SimpleTokenSetQuoteHandler {

public STQuoteHandler() {
super(createTokenSet(STLanguage.INSTANCE, STLexer.STRING, STLexer.EOF_STRING));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.antlr.jetbrains.st4plugin.highlight;

import com.intellij.lang.Language;
import com.intellij.lexer.Lexer;
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.FileType;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.templateLanguages.TemplateDataLanguageMappings;
import org.antlr.intellij.adaptor.lexer.ANTLRLexerAdaptor;
import org.antlr.jetbrains.st4plugin.STGroupFileType;
import org.antlr.jetbrains.st4plugin.parsing.STLexer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.antlr.jetbrains.st4plugin.psi.STTokenTypes.getTokenElementType;

/**
* Highlights the "outer language", i.e. the target language.
* The target language can be configured in the Template Data Languages settings.
*/
public class STEditorHighlighter extends LayeredLexerEditorHighlighter {

public STEditorHighlighter(@Nullable VirtualFile virtualFile,
@Nullable Project project,
@NotNull FileType fileType,
@NotNull EditorColorsScheme scheme) {
super(fileType == STGroupFileType.INSTANCE ? new STGroupSyntaxHighlighter() : new STSyntaxHighlighter(), scheme);

if (project != null && virtualFile != null) {
Language language = TemplateDataLanguageMappings.getInstance(project).getMapping(virtualFile);

if (language != null) {
SyntaxHighlighter outerHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(language.getAssociatedFileType(), project, virtualFile);

if (outerHighlighter != null) {
registerLayer(getTokenElementType(STLexer.TEXT), new LayerDescriptor(outerHighlighter, ""));
}
}
}
}

@Override
public @NotNull ANTLRLexerAdaptor getLexer() {
return (ANTLRLexerAdaptor) super.getLexer();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.antlr.jetbrains.st4plugin.highlight;

import com.intellij.lang.Language;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.ex.util.LayerDescriptor;
import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter;
Expand All @@ -26,29 +27,3 @@ public EditorHighlighter getEditorHighlighter(@Nullable Project project, @NotNul
return new STEditorHighlighter(virtualFile, project, fileType, colors);
}
}

/**
* Highlights the "outer language", i.e. the target language.
* The target language can be configured in the Template Data Languages settings.
*/
class STEditorHighlighter extends LayeredLexerEditorHighlighter {

public STEditorHighlighter(@Nullable VirtualFile virtualFile,
@Nullable Project project,
@NotNull FileType fileType,
@NotNull EditorColorsScheme scheme) {
super(fileType == STGroupFileType.INSTANCE ? new STGroupSyntaxHighlighter() : new STSyntaxHighlighter(), scheme);

if (project != null && virtualFile != null) {
Language language = TemplateDataLanguageMappings.getInstance(project).getMapping(virtualFile);

if (language != null) {
SyntaxHighlighter outerHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(language.getAssociatedFileType(), project, virtualFile);

if (outerHighlighter != null) {
registerLayer(getTokenElementType(STLexer.TEXT), new LayerDescriptor(outerHighlighter, ""));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ public void setDelimiters(char lDelim, char rDelim) {
this.rDelim = rDelim;
}

public char getlDelim() {
return lDelim;
}

public char getrDelim() {
return rDelim;
}

public boolean isLDelim() {
return lDelim == _input.LA(-1);
}
Expand Down
16 changes: 5 additions & 11 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<description><![CDATA[
<p>
This plugin is for StringTemplate v4 .stg/.st files. It works with
IntelliJ IDEA 15, 2016.1-2020.1. It should work in other IntelliJ-based IDEs.
IntelliJ IDEA 15, 2016.1-2020.2. It should work in other IntelliJ-based IDEs.
</p>
<p><a href="https://github.com/antlr/jetbrains-plugin-st4">Github source</a></p>
Expand All @@ -27,19 +27,9 @@

<!-- please see http://confluence.jetbrains.net/display/IDEADEV/Plugin+Compatibility+with+IntelliJ+Platform+Products
on how to target different products -->
<!-- uncomment to enable plugin in all products -->
<depends>com.intellij.modules.lang</depends>
<depends optional="true" config-file="st-intellilang.xml">org.intellij.intelliLang</depends>

<application-components>
</application-components>

<project-components>
</project-components>

<actions>
</actions>

<extensions defaultExtensionNs="com.intellij">
<fileTypeFactory implementation="org.antlr.jetbrains.st4plugin.STGroupFileType$Factory"/>
<fileTypeFactory implementation="org.antlr.jetbrains.st4plugin.STFileType$Factory"/>
Expand All @@ -61,5 +51,9 @@
<lang.braceMatcher language="STGroup" implementationClass="org.antlr.jetbrains.st4plugin.highlight.STGroupBraceMatcher"/>
<lang.braceMatcher language="ST" implementationClass="org.antlr.jetbrains.st4plugin.highlight.STBraceMatcher"/>
<languageInjector implementation="org.antlr.jetbrains.st4plugin.psi.STLanguageInjector"/>

<typedHandler implementation="org.antlr.jetbrains.st4plugin.editor.AutoCloseTypedHandler"/>
<lang.quoteHandler language="STGroup" implementationClass="org.antlr.jetbrains.st4plugin.editor.STGroupQuoteHandler"/>
<lang.quoteHandler language="ST" implementationClass="org.antlr.jetbrains.st4plugin.editor.STQuoteHandler"/>
</extensions>
</idea-plugin>
Loading

0 comments on commit 65df391

Please sign in to comment.