Skip to content

Commit d7cc120

Browse files
committed
added MultiplePsiFilesPerDocumentFileViewProvider impl with lexer and parser
1 parent 4e8e6c2 commit d7cc120

File tree

15 files changed

+792
-0
lines changed

15 files changed

+792
-0
lines changed

build.gradle

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
buildscript {
2+
repositories {
3+
maven { url 'https://jitpack.io'}
4+
}
5+
dependencies{
6+
classpath "com.github.hurricup:gradle-grammar-kit-plugin:2017.1"
7+
}
8+
}
9+
10+
111
plugins {
212
id 'org.jetbrains.intellij' version '0.2.17'
313
}
@@ -8,6 +18,10 @@ version '0.0.4'
818
apply plugin: 'idea'
919
apply plugin: 'org.jetbrains.intellij'
1020
apply plugin: 'java'
21+
apply plugin: 'org.jetbrains.grammarkit'
22+
23+
// import is optional to make task creation easier
24+
import org.jetbrains.grammarkit.tasks.*
1125

1226
sourceCompatibility = 1.8
1327

@@ -29,6 +43,56 @@ intellij {
2943
}
3044
}
3145

46+
sourceSets.main.java.srcDir new File(buildDir, 'generated-src/jflex')
47+
idea {
48+
module {
49+
// Marks the already(!) added srcDir as "generated"
50+
generatedSourceDirs += file('build/generated-src/jflex')
51+
}
52+
}
53+
54+
task generateTopLexer(type: GenerateLexer) {
55+
// source flex file
56+
source = "src/main/jflex/technology/svelte/lexer//svelte_top.flex"
57+
58+
// target directory for lexer
59+
targetDir = "build/generated-src/jflex"
60+
61+
// target classname, target file will be targetDir/targetClass.java
62+
targetClass = "_SvelteTopLexer"
63+
64+
// optional, path to the task-specific skeleton file. Default: none
65+
skeleton = 'src/main/jflex/idea-flex.skeleton'
66+
67+
// if set, plugin will remove a lexer output file before generating new one. Default: false
68+
purgeOldFiles = true
69+
}
70+
71+
task generateSubLexer(type: GenerateLexer) {
72+
// source flex file
73+
source = "src/main/jflex/technology/svelte/lexer//svelte_sub.flex"
74+
75+
// target directory for lexer
76+
targetDir = "build/generated-src/jflex"
77+
78+
// target classname, target file will be targetDir/targetClass.java
79+
targetClass = "_SvelteSubLexer"
80+
81+
// optional, path to the task-specific skeleton file. Default: none
82+
skeleton = 'src/main/jflex/idea-flex.skeleton'
83+
84+
// if set, plugin will remove a lexer output file before generating new one. Default: false
85+
purgeOldFiles = true
86+
}
87+
88+
task generateLexers {
89+
dependsOn generateTopLexer
90+
dependsOn generateSubLexer
91+
}
92+
93+
compileJava {
94+
dependsOn generateLexers
95+
}
3296
//patchPluginXml {
3397
// pluginId "${group}.${project.name}"
3498
// changeNotes """
665 Bytes
Loading
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package technology.svelte;
2+
3+
import com.intellij.openapi.util.IconLoader;
4+
import javax.swing.*;
5+
6+
/**
7+
* Created by IntelliJ IDEA.
8+
* User: juzna
9+
* Date: 1/11/12
10+
* Time: 3:13 AM
11+
* To change this template use File | Settings | File Templates.
12+
*/
13+
14+
public class SvelteIcons {
15+
public static Icon FILETYPE_ICON = IconLoader.getIcon("/icons/svelte.png", SvelteIcons.class);
16+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package technology.svelte.completion;
2+
3+
import com.intellij.codeInsight.completion.CompletionParameters;
4+
import com.intellij.codeInsight.completion.CompletionProvider;
5+
import com.intellij.codeInsight.completion.CompletionResultSet;
6+
import com.intellij.codeInsight.lookup.LookupElementBuilder;
7+
import com.intellij.psi.PsiElement;
8+
import com.intellij.psi.PsiWhiteSpace;
9+
import com.intellij.psi.xml.XmlAttribute;
10+
import com.intellij.psi.xml.XmlElement;
11+
import com.intellij.util.ProcessingContext;
12+
import technology.svelte.lexer.SvelteTokenTypes;
13+
import technology.svelte.psi.impl.MacroNodeImpl;
14+
import org.jetbrains.annotations.NotNull;
15+
16+
import java.util.*;
17+
18+
/**
19+
* Provides keywords to be auto-completed
20+
*/
21+
public class KeywordCompletionProvider extends CompletionProvider<CompletionParameters> {
22+
// constant lists
23+
private static final String[] KEYWORDS = {
24+
// CoreMacros.php
25+
"if", "ifset", "for", "foreach", "while", "first", "last", "sep", "class", "attr", "captute",
26+
"var", "default", "dump", "debugbreak", "l", "r",
27+
28+
// FormMacros.php
29+
"form", "input", "label", "formContainer",
30+
31+
// UIMacros.php
32+
"include", "extends", "block", "define", "snippet", "widget", "control", "href", "link", "plink", "contentType", "status",
33+
};
34+
35+
private static final List<String> PAIR_MACROS = Arrays.asList(
36+
"if", "ifset", "for", "foreach", "while", "first", "last", "sep"
37+
);
38+
39+
private static final String[] FILTERS = {
40+
// static
41+
"normalize", "toascii", "webalize", "truncate", "lower", "upper", "firstupper", "capitalize", "trim", "padleft", "padright",
42+
"replacere", "url", "striptags", "nl2br", "substr", "repeat", "implode", "number",
43+
44+
// methods in Helpers.php
45+
"espaceHtml", "escapeHtmlComment", "escapeXML", "escapeCss", "escapeJs", "strip", "indent", "date", "bytes",
46+
"length", "replace", "dataStream", "null",
47+
};
48+
49+
50+
// CompletionResultSet wants list of LookupElements
51+
private List<LookupElementBuilder> KEYWORD_LOOKUPS = new ArrayList();
52+
private List<LookupElementBuilder> ATTR_LOOKUPS = new ArrayList();
53+
private List<LookupElementBuilder> FILTER_LOOKUPS = new ArrayList();
54+
private HashMap<String, LookupElementBuilder> CLOSING_LOOKUPS = new HashMap<String, LookupElementBuilder>();
55+
56+
public KeywordCompletionProvider() {
57+
super();
58+
59+
for(String keyword: KEYWORDS) {
60+
KEYWORD_LOOKUPS.add(LookupElementBuilder.create(keyword));
61+
ATTR_LOOKUPS.add(LookupElementBuilder.create("n:" + keyword));
62+
}
63+
for(String filter: FILTERS) FILTER_LOOKUPS.add(LookupElementBuilder.create(filter));
64+
}
65+
66+
@Override
67+
protected void addCompletions(@NotNull CompletionParameters params,
68+
ProcessingContext ctx,
69+
@NotNull CompletionResultSet results) {
70+
71+
PsiElement curr = params.getPosition().getOriginalElement();
72+
73+
// n: attributes
74+
if(curr.getParent() instanceof XmlAttribute) {
75+
for(LookupElementBuilder x: ATTR_LOOKUPS) results.addElement(x);
76+
return;
77+
}
78+
79+
// Keywords
80+
if(curr.getNode().getElementType() == SvelteTokenTypes.MACRO_NAME) {
81+
for(LookupElementBuilder x: KEYWORD_LOOKUPS) results.addElement(x);
82+
83+
HashSet<String> openedMacros = new HashSet<String>(); // macros which are opened before (should complete closing)
84+
HashSet<String> closedMacros = new HashSet<String>(); // which are closed (should not complete closing)
85+
86+
// Go up and find open keywords (and offer them for closing)
87+
PsiElement cursor = curr;
88+
while(cursor != null && (cursor.getPrevSibling() != null || cursor.getParent() != null)) {
89+
// macro found {xx} found
90+
if(cursor instanceof MacroNodeImpl) {
91+
MacroNodeImpl node = (MacroNodeImpl) cursor;
92+
String name = node.getMacroName();
93+
94+
if(name.charAt(0) == '/') { // closing macro
95+
closedMacros.add(name.substring(1));
96+
} else if(PAIR_MACROS.contains(name)) {
97+
if(closedMacros.contains(name)) closedMacros.remove(name); // is closed later
98+
else openedMacros.add(name); // not closed, let us close it
99+
}
100+
}
101+
102+
103+
PsiElement tmp;
104+
if((tmp = cursor.getPrevSibling()) != null) cursor = tmp; // Go prev if possible...
105+
else cursor = cursor.getParent(); // ... or up
106+
}
107+
108+
for(String name: openedMacros) {
109+
if(name.equals("if")) {
110+
results.addElement(reuseClosingTag("else"));
111+
results.addElement(reuseClosingTag("elseif"));
112+
}
113+
results.addElement(reuseClosingTag("/" + name));
114+
}
115+
116+
results.stopHere();
117+
}
118+
119+
// Modifiers (if after pipe)
120+
PsiElement prev = curr.getPrevSibling();
121+
if(prev != null && prev instanceof PsiWhiteSpace) prev = prev.getPrevSibling();
122+
if(prev != null && prev.getNode().getElementType() == SvelteTokenTypes.MODIFIER) {
123+
for(LookupElementBuilder x: FILTER_LOOKUPS) results.addElement(x);
124+
results.stopHere();
125+
}
126+
}
127+
128+
// Get closing tag
129+
private LookupElementBuilder reuseClosingTag(String name) {
130+
if(!CLOSING_LOOKUPS.containsKey(name)) {
131+
CLOSING_LOOKUPS.put(name, LookupElementBuilder.create(name));
132+
}
133+
134+
return CLOSING_LOOKUPS.get(name);
135+
}
136+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package technology.svelte.completion;
2+
3+
import com.intellij.codeInsight.completion.CompletionContributor;
4+
import com.intellij.codeInsight.completion.CompletionType;
5+
import com.intellij.openapi.diagnostic.Logger;
6+
import com.intellij.patterns.StandardPatterns;
7+
import com.intellij.psi.PsiElement;
8+
9+
/**
10+
* @author Jan Dolecek - [email protected]
11+
*/
12+
public class SvelteCompletionContributor extends CompletionContributor {
13+
private static final Logger log = Logger.getInstance("SvelteCompletionContributor");
14+
15+
16+
public SvelteCompletionContributor() {
17+
log.info("Created Svelte completion contributor");
18+
19+
extend(CompletionType.BASIC, StandardPatterns.instanceOf(PsiElement.class), new KeywordCompletionProvider());
20+
}
21+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package technology.svelte.parser;
2+
3+
import com.intellij.lang.ASTNode;
4+
import com.intellij.lang.PsiBuilder;
5+
import com.intellij.lang.PsiBuilder.Marker;
6+
import com.intellij.lang.PsiParser;
7+
import com.intellij.psi.tree.IElementType;
8+
import technology.svelte.lexer.SvelteTokenTypes;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
public class SvelteParser implements PsiParser {
12+
@NotNull
13+
@Override
14+
public ASTNode parse(IElementType root, PsiBuilder builder) {
15+
Marker marker = builder.mark();
16+
17+
// Process all tokens
18+
while(!builder.eof()) {
19+
IElementType type = builder.getTokenType();
20+
21+
if(type == SvelteTokenTypes.OPENING) parseMacro(builder);
22+
else if(type == SvelteTokenTypes.N_ATTR) parseNAttr(builder);
23+
24+
builder.advanceLexer(); // move to next token
25+
}
26+
27+
marker.done(root);
28+
return builder.getTreeBuilt();
29+
}
30+
31+
// {macro ...}
32+
private void parseMacro(PsiBuilder builder) {
33+
Marker macroStart = builder.mark();
34+
builder.advanceLexer();
35+
36+
// is there a name?
37+
String tagName = null;
38+
if(builder.getTokenType() == SvelteTokenTypes.MACRO_NAME) {
39+
Marker macroNameMark = builder.mark();
40+
tagName = builder.getTokenText();
41+
builder.advanceLexer();
42+
macroNameMark.done(SvelteTokenTypes.MACRO_NAME);
43+
}
44+
45+
// params
46+
Marker paramsMark = builder.mark();
47+
parseParams(tagName, builder, SvelteTokenTypes.CLOSING);
48+
paramsMark.done(SvelteTokenTypes.PARAMS);
49+
50+
// finish him
51+
if(builder.getTokenType() == SvelteTokenTypes.CLOSING) {
52+
builder.advanceLexer();
53+
}
54+
macroStart.done(SvelteTokenTypes.MACRO_NODE);
55+
}
56+
57+
// n:link="something"
58+
// n:link=something
59+
private void parseNAttr(PsiBuilder builder) {
60+
Marker start = builder.mark();
61+
builder.advanceLexer();
62+
63+
// Process name
64+
String attrName = null;
65+
if(builder.getTokenType() == SvelteTokenTypes.ATTR_NAME) {
66+
Marker macroName = builder.mark();
67+
attrName = "@" + builder.getTokenText();
68+
builder.advanceLexer();
69+
macroName.done(SvelteTokenTypes.MACRO_NAME);
70+
}
71+
72+
if(builder.getTokenType() == SvelteTokenTypes.N_ATTR_EQ) builder.advanceLexer();
73+
74+
boolean inQuotes;
75+
if(builder.getTokenType() == SvelteTokenTypes.N_QUOTE) {
76+
inQuotes = true;
77+
builder.advanceLexer();
78+
} else inQuotes = false;
79+
80+
81+
// Process value
82+
Marker value = builder.mark();
83+
parseParams(attrName, builder, inQuotes ? SvelteTokenTypes.N_QUOTE : SvelteTokenTypes.TEMPLATE_HTML_TEXT);
84+
value.done(SvelteTokenTypes.PARAMS);
85+
86+
if(inQuotes && builder.getTokenType() == SvelteTokenTypes.N_QUOTE) {
87+
builder.advanceLexer();
88+
}
89+
90+
start.done(SvelteTokenTypes.MACRO_ATTR);
91+
}
92+
93+
// custom params
94+
private void parseParams(String macroName, PsiBuilder builder, IElementType closing) {
95+
// just process it atm
96+
while(builder.getTokenType() != closing && !builder.eof()) {
97+
builder.advanceLexer();
98+
}
99+
100+
}
101+
}

0 commit comments

Comments
 (0)