├── .gitignore ├── .scalafmt.conf ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── CodeCompletion.png └── StructureView.png ├── settings.gradle └── src ├── main ├── java │ └── amailp │ │ └── intellij │ │ └── robot │ │ ├── elements │ │ └── RobotTokenTypes.java │ │ ├── extensions │ │ └── ParserDefinition.java │ │ ├── file │ │ └── FileType.java │ │ └── lexer │ │ └── RobotLexer.java ├── resources │ ├── META-INF │ │ ├── MANIFEST.MF │ │ ├── plugin.xml │ │ └── robot-python.xml │ ├── amailp │ │ └── intellij │ │ │ └── robot │ │ │ └── lexer │ │ │ └── Robot.flex │ ├── fileTemplates │ │ └── internal │ │ │ ├── Robot Keywords.robot.ft │ │ │ └── Robot Test Suite.robot.ft │ └── icons │ │ ├── keyword_16.png │ │ ├── keywords_16.png │ │ ├── robot_16.png │ │ ├── robot_file_16.png │ │ ├── robot_toolwin_13.png │ │ ├── variable_16.png │ │ └── variables_16.png └── scala │ └── amailp │ └── intellij │ └── robot │ ├── actions │ └── NewRobotFileAction.scala │ ├── ast │ └── package.scala │ ├── extensions │ ├── Annotator.scala │ ├── Commenter.scala │ ├── DeclarationSearcher.scala │ ├── ElementDescriptionProvider.scala │ ├── FindUsagesProvider.scala │ ├── LibraryReferenceContributor.scala │ ├── NamesValidator.scala │ ├── PythonKeywordReferenceContributor.scala │ ├── RobotLibrariesCompletionContributor.scala │ ├── RobotPsiStructureViewFactory.scala │ ├── SyntaxHighlighterFactory.scala │ └── UsageTypeProvider.scala │ ├── file │ └── Icons.scala │ ├── findUsage │ ├── UsageFindable.scala │ ├── UsageTypes.scala │ └── WordsScanner.scala │ ├── highlighting │ └── SyntaxHighlighter.scala │ ├── lang │ └── RobotLanguage.scala │ ├── lexer │ └── RobotIElementType.scala │ ├── parser │ ├── PsiElementBuilder.scala │ ├── RobotParser.scala │ ├── SettingParser.scala │ └── package.scala │ ├── psi │ ├── Keyword.scala │ ├── KeywordDefinition.scala │ ├── KeywordName.scala │ ├── LibraryValue.scala │ ├── ResourceValue.scala │ ├── RobotPsiElement.scala │ ├── RobotPsiFile.scala │ ├── TestCaseDefinition.scala │ ├── VariableDefinition.scala │ ├── manipulator │ │ ├── Keyword.scala │ │ ├── LibraryValue.scala │ │ └── ResourceValue.scala │ ├── package.scala │ ├── reference │ │ ├── KeywordToDefinitionReference.scala │ │ ├── LibraryToDefinitionReference.scala │ │ ├── MethodFinderWithoutUnderscoresAndSpaces.scala │ │ ├── PythonKeywordToDefinitionReference.scala │ │ └── ResourceValueReference.scala │ └── utils │ │ ├── ExtRobotPsiUtils.scala │ │ └── RobotPsiUtils.scala │ └── structureView │ ├── InStructureView.scala │ ├── RobotStructureViewModel.scala │ └── RobotTreeBasedStructureViewBuilder.scala └── test ├── java └── amailp │ └── intellij │ └── robot │ └── parser │ └── ParserTest.java ├── resources ├── LibraryReferencesTest │ ├── ClassA.py │ ├── module_1 │ │ ├── module_2 │ │ │ └── module_c.py │ │ └── module_b.py │ ├── module_a.py │ ├── using_ClassA.robot │ ├── using_ClassA_ClassA.robot │ ├── using_ClassA_ClassB.robot │ ├── using_ClassA_with_synonym.robot │ ├── using_ClassC_with_args_and_synonym.robot │ ├── using_module_1_module_2_module_c.robot │ ├── using_module_1_module_b.robot │ ├── using_module_2_module_c.robot │ ├── using_module_a.robot │ ├── using_module_a_wrong_case.robot │ └── using_module_b_no_package.robot ├── PythonKeywordToDefinitionReferenceTest │ ├── DerivedClass.py │ ├── caret_inside_derived_class_keyword.robot │ ├── caret_inside_hello.robot │ ├── caret_inside_hello_world.robot │ ├── caret_inside_keyword_from_class_method.robot │ ├── caret_inside_multiple_defined.robot │ ├── caret_inside_no_underscores.robot │ ├── caret_inside_to_be_ignored.robot │ ├── keyword_with_curly_braces.robot │ ├── librarySynonyms │ │ ├── caret_inside_class_keyword.robot │ │ ├── caret_inside_class_keyword_multiple_defined.robot │ │ ├── caret_inside_class_keyword_with_dotted_prefix.robot │ │ ├── caret_inside_class_keyword_with_library_prefix.robot │ │ ├── caret_inside_class_keyword_with_lowercase_prefix.robot │ │ └── caret_inside_library_with_args_reference.robot │ └── module_a.py ├── RobotPsiFileTest │ ├── include_some_keywords.robot │ ├── include_some_keywords_with_path_separator.robot │ ├── include_some_other_keywords.robot │ ├── some_keywords.robot │ └── some_other_keywords.robot ├── RobotStructureTest │ └── one_variable.robot ├── complete.robot └── library.py └── scala └── amailp └── intellij └── robot ├── extensions ├── AnnotatorTest.scala └── RobotStructureTest.scala ├── lexer ├── BaseLexerTest.scala ├── TestCells.scala └── TestHeaders.scala ├── psi ├── RobotPsiFileTest.scala └── reference │ ├── LibraryToDefinitionReferenceTest.scala │ └── PythonKeywordToDefinitionReferenceTest.scala └── testFramework └── RobotCodeInsightFixtureTestCase.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | /.idea 3 | *.iml 4 | 5 | # Downloaded plugin dependencies 6 | /lib 7 | 8 | # Generated sources 9 | /src/main/java-gen 10 | 11 | # Gradle 12 | /.gradle 13 | /build 14 | 15 | #Mac 16 | .DS_store 17 | 18 | /src/test/resources/.idea 19 | *.py[cod] 20 | 21 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version=2.0.1 2 | maxColumn = 120 3 | lineEndings=preserve 4 | docstrings = JavaDoc 5 | align = none 6 | align.openParenDefnSite = true 7 | align.openParenCallSite = true 8 | align.tokens = [] 9 | continuationIndent.callSite = 4 10 | continuationIndent.extendSite = 4 11 | binPack.literalArgumentLists = true 12 | binPack.literalsExclude = [] 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | scala: 4 | - 2.13.3 5 | 6 | jdk: openjdk11 7 | 8 | # Intellij versions that are checked in CI: 9 | # 10 | # Usually the list contains least one for each major version between 11 | # "sinceBuild" and "untilBuild" (from build.gradle). 12 | 13 | env: 14 | - IDEA_VERSION=IC-2021.1 15 | - IDEA_VERSION=IC-2021.3 16 | - IDEA_VERSION=IC-2022.1 17 | 18 | jobs: 19 | include: 20 | - stage: test 21 | script: 22 | - ./gradlew --console=plain check verifyPlugin 23 | - stage: release 24 | if: tag IS present 25 | script: 26 | - ./gradlew --console=plain publishPlugin 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Release checklist 2 | 3 | - Cleanup change notes for the new version in `src/main/resources/META-INF/plugin.xml` 4 | - Make sure `sinceBuild` and `untilBuild` are updated in `build.gradle` and support the latest released version of IntelliJ (optimally also the next EAP). 5 | - Ensure that the plugin is checked in CI together with the `untilBuild` IntelliJ version. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | ========================== 3 | 4 | Version 3, 29 June 2007 5 | 6 | Copyright © 2007 Free Software Foundation, Inc. <> 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this license 9 | document, but changing it is not allowed. 10 | 11 | ## Preamble 12 | 13 | The GNU General Public License is a free, copyleft license for software and other 14 | kinds of works. 15 | 16 | The licenses for most software and other practical works are designed to take away 17 | your freedom to share and change the works. By contrast, the GNU General Public 18 | License is intended to guarantee your freedom to share and change all versions of a 19 | program--to make sure it remains free software for all its users. We, the Free 20 | Software Foundation, use the GNU General Public License for most of our software; it 21 | applies also to any other work released this way by its authors. You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not price. Our General 25 | Public Licenses are designed to make sure that you have the freedom to distribute 26 | copies of free software (and charge for them if you wish), that you receive source 27 | code or can get it if you want it, that you can change the software or use pieces of 28 | it in new free programs, and that you know you can do these things. 29 | 30 | To protect your rights, we need to prevent others from denying you these rights or 31 | asking you to surrender the rights. Therefore, you have certain responsibilities if 32 | you distribute copies of the software, or if you modify it: responsibilities to 33 | respect the freedom of others. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a fee, 36 | you must pass on to the recipients the same freedoms that you received. You must make 37 | sure that they, too, receive or can get the source code. And you must show them these 38 | terms so they know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: (1) assert 41 | copyright on the software, and (2) offer you this License giving you legal permission 42 | to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains that there is 45 | no warranty for this free software. For both users' and authors' sake, the GPL 46 | requires that modified versions be marked as changed, so that their problems will not 47 | be attributed erroneously to authors of previous versions. 48 | 49 | Some devices are designed to deny users access to install or run modified versions of 50 | the software inside them, although the manufacturer can do so. This is fundamentally 51 | incompatible with the aim of protecting users' freedom to change the software. The 52 | systematic pattern of such abuse occurs in the area of products for individuals to 53 | use, which is precisely where it is most unacceptable. Therefore, we have designed 54 | this version of the GPL to prohibit the practice for those products. If such problems 55 | arise substantially in other domains, we stand ready to extend this provision to 56 | those domains in future versions of the GPL, as needed to protect the freedom of 57 | users. 58 | 59 | Finally, every program is threatened constantly by software patents. States should 60 | not allow patents to restrict development and use of software on general-purpose 61 | computers, but in those that do, we wish to avoid the special danger that patents 62 | applied to a free program could make it effectively proprietary. To prevent this, the 63 | GPL assures that patents cannot be used to render the program non-free. 64 | 65 | The precise terms and conditions for copying, distribution and modification follow. 66 | 67 | ## TERMS AND CONDITIONS 68 | 69 | ### 0. Definitions. 70 | 71 | “This License” refers to version 3 of the GNU General Public License. 72 | 73 | “Copyright” also means copyright-like laws that apply to other kinds of 74 | works, such as semiconductor masks. 75 | 76 | “The Program” refers to any copyrightable work licensed under this 77 | License. Each licensee is addressed as “you”. “Licensees” and 78 | “recipients” may be individuals or organizations. 79 | 80 | To “modify” a work means to copy from or adapt all or part of the work in 81 | a fashion requiring copyright permission, other than the making of an exact copy. The 82 | resulting work is called a “modified version” of the earlier work or a 83 | work “based on” the earlier work. 84 | 85 | A “covered work” means either the unmodified Program or a work based on 86 | the Program. 87 | 88 | To “propagate” a work means to do anything with it that, without 89 | permission, would make you directly or secondarily liable for infringement under 90 | applicable copyright law, except executing it on a computer or modifying a private 91 | copy. Propagation includes copying, distribution (with or without modification), 92 | making available to the public, and in some countries other activities as well. 93 | 94 | To “convey” a work means any kind of propagation that enables other 95 | parties to make or receive copies. Mere interaction with a user through a computer 96 | network, with no transfer of a copy, is not conveying. 97 | 98 | An interactive user interface displays “Appropriate Legal Notices” to the 99 | extent that it includes a convenient and prominently visible feature that (1) 100 | displays an appropriate copyright notice, and (2) tells the user that there is no 101 | warranty for the work (except to the extent that warranties are provided), that 102 | licensees may convey the work under this License, and how to view a copy of this 103 | License. If the interface presents a list of user commands or options, such as a 104 | menu, a prominent item in the list meets this criterion. 105 | 106 | ### 1. Source Code. 107 | 108 | The “source code” for a work means the preferred form of the work for 109 | making modifications to it. “Object code” means any non-source form of a 110 | work. 111 | 112 | A “Standard Interface” means an interface that either is an official 113 | standard defined by a recognized standards body, or, in the case of interfaces 114 | specified for a particular programming language, one that is widely used among 115 | developers working in that language. 116 | 117 | The “System Libraries” of an executable work include anything, other than 118 | the work as a whole, that (a) is included in the normal form of packaging a Major 119 | Component, but which is not part of that Major Component, and (b) serves only to 120 | enable use of the work with that Major Component, or to implement a Standard 121 | Interface for which an implementation is available to the public in source code form. 122 | A “Major Component”, in this context, means a major essential component 123 | (kernel, window system, and so on) of the specific operating system (if any) on which 124 | the executable work runs, or a compiler used to produce the work, or an object code 125 | interpreter used to run it. 126 | 127 | The “Corresponding Source” for a work in object code form means all the 128 | source code needed to generate, install, and (for an executable work) run the object 129 | code and to modify the work, including scripts to control those activities. However, 130 | it does not include the work's System Libraries, or general-purpose tools or 131 | generally available free programs which are used unmodified in performing those 132 | activities but which are not part of the work. For example, Corresponding Source 133 | includes interface definition files associated with source files for the work, and 134 | the source code for shared libraries and dynamically linked subprograms that the work 135 | is specifically designed to require, such as by intimate data communication or 136 | control flow between those subprograms and other parts of the work. 137 | 138 | The Corresponding Source need not include anything that users can regenerate 139 | automatically from other parts of the Corresponding Source. 140 | 141 | The Corresponding Source for a work in source code form is that same work. 142 | 143 | ### 2. Basic Permissions. 144 | 145 | All rights granted under this License are granted for the term of copyright on the 146 | Program, and are irrevocable provided the stated conditions are met. This License 147 | explicitly affirms your unlimited permission to run the unmodified Program. The 148 | output from running a covered work is covered by this License only if the output, 149 | given its content, constitutes a covered work. This License acknowledges your rights 150 | of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not convey, without 153 | conditions so long as your license otherwise remains in force. You may convey covered 154 | works to others for the sole purpose of having them make modifications exclusively 155 | for you, or provide you with facilities for running those works, provided that you 156 | comply with the terms of this License in conveying all material for which you do not 157 | control copyright. Those thus making or running the covered works for you must do so 158 | exclusively on your behalf, under your direction and control, on terms that prohibit 159 | them from making any copies of your copyrighted material outside their relationship 160 | with you. 161 | 162 | Conveying under any other circumstances is permitted solely under the conditions 163 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 164 | 165 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 166 | 167 | No covered work shall be deemed part of an effective technological measure under any 168 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 169 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 170 | of such measures. 171 | 172 | When you convey a covered work, you waive any legal power to forbid circumvention of 173 | technological measures to the extent such circumvention is effected by exercising 174 | rights under this License with respect to the covered work, and you disclaim any 175 | intention to limit operation or modification of the work as a means of enforcing, 176 | against the work's users, your or third parties' legal rights to forbid circumvention 177 | of technological measures. 178 | 179 | ### 4. Conveying Verbatim Copies. 180 | 181 | You may convey verbatim copies of the Program's source code as you receive it, in any 182 | medium, provided that you conspicuously and appropriately publish on each copy an 183 | appropriate copyright notice; keep intact all notices stating that this License and 184 | any non-permissive terms added in accord with section 7 apply to the code; keep 185 | intact all notices of the absence of any warranty; and give all recipients a copy of 186 | this License along with the Program. 187 | 188 | You may charge any price or no price for each copy that you convey, and you may offer 189 | support or warranty protection for a fee. 190 | 191 | ### 5. Conveying Modified Source Versions. 192 | 193 | You may convey a work based on the Program, or the modifications to produce it from 194 | the Program, in the form of source code under the terms of section 4, provided that 195 | you also meet all of these conditions: 196 | 197 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 198 | relevant date. 199 | * **b)** The work must carry prominent notices stating that it is released under this 200 | License and any conditions added under section 7. This requirement modifies the 201 | requirement in section 4 to “keep intact all notices”. 202 | * **c)** You must license the entire work, as a whole, under this License to anyone who 203 | comes into possession of a copy. This License will therefore apply, along with any 204 | applicable section 7 additional terms, to the whole of the work, and all its parts, 205 | regardless of how they are packaged. This License gives no permission to license the 206 | work in any other way, but it does not invalidate such permission if you have 207 | separately received it. 208 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 209 | Notices; however, if the Program has interactive interfaces that do not display 210 | Appropriate Legal Notices, your work need not make them do so. 211 | 212 | A compilation of a covered work with other separate and independent works, which are 213 | not by their nature extensions of the covered work, and which are not combined with 214 | it such as to form a larger program, in or on a volume of a storage or distribution 215 | medium, is called an “aggregate” if the compilation and its resulting 216 | copyright are not used to limit the access or legal rights of the compilation's users 217 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 218 | does not cause this License to apply to the other parts of the aggregate. 219 | 220 | ### 6. Conveying Non-Source Forms. 221 | 222 | You may convey a covered work in object code form under the terms of sections 4 and 223 | 5, provided that you also convey the machine-readable Corresponding Source under the 224 | terms of this License, in one of these ways: 225 | 226 | * **a)** Convey the object code in, or embodied in, a physical product (including a 227 | physical distribution medium), accompanied by the Corresponding Source fixed on a 228 | durable physical medium customarily used for software interchange. 229 | * **b)** Convey the object code in, or embodied in, a physical product (including a 230 | physical distribution medium), accompanied by a written offer, valid for at least 231 | three years and valid for as long as you offer spare parts or customer support for 232 | that product model, to give anyone who possesses the object code either (1) a copy of 233 | the Corresponding Source for all the software in the product that is covered by this 234 | License, on a durable physical medium customarily used for software interchange, for 235 | a price no more than your reasonable cost of physically performing this conveying of 236 | source, or (2) access to copy the Corresponding Source from a network server at no 237 | charge. 238 | * **c)** Convey individual copies of the object code with a copy of the written offer to 239 | provide the Corresponding Source. This alternative is allowed only occasionally and 240 | noncommercially, and only if you received the object code with such an offer, in 241 | accord with subsection 6b. 242 | * **d)** Convey the object code by offering access from a designated place (gratis or for 243 | a charge), and offer equivalent access to the Corresponding Source in the same way 244 | through the same place at no further charge. You need not require recipients to copy 245 | the Corresponding Source along with the object code. If the place to copy the object 246 | code is a network server, the Corresponding Source may be on a different server 247 | (operated by you or a third party) that supports equivalent copying facilities, 248 | provided you maintain clear directions next to the object code saying where to find 249 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 250 | you remain obligated to ensure that it is available for as long as needed to satisfy 251 | these requirements. 252 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 253 | other peers where the object code and Corresponding Source of the work are being 254 | offered to the general public at no charge under subsection 6d. 255 | 256 | A separable portion of the object code, whose source code is excluded from the 257 | Corresponding Source as a System Library, need not be included in conveying the 258 | object code work. 259 | 260 | A “User Product” is either (1) a “consumer product”, which 261 | means any tangible personal property which is normally used for personal, family, or 262 | household purposes, or (2) anything designed or sold for incorporation into a 263 | dwelling. In determining whether a product is a consumer product, doubtful cases 264 | shall be resolved in favor of coverage. For a particular product received by a 265 | particular user, “normally used” refers to a typical or common use of 266 | that class of product, regardless of the status of the particular user or of the way 267 | in which the particular user actually uses, or expects or is expected to use, the 268 | product. A product is a consumer product regardless of whether the product has 269 | substantial commercial, industrial or non-consumer uses, unless such uses represent 270 | the only significant mode of use of the product. 271 | 272 | “Installation Information” for a User Product means any methods, 273 | procedures, authorization keys, or other information required to install and execute 274 | modified versions of a covered work in that User Product from a modified version of 275 | its Corresponding Source. The information must suffice to ensure that the continued 276 | functioning of the modified object code is in no case prevented or interfered with 277 | solely because modification has been made. 278 | 279 | If you convey an object code work under this section in, or with, or specifically for 280 | use in, a User Product, and the conveying occurs as part of a transaction in which 281 | the right of possession and use of the User Product is transferred to the recipient 282 | in perpetuity or for a fixed term (regardless of how the transaction is 283 | characterized), the Corresponding Source conveyed under this section must be 284 | accompanied by the Installation Information. But this requirement does not apply if 285 | neither you nor any third party retains the ability to install modified object code 286 | on the User Product (for example, the work has been installed in ROM). 287 | 288 | The requirement to provide Installation Information does not include a requirement to 289 | continue to provide support service, warranty, or updates for a work that has been 290 | modified or installed by the recipient, or for the User Product in which it has been 291 | modified or installed. Access to a network may be denied when the modification itself 292 | materially and adversely affects the operation of the network or violates the rules 293 | and protocols for communication across the network. 294 | 295 | Corresponding Source conveyed, and Installation Information provided, in accord with 296 | this section must be in a format that is publicly documented (and with an 297 | implementation available to the public in source code form), and must require no 298 | special password or key for unpacking, reading or copying. 299 | 300 | ### 7. Additional Terms. 301 | 302 | “Additional permissions” are terms that supplement the terms of this 303 | License by making exceptions from one or more of its conditions. Additional 304 | permissions that are applicable to the entire Program shall be treated as though they 305 | were included in this License, to the extent that they are valid under applicable 306 | law. If additional permissions apply only to part of the Program, that part may be 307 | used separately under those permissions, but the entire Program remains governed by 308 | this License without regard to the additional permissions. 309 | 310 | When you convey a copy of a covered work, you may at your option remove any 311 | additional permissions from that copy, or from any part of it. (Additional 312 | permissions may be written to require their own removal in certain cases when you 313 | modify the work.) You may place additional permissions on material, added by you to a 314 | covered work, for which you have or can give appropriate copyright permission. 315 | 316 | Notwithstanding any other provision of this License, for material you add to a 317 | covered work, you may (if authorized by the copyright holders of that material) 318 | supplement the terms of this License with terms: 319 | 320 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 321 | sections 15 and 16 of this License; or 322 | * **b)** Requiring preservation of specified reasonable legal notices or author 323 | attributions in that material or in the Appropriate Legal Notices displayed by works 324 | containing it; or 325 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 326 | modified versions of such material be marked in reasonable ways as different from the 327 | original version; or 328 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 329 | material; or 330 | * **e)** Declining to grant rights under trademark law for use of some trade names, 331 | trademarks, or service marks; or 332 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 333 | who conveys the material (or modified versions of it) with contractual assumptions of 334 | liability to the recipient, for any liability that these contractual assumptions 335 | directly impose on those licensors and authors. 336 | 337 | All other non-permissive additional terms are considered “further 338 | restrictions” within the meaning of section 10. If the Program as you received 339 | it, or any part of it, contains a notice stating that it is governed by this License 340 | along with a term that is a further restriction, you may remove that term. If a 341 | license document contains a further restriction but permits relicensing or conveying 342 | under this License, you may add to a covered work material governed by the terms of 343 | that license document, provided that the further restriction does not survive such 344 | relicensing or conveying. 345 | 346 | If you add terms to a covered work in accord with this section, you must place, in 347 | the relevant source files, a statement of the additional terms that apply to those 348 | files, or a notice indicating where to find the applicable terms. 349 | 350 | Additional terms, permissive or non-permissive, may be stated in the form of a 351 | separately written license, or stated as exceptions; the above requirements apply 352 | either way. 353 | 354 | ### 8. Termination. 355 | 356 | You may not propagate or modify a covered work except as expressly provided under 357 | this License. Any attempt otherwise to propagate or modify it is void, and will 358 | automatically terminate your rights under this License (including any patent licenses 359 | granted under the third paragraph of section 11). 360 | 361 | However, if you cease all violation of this License, then your license from a 362 | particular copyright holder is reinstated (a) provisionally, unless and until the 363 | copyright holder explicitly and finally terminates your license, and (b) permanently, 364 | if the copyright holder fails to notify you of the violation by some reasonable means 365 | prior to 60 days after the cessation. 366 | 367 | Moreover, your license from a particular copyright holder is reinstated permanently 368 | if the copyright holder notifies you of the violation by some reasonable means, this 369 | is the first time you have received notice of violation of this License (for any 370 | work) from that copyright holder, and you cure the violation prior to 30 days after 371 | your receipt of the notice. 372 | 373 | Termination of your rights under this section does not terminate the licenses of 374 | parties who have received copies or rights from you under this License. If your 375 | rights have been terminated and not permanently reinstated, you do not qualify to 376 | receive new licenses for the same material under section 10. 377 | 378 | ### 9. Acceptance Not Required for Having Copies. 379 | 380 | You are not required to accept this License in order to receive or run a copy of the 381 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 382 | using peer-to-peer transmission to receive a copy likewise does not require 383 | acceptance. However, nothing other than this License grants you permission to 384 | propagate or modify any covered work. These actions infringe copyright if you do not 385 | accept this License. Therefore, by modifying or propagating a covered work, you 386 | indicate your acceptance of this License to do so. 387 | 388 | ### 10. Automatic Licensing of Downstream Recipients. 389 | 390 | Each time you convey a covered work, the recipient automatically receives a license 391 | from the original licensors, to run, modify and propagate that work, subject to this 392 | License. You are not responsible for enforcing compliance by third parties with this 393 | License. 394 | 395 | An “entity transaction” is a transaction transferring control of an 396 | organization, or substantially all assets of one, or subdividing an organization, or 397 | merging organizations. If propagation of a covered work results from an entity 398 | transaction, each party to that transaction who receives a copy of the work also 399 | receives whatever licenses to the work the party's predecessor in interest had or 400 | could give under the previous paragraph, plus a right to possession of the 401 | Corresponding Source of the work from the predecessor in interest, if the predecessor 402 | has it or can get it with reasonable efforts. 403 | 404 | You may not impose any further restrictions on the exercise of the rights granted or 405 | affirmed under this License. For example, you may not impose a license fee, royalty, 406 | or other charge for exercise of rights granted under this License, and you may not 407 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 408 | that any patent claim is infringed by making, using, selling, offering for sale, or 409 | importing the Program or any portion of it. 410 | 411 | ### 11. Patents. 412 | 413 | A “contributor” is a copyright holder who authorizes use under this 414 | License of the Program or a work on which the Program is based. The work thus 415 | licensed is called the contributor's “contributor version”. 416 | 417 | A contributor's “essential patent claims” are all patent claims owned or 418 | controlled by the contributor, whether already acquired or hereafter acquired, that 419 | would be infringed by some manner, permitted by this License, of making, using, or 420 | selling its contributor version, but do not include claims that would be infringed 421 | only as a consequence of further modification of the contributor version. For 422 | purposes of this definition, “control” includes the right to grant patent 423 | sublicenses in a manner consistent with the requirements of this License. 424 | 425 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 426 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 427 | import and otherwise run, modify and propagate the contents of its contributor 428 | version. 429 | 430 | In the following three paragraphs, a “patent license” is any express 431 | agreement or commitment, however denominated, not to enforce a patent (such as an 432 | express permission to practice a patent or covenant not to sue for patent 433 | infringement). To “grant” such a patent license to a party means to make 434 | such an agreement or commitment not to enforce a patent against the party. 435 | 436 | If you convey a covered work, knowingly relying on a patent license, and the 437 | Corresponding Source of the work is not available for anyone to copy, free of charge 438 | and under the terms of this License, through a publicly available network server or 439 | other readily accessible means, then you must either (1) cause the Corresponding 440 | Source to be so available, or (2) arrange to deprive yourself of the benefit of the 441 | patent license for this particular work, or (3) arrange, in a manner consistent with 442 | the requirements of this License, to extend the patent license to downstream 443 | recipients. “Knowingly relying” means you have actual knowledge that, but 444 | for the patent license, your conveying the covered work in a country, or your 445 | recipient's use of the covered work in a country, would infringe one or more 446 | identifiable patents in that country that you have reason to believe are valid. 447 | 448 | If, pursuant to or in connection with a single transaction or arrangement, you 449 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 450 | license to some of the parties receiving the covered work authorizing them to use, 451 | propagate, modify or convey a specific copy of the covered work, then the patent 452 | license you grant is automatically extended to all recipients of the covered work and 453 | works based on it. 454 | 455 | A patent license is “discriminatory” if it does not include within the 456 | scope of its coverage, prohibits the exercise of, or is conditioned on the 457 | non-exercise of one or more of the rights that are specifically granted under this 458 | License. You may not convey a covered work if you are a party to an arrangement with 459 | a third party that is in the business of distributing software, under which you make 460 | payment to the third party based on the extent of your activity of conveying the 461 | work, and under which the third party grants, to any of the parties who would receive 462 | the covered work from you, a discriminatory patent license (a) in connection with 463 | copies of the covered work conveyed by you (or copies made from those copies), or (b) 464 | primarily for and in connection with specific products or compilations that contain 465 | the covered work, unless you entered into that arrangement, or that patent license 466 | was granted, prior to 28 March 2007. 467 | 468 | Nothing in this License shall be construed as excluding or limiting any implied 469 | license or other defenses to infringement that may otherwise be available to you 470 | under applicable patent law. 471 | 472 | ### 12. No Surrender of Others' Freedom. 473 | 474 | If conditions are imposed on you (whether by court order, agreement or otherwise) 475 | that contradict the conditions of this License, they do not excuse you from the 476 | conditions of this License. If you cannot convey a covered work so as to satisfy 477 | simultaneously your obligations under this License and any other pertinent 478 | obligations, then as a consequence you may not convey it at all. For example, if you 479 | agree to terms that obligate you to collect a royalty for further conveying from 480 | those to whom you convey the Program, the only way you could satisfy both those terms 481 | and this License would be to refrain entirely from conveying the Program. 482 | 483 | ### 13. Use with the GNU Affero General Public License. 484 | 485 | Notwithstanding any other provision of this License, you have permission to link or 486 | combine any covered work with a work licensed under version 3 of the GNU Affero 487 | General Public License into a single combined work, and to convey the resulting work. 488 | The terms of this License will continue to apply to the part which is the covered 489 | work, but the special requirements of the GNU Affero General Public License, section 490 | 13, concerning interaction through a network will apply to the combination as such. 491 | 492 | ### 14. Revised Versions of this License. 493 | 494 | The Free Software Foundation may publish revised and/or new versions of the GNU 495 | General Public License from time to time. Such new versions will be similar in spirit 496 | to the present version, but may differ in detail to address new problems or concerns. 497 | 498 | Each version is given a distinguishing version number. If the Program specifies that 499 | a certain numbered version of the GNU General Public License “or any later 500 | version” applies to it, you have the option of following the terms and 501 | conditions either of that numbered version or of any later version published by the 502 | Free Software Foundation. If the Program does not specify a version number of the GNU 503 | General Public License, you may choose any version ever published by the Free 504 | Software Foundation. 505 | 506 | If the Program specifies that a proxy can decide which future versions of the GNU 507 | General Public License can be used, that proxy's public statement of acceptance of a 508 | version permanently authorizes you to choose that version for the Program. 509 | 510 | Later license versions may give you additional or different permissions. However, no 511 | additional obligations are imposed on any author or copyright holder as a result of 512 | your choosing to follow a later version. 513 | 514 | ### 15. Disclaimer of Warranty. 515 | 516 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 517 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 518 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 519 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 520 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 521 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 522 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 523 | 524 | ### 16. Limitation of Liability. 525 | 526 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 527 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 528 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 529 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 530 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 531 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 532 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 533 | POSSIBILITY OF SUCH DAMAGES. 534 | 535 | ### 17. Interpretation of Sections 15 and 16. 536 | 537 | If the disclaimer of warranty and limitation of liability provided above cannot be 538 | given local legal effect according to their terms, reviewing courts shall apply local 539 | law that most closely approximates an absolute waiver of all civil liability in 540 | connection with the Program, unless a warranty or assumption of liability accompanies 541 | a copy of the Program in return for a fee. 542 | 543 | END OF TERMS AND CONDITIONS 544 | 545 | ## How to Apply These Terms to Your New Programs 546 | 547 | If you develop a new program, and you want it to be of the greatest possible use to 548 | the public, the best way to achieve this is to make it free software which everyone 549 | can redistribute and change under these terms. 550 | 551 | To do so, attach the following notices to the program. It is safest to attach them 552 | to the start of each source file to most effectively state the exclusion of warranty; 553 | and each file should have at least the “copyright” line and a pointer to 554 | where the full notice is found. 555 | 556 | 557 | Copyright (C) 558 | 559 | This program is free software: you can redistribute it and/or modify 560 | it under the terms of the GNU General Public License as published by 561 | the Free Software Foundation, either version 3 of the License, or 562 | (at your option) any later version. 563 | 564 | This program is distributed in the hope that it will be useful, 565 | but WITHOUT ANY WARRANTY; without even the implied warranty of 566 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 567 | GNU General Public License for more details. 568 | 569 | You should have received a copy of the GNU General Public License 570 | along with this program. If not, see . 571 | 572 | Also add information on how to contact you by electronic and paper mail. 573 | 574 | If the program does terminal interaction, make it output a short notice like this 575 | when it starts in an interactive mode: 576 | 577 | Copyright (C) 578 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 579 | This is free software, and you are welcome to redistribute it 580 | under certain conditions; type 'show c' for details. 581 | 582 | The hypothetical commands 'show w' and 'show c' should show the appropriate parts of 583 | the General Public License. Of course, your program's commands might be different; 584 | for a GUI interface, you would use an “about box”. 585 | 586 | You should also get your employer (if you work as a programmer) or school, if any, to 587 | sign a “copyright disclaimer” for the program, if necessary. For more 588 | information on this, and how to apply and follow the GNU GPL, see 589 | <>. 590 | 591 | The GNU General Public License does not permit incorporating your program into 592 | proprietary programs. If your program is a subroutine library, you may consider it 593 | more useful to permit linking proprietary applications with the library. If this is 594 | what you want to do, use the GNU Lesser General Public License instead of this 595 | License. But first, please read 596 | <>. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # robot-plugin 2 | _Robot Framework Support plugin for the IntelliJ Idea platform_ 3 | 4 | [![Circle CI](https://circleci.com/gh/AmailP/robot-plugin.svg?style=svg)](https://circleci.com/gh/AmailP/robot-plugin) [![Build Status](https://travis-ci.com/AmailP/robot-plugin.svg?branch=master)](https://travis-ci.com/AmailP/robot-plugin) [![Join the chat at https://gitter.im/AmailP/robot-plugin](https://badges.gitter.im/AmailP/robot-plugin.svg)](https://gitter.im/AmailP/robot-plugin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | [![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/amailp/donate) 7 | 8 | 1. Syntax highlighting 9 | 1. Code completion support: 10 | - User defined keywords from .robot files 11 | - User defined keywords from Static Python libraries (*) 12 | - Robot Library keywords (*) 13 | 1. Jump to keyword definition, from local file and from the recursively imported resources 14 | 1. Jump to resource 15 | 1. Find usages / rename of keywords and resources 16 | 1. Structure view for test cases and keywords 17 | 18 | (*) requires PyCharm or Python plugin from JetBrains 19 | 20 | ![Syntax highlighting, code completion](/img/CodeCompletion.png) 21 | ![Structure view, nice icons](/img/StructureView.png) 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id("scala") 4 | id("org.jetbrains.intellij") version "1.5.3" 5 | id("de.undercouch.download") version "4.1.2" 6 | id("com.palantir.git-version") version "0.12.3" 7 | id("cz.alenkacz.gradle.scalafmt") version "1.16.2" 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | // Build number ranges: https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 15 | // Python CE plugin: https://plugins.jetbrains.com/plugin/7322-python-community-edition 16 | // Python plugin: https://plugins.jetbrains.com/plugin/631-python 17 | // Intellij repositories: https://www.jetbrains.com/intellij-repository/releases 18 | // https://www.jetbrains.com/intellij-repository/snapshots 19 | 20 | version = gitVersion() 21 | 22 | def intellijVersion = System.getenv().getOrDefault("IDEA_VERSION", "IC-2022.1") 23 | 24 | def pythonPluginForVersion = [ 25 | "IC-2021.1": "PythonCore:211.6693.119", 26 | "IC-2021.2": "PythonCore:212.4746.96", 27 | "IC-2021.3": "PythonCore:213.5744.248", 28 | "IC-2022.1": "PythonCore:221.5080.210" 29 | ] 30 | 31 | intellij { 32 | version = intellijVersion 33 | plugins = [pythonPluginForVersion.get(intellijVersion)] 34 | } 35 | 36 | publishPlugin { 37 | token = System.getenv().getOrDefault("INTELLIJ_PUBLISH_TOKEN", "") 38 | } doFirst { 39 | def requiredVersion = JavaVersion.VERSION_11 40 | if(JavaVersion.current() != requiredVersion) 41 | throw new GradleException("The plugin should be published using JavaVersion $requiredVersion" ) 42 | } 43 | 44 | import org.jetbrains.intellij.tasks.RunPluginVerifierTask.FailureLevel 45 | runPluginVerifier { 46 | ideVersions = [intellijVersion] 47 | failureLevel = FailureLevel.ALL 48 | } 49 | 50 | patchPluginXml { 51 | sinceBuild = "211" 52 | untilBuild = "221.*" 53 | } 54 | 55 | sourceCompatibility = JavaVersion.VERSION_11 56 | targetCompatibility = JavaVersion.VERSION_11 57 | 58 | // Following two assignments are necessary to let the scala plugin handle both java and scala compilation 59 | sourceSets.main.java.srcDirs = [] 60 | sourceSets.test.java.srcDirs = [] 61 | // 62 | sourceSets.main.scala.srcDir("src/main/java") 63 | sourceSets.main.scala.srcDir("src/main/java-gen") 64 | sourceSets.test.scala.srcDir("src/test/java") 65 | 66 | def scalaVersion = System.getenv().getOrDefault("TRAVIS_SCALA_VERSION", "2.13.3") 67 | dependencies { 68 | implementation("org.scala-lang:scala-library:$scalaVersion") 69 | testImplementation("org.scalatest:scalatest_2.13:3.2.1") 70 | testImplementation("org.scalatestplus:junit-4-12_2.13:3.2.1.0") 71 | } 72 | 73 | import de.undercouch.gradle.tasks.download.Download 74 | task downloadJFlex(type: Download) { 75 | src("https://cache-redirector.jetbrains.com/intellij-dependencies/org/jetbrains/intellij/deps/jflex/jflex/1.7.0-2/jflex-1.7.0-2.jar") 76 | dest("${buildDir}/downloads/JFlex.jar") 77 | onlyIfNewer(true) 78 | overwrite(false) 79 | } 80 | 81 | task downloadSkeleton(dependsOn: downloadJFlex, type: Download) { 82 | src("https://github.com/JetBrains/intellij-community/raw/master/tools/lexer/idea-flex.skeleton") 83 | dest("${buildDir}/downloads/idea-flex.skeleton") 84 | onlyIfNewer(true) 85 | overwrite(false) 86 | } 87 | 88 | task generateLexer(dependsOn: downloadSkeleton, type: JavaExec) { 89 | classpath = files("${buildDir}/downloads/JFlex.jar") 90 | args = """--nobak 91 | |--skel 92 | |${buildDir}/downloads/idea-flex.skeleton 93 | |-d 94 | |${projectDir}/src/main/java-gen/amailp/intellij/robot/lexer 95 | |${projectDir}/src/main/resources/amailp/intellij/robot/lexer/Robot.flex""".stripMargin().split("\n").toList() 96 | inputs.file("${projectDir}/src/main/resources/amailp/intellij/robot/lexer/Robot.flex") 97 | outputs.dir("${projectDir}/src/main/java-gen/amailp/intellij/robot/lexer") 98 | } 99 | 100 | tasks.named("wrapper") { 101 | distributionType = Wrapper.DistributionType.ALL 102 | } 103 | 104 | compileScala.dependsOn(generateLexer) 105 | 106 | test { 107 | testLogging { 108 | showStandardStreams = true 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /img/CodeCompletion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/img/CodeCompletion.png -------------------------------------------------------------------------------- /img/StructureView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/img/StructureView.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "robot-plugin" 2 | -------------------------------------------------------------------------------- /src/main/java/amailp/intellij/robot/elements/RobotTokenTypes.java: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.elements; 2 | 3 | import amailp.intellij.robot.lexer.RobotIElementType; 4 | import com.intellij.psi.TokenType; 5 | import com.intellij.psi.tree.IElementType; 6 | import com.intellij.psi.tree.TokenSet; 7 | 8 | public interface RobotTokenTypes { 9 | 10 | 11 | final IElementType BadCharacter = TokenType.BAD_CHARACTER; 12 | 13 | final IElementType LineTerminator = new RobotIElementType("LineTerminator"); 14 | 15 | final IElementType SettingsHeader = new RobotIElementType("SettingsHeader"); 16 | final IElementType TestCasesHeader = new RobotIElementType("TestCasesHeader"); 17 | final IElementType KeywordsHeader = new RobotIElementType("KeywordsHeader"); 18 | final IElementType VariablesHeader = new RobotIElementType("VariablesHeader"); 19 | final IElementType TasksHeader = new RobotIElementType("TasksHeader"); 20 | 21 | final IElementType Ellipsis = new RobotIElementType("Ellipsis"); 22 | final IElementType ScalarVariable = new RobotIElementType("ScalarVariable"); 23 | final IElementType ListVariable = new RobotIElementType("ListVariable"); 24 | final IElementType DictionaryVariable = new RobotIElementType("DictionaryVariable"); 25 | final IElementType EnvironmentVariable = new RobotIElementType("EnvironmentVariable"); 26 | final IElementType TestCaseSetting = new RobotIElementType("TestCaseSetting"); 27 | final IElementType Word = new RobotIElementType("Word"); 28 | final IElementType Space = new RobotIElementType("Space"); 29 | final IElementType Separator = new RobotIElementType("Separator"); 30 | final IElementType IrrelevantSpaces = new RobotIElementType("IrrelevantSpaces"); 31 | final IElementType BlankLine = new RobotIElementType("BlankLine"); 32 | final IElementType Comment = new RobotIElementType("Comment"); 33 | final IElementType WithName = new RobotIElementType("LibraryAliasSeparator"); 34 | 35 | final TokenSet WhitespacesTokens = TokenSet.create(IrrelevantSpaces, BlankLine); 36 | final TokenSet CommentsTokens = TokenSet.create(Comment); 37 | final TokenSet StringLiteralElements = TokenSet.EMPTY; 38 | final TokenSet HeaderTokens = TokenSet.create(SettingsHeader, TestCasesHeader, KeywordsHeader, VariablesHeader, TasksHeader); 39 | } -------------------------------------------------------------------------------- /src/main/java/amailp/intellij/robot/extensions/ParserDefinition.java: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions; 2 | 3 | import amailp.intellij.robot.elements.RobotTokenTypes; 4 | import amailp.intellij.robot.lang.RobotLanguage; 5 | import amailp.intellij.robot.lexer.RobotLexer; 6 | import amailp.intellij.robot.parser.PsiElementBuilder; 7 | import amailp.intellij.robot.parser.RobotParser$; 8 | import amailp.intellij.robot.psi.RobotPsiFile; 9 | import com.intellij.lang.ASTNode; 10 | import com.intellij.lang.PsiParser; 11 | import com.intellij.lexer.Lexer; 12 | import com.intellij.openapi.project.Project; 13 | import com.intellij.psi.FileViewProvider; 14 | import com.intellij.psi.PsiElement; 15 | import com.intellij.psi.PsiFile; 16 | import com.intellij.psi.tree.IFileElementType; 17 | import com.intellij.psi.tree.TokenSet; 18 | import org.jetbrains.annotations.NotNull; 19 | 20 | public class ParserDefinition implements com.intellij.lang.ParserDefinition { 21 | 22 | @Override 23 | @NotNull 24 | public Lexer createLexer(Project project) { 25 | return new RobotLexer(); 26 | } 27 | 28 | @Override 29 | @NotNull 30 | public TokenSet getWhitespaceTokens() { 31 | return RobotTokenTypes.WhitespacesTokens; 32 | } 33 | 34 | 35 | @Override 36 | @NotNull 37 | public TokenSet getCommentTokens() { 38 | return RobotTokenTypes.CommentsTokens; 39 | } 40 | 41 | @Override 42 | @NotNull 43 | public TokenSet getStringLiteralElements() { 44 | return RobotTokenTypes.StringLiteralElements; 45 | } 46 | 47 | @Override 48 | @NotNull 49 | public PsiParser createParser(Project project) { 50 | return RobotParser$.MODULE$; 51 | } 52 | 53 | @Override 54 | @NotNull 55 | public IFileElementType getFileNodeType() { 56 | return ParserDefinition.RobotFileElementType; 57 | } 58 | 59 | @Override 60 | @NotNull 61 | public PsiElement createElement(ASTNode node) { 62 | return new PsiElementBuilder(node).build(); 63 | } 64 | 65 | @Override 66 | @NotNull 67 | public PsiFile createFile(@NotNull FileViewProvider viewProvider) { 68 | return new RobotPsiFile(viewProvider); 69 | } 70 | 71 | final static IFileElementType RobotFileElementType = new IFileElementType(RobotLanguage.Instance()); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/amailp/intellij/robot/file/FileType.java: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.file; 2 | 3 | import amailp.intellij.robot.lang.RobotLanguage; 4 | import com.intellij.openapi.fileTypes.LanguageFileType; 5 | import com.intellij.openapi.util.NlsContexts; 6 | import com.intellij.openapi.util.NlsSafe; 7 | import org.jetbrains.annotations.NonNls; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.Icon; 12 | 13 | public class FileType extends LanguageFileType { 14 | 15 | private static final String NAME = "Robot file"; 16 | private static final String DESCRIPTION = "Robotframework language file"; 17 | private static final String DEFAULT_EXTENSION = "robot"; 18 | public static final LanguageFileType INSTANCE = new FileType(); 19 | 20 | private FileType() { 21 | super(RobotLanguage.Instance()); 22 | } 23 | 24 | @Override 25 | @NotNull 26 | public String getName() { 27 | return NAME; 28 | } 29 | 30 | @Override 31 | @NotNull 32 | public String getDescription() { 33 | return DESCRIPTION; 34 | } 35 | 36 | @Override 37 | @NotNull 38 | public String getDefaultExtension() { 39 | return DEFAULT_EXTENSION; 40 | } 41 | 42 | @Override 43 | @Nullable 44 | public Icon getIcon() { 45 | return Icons.file(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/amailp/intellij/robot/lexer/RobotLexer.java: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.lexer; 2 | 3 | import com.intellij.lexer.FlexAdapter; 4 | 5 | import java.io.Reader; 6 | 7 | public class RobotLexer extends FlexAdapter { 8 | public RobotLexer() { 9 | super(new _RobotLexer((Reader)null)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | amailp.intellij.robot 3 | Robot Framework Support 4 | Valerio Angelini 5 | 6 | 9 |
  • Syntax highlighting
  • 10 |
  • Code completion support: 11 |
      12 |
    • User defined keywords from .robot files
    • 13 |
    • User defined keywords from Static Python libraries [*]
    • 14 |
    • Robot Library keywords [*]
    • 15 |
    16 |
  • 17 |
  • Jump to keyword definition, from local file and from the recursively imported resources
  • 18 |
  • Jump to resource
  • 19 |
  • Find usages / rename of keywords and resources
  • 20 |
  • Structure view for test cases and keywords
  • 21 | 22 | [*] requires PyCharm or Python plugin from JetBrains 23 | ]]>
    24 | 25 | 27 |
  • Add support for 2022.1
  • 28 | 29 | ]]> 30 |
    31 | 32 | 33 | com.intellij.modules.lang 34 | com.intellij.modules.python 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
    75 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/robot-python.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/amailp/intellij/robot/lexer/Robot.flex: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.lexer; 2 | 3 | import com.intellij.lexer.FlexLexer; 4 | import com.intellij.psi.tree.IElementType; 5 | import amailp.intellij.robot.elements.RobotTokenTypes; 6 | 7 | %% 8 | 9 | %class _RobotLexer 10 | %implements FlexLexer 11 | %public 12 | %unicode 13 | %function advance 14 | %type IElementType 15 | %eof{ 16 | return; 17 | %eof} 18 | 19 | LineTerminator = \n | \r | \r\n 20 | Space = "\ " 21 | SpaceChars = [\ \t\f] 22 | Separator = [\t\f] | {SpaceChars} {SpaceChars}+ 23 | Comment = "#" .* 24 | Ellipsis = \.\.\. 25 | WithName = "WITH NAME" 26 | 27 | SettingsHeader = "*** Settings ***" | "*** Setting ***" 28 | TestCasesHeader = "*** Test Cases ***" | "*** Test Case ***" 29 | KeywordsHeader = "*** Keywords ***" | "*** Keyword ***" 30 | VariablesHeader = "*** Variables ***" | "*** Variable ***" 31 | TasksHeader = "*** Tasks ***" | "*** Task ***" 32 | 33 | 34 | WordChar = [^$@&%\ \t\f\r\n] 35 | Word = {WordChar}+ 36 | 37 | TestCaseSetting = "[Documentation]" 38 | | "[Tags]" 39 | | "[Setup]" 40 | | "[Precondition]" 41 | | "[Teardown]" 42 | | "[Postcondition]" 43 | | "[Template]" 44 | | "[Timeout]" 45 | | "[Arguments]" 46 | | "[Return]" 47 | 48 | ScalarVariable = "${" ~"}" 49 | ListVariable = "@{" ~"}" 50 | DictionaryVariable = "&{" ~"}" 51 | EnvironmentVariable = "%{" ~"}" 52 | 53 | %state LINE 54 | 55 | %% 56 | 57 | { 58 | {SpaceChars}* {LineTerminator} { return RobotTokenTypes.BlankLine; } 59 | {SpaceChars}* {Comment} {LineTerminator} { return RobotTokenTypes.Comment; } 60 | 61 | { 62 | {SpaceChars}+ / {Comment} { yybegin(LINE); return RobotTokenTypes.IrrelevantSpaces; } 63 | {SpaceChars}+ $ { yybegin(LINE); return RobotTokenTypes.IrrelevantSpaces; } 64 | {Comment} { yybegin(LINE); return RobotTokenTypes.Comment; } 65 | 66 | {SettingsHeader} { yybegin(LINE); return RobotTokenTypes.SettingsHeader; } 67 | {TestCasesHeader} { yybegin(LINE); return RobotTokenTypes.TestCasesHeader; } 68 | {KeywordsHeader} { yybegin(LINE); return RobotTokenTypes.KeywordsHeader; } 69 | {VariablesHeader} { yybegin(LINE); return RobotTokenTypes.VariablesHeader; } 70 | {TasksHeader} { yybegin(LINE); return RobotTokenTypes.TasksHeader; } 71 | 72 | {Ellipsis} { yybegin(LINE); return RobotTokenTypes.Ellipsis; } 73 | {ScalarVariable} { yybegin(LINE); return RobotTokenTypes.ScalarVariable; } 74 | {ListVariable} { yybegin(LINE); return RobotTokenTypes.ListVariable; } 75 | {DictionaryVariable} { yybegin(LINE); return RobotTokenTypes.DictionaryVariable; } 76 | {EnvironmentVariable} { yybegin(LINE); return RobotTokenTypes.EnvironmentVariable; } 77 | {TestCaseSetting} { yybegin(LINE); return RobotTokenTypes.TestCaseSetting; } 78 | {Word} | "$" | "@" | "&" | "%" { yybegin(LINE); return RobotTokenTypes.Word; } 79 | {Space} { yybegin(LINE); return RobotTokenTypes.Space; } 80 | {Separator} { yybegin(LINE); return RobotTokenTypes.Separator; } 81 | {WithName} { yybegin(LINE); return RobotTokenTypes.WithName; } 82 | } 83 | } 84 | 85 | {LineTerminator} { yybegin(YYINITIAL); return RobotTokenTypes.LineTerminator; } 86 | 87 | //. { return RobotTokenTypes.BadCharacter; } -------------------------------------------------------------------------------- /src/main/resources/fileTemplates/internal/Robot Keywords.robot.ft: -------------------------------------------------------------------------------- 1 | *** Keywords *** 2 | First keyword 3 | Do something 4 | 5 | Second keyword 6 | Do more 7 | -------------------------------------------------------------------------------- /src/main/resources/fileTemplates/internal/Robot Test Suite.robot.ft: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Documentation Suite description 3 | 4 | *** Test Cases *** 5 | Test title 6 | [Tags] DEBUG 7 | Provided precondition 8 | When action 9 | Then check expectations 10 | 11 | *** Keywords *** 12 | Provided precondition 13 | Setup system under test -------------------------------------------------------------------------------- /src/main/resources/icons/keyword_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/main/resources/icons/keyword_16.png -------------------------------------------------------------------------------- /src/main/resources/icons/keywords_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/main/resources/icons/keywords_16.png -------------------------------------------------------------------------------- /src/main/resources/icons/robot_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/main/resources/icons/robot_16.png -------------------------------------------------------------------------------- /src/main/resources/icons/robot_file_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/main/resources/icons/robot_file_16.png -------------------------------------------------------------------------------- /src/main/resources/icons/robot_toolwin_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/main/resources/icons/robot_toolwin_13.png -------------------------------------------------------------------------------- /src/main/resources/icons/variable_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/main/resources/icons/variable_16.png -------------------------------------------------------------------------------- /src/main/resources/icons/variables_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/main/resources/icons/variables_16.png -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/actions/NewRobotFileAction.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.actions 2 | 3 | import com.intellij.ide.actions.CreateFileFromTemplateAction 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.psi.PsiDirectory 6 | import com.intellij.ide.actions.CreateFileFromTemplateDialog.Builder 7 | import amailp.intellij.robot.file.FileType 8 | 9 | class NewRobotFileAction 10 | extends CreateFileFromTemplateAction("Robot File", 11 | "Creates a Robot file from the specified template", 12 | FileType.INSTANCE.getIcon) { 13 | override def getActionName(directory: PsiDirectory, newName: String, templateName: String): String = 14 | s"Create Robot file $newName" 15 | 16 | override def buildDialog(project: Project, directory: PsiDirectory, builder: Builder): Unit = 17 | builder 18 | .setTitle("New Robot file") 19 | .addKind("Robot test suite", FileType.INSTANCE.getIcon, "Robot Test Suite") 20 | .addKind("Robot keywords", FileType.INSTANCE.getIcon, "Robot Keywords") 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/ast/package.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot 2 | 3 | import amailp.intellij.robot.lexer.RobotIElementType 4 | import com.intellij.psi.tree.TokenSet 5 | 6 | package object ast { 7 | object Tables extends RobotIElementType("Tables") 8 | 9 | object SettingsTable extends RobotIElementType("SettingsTable") 10 | object Setting extends RobotIElementType("Setting") 11 | object SettingName extends RobotIElementType("SettingName") 12 | object ResourceKey extends RobotIElementType("ResourceKey") 13 | object ResourceValue extends RobotIElementType("ResourceValue") 14 | object LibraryKey extends RobotIElementType("LibraryKey") 15 | object LibraryValue extends RobotIElementType("LibraryValue") 16 | object LibraryName extends RobotIElementType("LibraryName") 17 | object LibraryAlias extends RobotIElementType("LibraryAlias") 18 | 19 | object TestCasesTable extends RobotIElementType("TestCasesTable") 20 | object TestCaseName extends RobotIElementType("TestCaseTitle") 21 | object TestCaseDefinition extends RobotIElementType("TestCaseDefinition") 22 | 23 | object KeywordsTable extends RobotIElementType("KeywordsTable") 24 | object KeywordName extends RobotIElementType("KeywordName") 25 | object KeywordDefinition extends RobotIElementType("KeywordDefinition") 26 | object Keyword extends RobotIElementType("Keyword") 27 | 28 | object VariablesTable extends RobotIElementType("VariablesTable") 29 | object VariableDefinition extends RobotIElementType("VariableDefinition") 30 | object VariableName extends RobotIElementType("VariableName") 31 | object TableRow extends RobotIElementType("TableRow") 32 | object NonEmptyCell extends RobotIElementType("NonEmptyCell") 33 | 34 | val tableElementTypes = TokenSet.create(SettingsTable, TestCasesTable, KeywordsTable, VariablesTable) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/Annotator.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.lang.annotation.{AnnotationHolder, HighlightSeverity} 4 | import com.intellij.psi.PsiElement 5 | import amailp.intellij.robot.psi._ 6 | import com.intellij.openapi.editor.DefaultLanguageHighlighterColors 7 | import DefaultLanguageHighlighterColors._ 8 | import com.intellij.openapi.editor.colors.TextAttributesKey 9 | 10 | class Annotator extends com.intellij.lang.annotation.Annotator { 11 | def annotate(element: PsiElement, holder: AnnotationHolder): Unit = { 12 | 13 | def highlightAs(attr: TextAttributesKey): Unit = { 14 | holder.newSilentAnnotation(HighlightSeverity.INFORMATION).textAttributes(attr).create() 15 | } 16 | 17 | element match { 18 | case _: Ellipsis => highlightAs(LINE_COMMENT) 19 | case _: KeywordName => highlightAs(KEYWORD) 20 | case _: Keyword => highlightAs(NUMBER) 21 | case _: TestCaseName => highlightAs(LABEL) 22 | case _: SettingName => highlightAs(INSTANCE_FIELD) 23 | case _ => 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/Commenter.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | class Commenter extends com.intellij.lang.Commenter { 4 | def getLineCommentPrefix: String = "#" 5 | 6 | def getCommentedBlockCommentSuffix: String = null 7 | def getCommentedBlockCommentPrefix: String = null 8 | def getBlockCommentSuffix: String = null 9 | def getBlockCommentPrefix: String = null 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/DeclarationSearcher.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.pom.PomTarget 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.util.Consumer 6 | import amailp.intellij.robot.psi.KeywordName 7 | 8 | class DeclarationSearcher extends com.intellij.pom.PomDeclarationSearcher { 9 | def findDeclarationsAt(element: PsiElement, offsetInElement: Int, consumer: Consumer[PomTarget]): Unit = { 10 | element match { 11 | case keywordName: KeywordName => consumer.consume(keywordName.getDefinition) 12 | case _ => 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/ElementDescriptionProvider.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.psi.{ElementDescriptionLocation, PsiElement} 4 | import com.intellij.usageView.UsageViewLongNameLocation 5 | import amailp.intellij.robot.findUsage.UsageFindable 6 | 7 | class ElementDescriptionProvider extends com.intellij.psi.ElementDescriptionProvider { 8 | def getElementDescription(element: PsiElement, location: ElementDescriptionLocation): String = { 9 | (element, location) match { 10 | case (e: UsageFindable, _: UsageViewLongNameLocation) => s"'${e.getDescriptiveName}'" 11 | case _ => null 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/FindUsagesProvider.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.lang.cacheBuilder.WordsScanner 4 | import com.intellij.psi.PsiElement 5 | import amailp.intellij.robot.findUsage.UsageFindable 6 | import amailp.intellij.robot 7 | import amailp.intellij.robot.psi.RobotPsiFile 8 | 9 | class FindUsagesProvider extends com.intellij.lang.findUsages.FindUsagesProvider { 10 | override def getNodeText(element: PsiElement, useFullName: Boolean): String = 11 | element.asInstanceOf[UsageFindable].getNodeText(useFullName) 12 | 13 | override def getDescriptiveName(element: PsiElement): String = element match { 14 | case usageFindable: UsageFindable => usageFindable.getDescriptiveName 15 | case file: RobotPsiFile => file.getName 16 | } 17 | override def getType(element: PsiElement): String = element.asInstanceOf[UsageFindable].getType 18 | override def getHelpId(element: PsiElement): String = element.asInstanceOf[UsageFindable].getHelpId 19 | override def canFindUsagesFor(element: PsiElement): Boolean = element.isInstanceOf[UsageFindable] 20 | override def getWordsScanner: WordsScanner = new robot.findUsage.WordsScanner 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/LibraryReferenceContributor.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import amailp.intellij.robot.ast.LibraryValue 4 | import amailp.intellij.robot.psi.LibraryValue 5 | import amailp.intellij.robot.psi.reference.LibraryToDefinitionReference 6 | import com.intellij.patterns.PlatformPatterns 7 | import com.intellij.psi._ 8 | import com.intellij.util.ProcessingContext 9 | 10 | class LibraryReferenceContributor extends PsiReferenceContributor { 11 | 12 | override def registerReferenceProviders(registrar: PsiReferenceRegistrar): Unit = { 13 | registrar.registerReferenceProvider( 14 | PlatformPatterns.psiElement(LibraryValue), 15 | new PsiReferenceProvider() { 16 | override def getReferencesByElement(element: PsiElement, context: ProcessingContext): Array[PsiReference] = 17 | Array[PsiReference](new LibraryToDefinitionReference(element.asInstanceOf[LibraryValue])) 18 | } 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/NamesValidator.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.openapi.project.Project 4 | 5 | class NamesValidator extends com.intellij.lang.refactoring.NamesValidator { 6 | def isKeyword(name: String, project: Project): Boolean = false 7 | def isIdentifier(name: String, project: Project): Boolean = true 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/PythonKeywordReferenceContributor.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import amailp.intellij.robot.ast.Keyword 4 | import amailp.intellij.robot.psi.reference.PythonKeywordToDefinitionReference 5 | import com.intellij.openapi.util.TextRange 6 | import com.intellij.patterns.PlatformPatterns 7 | import com.intellij.psi._ 8 | import com.intellij.util.ProcessingContext 9 | 10 | class PythonKeywordReferenceContributor extends PsiReferenceContributor { 11 | 12 | override def registerReferenceProviders(registrar: PsiReferenceRegistrar): Unit = { 13 | registrar.registerReferenceProvider( 14 | PlatformPatterns.psiElement(Keyword), 15 | new PsiReferenceProvider() { 16 | override def getReferencesByElement(element: PsiElement, context: ProcessingContext): Array[PsiReference] = { 17 | List[PsiReference]( 18 | new PythonKeywordToDefinitionReference(element, new TextRange(0, element.getText.length)) 19 | ) 20 | }.toArray 21 | } 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/RobotLibrariesCompletionContributor.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import amailp.intellij.robot.elements.RobotTokenTypes 4 | import amailp.intellij.robot.file.Icons 5 | import amailp.intellij.robot.psi._ 6 | import amailp.intellij.robot.psi.utils.ExtRobotPsiUtils 7 | import com.intellij.codeInsight.completion._ 8 | import com.intellij.codeInsight.lookup.{AutoCompletionPolicy, LookupElement, LookupElementBuilder} 9 | import com.intellij.patterns.PlatformPatterns 10 | import com.intellij.psi.PsiElement 11 | import com.intellij.util.{ProcessingContext, Processor} 12 | import com.jetbrains.python.psi._ 13 | import com.jetbrains.python.psi.impl.PyPsiUtils._ 14 | import com.jetbrains.python.psi.stubs.PyModuleNameIndex 15 | import com.jetbrains.python.{PyNames, PythonFileType} 16 | import icons.PythonIcons.Python.Python 17 | 18 | import javax.swing.Icon 19 | import scala.collection.mutable 20 | import scala.jdk.CollectionConverters._ 21 | 22 | class RobotLibrariesCompletionContributor extends CompletionContributor { 23 | 24 | private def getParent(p: PsiElement): Option[PsiElement] = Option(p.getParent) 25 | 26 | extend( 27 | CompletionType.BASIC, 28 | PlatformPatterns.psiElement(RobotTokenTypes.Word), 29 | new CompletionProvider[CompletionParameters]() { 30 | override def addCompletions(completionParameters: CompletionParameters, 31 | processingContext: ProcessingContext, 32 | completionResultSet: CompletionResultSet) = { 33 | val currentPsiElem = completionParameters.getPosition 34 | val psiUtils: ExtRobotPsiUtils = new ExtRobotPsiUtils { 35 | def utilsPsiElement: PsiElement = 36 | Option(completionParameters.getOriginalPosition) 37 | .getOrElse(completionParameters.getPosition) 38 | } 39 | //TODO rethink filtering, this maybe does not work well enough 40 | getParent(currentPsiElem).flatMap(getParent).flatMap(getParent).foreach { 41 | case _: TestCaseDefinition | _: KeywordDefinition => 42 | for { 43 | library: Library <- librariesInScope 44 | lookupElements = lookupElementsForLibrary(library) 45 | } completionResultSet.addAllElements(lookupElements.asJava) 46 | case _ => 47 | } 48 | 49 | def librariesInScope = psiUtils.originalRobotFile.getImportedLibraries 50 | 51 | def lookupElementsForLibrary(library: Library): Seq[LookupElement] = { 52 | val libraryName = library.getName 53 | 54 | object WithSameNameClass { 55 | def unapply(pyFile: PyFile): Option[PyClass] = 56 | Option(pyFile.getVirtualFile) 57 | .flatMap(vf => Option(pyFile.findTopLevelClass(vf.getNameWithoutExtension))) 58 | } 59 | 60 | object PythonClassWithExactQName { 61 | def unapply(name: String): Option[PyClass] = searchForClass(s"$name") 62 | } 63 | 64 | object PythonClassFromRobotLib { 65 | def unapply(name: String): Option[PyClass] = searchForClass(s"robot.libraries.$name.$name") 66 | } 67 | 68 | object PythonClassSameNameAsModule { 69 | def unapply(name: String): Option[PyClass] = { 70 | val qNameComponents = name.split('.') 71 | val className = (qNameComponents :+ qNameComponents.last).mkString(".") 72 | searchForClass(className) 73 | } 74 | } 75 | 76 | object LocalPythonFile { 77 | def unapply(library: LibraryValue): Option[PyFile] = { 78 | for { 79 | virtualFile <- Option(library.currentDirectory.findFileByRelativePath(library.getName)) 80 | psiFile <- Option(psiUtils.psiManager.findFile(virtualFile)) 81 | if psiFile.getFileType == PythonFileType.INSTANCE 82 | } yield psiFile.asInstanceOf[PyFile] 83 | } 84 | } 85 | 86 | object InPathPythonFile { 87 | def unapply(library: LibraryValue): Option[PyFile] = 88 | PyModuleNameIndex.find(library.getName, currentPsiElem.getProject, true).asScala.headOption 89 | } 90 | 91 | object ClassName { 92 | def unapply(library: LibraryValue): Option[String] = 93 | Option(library.getName) 94 | } 95 | 96 | def searchForClass(qName: String) = 97 | Option(PyPsiFacade.getInstance(currentPsiElem.getProject).createClassByQName(qName, currentPsiElem)) 98 | 99 | library match { 100 | case LocalPythonFile(WithSameNameClass(pyClass)) => 101 | lookupElementsFromMethods(libraryName, pyClass, Python) 102 | case LocalPythonFile(pyFile) => lookupElementsFrom__all__(libraryName, pyFile, Python) 103 | case ClassName(PythonClassWithExactQName(pyClass)) => 104 | lookupElementsFromMethods(libraryName, pyClass, Python) 105 | case ClassName(PythonClassFromRobotLib(pyClass)) => 106 | lookupElementsFromMethods(libraryName, pyClass, Icons.robot) 107 | case ClassName(PythonClassSameNameAsModule(pyClass)) => 108 | lookupElementsFromMethods(libraryName, pyClass, Python) 109 | case InPathPythonFile(pyFile) => lookupElementsFrom__all__(libraryName, pyFile, Python) 110 | case _ => Nil 111 | } 112 | } 113 | 114 | def formatParameterName(parameter: PyParameter) = parameter match { 115 | case p if p.hasDefaultValue => s"${p.getName}=${p.getDefaultValue.getText}" 116 | case p => p.getName 117 | } 118 | 119 | def lookupElementsFromMethods(libName: String, baseClass: PyClass, icon: Icon): Seq[LookupElement] = { 120 | class CollectLookupElementsIfPublic extends Processor[PyFunction] { 121 | val lookupElements: mutable.ArrayDeque[LookupElement] = mutable.ArrayDeque() 122 | override def process(method: PyFunction): Boolean = { 123 | if (!method.getName.startsWith("_")) 124 | lookupElements += createLookupElement(method, libName, drop = 1, icon) 125 | true 126 | } 127 | } 128 | val processor = new CollectLookupElementsIfPublic() 129 | baseClass.visitMethods(processor, true, null) 130 | processor.lookupElements.toSeq 131 | } 132 | 133 | def lookupElementsFrom__all__(libName: String, pyFile: PyFile, icon: Icon): Seq[LookupElement] = 134 | for { 135 | function <- getFunctionsFrom__all__(pyFile) 136 | if !function.getName.startsWith("_") 137 | } yield createLookupElement(function, libName, drop = 0, icon) 138 | 139 | def getFunctionsFrom__all__(pyFile: PyFile): Seq[PyFunction] = { 140 | val attributesNamedAll = getAttributeValuesFromFile(pyFile, PyNames.ALL).toArray(Array[PyExpression]()) 141 | for { 142 | functionName <- getStringValues(attributesNamedAll).asScala.toIndexedSeq 143 | } yield Option(pyFile.findTopLevelFunction(functionName)) 144 | }.flatten 145 | 146 | def createLookupElement(function: PyFunction, libName: String, drop: Int, icon: Icon) = { 147 | val paramList = function.getParameterList 148 | LookupElementBuilder 149 | .create(function.getName.replace('_', ' ')) 150 | .withCaseSensitivity(false) 151 | .withIcon(icon) 152 | .withTypeText(libName, true) 153 | .withTailText( 154 | formatMethodParameters(paramList.getParameters.drop(drop), 155 | paramList.hasPositionalContainer, 156 | paramList.hasKeywordContainer) 157 | ) 158 | .withAutoCompletionPolicy(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE) 159 | } 160 | 161 | def formatMethodParameters(parameters: Array[PyParameter], 162 | hasPositionalContainer: Boolean, 163 | hasKeywordContainer: Boolean) = { 164 | val params = parameters.reverseIterator 165 | var paramNames: List[String] = Nil 166 | 167 | if (params.hasNext && hasKeywordContainer) 168 | paramNames = s"**${params.next().getName}" :: paramNames 169 | 170 | if (params.hasNext && hasPositionalContainer) 171 | paramNames = s"*${params.next().getName}" :: paramNames 172 | 173 | for (parameter <- params) 174 | paramNames = formatParameterName(parameter) :: paramNames 175 | paramNames 176 | }.mkString(" (", ", ", ")") 177 | 178 | } 179 | } 180 | ) 181 | } 182 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/RobotPsiStructureViewFactory.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.lang.PsiStructureViewFactory 4 | import com.intellij.psi.PsiFile 5 | import com.intellij.ide.structureView._ 6 | import com.intellij.openapi.fileEditor.FileEditor 7 | import com.intellij.openapi.project.Project 8 | import amailp.intellij.robot.psi.RobotPsiFile 9 | import amailp.intellij.robot.structureView.RobotTreeBasedStructureViewBuilder 10 | 11 | class RobotPsiStructureViewFactory extends PsiStructureViewFactory { 12 | def getStructureViewBuilder(psiFile: PsiFile): StructureViewBuilder = new StructureViewBuilder { 13 | def createStructureView(fileEditor: FileEditor, project: Project): StructureView = 14 | new RobotTreeBasedStructureViewBuilder(psiFile.asInstanceOf[RobotPsiFile]) 15 | .createStructureView(fileEditor, project) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/SyntaxHighlighterFactory.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.openapi.fileTypes.SyntaxHighlighter 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.openapi.vfs.VirtualFile 6 | import amailp.intellij.robot 7 | 8 | class SyntaxHighlighterFactory extends com.intellij.openapi.fileTypes.SyntaxHighlighterFactory { 9 | def getSyntaxHighlighter(project: Project, virtualFile: VirtualFile): SyntaxHighlighter = 10 | new robot.highlighting.SyntaxHighlighter 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/extensions/UsageTypeProvider.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.usages.impl.rules.UsageType 5 | import amailp.intellij.robot.psi.Keyword 6 | import amailp.intellij.robot.findUsage.UsageTypes 7 | 8 | class UsageTypeProvider extends com.intellij.usages.impl.rules.UsageTypeProvider { 9 | def getUsageType(element: PsiElement): UsageType = element match { 10 | case _: Keyword => UsageTypes.KeywordUsage 11 | case _ => UsageType.UNCLASSIFIED 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/file/Icons.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.file 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.util.IconLoader 5 | import com.intellij.ui.LayeredIcon 6 | 7 | object Icons { 8 | val file = IconLoader.getIcon("/icons/robot_file_16.png", Icons.getClass) 9 | val robot = IconLoader.getIcon("/icons/robot_16.png", Icons.getClass) 10 | val robotTest = new LayeredIcon(robot, AllIcons.Nodes.JunitTestMark) 11 | val keywords = IconLoader.getIcon("/icons/keywords_16.png", Icons.getClass) 12 | val keyword = IconLoader.getIcon("/icons/keyword_16.png", Icons.getClass) 13 | val variables = IconLoader.getIcon("/icons/variables_16.png", Icons.getClass) 14 | val variable = IconLoader.getIcon("/icons/variable_16.png", Icons.getClass) 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/findUsage/UsageFindable.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.findUsage 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | trait UsageFindable { 6 | self: PsiElement => 7 | def getNodeText(useFullName: Boolean): String = self.getText 8 | def getDescriptiveName: String 9 | def getType: String 10 | def getHelpId: String = "AAA getHelpId" 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/findUsage/UsageTypes.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.findUsage 2 | 3 | import com.intellij.usages.impl.rules.UsageType 4 | 5 | object UsageTypes { 6 | val KeywordUsage = new UsageType(() => "Keyword usage") 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/findUsage/WordsScanner.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.findUsage 2 | 3 | import com.intellij.lang.cacheBuilder.DefaultWordsScanner 4 | import amailp.intellij.robot.lexer.RobotLexer 5 | import com.intellij.psi.tree.TokenSet 6 | import amailp.intellij.robot.elements.RobotTokenTypes 7 | 8 | class WordsScanner 9 | extends DefaultWordsScanner( 10 | new RobotLexer, 11 | TokenSet.create(RobotTokenTypes.Word, RobotTokenTypes.Space, RobotTokenTypes.ScalarVariable), 12 | RobotTokenTypes.CommentsTokens, 13 | TokenSet.EMPTY 14 | ) 15 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/highlighting/SyntaxHighlighter.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.highlighting 2 | 3 | import com.intellij.openapi.fileTypes.SyntaxHighlighterBase 4 | import com.intellij.lexer.Lexer 5 | import amailp.intellij.robot.lexer.RobotLexer 6 | import com.intellij.psi.tree.IElementType 7 | import com.intellij.openapi.editor.colors.TextAttributesKey 8 | import amailp.intellij.robot.elements.RobotTokenTypes._ 9 | import com.intellij.openapi.editor.DefaultLanguageHighlighterColors._ 10 | import com.intellij.openapi.editor.HighlighterColors._ 11 | 12 | class SyntaxHighlighter extends SyntaxHighlighterBase { 13 | def getHighlightingLexer: Lexer = new RobotLexer() 14 | 15 | def getTokenHighlights(tokenType: IElementType): Array[TextAttributesKey] = { 16 | 17 | def arrayOfAttributesKeys(fallbackKey: TextAttributesKey) = { 18 | Array(TextAttributesKey.createTextAttributesKey(tokenType.toString, fallbackKey)) 19 | } 20 | 21 | tokenType match { 22 | case token if HeaderTokens.contains(token) => arrayOfAttributesKeys(METADATA) 23 | case ScalarVariable | ListVariable | DictionaryVariable | EnvironmentVariable => arrayOfAttributesKeys(STRING) 24 | case TestCaseSetting => arrayOfAttributesKeys(INSTANCE_FIELD) 25 | case Comment => arrayOfAttributesKeys(LINE_COMMENT) 26 | case BadCharacter => arrayOfAttributesKeys(BAD_CHARACTER) 27 | case _ => Array() 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/lang/RobotLanguage.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.lang 2 | 3 | import com.intellij.lang.Language 4 | 5 | class RobotLanguage private extends Language("Robotframework") 6 | 7 | object RobotLanguage { 8 | val Instance = new RobotLanguage 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/lexer/RobotIElementType.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.lexer 2 | 3 | import com.intellij.psi.tree.IElementType 4 | import amailp.intellij.robot.lang.RobotLanguage 5 | 6 | class RobotIElementType(debugName: String) extends IElementType(debugName, RobotLanguage.Instance) 7 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/parser/PsiElementBuilder.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.parser 2 | 3 | import com.intellij.lang.ASTNode 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.extapi.psi.ASTWrapperPsiElement 6 | import amailp.intellij.robot.elements.RobotTokenTypes.Ellipsis 7 | import amailp.intellij.robot.{ast, psi} 8 | 9 | class PsiElementBuilder(node: ASTNode) { 10 | def build(): PsiElement = { 11 | node.getElementType match { 12 | case ast.Tables => new psi.Tables(node) 13 | case Ellipsis => new psi.Ellipsis(node) 14 | case ast.SettingsTable => new psi.Settings(node) 15 | case ast.SettingName | ast.ResourceKey | ast.LibraryKey => new psi.SettingName(node) 16 | case ast.ResourceValue => new psi.ResourceValue(node) 17 | case ast.LibraryValue => new psi.LibraryValue(node) 18 | case ast.TestCasesTable => new psi.TestCases(node) 19 | case ast.TestCaseDefinition => new psi.TestCaseDefinition(node) 20 | case ast.TestCaseName => new psi.TestCaseName(node) 21 | case ast.KeywordsTable => new psi.Keywords(node) 22 | case ast.KeywordDefinition => new psi.KeywordDefinition(node) 23 | case ast.KeywordName => new psi.KeywordName(node) 24 | case ast.Keyword => new psi.Keyword(node) 25 | case ast.VariablesTable => new psi.Variables(node) 26 | case ast.VariableDefinition => new psi.VariableDefinition(node) 27 | case _ => new ASTWrapperPsiElement(node) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/parser/RobotParser.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.parser 2 | 3 | import com.intellij.lang.{ASTNode, PsiBuilder, PsiParser} 4 | import com.intellij.psi.tree.IElementType 5 | import amailp.intellij.robot.elements.RobotTokenTypes._ 6 | import amailp.intellij.robot.ast 7 | 8 | object RobotParser extends PsiParser { 9 | def parse(root: IElementType, builder: PsiBuilder): ASTNode = { 10 | val robotBuilder = new RobotPsiBuilder(builder) 11 | import robotBuilder._ 12 | 13 | def parseTable(): Unit = { 14 | val tableMarker = mark 15 | val tableType = parseHeaderRow() match { 16 | case SettingsHeader => parseTableItemsWithSubParser(SettingParser); Some(ast.SettingsTable) 17 | case TestCasesHeader | TasksHeader => parseTableItemsWith(parseTestCaseDefinition); Some(ast.TestCasesTable) 18 | case KeywordsHeader => parseTableItemsWith(parseKeywordDefinition); Some(ast.KeywordsTable) 19 | case VariablesHeader => parseTableItemsWith(parseVariableDefinition); Some(ast.VariablesTable) 20 | case _ => None 21 | } 22 | tableType match { 23 | case Some(t) => tableMarker done t 24 | case None => tableMarker.drop() 25 | } 26 | } 27 | 28 | def parseHeaderRow(): IElementType = { 29 | val headerMark = mark 30 | val headerType = currentType 31 | advanceLexer() 32 | if (isHeader(headerType)) headerMark done headerType 33 | else headerMark error "Table header expected" 34 | consumeLineTerminator() 35 | headerType 36 | } 37 | 38 | def parseTableItemsWithSubParser(subParser: SubParser): Unit = { 39 | while (hasMoreTokens && !isHeader(currentType)) subParser.parse(robotBuilder) 40 | } 41 | 42 | def parseTableItemsWith(parseItem: () => Unit): Unit = { 43 | while (hasMoreTokens && !isHeader(currentType)) parseItem() 44 | } 45 | 46 | def parseTestCaseDefinition(): Unit = { 47 | parseMultilineDefinition(ast.TestCaseName, ast.TestCaseDefinition) 48 | } 49 | 50 | def parseKeywordDefinition(): Unit = { 51 | parseMultilineDefinition(ast.KeywordName, ast.KeywordDefinition) 52 | } 53 | 54 | def parseMultilineDefinition(nameType: IElementType, definitionType: IElementType): Unit = { 55 | if (currentIsSpace) error(s"$nameType expected, not space") 56 | val definitionMark = mark 57 | parseCellOfType(nameType) 58 | consumeLineTerminator() 59 | while (currentIsSeparator) { 60 | advanceLexer() 61 | val rowMarker = mark 62 | currentType match { 63 | case TestCaseSetting => parseRowContent() 64 | case ScalarVariable | ListVariable | DictionaryVariable => parseRowContent() // Maybe parseVariableDefinition? 65 | case Ellipsis => parseEllipsis(); parseRowContent() 66 | case _ => parseAction() 67 | } 68 | rowMarker done ast.TableRow 69 | } 70 | definitionMark done definitionType 71 | } 72 | 73 | def parseVariableDefinition(): Unit = { 74 | val definitionMark = mark 75 | parseExpectedTypeCell(ast.VariableName, Set(ScalarVariable, ListVariable, DictionaryVariable)) 76 | parseRemainingCells() 77 | while (continuesInNextLine()) { 78 | consumeLineTerminator() 79 | if (currentIsSpace) advanceLexer() 80 | parseEllipsis() 81 | parseRowContent(includingTerminator = false) 82 | } 83 | definitionMark done ast.VariableDefinition 84 | consumeLineTerminator() 85 | } 86 | 87 | def continuesInNextLine(): Boolean = { 88 | val first = lookAhead(1) 89 | val second = lookAhead(2) 90 | (first == Ellipsis 91 | || (first == Separator && second == Ellipsis) 92 | || (first == Space && second == Ellipsis)) 93 | } 94 | 95 | def parseAction(): Unit = { 96 | parseCellOfType(ast.Keyword) 97 | parseRowContent() 98 | } 99 | 100 | builder.setDebugMode(true) 101 | val rootMarker = mark 102 | val tablesMarker = mark 103 | while (hasMoreTokens) { 104 | parseTable() 105 | } 106 | tablesMarker done ast.Tables 107 | rootMarker done root 108 | builder.getTreeBuilt 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/parser/SettingParser.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.parser 2 | 3 | import amailp.intellij.robot.ast 4 | import amailp.intellij.robot.elements.RobotTokenTypes._ 5 | import com.intellij.psi.tree.IElementType 6 | 7 | object SettingParser extends SubParser { 8 | 9 | private val otherSettingNames = Set[String]("Variables", "Documentation", "Metadata", "Suite Setup", "Suite Teardown", 10 | "Suite Precondition", "Suite Postcondition", "Force Tags", "Default Tags", "Test Setup", "Test Teardown", 11 | "Test Precondition", "Test Postcondition", "Test Template", "Test Timeout") 12 | 13 | def parse(builder: RobotPsiBuilder) = { 14 | import builder._ 15 | 16 | def parseSettingFirstCell(): IElementType = { 17 | if (currentType == Ellipsis) 18 | parseEllipsis() 19 | else 20 | parseCellThen { 21 | case "Resource" => doneWithType(ast.ResourceKey) 22 | case "Library" => doneWithType(ast.LibraryKey) 23 | case cnt if otherSettingNames contains cnt => doneWithType(ast.SettingName) 24 | case _ => asError("Settings name not known") 25 | } 26 | } 27 | 28 | def parseResouceValue(): Unit = { 29 | consumeSeparator() 30 | parseCellOfType(ast.ResourceValue) 31 | } 32 | 33 | def parseLibraryValueAndParameters(): Unit = { 34 | consumeSeparator() 35 | val libraryValueMarker = mark 36 | parseCellOfType(ast.LibraryName) 37 | parseLibraryArgumentsIfPresent() 38 | parseLibraryAliasIfPresent() 39 | libraryValueMarker done ast.LibraryValue 40 | } 41 | 42 | def parseLibraryArgumentsIfPresent(): Unit = { 43 | while (currentIsSeparator) { 44 | consumeSeparator() 45 | if (currentType == WithName) 46 | return 47 | parseCellOfType(ast.NonEmptyCell) 48 | // TODO Add arguments parsing implementation 49 | } 50 | } 51 | 52 | def parseLibraryAliasIfPresent(): Unit = { 53 | if (currentType == WithName) { 54 | advanceLexer() 55 | consumeSeparator() 56 | parseCellOfType(ast.LibraryAlias) 57 | } 58 | } 59 | 60 | val settingMarker = mark 61 | parseSettingFirstCell() match { 62 | case ast.ResourceKey => parseResouceValue() 63 | case ast.LibraryKey => parseLibraryValueAndParameters() 64 | case _ => parseRemainingCells() 65 | } 66 | settingMarker done ast.Setting 67 | consumeLineTerminator() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/parser/package.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot 2 | 3 | import amailp.intellij.robot.elements.RobotTokenTypes._ 4 | import com.intellij.lang.PsiBuilder 5 | import com.intellij.lang.PsiBuilder.Marker 6 | import com.intellij.lang.impl.PsiBuilderAdapter 7 | import com.intellij.psi.tree.IElementType 8 | 9 | import scala.language.implicitConversions 10 | 11 | package object parser { 12 | 13 | trait SubParser { 14 | def parse(builder: RobotPsiBuilder): Unit 15 | } 16 | 17 | implicit class RobotPsiBuilder(builder: PsiBuilder) extends PsiBuilderAdapter(builder) { 18 | def currentType = getTokenType 19 | def currentText = getTokenText 20 | 21 | def parseRowContent(includingTerminator: Boolean = true): Unit = { 22 | if (!currentIsSeparator) parseCellOfType(ast.NonEmptyCell) 23 | parseRemainingCells() 24 | if (includingTerminator) 25 | consumeLineTerminator() 26 | } 27 | 28 | def parseExpectedTypeCell(parseType: IElementType, expectedTypes: Set[IElementType]): IElementType = 29 | if (expectedTypes.contains(currentType)) 30 | parseCellThen(collapseWithType(parseType)) 31 | else 32 | parseCellThen(asError("Expected: " + expectedTypes.mkString(" | "))) 33 | 34 | def parseCellOfType(cellType: IElementType): IElementType = 35 | parseCellThen(doneWithType(cellType)) 36 | 37 | def parseCellThen(action: ParametricActionOnMarker): IElementType = { 38 | val cellMarker = mark 39 | val contentBuilder = new StringBuilder 40 | while (!currentIsLineTerminator && currentType != Separator) { 41 | contentBuilder append currentText 42 | advanceLexer() 43 | } 44 | action(contentBuilder.result())(cellMarker) 45 | } 46 | 47 | type ActionOnMarker = Marker => IElementType 48 | def doneWithType(t: IElementType): ActionOnMarker = m => { m done t; t } 49 | def collapseWithType(t: IElementType): ActionOnMarker = m => { m collapse t; t } 50 | def asError(message: String): ActionOnMarker = { error(message); doneWithType(ast.NonEmptyCell) } 51 | 52 | type ParametricActionOnMarker = String => ActionOnMarker 53 | implicit def ignoreParameter(a: ActionOnMarker): ParametricActionOnMarker = s => a 54 | 55 | def parseRemainingCells(): Unit = { 56 | while (currentIsSeparator) { 57 | consumeSeparator() 58 | parseCellOfType(ast.NonEmptyCell) 59 | } 60 | } 61 | 62 | def parseEllipsis(): IElementType = { 63 | val ellipsisMark = mark 64 | if (currentType != Ellipsis) error("Ellipsis expected") 65 | advanceLexer() 66 | ellipsisMark done Ellipsis 67 | Ellipsis 68 | } 69 | 70 | def hasMoreTokens = !eof 71 | 72 | def currentIsSpace = currentIsSeparator || currentType == Space 73 | 74 | def currentIsSeparator = currentType == Separator 75 | 76 | def currentIsLineTerminator = currentType == LineTerminator || eof 77 | 78 | def consumeSeparator(): Unit = { 79 | if (!currentIsSeparator) error("Cell separator expected") 80 | advanceLexer() 81 | } 82 | 83 | def consumeLineTerminator(): Unit = { 84 | if (!currentIsLineTerminator && !eof) error("Line terminator expected") 85 | advanceLexer() 86 | } 87 | 88 | def isHeader(token: IElementType) = HeaderTokens contains token 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/Keyword.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import com.intellij.psi._ 4 | import com.intellij.lang.ASTNode 5 | import com.intellij.extapi.psi.ASTWrapperPsiElement 6 | import amailp.intellij.robot.findUsage.UsageFindable 7 | import amailp.intellij.robot.psi.reference.KeywordToDefinitionReference 8 | import amailp.intellij.robot.psi.utils.RobotPsiUtils 9 | import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry 10 | 11 | /** 12 | * An instance of a keyword when is used 13 | */ 14 | class Keyword(node: ASTNode) 15 | extends ASTWrapperPsiElement(node) 16 | with RobotPsiUtils 17 | with UsageFindable 18 | with PsiNameIdentifierOwner { 19 | 20 | override def getReferences: Array[PsiReference] = 21 | Array[PsiReference](new KeywordToDefinitionReference(this)) ++ 22 | ReferenceProvidersRegistry.getReferencesFromProviders(this) 23 | 24 | def getTextStrippedFromIgnoredPrefixes = { 25 | val textLowerCase = getText.toLowerCase 26 | for { 27 | prefix <- Keyword.lowerCaseIgnoredPrefixes 28 | if textLowerCase.startsWith(prefix) 29 | stripped = textLowerCase.replaceFirst(prefix, "").trim 30 | } yield stripped 31 | } ensuring { _.size < 2 } 32 | 33 | override def setName(name: String): PsiElement = { 34 | val dummyKeyword = createKeyword(name) 35 | this.getNode.getTreeParent.replaceChild(this.getNode, dummyKeyword.getNode) 36 | this 37 | } 38 | 39 | def getType: String = "Keyword" 40 | def getDescriptiveName: String = getNode.getText 41 | override def getNameIdentifier: PsiElement = this 42 | } 43 | 44 | object Keyword { 45 | val ignoredPrefixes = List("Given", "When", "Then", "And") 46 | val lowerCaseIgnoredPrefixes = ignoredPrefixes.map(_.toLowerCase) 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/KeywordDefinition.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import amailp.intellij.robot.ast 4 | import com.intellij.lang.ASTNode 5 | import com.intellij.psi._ 6 | import com.intellij.psi.util.PsiTreeUtil 7 | import scala.jdk.CollectionConverters._ 8 | import amailp.intellij.robot.findUsage.UsageFindable 9 | import amailp.intellij.robot.structureView.InStructureView 10 | import amailp.intellij.robot.file.Icons 11 | 12 | class KeywordDefinition(node: ASTNode) 13 | extends RobotPsiElement(node) 14 | with PsiNamedElement 15 | with UsageFindable 16 | with InStructureView 17 | with PsiTarget { 18 | private def keywordName = getNode.findChildByType(ast.KeywordName).getPsi(classOf[KeywordName]) 19 | override def getNodeText(useFullName: Boolean) = getName 20 | override def getName: String = keywordName.getText 21 | override def setName(name: String): PsiElement = { 22 | val dummyKeyword = createKeywordDefinition(name) 23 | this.getNode.replaceChild(keywordName.getNode, dummyKeyword.keywordName.getNode) 24 | this 25 | } 26 | 27 | def getType: String = "keyword" 28 | def getDescriptiveName: String = getName 29 | 30 | def structureViewText = getName 31 | def structureViewIcon = Icons.keyword 32 | def structureViewChildrenTokenTypes = Nil 33 | } 34 | 35 | object KeywordDefinition { 36 | def findMatchingInFiles(files: LazyList[RobotPsiFile], reference: String) = { 37 | for { 38 | keywordDefinition <- findInFiles(files) 39 | if keywordDefinition.keywordName matches reference 40 | } yield keywordDefinition 41 | } 42 | 43 | def findInFiles(files: LazyList[RobotPsiFile]) = { 44 | for { 45 | file <- files 46 | keywordDefinition <- findInFile(file) 47 | } yield keywordDefinition 48 | } 49 | 50 | def findInFile(file: RobotPsiFile) = 51 | PsiTreeUtil.findChildrenOfType(file.getNode.getPsi, classOf[KeywordDefinition]).asScala.toSet 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/KeywordName.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import com.intellij.extapi.psi.ASTWrapperPsiElement 4 | import com.intellij.lang.ASTNode 5 | import com.intellij.psi._ 6 | import com.intellij.psi.tree.TokenSet 7 | import amailp.intellij.robot.elements.RobotTokenTypes._ 8 | import com.intellij.openapi.util.TextRange 9 | import java.util.regex.Pattern 10 | import amailp.intellij.robot.psi.utils.RobotPsiUtils 11 | 12 | class KeywordName(node: ASTNode) extends ASTWrapperPsiElement(node) with RobotPsiUtils { 13 | def getDefinition: KeywordDefinition = element.getParent.asInstanceOf[KeywordDefinition] 14 | def scalarVariables = getNode.getChildren(TokenSet.create(ScalarVariable)) 15 | 16 | def textCaseInsensitiveExcludingScalarVariables = { 17 | val text = this.currentRobotFile.getText 18 | def quoteRange(range: TextRange): String = Pattern.quote(range.substring(text)) 19 | val result = new StringBuilder("(?i)") // Set case insensitivity for regex 20 | var doneOffset = getTextRange.getStartOffset 21 | for (variable <- scalarVariables 22 | .sortWith((v1, v2) => v1.getTextRange.getStartOffset < v2.getTextRange.getStartOffset)) { 23 | val variableRange = variable.getTextRange 24 | result.append(quoteRange(new TextRange(doneOffset, variableRange.getStartOffset))) 25 | result.append(".*") 26 | doneOffset = variableRange.getEndOffset 27 | } 28 | result.append(quoteRange(new TextRange(doneOffset, getTextRange.getEndOffset))).toString() 29 | } 30 | 31 | def matches(string: String) = string matches textCaseInsensitiveExcludingScalarVariables 32 | val element: PsiElement = this 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/LibraryValue.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import amailp.intellij.robot.ast.{LibraryAlias, LibraryName} 4 | import amailp.intellij.robot.psi.utils.RobotPsiUtils 5 | import com.intellij.extapi.psi.ASTWrapperPsiElement 6 | import com.intellij.lang.ASTNode 7 | import com.intellij.psi.PsiReference 8 | import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry 9 | import com.intellij.psi.util.QualifiedName 10 | 11 | class LibraryValue(node: ASTNode) extends ASTWrapperPsiElement(node) with RobotPsiUtils with Library { 12 | val libraryName: ASTNode = getNode.findChildByType(LibraryName) 13 | assume(libraryName != null, "It should not be null because of the way a LibraryValue is parsed") 14 | 15 | val libraryAlias: Option[ASTNode] = Option(getNode.findChildByType(LibraryAlias)) 16 | 17 | def getType: String = "Library" 18 | def getRobotName: String = libraryAlias.getOrElse(libraryName).getText 19 | def getQualifiedName: QualifiedName = QualifiedName.fromDottedString(libraryName.getText) 20 | 21 | override def getName: String = libraryName.getText 22 | override def getReferences: Array[PsiReference] = ReferenceProvidersRegistry.getReferencesFromProviders(this) 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/ResourceValue.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import com.intellij.lang.ASTNode 4 | import com.intellij.extapi.psi.ASTWrapperPsiElement 5 | import com.intellij.psi._ 6 | import amailp.intellij.robot.psi.reference.ResourceValueReference 7 | 8 | class ResourceValue(node: ASTNode) extends ASTWrapperPsiElement(node) { 9 | override def getReference: ResourceValueReference = new ResourceValueReference(this) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/RobotPsiElement.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import com.intellij.extapi.psi.ASTWrapperPsiElement 4 | import com.intellij.lang.ASTNode 5 | import com.intellij.psi.tree.TokenSet 6 | import amailp.intellij.robot.lexer.RobotIElementType 7 | import com.intellij.psi.PsiElement 8 | import scala.jdk.CollectionConverters._ 9 | import amailp.intellij.robot.psi.utils.RobotPsiUtils 10 | 11 | abstract class RobotPsiElement(node: ASTNode) extends ASTWrapperPsiElement(node) with RobotPsiUtils { 12 | def findChildrenByType[T <: PsiElement](tokenTypes: List[RobotIElementType]): Iterable[T] = { 13 | findChildrenByType[T](TokenSet.create(tokenTypes: _*)).asScala 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/RobotPsiFile.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import javax.swing.Icon 4 | 5 | import amailp.intellij.robot 6 | import amailp.intellij.robot.lang.RobotLanguage 7 | import com.intellij.extapi.psi.PsiFileBase 8 | import com.intellij.openapi.fileTypes.FileType 9 | import com.intellij.psi.FileViewProvider 10 | import com.intellij.psi.util.PsiTreeUtil 11 | 12 | import scala.annotation.tailrec 13 | import scala.jdk.CollectionConverters._ 14 | import scala.collection.immutable.LazyList.#:: 15 | 16 | class RobotPsiFile(viewProvider: FileViewProvider) extends PsiFileBase(viewProvider, RobotLanguage.Instance) { 17 | 18 | def getFileType: FileType = robot.file.FileType.INSTANCE 19 | 20 | override def toString: String = "RobotFile: " + getVirtualFile.getName 21 | 22 | override def getIcon(flags: Int): Icon = super.getIcon(flags) 23 | 24 | private def getImportedRobotFiles: LazyList[RobotPsiFile] = { 25 | PsiTreeUtil 26 | .findChildrenOfType(getNode.getPsi, classOf[ResourceValue]) 27 | .asScala 28 | .view 29 | .flatMap(c => Option(c.getReference).flatMap(_.resolveReferenceValue())) 30 | .to(LazyList) 31 | } 32 | 33 | def getImportedLibraries: Iterable[Library] = 34 | getLocallyImportedLibraries ++ getRecursivelyImportedLibraries ++ 35 | Iterable[Library](BuiltInLibrary) 36 | 37 | private def getLocallyImportedLibraries: Iterable[Library] = { 38 | for { 39 | lib: Library <- PsiTreeUtil.findChildrenOfType(getNode.getPsi, classOf[LibraryValue]).asScala 40 | } yield lib 41 | } 42 | 43 | private def getRecursivelyImportedLibraries: Iterable[Library] = { 44 | for { 45 | file <- getRecursivelyImportedRobotFiles 46 | lib <- file.getLocallyImportedLibraries 47 | } yield lib 48 | } 49 | 50 | def getRecursivelyImportedRobotFiles: LazyList[RobotPsiFile] = { 51 | @tailrec 52 | def visit(toVisit: LazyList[RobotPsiFile], 53 | visited: Set[RobotPsiFile], 54 | cumulated: Set[RobotPsiFile], 55 | accumulator: LazyList[RobotPsiFile]): LazyList[RobotPsiFile] = { 56 | toVisit match { 57 | case head #:: tail if visited.contains(head) => 58 | visit(toVisit.tail, visited, cumulated, accumulator) 59 | case head #:: tail if !visited.contains(head) => 60 | val importedFromHead = head.getImportedRobotFiles 61 | visit(toVisit.tail #::: importedFromHead, 62 | visited + head, 63 | cumulated ++ importedFromHead, 64 | accumulator #::: importedFromHead.filterNot(cumulated.contains)) 65 | case LazyList() => accumulator 66 | } 67 | } 68 | visit(LazyList(this), Set(), Set(), LazyList()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/TestCaseDefinition.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import com.intellij.lang.ASTNode 4 | import com.intellij.extapi.psi.ASTWrapperPsiElement 5 | import amailp.intellij.robot.ast 6 | import amailp.intellij.robot.structureView.InStructureView 7 | import amailp.intellij.robot.file.Icons 8 | 9 | class TestCaseDefinition(node: ASTNode) extends RobotPsiElement(node) with InStructureView { 10 | override def getName = testCaseName.getText 11 | def testCaseName = getNode.findChildByType(ast.TestCaseName).getPsi(classOf[TestCaseName]) 12 | 13 | def structureViewText: String = getName 14 | def structureViewIcon = Icons.robotTest 15 | def structureViewChildrenTokenTypes = Nil 16 | } 17 | 18 | class TestCaseName(node: ASTNode) extends ASTWrapperPsiElement(node) 19 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/VariableDefinition.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import amailp.intellij.robot.ast 4 | import amailp.intellij.robot.file.Icons 5 | import amailp.intellij.robot.findUsage.UsageFindable 6 | import amailp.intellij.robot.structureView.InStructureView 7 | import com.intellij.lang.ASTNode 8 | import com.intellij.psi.{PsiNamedElement, PsiElement} 9 | 10 | class VariableDefinition(node: ASTNode) 11 | extends RobotPsiElement(node) 12 | with InStructureView 13 | with PsiNamedElement 14 | with UsageFindable { 15 | private def variableName = Option(getNode.findChildByType(ast.VariableName)) 16 | override def getName: String = variableName.map(_.getText).orNull 17 | override def setName(name: String): PsiElement = { 18 | val dummyKeyword = createVariableDefinition(name) 19 | 20 | variableName.foreach(vN => dummyKeyword.variableName.foreach(dVN => this.getNode.replaceChild(vN, dVN))) 21 | this 22 | } 23 | 24 | override def structureViewText = getName 25 | override def structureViewChildrenTokenTypes = Nil 26 | override def structureViewIcon = Icons.variable 27 | 28 | override def getType: String = "variable" 29 | override def getDescriptiveName: String = getName 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/manipulator/Keyword.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.manipulator 2 | 3 | import com.intellij.psi.AbstractElementManipulator 4 | import com.intellij.openapi.util.TextRange 5 | import amailp.intellij.robot.psi 6 | 7 | class Keyword extends AbstractElementManipulator[psi.Keyword] { 8 | override def handleContentChange(element: psi.Keyword, range: TextRange, newContent: String): psi.Keyword = null 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/manipulator/LibraryValue.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.manipulator 2 | 3 | import amailp.intellij.robot.psi 4 | import com.intellij.openapi.util.TextRange 5 | import com.intellij.psi.AbstractElementManipulator 6 | 7 | class LibraryValue extends AbstractElementManipulator[psi.LibraryValue] { 8 | override def handleContentChange(element: psi.LibraryValue, range: TextRange, newContent: String): psi.LibraryValue = 9 | null 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/manipulator/ResourceValue.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.manipulator 2 | 3 | import com.intellij.psi.{PsiElement, AbstractElementManipulator} 4 | import com.intellij.openapi.util.TextRange 5 | import amailp.intellij.robot.psi 6 | import amailp.intellij.robot.psi.utils.ExtRobotPsiUtils 7 | 8 | class ResourceValue extends AbstractElementManipulator[psi.ResourceValue] { 9 | override def handleContentChange(element: psi.ResourceValue, 10 | range: TextRange, 11 | newContent: String): psi.ResourceValue = { 12 | val newElement = new ExtRobotPsiUtils { 13 | def utilsPsiElement: PsiElement = element 14 | }.createResourceValue(newContent) 15 | element.getNode.getTreeParent.replaceChild(element.getNode, newElement.getNode) 16 | element 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/package.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot 2 | 3 | import amailp.intellij.robot.file.Icons 4 | import amailp.intellij.robot.structureView.InStructureView 5 | import com.intellij.extapi.psi.ASTWrapperPsiElement 6 | import com.intellij.icons.AllIcons 7 | import com.intellij.lang.ASTNode 8 | import com.intellij.psi.util.QualifiedName 9 | import javax.swing.Icon 10 | 11 | package object psi { 12 | class Ellipsis(node: ASTNode) extends ASTWrapperPsiElement(node) 13 | class Settings(node: ASTNode) extends ASTWrapperPsiElement(node) 14 | class SettingName(node: ASTNode) extends ASTWrapperPsiElement(node) 15 | class Variable(node: ASTNode) extends RobotPsiElement(node) 16 | 17 | class Tables(node: ASTNode) extends RobotPsiElement(node) with InStructureView { 18 | def structureViewText: String = "AAA Tables structure view text" 19 | def structureViewIcon: Icon = null 20 | def structureViewChildrenTokenTypes = List(ast.TestCasesTable, ast.KeywordsTable, ast.VariablesTable) 21 | } 22 | 23 | class TestCases(node: ASTNode) extends RobotPsiElement(node) with InStructureView { 24 | def structureViewText = "Test Cases" 25 | def structureViewIcon = AllIcons.Nodes.TestSourceFolder 26 | def structureViewChildrenTokenTypes = List(ast.TestCaseDefinition) 27 | } 28 | 29 | class Keywords(node: ASTNode) extends RobotPsiElement(node) with InStructureView { 30 | def structureViewText = "Keywords" 31 | def structureViewIcon = Icons.keywords 32 | def structureViewChildrenTokenTypes = List(ast.KeywordDefinition) 33 | } 34 | 35 | class Variables(node: ASTNode) extends RobotPsiElement(node) with InStructureView { 36 | override def structureViewText = "Variables" 37 | override def structureViewIcon: Icon = Icons.variables 38 | override def structureViewChildrenTokenTypes = List(ast.VariableDefinition) 39 | } 40 | 41 | trait Library { 42 | def getName: String 43 | def getRobotName: String 44 | def getQualifiedName: QualifiedName 45 | } 46 | 47 | object BuiltInLibrary extends Library { 48 | def getName: String = "BuiltIn" 49 | def getRobotName: String = getName 50 | def getQualifiedName: QualifiedName = QualifiedName.fromDottedString(getName) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/reference/KeywordToDefinitionReference.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.reference 2 | 3 | import com.intellij.psi.{PsiElementResolveResult, ResolveResult, PsiElement, PsiPolyVariantReferenceBase} 4 | import com.intellij.codeInsight.lookup.{AutoCompletionPolicy, LookupElementBuilder} 5 | import amailp.intellij.robot.psi.{Keyword, KeywordDefinition} 6 | import amailp.intellij.robot.psi.utils.ExtRobotPsiUtils 7 | import amailp.intellij.robot.file.Icons 8 | 9 | class KeywordToDefinitionReference(keyword: Keyword) 10 | extends PsiPolyVariantReferenceBase[Keyword](keyword) 11 | with ExtRobotPsiUtils { 12 | 13 | override def handleElementRename(newElementName: String): PsiElement = getElement.setName(newElementName) 14 | 15 | override def getVariants = { 16 | val externalKeywordDefinitions = 17 | KeywordDefinition.findInFiles(currentRobotFile.getRecursivelyImportedRobotFiles).toSet 18 | 19 | val keywordNames = (externalKeywordDefinitions | KeywordDefinition.findInFile(currentRobotFile)).map(_.getName) 20 | 21 | val prefixedKeywords = for { 22 | keyword <- keywordNames 23 | prefix <- Keyword.ignoredPrefixes 24 | } yield s"$prefix $keyword" 25 | 26 | ( 27 | for (keyword <- keywordNames | prefixedKeywords) 28 | yield LookupElementBuilder 29 | .create(keyword) 30 | .withCaseSensitivity(false) 31 | .withTypeText("Keyword", true) 32 | .withIcon(Icons.keyword) 33 | .withAutoCompletionPolicy(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE) 34 | .asInstanceOf[AnyRef] 35 | ).toArray 36 | } 37 | 38 | override def multiResolve(incompleteCode: Boolean): Array[ResolveResult] = { 39 | for { 40 | keywordName <- getElement.getText :: getElement.getTextStrippedFromIgnoredPrefixes 41 | keywordDefinition <- KeywordDefinition.findMatchingInFiles( 42 | currentRobotFile #:: currentRobotFile.getRecursivelyImportedRobotFiles, 43 | keywordName 44 | ) 45 | } yield new PsiElementResolveResult(keywordDefinition) 46 | }.toArray 47 | 48 | def utilsPsiElement: PsiElement = getElement 49 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/reference/LibraryToDefinitionReference.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.reference 2 | 3 | import amailp.intellij.robot.psi.LibraryValue 4 | import amailp.intellij.robot.psi.utils.ExtRobotPsiUtils 5 | import com.intellij.codeInsight.lookup.{AutoCompletionPolicy, LookupElementBuilder} 6 | import com.intellij.psi._ 7 | import com.intellij.psi.util.QualifiedName 8 | import com.jetbrains.python.psi.impl.PyElementPresentation 9 | import com.jetbrains.python.psi.resolve.QualifiedNameFinder 10 | import com.jetbrains.python.psi.stubs.{PyClassNameIndex, PyModuleNameIndex} 11 | import com.jetbrains.python.psi.{PyElement, PyFile} 12 | 13 | import scala.jdk.CollectionConverters._ 14 | 15 | class LibraryToDefinitionReference(element: LibraryValue) 16 | extends PsiReferenceBase[PsiElement](element) 17 | with PsiPolyVariantReference 18 | with ExtRobotPsiUtils { 19 | 20 | override def getVariants: Array[AnyRef] = 21 | (for { 22 | lib <- currentRobotFile.getImportedLibraries.view 23 | } yield LookupElementBuilder 24 | .create(lib.getName) 25 | .withCaseSensitivity(false) 26 | .withTypeText("Library", true) 27 | .withAutoCompletionPolicy(AutoCompletionPolicy.GIVE_CHANCE_TO_OVERWRITE) 28 | .asInstanceOf[AnyRef]).to(Array) 29 | 30 | override def multiResolve(incompleteCode: Boolean): Array[ResolveResult] = { 31 | val qNameLast = QualifiedName.fromDottedString(element.getName).getLastComponent 32 | def getClassIfHasSameName(file: PyFile): PyElement = 33 | Option(file.findTopLevelClass(qNameLast)).getOrElse(file) 34 | 35 | def findModuleOrClass(name: String) = 36 | PyModuleNameIndex 37 | .find(name, element.getProject, true) 38 | .asScala 39 | .filter(PyElementPresentation.getPackageForFile(_) == element.getName) 40 | .map(getClassIfHasSameName) ++ 41 | PyClassNameIndex 42 | .find(name, element.getProject, true) 43 | .asScala 44 | .filter(QualifiedNameFinder.getQualifiedName(_) == element.getName) 45 | 46 | findModuleOrClass(qNameLast).map(new PsiElementResolveResult(_)).to(Array) 47 | } 48 | 49 | override def resolve: PsiElement = multiResolve(false).headOption.map(_.getElement).orNull 50 | 51 | def utilsPsiElement: PsiElement = element 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/reference/MethodFinderWithoutUnderscoresAndSpaces.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.reference 2 | 3 | import com.intellij.util.Processor 4 | import com.jetbrains.python.psi.PyFunction 5 | 6 | import scala.collection.mutable.ListBuffer 7 | 8 | /** 9 | * @author Sergey Khomutinin skhomuti@gmail.com 10 | * Date: 30.08.2019 11 | */ 12 | class MethodFinderWithoutUnderscoresAndSpaces(reference: String) extends Processor[PyFunction] { 13 | 14 | import MethodFinderWithoutUnderscoresAndSpaces._ 15 | 16 | val result: ListBuffer[PyFunction] = ListBuffer[PyFunction]() 17 | 18 | override def process(currentElement: PyFunction): Boolean = { 19 | val matches: Boolean = pyElementMatches(currentElement, reference) 20 | if (matches) 21 | result += currentElement 22 | !matches 23 | } 24 | 25 | def getResult: List[PyFunction] = { 26 | result.toList 27 | } 28 | } 29 | 30 | object MethodFinderWithoutUnderscoresAndSpaces { 31 | private def prepareMethodName(name: String): String = 32 | name.replace(" ", "").replace("_", "").toLowerCase 33 | 34 | private def isPrivate(name: String): Boolean = 35 | name.startsWith("_") 36 | 37 | def pyElementMatches(pyElement: PyFunction, reference: String): Boolean = { 38 | if (!isPrivate(pyElement.getName)) { 39 | val methodNamePrepared = prepareMethodName(reference) 40 | val currentElementPrepared = prepareMethodName(pyElement.getName) 41 | methodNamePrepared == currentElementPrepared 42 | } else false 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/reference/PythonKeywordToDefinitionReference.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.reference 2 | 3 | import java.util 4 | 5 | import amailp.intellij.robot.psi.reference.MethodFinderWithoutUnderscoresAndSpaces.pyElementMatches 6 | import amailp.intellij.robot.psi.utils.ExtRobotPsiUtils 7 | import com.intellij.navigation.NavigationItem 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.util.TextRange 10 | import com.intellij.psi._ 11 | import com.intellij.psi.util.{PsiTreeUtil, QualifiedName} 12 | import com.jetbrains.python.psi.stubs.{PyClassNameIndex, PyModuleNameIndex} 13 | import com.jetbrains.python.psi.{PyClass, PyFile, PyFunction} 14 | 15 | import scala.jdk.CollectionConverters._ 16 | import scala.language.reflectiveCalls 17 | 18 | class KeywordReference(reference: String) { 19 | private val qReference: QualifiedName = QualifiedName.fromDottedString(reference) 20 | def keywordName: String = qReference.getLastComponent 21 | def libraryName: Option[String] = 22 | if (qReference.getComponentCount == 1) None else Some(qReference.removeLastComponent().toString) 23 | } 24 | 25 | class PythonKeywordToDefinitionReference(element: PsiElement, textRange: TextRange) 26 | extends PsiReferenceBase[PsiElement](element, textRange) 27 | with PsiPolyVariantReference 28 | with ExtRobotPsiUtils { 29 | 30 | override def getVariants = Array.empty 31 | 32 | override def multiResolve(incompleteCode: Boolean): Array[ResolveResult] = { 33 | for { 34 | keywordDefinition <- findMatchingInLibraries(new KeywordReference(element.getText)) 35 | } yield new PsiElementResolveResult(keywordDefinition) 36 | }.toArray 37 | 38 | override def resolve: PsiElement = multiResolve(false).headOption.map(_.getElement).orNull 39 | 40 | private def findMatchingInLibraries(reference: KeywordReference) = { 41 | 42 | val libraries = currentRobotFile.getImportedLibraries.filter( 43 | l => reference.libraryName.forall(_.equalsIgnoreCase(l.getRobotName)) 44 | ) 45 | 46 | type find[T] = (String, Project, Boolean) => util.Collection[T] 47 | 48 | def mmm(qName: QualifiedName, pySomething: NavigationItem) = 49 | qName.getComponentCount == 1 || ((pySomething.getPresentation.getLocationString != null) && 50 | (pySomething.getPresentation.getLocationString contains qName.removeLastComponent().toString)) 51 | 52 | def findWith[T <: NavigationItem](index: find[T], find: (T, String) => Iterable[PsiElement]) = 53 | for { 54 | library <- libraries 55 | elem <- index(library.getQualifiedName.getLastComponent, element.getProject, true).asScala 56 | if mmm(library.getQualifiedName, elem) 57 | psiElement <- find(elem, reference.keywordName) 58 | } yield psiElement 59 | 60 | findWith(PyModuleNameIndex.find, findInPyFile) ++ findWith(PyClassNameIndex.find, findInPyClass) 61 | } 62 | 63 | private def findInPyFile(pyFile: PyFile, reference: String) = 64 | for { 65 | keyword <- PsiTreeUtil.findChildrenOfType(pyFile, classOf[PyFunction]).asScala 66 | if Option(keyword.getContainingClass).isEmpty && pyElementMatches(keyword, reference) 67 | } yield keyword 68 | 69 | private def findInPyClass(pyClass: PyClass, name: String): Iterable[PyFunction] = { 70 | val process = new MethodFinderWithoutUnderscoresAndSpaces(name) 71 | pyClass.visitMethods(process, true, null) 72 | process.getResult 73 | } 74 | 75 | def utilsPsiElement: PsiElement = element 76 | } 77 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/reference/ResourceValueReference.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.reference 2 | 3 | import amailp.intellij.robot.psi.utils.ExtRobotPsiUtils 4 | import amailp.intellij.robot.psi.{ResourceValue, RobotPsiFile} 5 | import com.intellij.openapi.module.ModuleUtilCore 6 | import com.intellij.openapi.roots.ModuleRootManager 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.psi.{PsiElement, PsiReferenceBase} 9 | 10 | class ResourceValueReference(element: ResourceValue) 11 | extends PsiReferenceBase[ResourceValue](element) 12 | with ExtRobotPsiUtils { 13 | def resourceFilePath: String = getElement.getText.replace("${/}", "/") 14 | 15 | override def resolve() = resolveReferenceValue().orNull 16 | override def getVariants: Array[AnyRef] = Array() 17 | override def utilsPsiElement: PsiElement = getElement 18 | 19 | def resolveReferenceValue(): Option[RobotPsiFile] = 20 | resolveLocalFile orElse resolveAbsoluteFile orElse resolveFromSourceRoots 21 | 22 | private def resolveLocalFile: Option[RobotPsiFile] = 23 | maybeToRobotPsiFile(Option(currentDirectory.findFileByRelativePath(resourceFilePath))) 24 | 25 | private def resolveAbsoluteFile: Option[RobotPsiFile] = 26 | maybeToRobotPsiFile(Option(currentFile.getFileSystem.findFileByPath(resourceFilePath))) 27 | 28 | private def resolveFromSourceRoots: Option[RobotPsiFile] = { 29 | def sourceRoots = 30 | ModuleRootManager.getInstance(ModuleUtilCore.findModuleForPsiElement(getElement)).getSourceRoots(true).toList 31 | sourceRoots 32 | .map(s => maybeToRobotPsiFile(Option(s.findFileByRelativePath(resourceFilePath)))) 33 | .foldLeft(None: Option[RobotPsiFile])((a, b) => a orElse b) 34 | } 35 | 36 | private def maybeToRobotPsiFile(file: Option[VirtualFile]): Option[RobotPsiFile] = 37 | file.flatMap( 38 | f => 39 | Option(psiManager.findFile(f)) 40 | .filter(_.isInstanceOf[RobotPsiFile]) 41 | .map(_.asInstanceOf[RobotPsiFile]) 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/utils/ExtRobotPsiUtils.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.utils 2 | 3 | import com.intellij.psi.{PsiFileFactory, PsiManager, PsiElement} 4 | import com.intellij.psi.util.PsiTreeUtil 5 | import amailp.intellij.robot.psi._ 6 | import amailp.intellij.robot.file.FileType 7 | import scala.jdk.CollectionConverters._ 8 | 9 | trait ExtRobotPsiUtils { 10 | def utilsPsiElement: PsiElement 11 | def currentRobotFile = PsiTreeUtil.getParentOfType(utilsPsiElement, classOf[RobotPsiFile]) 12 | def originalRobotFile = currentRobotFile.getOriginalFile.asInstanceOf[RobotPsiFile] 13 | def currentFile = originalRobotFile.getVirtualFile 14 | def currentDirectory = currentFile.getParent 15 | def psiManager = PsiManager.getInstance(utilsPsiElement.getProject) 16 | 17 | def createKeyword(name: String): Keyword = { 18 | val dummyFile = shtg(s""" 19 | |*** Keywords *** 20 | |KeyDef 21 | | $name 22 | """) 23 | PsiTreeUtil.findChildrenOfType(dummyFile.getNode.getPsi, classOf[Keyword]).asScala.head 24 | } 25 | 26 | def createKeywordDefinition(name: String): KeywordDefinition = { 27 | val dummyFile = shtg(s""" 28 | |*** Keywords *** 29 | |$name 30 | """) 31 | PsiTreeUtil.findChildrenOfType(dummyFile.getNode.getPsi, classOf[KeywordDefinition]).asScala.head 32 | } 33 | 34 | def createVariableDefinition(name: String): VariableDefinition = { 35 | val dummyFile = shtg(s""" 36 | |*** Variables *** 37 | |$name 38 | """) 39 | PsiTreeUtil.findChildrenOfType(dummyFile.getNode.getPsi, classOf[VariableDefinition]).asScala.head 40 | } 41 | 42 | def createResourceValue(path: String): ResourceValue = { 43 | val dummyFile = shtg(s""" 44 | |*** Settings *** 45 | |Resource $path 46 | """) 47 | PsiTreeUtil.findChildrenOfType(dummyFile.getNode.getPsi, classOf[ResourceValue]).asScala.head 48 | } 49 | 50 | def shtg(fileContent: String): RobotPsiFile = { 51 | PsiFileFactory 52 | .getInstance(utilsPsiElement.getProject) 53 | .createFileFromText("dummy", FileType.INSTANCE, fileContent.stripMargin) 54 | .asInstanceOf[RobotPsiFile] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/psi/utils/RobotPsiUtils.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.utils 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | trait RobotPsiUtils extends PsiElement with ExtRobotPsiUtils { 6 | def utilsPsiElement = this 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/structureView/InStructureView.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.structureView 2 | 3 | import com.intellij.navigation.ItemPresentation 4 | import javax.swing.Icon 5 | import com.intellij.ide.util.treeView.smartTree.TreeElement 6 | import com.intellij.ide.structureView.StructureViewTreeElement 7 | import amailp.intellij.robot.lexer.RobotIElementType 8 | import amailp.intellij.robot.psi.RobotPsiElement 9 | import com.intellij.ide.util.PsiNavigationSupport 10 | import com.intellij.pom.Navigatable 11 | 12 | trait InStructureView extends RobotPsiElement { 13 | def structureViewText: String 14 | def structureViewIcon: Icon 15 | def structureViewChildrenTokenTypes: List[RobotIElementType] 16 | 17 | val structureTreeElement: StructureViewTreeElement = new StructureViewTreeElement { 18 | //TODO Use PsiTreeUtil and get rid of intermediate class RobotPsiElement 19 | def getChildren: Array[TreeElement] = 20 | findChildrenByType[InStructureView](structureViewChildrenTokenTypes).view.map(_.structureTreeElement).to(Array) 21 | 22 | def getPresentation: ItemPresentation = new ItemPresentation { 23 | override def getPresentableText: String = structureViewText 24 | override def getIcon(unused: Boolean): Icon = structureViewIcon 25 | } 26 | def canNavigateToSource: Boolean = true 27 | def canNavigate: Boolean = getNavigatable.canNavigate 28 | def navigate(requestFocus: Boolean): Unit = getNavigatable.navigate(requestFocus) 29 | private def getNavigatable: Navigatable = PsiNavigationSupport.getInstance().getDescriptor(InStructureView.this) 30 | 31 | def getValue: AnyRef = InStructureView.this 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/structureView/RobotStructureViewModel.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.structureView 2 | 3 | import amailp.intellij.robot.psi.RobotPsiFile 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.ide.structureView.{StructureViewModelBase, StructureViewTreeElement} 6 | 7 | class RobotStructureViewModel(file: RobotPsiFile, editor: Editor, element: StructureViewTreeElement) 8 | extends StructureViewModelBase(file, editor, element) 9 | -------------------------------------------------------------------------------- /src/main/scala/amailp/intellij/robot/structureView/RobotTreeBasedStructureViewBuilder.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.structureView 2 | 3 | import amailp.intellij.robot.psi.RobotPsiFile 4 | import com.intellij.ide.structureView.{StructureViewTreeElement, StructureViewModel, TreeBasedStructureViewBuilder} 5 | import com.intellij.openapi.editor.Editor 6 | import amailp.intellij.robot.psi 7 | 8 | class RobotTreeBasedStructureViewBuilder(psiFile: RobotPsiFile) extends TreeBasedStructureViewBuilder { 9 | override def createStructureViewModel(editor: Editor): StructureViewModel = { 10 | val element: StructureViewTreeElement = psiFile.findChildByClass(classOf[psi.Tables]).structureTreeElement 11 | new RobotStructureViewModel(psiFile, editor, element) 12 | } 13 | 14 | override val isRootNodeShown = false 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/amailp/intellij/robot/parser/ParserTest.java: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.parser; 2 | 3 | import amailp.intellij.robot.lang.RobotLanguage; 4 | import com.intellij.lang.ASTNode; 5 | import com.intellij.lang.ParserDefinition; 6 | import com.intellij.lang.PsiBuilder; 7 | import com.intellij.lang.PsiParser; 8 | import com.intellij.lang.impl.PsiBuilderFactoryImpl; 9 | import com.intellij.openapi.util.io.StreamUtil; 10 | import com.intellij.psi.tree.IFileElementType; 11 | import com.intellij.testFramework.ParsingTestCase; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStreamReader; 15 | import java.nio.charset.StandardCharsets; 16 | 17 | public class ParserTest extends ParsingTestCase { 18 | 19 | public ParserTest() { 20 | super("", "robot", new amailp.intellij.robot.extensions.ParserDefinition()); 21 | } 22 | 23 | @Override 24 | protected String getTestDataPath() { 25 | return "testData"; 26 | } 27 | 28 | // Smoke test 29 | public void testCompleteParsing() throws IOException { 30 | ParserDefinition pd = new amailp.intellij.robot.extensions.ParserDefinition(); 31 | String robotTestCase = 32 | StreamUtil.readText( 33 | new InputStreamReader(pd.getClass().getClassLoader().getResourceAsStream("complete.robot"), StandardCharsets.UTF_8)); 34 | PsiBuilder builder = new PsiBuilderFactoryImpl().createBuilder(pd, pd.createLexer(null), robotTestCase); 35 | builder.setDebugMode(true); 36 | final PsiParser parser = RobotParser$.MODULE$; 37 | IFileElementType fileElem = new IFileElementType(RobotLanguage.Instance()); 38 | ASTNode root = parser.parse(fileElem, builder); 39 | // System.out.println(DebugUtil.treeToString(root, true)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/ClassA.py: -------------------------------------------------------------------------------- 1 | class ClassA: 2 | pass 3 | 4 | class ClassB: 5 | pass 6 | 7 | class ClassC: 8 | def __init__(self, arg1, arg2): 9 | pass -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/module_1/module_2/module_c.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/test/resources/LibraryReferencesTest/module_1/module_2/module_c.py -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/module_1/module_b.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmailP/robot-plugin/0b96af30bdce95f30017c48faa74e588a93eec65/src/test/resources/LibraryReferencesTest/module_1/module_b.py -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/module_a.py: -------------------------------------------------------------------------------- 1 | def hello(name): 2 | print "Hello, %s!" % name -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_ClassA.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library ClassA 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_ClassA_ClassA.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library ClassA.ClassA 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_ClassA_ClassB.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library ClassA.ClassB 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_ClassA_with_synonym.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library ClassA WITH NAME SomeClass -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_ClassC_with_args_and_synonym.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library ClassA.ClassC arg1=${EMPTY} arg2=some WITH NAME SomeClass # some comment -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_module_1_module_2_module_c.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_1.module_2.module_c 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_module_1_module_b.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_1.module_b 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_module_2_module_c.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_2.module_c 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_module_a.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_module_a_wrong_case.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_A 3 | -------------------------------------------------------------------------------- /src/test/resources/LibraryReferencesTest/using_module_b_no_package.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_b 3 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/DerivedClass.py: -------------------------------------------------------------------------------- 1 | class BaseClassA: 2 | def keyword_a(self): 3 | pass 4 | 5 | class DerivedClassB(BaseClassA): 6 | pass 7 | 8 | class DerivedClass(DerivedClassB): 9 | pass 10 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/caret_inside_derived_class_keyword.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library DerivedClass 3 | 4 | *** Test Cases *** 5 | Case 6 | Keyword_A 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/caret_inside_hello.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a 3 | 4 | *** Test Cases *** 5 | First Test 6 | hello 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/caret_inside_hello_world.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a 3 | 4 | *** Test Cases *** 5 | First Test 6 | hello world 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/caret_inside_keyword_from_class_method.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a.SampleClass 3 | 4 | *** Test Cases *** 5 | First Test 6 | action one 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/caret_inside_multiple_defined.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a 3 | Library module_a.SampleClass 4 | 5 | *** Test Cases *** 6 | First Test 7 | multiple_defined 8 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/caret_inside_no_underscores.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a 3 | 4 | *** Test Cases *** 5 | First Test 6 | no underscores 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/caret_inside_to_be_ignored.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a 3 | 4 | *** Test Cases *** 5 | First Test 6 | to be ignored 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/keyword_with_curly_braces.robot: -------------------------------------------------------------------------------- 1 | *** Test Cases *** 2 | First Test 3 | hello world {dollar} 4 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a.SampleClass WITH NAME SomeLibrary 3 | 4 | *** Test Cases *** 5 | Some Case 6 | Action_One 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_multiple_defined.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a 3 | Library module_a.SampleClass WITH NAME SomeLibrary 4 | 5 | *** Test Cases *** 6 | Some Case 7 | SomeLibrary.multiple_defined 8 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_with_dotted_prefix.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a.SampleClass WITH NAME Some.Library 3 | 4 | *** Test Cases *** 5 | Some Case 6 | Some.Library.Action_One 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_with_library_prefix.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a.SampleClass WITH NAME SomeLibrary 3 | 4 | *** Test Cases *** 5 | Some Case 6 | SomeLibrary.Action_One 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_with_lowercase_prefix.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a.SampleClass WITH NAME SomeLibrary 3 | 4 | *** Test Cases *** 5 | Some Case 6 | somelibrary.Action_One 7 | -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_library_with_args_reference.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library module_a.SampleClass WITH NAME SomeLibrary -------------------------------------------------------------------------------- /src/test/resources/PythonKeywordToDefinitionReferenceTest/module_a.py: -------------------------------------------------------------------------------- 1 | def hello(): 2 | pass 3 | 4 | 5 | def hello_world(): 6 | pass 7 | 8 | 9 | def nounderscores(): 10 | pass 11 | 12 | 13 | def _to_be_ignored(): 14 | pass 15 | 16 | def multiple_defined(): 17 | pass 18 | 19 | class SampleClass: 20 | def __init__(self): 21 | pass 22 | 23 | def action_one(self): 24 | pass 25 | 26 | def multiple_defined(self): 27 | pass 28 | -------------------------------------------------------------------------------- /src/test/resources/RobotPsiFileTest/include_some_keywords.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource some_keywords.robot 3 | -------------------------------------------------------------------------------- /src/test/resources/RobotPsiFileTest/include_some_keywords_with_path_separator.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource ..${/}RobotPsiFileTest${/}some_keywords.robot 3 | -------------------------------------------------------------------------------- /src/test/resources/RobotPsiFileTest/include_some_other_keywords.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource some_other_keywords.robot 3 | -------------------------------------------------------------------------------- /src/test/resources/RobotPsiFileTest/some_keywords.robot: -------------------------------------------------------------------------------- 1 | *** Keywords *** 2 | Some keyword first 3 | Some keywords second 4 | 5 | Some keyword second 6 | Some keywords first -------------------------------------------------------------------------------- /src/test/resources/RobotPsiFileTest/some_other_keywords.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Resource some_keywords.robot 3 | 4 | *** Keywords *** 5 | Some other keyword first 6 | Some other keyword second 7 | 8 | Some other keyword second 9 | Some other keyword first -------------------------------------------------------------------------------- /src/test/resources/RobotStructureTest/one_variable.robot: -------------------------------------------------------------------------------- 1 | *** Variables *** 2 | ${first} first value -------------------------------------------------------------------------------- /src/test/resources/complete.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library OperatingSystem 3 | Library library 4 | Library library.MyLibrary 5 | 6 | *** Variables *** 7 | ${MESSAGE} Hello, world! 8 | 9 | *** Test Cases *** 10 | My Test 11 | [Documentation] Example test 12 | [Tags] TAG1 TAG2 13 | [Setup][Teardown] 14 | Specify test setup and teardown. Have also synonyms [Precondition] and [Postcondition], respectively. 15 | [Template] 16 | Specifies the template keyword to use. The test itself will contain only data to use as arguments to that keyword. 17 | [Timeout] 18 | Log ${MESSAGE} 19 | My Keyword /tmp 20 | 21 | Second Test 22 | Given something 23 | When something else 24 | Then we are happy 25 | 26 | Another Test 27 | Should Be Equal ${MESSAGE} Hello, world! 28 | 29 | One More Test 30 | Hello world 31 | Do Nothing 32 | Keyword From Class 33 | 34 | *** Keywords *** 35 | My Keyword 36 | [Arguments] ${path} 37 | Directory Should Exist ${path} 38 | 39 | *** Tasks *** 40 | Process invoice 41 | Read information from PDF 42 | Validate information 43 | 44 | -------------------------------------------------------------------------------- /src/test/resources/library.py: -------------------------------------------------------------------------------- 1 | def hello(name): 2 | print "Hello, %s!" % name 3 | 4 | 5 | def do_nothing(): 6 | pass 7 | 8 | 9 | class MyLibrary: 10 | 11 | def keyword_from_class(self): 12 | pass 13 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/extensions/AnnotatorTest.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import amailp.intellij.robot.testFramework.RobotCodeInsightFixtureTestCase 4 | 5 | class AnnotatorTest extends RobotCodeInsightFixtureTestCase { 6 | 7 | def testVariablesInAnnotator(): Unit = { 8 | myFixture 9 | .configureByFiles( 10 | "complete.robot" 11 | ) 12 | 13 | myFixture.checkHighlighting(false, true, false, true) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/extensions/RobotStructureTest.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.extensions 2 | 3 | import amailp.intellij.robot.psi.RobotPsiFile 4 | import amailp.intellij.robot.structureView.RobotTreeBasedStructureViewBuilder 5 | import amailp.intellij.robot.testFramework.RobotCodeInsightFixtureTestCase 6 | 7 | class RobotStructureTest extends RobotCodeInsightFixtureTestCase { 8 | 9 | def testVariablesInStructure(): Unit = { 10 | val files = myFixture 11 | .configureByFiles( 12 | "RobotStructureTest/one_variable.robot" 13 | ) 14 | .map(_.asInstanceOf[RobotPsiFile]) 15 | 16 | val oneVariable = files.head 17 | 18 | val structureViewModel = 19 | new RobotTreeBasedStructureViewBuilder(oneVariable).createStructureViewModel(myFixture.getEditor) 20 | 21 | val root = structureViewModel.getRoot 22 | root.getChildren should have size 1 23 | 24 | val variables = root.getChildren()(0) 25 | variables.getPresentation.getPresentableText should equal("Variables") 26 | variables.getChildren should have size 1 27 | 28 | val firstVariable = variables.getChildren()(0) 29 | firstVariable.getPresentation.getPresentableText should equal("${first}") 30 | firstVariable.getChildren should have size 0 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/lexer/BaseLexerTest.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.lexer 2 | 3 | import org.junit.runner.RunWith 4 | import org.scalatest.BeforeAndAfter 5 | import com.intellij.psi.tree.IElementType 6 | import org.scalatest.funsuite.AnyFunSuite 7 | import org.scalatestplus.junit.JUnitRunner 8 | 9 | @RunWith(classOf[JUnitRunner]) 10 | class BaseLexerTest extends AnyFunSuite with BeforeAndAfter { 11 | var robotLexer: RobotLexer = null 12 | 13 | before { 14 | robotLexer = new RobotLexer 15 | } 16 | 17 | after { 18 | def checkAllWasLexed() = assert(robotLexer.getTokenStart == robotLexer.getTokenEnd, "Something was left unlexed") 19 | checkAllWasLexed() 20 | } 21 | 22 | def scanString(toBeScanned: String): Unit = { 23 | robotLexer.start(toBeScanned) 24 | } 25 | 26 | def nextTokenIsType(elementType: IElementType): Unit = { 27 | assert(elementType == robotLexer.getTokenType) 28 | robotLexer.advance() 29 | } 30 | 31 | def nextTokenIs(text: String, elementType: IElementType): Unit = { 32 | assert(text == robotLexer.getTokenText) 33 | nextTokenIsType(elementType) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/lexer/TestCells.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.lexer 2 | 3 | import java.util.Scanner 4 | 5 | import amailp.intellij.robot.elements.RobotTokenTypes 6 | import org.junit.runner.RunWith 7 | import org.scalatestplus.junit.JUnitRunner 8 | @RunWith(classOf[JUnitRunner]) 9 | class TestCells extends BaseLexerTest { 10 | test("ScalarVariable") { 11 | scanString("${a_Variable}") 12 | nextTokenIsType(RobotTokenTypes.ScalarVariable) 13 | } 14 | 15 | test("ListVariable") { 16 | scanString("@{aListVariable}") 17 | nextTokenIsType(RobotTokenTypes.ListVariable) 18 | } 19 | 20 | test("EnvironmentVariable") { 21 | scanString("%{anEnvVariable}") 22 | nextTokenIsType(RobotTokenTypes.EnvironmentVariable) 23 | } 24 | 25 | test("Word") { 26 | scanString("ThisIsAWord") 27 | nextTokenIsType(RobotTokenTypes.Word) 28 | } 29 | 30 | test("Library Alias Separator") { 31 | scanString("WITH NAME") 32 | nextTokenIsType(RobotTokenTypes.WithName) 33 | } 34 | 35 | test("WordWithSymbols") { 36 | scanString("!IsThisAWord??.") 37 | nextTokenIsType(RobotTokenTypes.Word) 38 | } 39 | 40 | test("testCell") { 41 | scanString(" A cell") 42 | nextTokenIsType(RobotTokenTypes.Separator) 43 | nextTokenIs("A", RobotTokenTypes.Word) 44 | nextTokenIsType(RobotTokenTypes.Space) 45 | nextTokenIs("cell", RobotTokenTypes.Word) 46 | } 47 | 48 | test("VariableCell") { 49 | scanString(" This ${is} cell \n") 50 | nextTokenIsType(RobotTokenTypes.Separator) 51 | nextTokenIs("This", RobotTokenTypes.Word) 52 | nextTokenIsType(RobotTokenTypes.Space) 53 | nextTokenIs("${is}", RobotTokenTypes.ScalarVariable) 54 | nextTokenIsType(RobotTokenTypes.Space) 55 | nextTokenIs("cell", RobotTokenTypes.Word) 56 | nextTokenIsType(RobotTokenTypes.IrrelevantSpaces) 57 | nextTokenIsType(RobotTokenTypes.LineTerminator) 58 | } 59 | 60 | test("VariableQuotedCell") { 61 | scanString(" This '${is}' cell \n") 62 | nextTokenIsType(RobotTokenTypes.Separator) 63 | nextTokenIs("This", RobotTokenTypes.Word) 64 | nextTokenIsType(RobotTokenTypes.Space) 65 | nextTokenIs("'", RobotTokenTypes.Word) 66 | nextTokenIs("${is}", RobotTokenTypes.ScalarVariable) 67 | nextTokenIs("'", RobotTokenTypes.Word) 68 | nextTokenIsType(RobotTokenTypes.Space) 69 | nextTokenIs("cell", RobotTokenTypes.Word) 70 | nextTokenIsType(RobotTokenTypes.IrrelevantSpaces) 71 | nextTokenIsType(RobotTokenTypes.LineTerminator) 72 | } 73 | 74 | test("NonVariableCell") { 75 | scanString(" This $ {is} notCell \n") 76 | nextTokenIsType(RobotTokenTypes.Separator) 77 | nextTokenIs("This", RobotTokenTypes.Word) 78 | nextTokenIsType(RobotTokenTypes.Space) 79 | nextTokenIs("$", RobotTokenTypes.Word) 80 | nextTokenIsType(RobotTokenTypes.Space) 81 | nextTokenIs("{is}", RobotTokenTypes.Word) 82 | nextTokenIsType(RobotTokenTypes.Space) 83 | nextTokenIs("notCell", RobotTokenTypes.Word) 84 | nextTokenIsType(RobotTokenTypes.IrrelevantSpaces) 85 | nextTokenIsType(RobotTokenTypes.LineTerminator) 86 | } 87 | 88 | test("NotEndedVariableCell") { 89 | scanString("This ${is notCell\n") 90 | nextTokenIs("This", RobotTokenTypes.Word) 91 | nextTokenIsType(RobotTokenTypes.Space) 92 | nextTokenIs("$", RobotTokenTypes.Word) 93 | nextTokenIs("{is", RobotTokenTypes.Word) 94 | nextTokenIsType(RobotTokenTypes.Space) 95 | nextTokenIs("notCell", RobotTokenTypes.Word) 96 | nextTokenIsType(RobotTokenTypes.LineTerminator) 97 | } 98 | 99 | test("ListVariableCell") { 100 | scanString("@{hello}") 101 | nextTokenIs("@{hello}", RobotTokenTypes.ListVariable) 102 | } 103 | 104 | test("DictionaryVariableCell") { 105 | scanString("&{hello}") 106 | nextTokenIs("&{hello}", RobotTokenTypes.DictionaryVariable) 107 | } 108 | 109 | test("Comment") { 110 | scanString(" #This is comment ") 111 | nextTokenIsType(RobotTokenTypes.IrrelevantSpaces) 112 | nextTokenIs("#This is comment ", RobotTokenTypes.Comment) 113 | } 114 | 115 | test("BlankLine") { 116 | scanString(" \n") 117 | nextTokenIsType(RobotTokenTypes.BlankLine) 118 | } 119 | 120 | test("Ellipsis") { 121 | scanString(" ...\n") 122 | nextTokenIsType(RobotTokenTypes.Space) 123 | nextTokenIsType(RobotTokenTypes.Ellipsis) 124 | nextTokenIsType(RobotTokenTypes.LineTerminator) 125 | } 126 | 127 | test("All") { 128 | val s: String = 129 | new Scanner(this.getClass.getClassLoader.getResourceAsStream("complete.robot")).useDelimiter("\\A").next 130 | scanString(s) 131 | while (robotLexer.getTokenType != null) { 132 | System.out.println(robotLexer.getTokenType.toString + "\t\t" + robotLexer.getTokenText) 133 | robotLexer.advance() 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/lexer/TestHeaders.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.lexer 2 | 3 | import amailp.intellij.robot.elements.RobotTokenTypes 4 | import org.junit.runner.RunWith 5 | import org.scalatestplus.junit.JUnitRunner 6 | 7 | @RunWith(classOf[JUnitRunner]) 8 | class TestHeaders extends BaseLexerTest { 9 | test("Header") { 10 | scanString("*** Setting ***") 11 | nextTokenIsType(RobotTokenTypes.SettingsHeader) 12 | } 13 | 14 | test("HeaderPlural") { 15 | scanString("*** Settings ***") 16 | nextTokenIsType(RobotTokenTypes.SettingsHeader) 17 | } 18 | 19 | test("LineBeforeHeader") { 20 | scanString("\n*** Test Cases ***") 21 | nextTokenIsType(RobotTokenTypes.BlankLine) 22 | nextTokenIsType(RobotTokenTypes.TestCasesHeader) 23 | } 24 | 25 | test("LineBetweenHeaders") { 26 | scanString("*** Settings ***\n*** Setting ***") 27 | nextTokenIsType(RobotTokenTypes.SettingsHeader) 28 | nextTokenIsType(RobotTokenTypes.LineTerminator) 29 | nextTokenIsType(RobotTokenTypes.SettingsHeader) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/psi/RobotPsiFileTest.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi 2 | 3 | import amailp.intellij.robot.testFramework.RobotCodeInsightFixtureTestCase 4 | 5 | class RobotPsiFileTest extends RobotCodeInsightFixtureTestCase { 6 | 7 | def testIsRobotPsiFile(): Unit = { 8 | myFixture.configureByFile("RobotPsiFileTest/include_some_keywords.robot") 9 | myFixture.getFile shouldBe a[RobotPsiFile] 10 | } 11 | 12 | def testImportedRobotFiles(): Unit = { 13 | val files = myFixture 14 | .configureByFiles( 15 | "RobotPsiFileTest/include_some_keywords.robot", 16 | "RobotPsiFileTest/some_keywords.robot" 17 | ) 18 | .map(_.asInstanceOf[RobotPsiFile]) 19 | 20 | val includeSomeKeywords = files.head 21 | val someKeywords = files(1) 22 | 23 | val imported = includeSomeKeywords.getRecursivelyImportedRobotFiles 24 | imported should have size 1 25 | imported should contain(someKeywords) 26 | } 27 | 28 | def testImportedRobotFilesWithPathSeparator(): Unit = { 29 | val files = myFixture 30 | .configureByFiles( 31 | "RobotPsiFileTest/include_some_keywords_with_path_separator.robot", 32 | "RobotPsiFileTest/some_keywords.robot" 33 | ) 34 | .map(_.asInstanceOf[RobotPsiFile]) 35 | 36 | val includeSomeKeywords = files.head 37 | val someKeywords = files(1) 38 | 39 | val imported = includeSomeKeywords.getRecursivelyImportedRobotFiles 40 | imported should have size 1 41 | imported should contain(someKeywords) 42 | } 43 | 44 | def testRecursivelyImportedRobotFiles(): Unit = { 45 | val files = myFixture 46 | .configureByFiles( 47 | "RobotPsiFileTest/include_some_other_keywords.robot", 48 | "RobotPsiFileTest/some_other_keywords.robot", 49 | "RobotPsiFileTest/some_keywords.robot" 50 | ) 51 | .map(_.asInstanceOf[RobotPsiFile]) 52 | 53 | val includeSomeOtherKeywords = files.head 54 | val someOtherKeywords = files(1) 55 | val someKeywords = files(2) 56 | 57 | val imported = includeSomeOtherKeywords.getRecursivelyImportedRobotFiles 58 | imported should have size 2 59 | imported should contain(someOtherKeywords) 60 | imported should contain(someKeywords) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/psi/reference/LibraryToDefinitionReferenceTest.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.reference 2 | 3 | import amailp.intellij.robot.testFramework.RobotCodeInsightFixtureTestCase 4 | import com.intellij.psi.ResolveResult 5 | import com.jetbrains.python.psi.PyClass 6 | 7 | class LibraryToDefinitionReferenceTest extends RobotCodeInsightFixtureTestCase { 8 | 9 | def containigFileNameOf(resolveResult: ResolveResult): String = 10 | resolveResult.getElement.getContainingFile.getName 11 | 12 | def testDanglingReference(): Unit = { 13 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_module_a.robot") 14 | 15 | val resolvedPsis = getResolvedPsisAtCaret 16 | 17 | resolvedPsis should have size 0 18 | } 19 | 20 | def testReferencesWithoutPackage(): Unit = { 21 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_module_a.robot", "LibraryReferencesTest/module_a.py") 22 | 23 | val resolvedPsis = getResolvedLibraryPsisAtCaret 24 | 25 | resolvedPsis should have size 1 26 | containigFileNameOf(resolvedPsis.head) should equal("module_a.py") 27 | } 28 | 29 | def testReferencesShouldObserveCaseSensitivity(): Unit = { 30 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_module_a_wrong_case.robot", 31 | "LibraryReferencesTest/module_a.py") 32 | 33 | val resolvedPsis = getResolvedPsisAtCaret 34 | 35 | resolvedPsis should have size 0 36 | } 37 | 38 | def testReferencesAllowPackages(): Unit = { 39 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_module_1_module_b.robot", 40 | "LibraryReferencesTest/module_1/module_b.py") 41 | 42 | val resolvedPsis = getResolvedLibraryPsisAtCaret 43 | 44 | resolvedPsis should have size 1 45 | containigFileNameOf(resolvedPsis.head) should equal("module_b.py") 46 | } 47 | 48 | def testReferencesShouldHavePackageIfPresent(): Unit = { 49 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_module_b_no_package.robot", 50 | "LibraryReferencesTest/module_1/module_b.py") 51 | 52 | val resolvedPsis = getResolvedPsisAtCaret 53 | 54 | resolvedPsis should have size 0 55 | } 56 | 57 | def testReferencesAllowNestedPackages(): Unit = { 58 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_module_1_module_2_module_c.robot", 59 | "LibraryReferencesTest/module_1/module_2/module_c.py") 60 | 61 | val resolvedPsis = getResolvedLibraryPsisAtCaret 62 | 63 | resolvedPsis should have size 1 64 | containigFileNameOf(resolvedPsis.head) should equal("module_c.py") 65 | } 66 | 67 | def testReferencesShouldNotMatchPartialPackages(): Unit = { 68 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_module_2_module_c.robot", 69 | "LibraryReferencesTest/module_1/module_2/module_c.py") 70 | 71 | val resolvedPsis = getResolvedPsisAtCaret 72 | 73 | resolvedPsis should have size 0 74 | } 75 | 76 | def testReferencesAllowClass(): Unit = { 77 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_ClassA_ClassA.robot", "LibraryReferencesTest/ClassA.py") 78 | 79 | val resolvedPsis = getResolvedLibraryPsisAtCaret 80 | 81 | resolvedPsis should have size 1 82 | resolvedPsis.head.getElement.asInstanceOf[PyClass].getName should equal("ClassA") 83 | } 84 | 85 | def testReferencesAllowClassWithNameOtherThanModule(): Unit = { 86 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_ClassA_ClassB.robot", "LibraryReferencesTest/ClassA.py") 87 | 88 | val resolvedPsis = getResolvedLibraryPsisAtCaret 89 | 90 | resolvedPsis should have size 1 91 | resolvedPsis.head.getElement.asInstanceOf[PyClass].getName should equal("ClassB") 92 | } 93 | 94 | def testReferencesAllowClassWithoutModule(): Unit = { 95 | copyFilesToProjectSkipDir("LibraryReferencesTest/using_ClassA.robot", "LibraryReferencesTest/ClassA.py") 96 | 97 | val resolvedPsis = getResolvedLibraryPsisAtCaret 98 | 99 | resolvedPsis should have size 1 100 | resolvedPsis.head.getElement.asInstanceOf[PyClass].getName should equal("ClassA") 101 | } 102 | 103 | def testReferenceToLibraryWithSynonym(): Unit = { 104 | copyFilesToProjectSkipDir( 105 | "LibraryReferencesTest/using_ClassA_with_synonym.robot", 106 | "LibraryReferencesTest/ClassA.py" 107 | ) 108 | val resolvedPsis = getResolvedLibraryPsisAtCaret 109 | resolvedPsis should have size 1 110 | resolvedPsis.head.getElement.asInstanceOf[PyClass].getName should equal("ClassA") 111 | } 112 | 113 | def testReferenceToLibraryWithArgsAndSynonym(): Unit = { 114 | copyFilesToProjectSkipDir( 115 | "LibraryReferencesTest/using_ClassC_with_args_and_synonym.robot", 116 | "LibraryReferencesTest/ClassA.py" 117 | ) 118 | val resolvedPsis = getResolvedLibraryPsisAtCaret 119 | resolvedPsis should have size 1 120 | resolvedPsis.head.getElement.asInstanceOf[PyClass].getName should equal("ClassC") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/psi/reference/PythonKeywordToDefinitionReferenceTest.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.psi.reference 2 | 3 | import amailp.intellij.robot.testFramework.RobotCodeInsightFixtureTestCase 4 | import com.jetbrains.python.psi.{PyClass, PyFile, PyFunction} 5 | 6 | class PythonKeywordToDefinitionReferenceTest extends RobotCodeInsightFixtureTestCase { 7 | 8 | def testSimpleReference(): Unit = { 9 | copyFilesToProjectSkipDir( 10 | "PythonKeywordToDefinitionReferenceTest/caret_inside_hello.robot", 11 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 12 | ) 13 | 14 | val resolvedPsis = getResolvedPsisAtCaret 15 | 16 | resolvedPsis should have size 1 17 | resolvedPsis.head.getElement shouldBe a[PyFunction] 18 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "hello" 19 | } 20 | 21 | def testReferenceFromMultipleWordKeyword(): Unit = { 22 | copyFilesToProjectSkipDir( 23 | "PythonKeywordToDefinitionReferenceTest/caret_inside_hello_world.robot", 24 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 25 | ) 26 | 27 | val resolvedPsis = getResolvedPsisAtCaret 28 | 29 | resolvedPsis should have size 1 30 | resolvedPsis.head.getElement shouldBe a[PyFunction] 31 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "hello_world" 32 | } 33 | 34 | def testReferenceIgnoringSpaces(): Unit = { 35 | copyFilesToProjectSkipDir( 36 | "PythonKeywordToDefinitionReferenceTest/caret_inside_no_underscores.robot", 37 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 38 | ) 39 | 40 | val resolvedPsis = getResolvedPsisAtCaret 41 | 42 | resolvedPsis should have size 1 43 | resolvedPsis.head.getElement shouldBe a[PyFunction] 44 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "nounderscores" 45 | } 46 | 47 | def testReferenceToBeIgnored(): Unit = { 48 | copyFilesToProjectSkipDir( 49 | "PythonKeywordToDefinitionReferenceTest/caret_inside_to_be_ignored.robot", 50 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 51 | ) 52 | 53 | val resolvedPsis = getResolvedPsisAtCaret 54 | 55 | resolvedPsis should have size 0 56 | } 57 | 58 | def testReferenceToClassMethod(): Unit = { 59 | copyFilesToProjectSkipDir( 60 | "PythonKeywordToDefinitionReferenceTest/caret_inside_keyword_from_class_method.robot", 61 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 62 | ) 63 | 64 | val resolvedPsis = getResolvedPsisAtCaret 65 | 66 | resolvedPsis should have size 1 67 | resolvedPsis.head.getElement shouldBe a[PyFunction] 68 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "action_one" 69 | } 70 | 71 | def testReferenceToDerivedClassMethod(): Unit = { 72 | copyFilesToProjectSkipDir( 73 | "PythonKeywordToDefinitionReferenceTest/caret_inside_derived_class_keyword.robot", 74 | "PythonKeywordToDefinitionReferenceTest/DerivedClass.py" 75 | ) 76 | 77 | val resolvedPsis = getResolvedPsisAtCaret 78 | 79 | resolvedPsis should have size 1 80 | resolvedPsis.head.getElement shouldBe a[PyFunction] 81 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "keyword_a" 82 | } 83 | 84 | def testReferenceToMultipleDefinedMethods(): Unit = { 85 | copyFilesToProjectSkipDir( 86 | "PythonKeywordToDefinitionReferenceTest/caret_inside_multiple_defined.robot", 87 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 88 | ) 89 | 90 | val resolvedPsis = getResolvedPsisAtCaret 91 | resolvedPsis should have size 2 92 | 93 | val moduleMethod = resolvedPsis.apply(0) 94 | val classMethod = resolvedPsis.apply(1) 95 | moduleMethod.getElement shouldBe a[PyFunction] 96 | moduleMethod.getElement.asInstanceOf[PyFunction].getName shouldBe "multiple_defined" 97 | moduleMethod.getElement.getParent.asInstanceOf[PyFile].getName shouldBe "module_a.py" 98 | classMethod.getElement shouldBe a[PyFunction] 99 | classMethod.getElement.asInstanceOf[PyFunction].getName shouldBe "multiple_defined" 100 | classMethod.getElement.getParent.getParent.asInstanceOf[PyClass].getName shouldBe "SampleClass" 101 | } 102 | 103 | def testCurlyBracesDoNotRaiseException(): Unit = { 104 | copyFilesToProjectSkipDir( 105 | "PythonKeywordToDefinitionReferenceTest/keyword_with_curly_braces.robot" 106 | ) 107 | 108 | noException should be thrownBy { 109 | getResolvedPsisAtCaret 110 | } 111 | } 112 | 113 | def testSynonymReferenceToKeyword(): Unit = { 114 | copyFilesToProjectSkipDir( 115 | "PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword.robot", 116 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 117 | ) 118 | val resolvedPsis = getResolvedPsisAtCaret 119 | resolvedPsis should have size 1 120 | resolvedPsis.head.getElement shouldBe a[PyFunction] 121 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "action_one" 122 | } 123 | 124 | def testSynonymReferenceToKeywordWithLibraryPrefix(): Unit = { 125 | copyFilesToProjectSkipDir( 126 | "PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_with_library_prefix.robot", 127 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 128 | ) 129 | val resolvedPsis = getResolvedPsisAtCaret 130 | resolvedPsis should have size 1 131 | resolvedPsis.head.getElement shouldBe a[PyFunction] 132 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "action_one" 133 | } 134 | 135 | def testSynonymReferenceToKeywordWithLowercasePrefix(): Unit = { 136 | copyFilesToProjectSkipDir( 137 | "PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_with_lowercase_prefix.robot", 138 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 139 | ) 140 | val resolvedPsis = getResolvedPsisAtCaret 141 | resolvedPsis should have size 1 142 | resolvedPsis.head.getElement shouldBe a[PyFunction] 143 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "action_one" 144 | } 145 | 146 | def testSynonymReferenceToKeywordWithDottedPrefix(): Unit = { 147 | copyFilesToProjectSkipDir( 148 | "PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_with_dotted_prefix.robot", 149 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 150 | ) 151 | val resolvedPsis = getResolvedPsisAtCaret 152 | resolvedPsis should have size 1 153 | resolvedPsis.head.getElement shouldBe a[PyFunction] 154 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "action_one" 155 | } 156 | 157 | def testSynonymReferenceToMultipleDefinedMethods(): Unit = { 158 | copyFilesToProjectSkipDir( 159 | "PythonKeywordToDefinitionReferenceTest/librarySynonyms/caret_inside_class_keyword_multiple_defined.robot", 160 | "PythonKeywordToDefinitionReferenceTest/module_a.py" 161 | ) 162 | val resolvedPsis = getResolvedPsisAtCaret 163 | resolvedPsis should have size 1 164 | resolvedPsis.head.getElement shouldBe a[PyFunction] 165 | resolvedPsis.head.getElement.asInstanceOf[PyFunction].getName shouldBe "multiple_defined" 166 | resolvedPsis.head.getElement.getParent.getParent.asInstanceOf[PyClass].getName shouldBe "SampleClass" 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/test/scala/amailp/intellij/robot/testFramework/RobotCodeInsightFixtureTestCase.scala: -------------------------------------------------------------------------------- 1 | package amailp.intellij.robot.testFramework 2 | 3 | import java.io.File 4 | 5 | import amailp.intellij.robot.psi.LibraryValue 6 | import com.intellij.psi.util.PsiTreeUtil 7 | import com.intellij.psi.{PsiPolyVariantReference, ResolveResult} 8 | import com.intellij.testFramework.fixtures.BasePlatformTestCase 9 | import org.junit.Ignore 10 | import org.scalatest.matchers.should.Matchers 11 | import org.scalatestplus.junit.AssertionsForJUnit 12 | 13 | @Ignore 14 | class RobotCodeInsightFixtureTestCase 15 | extends BasePlatformTestCase 16 | with Matchers 17 | with AssertionsForJUnit { 18 | override def getTestDataPath = 19 | new File(this.getClass.getClassLoader.getResource("complete.robot").toURI).getParent 20 | 21 | def copyFilesToProjectSkipDir(files: String*): Unit = 22 | files 23 | .map(f => myFixture.copyFileToProject(f, f.substring(f.indexOf('/')))) 24 | .headOption 25 | .foreach(myFixture.configureFromExistingVirtualFile) 26 | 27 | def getResolvedPsisAtCaret: Array[ResolveResult] = 28 | myFixture.getFile 29 | .findElementAt(myFixture.getCaretOffset) 30 | .getParent 31 | .getReferences 32 | .flatMap(_.asInstanceOf[PsiPolyVariantReference].multiResolve(false)) 33 | 34 | def getResolvedLibraryPsisAtCaret: Array[ResolveResult] = { 35 | val caretElement = myFixture.getFile.findElementAt(myFixture.getCaretOffset) 36 | PsiTreeUtil 37 | .getParentOfType(caretElement, classOf[LibraryValue]) 38 | .getReferences 39 | .flatMap(_.asInstanceOf[PsiPolyVariantReference].multiResolve(false)) 40 | } 41 | 42 | } 43 | --------------------------------------------------------------------------------