├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── JFlex.jar ├── LICENCE.txt ├── META-INF └── plugin.xml ├── README.md ├── idea-flex.skeleton ├── pycharm-pyxl.iml ├── pycharm-pyxl.jar ├── src └── com │ └── christofferklang │ └── pyxl │ ├── PyxlColorSettingsPage.java │ ├── PyxlDialectTokenContributor.java │ ├── PyxlElementTypes.java │ ├── PyxlFormattingModelBuilder.java │ ├── PyxlHighlighter.java │ ├── PyxlHighlighterColors.java │ ├── PyxlHighlighterFactory.java │ ├── PyxlModuleReference.java │ ├── PyxlTokenTypes.java │ ├── parsing │ ├── Pyxl.flex │ ├── PyxlExpressionParsing.java │ ├── PyxlHighlightingLexer.java │ ├── PyxlIndentingLexer.java │ ├── PyxlLexer.java │ ├── PyxlParser.java │ ├── PyxlParserDefinition.java │ └── PyxlParsingContext.java │ └── psi │ ├── PythonClassReference.java │ ├── PyxlArgumentList.java │ ├── PyxlAttrName.java │ └── PyxlTag.java ├── testdata ├── Attributes.py ├── Attributes.txt ├── Comments.py ├── Comments.txt ├── ParsingTestData.py ├── ParsingTestData.txt ├── TagNames.py ├── TagNames.txt ├── WithStatements.py ├── WithStatements.txt ├── class_self_ref.py ├── class_self_ref.txt ├── nestedembed.py └── nestedembed.txt └── tests └── PyxlParsingTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | 3 | # This is recommended idea ignores (http://stackoverflow.com/questions/11968531) 4 | **/.idea/workspace.xml 5 | **/.idea/tasks.xml 6 | 7 | .DS_Store 8 | src/com/christofferklang/pyxl/parsing/_PyxlLexer.java 9 | *.java~ 10 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | pycharm-pyxl -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Class structureJava 15 | 16 | 17 | Code maturity issuesJava 18 | 19 | 20 | Java 21 | 22 | 23 | Java language level migration aidsJava 24 | 25 | 26 | Javadoc issuesJava 27 | 28 | 29 | Performance issuesJava 30 | 31 | 32 | TestNGJava 33 | 34 | 35 | Threading issuesJava 36 | 37 | 38 | 39 | 40 | Android 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /JFlex.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoffer/pycharm-pyxl/2c7cbe56196307923aa0c4eb8fd54aa067e16d85/JFlex.jar -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Christoffer Klang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.christofferklang.pyxl 3 | Pyxl Support 4 | 1.2 5 | Christoffer Klang 6 | 7 | Pyxl files. 9 |
10 | Created by Nils Bunger (nils@dropbox.com), Robert Kajic (kajic@dropbox.com) and Christoffer Klang (christoffer@dropbox.com). 11 |
12 | Please report any bugs or missing features to https://github.com/christoffer/pycharm-pyxl, or email any of the creators. 13 |
14 | Requires PyCharm or the Python plugin. 15 | ]]>
16 | 17 | 19 |
  • IDEA 2016.1 support
  • 20 | 21 | ]]> 22 |
    23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | com.intellij.modules.python 63 |
    64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pyxl Extensions for PyCharm 2 | =========================== 3 | 4 | This PyCharm plugin aims to provide extensions to PyCharm for working with [Pyxl](https://github.com/dropbox/pyxl) files. 5 | 6 | Written by [Nils Bunger](https://github.com/nilsbunger), [Robert Kajic](https://github.com/kajic) and [Christoffer Klang](https://github.com/christoffer). 7 | 8 | Installation 9 | ============ 10 | 11 | Download the [plugin jar](/pycharm-pyxl.jar?raw=true) and switch over to `PyCharm > Settings > Plugins > Install plugin from disk...` and select the Jar. 12 | 13 | ## Release 1.1 14 | - Performance fixes 15 | - Add proper support for language level Python syntax (`with as:`, etc). 16 | - Fix xml namespaced attributes 17 | - Add support for `` and `` 18 | 19 | ## Release 1.2 20 | - PyCharm 2016.1 support 21 | 22 | Development 23 | =========== 24 | 25 | ### 1. Set up plugin development environment 26 | Follow [helful guide from Jetbrains](http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/setting_up_environment.html) 27 | 28 | ### 2. Git clone this repo. 29 | 30 | ### 3. Generate lexer file using provided JFlex (provided in repo root): 31 | ``` 32 | $ java -jar JFlex.jar --skel idea-flex.skeleton --charat src/com/christofferklang/pyxl/parsing/Pyxl.flex 33 | ``` 34 | 35 | ### 4. Add Python plugin dependency 36 | First install the Python plugin in IntelliJ (`Settings > Plugins`). Then locate `python.jar` in your home directory. The exact location will vary depending on OS and IntelliJ version. For Idea 2016.2 on Linux for example, the file can be found in `$HOME/.IntelliJIdea2016.2/config/plugins/python/lib`. On OSX it would be in ``$HOME/Library/Application Support/IntelliJIdea2016.2/python/lib`. You'll probably figure it out by running `find . -name 'python.jar' | grep python/lib` in your home directory. 37 | 38 | Once located, you need to add the jar file as a dependency to the project. 39 | `File > Project Structure... > Modules > pycharm-pyxl > Dependencies` 40 | (Click the green + to add a new dependency) 41 | `> `JARs or directories` (locate python.jar). 42 | 43 | Make sure the Python JAR is above ``, and that the scope is set to "Provided". 44 | 45 | ### 5. Open this project in IDEA 46 | Then right-click on pycharm-pyxl project and choose "Prepare plugin module for development" 47 | at the bottom of context menu. This will generate new jar in the project root which you can add to your PyCharm. 48 | 49 | ### 6. Pro-tip - you can launch PyCharm in debug mode to actually debug the plugin. 50 | -------------------------------------------------------------------------------- /idea-flex.skeleton: -------------------------------------------------------------------------------- 1 | /** initial size of the lookahead buffer */ 2 | --- private static final int ZZ_BUFFERSIZE = ...; 3 | 4 | /** lexical states */ 5 | --- lexical states, charmap 6 | 7 | /* error codes */ 8 | private static final int ZZ_UNKNOWN_ERROR = 0; 9 | private static final int ZZ_NO_MATCH = 1; 10 | private static final int ZZ_PUSHBACK_2BIG = 2; 11 | private static final char[] EMPTY_BUFFER = new char[0]; 12 | private static final int YYEOF = -1; 13 | private static java.io.Reader zzReader = null; // Fake 14 | 15 | /* error messages for the codes above */ 16 | private static final String ZZ_ERROR_MSG[] = { 17 | "Unkown internal scanner error", 18 | "Error: could not match input", 19 | "Error: pushback value was too large" 20 | }; 21 | 22 | --- isFinal list 23 | /** the current state of the DFA */ 24 | private int zzState; 25 | 26 | /** the current lexical state */ 27 | private int zzLexicalState = YYINITIAL; 28 | 29 | /** this buffer contains the current text to be matched and is 30 | the source of the yytext() string */ 31 | private CharSequence zzBuffer = ""; 32 | 33 | /** this buffer may contains the current text array to be matched when it is cheap to acquire it */ 34 | private char[] zzBufferArray; 35 | 36 | /** the textposition at the last accepting state */ 37 | private int zzMarkedPos; 38 | 39 | /** the textposition at the last state to be included in yytext */ 40 | private int zzPushbackPos; 41 | 42 | /** the current text position in the buffer */ 43 | private int zzCurrentPos; 44 | 45 | /** startRead marks the beginning of the yytext() string in the buffer */ 46 | private int zzStartRead; 47 | 48 | /** endRead marks the last character in the buffer, that has been read 49 | from input */ 50 | private int zzEndRead; 51 | 52 | /** 53 | * zzAtBOL == true <=> the scanner is currently at the beginning of a line 54 | */ 55 | private boolean zzAtBOL = true; 56 | 57 | /** zzAtEOF == true <=> the scanner is at the EOF */ 58 | private boolean zzAtEOF; 59 | 60 | --- user class code 61 | 62 | --- constructor declaration 63 | 64 | public final int getTokenStart(){ 65 | return zzStartRead; 66 | } 67 | 68 | public final int getTokenEnd(){ 69 | return getTokenStart() + yylength(); 70 | } 71 | 72 | public void reset(CharSequence buffer, int start, int end,int initialState){ 73 | zzBuffer = buffer; 74 | zzBufferArray = com.intellij.util.text.CharArrayUtil.fromSequenceWithoutCopying(buffer); 75 | zzCurrentPos = zzMarkedPos = zzStartRead = start; 76 | zzPushbackPos = 0; 77 | zzAtEOF = false; 78 | zzAtBOL = true; 79 | zzEndRead = end; 80 | yybegin(initialState); 81 | } 82 | 83 | /** 84 | * Refills the input buffer. 85 | * 86 | * @return false, iff there was new input. 87 | * 88 | * @exception java.io.IOException if any I/O-Error occurs 89 | */ 90 | private boolean zzRefill() throws java.io.IOException { 91 | return true; 92 | } 93 | 94 | 95 | /** 96 | * Returns the current lexical state. 97 | */ 98 | public final int yystate() { 99 | return zzLexicalState; 100 | } 101 | 102 | 103 | /** 104 | * Enters a new lexical state 105 | * 106 | * @param newState the new lexical state 107 | */ 108 | public final void yybegin(int newState) { 109 | zzLexicalState = newState; 110 | } 111 | 112 | 113 | /** 114 | * Returns the text matched by the current regular expression. 115 | */ 116 | public final CharSequence yytext() { 117 | return zzBuffer.subSequence(zzStartRead, zzMarkedPos); 118 | } 119 | 120 | 121 | /** 122 | * Returns the character at position pos from the 123 | * matched text. 124 | * 125 | * It is equivalent to yytext().charAt(pos), but faster 126 | * 127 | * @param pos the position of the character to fetch. 128 | * A value from 0 to yylength()-1. 129 | * 130 | * @return the character at position pos 131 | */ 132 | public final char yycharat(int pos) { 133 | return zzBufferArray != null ? zzBufferArray[zzStartRead+pos]:zzBuffer.charAt(zzStartRead+pos); 134 | } 135 | 136 | 137 | /** 138 | * Returns the length of the matched text region. 139 | */ 140 | public final int yylength() { 141 | return zzMarkedPos-zzStartRead; 142 | } 143 | 144 | 145 | /** 146 | * Reports an error that occured while scanning. 147 | * 148 | * In a wellformed scanner (no or only correct usage of 149 | * yypushback(int) and a match-all fallback rule) this method 150 | * will only be called with things that "Can't Possibly Happen". 151 | * If this method is called, something is seriously wrong 152 | * (e.g. a JFlex bug producing a faulty scanner etc.). 153 | * 154 | * Usual syntax/scanner level error handling should be done 155 | * in error fallback rules. 156 | * 157 | * @param errorCode the code of the errormessage to display 158 | */ 159 | --- zzScanError declaration 160 | String message; 161 | try { 162 | message = ZZ_ERROR_MSG[errorCode]; 163 | } 164 | catch (ArrayIndexOutOfBoundsException e) { 165 | message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; 166 | } 167 | 168 | --- throws clause 169 | } 170 | 171 | 172 | /** 173 | * Pushes the specified amount of characters back into the input stream. 174 | * 175 | * They will be read again by then next call of the scanning method 176 | * 177 | * @param number the number of characters to be read again. 178 | * This number must not be greater than yylength()! 179 | */ 180 | --- yypushback decl (contains zzScanError exception) 181 | if ( number > yylength() ) 182 | zzScanError(ZZ_PUSHBACK_2BIG); 183 | 184 | zzMarkedPos -= number; 185 | } 186 | 187 | 188 | --- zzDoEOF 189 | /** 190 | * Resumes scanning until the next regular expression is matched, 191 | * the end of input is encountered or an I/O-Error occurs. 192 | * 193 | * @return the next token 194 | * @exception java.io.IOException if any I/O-Error occurs 195 | */ 196 | --- yylex declaration 197 | int zzInput; 198 | int zzAction; 199 | 200 | // cached fields: 201 | int zzCurrentPosL; 202 | int zzMarkedPosL; 203 | int zzEndReadL = zzEndRead; 204 | CharSequence zzBufferL = zzBuffer; 205 | char[] zzBufferArrayL = zzBufferArray; 206 | char [] zzCMapL = ZZ_CMAP; 207 | 208 | --- local declarations 209 | 210 | while (true) { 211 | zzMarkedPosL = zzMarkedPos; 212 | 213 | --- start admin (line, char, col count) 214 | zzAction = -1; 215 | 216 | zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; 217 | 218 | --- start admin (lexstate etc) 219 | 220 | zzForAction: { 221 | while (true) { 222 | 223 | --- next input, line, col, char count, next transition, isFinal action 224 | zzAction = zzState; 225 | zzMarkedPosL = zzCurrentPosL; 226 | --- line count update 227 | } 228 | 229 | } 230 | } 231 | 232 | // store back cached position 233 | zzMarkedPos = zzMarkedPosL; 234 | --- char count update 235 | 236 | --- actions 237 | default: 238 | if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { 239 | zzAtEOF = true; 240 | --- eofvalue 241 | } 242 | else { 243 | --- no match 244 | } 245 | } 246 | } 247 | } 248 | 249 | --- main 250 | 251 | } 252 | -------------------------------------------------------------------------------- /pycharm-pyxl.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /pycharm-pyxl.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoffer/pycharm-pyxl/2c7cbe56196307923aa0c4eb8fd54aa067e16d85/pycharm-pyxl.jar -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlColorSettingsPage.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.intellij.openapi.editor.colors.TextAttributesKey; 4 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 5 | import com.intellij.openapi.options.colors.AttributesDescriptor; 6 | import com.intellij.openapi.options.colors.ColorDescriptor; 7 | import com.intellij.openapi.options.colors.ColorSettingsPage; 8 | import com.jetbrains.python.PythonFileType; 9 | import com.jetbrains.python.psi.LanguageLevel; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import javax.swing.*; 14 | import java.util.Map; 15 | 16 | public class PyxlColorSettingsPage implements ColorSettingsPage { 17 | private static final AttributesDescriptor[] COLOR_ATTRIBUTES = new AttributesDescriptor[]{ 18 | new AttributesDescriptor("Tag name", PyxlHighlighterColors.PYXL_TAG_NAME), 19 | new AttributesDescriptor("Tag", PyxlHighlighterColors.PYXL_TAG), 20 | new AttributesDescriptor("Attribute name", PyxlHighlighterColors.PYXL_ATTRIBUTE_NAME), 21 | new AttributesDescriptor("Attribute value", PyxlHighlighterColors.PYXL_ATTRIBUTE_VALUE), 22 | new AttributesDescriptor("Embedded Python", PyxlHighlighterColors.PYXL_EMBEDDED), 23 | }; 24 | 25 | @Nullable 26 | @Override 27 | public Icon getIcon() { 28 | return PythonFileType.INSTANCE.getIcon(); 29 | } 30 | 31 | @NotNull 32 | @Override 33 | public SyntaxHighlighter getHighlighter() { 34 | return new PyxlHighlighter(LanguageLevel.getDefault()); 35 | } 36 | 37 | @NotNull 38 | @Override 39 | public String getDemoText() { 40 | return "# coding: pyxl\n" + 41 | "\n" + 42 | "from pyxl import html\n" + 43 | "\n" + 44 | "def generate():\n" + 45 | " markup = (\n" + 46 | " \n" + 47 | " \n" + 48 | " \n" + 49 | " Hello {user.get_name()}!\n" + 50 | " \n" + 51 | " \n" + 52 | " )\n" + 53 | " return
    {markup}
    "; 54 | } 55 | 56 | @Nullable 57 | @Override 58 | public Map getAdditionalHighlightingTagToDescriptorMap() { 59 | return null; 60 | } 61 | 62 | @NotNull 63 | @Override 64 | public AttributesDescriptor[] getAttributeDescriptors() { 65 | return COLOR_ATTRIBUTES; 66 | } 67 | 68 | @NotNull 69 | @Override 70 | public ColorDescriptor[] getColorDescriptors() { 71 | return ColorDescriptor.EMPTY_ARRAY; 72 | } 73 | 74 | @NotNull 75 | @Override 76 | public String getDisplayName() { 77 | return "Pyxl Colors"; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlDialectTokenContributor.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.intellij.psi.tree.TokenSet; 4 | import com.jetbrains.python.PythonDialectsTokenSetContributor; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | public class PyxlDialectTokenContributor implements PythonDialectsTokenSetContributor { 8 | @NotNull 9 | @Override 10 | public TokenSet getStatementTokens() { 11 | return TokenSet.EMPTY; 12 | } 13 | 14 | @NotNull 15 | @Override 16 | public TokenSet getExpressionTokens() { 17 | return TokenSet.create(PyxlElementTypes.TAG, PyxlElementTypes.MODULE_REFERENCE); 18 | } 19 | 20 | @NotNull 21 | @Override 22 | public TokenSet getKeywordTokens() { 23 | return TokenSet.EMPTY; 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public TokenSet getParameterTokens() { 29 | return TokenSet.EMPTY; 30 | } 31 | 32 | @NotNull 33 | @Override 34 | public TokenSet getFunctionDeclarationTokens() { 35 | return TokenSet.EMPTY; 36 | } 37 | 38 | @NotNull 39 | @Override 40 | public TokenSet getUnbalancedBracesRecoveryTokens() { 41 | return TokenSet.EMPTY; 42 | } 43 | 44 | @NotNull 45 | @Override 46 | public TokenSet getReferenceExpressionTokens() { 47 | return TokenSet.EMPTY; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlElementTypes.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.christofferklang.pyxl.psi.PythonClassReference; 4 | import com.christofferklang.pyxl.psi.PyxlArgumentList; 5 | import com.christofferklang.pyxl.psi.PyxlAttrName; 6 | import com.christofferklang.pyxl.psi.PyxlTag; 7 | import com.intellij.psi.tree.IElementType; 8 | import com.jetbrains.python.psi.PyElementType; 9 | 10 | public class PyxlElementTypes { 11 | public static IElementType TAG_REFERENCE = 12 | new PyElementType("TAG_REFERENCE", PythonClassReference.class); 13 | 14 | public static IElementType TAG = 15 | new PyElementType("TAG", PyxlTag.class); 16 | 17 | public static IElementType ATTRNAME = 18 | new PyElementType("ATTRNAME", PyxlAttrName.class); 19 | 20 | public static IElementType ARGUMENT_LIST = 21 | new PyElementType("PYXL_ARGUMENT_LIST", PyxlArgumentList.class); 22 | 23 | public static IElementType MODULE_REFERENCE = new PyElementType("MODULE_REFERENCE", PyxlModuleReference.class); 24 | } 25 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlFormattingModelBuilder.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | import com.intellij.formatting.SpacingBuilder; 3 | import com.intellij.psi.codeStyle.CodeStyleSettings; 4 | import com.jetbrains.python.formatter.PythonFormattingModelBuilder; 5 | 6 | public class PyxlFormattingModelBuilder extends PythonFormattingModelBuilder { 7 | 8 | //we should be able to add Pyxl indentation information here. See also 9 | // http://confluence.jetbrains.com/display/IDEADEV/Developing+Custom+Language+Plugins+for+IntelliJ+IDEA#DevelopingCustomLanguagePluginsforIntelliJIDEA-CodeFormatter 10 | 11 | public PyxlFormattingModelBuilder() { 12 | super(); 13 | } 14 | 15 | 16 | @Override 17 | protected SpacingBuilder createSpacingBuilder(CodeStyleSettings settings) { 18 | SpacingBuilder foo = super.createSpacingBuilder(settings); 19 | return foo; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlHighlighter.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.christofferklang.pyxl.parsing.PyxlHighlightingLexer; 4 | import com.intellij.lexer.Lexer; 5 | import com.intellij.openapi.editor.colors.TextAttributesKey; 6 | import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; 7 | import com.intellij.psi.tree.IElementType; 8 | import com.jetbrains.python.highlighting.PyHighlighter; 9 | import com.jetbrains.python.psi.LanguageLevel; 10 | import gnu.trove.THashMap; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.Map; 14 | 15 | class PyxlHighlighter extends PyHighlighter { 16 | private static final Map keys1; 17 | private static final Map keys2; 18 | private final LanguageLevel myLanguageLevel; 19 | 20 | public PyxlHighlighter(LanguageLevel languageLevel) { 21 | super(languageLevel); 22 | myLanguageLevel = languageLevel; 23 | } 24 | 25 | @NotNull 26 | @Override 27 | public Lexer getHighlightingLexer() { 28 | return new PyxlHighlightingLexer(myLanguageLevel); 29 | } 30 | 31 | static { 32 | keys1 = new THashMap(); 33 | keys2 = new THashMap(); 34 | 35 | keys1.put(PyxlTokenTypes.TAGBEGIN, PyxlHighlighterColors.PYXL_TAG); 36 | keys1.put(PyxlTokenTypes.TAGCLOSE, PyxlHighlighterColors.PYXL_TAG); 37 | keys1.put(PyxlTokenTypes.TAGEND, PyxlHighlighterColors.PYXL_TAG); 38 | keys1.put(PyxlTokenTypes.TAGENDANDCLOSE, PyxlHighlighterColors.PYXL_TAG); 39 | 40 | keys1.put(PyxlElementTypes.TAG, PyxlHighlighterColors.PYXL_EMBEDDED); 41 | 42 | keys1.put(PyxlTokenTypes.TAGNAME, PyxlHighlighterColors.PYXL_TAG_NAME); 43 | keys1.put(PyxlTokenTypes.TAGNAME_MODULE, PyxlHighlighterColors.PYXL_TAG_NAME); 44 | keys1.put(PyxlTokenTypes.BUILT_IN_TAG, PyxlHighlighterColors.PYXL_TAG_NAME); 45 | 46 | keys1.put(PyxlTokenTypes.ATTRNAME, PyxlHighlighterColors.PYXL_ATTRIBUTE_NAME); 47 | keys1.put(PyxlTokenTypes.ATTRVALUE, PyxlHighlighterColors.PYXL_ATTRIBUTE_VALUE); 48 | } 49 | 50 | @NotNull 51 | @Override 52 | public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { 53 | TextAttributesKey[] defaultTextAttributeKeys, extendedHighlighterKeys; 54 | 55 | defaultTextAttributeKeys = super.getTokenHighlights(tokenType); 56 | extendedHighlighterKeys = SyntaxHighlighterBase.pack(keys1.get(tokenType), keys2.get(tokenType)); 57 | 58 | int numKeys = defaultTextAttributeKeys.length + extendedHighlighterKeys.length; 59 | TextAttributesKey[] merged = new TextAttributesKey[numKeys]; 60 | 61 | System.arraycopy(defaultTextAttributeKeys, 0, merged, 0, defaultTextAttributeKeys.length); 62 | System.arraycopy(extendedHighlighterKeys, 63 | 0, merged, 64 | defaultTextAttributeKeys.length, 65 | extendedHighlighterKeys.length); 66 | 67 | return merged; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlHighlighterColors.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; 4 | import com.intellij.openapi.editor.XmlHighlighterColors; 5 | import com.intellij.openapi.editor.colors.TextAttributesKey; 6 | 7 | public class PyxlHighlighterColors { 8 | private PyxlHighlighterColors() { 9 | // Prevent instantiation 10 | } 11 | 12 | public static final TextAttributesKey PYXL_TAG_NAME = TextAttributesKey.createTextAttributesKey("PYXL_TAG_NAME", XmlHighlighterColors.HTML_TAG_NAME); 13 | public static final TextAttributesKey PYXL_TAG = TextAttributesKey.createTextAttributesKey("PYXL_TAG", XmlHighlighterColors.HTML_TAG); 14 | public static final TextAttributesKey PYXL_ATTRIBUTE_NAME = TextAttributesKey.createTextAttributesKey("PYXL_ATTRIBUTE_NAME", XmlHighlighterColors.HTML_ATTRIBUTE_NAME); 15 | public static final TextAttributesKey PYXL_ATTRIBUTE_VALUE = TextAttributesKey.createTextAttributesKey("PYXL_ATTRIBUTE_VALUE", XmlHighlighterColors.HTML_ATTRIBUTE_VALUE); 16 | public static final TextAttributesKey PYXL_EMBEDDED = TextAttributesKey.createTextAttributesKey("PYXL_EMBEDDED", DefaultLanguageHighlighterColors.TEMPLATE_LANGUAGE_COLOR); 17 | } 18 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlHighlighterFactory.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.intellij.openapi.fileTypes.SyntaxHighlighter; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.jetbrains.python.highlighting.PySyntaxHighlighterFactory; 7 | import com.jetbrains.python.psi.LanguageLevel; 8 | import com.jetbrains.python.psi.PyFile; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class PyxlHighlighterFactory extends PySyntaxHighlighterFactory { 12 | @NotNull 13 | @Override 14 | public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) { 15 | return new PyxlHighlighter(getLanguageLevelForFile(virtualFile)); 16 | } 17 | 18 | private LanguageLevel getLanguageLevelForFile(VirtualFile virtualFile) { 19 | if (virtualFile instanceof PyFile) { 20 | return ((PyFile) virtualFile).getLanguageLevel(); 21 | } 22 | return LanguageLevel.getDefault(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlModuleReference.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | public class PyxlModuleReference extends PyReferenceExpressionImpl { 8 | public PyxlModuleReference(ASTNode astNode) { 9 | super(astNode); 10 | } 11 | 12 | @Nullable 13 | @Override 14 | public ASTNode getNameElement() { 15 | return getNode().findChildByType(PyxlTokenTypes.TAGNAME_MODULE); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/PyxlTokenTypes.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl; 2 | 3 | import com.jetbrains.python.psi.PyElementType; 4 | 5 | public class PyxlTokenTypes { 6 | private PyxlTokenTypes() { 7 | // Prevent instantiation 8 | } 9 | 10 | public static final PyElementType BADCHAR = new PyElementType("PYXL BAD CHAR"); 11 | public static final PyElementType ATTRNAME = new PyElementType("PYXL ATTRNAME"); 12 | public static final PyElementType ATTRVALUE = new PyElementType("PYXL ATTRVALUE"); 13 | public static final PyElementType ATTRVALUE_START = new PyElementType("PYXL ATTRVALUE BEGIN"); 14 | public static final PyElementType ATTRVALUE_END = new PyElementType("PYXL ATTRVALUE END"); 15 | 16 | public static final PyElementType TAGBEGIN = new PyElementType("PYXL TAGBEGIN <"); 17 | public static final PyElementType TAGNAME_MODULE = new PyElementType("PYXL TAGNAME_MODULE"); 18 | public static final PyElementType TAGNAME = new PyElementType("PYXL TAGNAME"); 19 | public static final PyElementType TAGEND = new PyElementType("PYXL TAGEND >"); 20 | public static final PyElementType TAGCLOSE = new PyElementType("PYXL TAGCLOSE "); 23 | 24 | public static final PyElementType BUILT_IN_TAG = new PyElementType("PYXL BUILT IN TAG"); 25 | 26 | public static final PyElementType EMBED_START = new PyElementType("PYXL PYTHON EMBED BEGIN {"); 27 | public static final PyElementType EMBED_END = new PyElementType("PYXL PYTHON EMBED END }"); 28 | public static final PyElementType STRING = new PyElementType("PYXL STRING"); 29 | } 30 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/Pyxl.flex: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.intellij.lexer.FlexLexer; 4 | import com.intellij.psi.tree.IElementType; 5 | import com.jetbrains.python.PyTokenTypes; 6 | import com.christofferklang.pyxl.PyxlTokenTypes; 7 | import com.intellij.openapi.util.text.StringUtil; 8 | import java.util.Stack; 9 | import com.intellij.psi.tree.IElementType; 10 | 11 | 12 | // NOTE: JFlex lexer file is defined in http://www.jflex.de/manual.pdf 13 | 14 | %% 15 | // %debug uncomment for verbose output from the lexer 16 | %class _PyxlLexer 17 | %implements FlexLexer 18 | %unicode 19 | %function advance 20 | %type IElementType 21 | 22 | %eof{ return; 23 | %eof} 24 | 25 | DIGIT = [0-9] 26 | NONZERODIGIT = [1-9] 27 | OCTDIGIT = [0-7] 28 | HEXDIGIT = [0-9A-Fa-f] 29 | BINDIGIT = [01] 30 | 31 | HEXINTEGER = 0[Xx]({HEXDIGIT})+ 32 | OCTINTEGER = 0[Oo]?({OCTDIGIT})+ 33 | BININTEGER = 0[Bb]({BINDIGIT})+ 34 | DECIMALINTEGER = (({NONZERODIGIT}({DIGIT})*)|0) 35 | INTEGER = {DECIMALINTEGER}|{OCTINTEGER}|{HEXINTEGER}|{BININTEGER} 36 | LONGINTEGER = {INTEGER}[Ll] 37 | 38 | END_OF_LINE_COMMENT="#"[^\r\n]* 39 | PYXL_ENCODING_STRING = "#"{S}"coding:"{S}[^\n\r]* 40 | IDENT_START = [a-zA-Z_]|[:unicode_uppercase_letter:]|[:unicode_lowercase_letter:]|[:unicode_titlecase_letter:]|[:unicode_modifier_letter:]|[:unicode_other_letter:]|[:unicode_letter_number:] 41 | IDENT_CONTINUE = [a-zA-Z0-9_]|[:unicode_uppercase_letter:]|[:unicode_lowercase_letter:]|[:unicode_titlecase_letter:]|[:unicode_modifier_letter:]|[:unicode_other_letter:]|[:unicode_letter_number:]|[:unicode_non_spacing_mark:]|[:unicode_combining_spacing_mark:]|[:unicode_decimal_digit_number:]|[:unicode_connector_punctuation:] 42 | IDENTIFIER = {IDENT_START}{IDENT_CONTINUE}** 43 | 44 | FLOATNUMBER=({POINTFLOAT})|({EXPONENTFLOAT}) 45 | POINTFLOAT=(({INTPART})?{FRACTION})|({INTPART}\.) 46 | EXPONENTFLOAT=(({INTPART})|({POINTFLOAT})){EXPONENT} 47 | INTPART = ({DIGIT})+ 48 | FRACTION = \.({DIGIT})+ 49 | EXPONENT = [eE][+\-]?({DIGIT})+ 50 | 51 | IMAGNUMBER=(({FLOATNUMBER})|({INTPART}))[Jj] 52 | 53 | SINGLE_QUOTED_STRING=[UuBbCcRr]{0,2}({QUOTED_LITERAL} | {DOUBLE_QUOTED_LITERAL}) 54 | TRIPLE_QUOTED_STRING=[UuBbCcRr]{0,2}[UuBbCcRr]?({TRIPLE_QUOTED_LITERAL}|{TRIPLE_APOS_LITERAL}) 55 | 56 | DOCSTRING_LITERAL=({SINGLE_QUOTED_STRING}|{TRIPLE_QUOTED_STRING}) 57 | 58 | QUOTED_LITERAL="'" ([^\\\'\r\n] | {ESCAPE_SEQUENCE} | (\\[\r\n]))* ("'"|\\)? 59 | DOUBLE_QUOTED_LITERAL=\"([^\\\"\r\n]|{ESCAPE_SEQUENCE}|(\\[\r\n]))*?(\"|\\)? 60 | ESCAPE_SEQUENCE=\\[^\r\n] 61 | 62 | ANY_ESCAPE_SEQUENCE = \\[^] 63 | 64 | THREE_QUO = (\"\"\") 65 | ONE_TWO_QUO = (\"[^\"]) | (\"\\[^]) | (\"\"[^\"]) | (\"\"\\[^]) 66 | QUO_STRING_CHAR = [^\\\"] | {ANY_ESCAPE_SEQUENCE} | {ONE_TWO_QUO} 67 | TRIPLE_QUOTED_LITERAL = {THREE_QUO} {QUO_STRING_CHAR}* {THREE_QUO}? 68 | 69 | THREE_APOS = (\'\'\') 70 | ONE_TWO_APOS = ('[^']) | ('\\[^]) | (''[^']) | (''\\[^]) 71 | APOS_STRING_CHAR = [^\\'] | {ANY_ESCAPE_SEQUENCE} | {ONE_TWO_APOS} 72 | TRIPLE_APOS_LITERAL = {THREE_APOS} {APOS_STRING_CHAR}* {THREE_APOS}? 73 | 74 | 75 | S = [\ \t\n]* 76 | PYXL_TAGNAME = {IDENT_START}[a-zA-Z0-9_]** 77 | _ATTR_PART = {IDENT_START}[a-zA-Z0-9_-]** 78 | PYXL_ATTRNAME = ({_ATTR_PART}":")?{_ATTR_PART}** 79 | PYXL_PRE_OP = [\=\(\[\{,\:\>] // matching tokenizer.py in dropbox's pyxl parser 80 | PYXL_PRE_KEYWD = (print|else|yield|return) 81 | PYXL_TAG_COMING = ({PYXL_PRE_OP}|{PYXL_PRE_KEYWD}){S}"<" 82 | PYXL_TAGCLOSE = "" 83 | PYXL_COMMENT = "" 84 | PYXL_DOCTYPE = "])* ">" 85 | PYXL_CDATA = "]))* "]]>" 86 | 87 | // attribute value insides. Includes support for line-continuation both by keeping quotes open and using '\' marker 88 | // at EOL. That seems strange but i've seen examples of both in our code. 89 | PYXL_ATTRVAL_UNQUOTED_CHAR=[^\"\'\`=\<\>{ \r\n] 90 | PYXL_ATTRVALUE_2Q = ([^\\\"{]|{ESCAPE_SEQUENCE}|(\\[\r\n]))*? 91 | PYXL_ATTRVALUE_1Q = ([^\\'{]|{ESCAPE_SEQUENCE}|(\\[\r\n]))*? 92 | 93 | // a string in a pyxl block, outside tags and quotes (can't contain {} <> # etc) 94 | PYXL_BLOCK_STRING = ([^<{#])*? 95 | 96 | %state IN_PYXL_DOCUMENT 97 | %state IN_PYXL_BLOCK 98 | %state IN_PYXL_TAG_NAME 99 | %state IN_PYXL_PYTHON_EMBED 100 | %state PENDING_PYXL_TAG_FROM_PYTHON 101 | %state PENDING_PYXL_TAG_FROM_PYXL 102 | %state ATTR_VALUE_UNQUOTED 103 | %state ATTR_VALUE_1Q 104 | %state ATTR_VALUE_2Q 105 | %state IN_ATTR 106 | %state IN_ATTRVALUE 107 | %state IN_CLOSE_TAG 108 | 109 | %{ 110 | private void enterState(int state) { 111 | stateStack.push(new State(zzLexicalState, embedBraceCount)); 112 | yybegin(state); 113 | embedBraceCount = 0; 114 | } 115 | private boolean exitState() { 116 | int size = stateStack.size(); 117 | if (size <= 0) { 118 | yybegin(YYINITIAL); 119 | return false; // error condition 120 | } else { 121 | State mystate = stateStack.pop(); 122 | yybegin(mystate.lexState); 123 | embedBraceCount = mystate.embedBraceCount; 124 | return true; 125 | } 126 | } 127 | 128 | // Counter for keeping track of when an embed statement ends, as opposed to when inner braces closes. 129 | int embedBraceCount = 0; 130 | 131 | class State { 132 | public int lexState; 133 | public int embedBraceCount; 134 | 135 | State (int lexState, int embedBraceCount) { 136 | this.lexState = lexState; 137 | this.embedBraceCount = embedBraceCount; 138 | } 139 | } 140 | Stack stateStack = new Stack(); 141 | 142 | %} 143 | 144 | %state PENDING_DOCSTRING 145 | %state IN_DOCSTRING_OWNER 146 | %{ 147 | 148 | private int getSpaceLength(CharSequence string) { 149 | String string1 = string.toString(); 150 | string1 = StringUtil.trimEnd(string1, "\\"); 151 | string1 = StringUtil.trimEnd(string1, ";"); 152 | final String s = StringUtil.trimTrailing(string1); 153 | return yylength()-s.length(); 154 | 155 | } 156 | 157 | %} 158 | %% 159 | 160 | [\ ] { return PyTokenTypes.SPACE; } 161 | [\t] { return PyTokenTypes.TAB; } 162 | [\f] { return PyTokenTypes.FORMFEED; } 163 | "\\" { return PyTokenTypes.BACKSLASH; } 164 | 165 | { 166 | {SINGLE_QUOTED_STRING} { return PyTokenTypes.SINGLE_QUOTED_STRING; } 167 | {TRIPLE_QUOTED_STRING} { return PyTokenTypes.TRIPLE_QUOTED_STRING; } 168 | "{" { embedBraceCount++; return PyTokenTypes.LBRACE; } 169 | "}" { if (embedBraceCount-- == 0) { 170 | exitState(); 171 | return PyxlTokenTypes.EMBED_END; 172 | } else { 173 | return PyTokenTypes.RBRACE; 174 | } 175 | } 176 | // remainder of python is defined below in the python states. 177 | } 178 | 179 | // look for pyxl tag starts in python contexts using a lookahead 180 | { 181 | {PYXL_TAG_COMING} { 182 | yypushback(yylength()); 183 | enterState(PENDING_PYXL_TAG_FROM_PYTHON); 184 | } 185 | } 186 | 187 | // look for pyxl tag starts in pyxl contexts 188 | { 189 | "<" { 190 | if (zzLexicalState == PENDING_PYXL_TAG_FROM_PYTHON) { 191 | yybegin(IN_PYXL_TAG_NAME); 192 | } else { 193 | enterState(IN_PYXL_TAG_NAME); 194 | } 195 | return PyxlTokenTypes.TAGBEGIN; 196 | } 197 | } 198 | 199 | { 200 | "if" { return PyxlTokenTypes.BUILT_IN_TAG; } 201 | "else" { return PyxlTokenTypes.BUILT_IN_TAG; } 202 | {IDENTIFIER}"." { yypushback(1); return PyxlTokenTypes.TAGNAME_MODULE; } 203 | {PYXL_TAGNAME} { return PyxlTokenTypes.TAGNAME; } 204 | ">" { return exitState() ? PyxlTokenTypes.TAGEND : PyxlTokenTypes.BADCHAR; } 205 | "." { return PyTokenTypes.DOT; } 206 | . { return PyxlTokenTypes.BADCHAR; } 207 | } 208 | 209 | { 210 | // TODO(christoffer) Proper handling of inner Python code 211 | // In the interest of keeping the Lexer simple; I've taken a little shortcut for 212 | // comments, doctypes and cdata in that I don't consider the case where inner Python code can 213 | // contain the end delimiter, as this is likely to be an edge case. 214 | // 215 | // Here's how Pyxl behaves with Python sections containing the end delimiter inside them: 216 | // 217 | // "}> (output: ``, Python is ignored) 218 | // "}]]> (output: `]]>`, the Python is output *before* the CDATA?) 219 | // "}--> (output: ``, Python is ignored) 220 | // 221 | // We don't need a Python state for the inner Python code for DOCTYPE and COMMENT since it's 222 | // ignored anyway (it's not even executed), and since the Python behavior looks a bit undefined 223 | // for the CDATA case, I'm betting it's safe to ignore Python handling in there as well. 224 | // 225 | // Note however that the lexer currently will prematurely end the state in each 226 | // of the above examples. 227 | {PYXL_CDATA} { return PyTokenTypes.END_OF_LINE_COMMENT; } 228 | {PYXL_DOCTYPE} { return PyTokenTypes.END_OF_LINE_COMMENT; } 229 | {PYXL_COMMENT} { return PyTokenTypes.END_OF_LINE_COMMENT; } 230 | "{" { enterState(IN_PYXL_PYTHON_EMBED); return PyxlTokenTypes.EMBED_START; } 231 | {PYXL_TAGCLOSE} { yybegin(IN_CLOSE_TAG); yypushback(yylength()-2); return PyxlTokenTypes.TAGCLOSE; } 232 | {END_OF_LINE_COMMENT} { return PyTokenTypes.END_OF_LINE_COMMENT; } 233 | {PYXL_BLOCK_STRING} { return PyxlTokenTypes.STRING; } 234 | . { return PyxlTokenTypes.BADCHAR; } 235 | } 236 | 237 | // HTML allows attribute values without quotes, if they conform to a limited set of characters. 238 | { 239 | {PYXL_ATTRVAL_UNQUOTED_CHAR}* { exitState(); return PyxlTokenTypes.ATTRVALUE; } 240 | . { return PyxlTokenTypes.BADCHAR;} 241 | } 242 | 243 | { 244 | "'" { exitState(); return PyxlTokenTypes.ATTRVALUE_END; } // end of attribute value 245 | {PYXL_ATTRVALUE_1Q} { return PyxlTokenTypes.ATTRVALUE;} 246 | "{" { enterState(IN_PYXL_PYTHON_EMBED); return PyxlTokenTypes.EMBED_START; } 247 | . { return PyxlTokenTypes.BADCHAR;} 248 | } 249 | 250 | { 251 | "\"" { exitState(); return PyxlTokenTypes.ATTRVALUE_END;} // end of attribute value 252 | {PYXL_ATTRVALUE_2Q} { return PyxlTokenTypes.ATTRVALUE;} 253 | "{" { enterState(IN_PYXL_PYTHON_EMBED); return PyxlTokenTypes.EMBED_START; } 254 | [^] { return PyxlTokenTypes.BADCHAR;} 255 | 256 | } 257 | 258 | { // parse an attribute value 259 | "'" { yybegin(ATTR_VALUE_1Q); return PyxlTokenTypes.ATTRVALUE_START; } 260 | "\"" { yybegin(ATTR_VALUE_2Q); return PyxlTokenTypes.ATTRVALUE_START; } 261 | {PYXL_ATTRVAL_UNQUOTED_CHAR} { yypushback(1); yybegin(ATTR_VALUE_UNQUOTED); } 262 | // python embed without quotes 263 | "{" { yybegin(IN_PYXL_PYTHON_EMBED); return PyxlTokenTypes.EMBED_START; } 264 | [^] { return PyxlTokenTypes.BADCHAR; } 265 | 266 | } 267 | { // parse an attribute name and value 268 | {PYXL_ATTRNAME} { return PyxlTokenTypes.ATTRNAME; } 269 | "=" { enterState(IN_ATTRVALUE); return PyTokenTypes.EQ; } 270 | 271 | ">" { yybegin(IN_PYXL_BLOCK); return PyxlTokenTypes.TAGEND;} 272 | "/>" { return exitState() ? PyxlTokenTypes.TAGENDANDCLOSE : PyxlTokenTypes.BADCHAR; } 273 | {END_OF_LINE_COMMENT} { return PyTokenTypes.END_OF_LINE_COMMENT; } 274 | . { return PyxlTokenTypes.BADCHAR; } 275 | } 276 | 277 | { // parse a tag name 278 | //">" { yybegin(IN_PYXL_BLOCK); return PyxlTokenTypes.TAGEND; } 279 | "if" { yybegin(IN_ATTR); return PyxlTokenTypes.BUILT_IN_TAG; } 280 | "else" { yybegin(IN_ATTR); return PyxlTokenTypes.BUILT_IN_TAG; } 281 | {IDENTIFIER}"." { yypushback(1); return PyxlTokenTypes.TAGNAME_MODULE; } 282 | {PYXL_TAGNAME} { yybegin(IN_ATTR); return PyxlTokenTypes.TAGNAME; } 283 | "." { return PyTokenTypes.DOT; } 284 | . { return PyxlTokenTypes.BADCHAR; } 285 | } 286 | 287 | { 288 | ":"(\ )*{END_OF_LINE_COMMENT}?"\n" { yypushback(yylength()-1); enterState(PENDING_DOCSTRING); return PyTokenTypes.COLON; } 289 | } 290 | 291 | { 292 | {SINGLE_QUOTED_STRING} { if (zzInput == YYEOF) return PyTokenTypes.DOCSTRING; 293 | else exitState(); return PyTokenTypes.SINGLE_QUOTED_STRING; } 294 | {TRIPLE_QUOTED_STRING} { if (zzInput == YYEOF) return PyTokenTypes.DOCSTRING; 295 | else exitState(); return PyTokenTypes.TRIPLE_QUOTED_STRING; } 296 | {DOCSTRING_LITERAL}[\ \t]*[\n;] { yypushback(getSpaceLength(yytext())); exitState(); return PyTokenTypes.DOCSTRING; } 297 | {DOCSTRING_LITERAL}[\ \t]*"\\" { yypushback(getSpaceLength(yytext())); return PyTokenTypes.DOCSTRING; } 298 | 299 | . { yypushback(1); exitState(); } 300 | } 301 | 302 | // NOTE(christoffer): Must be above YYINITIAL:{END_OF_LINE_COMMENT} as the length is identical, and 303 | // this must match before. 304 | { 305 | {PYXL_ENCODING_STRING} { // Look for # coding: pyxl 306 | if(zzCurrentPos == 0) { 307 | enterState(IN_PYXL_DOCUMENT); 308 | return PyTokenTypes.END_OF_LINE_COMMENT; 309 | } 310 | } 311 | } 312 | 313 | { 314 | [\n] { if (zzCurrentPos == 0) enterState(PENDING_DOCSTRING); return PyTokenTypes.LINE_BREAK; } 315 | {END_OF_LINE_COMMENT} { if (zzCurrentPos == 0) enterState(PENDING_DOCSTRING); return PyTokenTypes.END_OF_LINE_COMMENT; } 316 | 317 | {SINGLE_QUOTED_STRING} { if (zzInput == YYEOF && zzStartRead == 0) return PyTokenTypes.DOCSTRING; 318 | else return PyTokenTypes.SINGLE_QUOTED_STRING; } 319 | {TRIPLE_QUOTED_STRING} { if (zzInput == YYEOF && zzStartRead == 0) return PyTokenTypes.DOCSTRING; 320 | else return PyTokenTypes.TRIPLE_QUOTED_STRING; } 321 | 322 | {SINGLE_QUOTED_STRING}[\ \t]*[\n;] { yypushback(getSpaceLength(yytext())); if (zzCurrentPos != 0) return PyTokenTypes.SINGLE_QUOTED_STRING; 323 | return PyTokenTypes.DOCSTRING; } 324 | 325 | {TRIPLE_QUOTED_STRING}[\ \t]*[\n;] { yypushback(getSpaceLength(yytext())); if (zzCurrentPos != 0) return PyTokenTypes.TRIPLE_QUOTED_STRING; 326 | return PyTokenTypes.DOCSTRING; } 327 | 328 | {SINGLE_QUOTED_STRING}[\ \t]*"\\" { 329 | yypushback(getSpaceLength(yytext())); if (zzCurrentPos != 0) return PyTokenTypes.SINGLE_QUOTED_STRING; 330 | enterState(PENDING_DOCSTRING); return PyTokenTypes.DOCSTRING; } 331 | 332 | {TRIPLE_QUOTED_STRING}[\ \t]*"\\" { 333 | yypushback(getSpaceLength(yytext())); if (zzCurrentPos != 0) return PyTokenTypes.TRIPLE_QUOTED_STRING; 334 | enterState(PENDING_DOCSTRING); return PyTokenTypes.DOCSTRING; } 335 | 336 | } 337 | 338 | [\n] { return PyTokenTypes.LINE_BREAK; } 339 | 340 | // python states 341 | { 342 | 343 | // this rule was for ALL states in python; with Pyxl addition we have to limit it to python states only. 344 | {END_OF_LINE_COMMENT} { return PyTokenTypes.END_OF_LINE_COMMENT; } 345 | 346 | {LONGINTEGER} { return PyTokenTypes.INTEGER_LITERAL; } 347 | {INTEGER} { return PyTokenTypes.INTEGER_LITERAL; } 348 | {FLOATNUMBER} { return PyTokenTypes.FLOAT_LITERAL; } 349 | {IMAGNUMBER} { return PyTokenTypes.IMAGINARY_LITERAL; } 350 | 351 | {SINGLE_QUOTED_STRING} { return PyTokenTypes.SINGLE_QUOTED_STRING; } 352 | {TRIPLE_QUOTED_STRING} { return PyTokenTypes.TRIPLE_QUOTED_STRING; } 353 | 354 | "and" { return PyTokenTypes.AND_KEYWORD; } 355 | "assert" { return PyTokenTypes.ASSERT_KEYWORD; } 356 | "break" { return PyTokenTypes.BREAK_KEYWORD; } 357 | "class" { yybegin(IN_DOCSTRING_OWNER); return PyTokenTypes.CLASS_KEYWORD; } 358 | "continue" { return PyTokenTypes.CONTINUE_KEYWORD; } 359 | "def" { yybegin(IN_DOCSTRING_OWNER); return PyTokenTypes.DEF_KEYWORD; } 360 | "del" { return PyTokenTypes.DEL_KEYWORD; } 361 | "elif" { return PyTokenTypes.ELIF_KEYWORD; } 362 | "else" { return PyTokenTypes.ELSE_KEYWORD; } 363 | "except" { return PyTokenTypes.EXCEPT_KEYWORD; } 364 | "finally" { return PyTokenTypes.FINALLY_KEYWORD; } 365 | "for" { return PyTokenTypes.FOR_KEYWORD; } 366 | "from" { return PyTokenTypes.FROM_KEYWORD; } 367 | "global" { return PyTokenTypes.GLOBAL_KEYWORD; } 368 | "if" { return PyTokenTypes.IF_KEYWORD; } 369 | "import" { return PyTokenTypes.IMPORT_KEYWORD; } 370 | "in" { return PyTokenTypes.IN_KEYWORD; } 371 | "is" { return PyTokenTypes.IS_KEYWORD; } 372 | "lambda" { return PyTokenTypes.LAMBDA_KEYWORD; } 373 | "not" { return PyTokenTypes.NOT_KEYWORD; } 374 | "or" { return PyTokenTypes.OR_KEYWORD; } 375 | "pass" { return PyTokenTypes.PASS_KEYWORD; } 376 | "print" { return PyTokenTypes.PRINT_KEYWORD; } 377 | "raise" { return PyTokenTypes.RAISE_KEYWORD; } 378 | "return" { return PyTokenTypes.RETURN_KEYWORD; } 379 | "try" { return PyTokenTypes.TRY_KEYWORD; } 380 | "while" { return PyTokenTypes.WHILE_KEYWORD; } 381 | "yield" { return PyTokenTypes.YIELD_KEYWORD; } 382 | 383 | {IDENTIFIER} { return PyTokenTypes.IDENTIFIER; } 384 | 385 | "+=" { return PyTokenTypes.PLUSEQ; } 386 | "-=" { return PyTokenTypes.MINUSEQ; } 387 | "**=" { return PyTokenTypes.EXPEQ; } 388 | "*=" { return PyTokenTypes.MULTEQ; } 389 | "//=" { return PyTokenTypes.FLOORDIVEQ; } 390 | "/=" { return PyTokenTypes.DIVEQ; } 391 | "%=" { return PyTokenTypes.PERCEQ; } 392 | "&=" { return PyTokenTypes.ANDEQ; } 393 | "|=" { return PyTokenTypes.OREQ; } 394 | "^=" { return PyTokenTypes.XOREQ; } 395 | ">>=" { return PyTokenTypes.GTGTEQ; } 396 | "<<=" { return PyTokenTypes.LTLTEQ; } 397 | "<<" { return PyTokenTypes.LTLT; } 398 | ">>" { return PyTokenTypes.GTGT; } 399 | "**" { return PyTokenTypes.EXP; } 400 | "//" { return PyTokenTypes.FLOORDIV; } 401 | "<=" { return PyTokenTypes.LE; } 402 | ">=" { return PyTokenTypes.GE; } 403 | "==" { return PyTokenTypes.EQEQ; } 404 | "!=" { return PyTokenTypes.NE; } 405 | "<>" { return PyTokenTypes.NE_OLD; } 406 | "+" { return PyTokenTypes.PLUS; } 407 | "-" { return PyTokenTypes.MINUS; } 408 | "*" { return PyTokenTypes.MULT; } 409 | "/" { return PyTokenTypes.DIV; } 410 | "%" { return PyTokenTypes.PERC; } 411 | "&" { return PyTokenTypes.AND; } 412 | "|" { return PyTokenTypes.OR; } 413 | "^" { return PyTokenTypes.XOR; } 414 | "~" { return PyTokenTypes.TILDE; } 415 | "<" { return PyTokenTypes.LT; } 416 | ">" { return PyTokenTypes.GT; } 417 | "(" { return PyTokenTypes.LPAR; } 418 | ")" { return PyTokenTypes.RPAR; } 419 | "[" { return PyTokenTypes.LBRACKET; } 420 | "]" { return PyTokenTypes.RBRACKET; } 421 | "{" { return PyTokenTypes.LBRACE; } 422 | "}" { return PyTokenTypes.RBRACE; } 423 | "@" { return PyTokenTypes.AT; } 424 | "," { return PyTokenTypes.COMMA; } 425 | ":" { return PyTokenTypes.COLON; } 426 | 427 | "." { return PyTokenTypes.DOT; } 428 | "`" { return PyTokenTypes.TICK; } 429 | "=" { return PyTokenTypes.EQ; } 430 | ";" { return PyTokenTypes.SEMICOLON; } 431 | 432 | 433 | . { return PyTokenTypes.BAD_CHARACTER; } 434 | } 435 | 436 | //[\n] { return PyTokenTypes.LINE_BREAK; } 437 | . { return PyxlTokenTypes.BADCHAR; } 438 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/PyxlExpressionParsing.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.christofferklang.pyxl.PyxlElementTypes; 4 | import com.christofferklang.pyxl.PyxlTokenTypes; 5 | import com.intellij.lang.PsiBuilder; 6 | import com.intellij.psi.tree.IElementType; 7 | import com.jetbrains.python.PyElementTypes; 8 | import com.jetbrains.python.PyTokenTypes; 9 | import com.jetbrains.python.parsing.ExpressionParsing; 10 | import com.jetbrains.python.parsing.ParsingContext; 11 | import com.jetbrains.python.psi.PyElementType; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | class PyxlExpressionParsing extends ExpressionParsing { 17 | private static class PyxlParsingException extends Throwable { 18 | public PyxlParsingException() { 19 | super(); 20 | } 21 | 22 | public PyxlParsingException(String message) { 23 | super(message); 24 | } 25 | } 26 | 27 | private static final List PYXL_CLOSE_TOKENS = 28 | Arrays.asList(PyxlTokenTypes.TAGCLOSE); // , PyxlTokenTypes.IFTAGCLOSE); 29 | 30 | public PyxlExpressionParsing(ParsingContext context) { 31 | super(context); 32 | } 33 | 34 | public boolean parsePrimaryExpression(boolean isTargetExpression) { 35 | if (myBuilder.getTokenType() == PyxlTokenTypes.TAGBEGIN) { 36 | parsePyxlTag(); 37 | return true; 38 | } else { 39 | return super.parsePrimaryExpression(isTargetExpression); 40 | } 41 | } 42 | 43 | /** 44 | * Parse a pyxl tag. 45 | */ 46 | private void parsePyxlTag() { 47 | final PsiBuilder.Marker pyxl = myBuilder.mark(); 48 | myBuilder.advanceLexer(); 49 | 50 | String qualifiedName; 51 | try { 52 | qualifiedName = parseQualifiedPyxlTagName(); 53 | } catch (PyxlParsingException e) { 54 | myBuilder.error(e.getMessage()); 55 | pyxl.done(PyxlElementTypes.TAG); 56 | return; 57 | } 58 | 59 | if (!parsePyxlAttributes()) { 60 | pyxl.done(PyxlElementTypes.TAG); 61 | return; 62 | } 63 | 64 | IElementType token = myBuilder.getTokenType(); 65 | if (token == PyxlTokenTypes.TAGENDANDCLOSE) { 66 | // The tag was self-closed ( /> ). 67 | final PsiBuilder.Marker argumentList = myBuilder.mark(); 68 | argumentList.done(PyxlElementTypes.ARGUMENT_LIST); 69 | myBuilder.advanceLexer(); 70 | } else if (token == PyxlTokenTypes.TAGEND) { 71 | // The tag has content (even empty content counts). 72 | myBuilder.advanceLexer(); 73 | 74 | final PsiBuilder.Marker argumentList = myBuilder.mark(); 75 | 76 | // Parse pyxl tag content. 77 | boolean error = false; 78 | while ((token = myBuilder.getTokenType()) != PyxlTokenTypes.TAGCLOSE) { 79 | // Parse embed expressions of the form {python_code}. 80 | try { 81 | if (parsePyxlEmbed()) { 82 | continue; 83 | } 84 | } catch (PyxlParsingException e) { 85 | error = true; 86 | break; 87 | } 88 | 89 | if (token == PyxlTokenTypes.TAGBEGIN) { 90 | parsePyxlTag(); 91 | } else if (token == PyxlTokenTypes.STRING) { 92 | PsiBuilder.Marker stringLiteral = myBuilder.mark(); 93 | myBuilder.advanceLexer(); 94 | stringLiteral.done(PyElementTypes.STRING_LITERAL_EXPRESSION); 95 | } else { 96 | myBuilder.error(String.format("pyxl encountered unexpected token: %s", token)); 97 | error = true; 98 | break; 99 | } 100 | } 101 | 102 | argumentList.done(PyElementTypes.ARGUMENT_LIST); 103 | 104 | if (error) { 105 | pyxl.done(PyxlElementTypes.TAG); 106 | return; 107 | } 108 | 109 | // Consume the ", qualifiedName)); 117 | return; 118 | } 119 | 120 | if (myBuilder.getTokenType() == PyxlTokenTypes.TAGEND) { 121 | myBuilder.advanceLexer(); 122 | } else { 123 | myBuilder.error("pyxl expected >"); 124 | } 125 | } 126 | pyxl.done(PyxlElementTypes.TAG); 127 | } 128 | 129 | /** 130 | * Parses a pyxl tag name, including an optional list of module qualifiers. 131 | * 132 | * @return the full qualified name (e.g. "module1.module2.tag") 133 | * @throws PyxlParsingException if @requiredQualifiedName is non-null and doesn't match the parsed qualified name. 134 | */ 135 | private String parseQualifiedPyxlTagName(String requiredQualifiedName, 136 | PsiBuilder.Marker callExpressionMarker, 137 | PsiBuilder.Marker tagStartMarker) throws PyxlParsingException { 138 | final IElementType token = myBuilder.getTokenType(); 139 | 140 | String fullQualifiedName = myBuilder.getTokenText(); 141 | 142 | // Module qualifiers are straight up python identifiers followed by a dot 143 | if (token == PyxlTokenTypes.TAGNAME_MODULE) { 144 | if (callExpressionMarker == null) callExpressionMarker = myBuilder.mark(); 145 | if (tagStartMarker == null) tagStartMarker = myBuilder.mark(); 146 | PsiBuilder.Marker moduleExpression = myBuilder.mark(); 147 | myBuilder.advanceLexer(); 148 | moduleExpression.done(PyxlElementTypes.MODULE_REFERENCE); 149 | if (myBuilder.getTokenType() != PyTokenTypes.DOT) { 150 | throw new PyxlParsingException(); 151 | } else { 152 | myBuilder.advanceLexer(); 153 | } 154 | fullQualifiedName = fullQualifiedName + "." + 155 | parseQualifiedPyxlTagName(null, callExpressionMarker, tagStartMarker); 156 | } else if (token == PyxlTokenTypes.TAGNAME) { 157 | // pyxl expands

    to x_p()(x_c()); 158 | // so in order to get the same semantics as the corresponding python would have, we fake a call to the init 159 | // function of the pyxl tag class here. 160 | 161 | if (callExpressionMarker == null) callExpressionMarker = myBuilder.mark(); 162 | if (tagStartMarker == null) tagStartMarker = myBuilder.mark(); 163 | myBuilder.advanceLexer(); 164 | tagStartMarker.done(PyxlElementTypes.TAG_REFERENCE); 165 | callExpressionMarker.done(PyElementTypes.CALL_EXPRESSION); 166 | } else if (token == PyxlTokenTypes.BUILT_IN_TAG) { 167 | myBuilder.advanceLexer(); 168 | } else { 169 | throw new PyxlParsingException(); 170 | } 171 | 172 | if (requiredQualifiedName != null && !requiredQualifiedName.equals(fullQualifiedName)) { 173 | throw new PyxlParsingException("expected starting tag " + requiredQualifiedName); 174 | } 175 | 176 | return fullQualifiedName; 177 | } 178 | 179 | private String parseQualifiedPyxlTagName() throws PyxlParsingException { 180 | return parseQualifiedPyxlTagName(null, null, null); 181 | } 182 | 183 | 184 | /** 185 | * Attempt to parse an embedded python expression. For example: 186 | * {self.counter + 1}. If an error occurs while the embedded expression 187 | * is being parsed a parse error will be set. It is ok to call this 188 | * method whenever a embedded python expression could occur, even if 189 | * the lexer isn't currently ready to produce one. 190 | * 191 | * @return true if an embedded expression was parsed, or false 192 | * otherwise. 193 | * @throws PyxlParsingException if an error occurs while the embedded 194 | * expression is being parsed. 195 | */ 196 | private boolean parsePyxlEmbed() throws PyxlParsingException { 197 | if (myBuilder.getTokenType() == PyxlTokenTypes.EMBED_START) { 198 | myBuilder.advanceLexer(); 199 | parseExpression(); 200 | if (myBuilder.getTokenType() == PyxlTokenTypes.EMBED_END) { 201 | myBuilder.advanceLexer(); 202 | return true; 203 | } else { 204 | myBuilder.error("pyxl expected embed end"); 205 | throw new PyxlParsingException(); 206 | } 207 | } 208 | return false; 209 | } 210 | 211 | /** 212 | * Parse all pyxl tag attribute="value" pairs. 213 | */ 214 | private boolean parsePyxlAttributes() { 215 | while (myBuilder.getTokenType() == PyxlTokenTypes.ATTRNAME) { 216 | final PsiBuilder.Marker attr = myBuilder.mark(); 217 | final PsiBuilder.Marker attrName = myBuilder.mark(); 218 | myBuilder.advanceLexer(); 219 | attrName.done(PyxlElementTypes.ATTRNAME); 220 | 221 | if (myBuilder.getTokenType() == PyTokenTypes.EQ) { 222 | myBuilder.advanceLexer(); 223 | if (parsePyxlAttributeValue()) { 224 | attr.done(PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION); 225 | // Parse the next attribute="value" pair. 226 | continue; 227 | } else { 228 | // parsePyxlAttributeValue sets its own errors. 229 | attr.done(PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION); 230 | return false; 231 | } 232 | } else { 233 | myBuilder.error("pyxl expected ="); 234 | attr.done(PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION); 235 | return false; 236 | } 237 | } 238 | return true; 239 | } 240 | 241 | /** 242 | * Parse a pyxl attribute value. If an error occurs while the attribute 243 | * value is being parsed a parse error will be set. 244 | * 245 | * @return true if a value was successfully parsed and false 246 | * otherwise. 247 | */ 248 | private boolean parsePyxlAttributeValue() { 249 | IElementType token = myBuilder.getTokenType(); 250 | 251 | // Consume the start of an attribute. 252 | IElementType attrStartToken = null; 253 | if (token == PyxlTokenTypes.ATTRVALUE_START) { 254 | myBuilder.advanceLexer(); 255 | attrStartToken = token; 256 | } 257 | 258 | boolean foundValue = false; 259 | while (true) { 260 | token = myBuilder.getTokenType(); 261 | 262 | // Attempt to parse an embed expression. 263 | try { 264 | if (parsePyxlEmbed()) { 265 | foundValue = true; 266 | continue; 267 | } 268 | } catch (PyxlParsingException e) { 269 | break; 270 | } 271 | 272 | // Or consume literal attribute values. 273 | if (token == PyxlTokenTypes.ATTRVALUE) { 274 | foundValue = true; 275 | myBuilder.advanceLexer(); 276 | continue; 277 | } 278 | 279 | if (attrStartToken != null) { 280 | if (token == PyxlTokenTypes.ATTRVALUE_END) { 281 | myBuilder.advanceLexer(); 282 | return true; 283 | } else { 284 | myBuilder.error("pyxl expected attribute end"); 285 | return false; 286 | } 287 | } else if (foundValue) { 288 | return true; 289 | } else { 290 | myBuilder.error("pyxl expected attribute value"); 291 | return false; 292 | } 293 | } 294 | return false; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/PyxlHighlightingLexer.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.intellij.psi.tree.IElementType; 4 | import com.jetbrains.python.PyNames; 5 | import com.jetbrains.python.PyTokenTypes; 6 | import com.jetbrains.python.psi.LanguageLevel; 7 | 8 | public class PyxlHighlightingLexer extends PyxlLexer { 9 | private final LanguageLevel mLanguageLevel; 10 | 11 | public PyxlHighlightingLexer(LanguageLevel languageLevel) { 12 | super(); 13 | mLanguageLevel = languageLevel; 14 | } 15 | 16 | static public IElementType convertStringType(IElementType tokenType, String tokenText, 17 | LanguageLevel languageLevel, boolean unicodeImport) { 18 | if (tokenType == PyTokenTypes.SINGLE_QUOTED_STRING) { 19 | if (languageLevel.isPy3K()) { 20 | if (!tokenText.toLowerCase().startsWith("b")) 21 | return PyTokenTypes.SINGLE_QUOTED_UNICODE; 22 | } else { 23 | if ((unicodeImport && !tokenText.toLowerCase().startsWith("b")) 24 | || tokenText.toLowerCase().startsWith("u")) 25 | return PyTokenTypes.SINGLE_QUOTED_UNICODE; 26 | } 27 | } 28 | if (tokenType == PyTokenTypes.TRIPLE_QUOTED_STRING) { 29 | if (languageLevel.isPy3K()) { 30 | if (!tokenText.toLowerCase().startsWith("b")) 31 | return PyTokenTypes.TRIPLE_QUOTED_UNICODE; 32 | } else { 33 | if ((unicodeImport && !tokenText.toLowerCase().startsWith("b")) 34 | || tokenText.toLowerCase().startsWith("u")) 35 | return PyTokenTypes.TRIPLE_QUOTED_UNICODE; 36 | } 37 | } 38 | return tokenType; 39 | } 40 | 41 | public IElementType convertStringType(IElementType tokenType, String tokenText) { 42 | return convertStringType(tokenType, tokenText, mLanguageLevel, hasUnicodeImport); 43 | } 44 | 45 | @Override 46 | public IElementType getTokenType() { 47 | final IElementType tokenType = super.getTokenType(); 48 | 49 | if (PyTokenTypes.STRING_NODES.contains(tokenType)) { 50 | return convertStringType(tokenType, getTokenText()); 51 | } 52 | 53 | if (tokenType == PyTokenTypes.IDENTIFIER) { 54 | final String tokenText = getTokenText(); 55 | 56 | if (mLanguageLevel.hasWithStatement()) { 57 | if (tokenText.equals("with")) return PyTokenTypes.WITH_KEYWORD; 58 | if (tokenText.equals("as")) return PyTokenTypes.AS_KEYWORD; 59 | } 60 | 61 | if (mLanguageLevel.hasPrintStatement()) { 62 | if (tokenText.equals("print")) return PyTokenTypes.PRINT_KEYWORD; 63 | } 64 | 65 | if (mLanguageLevel.isPy3K()) { 66 | if (tokenText.equals("None")) return PyTokenTypes.NONE_KEYWORD; 67 | if (tokenText.equals("True")) return PyTokenTypes.TRUE_KEYWORD; 68 | if (tokenText.equals("False")) return PyTokenTypes.FALSE_KEYWORD; 69 | if (tokenText.equals("nonlocal")) return PyTokenTypes.NONLOCAL_KEYWORD; 70 | if (tokenText.equals("__debug__")) return PyTokenTypes.DEBUG_KEYWORD; 71 | } else if (tokenText.equals("exec")) { 72 | return PyTokenTypes.EXEC_KEYWORD; 73 | } 74 | } 75 | 76 | return tokenType; 77 | } 78 | 79 | private enum state { 80 | init, 81 | pending_future, 82 | pending_import, 83 | pending_lpar, 84 | pending_id, 85 | pending_comma, 86 | stop 87 | } 88 | 89 | private state myState = state.init; 90 | private boolean hasUnicodeImport = false; 91 | private int myImportOffset = -1; 92 | 93 | @Override 94 | public void advance() { 95 | IElementType type = super.getTokenType(); 96 | switch (myState) { 97 | case init: 98 | if (type == PyTokenTypes.BACKSLASH) break; 99 | if (PyTokenTypes.WHITESPACE_OR_LINEBREAK.contains(type)) break; 100 | if (PyTokenTypes.END_OF_LINE_COMMENT == type) break; 101 | if (PyTokenTypes.DOCSTRING == type) break; 102 | if (type == PyTokenTypes.FROM_KEYWORD) 103 | myState = state.pending_future; 104 | else myState = state.stop; 105 | break; 106 | case pending_future: 107 | if (type == PyTokenTypes.BACKSLASH) break; 108 | if (PyTokenTypes.WHITESPACE_OR_LINEBREAK.contains(type)) break; 109 | if (type == PyTokenTypes.IDENTIFIER && PyNames.FUTURE_MODULE.equals(super.getTokenText())) 110 | myState = state.pending_import; 111 | else myState = state.stop; 112 | break; 113 | case pending_import: 114 | if (type == PyTokenTypes.BACKSLASH) break; 115 | if (PyTokenTypes.WHITESPACE_OR_LINEBREAK.contains(type)) break; 116 | if (type == PyTokenTypes.IMPORT_KEYWORD) 117 | myState = state.pending_lpar; 118 | else myState = state.stop; 119 | break; 120 | 121 | case pending_lpar: 122 | if (type == PyTokenTypes.LPAR) { 123 | myState = state.pending_id; 124 | break; 125 | } 126 | case pending_id: 127 | if (type == PyTokenTypes.BACKSLASH) break; 128 | if (PyTokenTypes.WHITESPACE_OR_LINEBREAK.contains(type)) break; 129 | if (type == PyTokenTypes.IDENTIFIER) { 130 | myState = state.pending_comma; 131 | if (PyNames.UNICODE_LITERALS.equals(super.getTokenText())) { 132 | hasUnicodeImport = true; 133 | myImportOffset = getTokenEnd(); 134 | } 135 | } else myState = state.init; 136 | break; 137 | case pending_comma: 138 | if (type == PyTokenTypes.RPAR) break; 139 | if (type == PyTokenTypes.BACKSLASH) break; 140 | if (PyTokenTypes.LINE_BREAK == type) myState = state.init; 141 | if (PyTokenTypes.WHITESPACE_OR_LINEBREAK.contains(type)) break; 142 | if (type == PyTokenTypes.COMMA) 143 | myState = state.pending_id; 144 | break; 145 | case stop: 146 | break; 147 | } 148 | super.advance(); 149 | } 150 | 151 | 152 | public int getImportOffset() { 153 | return myImportOffset; 154 | } 155 | 156 | public void clearState(int position) { 157 | myState = state.init; 158 | myImportOffset = position; 159 | hasUnicodeImport = false; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/PyxlIndentingLexer.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.intellij.psi.tree.TokenSet; 4 | import com.jetbrains.python.PyTokenTypes; 5 | import com.jetbrains.python.lexer.PythonIndentingProcessor; 6 | 7 | import java.io.Reader; 8 | 9 | public class PyxlIndentingLexer extends PythonIndentingProcessor { 10 | public PyxlIndentingLexer() { 11 | super(new _PyxlLexer((Reader) null), TokenSet.EMPTY); 12 | } 13 | 14 | boolean addFinalBreak = true; 15 | 16 | protected void processSpecialTokens() { 17 | super.processSpecialTokens(); 18 | int tokenStart = getBaseTokenStart(); 19 | if (getBaseTokenType() == null && addFinalBreak) { 20 | pushToken(PyTokenTypes.STATEMENT_BREAK, tokenStart, tokenStart); 21 | addFinalBreak = false; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/PyxlLexer.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.intellij.lexer.FlexAdapter; 4 | 5 | import java.io.Reader; 6 | 7 | public class PyxlLexer extends FlexAdapter { 8 | public PyxlLexer() { 9 | super(new _PyxlLexer((Reader) null)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/PyxlParser.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.intellij.lang.PsiBuilder; 4 | import com.jetbrains.python.parsing.ParsingContext; 5 | import com.jetbrains.python.parsing.PyParser; 6 | import com.jetbrains.python.parsing.StatementParsing; 7 | import com.jetbrains.python.psi.LanguageLevel; 8 | 9 | class PyxlParser extends PyParser { 10 | protected ParsingContext createParsingContext( 11 | PsiBuilder builder, LanguageLevel languageLevel, 12 | StatementParsing.FUTURE futureFlag) { 13 | 14 | return new PyxlParsingContext(builder, languageLevel, futureFlag); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/PyxlParserDefinition.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.christofferklang.pyxl.PyxlTokenTypes; 4 | import com.intellij.lang.PsiParser; 5 | import com.intellij.lexer.Lexer; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.psi.tree.TokenSet; 8 | import com.jetbrains.python.PythonParserDefinition; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class PyxlParserDefinition extends PythonParserDefinition { 12 | 13 | @NotNull 14 | @Override 15 | public Lexer createLexer(Project project) { 16 | return new PyxlIndentingLexer(); 17 | } 18 | 19 | @NotNull 20 | public PsiParser createParser(Project project) { 21 | return new PyxlParser(); 22 | } 23 | 24 | @NotNull 25 | @Override 26 | public TokenSet getStringLiteralElements() { 27 | return TokenSet.orSet(super.getStringLiteralElements(), TokenSet.create(PyxlTokenTypes.STRING)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/parsing/PyxlParsingContext.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.parsing; 2 | 3 | import com.intellij.lang.PsiBuilder; 4 | import com.jetbrains.python.parsing.ExpressionParsing; 5 | import com.jetbrains.python.parsing.ParsingContext; 6 | import com.jetbrains.python.parsing.StatementParsing; 7 | import com.jetbrains.python.psi.LanguageLevel; 8 | 9 | class PyxlParsingContext extends ParsingContext { 10 | private final PyxlExpressionParsing pyxlExpressionParser; 11 | 12 | public PyxlParsingContext( 13 | final PsiBuilder builder, LanguageLevel languageLevel, 14 | StatementParsing.FUTURE futureFlag) { 15 | super(builder, languageLevel, futureFlag); 16 | pyxlExpressionParser = new PyxlExpressionParsing(this); 17 | } 18 | 19 | public ExpressionParsing getExpressionParser() { 20 | return pyxlExpressionParser; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/psi/PythonClassReference.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.psi; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.intellij.psi.PsiElement; 5 | import com.intellij.psi.tree.TokenSet; 6 | import com.intellij.psi.util.QualifiedName; 7 | import com.jetbrains.python.psi.*; 8 | import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import java.util.HashSet; 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.WeakHashMap; 15 | 16 | public class PythonClassReference extends PyReferenceExpressionImpl { 17 | private static final Set EMPTY_HASH_SET = new HashSet(); 18 | private static final Set PYXL_TAG_NAMES = new HashSet(); 19 | 20 | static { 21 | // These are pulled from pyxl/html.py 22 | PYXL_TAG_NAMES.add("x_a"); 23 | PYXL_TAG_NAMES.add("x_abbr"); 24 | PYXL_TAG_NAMES.add("x_acronym"); 25 | PYXL_TAG_NAMES.add("x_address"); 26 | PYXL_TAG_NAMES.add("x_area"); 27 | PYXL_TAG_NAMES.add("x_article"); 28 | PYXL_TAG_NAMES.add("x_aside"); 29 | PYXL_TAG_NAMES.add("x_audio"); 30 | PYXL_TAG_NAMES.add("x_b"); 31 | PYXL_TAG_NAMES.add("x_big"); 32 | PYXL_TAG_NAMES.add("x_blockquote"); 33 | PYXL_TAG_NAMES.add("x_body"); 34 | PYXL_TAG_NAMES.add("x_br"); 35 | PYXL_TAG_NAMES.add("x_button"); 36 | PYXL_TAG_NAMES.add("x_canvas"); 37 | PYXL_TAG_NAMES.add("x_caption"); 38 | PYXL_TAG_NAMES.add("x_cite"); 39 | PYXL_TAG_NAMES.add("x_code"); 40 | PYXL_TAG_NAMES.add("x_col"); 41 | PYXL_TAG_NAMES.add("x_colgroup"); 42 | PYXL_TAG_NAMES.add("x_cond_comment"); 43 | PYXL_TAG_NAMES.add("x_datalist"); 44 | PYXL_TAG_NAMES.add("x_dd"); 45 | PYXL_TAG_NAMES.add("x_del"); 46 | PYXL_TAG_NAMES.add("x_dfn"); 47 | PYXL_TAG_NAMES.add("x_div"); 48 | PYXL_TAG_NAMES.add("x_dl"); 49 | PYXL_TAG_NAMES.add("x_dt"); 50 | PYXL_TAG_NAMES.add("x_em"); 51 | PYXL_TAG_NAMES.add("x_embed"); 52 | PYXL_TAG_NAMES.add("x_fieldset"); 53 | PYXL_TAG_NAMES.add("x_figcaption"); 54 | PYXL_TAG_NAMES.add("x_figure"); 55 | PYXL_TAG_NAMES.add("x_footer"); 56 | PYXL_TAG_NAMES.add("x_form"); 57 | PYXL_TAG_NAMES.add("x_form_error"); 58 | PYXL_TAG_NAMES.add("x_frag"); 59 | PYXL_TAG_NAMES.add("x_frame"); 60 | PYXL_TAG_NAMES.add("x_frameset"); 61 | PYXL_TAG_NAMES.add("x_h1"); 62 | PYXL_TAG_NAMES.add("x_h2"); 63 | PYXL_TAG_NAMES.add("x_h3"); 64 | PYXL_TAG_NAMES.add("x_h4"); 65 | PYXL_TAG_NAMES.add("x_h5"); 66 | PYXL_TAG_NAMES.add("x_h6"); 67 | PYXL_TAG_NAMES.add("x_head"); 68 | PYXL_TAG_NAMES.add("x_header"); 69 | PYXL_TAG_NAMES.add("x_hr"); 70 | PYXL_TAG_NAMES.add("x_html"); 71 | PYXL_TAG_NAMES.add("x_html_comment"); 72 | PYXL_TAG_NAMES.add("x_html_decl"); 73 | PYXL_TAG_NAMES.add("x_html_element"); 74 | PYXL_TAG_NAMES.add("x_html_element_nochild"); 75 | PYXL_TAG_NAMES.add("x_html_marked_decl"); 76 | PYXL_TAG_NAMES.add("x_html_ms_decl"); 77 | PYXL_TAG_NAMES.add("x_i"); 78 | PYXL_TAG_NAMES.add("x_iframe"); 79 | PYXL_TAG_NAMES.add("x_img"); 80 | PYXL_TAG_NAMES.add("x_input"); 81 | PYXL_TAG_NAMES.add("x_ins"); 82 | PYXL_TAG_NAMES.add("x_kbd"); 83 | PYXL_TAG_NAMES.add("x_label"); 84 | PYXL_TAG_NAMES.add("x_legend"); 85 | PYXL_TAG_NAMES.add("x_li"); 86 | PYXL_TAG_NAMES.add("x_link"); 87 | PYXL_TAG_NAMES.add("x_main"); 88 | PYXL_TAG_NAMES.add("x_map"); 89 | PYXL_TAG_NAMES.add("x_meta"); 90 | PYXL_TAG_NAMES.add("x_nav"); 91 | PYXL_TAG_NAMES.add("x_noframes"); 92 | PYXL_TAG_NAMES.add("x_noscript"); 93 | PYXL_TAG_NAMES.add("x_object"); 94 | PYXL_TAG_NAMES.add("x_ol"); 95 | PYXL_TAG_NAMES.add("x_optgroup"); 96 | PYXL_TAG_NAMES.add("x_option"); 97 | PYXL_TAG_NAMES.add("x_p"); 98 | PYXL_TAG_NAMES.add("x_param"); 99 | PYXL_TAG_NAMES.add("x_pre"); 100 | PYXL_TAG_NAMES.add("x_progress"); 101 | PYXL_TAG_NAMES.add("x_q"); 102 | PYXL_TAG_NAMES.add("x_rawhtml"); 103 | PYXL_TAG_NAMES.add("x_samp"); 104 | PYXL_TAG_NAMES.add("x_script"); 105 | PYXL_TAG_NAMES.add("x_section"); 106 | PYXL_TAG_NAMES.add("x_select"); 107 | PYXL_TAG_NAMES.add("x_small"); 108 | PYXL_TAG_NAMES.add("x_span"); 109 | PYXL_TAG_NAMES.add("x_strong"); 110 | PYXL_TAG_NAMES.add("x_style"); 111 | PYXL_TAG_NAMES.add("x_sub"); 112 | PYXL_TAG_NAMES.add("x_sup"); 113 | PYXL_TAG_NAMES.add("x_table"); 114 | PYXL_TAG_NAMES.add("x_tbody"); 115 | PYXL_TAG_NAMES.add("x_td"); 116 | PYXL_TAG_NAMES.add("x_textarea"); 117 | PYXL_TAG_NAMES.add("x_tfoot"); 118 | PYXL_TAG_NAMES.add("x_th"); 119 | PYXL_TAG_NAMES.add("x_thead"); 120 | PYXL_TAG_NAMES.add("x_time"); 121 | PYXL_TAG_NAMES.add("x_title"); 122 | PYXL_TAG_NAMES.add("x_tr"); 123 | PYXL_TAG_NAMES.add("x_tt"); 124 | PYXL_TAG_NAMES.add("x_u"); 125 | PYXL_TAG_NAMES.add("x_ul"); 126 | PYXL_TAG_NAMES.add("x_var"); 127 | PYXL_TAG_NAMES.add("x_video"); 128 | } 129 | 130 | private static Set mCachedSpecialPyxlTagNames = null; 131 | private static WeakHashMap sHtmlImportCache = 132 | new WeakHashMap(); 133 | 134 | public PythonClassReference(ASTNode astNode) { 135 | super(astNode); 136 | } 137 | 138 | @Nullable 139 | @Override 140 | public String getReferencedName() { 141 | return pyxlClassName(getNode().getText()); 142 | } 143 | 144 | @Nullable 145 | @Override 146 | public PyExpression getQualifier() { 147 | PyExpression realQualifier = super.getQualifier(); 148 | if (realQualifier == null && isPyxlHtmlTag(getReferencedName())) { 149 | // Implicitly assume the tag is a reference to a pyxl html tag if the pyxl html module is imported and we 150 | // aren't using a qualifier already. This will break resolution of tags defined in a more local scope than 151 | // pyxl.html (e.g. if you make your own class x_div in a file that also imports pyxl.html). 152 | // This is consistent with how Pyxl works: 153 | // https://github.com/dropbox/pyxl/blob/daa01ca026ef3dba931d3ba56118ad8f8f6bec94/pyxl/codec/parser.py#L211 154 | PyImportElement pyxlHtmlImportElement = getImportedPyxlHtmlModuleElement(); 155 | if (pyxlHtmlImportElement != null) { 156 | return pyxlHtmlImportElement.getImportReferenceExpression(); 157 | } 158 | } 159 | return realQualifier; 160 | } 161 | 162 | private boolean isPyxlHtmlTag(String name) { 163 | return PYXL_TAG_NAMES.contains(name); 164 | // Uncomment the line below to get the "live" set of Pyxl tags. 165 | // return getSpecialPyxlTagsFromImportedHtmlModule().contains(name); 166 | } 167 | 168 | @SuppressWarnings("UnusedDeclaration") 169 | private Set getSpecialPyxlTagsFromImportedHtmlModule() { 170 | if (mCachedSpecialPyxlTagNames == null) { 171 | PyImportElement importPyxlHtmlElement = getImportedPyxlHtmlModuleElement(); 172 | if (importPyxlHtmlElement != null) { 173 | PyFile htmlModule = (PyFile) importPyxlHtmlElement.getElementNamed("html", true); 174 | 175 | mCachedSpecialPyxlTagNames = new HashSet(); 176 | //noinspection ConstantConditions 177 | for (PyClass topLevelClass : htmlModule.getTopLevelClasses()) { 178 | mCachedSpecialPyxlTagNames.add(topLevelClass.getName()); 179 | } 180 | 181 | // Consider transient classes in the top level scope as well 182 | for (PyFromImportStatement importStatement : htmlModule.getFromImports()) { 183 | for(PyImportElement importElement : importStatement.getImportElements()) { 184 | final String visibleName = importElement.getVisibleName(); 185 | if(visibleName != null && visibleName.startsWith("x_")) { 186 | // Just swallowing all import classes starting with 187 | // x_ isn't *technically* correct (any class can be named x_), but 188 | // definitely good enough for our purposes. 189 | mCachedSpecialPyxlTagNames.add(importElement.getVisibleName()); 190 | } 191 | } 192 | } 193 | } 194 | } 195 | 196 | return mCachedSpecialPyxlTagNames == null ? EMPTY_HASH_SET : mCachedSpecialPyxlTagNames; 197 | } 198 | 199 | /** 200 | * Try and a find an import statement such as "from mymodule.pyxl import html" 201 | */ 202 | private PyImportElement getImportedPyxlHtmlModuleElement() { 203 | if (!(getContainingFile() instanceof PyFile)) return null; // not a python file 204 | 205 | final PyFile pyFile = (PyFile) getContainingFile(); 206 | final String cacheKey = String.format("%s:%s", 207 | pyFile.getContainingDirectory(), 208 | pyFile.getName()); 209 | 210 | if(sHtmlImportCache.containsKey(cacheKey)) { 211 | PyImportElement importElement = sHtmlImportCache.get(cacheKey); 212 | if(importElement != null) { 213 | return importElement; 214 | } 215 | } 216 | 217 | List imports = pyFile.getFromImports(); 218 | 219 | for (PyFromImportStatement importStatement : imports) { 220 | // check for import statements that import from a "pyxl" package 221 | final QualifiedName qualifiedName = importStatement.getImportSourceQName(); 222 | if (qualifiedName != null && "pyxl".equals(qualifiedName.getLastComponent())) { 223 | // check only for imports of the module "html" 224 | PyImportElement[] importedElements = importStatement.getImportElements(); 225 | for (PyImportElement importedElement : importedElements) { 226 | PsiElement htmlElement = importedElement.getElementNamed("html", true); 227 | if (htmlElement instanceof PyFile) { 228 | sHtmlImportCache.put(cacheKey, importedElement); 229 | return importedElement; 230 | } 231 | } 232 | } 233 | } 234 | 235 | return null; 236 | } 237 | 238 | private String pyxlClassName(String tagName) { 239 | if (tagName.indexOf(".") > 0) { 240 | // tag contains a module reference like: 241 | final StringBuilder qualifiedTagName = new StringBuilder(tagName); 242 | final int offset = qualifiedTagName.lastIndexOf("."); 243 | tagName = qualifiedTagName.subSequence(offset + 1, qualifiedTagName.length()).toString(); 244 | return "x_" + tagName; 245 | } 246 | return "x_" + tagName; 247 | } 248 | 249 | @Override 250 | public String toString() { 251 | return "PyClassTagReference: " + getReferencedName(); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/psi/PyxlArgumentList.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.psi; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.jetbrains.python.psi.impl.PyArgumentListImpl; 5 | 6 | public class PyxlArgumentList extends PyArgumentListImpl { 7 | public PyxlArgumentList(ASTNode astNode) { 8 | super(astNode); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/psi/PyxlAttrName.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.psi; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl; 5 | 6 | public class PyxlAttrName extends PyStringLiteralExpressionImpl { 7 | public PyxlAttrName(ASTNode astNode) { 8 | super(astNode); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/com/christofferklang/pyxl/psi/PyxlTag.java: -------------------------------------------------------------------------------- 1 | package com.christofferklang.pyxl.psi; 2 | 3 | import com.christofferklang.pyxl.PyxlElementTypes; 4 | import com.intellij.lang.ASTNode; 5 | import com.jetbrains.python.psi.PyArgumentList; 6 | import com.jetbrains.python.psi.PyExpression; 7 | import com.jetbrains.python.psi.impl.PyCallExpressionImpl; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public class PyxlTag extends PyCallExpressionImpl { 11 | private String pyxlTagName; 12 | 13 | public PyxlTag(ASTNode astNode) { 14 | super(astNode); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | PyExpression callee = getCallee(); 20 | return String.format("Pyxl Tag: %s", callee == null ? "null" : callee.getName()); 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public PyExpression getCallee() { 26 | return (PyExpression) findChildByType(PyxlElementTypes.TAG_REFERENCE); 27 | } 28 | 29 | @Override 30 | public PyArgumentList getArgumentList() { 31 | return (PyArgumentList) findChildByType(PyxlElementTypes.ARGUMENT_LIST); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /testdata/Attributes.py: -------------------------------------------------------------------------------- 1 | # coding: pyxl 2 |
    3 |
    4 |
    5 |
    6 |
    "}" /> 7 |
    8 | -------------------------------------------------------------------------------- /testdata/Attributes.txt: -------------------------------------------------------------------------------- 1 | PyFile:Attributes.py(0,255) 2 | PsiComment(Py:END_OF_LINE_COMMENT)('# coding: pyxl')(0,14) 3 | PsiWhiteSpace('\n')(14,15) 4 | PsiElement(Py:LT)('<')(15,16) 5 | PsiErrorElement:Statement expected, found Py:LT(16,16) 6 | 7 | PyExpressionStatement(16,19) 8 | PyReferenceExpression: div(16,19) 9 | PsiElement(Py:IDENTIFIER)('div')(16,19) 10 | PsiErrorElement:End of statement expected(19,19) 11 | 12 | PsiWhiteSpace(' ')(19,20) 13 | PyAssignmentStatement(20,40) 14 | PyTargetExpression: attribute(20,29) 15 | PsiElement(Py:IDENTIFIER)('attribute')(20,29) 16 | PsiElement(Py:EQ)('=')(29,30) 17 | PyBinaryExpression(30,40) 18 | PyBinaryExpression(30,39) 19 | PyStringLiteralExpression: value(30,37) 20 | PsiElement(Py:SINGLE_QUOTED_STRING)('"value"')(30,37) 21 | PsiWhiteSpace(' ')(37,38) 22 | PsiElement(Py:DIV)('/')(38,39) 23 | PsiErrorElement:expression expected(39,39) 24 | 25 | PsiElement(Py:GT)('>')(39,40) 26 | PsiErrorElement:expression expected(40,40) 27 | 28 | PsiWhiteSpace('\n')(40,41) 29 | PyExpressionStatement(41,81) 30 | Pyxl Tag: null(41,81) 31 | PsiElement(Py:PYXL TAGBEGIN <)('<')(41,42) 32 | PyCallExpression: div(42,45) 33 | PyClassTagReference: x_div(42,45) 34 | PsiElement(Py:PYXL TAGNAME)('div')(42,45) 35 | PsiWhiteSpace(' ')(45,46) 36 | PyKeywordArgumentImpl: null(46,78) 37 | PyxlAttrName: (46,55) 38 | PsiElement(Py:PYXL ATTRNAME)('attribute')(46,55) 39 | PsiElement(Py:EQ)('=')(55,56) 40 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(56,57) 41 | PyStringLiteralExpression: no wrapping quotes(57,77) 42 | PsiElement(Py:SINGLE_QUOTED_STRING)('"no wrapping quotes"')(57,77) 43 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(77,78) 44 | PsiWhiteSpace(' ')(78,79) 45 | PyxlArgumentList(79,79) 46 | 47 | PsiElement(Py:PYXL TAGENDANDCLOSE />)('/>')(79,81) 48 | PsiWhiteSpace('\n')(81,82) 49 | PsiElement(Py:LT)('<')(82,83) 50 | PsiErrorElement:Statement expected, found Py:LT(83,83) 51 | 52 | PyExpressionStatement(83,86) 53 | PyReferenceExpression: div(83,86) 54 | PsiElement(Py:IDENTIFIER)('div')(83,86) 55 | PsiErrorElement:End of statement expected(86,86) 56 | 57 | PsiWhiteSpace(' ')(86,87) 58 | PyAssignmentStatement(87,128) 59 | PyTargetExpression: attribute(87,96) 60 | PsiElement(Py:IDENTIFIER)('attribute')(87,96) 61 | PsiElement(Py:EQ)('=')(96,97) 62 | PyBinaryExpression(97,128) 63 | PyBinaryExpression(97,127) 64 | PyStringLiteralExpression: {"wrapping single quotes"}(97,125) 65 | PsiElement(Py:SINGLE_QUOTED_STRING)(''{"wrapping single quotes"}'')(97,125) 66 | PsiWhiteSpace(' ')(125,126) 67 | PsiElement(Py:DIV)('/')(126,127) 68 | PsiErrorElement:expression expected(127,127) 69 | 70 | PsiElement(Py:GT)('>')(127,128) 71 | PsiErrorElement:expression expected(128,128) 72 | 73 | PsiWhiteSpace('\n')(128,129) 74 | PyExpressionStatement(129,175) 75 | Pyxl Tag: null(129,175) 76 | PsiElement(Py:PYXL TAGBEGIN <)('<')(129,130) 77 | PyCallExpression: div(130,133) 78 | PyClassTagReference: x_div(130,133) 79 | PsiElement(Py:PYXL TAGNAME)('div')(130,133) 80 | PsiWhiteSpace(' ')(133,134) 81 | PyKeywordArgumentImpl: null(134,172) 82 | PyxlAttrName: (134,143) 83 | PsiElement(Py:PYXL ATTRNAME)('attribute')(134,143) 84 | PsiElement(Py:EQ)('=')(143,144) 85 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(144,145) 86 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(145,146) 87 | PyStringLiteralExpression: wrapping double quotes(146,170) 88 | PsiElement(Py:SINGLE_QUOTED_STRING)('"wrapping double quotes"')(146,170) 89 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(170,171) 90 | PsiElement(Py:PYXL ATTRVALUE END)('"')(171,172) 91 | PsiWhiteSpace(' ')(172,173) 92 | PyxlArgumentList(173,173) 93 | 94 | PsiElement(Py:PYXL TAGENDANDCLOSE />)('/>')(173,175) 95 | PsiWhiteSpace('\n')(175,176) 96 | PsiElement(Py:LT)('<')(176,177) 97 | PsiErrorElement:Statement expected, found Py:LT(177,177) 98 | 99 | PyExpressionStatement(177,180) 100 | PyReferenceExpression: div(177,180) 101 | PsiElement(Py:IDENTIFIER)('div')(177,180) 102 | PsiErrorElement:End of statement expected(180,180) 103 | 104 | PsiWhiteSpace(' ')(180,181) 105 | PyAssignmentStatement(181,199) 106 | PyTargetExpression: attribute(181,190) 107 | PsiElement(Py:IDENTIFIER)('attribute')(181,190) 108 | PsiElement(Py:EQ)('=')(190,191) 109 | PyBinaryExpression(191,199) 110 | PyStringLiteralExpression: {(191,194) 111 | PsiElement(Py:SINGLE_QUOTED_STRING)('"{"')(191,194) 112 | PsiElement(Py:LT)('<')(194,195) 113 | PyReferenceExpression: fake(195,199) 114 | PsiElement(Py:IDENTIFIER)('fake')(195,199) 115 | PsiErrorElement:End of statement expected(199,199) 116 | 117 | PsiWhiteSpace(' ')(199,200) 118 | PyExpressionStatement(200,205) 119 | PyReferenceExpression: inner(200,205) 120 | PsiElement(Py:IDENTIFIER)('inner')(200,205) 121 | PsiErrorElement:End of statement expected(205,205) 122 | 123 | PsiWhiteSpace(' ')(205,206) 124 | PyExpressionStatement(206,219) 125 | PyBinaryExpression(206,219) 126 | PyBinaryExpression(206,218) 127 | PyBinaryExpression(206,212) 128 | PyReferenceExpression: pyxl(206,210) 129 | PsiElement(Py:IDENTIFIER)('pyxl')(206,210) 130 | PsiWhiteSpace(' ')(210,211) 131 | PsiElement(Py:DIV)('/')(211,212) 132 | PsiErrorElement:expression expected(212,212) 133 | 134 | PsiElement(Py:GT)('>')(212,213) 135 | PyBinaryExpression(213,218) 136 | PyStringLiteralExpression: }(213,216) 137 | PsiElement(Py:SINGLE_QUOTED_STRING)('"}"')(213,216) 138 | PsiWhiteSpace(' ')(216,217) 139 | PsiElement(Py:DIV)('/')(217,218) 140 | PsiErrorElement:expression expected(218,218) 141 | 142 | PsiElement(Py:GT)('>')(218,219) 143 | PsiErrorElement:expression expected(219,219) 144 | 145 | PsiWhiteSpace('\n')(219,220) 146 | PyExpressionStatement(220,255) 147 | Pyxl Tag: null(220,255) 148 | PsiElement(Py:PYXL TAGBEGIN <)('<')(220,221) 149 | PyCallExpression: div(221,224) 150 | PyClassTagReference: x_div(221,224) 151 | PsiElement(Py:PYXL TAGNAME)('div')(221,224) 152 | PsiWhiteSpace(' ')(224,225) 153 | PyKeywordArgumentImpl: null(225,252) 154 | PyxlAttrName: (225,244) 155 | PsiElement(Py:PYXL ATTRNAME)('namspaced:attribute')(225,244) 156 | PsiElement(Py:EQ)('=')(244,245) 157 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(245,246) 158 | PsiElement(Py:PYXL ATTRVALUE)('value')(246,251) 159 | PsiElement(Py:PYXL ATTRVALUE END)('"')(251,252) 160 | PsiWhiteSpace(' ')(252,253) 161 | PyxlArgumentList(253,253) 162 | 163 | PsiElement(Py:PYXL TAGENDANDCLOSE />)('/>')(253,255) -------------------------------------------------------------------------------- /testdata/Comments.py: -------------------------------------------------------------------------------- 1 | # coding: pyxl 2 | 3 | def stuff_with_comments(): 4 | return ( 5 | 6 | 7 | 9 | ]]> 10 | 11 | 12 | 13 | ) 14 | -------------------------------------------------------------------------------- /testdata/Comments.txt: -------------------------------------------------------------------------------- 1 | PyFile:Comments.py(0,249) 2 | PsiComment(Py:END_OF_LINE_COMMENT)('# coding: pyxl')(0,14) 3 | PsiWhiteSpace('\n\n')(14,16) 4 | PyFunction('stuff_with_comments')(16,249) 5 | PsiElement(Py:DEF_KEYWORD)('def')(16,19) 6 | PsiWhiteSpace(' ')(19,20) 7 | PsiElement(Py:IDENTIFIER)('stuff_with_comments')(20,39) 8 | PyParameterList(39,41) 9 | PsiElement(Py:LPAR)('(')(39,40) 10 | PsiElement(Py:RPAR)(')')(40,41) 11 | PsiElement(Py:COLON)(':')(41,42) 12 | PsiWhiteSpace('\n ')(42,47) 13 | PyStatementList(47,249) 14 | PyReturnStatement(47,249) 15 | PsiElement(Py:RETURN_KEYWORD)('return')(47,53) 16 | PsiWhiteSpace(' ')(53,54) 17 | PyParenthesizedExpression(54,249) 18 | PsiElement(Py:LPAR)('(')(54,55) 19 | PsiWhiteSpace('\n ')(55,62) 20 | Pyxl Tag: null(62,243) 21 | PsiElement(Py:PYXL TAGBEGIN <)('<')(62,63) 22 | PyCallExpression: frag(63,67) 23 | PyClassTagReference: x_frag(63,67) 24 | PsiElement(Py:PYXL TAGNAME)('frag')(63,67) 25 | PsiElement(Py:PYXL TAGEND >)('>')(67,68) 26 | PyArgumentList(68,236) 27 | PyStringLiteralExpression: (68,77) 28 | PsiElement(Py:PYXL STRING)('\n ')(68,77) 29 | PsiComment(Py:END_OF_LINE_COMMENT)('')(77,92) 30 | PyStringLiteralExpression: (92,101) 31 | PsiElement(Py:PYXL STRING)('\n ')(92,101) 32 | Pyxl Tag: null(101,229) 33 | PsiElement(Py:PYXL TAGBEGIN <)('<')(101,102) 34 | PyCallExpression: html(102,106) 35 | PyClassTagReference: x_html(102,106) 36 | PsiElement(Py:PYXL TAGNAME)('html')(102,106) 37 | PsiElement(Py:PYXL TAGEND >)('>')(106,107) 38 | PsiComment(Py:END_OF_LINE_COMMENT)('\n ]]>')(107,188) 39 | PyArgumentList(188,222) 40 | PyStringLiteralExpression: (188,197) 41 | PsiElement(Py:PYXL STRING)('\n ')(188,197) 42 | PsiComment(Py:END_OF_LINE_COMMENT)('')(197,213) 43 | PyStringLiteralExpression: (213,222) 44 | PsiElement(Py:PYXL STRING)('\n ')(213,222) 45 | PsiElement(Py:PYXL TAGCLOSE )('>')(228,229) 50 | PyStringLiteralExpression: (229,236) 51 | PsiElement(Py:PYXL STRING)('\n ')(229,236) 52 | PsiElement(Py:PYXL TAGCLOSE )('>')(242,243) 57 | PsiWhiteSpace('\n ')(243,248) 58 | PsiElement(Py:RPAR)(')')(248,249) -------------------------------------------------------------------------------- /testdata/ParsingTestData.py: -------------------------------------------------------------------------------- 1 | # coding: pyxl 2 | 3 | def x_hi(): 4 | pass 5 | 6 | def foo(): 7 | yy = 20 8 | scaley = (yy<5) or (yy>100) 9 | 10 | return ( 11 | 12 | 13 | goo 14 | 15 | 16 | zoo 17 | 18 | 19 | 20 | 21 | ) 22 | 23 | def unparenthesized_multiline_expression(): 24 | return 28 | 29 | def zoo(): 30 | str = "abcdefg" 31 | return( 32 | 33 | 34 | 35 | {str} 36 | 37 | 38 | 39 | 40 | ) 41 | 42 | return ( 43 | One 44 | ) 45 | 46 | def goo(): 47 | b = True 48 | return ( 55 | {5+5} 56 | 57 | ) 58 | -------------------------------------------------------------------------------- /testdata/ParsingTestData.txt: -------------------------------------------------------------------------------- 1 | PyFile:ParsingTestData.py(0,978) 2 | PsiComment(Py:END_OF_LINE_COMMENT)('# coding: pyxl')(0,14) 3 | PsiWhiteSpace('\n\n')(14,16) 4 | PyFunction('x_hi')(16,36) 5 | PsiElement(Py:DEF_KEYWORD)('def')(16,19) 6 | PsiWhiteSpace(' ')(19,20) 7 | PsiElement(Py:IDENTIFIER)('x_hi')(20,24) 8 | PyParameterList(24,26) 9 | PsiElement(Py:LPAR)('(')(24,25) 10 | PsiElement(Py:RPAR)(')')(25,26) 11 | PsiElement(Py:COLON)(':')(26,27) 12 | PsiWhiteSpace('\n ')(27,32) 13 | PyStatementList(32,36) 14 | PyPassStatement(32,36) 15 | PsiElement(Py:PASS_KEYWORD)('pass')(32,36) 16 | PsiWhiteSpace('\n\n')(36,38) 17 | PyFunction('foo')(38,334) 18 | PsiElement(Py:DEF_KEYWORD)('def')(38,41) 19 | PsiWhiteSpace(' ')(41,42) 20 | PsiElement(Py:IDENTIFIER)('foo')(42,45) 21 | PyParameterList(45,47) 22 | PsiElement(Py:LPAR)('(')(45,46) 23 | PsiElement(Py:RPAR)(')')(46,47) 24 | PsiElement(Py:COLON)(':')(47,48) 25 | PsiWhiteSpace('\n ')(48,53) 26 | PyStatementList(53,334) 27 | PyAssignmentStatement(53,60) 28 | PyTargetExpression: yy(53,55) 29 | PsiElement(Py:IDENTIFIER)('yy')(53,55) 30 | PsiWhiteSpace(' ')(55,56) 31 | PsiElement(Py:EQ)('=')(56,57) 32 | PsiWhiteSpace(' ')(57,58) 33 | PyNumericLiteralExpression(58,60) 34 | PsiElement(Py:INTEGER_LITERAL)('20')(58,60) 35 | PsiWhiteSpace('\n ')(60,65) 36 | PyAssignmentStatement(65,92) 37 | PyTargetExpression: scaley(65,71) 38 | PsiElement(Py:IDENTIFIER)('scaley')(65,71) 39 | PsiWhiteSpace(' ')(71,72) 40 | PsiElement(Py:EQ)('=')(72,73) 41 | PsiWhiteSpace(' ')(73,74) 42 | PyBinaryExpression(74,92) 43 | PyParenthesizedExpression(74,80) 44 | PsiElement(Py:LPAR)('(')(74,75) 45 | PyBinaryExpression(75,79) 46 | PyReferenceExpression: yy(75,77) 47 | PsiElement(Py:IDENTIFIER)('yy')(75,77) 48 | PsiElement(Py:LT)('<')(77,78) 49 | PyNumericLiteralExpression(78,79) 50 | PsiElement(Py:INTEGER_LITERAL)('5')(78,79) 51 | PsiElement(Py:RPAR)(')')(79,80) 52 | PsiWhiteSpace(' ')(80,81) 53 | PsiElement(Py:OR_KEYWORD)('or')(81,83) 54 | PsiWhiteSpace(' ')(83,84) 55 | PyParenthesizedExpression(84,92) 56 | PsiElement(Py:LPAR)('(')(84,85) 57 | PyBinaryExpression(85,91) 58 | PyReferenceExpression: yy(85,87) 59 | PsiElement(Py:IDENTIFIER)('yy')(85,87) 60 | PsiElement(Py:GT)('>')(87,88) 61 | PyNumericLiteralExpression(88,91) 62 | PsiElement(Py:INTEGER_LITERAL)('100')(88,91) 63 | PsiElement(Py:RPAR)(')')(91,92) 64 | PsiWhiteSpace('\n\n ')(92,98) 65 | PyReturnStatement(98,334) 66 | PsiElement(Py:RETURN_KEYWORD)('return')(98,104) 67 | PsiWhiteSpace(' ')(104,105) 68 | PyParenthesizedExpression(105,334) 69 | PsiElement(Py:LPAR)('(')(105,106) 70 | PsiWhiteSpace('\n ')(106,115) 71 | Pyxl Tag: null(115,328) 72 | PsiElement(Py:PYXL TAGBEGIN <)('<')(115,116) 73 | PyCallExpression: hi(116,118) 74 | PyClassTagReference: x_hi(116,118) 75 | PsiElement(Py:PYXL TAGNAME)('hi')(116,118) 76 | PsiElement(Py:PYXL TAGEND >)('>')(118,119) 77 | PyArgumentList(119,323) 78 | PyStringLiteralExpression: (119,131) 79 | PsiElement(Py:PYXL STRING)('\n ')(119,131) 80 | Pyxl Tag: null(131,184) 81 | PsiElement(Py:PYXL TAGBEGIN <)('<')(131,132) 82 | PsiElement(Py:PYXL BUILT IN TAG)('if')(132,134) 83 | PsiWhiteSpace(' ')(134,135) 84 | PyKeywordArgumentImpl: null(135,145) 85 | PyxlAttrName: (135,139) 86 | PsiElement(Py:PYXL ATTRNAME)('cond')(135,139) 87 | PsiElement(Py:EQ)('=')(139,140) 88 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(140,141) 89 | PsiElement(Py:PYXL ATTRVALUE)('moo')(141,144) 90 | PsiElement(Py:PYXL ATTRVALUE END)('"')(144,145) 91 | PsiElement(Py:PYXL TAGEND >)('>')(145,146) 92 | PyArgumentList(146,179) 93 | PyStringLiteralExpression: (146,179) 94 | PsiElement(Py:PYXL STRING)('\n goo\n ')(146,179) 95 | PsiElement(Py:PYXL TAGCLOSE )('>')(183,184) 98 | PyStringLiteralExpression: (184,197) 99 | PsiElement(Py:PYXL STRING)('\n ')(184,197) 100 | Pyxl Tag: null(197,243) 101 | PsiElement(Py:PYXL TAGBEGIN <)('<')(197,198) 102 | PsiElement(Py:PYXL BUILT IN TAG)('else')(198,202) 103 | PsiElement(Py:PYXL TAGEND >)('>')(202,203) 104 | PyArgumentList(203,236) 105 | PyStringLiteralExpression: (203,236) 106 | PsiElement(Py:PYXL STRING)('\n zoo\n ')(203,236) 107 | PsiElement(Py:PYXL TAGCLOSE )('>')(242,243) 110 | PyStringLiteralExpression: (243,256) 111 | PsiElement(Py:PYXL STRING)('\n ')(243,256) 112 | Pyxl Tag: null(256,314) 113 | PsiElement(Py:PYXL TAGBEGIN <)('<')(256,257) 114 | PyCallExpression: hi(257,259) 115 | PyClassTagReference: x_hi(257,259) 116 | PsiElement(Py:PYXL TAGNAME)('hi')(257,259) 117 | PsiWhiteSpace(' ')(259,260) 118 | PyKeywordArgumentImpl: null(260,267) 119 | PyxlAttrName: (260,263) 120 | PsiElement(Py:PYXL ATTRNAME)('boo')(260,263) 121 | PsiElement(Py:EQ)('=')(263,264) 122 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(264,265) 123 | PsiElement(Py:PYXL ATTRVALUE)('#')(265,266) 124 | PsiElement(Py:PYXL ATTRVALUE END)('"')(266,267) 125 | PsiWhiteSpace(' ')(267,268) 126 | PyKeywordArgumentImpl: null(268,295) 127 | PyxlAttrName: (268,270) 128 | PsiElement(Py:PYXL ATTRNAME)('id')(268,270) 129 | PsiElement(Py:EQ)('=')(270,271) 130 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(271,272) 131 | PsiElement(Py:PYXL ATTRVALUE)('Zasdf ')(272,278) 132 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(278,279) 133 | PsiWhiteSpace(' ')(279,280) 134 | PyStringLiteralExpression: (280,283) 135 | PsiElement(Py:SINGLE_QUOTED_STRING)('" "')(280,283) 136 | PsiWhiteSpace(' ')(283,284) 137 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(284,285) 138 | PsiElement(Py:PYXL ATTRVALUE)(' asdfjkl ')(285,294) 139 | PsiElement(Py:PYXL ATTRVALUE END)('"')(294,295) 140 | PsiElement(Py:PYXL TAGEND >)('>')(295,296) 141 | PyArgumentList(296,309) 142 | PyStringLiteralExpression: (296,309) 143 | PsiElement(Py:PYXL STRING)('\n ')(296,309) 144 | PsiElement(Py:PYXL TAGCLOSE )('>')(313,314) 149 | PyStringLiteralExpression: (314,323) 150 | PsiElement(Py:PYXL STRING)('\n ')(314,323) 151 | PsiElement(Py:PYXL TAGCLOSE )('>')(327,328) 156 | PsiWhiteSpace('\n ')(328,333) 157 | PsiElement(Py:RPAR)(')')(333,334) 158 | PsiWhiteSpace('\n\n')(334,336) 159 | PyFunction('unparenthesized_multiline_expression')(336,978) 160 | PsiElement(Py:DEF_KEYWORD)('def')(336,339) 161 | PsiWhiteSpace(' ')(339,340) 162 | PsiElement(Py:IDENTIFIER)('unparenthesized_multiline_expression')(340,376) 163 | PyParameterList(376,378) 164 | PsiElement(Py:LPAR)('(')(376,377) 165 | PsiElement(Py:RPAR)(')')(377,378) 166 | PsiElement(Py:COLON)(':')(378,379) 167 | PsiWhiteSpace('\n ')(379,384) 168 | PyStatementList(384,978) 169 | PyReturnStatement(384,394) 170 | PsiElement(Py:RETURN_KEYWORD)('return')(384,390) 171 | PsiWhiteSpace(' ')(390,391) 172 | Pyxl Tag: null(391,394) 173 | PsiElement(Py:PYXL TAGBEGIN <)('<')(391,392) 174 | PyCallExpression: hi(392,394) 175 | PyClassTagReference: x_hi(392,394) 176 | PsiElement(Py:PYXL TAGNAME)('hi')(392,394) 177 | PsiWhiteSpace('\n ')(394,403) 178 | PsiErrorElement:Unexpected indent(403,403) 179 | 180 | PsiElement(Py:PYXL ATTRNAME)('id')(403,405) 181 | PsiErrorElement:Statement expected, found Py:PYXL ATTRNAME(405,405) 182 | 183 | PsiElement(Py:EQ)('=')(405,406) 184 | PsiErrorElement:Statement expected, found Py:EQ(406,406) 185 | 186 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(406,407) 187 | PsiErrorElement:Statement expected, found Py:PYXL ATTRVALUE BEGIN(407,407) 188 | 189 | PsiElement(Py:PYXL ATTRVALUE)('foo')(407,410) 190 | PsiErrorElement:Statement expected, found Py:PYXL ATTRVALUE(410,410) 191 | 192 | PsiElement(Py:PYXL ATTRVALUE END)('"')(410,411) 193 | PsiErrorElement:Statement expected, found Py:PYXL ATTRVALUE END(411,411) 194 | 195 | PsiWhiteSpace('\n ')(411,420) 196 | PsiElement(Py:PYXL ATTRNAME)('class')(420,425) 197 | PsiErrorElement:Statement expected, found Py:PYXL ATTRNAME(425,425) 198 | 199 | PsiElement(Py:EQ)('=')(425,426) 200 | PsiErrorElement:Statement expected, found Py:EQ(426,426) 201 | 202 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(426,427) 203 | PsiErrorElement:Statement expected, found Py:PYXL ATTRVALUE BEGIN(427,427) 204 | 205 | PsiElement(Py:PYXL ATTRVALUE)('bar')(427,430) 206 | PsiErrorElement:Statement expected, found Py:PYXL ATTRVALUE(430,430) 207 | 208 | PsiElement(Py:PYXL ATTRVALUE END)('"')(430,431) 209 | PsiErrorElement:Statement expected, found Py:PYXL ATTRVALUE END(431,431) 210 | 211 | PsiErrorElement:Statement expected, found Py:DEDENT(431,431) 212 | 213 | PsiWhiteSpace('\n ')(431,436) 214 | PsiElement(Py:PYXL TAGEND >)('>')(436,437) 215 | PsiErrorElement:Statement expected, found Py:PYXL TAGEND >(437,437) 216 | 217 | PsiElement(Py:PYXL TAGCLOSE 220 | PsiElement(Py:PYXL TAGNAME)('hi')(439,441) 221 | PsiErrorElement:Statement expected, found Py:PYXL TAGNAME(441,441) 222 | 223 | PsiElement(Py:PYXL TAGEND >)('>')(441,442) 224 | PsiErrorElement:Statement expected, found Py:PYXL TAGEND >(442,442) 225 | 226 | PsiErrorElement:Statement expected, found Py:DEDENT(442,442) 227 | 228 | PsiWhiteSpace('\n\n')(442,444) 229 | PyFunction('zoo')(444,714) 230 | PsiElement(Py:DEF_KEYWORD)('def')(444,447) 231 | PsiWhiteSpace(' ')(447,448) 232 | PsiElement(Py:IDENTIFIER)('zoo')(448,451) 233 | PyParameterList(451,453) 234 | PsiElement(Py:LPAR)('(')(451,452) 235 | PsiElement(Py:RPAR)(')')(452,453) 236 | PsiElement(Py:COLON)(':')(453,454) 237 | PsiWhiteSpace('\n ')(454,459) 238 | PyStatementList(459,714) 239 | PyAssignmentStatement(459,474) 240 | PyTargetExpression: str(459,462) 241 | PsiElement(Py:IDENTIFIER)('str')(459,462) 242 | PsiWhiteSpace(' ')(462,463) 243 | PsiElement(Py:EQ)('=')(463,464) 244 | PsiWhiteSpace(' ')(464,465) 245 | PyStringLiteralExpression: abcdefg(465,474) 246 | PsiElement(Py:SINGLE_QUOTED_STRING)('"abcdefg"')(465,474) 247 | PsiWhiteSpace('\n ')(474,479) 248 | PyReturnStatement(479,638) 249 | PsiElement(Py:RETURN_KEYWORD)('return')(479,485) 250 | PyParenthesizedExpression(485,638) 251 | PsiElement(Py:LPAR)('(')(485,486) 252 | PsiWhiteSpace('\n ')(486,495) 253 | Pyxl Tag: null(495,632) 254 | PsiElement(Py:PYXL TAGBEGIN <)('<')(495,496) 255 | PyCallExpression: hi(496,498) 256 | PyClassTagReference: x_hi(496,498) 257 | PsiElement(Py:PYXL TAGNAME)('hi')(496,498) 258 | PsiWhiteSpace(' ')(498,499) 259 | PyKeywordArgumentImpl: null(499,514) 260 | PyxlAttrName: (499,501) 261 | PsiElement(Py:PYXL ATTRNAME)('id')(499,501) 262 | PsiElement(Py:EQ)('=')(501,502) 263 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(502,503) 264 | PsiElement(Py:PYXL ATTRVALUE)('foo-footer')(503,513) 265 | PsiElement(Py:PYXL ATTRVALUE END)('"')(513,514) 266 | PsiWhiteSpace(' ')(514,515) 267 | PyKeywordArgumentImpl: null(515,539) 268 | PyxlAttrName: (515,520) 269 | PsiElement(Py:PYXL ATTRNAME)('class')(515,520) 270 | PsiElement(Py:EQ)('=')(520,521) 271 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(521,522) 272 | PsiElement(Py:PYXL ATTRVALUE)('zoo-bar clearfix')(522,538) 273 | PsiElement(Py:PYXL ATTRVALUE END)('"')(538,539) 274 | PsiElement(Py:PYXL TAGEND >)('>')(539,540) 275 | PyArgumentList(540,627) 276 | PyStringLiteralExpression: (540,554) 277 | PsiElement(Py:PYXL STRING)('\n\n ')(540,554) 278 | Pyxl Tag: null(554,598) 279 | PsiElement(Py:PYXL TAGBEGIN <)('<')(554,555) 280 | PyCallExpression: hi(555,557) 281 | PyClassTagReference: x_hi(555,557) 282 | PsiElement(Py:PYXL TAGNAME)('hi')(555,557) 283 | PsiElement(Py:PYXL TAGEND >)('>')(557,558) 284 | PyArgumentList(558,593) 285 | PyStringLiteralExpression: (558,575) 286 | PsiElement(Py:PYXL STRING)('\n ')(558,575) 287 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(575,576) 288 | PyReferenceExpression: str(576,579) 289 | PsiElement(Py:IDENTIFIER)('str')(576,579) 290 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(579,580) 291 | PyStringLiteralExpression: (580,593) 292 | PsiElement(Py:PYXL STRING)('\n ')(580,593) 293 | PsiElement(Py:PYXL TAGCLOSE )('>')(597,598) 298 | PyStringLiteralExpression: (598,612) 299 | PsiElement(Py:PYXL STRING)('\n\n ')(598,612) 300 | Pyxl Tag: null(612,618) 301 | PsiElement(Py:PYXL TAGBEGIN <)('<')(612,613) 302 | PyCallExpression: hi(613,615) 303 | PyClassTagReference: x_hi(613,615) 304 | PsiElement(Py:PYXL TAGNAME)('hi')(613,615) 305 | PsiWhiteSpace(' ')(615,616) 306 | PyxlArgumentList(616,616) 307 | 308 | PsiElement(Py:PYXL TAGENDANDCLOSE />)('/>')(616,618) 309 | PyStringLiteralExpression: (618,627) 310 | PsiElement(Py:PYXL STRING)('\n ')(618,627) 311 | PsiElement(Py:PYXL TAGCLOSE )('>')(631,632) 316 | PsiWhiteSpace('\n ')(632,637) 317 | PsiElement(Py:RPAR)(')')(637,638) 318 | PsiWhiteSpace('\n\n ')(638,644) 319 | PyReturnStatement(644,714) 320 | PsiElement(Py:RETURN_KEYWORD)('return')(644,650) 321 | PsiWhiteSpace(' ')(650,651) 322 | PyParenthesizedExpression(651,714) 323 | PsiElement(Py:LPAR)('(')(651,652) 324 | PsiWhiteSpace(' ')(652,653) 325 | Pyxl Tag: null(653,713) 326 | PsiElement(Py:PYXL TAGBEGIN <)('<')(653,654) 327 | PyCallExpression: select_option(654,667) 328 | PyClassTagReference: x_select_option(654,667) 329 | PsiElement(Py:PYXL TAGNAME)('select_option')(654,667) 330 | PsiWhiteSpace(' ')(667,668) 331 | PyKeywordArgumentImpl: null(668,675) 332 | PyxlAttrName: (668,673) 333 | PsiElement(Py:PYXL ATTRNAME)('value')(668,673) 334 | PsiElement(Py:EQ)('=')(673,674) 335 | PsiElement(Py:PYXL ATTRVALUE)('1')(674,675) 336 | PsiElement(Py:PYXL TAGEND >)('>')(675,676) 337 | PyArgumentList(676,697) 338 | PyStringLiteralExpression: (676,697) 339 | PsiElement(Py:PYXL STRING)('\n One\n ')(676,697) 340 | PsiElement(Py:PYXL TAGCLOSE )('>')(712,713) 345 | PsiElement(Py:RPAR)(')')(713,714) 346 | PsiWhiteSpace('\n\n')(714,716) 347 | PyFunction('goo')(716,978) 348 | PsiElement(Py:DEF_KEYWORD)('def')(716,719) 349 | PsiWhiteSpace(' ')(719,720) 350 | PsiElement(Py:IDENTIFIER)('goo')(720,723) 351 | PyParameterList(723,725) 352 | PsiElement(Py:LPAR)('(')(723,724) 353 | PsiElement(Py:RPAR)(')')(724,725) 354 | PsiElement(Py:COLON)(':')(725,726) 355 | PsiWhiteSpace('\n ')(726,731) 356 | PyStatementList(731,978) 357 | PyAssignmentStatement(731,739) 358 | PyTargetExpression: b(731,732) 359 | PsiElement(Py:IDENTIFIER)('b')(731,732) 360 | PsiWhiteSpace(' ')(732,733) 361 | PsiElement(Py:EQ)('=')(733,734) 362 | PsiWhiteSpace(' ')(734,735) 363 | PyReferenceExpression: True(735,739) 364 | PsiElement(Py:IDENTIFIER)('True')(735,739) 365 | PsiWhiteSpace('\n ')(739,744) 366 | PyReturnStatement(744,978) 367 | PsiElement(Py:RETURN_KEYWORD)('return')(744,750) 368 | PsiWhiteSpace(' ')(750,751) 369 | PyParenthesizedExpression(751,978) 370 | PsiElement(Py:LPAR)('(')(751,752) 371 | Pyxl Tag: null(752,972) 372 | PsiElement(Py:PYXL TAGBEGIN <)('<')(752,753) 373 | PyCallExpression: hi(753,755) 374 | PyClassTagReference: x_hi(753,755) 375 | PsiElement(Py:PYXL TAGNAME)('hi')(753,755) 376 | PsiWhiteSpace('\n ')(755,764) 377 | PyKeywordArgumentImpl: null(764,838) 378 | PyxlAttrName: (764,769) 379 | PsiElement(Py:PYXL ATTRNAME)('class')(764,769) 380 | PsiElement(Py:EQ)('=')(769,770) 381 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(770,771) 382 | PsiElement(Py:PYXL ATTRVALUE)('string over two lines without backslash\n ')(771,819) 383 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(819,820) 384 | PyConditionalExpression(820,836) 385 | PyStringLiteralExpression: a(820,823) 386 | PsiElement(Py:SINGLE_QUOTED_STRING)(''a'')(820,823) 387 | PsiWhiteSpace(' ')(823,824) 388 | PsiElement(Py:IF_KEYWORD)('if')(824,826) 389 | PsiWhiteSpace(' ')(826,827) 390 | PyReferenceExpression: b(827,828) 391 | PsiElement(Py:IDENTIFIER)('b')(827,828) 392 | PsiWhiteSpace(' ')(828,829) 393 | PsiElement(Py:ELSE_KEYWORD)('else')(829,833) 394 | PsiWhiteSpace(' ')(833,834) 395 | PyStringLiteralExpression: (834,836) 396 | PsiElement(Py:SINGLE_QUOTED_STRING)('''')(834,836) 397 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(836,837) 398 | PsiElement(Py:PYXL ATTRVALUE END)('"')(837,838) 399 | PsiWhiteSpace('\n ')(838,847) 400 | PyKeywordArgumentImpl: null(847,859) 401 | PyxlAttrName: (847,852) 402 | PsiElement(Py:PYXL ATTRNAME)('prop2')(847,852) 403 | PsiElement(Py:EQ)('=')(852,853) 404 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(853,854) 405 | PsiElement(Py:PYXL ATTRVALUE)('true')(854,858) 406 | PsiElement(Py:PYXL ATTRVALUE END)('"')(858,859) 407 | PsiWhiteSpace(' ')(859,860) 408 | PyKeywordArgumentImpl: null(860,876) 409 | PyxlAttrName: (860,867) 410 | PsiElement(Py:PYXL ATTRNAME)('nohover')(860,867) 411 | PsiElement(Py:EQ)('=')(867,868) 412 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(868,869) 413 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(869,870) 414 | PyReferenceExpression: True(870,874) 415 | PsiElement(Py:IDENTIFIER)('True')(870,874) 416 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(874,875) 417 | PsiElement(Py:PYXL ATTRVALUE END)('"')(875,876) 418 | PsiWhiteSpace('\n ')(876,885) 419 | PyKeywordArgumentImpl: null(885,934) 420 | PyxlAttrName: (885,888) 421 | PsiElement(Py:PYXL ATTRNAME)('zoo')(885,888) 422 | PsiElement(Py:EQ)('=')(888,889) 423 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(889,890) 424 | PsiElement(Py:PYXL ATTRVALUE)('string with backslash \\n asdfjkl')(890,933) 425 | PsiElement(Py:PYXL ATTRVALUE END)('"')(933,934) 426 | PsiWhiteSpace('\n ')(934,943) 427 | PsiElement(Py:PYXL TAGEND >)('>')(943,944) 428 | PyArgumentList(944,967) 429 | PyStringLiteralExpression: (944,953) 430 | PsiElement(Py:PYXL STRING)('\n ')(944,953) 431 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(953,954) 432 | PyBinaryExpression(954,957) 433 | PyNumericLiteralExpression(954,955) 434 | PsiElement(Py:INTEGER_LITERAL)('5')(954,955) 435 | PsiElement(Py:PLUS)('+')(955,956) 436 | PyNumericLiteralExpression(956,957) 437 | PsiElement(Py:INTEGER_LITERAL)('5')(956,957) 438 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(957,958) 439 | PyStringLiteralExpression: (958,967) 440 | PsiElement(Py:PYXL STRING)('\n ')(958,967) 441 | PsiElement(Py:PYXL TAGCLOSE )('>')(971,972) 446 | PsiWhiteSpace('\n ')(972,977) 447 | PsiElement(Py:RPAR)(')')(977,978) -------------------------------------------------------------------------------- /testdata/TagNames.py: -------------------------------------------------------------------------------- 1 | # coding: pyxl 2 | regular =
    3 | module = 4 | -------------------------------------------------------------------------------- /testdata/TagNames.txt: -------------------------------------------------------------------------------- 1 | PyFile:TagNames.py(0,56) 2 | PsiComment(Py:END_OF_LINE_COMMENT)('# coding: pyxl')(0,14) 3 | PsiWhiteSpace('\n')(14,15) 4 | PyAssignmentStatement(15,32) 5 | PyTargetExpression: regular(15,22) 6 | PsiElement(Py:IDENTIFIER)('regular')(15,22) 7 | PsiWhiteSpace(' ')(22,23) 8 | PsiElement(Py:EQ)('=')(23,24) 9 | PsiWhiteSpace(' ')(24,25) 10 | Pyxl Tag: null(25,32) 11 | PsiElement(Py:PYXL TAGBEGIN <)('<')(25,26) 12 | PyCallExpression: div(26,29) 13 | PyClassTagReference: x_div(26,29) 14 | PsiElement(Py:PYXL TAGNAME)('div')(26,29) 15 | PsiWhiteSpace(' ')(29,30) 16 | PyxlArgumentList(30,30) 17 | 18 | PsiElement(Py:PYXL TAGENDANDCLOSE />)('/>')(30,32) 19 | PsiWhiteSpace('\n')(32,33) 20 | PyAssignmentStatement(33,56) 21 | PyTargetExpression: module(33,39) 22 | PsiElement(Py:IDENTIFIER)('module')(33,39) 23 | PsiWhiteSpace(' ')(39,40) 24 | PsiElement(Py:EQ)('=')(40,41) 25 | PsiWhiteSpace(' ')(41,42) 26 | Pyxl Tag: null(42,56) 27 | PsiElement(Py:PYXL TAGBEGIN <)('<')(42,43) 28 | PyCallExpression: module.tag(43,53) 29 | PyClassTagReference: x_tag(43,53) 30 | PyReferenceExpression: module(43,49) 31 | PsiElement(Py:PYXL TAGNAME_MODULE)('module')(43,49) 32 | PsiElement(Py:DOT)('.')(49,50) 33 | PsiElement(Py:PYXL TAGNAME)('tag')(50,53) 34 | PsiWhiteSpace(' ')(53,54) 35 | PyxlArgumentList(54,54) 36 | 37 | PsiElement(Py:PYXL TAGENDANDCLOSE />)('/>')(54,56) -------------------------------------------------------------------------------- /testdata/WithStatements.py: -------------------------------------------------------------------------------- 1 | # coding: pyxl 2 | 3 | with open('file.txt', 'r') as file: 4 | print file.readline() 5 | 6 | with open('foo.txt'), open('bar.txt'): 7 | print "baz" 8 | -------------------------------------------------------------------------------- /testdata/WithStatements.txt: -------------------------------------------------------------------------------- 1 | PyFile:WithStatements.py(0,133) 2 | PsiComment(Py:END_OF_LINE_COMMENT)('# coding: pyxl')(0,14) 3 | PsiWhiteSpace('\n\n')(14,16) 4 | PyWithStatement(16,77) 5 | PsiElement(Py:WITH_KEYWORD)('with')(16,20) 6 | PsiWhiteSpace(' ')(20,21) 7 | PyWithItem(21,50) 8 | PyCallExpression: open(21,42) 9 | PyReferenceExpression: open(21,25) 10 | PsiElement(Py:IDENTIFIER)('open')(21,25) 11 | PyArgumentList(25,42) 12 | PsiElement(Py:LPAR)('(')(25,26) 13 | PyStringLiteralExpression: file.txt(26,36) 14 | PsiElement(Py:SINGLE_QUOTED_STRING)(''file.txt'')(26,36) 15 | PsiElement(Py:COMMA)(',')(36,37) 16 | PsiWhiteSpace(' ')(37,38) 17 | PyStringLiteralExpression: r(38,41) 18 | PsiElement(Py:SINGLE_QUOTED_STRING)(''r'')(38,41) 19 | PsiElement(Py:RPAR)(')')(41,42) 20 | PsiWhiteSpace(' ')(42,43) 21 | PsiElement(Py:AS_KEYWORD)('as')(43,45) 22 | PsiWhiteSpace(' ')(45,46) 23 | PyTargetExpression: file(46,50) 24 | PsiElement(Py:IDENTIFIER)('file')(46,50) 25 | PsiElement(Py:COLON)(':')(50,51) 26 | PsiWhiteSpace('\n ')(51,56) 27 | PyStatementList(56,77) 28 | PyPrintStatement(56,77) 29 | PsiElement(Py:PRINT_KEYWORD)('print')(56,61) 30 | PsiWhiteSpace(' ')(61,62) 31 | PyCallExpression: file.readline(62,77) 32 | PyReferenceExpression: readline(62,75) 33 | PyReferenceExpression: file(62,66) 34 | PsiElement(Py:IDENTIFIER)('file')(62,66) 35 | PsiElement(Py:DOT)('.')(66,67) 36 | PsiElement(Py:IDENTIFIER)('readline')(67,75) 37 | PyArgumentList(75,77) 38 | PsiElement(Py:LPAR)('(')(75,76) 39 | PsiElement(Py:RPAR)(')')(76,77) 40 | PsiWhiteSpace('\n\n')(77,79) 41 | PyWithStatement(79,133) 42 | PsiElement(Py:WITH_KEYWORD)('with')(79,83) 43 | PsiWhiteSpace(' ')(83,84) 44 | PyWithItem(84,116) 45 | PyTupleExpression(84,116) 46 | PyCallExpression: open(84,99) 47 | PyReferenceExpression: open(84,88) 48 | PsiElement(Py:IDENTIFIER)('open')(84,88) 49 | PyArgumentList(88,99) 50 | PsiElement(Py:LPAR)('(')(88,89) 51 | PyStringLiteralExpression: foo.txt(89,98) 52 | PsiElement(Py:SINGLE_QUOTED_STRING)(''foo.txt'')(89,98) 53 | PsiElement(Py:RPAR)(')')(98,99) 54 | PsiElement(Py:COMMA)(',')(99,100) 55 | PsiWhiteSpace(' ')(100,101) 56 | PyCallExpression: open(101,116) 57 | PyReferenceExpression: open(101,105) 58 | PsiElement(Py:IDENTIFIER)('open')(101,105) 59 | PyArgumentList(105,116) 60 | PsiElement(Py:LPAR)('(')(105,106) 61 | PyStringLiteralExpression: bar.txt(106,115) 62 | PsiElement(Py:SINGLE_QUOTED_STRING)(''bar.txt'')(106,115) 63 | PsiElement(Py:RPAR)(')')(115,116) 64 | PsiElement(Py:COLON)(':')(116,117) 65 | PsiWhiteSpace('\n ')(117,122) 66 | PyStatementList(122,133) 67 | PyPrintStatement(122,133) 68 | PsiElement(Py:PRINT_KEYWORD)('print')(122,127) 69 | PsiWhiteSpace(' ')(127,128) 70 | PyStringLiteralExpression: baz(128,133) 71 | PsiElement(Py:SINGLE_QUOTED_STRING)('"baz"')(128,133) -------------------------------------------------------------------------------- /testdata/class_self_ref.py: -------------------------------------------------------------------------------- 1 | # coding: pyxl 2 | class x_g(): 3 | pass 4 | 5 | class x_cool_class(): 6 | foo = True 7 | def renderFooter(self): 8 | if self.foo: 9 | return {self.footer_items} 10 | -------------------------------------------------------------------------------- /testdata/class_self_ref.txt: -------------------------------------------------------------------------------- 1 | PyFile:class_self_ref.py(0,169) 2 | PsiComment(Py:END_OF_LINE_COMMENT)('# coding: pyxl')(0,14) 3 | PsiWhiteSpace('\n')(14,15) 4 | PyClass: x_g(15,36) 5 | PsiElement(Py:CLASS_KEYWORD)('class')(15,20) 6 | PsiWhiteSpace(' ')(20,21) 7 | PsiElement(Py:IDENTIFIER)('x_g')(21,24) 8 | PyArgumentList(24,26) 9 | PsiElement(Py:LPAR)('(')(24,25) 10 | PsiElement(Py:RPAR)(')')(25,26) 11 | PsiElement(Py:COLON)(':')(26,27) 12 | PsiWhiteSpace('\n ')(27,32) 13 | PyStatementList(32,36) 14 | PyPassStatement(32,36) 15 | PsiElement(Py:PASS_KEYWORD)('pass')(32,36) 16 | PsiWhiteSpace('\n\n')(36,38) 17 | PyClass: x_cool_class(38,169) 18 | PsiElement(Py:CLASS_KEYWORD)('class')(38,43) 19 | PsiWhiteSpace(' ')(43,44) 20 | PsiElement(Py:IDENTIFIER)('x_cool_class')(44,56) 21 | PyArgumentList(56,58) 22 | PsiElement(Py:LPAR)('(')(56,57) 23 | PsiElement(Py:RPAR)(')')(57,58) 24 | PsiElement(Py:COLON)(':')(58,59) 25 | PsiWhiteSpace('\n ')(59,64) 26 | PyStatementList(64,169) 27 | PyAssignmentStatement(64,74) 28 | PyTargetExpression: foo(64,67) 29 | PsiElement(Py:IDENTIFIER)('foo')(64,67) 30 | PsiWhiteSpace(' ')(67,68) 31 | PsiElement(Py:EQ)('=')(68,69) 32 | PsiWhiteSpace(' ')(69,70) 33 | PyReferenceExpression: True(70,74) 34 | PsiElement(Py:IDENTIFIER)('True')(70,74) 35 | PsiWhiteSpace('\n ')(74,79) 36 | PyFunction('renderFooter')(79,169) 37 | PsiElement(Py:DEF_KEYWORD)('def')(79,82) 38 | PsiWhiteSpace(' ')(82,83) 39 | PsiElement(Py:IDENTIFIER)('renderFooter')(83,95) 40 | PyParameterList(95,101) 41 | PsiElement(Py:LPAR)('(')(95,96) 42 | PyNamedParameter('self')(96,100) 43 | PsiElement(Py:IDENTIFIER)('self')(96,100) 44 | PsiElement(Py:RPAR)(')')(100,101) 45 | PsiElement(Py:COLON)(':')(101,102) 46 | PsiWhiteSpace('\n ')(102,111) 47 | PyStatementList(111,169) 48 | PyIfStatement(111,169) 49 | PyIfPartIf(111,169) 50 | PsiElement(Py:IF_KEYWORD)('if')(111,113) 51 | PsiWhiteSpace(' ')(113,114) 52 | PyReferenceExpression: foo(114,122) 53 | PyReferenceExpression: self(114,118) 54 | PsiElement(Py:IDENTIFIER)('self')(114,118) 55 | PsiElement(Py:DOT)('.')(118,119) 56 | PsiElement(Py:IDENTIFIER)('foo')(119,122) 57 | PsiElement(Py:COLON)(':')(122,123) 58 | PsiWhiteSpace('\n ')(123,136) 59 | PyStatementList(136,169) 60 | PyReturnStatement(136,169) 61 | PsiElement(Py:RETURN_KEYWORD)('return')(136,142) 62 | PsiWhiteSpace(' ')(142,143) 63 | Pyxl Tag: null(143,169) 64 | PsiElement(Py:PYXL TAGBEGIN <)('<')(143,144) 65 | PyCallExpression: g(144,145) 66 | PyClassTagReference: x_g(144,145) 67 | PsiElement(Py:PYXL TAGNAME)('g')(144,145) 68 | PsiElement(Py:PYXL TAGEND >)('>')(145,146) 69 | PyArgumentList(146,165) 70 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(146,147) 71 | PyReferenceExpression: footer_items(147,164) 72 | PyReferenceExpression: self(147,151) 73 | PsiElement(Py:IDENTIFIER)('self')(147,151) 74 | PsiElement(Py:DOT)('.')(151,152) 75 | PsiElement(Py:IDENTIFIER)('footer_items')(152,164) 76 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(164,165) 77 | PsiElement(Py:PYXL TAGCLOSE )('>')(168,169) -------------------------------------------------------------------------------- /testdata/nestedembed.py: -------------------------------------------------------------------------------- 1 | # coding: pyxl 2 | 3 | class x_a(): 4 | pass 5 | 6 | class x_b(): 7 | pass 8 | 9 | def foo(): 10 | zoo = {'choice1':'value1', } 11 | return ( 12 | 13 | {[ 14 | {v} 15 | for k, v in zoo.items() 16 | ]} 17 | 18 | ) -------------------------------------------------------------------------------- /testdata/nestedembed.txt: -------------------------------------------------------------------------------- 1 | PyFile:nestedembed.py(0,274) 2 | PsiComment(Py:END_OF_LINE_COMMENT)('# coding: pyxl')(0,14) 3 | PsiWhiteSpace('\n\n')(14,16) 4 | PyClass: x_a(16,37) 5 | PsiElement(Py:CLASS_KEYWORD)('class')(16,21) 6 | PsiWhiteSpace(' ')(21,22) 7 | PsiElement(Py:IDENTIFIER)('x_a')(22,25) 8 | PyArgumentList(25,27) 9 | PsiElement(Py:LPAR)('(')(25,26) 10 | PsiElement(Py:RPAR)(')')(26,27) 11 | PsiElement(Py:COLON)(':')(27,28) 12 | PsiWhiteSpace('\n ')(28,33) 13 | PyStatementList(33,37) 14 | PyPassStatement(33,37) 15 | PsiElement(Py:PASS_KEYWORD)('pass')(33,37) 16 | PsiWhiteSpace('\n\n')(37,39) 17 | PyClass: x_b(39,60) 18 | PsiElement(Py:CLASS_KEYWORD)('class')(39,44) 19 | PsiWhiteSpace(' ')(44,45) 20 | PsiElement(Py:IDENTIFIER)('x_b')(45,48) 21 | PyArgumentList(48,50) 22 | PsiElement(Py:LPAR)('(')(48,49) 23 | PsiElement(Py:RPAR)(')')(49,50) 24 | PsiElement(Py:COLON)(':')(50,51) 25 | PsiWhiteSpace('\n ')(51,56) 26 | PyStatementList(56,60) 27 | PyPassStatement(56,60) 28 | PsiElement(Py:PASS_KEYWORD)('pass')(56,60) 29 | PsiWhiteSpace('\n\n')(60,62) 30 | PyFunction('foo')(62,274) 31 | PsiElement(Py:DEF_KEYWORD)('def')(62,65) 32 | PsiWhiteSpace(' ')(65,66) 33 | PsiElement(Py:IDENTIFIER)('foo')(66,69) 34 | PyParameterList(69,71) 35 | PsiElement(Py:LPAR)('(')(69,70) 36 | PsiElement(Py:RPAR)(')')(70,71) 37 | PsiElement(Py:COLON)(':')(71,72) 38 | PsiWhiteSpace('\n ')(72,77) 39 | PyStatementList(77,274) 40 | PyAssignmentStatement(77,105) 41 | PyTargetExpression: zoo(77,80) 42 | PsiElement(Py:IDENTIFIER)('zoo')(77,80) 43 | PsiWhiteSpace(' ')(80,81) 44 | PsiElement(Py:EQ)('=')(81,82) 45 | PsiWhiteSpace(' ')(82,83) 46 | PyDictLiteralExpression(83,105) 47 | PsiElement(Py:LBRACE)('{')(83,84) 48 | PyKeyValueExpression(84,102) 49 | PyStringLiteralExpression: choice1(84,93) 50 | PsiElement(Py:SINGLE_QUOTED_STRING)(''choice1'')(84,93) 51 | PsiElement(Py:COLON)(':')(93,94) 52 | PyStringLiteralExpression: value1(94,102) 53 | PsiElement(Py:SINGLE_QUOTED_STRING)(''value1'')(94,102) 54 | PsiElement(Py:COMMA)(',')(102,103) 55 | PsiWhiteSpace(' ')(103,104) 56 | PsiElement(Py:RBRACE)('}')(104,105) 57 | PsiWhiteSpace('\n ')(105,110) 58 | PyReturnStatement(110,274) 59 | PsiElement(Py:RETURN_KEYWORD)('return')(110,116) 60 | PsiWhiteSpace(' ')(116,117) 61 | PyParenthesizedExpression(117,274) 62 | PsiElement(Py:LPAR)('(')(117,118) 63 | PsiWhiteSpace('\n ')(118,127) 64 | Pyxl Tag: null(127,268) 65 | PsiElement(Py:PYXL TAGBEGIN <)('<')(127,128) 66 | PyCallExpression: a(128,129) 67 | PyClassTagReference: x_a(128,129) 68 | PsiElement(Py:PYXL TAGNAME)('a')(128,129) 69 | PsiWhiteSpace(' ')(129,130) 70 | PyKeywordArgumentImpl: null(130,141) 71 | PyxlAttrName: (130,134) 72 | PsiElement(Py:PYXL ATTRNAME)('name')(130,134) 73 | PsiElement(Py:EQ)('=')(134,135) 74 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(135,136) 75 | PsiElement(Py:PYXL ATTRVALUE)('abcd')(136,140) 76 | PsiElement(Py:PYXL ATTRVALUE END)('"')(140,141) 77 | PsiElement(Py:PYXL TAGEND >)('>')(141,142) 78 | PyArgumentList(142,264) 79 | PyStringLiteralExpression: (142,155) 80 | PsiElement(Py:PYXL STRING)('\n ')(142,155) 81 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(155,156) 82 | PyListCompExpression(156,254) 83 | PsiElement(Py:LBRACKET)('[')(156,157) 84 | PsiWhiteSpace('\n ')(157,174) 85 | Pyxl Tag: null(174,196) 86 | PsiElement(Py:PYXL TAGBEGIN <)('<')(174,175) 87 | PyCallExpression: b(175,176) 88 | PyClassTagReference: x_b(175,176) 89 | PsiElement(Py:PYXL TAGNAME)('b')(175,176) 90 | PsiWhiteSpace(' ')(176,177) 91 | PyKeywordArgumentImpl: null(177,188) 92 | PyxlAttrName: (177,182) 93 | PsiElement(Py:PYXL ATTRNAME)('value')(177,182) 94 | PsiElement(Py:EQ)('=')(182,183) 95 | PsiElement(Py:PYXL ATTRVALUE BEGIN)('"')(183,184) 96 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(184,185) 97 | PyReferenceExpression: k(185,186) 98 | PsiElement(Py:IDENTIFIER)('k')(185,186) 99 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(186,187) 100 | PsiElement(Py:PYXL ATTRVALUE END)('"')(187,188) 101 | PsiElement(Py:PYXL TAGEND >)('>')(188,189) 102 | PyArgumentList(189,192) 103 | PsiElement(Py:PYXL PYTHON EMBED BEGIN {)('{')(189,190) 104 | PyReferenceExpression: v(190,191) 105 | PsiElement(Py:IDENTIFIER)('v')(190,191) 106 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(191,192) 107 | PsiElement(Py:PYXL TAGCLOSE )('>')(195,196) 112 | PsiWhiteSpace('\n ')(196,217) 113 | PsiElement(Py:FOR_KEYWORD)('for')(217,220) 114 | PsiWhiteSpace(' ')(220,221) 115 | PyTupleExpression(221,225) 116 | PyTargetExpression: k(221,222) 117 | PsiElement(Py:IDENTIFIER)('k')(221,222) 118 | PsiElement(Py:COMMA)(',')(222,223) 119 | PsiWhiteSpace(' ')(223,224) 120 | PyTargetExpression: v(224,225) 121 | PsiElement(Py:IDENTIFIER)('v')(224,225) 122 | PsiWhiteSpace(' ')(225,226) 123 | PsiElement(Py:IN_KEYWORD)('in')(226,228) 124 | PsiWhiteSpace(' ')(228,229) 125 | PyCallExpression: zoo.items(229,240) 126 | PyReferenceExpression: items(229,238) 127 | PyReferenceExpression: zoo(229,232) 128 | PsiElement(Py:IDENTIFIER)('zoo')(229,232) 129 | PsiElement(Py:DOT)('.')(232,233) 130 | PsiElement(Py:IDENTIFIER)('items')(233,238) 131 | PyArgumentList(238,240) 132 | PsiElement(Py:LPAR)('(')(238,239) 133 | PsiElement(Py:RPAR)(')')(239,240) 134 | PsiWhiteSpace('\n ')(240,253) 135 | PsiElement(Py:RBRACKET)(']')(253,254) 136 | PsiElement(Py:PYXL PYTHON EMBED END })('}')(254,255) 137 | PyStringLiteralExpression: (255,264) 138 | PsiElement(Py:PYXL STRING)('\n ')(255,264) 139 | PsiElement(Py:PYXL TAGCLOSE )('>')(267,268) 144 | PsiWhiteSpace('\n ')(268,273) 145 | PsiElement(Py:RPAR)(')')(273,274) -------------------------------------------------------------------------------- /tests/PyxlParsingTest.java: -------------------------------------------------------------------------------- 1 | 2 | import com.christofferklang.pyxl.parsing.PyxlParserDefinition; 3 | import com.intellij.testFramework.ParsingTestCase; 4 | import com.jetbrains.python.PythonDialectsTokenSetContributor; 5 | import com.jetbrains.python.PythonTokenSetContributor; 6 | 7 | public class PyxlParsingTest extends ParsingTestCase { 8 | /** 9 | * These tests work like this: 10 | * - They look at the name of the method, without the test-part to 11 | * figure out which files to use under testdata. 12 | * 13 | * I.e. adding a method called testMyTest() { doTest(true); } here 14 | * will run the file "testdata/MyTest.py" through the parser and expect the output 15 | * from that run to match the PSI tree decribed in "testdata/MyTest.txt". 16 | * 17 | * If the text file is not there on the first run of the test, one will be created 18 | * with the _current_ parser definition. 19 | */ 20 | 21 | public void testParsingTestData() { 22 | doTest(true); 23 | } 24 | 25 | public void testnestedembed() { 26 | doTest(true); 27 | } 28 | 29 | public void testclass_self_ref() { 30 | doTest(true); 31 | } 32 | 33 | public void testWithStatements() { 34 | doTest(true); 35 | } 36 | 37 | public void testComments() { 38 | doTest(true); 39 | } 40 | 41 | public void testTagNames() { 42 | doTest(true); 43 | } 44 | 45 | public void testAttributes() { 46 | doTest(true); 47 | } 48 | 49 | public PyxlParsingTest() { 50 | super("", "py", new PyxlParserDefinition()); 51 | } 52 | 53 | @Override 54 | protected void setUp() throws Exception { 55 | super.setUp(); 56 | // Following lines learned from python plugin's test case - PythonParsingTest.java 57 | registerExtensionPoint(PythonDialectsTokenSetContributor.EP_NAME, PythonDialectsTokenSetContributor.class); 58 | registerExtension(PythonDialectsTokenSetContributor.EP_NAME, new PythonTokenSetContributor()); 59 | 60 | } 61 | @Override 62 | protected String getTestDataPath() { 63 | return "../pycharm-pyxl/testdata/"; 64 | } 65 | 66 | @Override 67 | protected boolean skipSpaces() { 68 | return false; 69 | } 70 | 71 | @Override 72 | protected boolean includeRanges() { 73 | return true; 74 | } 75 | } 76 | --------------------------------------------------------------------------------