diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index dfe07704..00000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-# Auto detect text files and perform LF normalization
-* text=auto
diff --git a/.gitignore b/.gitignore
index 130f7e96..a83e773d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,10 @@
-# See https://help.github.com/ignore-files/ for more about ignoring files.
+*.DS_Store
-# dependencies
-/node_modules
+/.idea/
+*.iml
+/out/
+/build/
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-package-lock.json
\ No newline at end of file
+.gradle/
+gradle.properties
+/.sandbox/
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index e7e3f9ad..00000000
--- a/Dockerfile
+++ /dev/null
@@ -1,17 +0,0 @@
-# build environment
-FROM node:9.6.1 as builder
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-ENV PATH /usr/src/app/node_modules/.bin:$PATH
-COPY package.json /usr/src/app/package.json
-RUN npm install --silent
-RUN npm install react-scripts@1.1.1 -g --silent
-COPY . /usr/src/app
-RUN npm run build
-
-
-# production environment
-FROM nginx:1.13.9-alpine
-COPY --from=builder /usr/src/app/build /usr/share/nginx/html
-EXPOSE 80
-CMD ["nginx", "-g", "daemon off;"]
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 6ce4e73d..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2018 Jungwon Seo
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 00000000..e06d2081
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,202 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/README.md b/README.md
index 2fae463b..401d3ccf 100644
--- a/README.md
+++ b/README.md
@@ -1,64 +1,351 @@
-# Simple React
+# Custom Postfix Templates for Intellij IDEA
-Sample React.js application for the Docker environment.
+**Custom Postfix Templates** is an Intellij IDEA plugin that allows you to define your own custom [postfix completion templates](https://blog.jetbrains.com/idea/2014/03/postfix-completion/).
+At the moment it supports the following programming languages with : Java, Scala, SQL, PHP, Go, Groovy, Python, LaTeX, Kotlin (untyped templates), Dart (untyped templates), JavaScript (untyped templates), and Rust (untyped templates).
-## Getting Started
-
-App with one container. Reading from external open API. No storage. No secrets. Dynamic web page - including information from external API.
+## So what is the difference to IDEA's postfix templates?
-### Prerequisites
+Since IDEA 2018 you are now able to define your own postfix templates in the settings UI (*Editor → General → Postfix Templates*). However, this is a pretty new feature and it's less functional than this plugin. Here are some of the **advantages of this plugin**:
-Make sure you have already installed Docker Engine.
-You don’t need to install Nginx or NPM, as both are provided by Docker images.
+* You can **define different template rules for the same template name**, e.g. .toList should behave differently for arrays and for sets.
+* You can **use template variables** (e.g. `$varName$`) which are filled by the user while applying the template.
+* You can **use live template macros** to automatically fill some of the template variables (e.g. `$var:suggestVariableName()$`) as well as you can define default values.
+* You can **restrict the availability of templates or template rules to the availability of certain classes or libraries** (e.g. expand `"test".val` to `val s = "test"` if Lombok is available).
+* It allows you to **use static imports instead of class imports** (e.g. `array.toList` can be expanded to `asList(array)` instead of `Arrays.asList(array)` if you add `[USE_STATIC_IMPORTS]` to the rule).
+* It **comes with more than 500 editable postfix templates** with more than 700 template rules, e.g.
+ * `string.toInt` → `Integer.parse(string)`
+ * `array.toList` → `Arrays.asList(array)`
+ * `file.lines` → `Files.readAllLines(file.toPath(), Charset.forName("UTF-8"))`
+ * `file.getName().val` → `final String name = file.getName();`
+* The text based format for defining your templates allows you to **easily share** them via copy and paste.
-```
-$ docker -v
-Docker version 18.03.1-ce, build 9ee9f40
-```
+## Screencast
+
+
+
+## Download
+
+You can download the plugin **Custom Postfix Templates** via *Settings → Plugins → Browse Repositories*.
+
+## Usage
+
+The plugin comes with a predefined set of templates for Java and Scala (see below) which can be immediatly applied in Java/Scala files.
+For instance, write
+
+ "1".toInt
+
+in a Java file. If the completion popup does not automatically show up, press *Ctrl+SPACE*.
+Select the `.toInt` template and see how it is expanded.
+
+And if you want to see the template definition, just press *Alt+ENTER* in the completiion popup and select *Edit '.toInt' template*.
+
+## Kinds of template files
+
+There are three different types of template files:
+* User template files: use them to define your own templates and/or override local or web template rules
+* Local template files: loaded from the local file system, read-only, and updated automatically when an IDEA project is opened
+* Web template files: loaded from the web, read-only, and updated automatically once a day
+## Order of template files/rules
-### Installing
+Template rules are applied in a first-come-first-serve manner, i.e., more specific rules/files should be placed above more general rules/files.
+Reorder files in the tree by selecting them and by using the up/down buttons.
+## Predefined web templates files
+
+The plugin comes with a set of so-called "web template files" which provide in total [more than 200 useful templates](https://github.com/xylo/intellij-postfix-templates/wiki).
+While web template files are read-only and shall not be edited by the user because of automatic updates, you can still edit or deactivate templates of these files.
+
+To change or deactivate a predefined template you just have to start the template name completion with *Ctrl+Space* and then press *ALT+Enter* and select the third item (*Edit .TEMPLATE_NAME template*). The corresponding web template file is opened and you see the definition of the template rule. Since you cannot this template file directly you have to override the template rule by pressing *Alt+Enter* and selecting *Override template rule*. This overriding works in a way that your template rule needs to be loaded before the predefined template gets loaded. This is done by adding your rule to a user template file which is placed above the predefined web template file in the plugin settings. In case that you don't have a user template file which is loaded before, you are offered to create one. After you selected an existing user template or created a new one the template rule to override is automatically added to this file and you can start adapting it. To deactivate a template rule, replace the rigth side of the rule with *[SKIP]*.
+
+## Edit the templates
+
+Press *Shift+Alt+P* (or go to menu *Tools → Custom Postfix Templates → Edit Templates of Current Language*)
+to open the custom postfix templates for the programming language in your current editor.
+Here you can easily change, remove, or add new templates matching your needs.
+Note that you have to save the template file explicitly (via *Ctrl+S*) in order to update the postfix templates in the IDE.
+
+### Template definitions
+
+The file may contain multiple template definitions of the form:
```
-git clone https://github.com/thejungwon/docker-reactjs.git
-cd docker-reactjs
-docker build -t docker-reactjs .
-docker run -p 80:80 docker-reactjs
+.TEMPLATE_NAME : TEMPLATE_DESCRIPTION
+ TEMPLATE_RULE1
+ TEMPLATE_RULE2
+ ...
+```
+Each template definition consists of a template name, a template description and an arbitrary number of template rules. The template name is used as key in the code completion and the template description is shown as hint in the code completion popup. The template rules define on which types the template can be applied and how the application is performed.
+
+### Simple template rules
+A simple template rule has the form
+```
+ MATCHING_TYPE → TEMPLATE_CODE
```
-Go to [http://localhost](http://localhost)
+whereas
+* *[`MATCHING_TYPE`](#matching_type)* defines the type the template can be applied to, and
+* *[`TEMPLATE_CODE`](#template_code)* defines how the template is applied (how the expression is replaced).
+
+#### MATCHING_TYPE
+
+The options for *MATCHING_TYPE* may differ from programming language to programming language:
+* In **Java** the *MATCHING_TYPE* can be either a Java class name or one of the following special types:
+ * `ANY` - any expression
+ * `VOID` - any void expression
+ * `NON_VOID` - any non-void expression
+ * `ARRAY` - any Java array
+ * `BOOLEAN` - boxed or unboxed boolean expressions
+ * `ITERABLE_OR_ARRAY` - any iterable or array
+ * `NOT_PRIMITIVE` - any non-primitive value
+ * `NUMBER` - any boxed or unboxed number
+ * `BYTE` - a boxed or unboxed byte value
+ * `SHORT` - a boxed or unboxed short value
+ * `CHAR` - a boxed or unboxed char value
+ * `INT` - a boxed or unboxed int value
+ * `LONG` - a boxed or unboxed long value
+ * `FLOAT` - a boxed or unboxed float value
+ * `DOUBLE` - a boxed or unboxed double value
+ * `NUMBER_LITERAL` - any number literal
+ * `BYTE_LITERAL` - a byte literal
+ * `SHORT_LITERAL` - a short literal
+ * `CHAR_LITERAL` - a char literal
+ * `INT_LITERAL` - an int literal
+ * `LONG_LITERAL` - a long literal
+ * `FLOAT_LITERAL` - a float literal
+ * `DOUBLE_LITERAL` - a double literal
+ * `STRING_LITERAL` - a String literal
+ * `CLASS` - any class reference
+* In **Scala** the *MATCHING_TYPE* can be either a Java class name or one of the following special types:
+ * `ANY` - any expression
+ * `VOID` - any void (Unit) expression
+ * `NON_VOID` - any non-void (non-Unit) expression
+ * `BOOLEAN` - scala.Boolean or java.lang.Boolean
+ * `NUMBER` - any Scala or Java number value
+ * `BYTE` - scala.Byte or java.lang.Byte
+ * `SHORT` - scala.Short or java.lang.Short
+ * `CHAR` - scala.Char or java.lang.Char
+ * `INT` - scala.Int or java.lang.Integer
+ * `LONG` - scala.Long or java.lang.Long
+ * `FLOAT` - scala.Float or java.lang.Float
+ * `DOUBLE` - scala.Double or java.lang.Double
+* In **SQL** the *MATCHING_TYPE* can be either a Java class name or one of the following special types:
+ * `ANY` - any expression
+ * `UNKNOWN` - unknown expression
+ * `DEFAULT` - ?
+ * `INTEGER` - integer expression
+ * `REAL` - real expression
+ * `STRING` - string expression
+ * `BOOLEAN` - boolean expression
+ * `DATE_TIME` - date-time expression
+ * `DATE` - date expression
+ * `TIME` - time expression
+ * `TIMESTAMP` - timestamp expression
+ * `INTERVAL` - interval expression
+ * `BYTES` - bytes expression
+ * `REFERENCE` - ?
+ * `ARRAY` - array expression
+ * `COLLECTION` - collection expression
+ * `TABLE` - table reference
+ * `RECORD` - ?
+ * `SETO` - ?
+* In **PHP** the *MATCHING_TYPE* can be either a PHP class name or one of the following special types:
+ * `ANY` - any expression
+ * `empty`
+ * `null`
+ * `string`
+ * `boolean`
+ * `int`
+ * `float`
+ * `object`
+ * `callable`
+ * `resource`
+ * `array`
+ * `iterable`
+ * `number`
+ * `void`
+ * `unset`
+ * `static`
+ * `\Closure`
+ * `\Exception`
+ * `\Throwable`
+* In **Go** the *MATCHING_TYPE* can be one of the following special types:
+ * `ANY` - any expression
+ * `ARRAY` - any array
+ * `BOOLEAN` - any boolean expression
+ * `STRING` - any string expression
+ * `INT` - any integer expression
+ * `INT64` - any 64 bit integer expression
+ * `UINT` - any unsigned integer expression
+ * `FLOAT` - any floating point expression
+ * `FLOAT32` - any 32 bit floating point expression
+ * `FLOAT64` - any 64 bit floating point expression
+ * `BYTESLICE` - any byte slice expression
+ * `ERROR` - any error expression
+ * `COMPLEX` - ???
+ * `NIL` - any expression of type Nil
+* In **Groovy** the *MATCHING_TYPE* can be either a Java/Groovy class name or one of the following special types:
+ * `ANY` - any expression
+ * `ARRAY` - any Java array
+ * `BOOLEAN` - boxed or unboxed boolean expressions
+ * `ITERABLE_OR_ARRAY` - any iterable or array
+ * `NUMBER` - any boxed or unboxed number
+ * `BYTE` - a boxed or unboxed byte value
+ * `SHORT` - a boxed or unboxed short value
+ * `CHAR` - a boxed or unboxed char value
+ * `INT` - a boxed or unboxed int value
+ * `LONG` - a boxed or unboxed long value
+ * `FLOAT` - a boxed or unboxed float value
+ * `DOUBLE` - a boxed or unboxed double value
+ * `CLASS` - any class reference
+* In **Python** the *MATCHING_TYPE* can be one of the following special types:
+ * `ANY` - any expression
+ * `object`
+ * `list`
+ * `dict`
+ * `set`
+ * `tuple`
+ * `int`
+ * `float`
+ * `complex`
+ * `str`
+ * `unicode`
+ * `bytes`
+ * `bool`
+ * `classmethod`
+ * `staticmethod`
+ * `type`
+* In **Kotlin** the *MATCHING_TYPE* has to be `ANY`.
+* In **Dart** the *MATCHING_TYPE* has to be `ANY`.
+* In **JavaScript** the *MATCHING_TYPE* has to be `ANY`.
+* In **Rust** the *MATCHING_TYPE* has to be `ANY`
+* In **Latex** the *MATCHING_TYPE* can be one of the following types:
+ * `ANY` - any expression in any context
+ * `TEXT` - any expression that is *not* within a math environment
+ * `MATH` - any expression that *is* within a math environment
-## Running the tests
+#### TEMPLATE_CODE
-TBD
+The *TEMPLATE_CODE* can be any text which may also contain template variables used as placeholder.
+* Simple template variables have the format `$NAME$`.
+* The following template variables have a special meaning:
+ * `$expr$` - the expression the template shall be applied to
+ * `$END$` - the final cursor position after the template application
+* All other variables will be replaced interactively during the template expansion.
+* If you want to change the order of variables, set default values or use live template macros for filling the variables automatically, you can use the following variable format:
+ ```
+ $NAME#NO:EXPRESSION:DEFAULT_VALUE$
+ ```
+ * *NAME* - name of the variable; use a `*` at the end of the name to skip user interaction
+ * *NO* (optional) - number of the variable (defining in which order the variables are expanded)
+ * *EXPRESSION* (optional) - a live template macro used to generate a replacement (e.g. `suggestVariableName()`)
+ * *DEFAULT_VALUE* (optional) - a default value that may be used by the macro
+* If you want to create multi-line templates you can use a backslash (`\`) at the end of a line to indicate that the template code continues at the next line.
-### Break down into end to end tests
+#### Template Examples
-TBD
+* Artificial example showing variable reordering, variable reusage, interaction skipping, macros, and default values:
+ ```
+ .test : test
+ NON_VOID → "$user*#1:user()$: $second#3:className()$ + $first#2::"1st"$ + $first$" + $expr$
+ ```
+* Real world example: Write a variable to the debug log, including the developer name, the class name, and method name:
+ ```
+ .logd : log a variable
+ NON_VOID → Log.d("$user*:user():"MyTag"$", "$className*:className()$ :: $methodName*:methodName()$): $expr$="+$expr$);
+ ```
+* Multi-line template:
+ ```
+ .for : iterate over ...
+ ITERABLE_OR_ARRAY → for ($ELEMENT_TYPE:iterableComponentType(expr):"java.lang.Object"$ $VAR:suggestVariableName()$ : $expr$) {\
+ $END$\
+ }
+ ```
-### And coding style tests
+While writing the templates you can use the code completion for completing class names, variable names, template macros and arrows (→).
-TBD
+### Advanced template rules
+In the chapter above some options have been omitted for simplicity. If you need more functionality here is the full format of template rules including two optional parameters:
+```
+ MATCHING_TYPE [REQUIRED_CLASS] → TEMPLATE_CODE [FLAG]
+```
+* *REQUIRED_CLASS* (optional) is a name of a class that needs to be available in the module to activate the template rule (see next section for a detailed explaination)
+* *FLAG* (optional) can be one of the following flags:
+ * [`SKIP`](#skip) - skips the rule
+ * [`USE_STATIC_IMPORTS`](#use_static_imports) - adds static method imports automatically if possible (Java only)
+ * [`IMPORT` ...](#import) - adds an import to the file header (Scala only)
+
+#### Writing library specific template rules via REQUIRED_CLASS
+
+Sometimes you may want to write library specific template rules, i.e. rules that shall be only applied when a certain library is included in the project. For instance, take a look at the `.val` template provided with this plugin:
+```
+.val : extract as value
+ NON_VOID [lombok.val] → val $var:suggestVariableName()$ = $expr$;
+ NON_VOID → final $type*:expressionType(expr))$ $var:suggestVariableName()$ = $expr$;
+```
+It can be applied to any non-void expression and expands either to
+```
+val myVar = myExpression;
+```
+if lombok is available, or to
+```
+final MyType myVar = myExpression;
+```
+if you're using Java without lombok.
+
+In this exmaple template the `[lombok.val]` part after the matching type is used to restrict the rule appliction to those cases where the class `lombok.val` is available in the class path.
-## Built With
+In general you can use any class name between the square brackets you want to define a restriction on.
-* [Nginx](https://nginx.org/en/) - Web server
-* [React.js](https://reactjs.org/) - The front-end framework used
-* [Docker](https://www.docker.com/) - Containerization
-* [Materialize](https://materializecss.com/) - Front-end framework
+#### FLAGs
+##### SKIP
-## Authors
+You can use the `[SKIP]` flag for deactivating the template rule for a given matching type.
-* **Jungwon Seo** - *Initial work* - [thejungwon](https://github.com/thejungwon)
+Example:
+```
+.sort : sort naturally
+ de.endrullis.lazyseq.LazySeq → [SKIP]
+ java.util.List → java.util.Collections.sort($expr$)
+```
+
+In this example a postfix template `.sort` is defined.
+The first rule tells the plugin that there shall be no completition for expressions of type `LazySeq`.
+The second rule defines how `List` expressions shall be completed.
+##### USE_STATIC_IMPORTS
-## License
+If you tag a template rule for Java with `[USE_STATIC_IMPORTS]` all static methods that are used will be automatically imported and your code gets more compact. For instance, lets take the following template rule:
+```
+.toList : convert to List
+ ARRAY → java.util.Arrays.asList($expr$) [USE_STATIC_IMPORTS]
+```
+Since the rule is tagged with `[USE_STATIC_IMPORTS]` expanding of `array.toList` does not lead to `Arrays.asList(array)` but to `asList(array)` and the following line is added to your import statements:
+```
+import static java.util.Arrays.asList;
+```
+
+##### IMPORT
+
+If you tag a template rule for Scala with `[IMPORT FULLY_QUALIFIED_CLASSNAME]` the given class (or method) import is automatically added to the file header when the template gets applied:
+```
+.printStream : get PrintStream
+ java.io.File → new PrintStream($expr$) [IMPORT java.io.PrintStream]
+```
+Note that you can use the `IMPORT` flag multiple times.
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
+## Update templates and open plugin settings
+Go to *Settings → Editor → Custom Postfix Templates* or *Tools → Custom Postfix Templates → Open Settings / Upgrade Templates*. There you can chose between two different lambda styles and check/uncheck the template files you want to enable/disable.
+## Contribute
+Any contributions are welcome. Just fork the project, make your changes and create a pull request.
+Here are some guides:
+* [Create a template file for a utility class/library](https://github.com/xylo/intellij-postfix-templates/wiki/Create-a-template-file-for-a-utility-class-library)
+* [Add new language support](https://github.com/xylo/intellij-postfix-templates/wiki/Add-new-language-support)
+# See also
+* [Feature request for custom postfix completion at jetbrains.com](https://youtrack.jetbrains.com/issue/IDEA-122443)
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 00000000..49ea8ab1
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,125 @@
+plugins {
+ // Java support
+ id("java")
+ // Kotlin support
+ //id("org.jetbrains.kotlin.jvm") version "1.7.10"
+ // Gradle Changelog Plugin
+ //id("org.jetbrains.changelog") version "1.3.1"
+ // Gradle Qodana Plugin
+ id("org.jetbrains.qodana") version "0.1.13"
+ // Gradle IntelliJ Plugin
+ id("org.jetbrains.intellij") version "1.14.2"
+ kotlin("jvm") version "1.9.10"
+}
+
+group = "com.intellij"
+version = "2.20.2.241"
+
+tasks.withType {
+ sourceCompatibility = "17"
+ targetCompatibility = "17"
+ options.encoding = "UTF-8"
+}
+
+repositories {
+ mavenCentral()
+}
+
+
+dependencies {
+ implementation("commons-io:commons-io:2.11.0")
+ implementation("org.apache.commons:commons-lang3:3.13.0")
+ implementation("com.fasterxml.jackson.core:jackson-core:2.15.1")
+ implementation("com.fasterxml.jackson.core:jackson-databind:2.15.1")
+ implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.1")
+ implementation("io.sentry:sentry:6.9.0") {
+ exclude("org.slf4j")
+ }
+ // https://mvnrepository.com/artifact/org.projectlombok/lombok
+
+ compileOnly("org.projectlombok:lombok:1.18.26")
+ annotationProcessor("org.projectlombok:lombok:1.18.26")
+
+ testCompileOnly("org.projectlombok:lombok:1.18.26")
+ testAnnotationProcessor("org.projectlombok:lombok:1.18.26")
+
+ testImplementation("junit:junit:4.13.2")
+ //implementation(kotlin("stdlib-jdk8"))
+}
+
+
+
+
+sourceSets {
+ main {
+ java.srcDirs("src", "gen")
+ resources.srcDir("resources")
+ }
+
+ test {
+ java.srcDir("test/src")
+ resources.srcDir("test/resources")
+ }
+
+}
+
+
+intellij {
+ // full list of IntelliJ IDEA releases at https://www.jetbrains.com/intellij-repository/releases
+ // full list of IntelliJ IDEA EAP releases at https://www.jetbrains.com/intellij-repository/snapshots
+ //version "IU-233.11799.6-EAP-SNAPSHOT"
+ type.set("IU")
+ //version.set("241.14024.14-EAP-SNAPSHOT")
+ version.set("241.14494.240")
+
+ plugins.set(
+ listOf(
+ "java",
+ "Pythonid:241.14494.240",
+ "Kotlin",
+ "org.intellij.scala:2024.1.7",
+ "JavaScript",
+ //"CSS",
+ "Dart:241.15845",
+ "Groovy",
+ "properties",
+ "org.jetbrains.plugins.ruby:241.14494.240",
+ "com.jetbrains.php:241.14494.240",
+ "java-i18n",
+ "DatabaseTools",
+ "org.toml.lang",
+ "org.jetbrains.plugins.go:241.14494.240",
+ "nl.rubensten.texifyidea:0.9.4"
+ )
+ )
+ updateSinceUntilBuild.set(true)
+}
+
+tasks {
+ // Avoid ClassNotFoundException: com.maddyhome.idea.copyright.psi.UpdateCopyrightsProvider
+ buildSearchableOptions {
+ // jvmArgs = ["-Djava.system.class.loader=com.intellij.util.lang.PathClassLoader"]
+ enabled = false
+ }
+
+ runPluginVerifier {
+ //ideVersions.set(listOf(intellij.type.get() + "-" + intellij.version.get()))
+ //ideVersions("IU-222.3345.118")
+ //setFailureLevel(RunPluginVerifierTask.FailureLevel.ALL)
+ }
+
+ publishPlugin {
+ token.set(System.getenv("ORG_GRADLE_PROJECT_intellijPublishToken"))
+ }
+}
+/*
+kotlin {
+ jvmToolchain(20)
+}
+ */
+tasks.compileKotlin {
+ kotlinOptions {
+ jvmTarget = "17"
+ freeCompilerArgs = listOf("-Xjvm-default=all")
+ }
+}
diff --git a/dev/images/cpt-icon.svg b/dev/images/cpt-icon.svg
new file mode 100644
index 00000000..43eceb96
--- /dev/null
+++ b/dev/images/cpt-icon.svg
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+ p
+
+
+
+
diff --git a/dev/images/cpt-icon_2.svg b/dev/images/cpt-icon_2.svg
new file mode 100644
index 00000000..7eb19248
--- /dev/null
+++ b/dev/images/cpt-icon_2.svg
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/images/cpt-icon_3.svg b/dev/images/cpt-icon_3.svg
new file mode 100644
index 00000000..c416d3d2
--- /dev/null
+++ b/dev/images/cpt-icon_3.svg
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev/images/cpt-icon_3_opt.svg b/dev/images/cpt-icon_3_opt.svg
new file mode 100644
index 00000000..95619097
--- /dev/null
+++ b/dev/images/cpt-icon_3_opt.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/parser/CptParser.java b/gen/de/endrullis/idea/postfixtemplates/language/parser/CptParser.java
new file mode 100644
index 00000000..2b0a9f73
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/parser/CptParser.java
@@ -0,0 +1,340 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.parser;
+
+import com.intellij.lang.PsiBuilder;
+import com.intellij.lang.PsiBuilder.Marker;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import static com.intellij.lang.parser.GeneratedParserUtilBase.*;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.tree.TokenSet;
+import com.intellij.lang.PsiParser;
+import com.intellij.lang.LightPsiParser;
+
+@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"})
+public class CptParser implements PsiParser, LightPsiParser {
+
+ public ASTNode parse(IElementType t, PsiBuilder b) {
+ parseLight(t, b);
+ return b.getTreeBuilt();
+ }
+
+ public void parseLight(IElementType t, PsiBuilder b) {
+ boolean r;
+ b = adapt_builder_(t, b, this, null);
+ Marker m = enter_section_(b, 0, _COLLAPSE_, null);
+ if (t == ESCAPE) {
+ r = escape(b, 0);
+ }
+ else if (t == MAPPING) {
+ r = mapping(b, 0);
+ }
+ else if (t == MAPPINGS) {
+ r = mappings(b, 0);
+ }
+ else if (t == REPLACEMENT) {
+ r = replacement(b, 0);
+ }
+ else if (t == TEMPLATE) {
+ r = template(b, 0);
+ }
+ else if (t == TEMPLATE_CODE_G) {
+ r = templateCodeG(b, 0);
+ }
+ else if (t == TEMPLATE_VARIABLE) {
+ r = templateVariable(b, 0);
+ }
+ else if (t == TEMPLATE_VARIABLE_EXPRESSION_G) {
+ r = templateVariableExpressionG(b, 0);
+ }
+ else if (t == TEMPLATE_VARIABLE_NAME_G) {
+ r = templateVariableNameG(b, 0);
+ }
+ else if (t == TEMPLATE_VARIABLE_VALUE_G) {
+ r = templateVariableValueG(b, 0);
+ }
+ else {
+ r = parse_root_(t, b, 0);
+ }
+ exit_section_(b, 0, m, t, r, true, TRUE_CONDITION);
+ }
+
+ protected boolean parse_root_(IElementType t, PsiBuilder b, int l) {
+ return CptFile(b, l + 1);
+ }
+
+ /* ********************************************************** */
+ // (template|COMMENT)*
+ static boolean CptFile(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "CptFile")) return false;
+ int c = current_position_(b);
+ while (true) {
+ if (!CptFile_0(b, l + 1)) break;
+ if (!empty_element_parsed_guard_(b, "CptFile", c)) break;
+ c = current_position_(b);
+ }
+ return true;
+ }
+
+ // template|COMMENT
+ private static boolean CptFile_0(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "CptFile_0")) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = template(b, l + 1);
+ if (!r) r = consumeToken(b, COMMENT);
+ exit_section_(b, m, null, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // TEMPLATE_ESCAPE
+ public static boolean escape(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "escape")) return false;
+ if (!nextTokenIs(b, TEMPLATE_ESCAPE)) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_ESCAPE);
+ exit_section_(b, m, ESCAPE, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // CLASS_NAME (BRACKET_OPEN CLASS_NAME BRACKET_CLOSE)? MAP replacement
+ public static boolean mapping(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "mapping")) return false;
+ boolean r;
+ Marker m = enter_section_(b, l, _NONE_, MAPPING, "");
+ r = consumeToken(b, CLASS_NAME);
+ r = r && mapping_1(b, l + 1);
+ r = r && consumeToken(b, MAP);
+ r = r && replacement(b, l + 1);
+ exit_section_(b, l, m, r, false, recover_parser_parser_);
+ return r;
+ }
+
+ // (BRACKET_OPEN CLASS_NAME BRACKET_CLOSE)?
+ private static boolean mapping_1(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "mapping_1")) return false;
+ mapping_1_0(b, l + 1);
+ return true;
+ }
+
+ // BRACKET_OPEN CLASS_NAME BRACKET_CLOSE
+ private static boolean mapping_1_0(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "mapping_1_0")) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeTokens(b, 0, BRACKET_OPEN, CLASS_NAME, BRACKET_CLOSE);
+ exit_section_(b, m, null, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // mapping*
+ public static boolean mappings(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "mappings")) return false;
+ Marker m = enter_section_(b, l, _NONE_, MAPPINGS, "");
+ int c = current_position_(b);
+ while (true) {
+ if (!mapping(b, l + 1)) break;
+ if (!empty_element_parsed_guard_(b, "mappings", c)) break;
+ c = current_position_(b);
+ }
+ exit_section_(b, l, m, true, false, null);
+ return true;
+ }
+
+ /* ********************************************************** */
+ // !(TEMPLATE_NAME|CLASS_NAME|COMMENT)
+ static boolean recover_parser(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "recover_parser")) return false;
+ boolean r;
+ Marker m = enter_section_(b, l, _NOT_);
+ r = !recover_parser_0(b, l + 1);
+ exit_section_(b, l, m, r, false, null);
+ return r;
+ }
+
+ // TEMPLATE_NAME|CLASS_NAME|COMMENT
+ private static boolean recover_parser_0(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "recover_parser_0")) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_NAME);
+ if (!r) r = consumeToken(b, CLASS_NAME);
+ if (!r) r = consumeToken(b, COMMENT);
+ exit_section_(b, m, null, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // (templateCodeG|escape|templateVariable)+
+ public static boolean replacement(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "replacement")) return false;
+ boolean r;
+ Marker m = enter_section_(b, l, _NONE_, REPLACEMENT, "");
+ r = replacement_0(b, l + 1);
+ int c = current_position_(b);
+ while (r) {
+ if (!replacement_0(b, l + 1)) break;
+ if (!empty_element_parsed_guard_(b, "replacement", c)) break;
+ c = current_position_(b);
+ }
+ exit_section_(b, l, m, r, false, null);
+ return r;
+ }
+
+ // templateCodeG|escape|templateVariable
+ private static boolean replacement_0(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "replacement_0")) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = templateCodeG(b, l + 1);
+ if (!r) r = escape(b, l + 1);
+ if (!r) r = templateVariable(b, l + 1);
+ exit_section_(b, m, null, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // (TEMPLATE_NAME SEPARATOR TEMPLATE_DESCRIPTION) mappings
+ public static boolean template(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "template")) return false;
+ boolean r;
+ Marker m = enter_section_(b, l, _NONE_, TEMPLATE, "");
+ r = template_0(b, l + 1);
+ r = r && mappings(b, l + 1);
+ exit_section_(b, l, m, r, false, recover_parser_parser_);
+ return r;
+ }
+
+ // TEMPLATE_NAME SEPARATOR TEMPLATE_DESCRIPTION
+ private static boolean template_0(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "template_0")) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeTokens(b, 0, TEMPLATE_NAME, SEPARATOR, TEMPLATE_DESCRIPTION);
+ exit_section_(b, m, null, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // TEMPLATE_CODE
+ public static boolean templateCodeG(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateCodeG")) return false;
+ if (!nextTokenIs(b, TEMPLATE_CODE)) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_CODE);
+ exit_section_(b, m, TEMPLATE_CODE_G, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // TEMPLATE_VARIABLE_START templateVariableNameG
+ // (TEMPLATE_VARIABLE_SEPARATOR templateVariableExpressionG
+ // (TEMPLATE_VARIABLE_SEPARATOR templateVariableValueG)?)? TEMPLATE_VARIABLE_END
+ public static boolean templateVariable(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariable")) return false;
+ if (!nextTokenIs(b, TEMPLATE_VARIABLE_START)) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_VARIABLE_START);
+ r = r && templateVariableNameG(b, l + 1);
+ r = r && templateVariable_2(b, l + 1);
+ r = r && consumeToken(b, TEMPLATE_VARIABLE_END);
+ exit_section_(b, m, TEMPLATE_VARIABLE, r);
+ return r;
+ }
+
+ // (TEMPLATE_VARIABLE_SEPARATOR templateVariableExpressionG
+ // (TEMPLATE_VARIABLE_SEPARATOR templateVariableValueG)?)?
+ private static boolean templateVariable_2(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariable_2")) return false;
+ templateVariable_2_0(b, l + 1);
+ return true;
+ }
+
+ // TEMPLATE_VARIABLE_SEPARATOR templateVariableExpressionG
+ // (TEMPLATE_VARIABLE_SEPARATOR templateVariableValueG)?
+ private static boolean templateVariable_2_0(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariable_2_0")) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_VARIABLE_SEPARATOR);
+ r = r && templateVariableExpressionG(b, l + 1);
+ r = r && templateVariable_2_0_2(b, l + 1);
+ exit_section_(b, m, null, r);
+ return r;
+ }
+
+ // (TEMPLATE_VARIABLE_SEPARATOR templateVariableValueG)?
+ private static boolean templateVariable_2_0_2(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariable_2_0_2")) return false;
+ templateVariable_2_0_2_0(b, l + 1);
+ return true;
+ }
+
+ // TEMPLATE_VARIABLE_SEPARATOR templateVariableValueG
+ private static boolean templateVariable_2_0_2_0(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariable_2_0_2_0")) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_VARIABLE_SEPARATOR);
+ r = r && templateVariableValueG(b, l + 1);
+ exit_section_(b, m, null, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // TEMPLATE_VARIABLE_EXPRESSION*
+ public static boolean templateVariableExpressionG(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariableExpressionG")) return false;
+ Marker m = enter_section_(b, l, _NONE_, TEMPLATE_VARIABLE_EXPRESSION_G, "");
+ int c = current_position_(b);
+ while (true) {
+ if (!consumeToken(b, TEMPLATE_VARIABLE_EXPRESSION)) break;
+ if (!empty_element_parsed_guard_(b, "templateVariableExpressionG", c)) break;
+ c = current_position_(b);
+ }
+ exit_section_(b, l, m, true, false, null);
+ return true;
+ }
+
+ /* ********************************************************** */
+ // TEMPLATE_VARIABLE_NAME
+ public static boolean templateVariableNameG(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariableNameG")) return false;
+ if (!nextTokenIs(b, TEMPLATE_VARIABLE_NAME)) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_VARIABLE_NAME);
+ exit_section_(b, m, TEMPLATE_VARIABLE_NAME_G, r);
+ return r;
+ }
+
+ /* ********************************************************** */
+ // TEMPLATE_VARIABLE_VALUE+
+ public static boolean templateVariableValueG(PsiBuilder b, int l) {
+ if (!recursion_guard_(b, l, "templateVariableValueG")) return false;
+ if (!nextTokenIs(b, TEMPLATE_VARIABLE_VALUE)) return false;
+ boolean r;
+ Marker m = enter_section_(b);
+ r = consumeToken(b, TEMPLATE_VARIABLE_VALUE);
+ int c = current_position_(b);
+ while (r) {
+ if (!consumeToken(b, TEMPLATE_VARIABLE_VALUE)) break;
+ if (!empty_element_parsed_guard_(b, "templateVariableValueG", c)) break;
+ c = current_position_(b);
+ }
+ exit_section_(b, m, TEMPLATE_VARIABLE_VALUE_G, r);
+ return r;
+ }
+
+ final static Parser recover_parser_parser_ = new Parser() {
+ public boolean parse(PsiBuilder b, int l) {
+ return recover_parser(b, l + 1);
+ }
+ };
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptEscape.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptEscape.java
new file mode 100644
index 00000000..3f75996b
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptEscape.java
@@ -0,0 +1,8 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import com.intellij.psi.PsiElement;
+
+public interface CptEscape extends PsiElement {
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptMapping.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptMapping.java
new file mode 100644
index 00000000..01d3615a
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptMapping.java
@@ -0,0 +1,28 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+import com.intellij.navigation.ItemPresentation;
+
+public interface CptMapping extends CptNamedElement {
+
+ @NotNull
+ CptReplacement getReplacement();
+
+ String getMatchingClassName();
+
+ String getConditionClassName();
+
+ String getReplacementString();
+
+ String getName();
+
+ PsiElement setName(String newName);
+
+ PsiElement getNameIdentifier();
+
+ ItemPresentation getPresentation();
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptMappings.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptMappings.java
new file mode 100644
index 00000000..fa299f43
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptMappings.java
@@ -0,0 +1,13 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+
+public interface CptMappings extends PsiElement {
+
+ @NotNull
+ List getMappingList();
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptReplacement.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptReplacement.java
new file mode 100644
index 00000000..503ae2fa
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptReplacement.java
@@ -0,0 +1,19 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+
+public interface CptReplacement extends PsiElement {
+
+ @NotNull
+ List getEscapeList();
+
+ @NotNull
+ List getTemplateCodeGList();
+
+ @NotNull
+ List getTemplateVariableList();
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplate.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplate.java
new file mode 100644
index 00000000..92208e0c
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplate.java
@@ -0,0 +1,26 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+import com.intellij.navigation.ItemPresentation;
+
+public interface CptTemplate extends CptNamedElement {
+
+ @NotNull
+ CptMappings getMappings();
+
+ String getTemplateName();
+
+ String getTemplateDescription();
+
+ String getName();
+
+ PsiElement setName(String newName);
+
+ PsiElement getNameIdentifier();
+
+ ItemPresentation getPresentation();
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateCodeG.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateCodeG.java
new file mode 100644
index 00000000..f821182f
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateCodeG.java
@@ -0,0 +1,10 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+
+public interface CptTemplateCodeG extends PsiElement {
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariable.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariable.java
new file mode 100644
index 00000000..1c5efc4e
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariable.java
@@ -0,0 +1,19 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+
+public interface CptTemplateVariable extends PsiElement {
+
+ @Nullable
+ CptTemplateVariableExpressionG getTemplateVariableExpressionG();
+
+ @NotNull
+ CptTemplateVariableNameG getTemplateVariableNameG();
+
+ @Nullable
+ CptTemplateVariableValueG getTemplateVariableValueG();
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableExpressionG.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableExpressionG.java
new file mode 100644
index 00000000..8799fee6
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableExpressionG.java
@@ -0,0 +1,10 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+
+public interface CptTemplateVariableExpressionG extends PsiElement {
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableNameG.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableNameG.java
new file mode 100644
index 00000000..8073561e
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableNameG.java
@@ -0,0 +1,10 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+
+public interface CptTemplateVariableNameG extends PsiElement {
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableValueG.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableValueG.java
new file mode 100644
index 00000000..36da428c
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTemplateVariableValueG.java
@@ -0,0 +1,10 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElement;
+
+public interface CptTemplateVariableValueG extends PsiElement {
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTypes.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTypes.java
new file mode 100644
index 00000000..2da5276d
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptTypes.java
@@ -0,0 +1,75 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.PsiElement;
+import com.intellij.lang.ASTNode;
+import de.endrullis.idea.postfixtemplates.language.psi.impl.*;
+
+public interface CptTypes {
+
+ IElementType ESCAPE = new CptElementType("ESCAPE");
+ IElementType MAPPING = new CptElementType("MAPPING");
+ IElementType MAPPINGS = new CptElementType("MAPPINGS");
+ IElementType REPLACEMENT = new CptElementType("REPLACEMENT");
+ IElementType TEMPLATE = new CptElementType("TEMPLATE");
+ IElementType TEMPLATE_CODE_G = new CptElementType("TEMPLATE_CODE_G");
+ IElementType TEMPLATE_VARIABLE = new CptElementType("TEMPLATE_VARIABLE");
+ IElementType TEMPLATE_VARIABLE_EXPRESSION_G = new CptElementType("TEMPLATE_VARIABLE_EXPRESSION_G");
+ IElementType TEMPLATE_VARIABLE_NAME_G = new CptElementType("TEMPLATE_VARIABLE_NAME_G");
+ IElementType TEMPLATE_VARIABLE_VALUE_G = new CptElementType("TEMPLATE_VARIABLE_VALUE_G");
+
+ IElementType BRACKET_CLOSE = new CptTokenType("BRACKET_CLOSE");
+ IElementType BRACKET_OPEN = new CptTokenType("BRACKET_OPEN");
+ IElementType CLASS_NAME = new CptTokenType("CLASS_NAME");
+ IElementType COMMENT = new CptTokenType("COMMENT");
+ IElementType MAP = new CptTokenType("MAP");
+ IElementType SEPARATOR = new CptTokenType("SEPARATOR");
+ IElementType TEMPLATE_CODE = new CptTokenType("TEMPLATE_CODE");
+ IElementType TEMPLATE_DESCRIPTION = new CptTokenType("TEMPLATE_DESCRIPTION");
+ IElementType TEMPLATE_ESCAPE = new CptTokenType("TEMPLATE_ESCAPE");
+ IElementType TEMPLATE_NAME = new CptTokenType("TEMPLATE_NAME");
+ IElementType TEMPLATE_VARIABLE_END = new CptTokenType("TEMPLATE_VARIABLE_END");
+ IElementType TEMPLATE_VARIABLE_EXPRESSION = new CptTokenType("TEMPLATE_VARIABLE_EXPRESSION");
+ IElementType TEMPLATE_VARIABLE_NAME = new CptTokenType("TEMPLATE_VARIABLE_NAME");
+ IElementType TEMPLATE_VARIABLE_SEPARATOR = new CptTokenType("TEMPLATE_VARIABLE_SEPARATOR");
+ IElementType TEMPLATE_VARIABLE_START = new CptTokenType("TEMPLATE_VARIABLE_START");
+ IElementType TEMPLATE_VARIABLE_VALUE = new CptTokenType("TEMPLATE_VARIABLE_VALUE");
+
+ class Factory {
+ public static PsiElement createElement(ASTNode node) {
+ IElementType type = node.getElementType();
+ if (type == ESCAPE) {
+ return new CptEscapeImpl(node);
+ }
+ else if (type == MAPPING) {
+ return new CptMappingImpl(node);
+ }
+ else if (type == MAPPINGS) {
+ return new CptMappingsImpl(node);
+ }
+ else if (type == REPLACEMENT) {
+ return new CptReplacementImpl(node);
+ }
+ else if (type == TEMPLATE) {
+ return new CptTemplateImpl(node);
+ }
+ else if (type == TEMPLATE_CODE_G) {
+ return new CptTemplateCodeGImpl(node);
+ }
+ else if (type == TEMPLATE_VARIABLE) {
+ return new CptTemplateVariableImpl(node);
+ }
+ else if (type == TEMPLATE_VARIABLE_EXPRESSION_G) {
+ return new CptTemplateVariableExpressionGImpl(node);
+ }
+ else if (type == TEMPLATE_VARIABLE_NAME_G) {
+ return new CptTemplateVariableNameGImpl(node);
+ }
+ else if (type == TEMPLATE_VARIABLE_VALUE_G) {
+ return new CptTemplateVariableValueGImpl(node);
+ }
+ throw new AssertionError("Unknown element type: " + type);
+ }
+ }
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/CptVisitor.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptVisitor.java
new file mode 100644
index 00000000..a5c01535
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/CptVisitor.java
@@ -0,0 +1,58 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import org.jetbrains.annotations.*;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.PsiElement;
+
+public class CptVisitor extends PsiElementVisitor {
+
+ public void visitEscape(@NotNull CptEscape o) {
+ visitPsiElement(o);
+ }
+
+ public void visitMapping(@NotNull CptMapping o) {
+ visitNamedElement(o);
+ }
+
+ public void visitMappings(@NotNull CptMappings o) {
+ visitPsiElement(o);
+ }
+
+ public void visitReplacement(@NotNull CptReplacement o) {
+ visitPsiElement(o);
+ }
+
+ public void visitTemplate(@NotNull CptTemplate o) {
+ visitNamedElement(o);
+ }
+
+ public void visitTemplateCodeG(@NotNull CptTemplateCodeG o) {
+ visitPsiElement(o);
+ }
+
+ public void visitTemplateVariable(@NotNull CptTemplateVariable o) {
+ visitPsiElement(o);
+ }
+
+ public void visitTemplateVariableExpressionG(@NotNull CptTemplateVariableExpressionG o) {
+ visitPsiElement(o);
+ }
+
+ public void visitTemplateVariableNameG(@NotNull CptTemplateVariableNameG o) {
+ visitPsiElement(o);
+ }
+
+ public void visitTemplateVariableValueG(@NotNull CptTemplateVariableValueG o) {
+ visitPsiElement(o);
+ }
+
+ public void visitNamedElement(@NotNull CptNamedElement o) {
+ visitPsiElement(o);
+ }
+
+ public void visitPsiElement(@NotNull PsiElement o) {
+ visitElement(o);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptEscapeImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptEscapeImpl.java
new file mode 100644
index 00000000..eab111b2
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptEscapeImpl.java
@@ -0,0 +1,26 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElementVisitor;
+import de.endrullis.idea.postfixtemplates.language.psi.CptEscape;
+import de.endrullis.idea.postfixtemplates.language.psi.CptVisitor;
+import org.jetbrains.annotations.NotNull;
+
+public class CptEscapeImpl extends ASTWrapperPsiElement implements CptEscape {
+
+ public CptEscapeImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitEscape(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptMappingImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptMappingImpl.java
new file mode 100644
index 00000000..53161b29
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptMappingImpl.java
@@ -0,0 +1,62 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.navigation.ItemPresentation;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.language.psi.CptReplacement;
+import de.endrullis.idea.postfixtemplates.language.psi.CptVisitor;
+import org.jetbrains.annotations.NotNull;
+
+public class CptMappingImpl extends CptNamedElementImpl implements CptMapping {
+
+ public CptMappingImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitMapping(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+ @Override
+ @NotNull
+ public CptReplacement getReplacement() {
+ return findNotNullChildByClass(CptReplacement.class);
+ }
+
+ public String getMatchingClassName() {
+ return CptPsiImplUtil.getMatchingClassName(this);
+ }
+
+ public String getConditionClassName() {
+ return CptPsiImplUtil.getConditionClassName(this);
+ }
+
+ public String getReplacementString() {
+ return CptPsiImplUtil.getReplacementString(this);
+ }
+
+ public String getName() {
+ return CptPsiImplUtil.getName(this);
+ }
+
+ public PsiElement setName(String newName) {
+ return CptPsiImplUtil.setName(this, newName);
+ }
+
+ public PsiElement getNameIdentifier() {
+ return CptPsiImplUtil.getNameIdentifier(this);
+ }
+
+ public ItemPresentation getPresentation() {
+ return CptPsiImplUtil.getPresentation(this);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptMappingsImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptMappingsImpl.java
new file mode 100644
index 00000000..33f15ca9
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptMappingsImpl.java
@@ -0,0 +1,35 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+
+public class CptMappingsImpl extends ASTWrapperPsiElement implements CptMappings {
+
+ public CptMappingsImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitMappings(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+ @Override
+ @NotNull
+ public List getMappingList() {
+ return PsiTreeUtil.getChildrenOfTypeAsList(this, CptMapping.class);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptReplacementImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptReplacementImpl.java
new file mode 100644
index 00000000..b8d68eca
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptReplacementImpl.java
@@ -0,0 +1,47 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+
+public class CptReplacementImpl extends ASTWrapperPsiElement implements CptReplacement {
+
+ public CptReplacementImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitReplacement(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+ @Override
+ @NotNull
+ public List getEscapeList() {
+ return PsiTreeUtil.getChildrenOfTypeAsList(this, CptEscape.class);
+ }
+
+ @Override
+ @NotNull
+ public List getTemplateCodeGList() {
+ return PsiTreeUtil.getChildrenOfTypeAsList(this, CptTemplateCodeG.class);
+ }
+
+ @Override
+ @NotNull
+ public List getTemplateVariableList() {
+ return PsiTreeUtil.getChildrenOfTypeAsList(this, CptTemplateVariable.class);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateCodeGImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateCodeGImpl.java
new file mode 100644
index 00000000..3b4251e4
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateCodeGImpl.java
@@ -0,0 +1,29 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+
+public class CptTemplateCodeGImpl extends ASTWrapperPsiElement implements CptTemplateCodeG {
+
+ public CptTemplateCodeGImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitTemplateCodeG(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateImpl.java
new file mode 100644
index 00000000..32df5434
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateImpl.java
@@ -0,0 +1,59 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+import com.intellij.navigation.ItemPresentation;
+
+public class CptTemplateImpl extends CptNamedElementImpl implements CptTemplate {
+
+ public CptTemplateImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitTemplate(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+ @Override
+ @NotNull
+ public CptMappings getMappings() {
+ return findNotNullChildByClass(CptMappings.class);
+ }
+
+ public String getTemplateName() {
+ return CptPsiImplUtil.getTemplateName(this);
+ }
+
+ public String getTemplateDescription() {
+ return CptPsiImplUtil.getTemplateDescription(this);
+ }
+
+ public String getName() {
+ return CptPsiImplUtil.getName(this);
+ }
+
+ public PsiElement setName(String newName) {
+ return CptPsiImplUtil.setName(this, newName);
+ }
+
+ public PsiElement getNameIdentifier() {
+ return CptPsiImplUtil.getNameIdentifier(this);
+ }
+
+ public ItemPresentation getPresentation() {
+ return CptPsiImplUtil.getPresentation(this);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableExpressionGImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableExpressionGImpl.java
new file mode 100644
index 00000000..6f3ca518
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableExpressionGImpl.java
@@ -0,0 +1,29 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+
+public class CptTemplateVariableExpressionGImpl extends ASTWrapperPsiElement implements CptTemplateVariableExpressionG {
+
+ public CptTemplateVariableExpressionGImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitTemplateVariableExpressionG(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableImpl.java
new file mode 100644
index 00000000..344b3c94
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableImpl.java
@@ -0,0 +1,47 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+
+public class CptTemplateVariableImpl extends ASTWrapperPsiElement implements CptTemplateVariable {
+
+ public CptTemplateVariableImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitTemplateVariable(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+ @Override
+ @Nullable
+ public CptTemplateVariableExpressionG getTemplateVariableExpressionG() {
+ return findChildByClass(CptTemplateVariableExpressionG.class);
+ }
+
+ @Override
+ @NotNull
+ public CptTemplateVariableNameG getTemplateVariableNameG() {
+ return findNotNullChildByClass(CptTemplateVariableNameG.class);
+ }
+
+ @Override
+ @Nullable
+ public CptTemplateVariableValueG getTemplateVariableValueG() {
+ return findChildByClass(CptTemplateVariableValueG.class);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableNameGImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableNameGImpl.java
new file mode 100644
index 00000000..15d785ca
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableNameGImpl.java
@@ -0,0 +1,29 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+
+public class CptTemplateVariableNameGImpl extends ASTWrapperPsiElement implements CptTemplateVariableNameG {
+
+ public CptTemplateVariableNameGImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitTemplateVariableNameG(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+}
diff --git a/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableValueGImpl.java b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableValueGImpl.java
new file mode 100644
index 00000000..b7982f39
--- /dev/null
+++ b/gen/de/endrullis/idea/postfixtemplates/language/psi/impl/CptTemplateVariableValueGImpl.java
@@ -0,0 +1,29 @@
+// This is a generated file. Not intended for manual editing.
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import java.util.List;
+import org.jetbrains.annotations.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.util.PsiTreeUtil;
+import static de.endrullis.idea.postfixtemplates.language.psi.CptTypes.*;
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+
+public class CptTemplateVariableValueGImpl extends ASTWrapperPsiElement implements CptTemplateVariableValueG {
+
+ public CptTemplateVariableValueGImpl(ASTNode node) {
+ super(node);
+ }
+
+ public void accept(@NotNull CptVisitor visitor) {
+ visitor.visitTemplateVariableValueG(this);
+ }
+
+ public void accept(@NotNull PsiElementVisitor visitor) {
+ if (visitor instanceof CptVisitor) accept((CptVisitor)visitor);
+ else super.accept(visitor);
+ }
+
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..28861d27
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..070cb702
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..cccdd3d5
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..e95643d6
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/images/screenshot.png b/images/screenshot.png
deleted file mode 100644
index 139897c9..00000000
Binary files a/images/screenshot.png and /dev/null differ
diff --git a/package.json b/package.json
deleted file mode 100644
index 0bfaae82..00000000
--- a/package.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "radix-simple-react",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "axios": "^0.21.1",
- "lodash": "^4.17.10",
- "react": "^16.4.1",
- "react-dom": "^16.4.1",
- "react-scripts": "1.1.4",
- "react-sparklines": "^1.7.0"
- },
- "scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test --env=jsdom",
- "eject": "react-scripts eject"
- }
-}
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index a11777cc..00000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index afa6ceb6..00000000
--- a/public/index.html
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- React Sample
-
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
-
diff --git a/public/manifest.json b/public/manifest.json
deleted file mode 100644
index ef19ec24..00000000
--- a/public/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "short_name": "React App",
- "name": "Create React App Sample",
- "icons": [
- {
- "src": "favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- }
- ],
- "start_url": "./index.html",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
new file mode 100755
index 00000000..7bc58d62
--- /dev/null
+++ b/resources/META-INF/plugin.xml
@@ -0,0 +1,736 @@
+
+ de.endrullis.idea.postfixtemplates
+ Custom Postfix Templates
+ Stefan Endrullis
+ code editing
+
+ Documentation |
+ Screencast |
+ Predefined templates
+
+
+
+ This open source plugin lets you define your own postfix templates for Java, Scala, SQL, PHP, Kotlin, Python, Dart, JavaScript, Ruby, Rust, Go and Groovy.
+ It also comes with hundreds of useful postfix templates shared by the community. You're welcome to contribute your templates as well.
+
+
+
+ Apart from the community approach, how does the plugin differ from IDEA's postfix templates?
+
+
+
+ Since IDEA 2018 you are now able to define your own postfix templates for some languages in the settings UI (Editor → General → Postfix Templates ).
+ However, this is a pretty new feature and it's less functional than this plugin.
+
+ Here are some of the advantages of this plugin:
+
+ You can define different template rules (context based) for the same template name, e.g. .toList
should behave differently for arrays and for sets.
+ You can use template variables (e.g. $varName$
) which are filled in by the user while applying the template.
+ You can use live template macros to automatically fill some of the template variables (e.g. $var:suggestVariableName()$
) as well as you can define default values.
+ You can restrict the availability of templates or template rules to the availability of certain classes or libraries (e.g. expand "test".val
to val s = "test"
if Lombok is available).
+ It allows you to use static imports instead of class imports (e.g. array.toList
can be expanded to asList(array)
instead of Arrays.asList(array)
if you add [USE_STATIC_IMPORTS]
to the rule).
+ It comes with more than 500 editable postfix templates with more than 700 template rules, e.g.
+
+ string.toInt → Integer.parse(string)
+ array.toList → Arrays.asList(array)
+ file.lines → Files.readAllLines(file.toPath(), Charset.forName("UTF-8"))
+ file.getName().val → final String name = file.getName();
+
+
+ There are multiple ways to easily share your templates with others .
+
+
+ ]]>
+
+
+ Version 2.20.2
+
+ Removed Rust support in IDEA 2024.1.
+
+
+ Version 2.20.1
+
+ Fixed deprecated API usage.
+
+
+ Version 2.20.0
+
+ Replaced some deprecated libs.
+
+
+ Version 2.19.0
+
+ Lost support for typed SQL templates (because of API changes in SQL module).
+
+
+ Version 2.18.6
+
+ Show an error notification when postfix web templates could not be loaded from the internet.
+ Fixed potential memory leak.
+
+
+ Version 2.18.5
+
+ Fixed RuntimeExceptionWithAttachments.
+
+
+ Version 2.18.4
+
+ Plugin update requires restart.
+
+
+ Version 2.18.3
+
+ Allow to cancel the web templates update process.
+ Minor improvements of the update process.
+
+
+ Version 2.18.2
+
+ Fixed progress bar when updating web templates.
+
+
+ Version 2.18.1
+
+ Fixed Kotlin compile error by downgrading Kotlin.
+
+
+ Version 2.18.0
+
+ Fixed a deprecation.
+ Updated some libs.
+
+
+ Version 2.17.1
+
+ Re-added typed support for SQL again.
+
+
+ Version 2.17.0
+
+ Made CPT compatible with IDEA 2023.2.
+ Lost support for typed SQL templates (because of update).
+
+
+ Version 2.16.4
+
+ Fixed some deprecations.
+ Updated some libs.
+
+
+ Version 2.16.3
+
+ Fixed compatibility problems with IDEA 2023.1.
+
+
+ Version 2.16.2
+
+ Fixed some deprecations.
+
+
+ Version 2.16.1
+
+ Fixed some deprecations.
+
+
+ Version 2.16.0
+
+ Added experimental and untyped support for Swift.
+ Credits for implementation go to Tsungyu Yu .
+
+
+ Version 2.15.0
+
+
+ Version 2.14.0
+
+ Added support for reporting errors.
+
+
+ Version 2.13.2
+
+ Fixed bug #213: Cannot load local template file.
+ Deactivated error reporting again.
+
+
+ Version 2.13.1
+
+ Fixed bug #213: Cannot load local template file.
+
+
+ Version 2.13.0
+
+ Added support for reporting errors.
+
+
+ Version 2.12.5
+
+ Fixed two deprecations.
+ Updated dependencies.
+
+
+ Version 2.12.4
+
+ Fixed compatibility problems with IDEA 2022.2.
+ Updated dependencies.
+
+
+ Version 2.12.3
+
+ Fixed problem with gradle plugin.
+
+
+ Version 2.12.2
+
+ Fixed compatibility problems with IDEA 2021.3.
+ Fixed more deprecations.
+
+
+ Version 2.12.1
+
+ Added typed Rust support (for primitive types like integer, float, bool, ...).
+
+
+ Version 2.12.0
+
+ Added untyped support for Ruby.
+ Credits for implementation go to Jim Chen .
+
+
+ Version 2.11.7
+
+ Fixed more deprecations.
+
+
+ Version 2.11.6
+
+ Fixed more deprecations.
+
+
+ Version 2.11.5
+
+ Fixed some deprecations.
+
+
+ Version 2.11.4
+
+ Fixed some deprecations.
+
+
+ Version 2.11.3
+
+ Hopefully fixed bug #176: Plugin can't be enabled in Android Studio 4.1.
+ Fixed bug #175: IDEA log warning: Class constructor must not have parameters.
+ Credits for implementation go to ikopysov .
+
+
+
+ Version 2.11.2
+
+ Fixed bug #161: Kotlin String to Toast - String is not detected.
+ Fixed bug #163: Kotlin template is only applied to method parameter instead of whole expression.
+
+
+ Version 2.11.1
+
+ Fixed bug #165: ANY doesn't work when function has parameter in Go.
+ Credits go to fnsne .
+
+
+ Version 2.11.0
+
+ Implemented #158: Add full type support for Kotlin.
+ Credits go to MaaxGr for implementing it.
+
+
+ Version 2.10.15
+
+ Fixed #157 by including commons-io and commons-lang3 again.
+
+
+ Version 2.10.14
+
+ Fixed that Java postfix templates did not work in the Community Edition.
+
+
+ Version 2.10.13
+
+ Replaced org.reflections
library.
+
+
+ Version 2.10.12
+
+ Removed deprecated code.
+ Fixed a minor exception.
+
+
+ Version 2.10.11
+
+ Removed deprecated code.
+
+
+ Version 2.10.10
+
+ Removed deprecated code.
+
+
+ Version 2.10.9
+
+ Fixed #143: java.lang.NoClassDefFoundError: com/intellij/lang/javascript/psi/JSExpression.
+
+
+ Version 2.10.8
+
+
+ Version 2.10.7
+
+ Fixed #139: Cannot use $ in template variables -> escaping (\$) does not work.
+
+
+ Version 2.10.6
+
+ Fixed #160: NegativeArraySizeException on template expansion.
+
+
+ Version 2.10.5
+
+ Fixed text color of table in settings dialog.
+ Added hints in the settings dialog.
+
+
+ Version 2.10.4
+
+ Fixed a typo.
+ Updated plugin description.
+
+
+ Version 2.10.3
+
+ Fixed bug #127: Edit Variable Popup in "live template" settings does not open.
+
+
+ Version 2.10.2
+
+
+ Version 2.10.1
+
+ Fixed bug #113: Completion doesn't work with non-empty string literals in PHP.
+ Fixed bug #115: Completion doesn't work if arg is nullable in PHP.
+
+
+ Version 2.10.0
+
+ Added support for basic Python types.
+
+
+ Version 2.9.2
+
+ Fixed bug #118: Chains of postfix completions in PyCharm do not work.
+
+
+ Version 2.9.1
+
+ Minor bug fix in new Scala import function.
+
+
+ Version 2.9.0
+
+ Added function to add Scala imports using [IMPORT fully.qualified.ClassName]
.
+ Improved colors in settings dialog for default theme.
+
+
+ Version 2.8.3
+
+ Fixed a compatibility problem with IDEA 2019.1 Go and Kotlin plugins.
+ Fixed an exception in the plugin initialization.
+
+
+ Version 2.8.2
+
+ Fixed bug #97: Application of python templates failed.
+ Use always UTF-8 for template files.
+
+
+ Version 2.8.1
+
+
+ Version 2.8.0
+
+ Added support for Go.
+ Credits go to Philip Griggs for implementing it.
+
+
+ Version 2.7.0
+
+ Added support for new Kotlin matching types: STRING_LITERAL, INT_LITERAL, FLOAT_LITERAL, CHAR_LITERAL.
+
+
+ Version 2.6.3
+
+ Underscores can be used in template names.
+
+
+ Version 2.6.2
+
+ Fixed bug #85: NoClassDefFoundError: com/intellij/psi/PsiType in PHPStorm and PyCharm.
+
+
+ Version 2.6.1
+
+ Fixed bug #84: NoClassDefFoundError: com/intellij/openapi/actionSystem/DataKeys.
+
+
+ Version 2.6.0
+
+ Added typed Groovy support.
+
+
+ Version 2.5.0
+
+ Added typed PHP support.
+
+
+ Version 2.4.0
+
+ Added untyped Rust support.
+ Added untyped Groovy support.
+
+
+ Version 2.3.1
+
+ Added compatibility to PHPStorm and PyCharm.
+
+
+ Version 2.3.0
+
+ Added untyped Python support.
+ Added untyped PHP support.
+
+
+ Version 2.2.0
+
+ Added typed SQL support.
+
+
+ Version 2.1.0
+
+ Made plugin compatible with IDEA 2018.3.
+
+
+ Version 2.0.7
+
+ Fixed a NullPointerException that occurred sometimes when opening a template file.
+
+
+ Version 2.0.6
+
+ Fixed that some templates had [SKIP] as template description.
+
+
+ Version 2.0.5
+
+ Added "Update now" button to settings dialog.
+
+
+ Version 2.0.4
+
+ Fixed bug #78: IllegalStateException: @NotNull method de/endrullis/idea/postfixtemplates/language/CptUtil.getAbsoluteVirtualFile must not return null.
+
+
+ Version 2.0.3
+
+ Added an option to skip the application of an postfix template for a given data type (just use "[SKIP]" as template code).
+
+
+ Version 2.0.2
+
+ You can now split your templates into multiple files.
+ You can import templates from URLs or local files and keep them automatically up-to-date.
+ Predefined web templates have been separated from user templates in order to allow smooth and effortless updates (no manually merging required anymore).
+ Sharing of templates is now much easier than before.
+
+
+ Version 1.8.8
+
+ Added new Java postfix templates: .isEmpty
, .isNotEmpty
, .isBlank
, .isNotBlank
+ Improved the Java postfix template .reverse
+ Added 6 additional Java postfix templates for IDEA plugin developers.
+
+
+ Version 1.8.7
+
+ Added basic support for Dart (untyped templates only).
+ Credits go to Maksim Ryzhikov for implementing it.
+
+
+ Version 1.8.6
+
+ Fixed bug #65: AssertionError in scratch files
+
+
+ Version 1.8.5
+
+ Improved some templates: .sout
and .soutv
+ Added some templates: .toFile
, .toURL
, and .run
+ Fixed bug #63 (NullPointerException in application of JavaScript templates).
+
+
+ Version 1.8.4
+
+ Added new Java postfix templates: .toOptional
and .flatMap
+ Improved some Java postfix templates: .toList
and .toSet
+
+
+ Version 1.8.3
+
+ Added new Java postfix templates: .for
, .iter
, .format
, .stream
, and .sout
+ Added new Scala postfix template: .sout
+ Improved some predefined postfix templates.
+ Removed the default template suffix because of disadvantages in the ranking.
+
+
+ Version 1.8.2
+
+ Added a configurable template suffix to distinguish the templates of this plugin from the templates of IDEA and other plugins.
+
+
+ Version 1.8.1
+
+ Fixed that line breaks in template codes did not work anymore.
+
+
+ Version 1.8.0
+
+ New feature: Prepend or append "[USE_STATIC_IMPORTS]" to your Java template code to enable the use of static imports.
+
+
+ Version 1.7.0
+
+ New feature: Jump to template definition by pressing Alt+Enter and selecting Edit ... template .
+
+
+ Version 1.6.6
+
+ Fixed the bug that in IDEA 2018.1 only the first rule of each template takes effect.
+
+
+ Version 1.6.5
+
+ Fixed that Ctrl+S did not reload the templates when the template file is a symlink.
+
+
+ Version 1.6.4
+
+ Fixed the bug that templates with escape characters (e.g. "\"UTF-8\"") did not work as expected anymore.
+
+
+ Version 1.6.3
+
+ Made plugin compatible with IDEA 2018.1.
+
+
+ Version 1.6.2
+
+ Fixed that the dollar character ($) could not be used in template code.
+
+
+ Version 1.6.1
+
+ Added the predefined Java and Scala template .soutv
+
+
+ Version 1.6.0
+
+ Improved settings dialog: Added option to select one of the supported languages
+ and to reset/merge your templates with the predefined ones.
+ Added more Java and Scala templates.
+ Restructured tools menu:
+
+ Removed language specific menu items like "Open Java Templates".
+ Added menu item "Open Settings / Upgrade Templates".
+
+
+
+
+ Version 1.5.1
+
+ Added some predefined Scala templates.
+ Fixed, improved and added some predefined Java templates
+
+
+ Version 1.5.0
+
+ Added full support for Scala (typed templates).
+
+
+ Version 1.4.1
+
+ Added basic support for Scala (untyped templates only).
+
+
+ Version 1.4.0
+
+ Added the matching types to allow special treatments of literals:
+
+ NUMBER_LITERAL
+ BYTE_LITERAL
+ SHORT_LITERAL
+ CHAR_LITERAL
+ INT_LITERAL
+ LONG_LITERAL
+ FLOAT_LITERAL
+ DOUBLE_LITERAL
+ STRING_LITERAL
+
+
+
+
+ Version 1.3.0
+
+ Added option to add class/library dependencies to templates rules.
+ For instance "".val
expands now to
+
+ val s = "";
if Lombok is available, or to
+ final String s = "";
if Lombok is not available.
+
+
+ Added 16 templates for IDEA developers to the predefined templates.
+
+
+ Version 1.2.3
+
+ Fixed #43: Could not use $ in default values of template variables.
+
+
+ Version 1.2.2
+
+ Improved code completion and error highlighting for JavaScript and Kotlin templates.
+ Added shortcut Shift+Alt+P to open the postfix templates of the current language.
+ Fixed #42: JavaScript and Kotlin postfix templates did not detect complete dot expressions.
+
+
+ Version 1.2.1
+
+ Added basic support for Kotlin (untyped templates only).
+
+
+ Version 1.2.0
+
+ Added JavaScript support.
+
+
+ Version 1.1.1
+
+
+ Version 1.1.0
+
+ Added matching type CLASS
to detect class references.
+ Added postfix template .new
which can be applied to class references.
+
+
+ Version 1.0.2
+
+ Fixed that templates could not be applied to partial expressions or method parameters.
+ Fixed that each template started with a dot in the completion dialog.
+ Fixed bug #33: Read access is allowed from event dispatch thread or inside read-action only.
+
+ ]]>
+
+
+
+
+
+
+ com.intellij.modules.lang
+ com.intellij.modules.java
+ org.intellij.scala
+ org.jetbrains.kotlin
+ Dart
+ com.intellij.modules.python
+ com.jetbrains.php
+ org.intellij.groovy
+ org.jetbrains.plugins.go
+ com.intellij.modules.ruby
+ nl.rubensten.texifyidea
+ JavaScript
+ com.intellij.database
+ Swift
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ de.endrullis.idea.postfixtemplates.intention.OverrideTemplateRuleIntention
+ Custom postfix templates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/META-INF/pluginIcon.svg b/resources/META-INF/pluginIcon.svg
new file mode 100644
index 00000000..95619097
--- /dev/null
+++ b/resources/META-INF/pluginIcon.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/META-INF/pluginIcon_dark.svg b/resources/META-INF/pluginIcon_dark.svg
new file mode 100644
index 00000000..95619097
--- /dev/null
+++ b/resources/META-INF/pluginIcon_dark.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/META-INF/withDartModule.xml b/resources/META-INF/withDartModule.xml
new file mode 100644
index 00000000..1eb2ab95
--- /dev/null
+++ b/resources/META-INF/withDartModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withDatabaseModule.xml b/resources/META-INF/withDatabaseModule.xml
new file mode 100644
index 00000000..61fa85c6
--- /dev/null
+++ b/resources/META-INF/withDatabaseModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withGoModule.xml b/resources/META-INF/withGoModule.xml
new file mode 100644
index 00000000..6c44511d
--- /dev/null
+++ b/resources/META-INF/withGoModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withGroovyModule.xml b/resources/META-INF/withGroovyModule.xml
new file mode 100644
index 00000000..8fefa22e
--- /dev/null
+++ b/resources/META-INF/withGroovyModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withJavaModule.xml b/resources/META-INF/withJavaModule.xml
new file mode 100644
index 00000000..5132772e
--- /dev/null
+++ b/resources/META-INF/withJavaModule.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/resources/META-INF/withJavaScriptModule.xml b/resources/META-INF/withJavaScriptModule.xml
new file mode 100644
index 00000000..27a71adc
--- /dev/null
+++ b/resources/META-INF/withJavaScriptModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withKotlinModule.xml b/resources/META-INF/withKotlinModule.xml
new file mode 100644
index 00000000..5d3d69db
--- /dev/null
+++ b/resources/META-INF/withKotlinModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withLatexModule.xml b/resources/META-INF/withLatexModule.xml
new file mode 100644
index 00000000..85f611c6
--- /dev/null
+++ b/resources/META-INF/withLatexModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withPhpModule.xml b/resources/META-INF/withPhpModule.xml
new file mode 100644
index 00000000..66558995
--- /dev/null
+++ b/resources/META-INF/withPhpModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withPythonModule.xml b/resources/META-INF/withPythonModule.xml
new file mode 100644
index 00000000..99e61f9b
--- /dev/null
+++ b/resources/META-INF/withPythonModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withRiderModule.xml b/resources/META-INF/withRiderModule.xml
new file mode 100644
index 00000000..a3120e85
--- /dev/null
+++ b/resources/META-INF/withRiderModule.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/resources/META-INF/withRubyModule.xml b/resources/META-INF/withRubyModule.xml
new file mode 100644
index 00000000..63622814
--- /dev/null
+++ b/resources/META-INF/withRubyModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withScalaModule.xml b/resources/META-INF/withScalaModule.xml
new file mode 100644
index 00000000..7bbff52b
--- /dev/null
+++ b/resources/META-INF/withScalaModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/META-INF/withSwiftModule.xml b/resources/META-INF/withSwiftModule.xml
new file mode 100644
index 00000000..b08b84c8
--- /dev/null
+++ b/resources/META-INF/withSwiftModule.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/resources/de/endrullis/idea/postfixtemplates/bundle/PostfixTemplatesBundle.properties b/resources/de/endrullis/idea/postfixtemplates/bundle/PostfixTemplatesBundle.properties
new file mode 100644
index 00000000..cefed4b4
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/bundle/PostfixTemplatesBundle.properties
@@ -0,0 +1,7 @@
+plugin.name = Custom Postfix Templates
+
+settings.plugin.name=Custom Postfix Templates
+settings.pluginEnable.label=Enable Custom Postfix Templates
+settings.templates.label=Java Postfix Templates:
+settings.editButton.label=Edit Your Temples in an Editor
+settings.templates.fromPlugin.label=Templates from the plugin:
diff --git a/resources/de/endrullis/idea/postfixtemplates/icons/cpt-icon.png b/resources/de/endrullis/idea/postfixtemplates/icons/cpt-icon.png
new file mode 100644
index 00000000..cb7c3dad
Binary files /dev/null and b/resources/de/endrullis/idea/postfixtemplates/icons/cpt-icon.png differ
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/dart.postfixTemplates b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/dart.postfixTemplates
new file mode 100644
index 00000000..da1a857b
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/dart.postfixTemplates
@@ -0,0 +1,3 @@
+
+.example : example template
+ ANY → foo($expr$)
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/example.postfixTemplates b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/example.postfixTemplates
new file mode 100644
index 00000000..da1a857b
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/example.postfixTemplates
@@ -0,0 +1,3 @@
+
+.example : example template
+ ANY → foo($expr$)
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/java.postfixTemplates b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/java.postfixTemplates
new file mode 100644
index 00000000..5628fe69
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/java.postfixTemplates
@@ -0,0 +1,421 @@
+## number conversions ##
+
+.toBoolean : convert to boolean
+ java.lang.String → Boolean.parseBoolean($expr$)
+
+.toByte : convert to byte
+ java.lang.String → Byte.parseByte($expr$)
+ java.lang.Number → $expr$.byteValue()
+ NUMBER → ((byte) ($expr$))
+
+.toShort : convert to short
+ java.lang.String → Short.parseShort($expr$)
+ java.lang.Number → $expr$.shortValue()
+ NUMBER → ((short) ($expr$))
+
+.toChar : convert to char
+ java.lang.String → $expr$.charAt(0)
+ NUMBER → ((char) ($expr$))
+
+.toInt : convert to int
+ java.lang.String → Integer.parseInt($expr$)
+ java.lang.Number → $expr$.intValue()
+ NUMBER → ((int) ($expr$))
+
+.toLong : convert to long
+ java.lang.String → Long.parseLong($expr$)
+ java.lang.Number → $expr$.longValue()
+ NUMBER → ((long) ($expr$))
+
+.toFloat : convert to float
+ java.lang.String → Float.parseFloat($expr$)
+ java.lang.Number → $expr$.floatValue()
+ NUMBER → ((float) ($expr$))
+
+.toDouble : convert to double
+ java.lang.String → Double.parseDouble($expr$)
+ java.lang.Number → $expr$.doubleValue()
+ NUMBER → ((double) ($expr$))
+
+.format : format number
+ BYTE → String.format("$format::"%d"$", $expr$)
+ SHORT → String.format("$format::"%d"$", $expr$)
+ CHAR → String.format("$format::"%c"$", $expr$)
+ INT → String.format("$format::"%d"$", $expr$)
+ LONG → String.format("$format::"%d"$", $expr$)
+ FLOAT → String.format("$format::"%f"$", $expr$)
+ DOUBLE → String.format("$format::"%f"$", $expr$)
+ java.lang.String → String.format($expr$, $END$)
+
+
+
+## collection conversions ##
+
+.toList : convert to List
+ ARRAY → java.util.Arrays.asList($expr$)
+ java.util.Collection → new java.util.ArrayList<>($expr$)
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).collect(java.util.stream.Collectors.toList())
+ java.util.Map → new ArrayList<>($expr$.entrySet())
+ java.util.Optional → $expr$.map(e -> Collections.singletonList(e)).orElse(Collections.emptyList())
+ java.util.stream.Stream → $expr$.collect(java.util.stream.Collectors.toList())
+
+.toSet : convert to Set
+ ARRAY → java.util.stream.Stream.of($expr$).collect(java.util.stream.Collectors.toSet())
+ java.util.Collection → new java.util.HashSet<>($expr$)
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).collect(java.util.stream.Collectors.toSet())
+ java.util.Map → $expr$.entrySet()
+ java.util.Optional → $expr$.map(e -> Collections.singleton(e)).orElse(Collections.emptySet())
+ java.util.stream.Stream → $expr$.collect(java.util.stream.Collectors.toSet())
+
+.toMap : convert to Map
+ ARRAY → java.util.Arrays.stream($expr$).collect(java.util.stream.Collectors.toMap(e -> $key$, e -> $value$))
+ java.util.Collection → $expr$.stream().collect(java.util.stream.Collectors.toMap(e -> $key$, e -> $value$))
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).collect(java.util.stream.Collectors.toMap(e -> $key$, e -> $value$))
+ java.util.stream.Stream → $expr$.collect(java.util.stream.Collectors.toMap(e -> $key$, e -> $value$))
+ java.util.Map → $expr$.entrySet().stream().collect(java.util.stream.Collectors.toMap(e -> $key$, e -> $value$))
+
+.toOptional : wrap in Otional
+ NON_VOID → Optional.ofNullable($expr$)
+
+.stream : convert to Stream
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false)
+
+
+
+## collection iterations ##
+
+.for : iterate over ...
+ ITERABLE_OR_ARRAY → for ($ELEMENT_TYPE:iterableComponentType(expr):"java.lang.Object"$ $VAR:suggestVariableName()$ : $expr$) {\
+ $END$\
+ }
+ java.util.Enumeration → while($expr$.hasMoreElements()) {\
+ $TYPE:rightSideType():"Object"$ $VAR:suggestVariableName()$ = $CAST*:castToLeftSideType()$ $expr$.nextElement();\
+ $END$\
+ }
+ java.util.Iterator → while($expr$.hasNext()) {\
+ $TYPE:rightSideType():"Object"$ $VAR:suggestVariableName()$ = $CAST*:castToLeftSideType()$ $expr$.next();\
+ $END$\
+ }
+
+.iter : iterate over ...
+ ITERABLE_OR_ARRAY → for ($ELEMENT_TYPE:iterableComponentType(expr):"java.lang.Object"$ $VAR:suggestVariableName()$ : $expr$) {\
+ $END$\
+ }
+ java.util.Enumeration → while($expr$.hasMoreElements()) {\
+ $TYPE:rightSideType():"Object"$ $VAR:suggestVariableName()$ = $CAST*:castToLeftSideType()$ $expr$.nextElement();\
+ $END$\
+ }
+ java.util.Iterator → while($expr$.hasNext()) {\
+ $TYPE:rightSideType():"Object"$ $VAR:suggestVariableName()$ = $CAST*:castToLeftSideType()$ $expr$.next();\
+ $END$\
+ }
+
+
+
+## collection operations ##
+
+.sort : sort naturally
+ ARRAY → java.util.Arrays.sort($expr$)
+ java.util.List → java.util.Collections.sort($expr$)
+
+.sortBy : sort by attribute
+ ARRAY → java.util.Arrays.sort($expr$, java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.List → $expr$.sort(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.stream.Stream → $expr$.sorted(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+
+.minBy : minimum by attribute
+ ARRAY → java.util.Arrays.stream($expr$).min(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.Collection → $expr$.stream().min(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).min(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.stream.Stream → $expr$.min(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+
+.maxBy : maximum by attribute
+ ARRAY → java.util.Arrays.stream($expr$).max(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.Collection → $expr$.stream().max(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).max(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.stream.Stream → $expr$.max(java.util.Comparator.comparing($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+
+.groupBy : group by attribute
+ ARRAY → java.util.Arrays.stream($expr$).collect(java.util.stream.Collectors.groupingBy($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.Collection → $expr$.stream().collect(java.util.stream.Collectors.groupingBy($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).collect(java.util.stream.Collectors.groupingBy($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+ java.util.stream.Stream → $expr$.collect(java.util.stream.Collectors.groupingBy($attributeVar*:suggestShortVariableName()$ -> $attribute$))
+
+.exists : any match
+ ARRAY → java.util.Arrays.stream($expr$).anyMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+ java.util.Collection → $expr$.stream().anyMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).anyMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+ java.util.stream.Stream → $expr$.anyMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+
+.forall : all match
+ ARRAY → java.util.Arrays.stream($expr$).allMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+ java.util.Collection → $expr$.stream().allMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).allMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+ java.util.stream.Stream → $expr$.allMatch($conditionVar*:suggestShortVariableName()$ -> $condition$)
+
+.reverse : reverse collection
+ ARRAY [org.apache.commons.lang.ArrayUtils] → org.apache.commons.lang.ArrayUtils.reverse($expr$)
+ java.util.List → java.util.Collections.reverse($expr$)
+ java.lang.String → new StringBuilder($expr$).reverse().toString()
+
+.concat : concat
+ ARRAY → java.util.stream.Stream.concat(java.util.Arrays.stream($expr$), $stream$)
+ java.util.Collection → java.util.stream.Stream.concat($expr$.stream(), $stream$)
+ java.util.stream.Stream → java.util.stream.Stream.concat($expr$, $stream$)
+
+.forEach : for each
+ ARRAY → java.util.Arrays.stream($expr$).forEach($actionVar*:suggestShortVariableName()$ -> $END$)
+ java.util.Optional → $expr$.ifPresent($actionVar*:suggestShortVariableName()$ -> $END$)
+
+.mkString : make a string
+ ARRAY → java.util.Arrays.stream($expr$).collect(java.util.stream.Collectors.joining($separator$))
+ java.util.Collection → $expr$.stream().collect(java.util.stream.Collectors.joining($separator$))
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).collect(java.util.stream.Collectors.joining($separator$))
+ java.util.stream.Stream → $expr$.collect(java.util.stream.Collectors.joining($separator$))
+
+.map : map entries
+ java.util.List → $expr$.stream().map($fVar*:suggestShortVariableName()$ -> $f$).collect(java.util.stream.Collectors.toList())
+ java.util.Set → $expr$.stream().map($fVar*:suggestShortVariableName()$ -> $f$).collect(java.util.stream.Collectors.toSet())
+ java.util.Map → $expr$.entrySet().stream().collect(java.util.stream.Collectors.toMap(e -> $key$, e -> $value$))
+
+.flatMap : map entries
+ java.util.List → $expr$.stream().flatMap($fVar*:suggestShortVariableName()$ -> $f$).collect(java.util.stream.Collectors.toList())
+ java.util.Set → $expr$.stream().flatMap($fVar*:suggestShortVariableName()$ -> $f$).collect(java.util.stream.Collectors.toSet())
+
+.mapKeys : map keys
+ java.util.Map → $expr$.entrySet().stream().collect(java.util.stream.Collectors.toMap(e -> $key$, e -> e.getValue()))
+
+.mapValues : map values
+ java.util.Map → $expr$.entrySet().stream().collect(java.util.stream.Collectors.toMap(e -> e.getKey(), e -> $value$))
+
+.getOrElseUpdate : get or else update
+ java.util.Map → $expr$.computeIfAbsent($key$, e -> $value$)
+
+.filter : filter map entries
+ java.util.List → $expr$.stream().filter($conditionVar*:suggestShortVariableName()$ -> $condition$).collect(java.util.stream.Collectors.toList())
+ java.util.Set → $expr$.stream().filter($conditionVar*:suggestShortVariableName()$ -> $condition$).collect(java.util.stream.Collectors.toSet())
+ java.util.Map → $expr$.entrySet().stream().filter($conditionVar*:suggestShortVariableName()$ -> $condition$).collect(java.util.stream.Collectors.toMap(e -> e.getKey(), e -> e.getValue()))
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).collect(java.util.stream.Collectors.toList())
+
+.reduce : reduce
+ ARRAY → java.util.Arrays.stream($expr$).reduce((a, b) -> $accumulator$)
+ java.util.Collection → $expr$.stream().reduce((a, b) -> $accumulator$)
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).reduce((a, b) -> $accumulator$)
+
+.fold : fold
+ ARRAY → java.util.Arrays.stream($expr$).reduce($neutralElement$, (a, b) -> $accumulator$)
+ java.util.Collection → $expr$.stream().reduce($neutralElement$, (a, b) -> $accumulator$)
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).reduce($neutralElement$, (a, b) -> $accumulator$)
+ java.util.stream.Stream → $expr$.reduce($neutralElement$, (a, b) -> $accumulator$)
+
+.find : find element
+ ARRAY → java.util.Arrays.stream($expr$).filter($conditionVar*:suggestShortVariableName()$ -> $condition$).findFirst()
+ java.util.Collection → $expr$.stream().filter($conditionVar*:suggestShortVariableName()$ -> $condition$).findFirst()
+ java.lang.Iterable → java.util.stream.StreamSupport.stream($expr$.spliterator(), false).filter($conditionVar*:suggestShortVariableName()$ -> $condition$).findFirst()
+ java.util.stream.Stream → $expr$.filter($conditionVar*:suggestShortVariableName()$ -> $condition$).findFirst()
+
+.take : take a certain number of elements
+ java.util.stream.Stream → $expr$.limit($intValue$)
+
+.drop : drop a certain number of elements
+ java.util.stream.Stream → $expr$.skip($intValue$)
+
+.size : size of collection
+ ARRAY → $expr$.length
+
+.get : get element
+ ARRAY → $expr$[$i$]
+
+.isEmpty : null-safe isEmpty check
+ java.util.Collection [org.apache.commons.collections4.CollectionUtils] → org.apache.commons.collections4.CollectionUtils.isEmpty($expr$)
+ java.util.Map [org.apache.commons.collections4.MapUtils] → org.apache.commons.collections4.MapUtils.isEmpty($expr$)
+ java.lang.String [org.apache.commons.lang3.StringUtils] → org.apache.commons.lang3.StringUtils.isEmpty($expr$)
+
+.isNotEmpty : null-safe isNotEmpty check
+ java.util.Collection [org.apache.commons.collections4.CollectionUtils] → org.apache.commons.collections4.CollectionUtils.isNotEmpty($expr$)
+ java.util.Map [org.apache.commons.collections4.MapUtils] → org.apache.commons.collections4.MapUtils.isNotEmpty($expr$)
+ java.lang.String [org.apache.commons.lang3.StringUtils] → org.apache.commons.lang3.StringUtils.isNotEmpty($expr$)
+
+.isBlank : StringUtils.isBlank()
+ java.lang.String [org.apache.commons.lang3.StringUtils] → org.apache.commons.lang3.StringUtils.isBlank($expr$)
+
+.isNotBlank : StringUtils.isNotBlank()
+ java.lang.String [org.apache.commons.lang3.StringUtils] → org.apache.commons.lang3.StringUtils.isNotBlank($expr$)
+
+
+
+## function applications ##
+
+.apply : apply the function
+ java.lang.Runnable → $expr$.run()
+ java.util.function.Supplier → $expr$.get()
+ java.util.function.BooleanSupplier → $expr$.getAsBoolean()
+ java.util.function.DoubleSupplier → $expr$.getAsDouble()
+ java.util.function.IntSupplier → $expr$.getAsInt()
+ java.util.function.LongSupplier → $expr$.getAsLong()
+ java.util.function.Consumer → $expr$.accept($object$)
+ java.util.function.BiConsumer → $expr$.accept($object$, $object$)
+ java.util.function.DoubleConsumer → $expr$.accept($doubleValue$)
+ java.util.function.IntConsumer → $expr$.accept($intValue$)
+ java.util.function.LongConsumer → $expr$.accept($longValue$)
+ java.util.function.ObjDoubleConsumer → $expr$.accept($object$, $doubleValue$)
+ java.util.function.ObjIntConsumer → $expr$.accept($object$, $intValue$)
+ java.util.function.ObjLongConsumer → $expr$.accept($object$, $longValue$)
+ java.util.function.Predicate → $expr$.test($object$)
+ java.util.function.BiPredicate → $expr$.test($object$, $object$)
+ java.util.function.DoublePredicate → $expr$.test($doubleValue$)
+ java.util.function.IntPredicate → $expr$.test($intValue$)
+ java.util.function.LongPredicate → $expr$.test($longValue$)
+
+
+
+## I/O ##
+
+.toFile : get file
+ java.lang.String → new java.io.File($expr$)
+
+.toURL : get URL
+ java.lang.String → new java.net.URL($expr$)
+
+.lines : get lines
+ java.io.File → java.nio.file.Files.readAllLines($expr$.toPath(), java.nio.charset.Charset.forName($encoding::"\"UTF-8\""$))
+ java.nio.file.Path → java.nio.file.Files.readAllLines($expr$, java.nio.charset.Charset.forName($encoding::"\"UTF-8\""$))
+ java.lang.String → $expr$.split("\\r?\\n")
+ java.io.InputStream → new java.io.BufferedReader(new java.io.InputStreamReader($expr$)).lines()
+
+.content : get content
+ java.io.File → new String(java.nio.file.Files.readAllBytes($expr$.toPath()), $encoding::"\"UTF-8\""$)
+ java.nio.file.Path → new String(java.nio.file.Files.readAllBytes($expr$), $encoding::"\"UTF-8\""$)
+ java.io.InputStream → new java.util.Scanner($expr$, $encoding::"\"UTF-8\""$).useDelimiter("\\\\A").next()
+ java.net.URL → new java.util.Scanner($expr$.openStream(), $encoding::"\"UTF-8\""$).useDelimiter("\\\\A").next()
+
+.inputStream : get input stream
+ java.lang.String → new java.io.ByteArrayInputStream($expr$.getBytes())
+ java.io.File → new java.io.FileInputStream($expr$)
+ java.net.URL → $expr$.openStream()
+
+.outputStream : get output stream
+ java.io.File → new java.io.FileOutputStream($expr$)
+
+.bufferedReader : get BufferedReader
+ java.io.File → new java.io.BufferedReader(new java.io.FileReader($expr$))
+ java.io.InputStream → new java.io.BufferedReader(new java.io.InputStreamReader($expr$))
+ java.net.URL → new java.io.BufferedReader(new java.io.InputStreamReader($expr$.openStream()))
+
+.bufferedWriter : get BufferedWriter
+ java.io.File → new java.io.BufferedWriter(new java.io.FileWriter($expr$))
+ java.io.OutputStream → new java.io.BufferedWriter(new java.io.OutputStreamWriter($expr$))
+
+.printStream : get PrintStream
+ java.io.File → new java.io.PrintStream($expr$)
+ java.io.OutputStream → new java.io.PrintStream($expr$)
+
+.run : run shell command
+ java.lang.String → java.lang.Runtime.getRuntime().exec($expr$)
+
+
+
+## others ##
+
+.new : new instance
+ CLASS → new $expr$($END$)
+
+.r : compile pattern
+ java.lang.String → java.util.regex.Pattern.compile($expr$)
+
+.val : extract as value
+ NON_VOID [lombok.val] → val $var:suggestVariableName()$ = $expr$;
+ NON_VOID → final $type*:expressionType(expr))$ $var:suggestVariableName()$ = $expr$;
+
+.sout : print variable to System.out
+ ARRAY → System.out.println(java.util.Arrays.toString($expr$));
+ NON_VOID → System.out.println($expr$);
+
+.soutv : print variable to System.out
+ ARRAY → System.out.println("$expr$ = " + java.util.Arrays.toString($expr$));
+ NON_VOID → System.out.println("$expr$ = " + $expr$);
+
+
+
+## IDEA development templates ##
+
+.toVirtualFile : as virtual file
+ java.io.File [com.intellij.openapi.vfs.VirtualFile] → com.intellij.openapi.vfs.LocalFileSystem.getInstance().findFileByIoFile($expr$)
+
+.toFile : as file
+ com.intellij.openapi.vfs.VirtualFile → new java.io.File($expr$.getPath())
+
+.getAttributes : get file attributes
+ com.intellij.openapi.vfs.VirtualFile → com.intellij.openapi.vfs.LocalFileSystem.getInstance().getAttributes($expr$)
+
+.openInEditor : open file in editor
+ com.intellij.openapi.vfs.VirtualFile → new com.intellij.openapi.fileEditor.OpenFileDescriptor($project:variableOfType("com.intellij.openapi.project.Project")$, $expr$).navigate(true)
+
+.getVirtualFile : get virtual file
+ com.intellij.openapi.editor.Document → com.intellij.openapi.fileEditor.FileDocumentManager.getInstance().getFile($expr$)
+
+.getDocument : get document
+ com.intellij.psi.PsiFile → com.intellij.psi.PsiDocumentManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project"):concat(expr, ".getProject()")$).getDocument($expr$)
+ com.intellij.psi.PsiElement → com.intellij.psi.PsiDocumentManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project"):concat(expr, ".getProject()")$).getDocument($expr$.getContainingFile())
+ com.intellij.openapi.vfs.VirtualFile → com.intellij.psi.PsiDocumentManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project")$).getDocument(com.intellij.psi.PsiManager.getInstance($project*$).findFile($expr$))
+ com.intellij.openapi.fileEditor.FileEditor → com.intellij.psi.PsiDocumentManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project")$).getDocument(com.intellij.psi.PsiManager.getInstance($project*$).findFile($expr$.getFile()))
+
+.getPsiFile : get PSI file
+ com.intellij.openapi.vfs.VirtualFile → com.intellij.psi.PsiManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project")$).findFile($expr$)
+ com.intellij.openapi.editor.Document → com.intellij.psi.PsiDocumentManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project")$).getPsiFile($expr$)
+ com.intellij.openapi.fileEditor.FileEditor → com.intellij.psi.PsiManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project")$).findFile($expr$.getFile())
+
+.getPsiJavaFile : get PSI Java file
+ com.intellij.psi.PsiClass → ((com.intellij.psi.PsiJavaFile) $expr$.getContaningFile())
+
+.getPsiPackage : get PSI package
+ com.intellij.psi.PsiClass → com.intellij.psi.JavaPsiFacade.getInstance($project:variableOfType("com.intellij.openapi.project.Project")$).findPackage(((com.intellij.psi.PsiJavaFile) $expr$.getContaningFile()).getPackageName())
+ com.intellij.psi.PsiJavaFile → com.intellij.psi.JavaPsiFacade.getInstance($project:variableOfType("com.intellij.openapi.project.Project")$).findPackage($expr$.getPackageName())
+
+.getChildrenOfType : get children of certain type
+ com.intellij.psi.PsiElement → com.intellij.psi.util.PsiTreeUtil.getChildrenOfType($expr$, $class$)
+
+.getModule : get Module
+ com.intellij.openapi.vfs.VirtualFile → com.intellij.openapi.roots.ProjectRootManager.getInstance($project:variableOfType("com.intellij.openapi.project.Project"):concat(expr, ".getProject()")$).getFileIndex().getModuleForFile($expr$)
+
+.getProject : get Project
+ com.intellij.openapi.actionSystem.DataContext → com.intellij.openapi.actionSystem.DataKeys.PROJECT.getData($expr$)
+
+.getFileEditorManager : get FileEditorManager
+ com.intellij.openapi.project.Project → com.intellij.openapi.fileEditor.FileEditorManager.getInstance($expr$)
+
+.getPsiManager : get PsiManager
+ com.intellij.openapi.project.Project → com.intellij.psi.PsiManager.getInstance($expr$)
+
+.getPsiFileFactory : get PsiFileFactory
+ com.intellij.openapi.project.Project → com.intellij.psi.PsiFileFactory.getInstance($expr$)
+
+.getProjectRootManager : get ProjectRootManager
+ com.intellij.openapi.project.Project → com.intellij.openapi.roots.ProjectRootManager.getInstance($expr$)
+
+.getTemplateManager : get TemplateManager
+ com.intellij.openapi.project.Project → com.intellij.codeInsight.template.TemplateManager.getInstance($expr$)
+
+.getJavaPsiFacade : get JavaPsiFacade
+ com.intellij.openapi.project.Project → com.intellij.psi.JavaPsiFacade.getInstance($expr$)
+
+.runReadAction : runWriteAction(...)
+ ANY [com.intellij.openapi.application.Application] → com.intellij.openapi.application.ApplicationManager.getApplication().runReadAction(() -> {\
+ $expr$$END$\
+ });
+
+.runWriteAction : runWriteAction(...)
+ ANY [com.intellij.openapi.application.Application] → com.intellij.openapi.application.ApplicationManager.getApplication().runWriteAction(() -> {\
+ $expr$$END$\
+ });
+
+.invokeLater : invokeLater(...)
+ ANY [com.intellij.openapi.application.Application] → com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater(() -> {\
+ $expr$$END$\
+ });
+
+.showDiff : show diff
+ java.lang.String [com.intellij.diff.DiffManager] → com.intellij.diff.DiffManager.getInstance().showDiff($project:variableOfType("com.intellij.openapi.project.Project")$, new SimpleDiffRequest("$title::"Diff Title"$",\
+ com.intellij.diff.DiffContentFactory.getInstance().create($expr$),\
+ com.intellij.diff.DiffContentFactory.getInstance().create($content2::"content2"$),\
+ "$leftTitle::"Left side"$", "$rightTitle::"Right side"$", "$title::"Diff Title"$");
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/javascript.postfixTemplates b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/javascript.postfixTemplates
new file mode 100644
index 00000000..da1a857b
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/javascript.postfixTemplates
@@ -0,0 +1,3 @@
+
+.example : example template
+ ANY → foo($expr$)
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/kotlin.postfixTemplates b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/kotlin.postfixTemplates
new file mode 100644
index 00000000..da1a857b
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/kotlin.postfixTemplates
@@ -0,0 +1,3 @@
+
+.example : example template
+ ANY → foo($expr$)
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/scala.postfixTemplates b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/scala.postfixTemplates
new file mode 100644
index 00000000..09e6500c
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/defaulttemplates/scala.postfixTemplates
@@ -0,0 +1,94 @@
+## collection operations ##
+
+.toList : to list
+ NON_VOID → List($expr$)
+
+.toSet : to set
+ NON_VOID → Set($expr$)
+
+.toMap : to map
+ scala.Tuple2 → Map($expr$)
+
+.toOption : to option
+ NON_VOID → Option($expr$)
+
+.toSome : to some
+ NON_VOID → Some($expr$)
+
+
+## function applications ##
+
+.apply : apply the function
+ java.lang.Runnable → $expr$.run()
+ java.util.function.Supplier → $expr$.get()
+ java.util.function.BooleanSupplier → $expr$.getAsBoolean()
+ java.util.function.DoubleSupplier → $expr$.getAsDouble()
+ java.util.function.IntSupplier → $expr$.getAsInt()
+ java.util.function.LongSupplier → $expr$.getAsLong()
+ java.util.function.Consumer → $expr$.accept($object$)
+ java.util.function.BiConsumer → $expr$.accept($object$, $object$)
+ java.util.function.DoubleConsumer → $expr$.accept($doubleValue$)
+ java.util.function.IntConsumer → $expr$.accept($intValue$)
+ java.util.function.LongConsumer → $expr$.accept($longValue$)
+ java.util.function.ObjDoubleConsumer → $expr$.accept($object$, $doubleValue$)
+ java.util.function.ObjIntConsumer → $expr$.accept($object$, $intValue$)
+ java.util.function.ObjLongConsumer → $expr$.accept($object$, $longValue$)
+ java.util.function.Predicate → $expr$.test($object$)
+ java.util.function.BiPredicate → $expr$.test($object$, $object$)
+ java.util.function.DoublePredicate → $expr$.test($doubleValue$)
+ java.util.function.IntPredicate → $expr$.test($intValue$)
+ java.util.function.LongPredicate → $expr$.test($longValue$)
+
+
+## I/O ##
+
+.toFile : get file
+ java.lang.String → new File($expr$)
+
+.toURL : get URL
+ java.lang.String → new URL($expr$)
+
+.lines : get lines
+ java.io.File → Source.fromFile($expr$, $enc::"\"UTF-8\""$).getLines()
+ java.nio.file.Path → Files.readAllLines($expr$, Charset.forName($encoding::"\"UTF-8\""$))
+ java.io.InputStream → Source.fromInputStream($expr$, $enc::"\"UTF-8\""$).getLines()
+ java.net.URL → Source.fromURL($expr$, $enc::"\"UTF-8\""$).getLines()
+ java.lang.String → $expr$.split("\\r?\\n")
+
+.content : get content
+ java.io.File → new String(Files.readAllBytes($expr$.toPath()), $encoding::"\"UTF-8\""$)
+ java.nio.file.Path → new String(Files.readAllBytes($expr$), $encoding::"\"UTF-8\""$)
+ java.io.InputStream → new Scanner($expr$, $encoding::"\"UTF-8\""$).useDelimiter("\\A").next()
+ java.net.URL → new Scanner($expr$.openStream(), $encoding::"\"UTF-8\""$).useDelimiter("\\A").next()
+
+.inputStream : get input stream
+ java.lang.String → new ByteArrayInputStream($expr$.getBytes())
+ java.io.File → new FileInputStream($expr$)
+ java.net.URL → $expr$.openStream()
+
+.outputStream : get output stream
+ java.io.File → new FileOutputStream($expr$)
+
+.bufferedReader : get BufferedReader
+ java.io.File → new BufferedReader(new FileReader($expr$))
+ java.io.InputStream → new BufferedReader(new InputStreamReader($expr$))
+ java.net.URL → new BufferedReader(new InputStreamReader($expr$.openStream()))
+
+.bufferedWriter : get BufferedWriter
+ java.io.File → new BufferedWriter(new FileWriter($expr$))
+ java.io.OutputStream → new BufferedWriter(new OutputStreamWriter($expr$))
+
+.printStream : get PrintStream
+ java.io.File → new PrintStream($expr$)
+ java.io.OutputStream → new PrintStream($expr$)
+
+
+## others ##
+
+.sout : print variable to System.out
+ scala.Array → println($expr$.mkString("(", ", ", ")"))
+ #NON_VOID → println($expr$)
+
+.soutv : print variable to System.out
+ scala.Array → println("$expr$ = " + $expr$.mkString("(", ", ", ")"))
+ NON_VOID → println("$expr$ = " + $expr$)
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/templatemapping/emptyLambda.txt b/resources/de/endrullis/idea/postfixtemplates/language/templatemapping/emptyLambda.txt
new file mode 100644
index 00000000..202d4686
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/templatemapping/emptyLambda.txt
@@ -0,0 +1 @@
+$endActionF$ -> $END$
diff --git a/resources/de/endrullis/idea/postfixtemplates/language/templatemapping/varLambda.txt b/resources/de/endrullis/idea/postfixtemplates/language/templatemapping/varLambda.txt
new file mode 100644
index 00000000..50c9b571
--- /dev/null
+++ b/resources/de/endrullis/idea/postfixtemplates/language/templatemapping/varLambda.txt
@@ -0,0 +1,8 @@
+$f$ → $fVar:suggestShortVariableName()$ -> $f$
+$keyF$ → e -> $key$
+$valueF$ → e -> $value$
+$attributeF$ → $attributeVar:suggestShortVariableName()$ -> $attribute$
+$conditionF$ → $conditionVar:suggestShortVariableName()$ -> $condition$
+$endActionF$ → $actionVar:suggestShortVariableName()$ -> $END$
+$accumulatorF$ → (a, b) -> $accumulator$
+$supplierF$ → () -> $supplier$
diff --git a/resources/intentionDescriptions/OverrideTemplateRuleIntention/description.html b/resources/intentionDescriptions/OverrideTemplateRuleIntention/description.html
new file mode 100644
index 00000000..8cee08d7
--- /dev/null
+++ b/resources/intentionDescriptions/OverrideTemplateRuleIntention/description.html
@@ -0,0 +1,5 @@
+
+
+This intention allows you to override template rules.
+
+
\ No newline at end of file
diff --git a/resources/liveTemplates/CustomPostfixTemplates.xml b/resources/liveTemplates/CustomPostfixTemplates.xml
new file mode 100644
index 00000000..3785cf0d
--- /dev/null
+++ b/resources/liveTemplates/CustomPostfixTemplates.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/postfixTemplates/CombinedPostfixTemplate/after.java.template b/resources/postfixTemplates/CombinedPostfixTemplate/after.java.template
new file mode 100644
index 00000000..195d4956
--- /dev/null
+++ b/resources/postfixTemplates/CombinedPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a combination of two or more templates
+// see "Custom Postfix Templates" for template definitions
\ No newline at end of file
diff --git a/resources/postfixTemplates/CombinedPostfixTemplate/before.java.template b/resources/postfixTemplates/CombinedPostfixTemplate/before.java.template
new file mode 100644
index 00000000..195d4956
--- /dev/null
+++ b/resources/postfixTemplates/CombinedPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a combination of two or more templates
+// see "Custom Postfix Templates" for template definitions
\ No newline at end of file
diff --git a/resources/postfixTemplates/CombinedPostfixTemplate/description.html b/resources/postfixTemplates/CombinedPostfixTemplate/description.html
new file mode 100644
index 00000000..005138ef
--- /dev/null
+++ b/resources/postfixTemplates/CombinedPostfixTemplate/description.html
@@ -0,0 +1,6 @@
+
+
+
+Combined postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomCsharpStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomDartStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomDartStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomDartStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomDartStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomDartStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomDartStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomDartStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomDartStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomDartStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomGoStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomGoStringPostfixTemplate/after.java.template
new file mode 100755
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomGoStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomGoStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomGoStringPostfixTemplate/before.java.template
new file mode 100755
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomGoStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomGoStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomGoStringPostfixTemplate/description.html
new file mode 100755
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomGoStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomGroovyStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomJavaScriptStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomJavaStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomJavaStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomJavaStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomJavaStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomJavaStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomJavaStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomJavaStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomJavaStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomJavaStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomKotlinStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomLatexStringPostfixTempalte/after.java.template b/resources/postfixTemplates/CustomLatexStringPostfixTempalte/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomLatexStringPostfixTempalte/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomLatexStringPostfixTempalte/before.java.template b/resources/postfixTemplates/CustomLatexStringPostfixTempalte/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomLatexStringPostfixTempalte/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomLatexStringPostfixTempalte/description.html b/resources/postfixTemplates/CustomLatexStringPostfixTempalte/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomLatexStringPostfixTempalte/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomPhpStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomPhpStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomPhpStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomPhpStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomPhpStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomPhpStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomPhpStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomPhpStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomPhpStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomPythonStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomPythonStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomPythonStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomPythonStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomPythonStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomPythonStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomPythonStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomPythonStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomPythonStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomRubyStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomRubyStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomRubyStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomRubyStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomRubyStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomRubyStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomRubyStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomRubyStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomRubyStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomRustStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomRustStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomRustStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomRustStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomRustStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomRustStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomRustStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomRustStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomRustStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomScalaStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomScalaStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomScalaStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomScalaStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomScalaStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomScalaStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomScalaStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomScalaStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomScalaStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomSqlStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomSqlStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomSqlStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomSqlStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomSqlStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomSqlStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomSqlStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomSqlStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomSqlStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/after.java.template b/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/after.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/after.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/before.java.template b/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/before.java.template
new file mode 100644
index 00000000..454bcfa4
--- /dev/null
+++ b/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/before.java.template
@@ -0,0 +1,2 @@
+// this is a custom template defined by the user
+// see "Custom Postfix Templates" for template definition
\ No newline at end of file
diff --git a/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/description.html b/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/description.html
new file mode 100644
index 00000000..67e98777
--- /dev/null
+++ b/resources/postfixTemplates/CustomSwiftStringPostfixTemplate/description.html
@@ -0,0 +1,21 @@
+
+
+
+
+Custom postfix template.
+
+
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 00000000..a5fbeba8
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,8 @@
+rootProject.name = "intellij-postfix-templates"
+
+pluginManagement {
+ repositories {
+ maven("https://oss.sonatype.org/content/repositories/snapshots/")
+ gradlePluginPortal()
+ }
+}
diff --git a/src/App.css b/src/App.css
deleted file mode 100644
index c9b9bb40..00000000
--- a/src/App.css
+++ /dev/null
@@ -1,22 +0,0 @@
-.card-title{
- text-transform: uppercase;
-}
-
-.card-content-list label{
- color: #fff;
- font-size: 20px;
- width:4em;
-}
-nav{
- background-color: #ff0037;
-}
-nav .nav-wrapper{
- width: 80%;
- margin: auto;
-}
-.page-footer{
- background-color: #ff0037;
-}
-.card.blink{
- background-color: #ff0037 !important;
-}
diff --git a/src/App.js b/src/App.js
deleted file mode 100644
index b85f5997..00000000
--- a/src/App.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React, { Component } from 'react';
-import logo from './logo.svg';
-import './App.css';
-import Nav from './components/nav';
-import Footer from './components/footer';
-import MainContainer from './components/main-container';
-
-
-class App extends Component {
- render() {
- return (
-
-
-
-
-
-
- );
- }
-}
-
-export default App;
diff --git a/src/App.test.js b/src/App.test.js
deleted file mode 100644
index a754b201..00000000
--- a/src/App.test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import App from './App';
-
-it('renders without crashing', () => {
- const div = document.createElement('div');
- ReactDOM.render( , div);
- ReactDOM.unmountComponentAtNode(div);
-});
diff --git a/src/components/card.js b/src/components/card.js
deleted file mode 100644
index 8bc257c8..00000000
--- a/src/components/card.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import React, { Component } from 'react';
-
-export default class Card extends Component {
-
- constructor(){
- super()
- this.state = {blink:false}
- }
- shouldComponentUpdate(nextProps, nextState) {
-
-
- if (this.props.coin.last !== nextProps.coin.last ||
- this.props.coin.yesterday_last !== nextProps.coin.yesterday_last ||
- this.props.coin.volume !== nextProps.coin.volume
- ){
- this.setState({blink: true});
- this.timeoutId= setTimeout(function () {
- this.setState({blink: false});
- }.bind(this), 1000);
- }
-
-
- return true;
- }
-
- componentWillUnmount () {
- if (this.timeoutId) {
- clearTimeout(this.timeoutId);
- }
- }
- render() {
- return (
-
-
-
-
-
{this.props.coin.currency}
-
-
- NOW : ₩{String(this.props.coin.last).replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
- -24H : ₩{String(this.props.coin.yesterday_last).replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
-
-
-
- Volume {parseFloat(this.props.coin.volume).toLocaleString()}
-
-
-
- );
- }
-}
diff --git a/src/components/footer.js b/src/components/footer.js
deleted file mode 100644
index 3074cffc..00000000
--- a/src/components/footer.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React, { Component } from 'react';
-
-
-export default class Footer extends Component {
- render() {
- return (
-
-
-
-
-
Footer Content
-
You can use rows and columns here to organize your footer content.
-
-
-
-
-
-
- );
- }
-}
diff --git a/src/components/main-container.js b/src/components/main-container.js
deleted file mode 100644
index 70fbf0bf..00000000
--- a/src/components/main-container.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import React, { Component } from 'react';
-import _ from 'lodash';
-import Card from './card';
-import axios from 'axios';
-
-
-
-export default class MainContainer extends Component {
- constructor(){
- super();
- this.state = {
- coins:[],
- }
- this.url = 'https://thingproxy.freeboard.io/fetch/https://api.coinone.co.kr/ticker?currency=all'
- this.headers = {
- 'content-type': 'application/json',
- 'accept': 'application/json',
- };
-
- this.options = {
- headers: this.headers,
- timeout: 5000,
- };
- }
- currencies = {};
-
- getData(){
- axios.get(this.url,this.options)
- .then(result => {
-
- let data = result.data;
- let coins = [];
- Object.keys(data).map((k,i) => {
- if(data[k].last){
- coins.push(data[k]);
- }
- });
- this.setState({coins:coins});
- });
- }
- timer() {
- this.getData();
- }
- componentDidMount() {
- this.intervalId = setInterval(this.timer.bind(this), 2000);
- this.getData();
- }
- componentWillUnmount(){
- clearInterval(this.intervalId);
- }
-
- renderCoinCard(){
-
- return _.map(this.state.coins, coin => {
-
- return(
-
- )
- });
-
- }
- render() {
- return (
-
-
-
-
-
Cryptocurrency Monitor
-
-
-
-
- {this.state.coins.length > 0 && this.renderCoinCard()}
-
-
- );
- }
-}
diff --git a/src/components/nav.js b/src/components/nav.js
deleted file mode 100644
index 472b92ad..00000000
--- a/src/components/nav.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import React, { Component } from 'react';
-
-
-export default class Nav extends Component {
- render() {
- return (
-
-
-
- );
- }
-}
diff --git a/src/de/endrullis/idea/postfixtemplates/ApplicationInterface.java b/src/de/endrullis/idea/postfixtemplates/ApplicationInterface.java
new file mode 100644
index 00000000..1d9557ef
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/ApplicationInterface.java
@@ -0,0 +1,7 @@
+package de.endrullis.idea.postfixtemplates;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public interface ApplicationInterface {
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/MyPostStartupActivity.kt b/src/de/endrullis/idea/postfixtemplates/MyPostStartupActivity.kt
new file mode 100644
index 00000000..21bd7930
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/MyPostStartupActivity.kt
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates
+
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.startup.ProjectActivity
+import de.endrullis.idea.postfixtemplates.utils.CptUpdateUtils
+
+/**
+ * Project opening activity.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class MyPostStartupActivity : ProjectActivity {
+ override suspend fun execute(project: Project) {
+ CptUpdateUtils.checkForWebTemplateUpdates(project)
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/SentryErrorReporter.java b/src/de/endrullis/idea/postfixtemplates/SentryErrorReporter.java
new file mode 100644
index 00000000..faddabc1
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/SentryErrorReporter.java
@@ -0,0 +1,116 @@
+package de.endrullis.idea.postfixtemplates;
+
+import com.intellij.diagnostic.AbstractMessage;
+import com.intellij.diagnostic.IdeaReportingEvent;
+import com.intellij.ide.DataManager;
+import com.intellij.ide.plugins.IdeaPluginDescriptor;
+import com.intellij.idea.IdeaLogger;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.ErrorReportSubmitter;
+import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
+import com.intellij.openapi.diagnostic.SubmittedReportInfo;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.util.Consumer;
+import io.sentry.*;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+
+/**
+ * Error reporter that uses Sentry.
+ *
+ * @author Stefan Endrullis (stefan@endrullis.de)
+ * @author jansorg
+ */
+public class SentryErrorReporter extends ErrorReportSubmitter {
+
+ private Hub hub;
+
+ @NotNull
+ @Override
+ public String getReportActionText() {
+ return "Report to Author";
+ }
+
+ @Override
+ public boolean submit(IdeaLoggingEvent @NotNull [] events,
+ @Nullable String additionalInfo,
+ @NotNull Component parentComponent,
+ @NotNull Consumer super SubmittedReportInfo> consumer) {
+
+ if (hub == null) {
+ hub = createHub();
+ }
+
+ val context = DataManager.getInstance().getDataContext(parentComponent);
+ val project = CommonDataKeys.PROJECT.getData(context);
+
+ new Task.Backgroundable(project, "Sending Error Report") {
+ @Override
+ public void run(@NotNull ProgressIndicator indicator) {
+ SentryEvent event = new SentryEvent();
+ event.setLevel(SentryLevel.ERROR);
+
+ if (getPluginDescriptor() instanceof IdeaPluginDescriptor) {
+ event.setRelease(getPluginDescriptor().getVersion());
+ }
+ // set server name to empty to avoid tracking personal data
+ event.setServerName("");
+
+ // now, attach all exceptions to the message
+ //List errors = new ArrayList<>(events.length);
+ for (IdeaLoggingEvent ideaEvent : events) {
+ // this is the tricky part
+ // ideaEvent.throwable is a com.intellij.diagnostic.IdeaReportingEvent.TextBasedThrowable
+ // This is a wrapper and is only providing the original stacktrace via 'printStackTrace(...)',
+ // but not via 'getStackTrace()'.
+ //
+ // Sentry accesses Throwable.getStackTrace(),
+ // So, we workaround this by retrieving the original exception from the data property
+ if (ideaEvent instanceof IdeaReportingEvent) {
+ Throwable ex = ((AbstractMessage) ideaEvent.getData()).getThrowable();
+
+ event.setThrowable(ex);
+ break;
+ } else {
+ // ignoring this ideaEvent, you might not want to do this
+ }
+ }
+ //event.setExceptions(errors);
+ // might be useful to debug the exception
+ event.setExtra("last_action", IdeaLogger.ourLastActionId);
+
+ // by default, Sentry is sending async in a background thread
+ hub.captureEvent(event);
+
+ ApplicationManager.getApplication().invokeLater(() -> {
+ // we're a bit lazy here.
+ // Alternatively, we could add a listener to the sentry client
+ // to be notified if the message was successfully send
+ Messages.showInfoMessage(parentComponent, "Thank you for submitting your report!", "Error Report");
+ consumer.consume(new SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE));
+ });
+ }
+ }.queue();
+
+ return true;
+ }
+
+ private static Hub createHub() {
+ val options = new SentryOptions();
+ options.setDsn("https://d5db57a4e01b468b823e45f831d58fb7@o1399782.ingest.sentry.io/6727652");
+ // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
+ // We recommend adjusting this value in production.
+ options.setTracesSampleRate(1.0);
+ // When first trying Sentry it's good to see what the SDK is doing:
+ //options.setDebug(true);
+
+ return new Hub(options);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/actions/EditorTypedHandlerDelegate.java b/src/de/endrullis/idea/postfixtemplates/actions/EditorTypedHandlerDelegate.java
new file mode 100644
index 00000000..6afbcef4
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/actions/EditorTypedHandlerDelegate.java
@@ -0,0 +1,254 @@
+package de.endrullis.idea.postfixtemplates.actions;
+
+import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.openapi.ui.DialogBuilder;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.ui.SeparatorComponent;
+import com.intellij.ui.components.panels.VerticalBox;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.util.ui.components.BorderLayoutPanel;
+import de.endrullis.idea.postfixtemplates.language.CptFileType;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTemplate;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.text.TextAction;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._List;
+
+/**
+ * @author Stefan Endrullis (endrullis@iat.uni-leipzig.de)
+ */
+public class EditorTypedHandlerDelegate extends TypedHandlerDelegate {
+
+ @Override
+ public @NotNull Result beforeCharTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile psiFile, @NotNull FileType fileType) {
+ val document = editor.getDocument();
+
+ boolean isWebTemplateFile = false;
+
+ //val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
+
+ //if (psiFile != null) {
+ if (fileType.equals(CptFileType.INSTANCE)) {
+ val virtualFile = FileDocumentManager.getInstance().getFile(document);
+
+ if (virtualFile != null) {
+ val langAndVFile = CptUtil.getLangAndVFile(virtualFile);
+
+ isWebTemplateFile = langAndVFile != null && langAndVFile._2.id != null;
+ }
+ //WriteCommandAction.runWriteCommandAction(project, () -> document.insertString(0, "Typed\n"));
+ }
+ //}
+
+ if (isWebTemplateFile) {
+ val offset = editor.getCaretModel().getOffset();
+
+ val element = psiFile.findElementAt(offset);
+ eventuallyOpenFileEditDialog(document, project, element, true);
+
+ return Result.STOP;
+ } else {
+ return Result.DEFAULT;
+ }
+ }
+
+ public static void eventuallyOpenFileEditDialog(Document document, Project project, PsiElement element, boolean userTriedToEditFile) {
+ while (element != null && !(element instanceof CptMapping) && !(element instanceof CptTemplate)) {
+ element = element.getParent();
+ }
+
+ val vFile = Objects.requireNonNull(FileDocumentManager.getInstance().getFile(document));
+ val language = CptUtil.getLanguageOfTemplateFile(vFile);
+
+ if (language != null && element != null) {
+ if (element instanceof CptMapping cptMapping) {
+ CptTemplate cptTemplate = (CptTemplate) cptMapping.getParent().getParent();
+ openFileEditDialog(project, language.getLanguage(), vFile, cptTemplate, cptMapping, userTriedToEditFile);
+ }
+ }
+ }
+
+ private static void openFileEditDialog(Project project, String language, VirtualFile originalFile, CptTemplate cptTemplate, CptMapping cptMapping, boolean userTriedToEditFile) {
+ val editableTemplateFiles = new ArrayList();
+ for (File file : CptUtil.getTemplateFiles(language)) {
+ if (file.getName().equals(originalFile.getName())) {
+ break;
+ }
+ val langAndVFile = CptUtil.getLangAndVFile(Objects.requireNonNull(LocalFileSystem.getInstance().findFileByIoFile(file)));
+ if (langAndVFile != null && langAndVFile._2.id == null) {
+ editableTemplateFiles.add(new FileWrapper(file));
+ }
+ }
+
+ if (editableTemplateFiles.isEmpty()) {
+ val builder = new DialogBuilder().title("Override Template Rule").centerPanel(
+ new VerticalBox() {{
+ if (userTriedToEditFile) {
+ add(new JLabel("Web template files cannot be edited directly."));
+ add(new JLabel("But you can override templates from web template files in your own user template files that are loaded before."));
+ add(new JLabel("To do so, please create a template file in the settings dialog and put it somewhere above the file " + originalFile.getName().replace(".postfixTemplates", "") + ""));
+ add(new JLabel("or use the button Create new user template file directly ."));
+ add(new JLabel("Afterwards, go to the template rule you want to override again, press Alt+Enter , and choose Override template rule ."));
+ } else {
+ add(new JLabel("You can override templates from web template files in your own user template files that are loaded before."));
+ add(new JLabel("To do so, please create a template file in the settings dialog and put it somewhere above the file " + originalFile.getName().replace(".postfixTemplates", "") + ""));
+ add(new JLabel("or use the button Create new user template file directly ."));
+ add(new JLabel("Afterwards, go to the template rule you want to override again, press Alt+Enter , and choose Override template rule ."));
+ }
+ }}
+ );
+ builder.addAction(new TextAction("Open settings dialog") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ builder.getDialogWrapper().close(DialogWrapper.CLOSE_EXIT_CODE);
+ CptUtil.openPluginSettings(project);
+ }
+ });
+ builder.addOkAction().setText("Create new user template file directly");
+ builder.addCancelAction();
+
+ ApplicationManager.getApplication().invokeLater(() -> {
+ if (builder.show() == DialogWrapper.OK_EXIT_CODE) {
+ CptUtil.createTopPrioUserTemplateFile(language, "overriddenTemplates");
+ final File templateFile = CptUtil.getTemplateFile(language, "overriddenTemplates");
+ LocalFileSystem.getInstance().refreshIoFiles(_List(templateFile));
+
+ val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(templateFile);
+ if (virtualFile != null) {
+ addTemplateRuleToFile(project, cptTemplate, cptMapping, virtualFile);
+ }
+
+ CptUtil.openFileInEditor(project, templateFile);
+ }
+ });
+ } else {
+ final ComboBox fileComboBox = new ComboBox<>(editableTemplateFiles.toArray(new FileWrapper[0]));
+
+ val builder = new DialogBuilder().title("Override Template Rule").centerPanel(
+ new VerticalBox() {{
+ if (userTriedToEditFile) {
+ add(new JLabel("Web template files cannot be edited directly."));
+ add(new JLabel("But you can override templates from web template files in your own user template files that are loaded before."));
+ add(new SeparatorComponent(6, UIUtil.getPanelBackground(), UIUtil.getPanelBackground()));
+ } else {
+ //add(new JLabel("You can override this template rule by putting your own rule in a user template file"));
+ //add(new JLabel("that is loaded before this web template file (" + cptTemplate.getTemplateName() + ")."));
+ }
+ add(new JLabel("Please choose the template file in which you want to override the template rule."));
+ add(new SeparatorComponent(6, UIUtil.getPanelBackground(), UIUtil.getPanelBackground()));
+ add(new BorderLayoutPanel() {{
+ add(new JLabel("Template file to edit: "), BorderLayout.WEST);
+ add(fileComboBox, BorderLayout.CENTER);
+ }});
+ }}
+ );
+ builder.setPreferredFocusComponent(fileComboBox);
+ builder.addOkAction().setText("Edit template file");
+ builder.addCancelAction();
+
+ ApplicationManager.getApplication().invokeLater(() -> {
+ if (builder.show() == DialogWrapper.OK_EXIT_CODE) {
+ FileWrapper selectedFileWrapper = (FileWrapper) fileComboBox.getSelectedItem();
+
+ if (selectedFileWrapper != null) {
+ val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(selectedFileWrapper.file);
+ if (virtualFile != null) {
+ addTemplateRuleToFile(project, cptTemplate, cptMapping, virtualFile);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ private static void addTemplateRuleToFile(Project project, CptTemplate cptTemplate, CptMapping cptMapping, VirtualFile templateFileToEdit) {
+ ApplicationManager.getApplication().invokeLater(() -> {
+ CptUtil.openFileInEditor(project, templateFileToEdit);
+
+ // find insertion offset in editable template file (use template offset if template already exists)
+ AtomicInteger offset = new AtomicInteger(-1);
+ CptUtil.processTemplates(project, templateFileToEdit, (thatCptTemplate, thatCptMapping) -> {
+ if (cptTemplate.getTemplateName().equals(thatCptTemplate.getTemplateName())) {
+ offset.set(thatCptMapping.getTextRange().getEndOffset());
+ }
+ });
+
+ val psiFile = Objects.requireNonNull(PsiManager.getInstance(project).findFile(templateFileToEdit));
+ val document = Objects.requireNonNull(PsiDocumentManager.getInstance(project).getDocument(psiFile));
+
+ // compose template/mapping to insert
+ String textToInsert = "";
+ if (offset.get() == -1) {
+ textToInsert = "\n" + cptTemplate.getTemplateName() + " : " + cptTemplate.getTemplateDescription();
+ offset.set(document.getTextLength());
+ }
+ textToInsert += "\n\t" + cptMapping.getMatchingClassName();
+
+ if (cptMapping.getConditionClassName() != null) {
+ textToInsert += " [" + cptMapping.getConditionClassName() + "]";
+ }
+ textToInsert += " → " + cptMapping.getReplacementString();
+
+ String finalTextToInsert = textToInsert;
+
+ WriteCommandAction.runWriteCommandAction(project, () -> {
+ // insert template/mapping
+ document.insertString(offset.get(), finalTextToInsert);
+ PsiDocumentManager.getInstance(project).commitDocument(document);
+ val editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
+
+ if (editor != null) {
+ // move caret to newly inserted template/mapping
+ CptUtil.processTemplates(project, templateFileToEdit, (thatCptTemplate, thatCptMapping) -> {
+ if (cptTemplate.getTemplateName().equals(thatCptTemplate.getTemplateName())
+ && cptMapping.getMatchingClassName().equals(thatCptMapping.getMatchingClassName())) {
+ final int textOffset = thatCptMapping.getTextRange().getStartOffset();
+ editor.getCaretModel().moveToOffset(textOffset);
+ }
+ });
+ }
+ });
+ }, ModalityState.nonModal());
+ }
+
+ @Data
+ @AllArgsConstructor
+ static class FileWrapper {
+ public File file;
+
+ @Override
+ public String toString() {
+ return file.getName().replace(".postfixTemplates", "");
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/actions/OpenSettingsAction.java b/src/de/endrullis/idea/postfixtemplates/actions/OpenSettingsAction.java
new file mode 100644
index 00000000..eb3509f4
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/actions/OpenSettingsAction.java
@@ -0,0 +1,22 @@
+package de.endrullis.idea.postfixtemplates.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.project.Project;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+
+/**
+ * Action to open the templates of the language in the current editor.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class OpenSettingsAction extends AnAction {
+ @Override
+ public void actionPerformed(AnActionEvent anActionEvent) {
+ Project project = anActionEvent.getProject();
+
+ if (project != null) {
+ CptUtil.openPluginSettings(project);
+ }
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/actions/OpenTemplatesAction.java b/src/de/endrullis/idea/postfixtemplates/actions/OpenTemplatesAction.java
new file mode 100644
index 00000000..ee5269c5
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/actions/OpenTemplatesAction.java
@@ -0,0 +1,125 @@
+package de.endrullis.idea.postfixtemplates.actions;
+
+import com.intellij.lang.Language;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.ListPopup;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.ui.components.panels.VerticalBox;
+import de.endrullis.idea.postfixtemplates.language.CptFileType;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.languages.SupportedLanguages;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._List;
+
+/**
+ * Action to open the templates of the language in the current editor.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class OpenTemplatesAction extends AnAction {
+ @Override
+ public void actionPerformed(AnActionEvent anActionEvent) {
+ Project project = anActionEvent.getProject();
+
+ if (project != null) {
+ FileEditor[] selectedEditors = FileEditorManager.getInstance(project).getSelectedEditors();
+
+ if (selectedEditors.length > 0) {
+ FileEditor editor = selectedEditors[0];
+
+ final PsiFile psiFile = PsiManager.getInstance(project).findFile(Objects.requireNonNull(editor.getFile()));
+
+ assert psiFile != null;
+
+ List supportedLanguages = new ArrayList<>();
+
+ {
+ Language language = psiFile.getLanguage();
+ while (language != null) {
+ if (SupportedLanguages.supportedLanguageIds.contains(language.getID().toLowerCase())) {
+ supportedLanguages.add(language);
+ }
+ language = language.getBaseLanguage();
+ }
+ }
+
+ if (supportedLanguages.isEmpty()) {
+ if (!psiFile.getFileType().equals(CptFileType.INSTANCE)) {
+ val builder = new DialogBuilder().title("Custom Postfix Templates").centerPanel(
+ new VerticalBox() {{
+ add(new JLabel("The Custom Postfix Templates plugin does not support " + psiFile.getLanguage().getDisplayName() + " at the moment."));
+ }}
+ );
+ builder.addOkAction().setText("OK");
+ builder.show();
+ }
+ } else {
+ boolean multiLang = supportedLanguages.size() > 1;
+
+ DefaultActionGroup actionGroup = new DefaultActionGroup();
+
+ for (Language language : supportedLanguages) {
+ if (SupportedLanguages.supportedLanguageIds.contains(language.getID().toLowerCase())) {
+
+ List templateFiles = CptUtil.getEditableTemplateFiles(language.getID().toLowerCase());
+
+ if (!templateFiles.isEmpty()) {
+ for (File file : templateFiles) {
+ String prefix = multiLang ? language.getDisplayName() + ": " : "";
+ actionGroup.add(new DumbAwareAction(prefix + file.getName().replace(".postfixTemplates", "")) {
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
+ CptUtil.openFileInEditor(Objects.requireNonNull(project), file);
+ }
+ });
+ }
+ } else {
+ actionGroup.add(new DumbAwareAction("Create new user template file" + (multiLang ? " for " + language.getDisplayName() : "")) {
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
+ CptUtil.createTopPrioUserTemplateFile(language.getID().toLowerCase(), "user");
+ final File templateFile = CptUtil.getTemplateFile(language.getID().toLowerCase(), "user");
+ LocalFileSystem.getInstance().refreshIoFiles(_List(templateFile));
+
+ CptUtil.openFileInEditor(project, templateFile);
+ }
+ });
+ }
+ }
+ }
+
+ actionGroup.add(new DumbAwareAction("Open settings of Custom Postfix Templates") {
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
+ CptUtil.openPluginSettings(project);
+ }
+ });
+
+ DataContext context = anActionEvent.getDataContext();
+ ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup("Choose postfix template file to open", actionGroup, context,
+ JBPopupFactory.ActionSelectionAid.ALPHA_NUMBERING, true, null);
+ popup.showInBestPositionFor(context);
+ }
+ }
+ }
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/bundle/PostfixTemplatesBundle.java b/src/de/endrullis/idea/postfixtemplates/bundle/PostfixTemplatesBundle.java
new file mode 100644
index 00000000..0872451a
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/bundle/PostfixTemplatesBundle.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.endrullis.idea.postfixtemplates.bundle;
+
+import com.intellij.AbstractBundle;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.PropertyKey;
+
+import java.util.ResourceBundle;
+
+public class PostfixTemplatesBundle {
+ @NotNull
+ private static final String BUNDLE_NAME = "de.endrullis.idea.postfixtemplates.bundle.PostfixTemplatesBundle";
+ @NotNull
+ private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
+
+ @NotNull
+ public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE_NAME) String key, Object... params) {
+ return AbstractBundle.message(BUNDLE, key, params);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/intention/OverrideTemplateRuleIntention.java b/src/de/endrullis/idea/postfixtemplates/intention/OverrideTemplateRuleIntention.java
new file mode 100644
index 00000000..520382d8
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/intention/OverrideTemplateRuleIntention.java
@@ -0,0 +1,68 @@
+package de.endrullis.idea.postfixtemplates.intention;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.IncorrectOperationException;
+import de.endrullis.idea.postfixtemplates.actions.EditorTypedHandlerDelegate;
+import de.endrullis.idea.postfixtemplates.language.CptFileType;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTemplate;
+import lombok.val;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+@NonNls
+public class OverrideTemplateRuleIntention extends PsiElementBaseIntentionAction implements IntentionAction {
+
+ @NotNull
+ public String getText() {
+ return "Override template rule";
+ }
+
+ @Override
+ public boolean checkFile(@Nullable PsiFile file) {
+ if (file != null) {
+ return file.getFileType().equals(CptFileType.INSTANCE);
+ } else {
+ return false;
+ }
+ }
+
+ @NotNull
+ public String getFamilyName() {
+ return getText();
+ }
+
+ public boolean isAvailable(@NotNull Project project, Editor editor, @Nullable PsiElement element) {
+ if (element == null || editor == null || CptUtil.isUserTemplateFile(CptUtil.getVirtualFile(element))) {
+ return false;
+ }
+
+ while (element != null && !(element instanceof CptMapping) && !(element instanceof CptTemplate)) {
+ element = element.getParent();
+ }
+
+ return element instanceof CptMapping;
+ }
+
+ public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
+ val document = editor.getDocument();
+ val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
+
+ if (psiFile != null) {
+ EditorTypedHandlerDelegate.eventuallyOpenFileEditDialog(document, project, element, false);
+ }
+ }
+
+ public boolean startInWriteAction() {
+ return false;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/Cpt.bnf b/src/de/endrullis/idea/postfixtemplates/language/Cpt.bnf
new file mode 100644
index 00000000..3ac316ed
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/Cpt.bnf
@@ -0,0 +1,52 @@
+{
+ parserClass="de.endrullis.idea.postfixtemplates.language.parser.CptParser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="Cpt"
+ psiImplClassSuffix="Impl"
+ psiPackage="de.endrullis.idea.postfixtemplates.language.psi"
+ psiImplPackage="de.endrullis.idea.postfixtemplates.language.psi.impl"
+
+ elementTypeHolderClass="de.endrullis.idea.postfixtemplates.language.psi.CptTypes"
+ elementTypeClass="de.endrullis.idea.postfixtemplates.language.psi.CptElementType"
+ tokenTypeClass="de.endrullis.idea.postfixtemplates.language.psi.CptTokenType"
+
+ psiImplUtilClass="de.endrullis.idea.postfixtemplates.language.psi.impl.CptPsiImplUtil"
+}
+
+CptFile ::= (template|COMMENT)*
+
+template ::= (TEMPLATE_NAME SEPARATOR TEMPLATE_DESCRIPTION) mappings {
+ //pin=3
+ recoverWhile="recover_parser"
+ mixin="de.endrullis.idea.postfixtemplates.language.psi.impl.CptNamedElementImpl"
+ implements="de.endrullis.idea.postfixtemplates.language.psi.CptNamedElement"
+ methods=[getTemplateName getTemplateDescription getName setName getNameIdentifier getPresentation]
+}
+
+mappings ::= mapping*
+
+mapping ::= (CLASS_NAME (BRACKET_OPEN CLASS_NAME BRACKET_CLOSE)? MAP replacement) {
+ //pin=3
+ recoverWhile="recover_parser"
+ mixin="de.endrullis.idea.postfixtemplates.language.psi.impl.CptNamedElementImpl"
+ implements="de.endrullis.idea.postfixtemplates.language.psi.CptNamedElement"
+ methods=[getMatchingClassName getConditionClassName getReplacementString getName setName getNameIdentifier getPresentation]
+}
+
+replacement ::= (templateCodeG|escape|templateVariable)+
+
+templateCodeG ::= TEMPLATE_CODE
+
+escape ::= TEMPLATE_ESCAPE
+
+templateVariable ::= TEMPLATE_VARIABLE_START templateVariableNameG
+ (TEMPLATE_VARIABLE_SEPARATOR templateVariableExpressionG
+ (TEMPLATE_VARIABLE_SEPARATOR templateVariableValueG)?)? TEMPLATE_VARIABLE_END
+
+templateVariableNameG ::= TEMPLATE_VARIABLE_NAME
+templateVariableExpressionG ::= TEMPLATE_VARIABLE_EXPRESSION*
+templateVariableValueG ::= TEMPLATE_VARIABLE_VALUE+
+
+private recover_parser ::= !(TEMPLATE_NAME|CLASS_NAME|COMMENT)
diff --git a/src/de/endrullis/idea/postfixtemplates/language/Cpt.flex b/src/de/endrullis/idea/postfixtemplates/language/Cpt.flex
new file mode 100644
index 00000000..d30933f4
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/Cpt.flex
@@ -0,0 +1,106 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lexer.FlexLexer;
+import com.intellij.psi.tree.IElementType;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+import com.intellij.psi.TokenType;
+
+%%
+
+%class CptLexer
+%implements FlexLexer
+%unicode
+%function advance
+%type IElementType
+%eof{ return;
+%eof}
+
+CRLF=\R
+WHITE_SPACE=[\ \t]
+TEMPLATE_DESCRIPTION_FIRST_CHAR=[^ \n\f]
+TEMPLATE_DESCRIPTION_CHAR=[^\n\f]
+END_OF_LINE_COMMENT=("#")[^\r\n]*
+SEPARATOR=[:=]
+DOT=[.]
+BRACKET_OPEN=\[
+BRACKET_CLOSE=\]
+MAP=("->"|"→")
+NAME_CHAR=[a-zA-Z0-9_]
+CLASS_NAME_CHAR=[a-zA-Z0-9._]
+TEMPLATE_VARIABLE_START_CHAR=[$]
+TEMPLATE_VARIABLE_CHAR=[^$\r\n\f\t\\:]
+TEMPLATE_VARIABLE_SEPARATOR=[:]
+TEMPLATE_CODE_FIRST_CHAR=[^ $\n\f\\]
+TEMPLATE_CODE_CHAR=[^$\n\f\\]
+ESCAPE_CHAR="\\"
+ANY_CHAR=[^\n\f]
+
+%state WAITING_DESCRIPTION
+%state WAITING_TEMPLATE_CODE
+%state WAITING_TEMPLATE_CODE_CON
+%state WAITING_TEMPLATE_ESC
+%state WAITING_TEMPLATE_VAR_NAME
+%state WAITING_TEMPLATE_VAR_NAME_NEXT
+%state WAITING_TEMPLATE_VAR_EXPRESSION
+%state WAITING_TEMPLATE_VAR_EXPRESSION_ESC
+%state WAITING_TEMPLATE_VAR_VALUE
+%state WAITING_TEMPLATE_VAR_VALUE_ESC
+
+%%
+
+ {END_OF_LINE_COMMENT} { yybegin(YYINITIAL); return CptTypes.COMMENT; }
+ {DOT}{NAME_CHAR}+ { yybegin(YYINITIAL); return CptTypes.TEMPLATE_NAME; }
+ {CLASS_NAME_CHAR}+ { yybegin(YYINITIAL); return CptTypes.CLASS_NAME; }
+ {BRACKET_OPEN} { yybegin(YYINITIAL); return CptTypes.BRACKET_OPEN; }
+ {BRACKET_CLOSE} { yybegin(YYINITIAL); return CptTypes.BRACKET_CLOSE; }
+ {MAP} { yybegin(WAITING_TEMPLATE_CODE); return CptTypes.MAP; }
+ {SEPARATOR} { yybegin(WAITING_DESCRIPTION); return CptTypes.SEPARATOR; }
+ {TEMPLATE_VARIABLE_START_CHAR} { yybegin(WAITING_TEMPLATE_VAR_NAME); return CptTypes.TEMPLATE_VARIABLE_START; }
+
+
+ {CRLF}({CRLF}|{WHITE_SPACE})+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; }
+ {WHITE_SPACE}+ { yybegin(WAITING_DESCRIPTION); return TokenType.WHITE_SPACE; }
+ {TEMPLATE_DESCRIPTION_FIRST_CHAR}{TEMPLATE_DESCRIPTION_CHAR}* { yybegin(YYINITIAL); return CptTypes.TEMPLATE_DESCRIPTION; }
+
+
+ {CRLF}({CRLF}|{WHITE_SPACE})+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; }
+ {WHITE_SPACE}+ { yybegin(WAITING_TEMPLATE_CODE_CON); return TokenType.WHITE_SPACE; }
+ {TEMPLATE_CODE_FIRST_CHAR}{TEMPLATE_CODE_CHAR}* { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_CODE; }
+ {TEMPLATE_VARIABLE_START_CHAR} { yybegin(WAITING_TEMPLATE_VAR_NAME); return CptTypes.TEMPLATE_VARIABLE_START; }
+ {ESCAPE_CHAR} { yybegin(WAITING_TEMPLATE_ESC); return CptTypes.TEMPLATE_ESCAPE; }
+
+ {CRLF}({CRLF}|{WHITE_SPACE})+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; }
+ {WHITE_SPACE}+ { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_CODE; }
+ {TEMPLATE_CODE_CHAR}+ { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_CODE; }
+ {TEMPLATE_VARIABLE_START_CHAR} { yybegin(WAITING_TEMPLATE_VAR_NAME); return CptTypes.TEMPLATE_VARIABLE_START; }
+ {ESCAPE_CHAR} { yybegin(WAITING_TEMPLATE_ESC); return CptTypes.TEMPLATE_ESCAPE; }
+
+ ({CRLF}|{ANY_CHAR}){WHITE_SPACE}* { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_CODE; }
+
+
+ {TEMPLATE_VARIABLE_CHAR}+ { yybegin(WAITING_TEMPLATE_VAR_NAME_NEXT); return CptTypes.TEMPLATE_VARIABLE_NAME; }
+ {TEMPLATE_VARIABLE_START_CHAR} { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_ESCAPE; }
+
+ {TEMPLATE_VARIABLE_SEPARATOR} { yybegin(WAITING_TEMPLATE_VAR_EXPRESSION); return CptTypes.TEMPLATE_VARIABLE_SEPARATOR; }
+ {TEMPLATE_VARIABLE_START_CHAR} { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_VARIABLE_END; }
+
+
+ {TEMPLATE_VARIABLE_CHAR}+ { yybegin(WAITING_TEMPLATE_VAR_EXPRESSION); return CptTypes.TEMPLATE_VARIABLE_EXPRESSION; }
+ {TEMPLATE_VARIABLE_SEPARATOR} { yybegin(WAITING_TEMPLATE_VAR_VALUE); return CptTypes.TEMPLATE_VARIABLE_SEPARATOR; }
+ {TEMPLATE_VARIABLE_START_CHAR} { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_VARIABLE_END; }
+ {ESCAPE_CHAR} { yybegin(WAITING_TEMPLATE_VAR_EXPRESSION_ESC); return CptTypes.TEMPLATE_VARIABLE_EXPRESSION; }
+
+ ({CRLF}|{ANY_CHAR}){WHITE_SPACE}* { yybegin(WAITING_TEMPLATE_VAR_EXPRESSION); return CptTypes.TEMPLATE_VARIABLE_EXPRESSION; }
+
+
+ {TEMPLATE_VARIABLE_CHAR}+ { yybegin(WAITING_TEMPLATE_VAR_VALUE); return CptTypes.TEMPLATE_VARIABLE_VALUE; }
+ {TEMPLATE_VARIABLE_SEPARATOR} { yybegin(WAITING_TEMPLATE_VAR_VALUE); return CptTypes.TEMPLATE_VARIABLE_SEPARATOR; }
+ {TEMPLATE_VARIABLE_START_CHAR} { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_VARIABLE_END; }
+ {ESCAPE_CHAR} { yybegin(WAITING_TEMPLATE_VAR_VALUE_ESC); return CptTypes.TEMPLATE_VARIABLE_VALUE; }
+
+ ({CRLF}|{ANY_CHAR}){WHITE_SPACE}* { yybegin(WAITING_TEMPLATE_VAR_VALUE); return CptTypes.TEMPLATE_VARIABLE_VALUE; }
+
+
+({CRLF}|{WHITE_SPACE})+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE; }
+
+. { return TokenType.BAD_CHARACTER; }
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptAnnotator.java b/src/de/endrullis/idea/postfixtemplates/language/CptAnnotator.java
new file mode 100644
index 00000000..98beb4b4
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptAnnotator.java
@@ -0,0 +1,37 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lang.annotation.AnnotationHolder;
+import com.intellij.lang.annotation.Annotator;
+import com.intellij.lang.annotation.HighlightSeverity;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+import de.endrullis.idea.postfixtemplates.languages.SupportedLanguages;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Action to open the java templates.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class CptAnnotator implements Annotator {
+
+ @Override
+ public void annotate(@NotNull final PsiElement element, @NotNull AnnotationHolder holder) {
+ if (element instanceof final LeafPsiElement psiElement) {
+
+ if (psiElement.getElementType().equals(CptTypes.CLASS_NAME)) {
+ final String className = element.getText();
+
+ SupportedLanguages.getCptLang(element).ifPresent(lang -> {
+ final CptLangAnnotator annotator = lang.getAnnotator();
+
+ if (!annotator.isMatchingType(psiElement, className)) {
+ holder.newAnnotation(HighlightSeverity.ERROR, "Class not found").range(element.getTextRange()).create();
+ }
+ });
+ }
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptBlock.java b/src/de/endrullis/idea/postfixtemplates/language/CptBlock.java
new file mode 100644
index 00000000..bec7f4e5
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptBlock.java
@@ -0,0 +1,88 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.formatting.*;
+import com.intellij.lang.ASTNode;
+import com.intellij.psi.TokenType;
+import com.intellij.psi.formatter.common.AbstractBlock;
+import com.intellij.psi.tree.IElementType;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._Set;
+
+public class CptBlock extends AbstractBlock {
+ private final SpacingBuilder spacingBuilder;
+
+ private final Indent indent;
+ private final Indent childIndent;
+
+ private final Alignment mapAlignment = Alignment.createAlignment(false, Alignment.Anchor.LEFT);
+
+ protected CptBlock(@NotNull ASTNode node, @Nullable Wrap wrap, Indent indent, Indent childIndent, @Nullable Alignment alignment,
+ SpacingBuilder spacingBuilder) {
+ super(node, wrap, alignment);
+ this.indent = indent;
+ this.childIndent = childIndent;
+ this.spacingBuilder = spacingBuilder;
+ }
+
+ @Override
+ protected List buildChildren() {
+ List blocks = new ArrayList<>();
+ ASTNode child = myNode.getFirstChildNode();
+ while (child != null) {
+ final IElementType elementType = child.getElementType();
+
+ final Indent indent = elementType == CptTypes.MAPPINGS
+ ? Indent.getNormalIndent()
+ : Indent.getNoneIndent();
+
+ final Indent childIndent = _Set(CptTypes.TEMPLATE,CptTypes.MAPPINGS, CptTypes.MAPPING, CptTypes.REPLACEMENT,
+ CptTypes.TEMPLATE_ESCAPE, CptTypes.TEMPLATE_CODE, CptTypes.TEMPLATE_VARIABLE).contains(elementType)
+ ? Indent.getNormalIndent()
+ : Indent.getNoneIndent();
+
+ if (elementType != TokenType.WHITE_SPACE) {
+ Alignment alignment = null; //Alignment.createAlignment();
+
+ /*
+ if (elementType == CptTypes.MAP) {
+ alignment = mapAlignment;
+ }
+ */
+
+ Block block = new CptBlock(child, Wrap.createWrap(WrapType.NONE, false), indent, childIndent, alignment, spacingBuilder);
+ blocks.add(block);
+ }
+
+ child = child.getTreeNext();
+ }
+ return blocks;
+ }
+
+ @Nullable
+ @Override
+ protected Indent getChildIndent() {
+ return childIndent;
+ }
+
+ @Override
+ public Indent getIndent() {
+ return indent;
+ }
+
+ @Nullable
+ @Override
+ public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
+ return spacingBuilder.getSpacing(this, child1, child2);
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return myNode.getFirstChildNode() == null;
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptChooseByNameContributor.java b/src/de/endrullis/idea/postfixtemplates/language/CptChooseByNameContributor.java
new file mode 100644
index 00000000..168caa92
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptChooseByNameContributor.java
@@ -0,0 +1,34 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.navigation.ChooseByNameContributor;
+import com.intellij.navigation.NavigationItem;
+import com.intellij.openapi.project.Project;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+
+public class CptChooseByNameContributor implements ChooseByNameContributor {
+ @NotNull
+ @Override
+ public String @NotNull [] getNames(Project project, boolean includeNonProjectItems) {
+ val mappings = CptUtil.findMappings(project);
+ val names = new ArrayList(mappings.size());
+ for (CptMapping mapping : mappings) {
+ if (mapping.getMatchingClassName() != null && !mapping.getMatchingClassName().isEmpty()) {
+ names.add(mapping.getMatchingClassName());
+ }
+ }
+ return names.toArray(new String[0]);
+ }
+
+ @NotNull
+ @Override
+ public NavigationItem @NotNull [] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) {
+ // todo include non project items
+ val properties = CptUtil.findMappings(project, name);
+ //noinspection SuspiciousToArrayCall
+ return properties.toArray(new NavigationItem[0]);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptCodeStyleMainPanel.java b/src/de/endrullis/idea/postfixtemplates/language/CptCodeStyleMainPanel.java
new file mode 100644
index 00000000..e9c3a6e4
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptCodeStyleMainPanel.java
@@ -0,0 +1,23 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.application.options.TabbedLanguageCodeStylePanel;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+
+/**
+ * Main panel of code style settings.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class CptCodeStyleMainPanel extends TabbedLanguageCodeStylePanel {
+ public CptCodeStyleMainPanel(CodeStyleSettings currentSettings, CodeStyleSettings settings) {
+ super(CptLanguage.INSTANCE, currentSettings, settings);
+ }
+
+ @Override
+ protected void initTabs(CodeStyleSettings settings) {
+ addIndentOptionsTab(settings);
+ addSpacesTab(settings);
+ addWrappingAndBracesTab(settings);
+ addBlankLinesTab(settings);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptCodeStyleSettingsProvider.java b/src/de/endrullis/idea/postfixtemplates/language/CptCodeStyleSettingsProvider.java
new file mode 100644
index 00000000..95e8aa54
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptCodeStyleSettingsProvider.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.application.options.CodeStyleAbstractConfigurable;
+import com.intellij.application.options.CodeStyleAbstractPanel;
+import com.intellij.psi.codeStyle.CodeStyleConfigurable;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CodeStyleSettingsProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class CptCodeStyleSettingsProvider extends CodeStyleSettingsProvider {
+ @Nullable
+ @Override
+ public String getConfigurableDisplayName() {
+ return "Custom Postfix Templates";
+ }
+
+ @NotNull
+ @Override
+ public CodeStyleConfigurable createConfigurable(@NotNull CodeStyleSettings settings, @NotNull CodeStyleSettings originalSettings) {
+ return new CodeStyleAbstractConfigurable(settings, originalSettings, "CustomPostfixTemplates") {
+ @Override
+ protected @NotNull CodeStyleAbstractPanel createPanel(@NotNull CodeStyleSettings settings) {
+ return new CptCodeStyleMainPanel(getCurrentSettings(), settings);
+ }
+
+ @Nullable
+ @Override
+ public String getHelpTopic() {
+ return null;
+ }
+ };
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptColorSettingsPage.java b/src/de/endrullis/idea/postfixtemplates/language/CptColorSettingsPage.java
new file mode 100644
index 00000000..544f05ad
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptColorSettingsPage.java
@@ -0,0 +1,64 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.options.colors.AttributesDescriptor;
+import com.intellij.openapi.options.colors.ColorDescriptor;
+import com.intellij.openapi.options.colors.ColorSettingsPage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.Map;
+
+public class CptColorSettingsPage implements ColorSettingsPage {
+ private static final AttributesDescriptor[] DESCRIPTORS = new AttributesDescriptor[]{
+ new AttributesDescriptor("Template name", CptSyntaxHighlighter.TEMPLATE_NAME),
+ new AttributesDescriptor("Class name", CptSyntaxHighlighter.CLASS_NAME),
+ new AttributesDescriptor("Separator", CptSyntaxHighlighter.SEPARATOR),
+ new AttributesDescriptor("Description", CptSyntaxHighlighter.TEMPLATE_DESCRIPTION),
+ new AttributesDescriptor("Template code", CptSyntaxHighlighter.TEMPLATE_CODE),
+ };
+
+ @Nullable
+ @Override
+ public Icon getIcon() {
+ return CptIcons.FILE;
+ }
+
+ @NotNull
+ @Override
+ public SyntaxHighlighter getHighlighter() {
+ return new CptSyntaxHighlighter();
+ }
+
+ @NotNull
+ @Override
+ public String getDemoText() {
+ return CptUtil.getDefaultTemplates("java");
+ }
+
+ @Nullable
+ @Override
+ public Map getAdditionalHighlightingTagToDescriptorMap() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public AttributesDescriptor @NotNull [] getAttributeDescriptors() {
+ return DESCRIPTORS;
+ }
+
+ @NotNull
+ @Override
+ public ColorDescriptor @NotNull [] getColorDescriptors() {
+ return ColorDescriptor.EMPTY_ARRAY;
+ }
+
+ @NotNull
+ @Override
+ public String getDisplayName() {
+ return "Custom Postfix Templates";
+ }
+}
\ No newline at end of file
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptCommenter.java b/src/de/endrullis/idea/postfixtemplates/language/CptCommenter.java
new file mode 100644
index 00000000..101dc8a2
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptCommenter.java
@@ -0,0 +1,36 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lang.Commenter;
+import org.jetbrains.annotations.Nullable;
+
+public class CptCommenter implements Commenter {
+ @Nullable
+ @Override
+ public String getLineCommentPrefix() {
+ return "#";
+ }
+
+ @Nullable
+ @Override
+ public String getBlockCommentPrefix() {
+ return "";
+ }
+
+ @Nullable
+ @Override
+ public String getBlockCommentSuffix() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getCommentedBlockCommentPrefix() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getCommentedBlockCommentSuffix() {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptCompletionContributor.java b/src/de/endrullis/idea/postfixtemplates/language/CptCompletionContributor.java
new file mode 100644
index 00000000..ccef376a
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptCompletionContributor.java
@@ -0,0 +1,81 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.codeInsight.completion.*;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.codeInsight.template.Macro;
+import com.intellij.codeInsight.template.macro.MacroFactory;
+import com.intellij.patterns.PlatformPatterns;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.ProcessingContext;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+import de.endrullis.idea.postfixtemplates.languages.SupportedLanguages;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import static de.endrullis.idea.postfixtemplates.settings.CustomPostfixTemplates.PREDEFINED_VARIABLES;
+
+public class CptCompletionContributor extends CompletionContributor {
+ public CptCompletionContributor() {
+ extend(CompletionType.BASIC,
+ PlatformPatterns.psiElement(CptTypes.CLASS_NAME).withLanguage(CptLanguage.INSTANCE),
+ new CompletionProvider<>() {
+ public void addCompletions(@NotNull CompletionParameters parameters,
+ @NotNull ProcessingContext context,
+ @NotNull CompletionResultSet resultSet) {
+ PsiElement psiElement = parameters.getPosition();
+
+ if (isUserTemplateFile(psiElement)) {
+ SupportedLanguages.getCptLang(psiElement).ifPresent(lang -> {
+ final CptLangAnnotator annotator = lang.getAnnotator();
+
+ annotator.completeMatchingType(parameters, resultSet);
+ });
+ }
+ }
+ }
+ );
+
+ extend(CompletionType.BASIC,
+ PlatformPatterns.psiElement(CptTypes.TEMPLATE_VARIABLE_NAME).withLanguage(CptLanguage.INSTANCE),
+ new CompletionProvider<>() {
+ public void addCompletions(@NotNull CompletionParameters parameters,
+ @NotNull ProcessingContext context,
+ @NotNull CompletionResultSet resultSet) {
+ if (isUserTemplateFile(parameters.getPosition())) {
+ for (String name : PREDEFINED_VARIABLES) {
+ resultSet.addElement(LookupElementBuilder.create(name));
+ }
+ }
+ }
+ }
+ );
+
+ extend(CompletionType.BASIC,
+ PlatformPatterns.psiElement(CptTypes.TEMPLATE_VARIABLE_EXPRESSION).withLanguage(CptLanguage.INSTANCE),
+ new CompletionProvider<>() {
+ public void addCompletions(@NotNull CompletionParameters parameters,
+ @NotNull ProcessingContext context,
+ @NotNull CompletionResultSet resultSet) {
+ if (isUserTemplateFile(parameters.getPosition())) {
+ for (Macro macro : MacroFactory.getMacros()) {
+ resultSet.addElement(LookupElementBuilder.create(macro.getPresentableName()));
+ }
+ }
+ }
+ }
+ );
+ }
+
+ /**
+ * Returns true if the given file is a user template file (editable).
+ *
+ * @param psiElement PSI element of the file
+ * @return true if the given file is a user template file (editable)
+ */
+ private boolean isUserTemplateFile(PsiElement psiElement) {
+ val virtualFile = CptUtil.getVirtualFile(psiElement);
+ val langAndVFile = CptUtil.getLangAndVFile(virtualFile);
+
+ return langAndVFile == null || langAndVFile._2.id == null;
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptCompletionUtil.java b/src/de/endrullis/idea/postfixtemplates/language/CptCompletionUtil.java
new file mode 100644
index 00000000..567821c2
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptCompletionUtil.java
@@ -0,0 +1,125 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.codeInsight.completion.AllClassesGetter;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.completion.JavaPsiClassReferenceElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.*;
+import lombok.experimental.UtilityClass;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Completion utils for Java class names.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+@UtilityClass
+public class CptCompletionUtil {
+
+ public static void addCompletions(@NotNull CompletionParameters params, @NotNull CompletionResultSet resultSet) {
+ final PsiElement originalPosition = params.getOriginalPosition();
+ final PsiElement originalParent = originalPosition == null ? null : originalPosition.getParent();
+ if (originalParent == null) {
+ return;
+ }
+ final Project project = CptUtil.findProject(originalParent);
+ /*
+ final Module module = ModuleUtilCore.findModuleForPsiElement(originalParent);
+ if (module == null) {
+ return;
+ }
+ */
+ final PsiFile containingFile = originalParent.getContainingFile();
+ if (containingFile == null) {
+ return;
+ }
+ final String packagePrefix = getPackagePrefix(originalParent, params.getOffset());
+ //fillAliases(resultSet, packagePrefix, originalPosition, module, originalParent);
+ fillClassNames(resultSet, packagePrefix, project);
+ /*
+ JavaClassNameCompletionContributor.addAllClasses(params, true, resultSet.getPrefixMatcher(), element -> {
+ System.out.println(packagePrefix + "." + element.getLookupString());
+ resultSet.addElement(LookupElementBuilder.create(packagePrefix + "." + element.getLookupString()));
+ });
+ */
+ resultSet.stopHere();
+ }
+
+ private static void fillClassNames(@NotNull CompletionResultSet resultSet, @NotNull String packagePrefix, @NotNull Project project) {
+ JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project);
+ PsiPackage basePackage = javaPsiFacade.findPackage(packagePrefix);
+ if (basePackage != null) {
+ PsiPackage[] subPackages = basePackage.getSubPackages();
+ for (PsiPackage pkg : subPackages) {
+ // For some reason, we see some invalid packages here - e.g. META-INF. Filter them out.
+ String name = pkg.getName();
+ boolean invalidPkg = false;
+ assert name != null; // can only be null for default package, which this is not, as it's a subpackage.
+ for (int i = 0; i < name.length(); i++) {
+ if (!Character.isJavaIdentifierPart(name.charAt(i))) {
+ invalidPkg = true;
+ break;
+ }
+ }
+ if (invalidPkg) {
+ continue; // skip adding this package.
+ }
+ LookupElementBuilder element = LookupElementBuilder.create(pkg.getQualifiedName()).withIcon(pkg.getIcon(0));
+ /*
+ LookupElement element = new TailTypeDecorator(LookupElementBuilder.createWithIcon(pkg)) {
+ @Nullable
+ @Override
+ protected TailType computeTailType(InsertionContext context) {
+ return TailType.DOT;
+ }
+
+ @Override
+ public void handleInsert(InsertionContext context) {
+ super.handleInsert(context);
+ System.out.println("DataBindingCompletionUtil.handleInsert");
+ AutoPopupController.getInstance(project).scheduleAutoPopup(context.getEditor());
+ }
+ };
+ //System.out.println("element = " + element);
+ */
+ resultSet.addElement(element);
+ }
+
+ for (PsiClass psiClass : basePackage.getClasses()) {
+ LookupElementBuilder element = LookupElementBuilder.create(psiClass.getQualifiedName()).withIcon(psiClass.getIcon(0));
+ resultSet.addElement(element);
+ //resultSet.addElement(new JavaPsiClassReferenceElement(psiClass));
+ }
+ }
+ }
+
+ @NotNull
+ private static JavaPsiClassReferenceElement getClassReferenceElement(String alias, PsiClass referenceClass) {
+ JavaPsiClassReferenceElement element = new JavaPsiClassReferenceElement(referenceClass);
+ element.setForcedPresentableName(alias);
+ element.setInsertHandler((context, item) -> {
+ // Override the default InsertHandler to prevent adding the FQCN.
+ });
+ return element;
+ }
+
+ /**
+ * Copied from {@link AllClassesGetter}#getPackagePrefix(PsiElement, int), since that method is private.
+ */
+ private static String getPackagePrefix(@NotNull final PsiElement context, final int offset) {
+ final CharSequence fileText = context.getContainingFile().getViewProvider().getContents();
+ int i = offset - 1;
+ while (i >= 0) {
+ final char c = fileText.charAt(i);
+ if (!Character.isJavaIdentifierPart(c) && c != '.') break;
+ i--;
+ }
+ String prefix = fileText.subSequence(i + 1, offset).toString();
+ final int j = prefix.lastIndexOf('.');
+ return j > 0 ? prefix.substring(0, j) : "";
+ }
+
+}
+
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptContext.java b/src/de/endrullis/idea/postfixtemplates/language/CptContext.java
new file mode 100644
index 00000000..f09df7b3
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptContext.java
@@ -0,0 +1,21 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.codeInsight.template.TemplateActionContext;
+import com.intellij.codeInsight.template.TemplateContextType;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * CPT context.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class CptContext extends TemplateContextType {
+ public CptContext() {
+ super("Custom postfix templates");
+ }
+
+ @Override
+ public boolean isInContext(@NotNull TemplateActionContext templateActionContext) {
+ return templateActionContext.getFile().getFileType() == CptFileType.INSTANCE;
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptFileType.java b/src/de/endrullis/idea/postfixtemplates/language/CptFileType.java
new file mode 100644
index 00000000..d0ef47ec
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptFileType.java
@@ -0,0 +1,45 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class CptFileType extends LanguageFileType {
+ public static final CptFileType INSTANCE = new CptFileType();
+
+ private CptFileType() {
+ super(CptLanguage.INSTANCE);
+ }
+
+ @NotNull
+ @Override
+ public String getName() {
+ return "Postfix templates file type";
+ }
+
+ @NotNull
+ @Override
+ public String getDescription() {
+ return "Postfix templates file";
+ }
+
+ @NotNull
+ @Override
+ public String getDefaultExtension() {
+ return "postfixTemplates";
+ }
+
+ @Nullable
+ @Override
+ public Icon getIcon() {
+ return CptIcons.FILE;
+ }
+
+ @Override
+ public String getCharset(@NotNull VirtualFile file, byte @NotNull [] content) {
+ return "UTF-8";
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptFilterLexer.java b/src/de/endrullis/idea/postfixtemplates/language/CptFilterLexer.java
new file mode 100644
index 00000000..d7674b67
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptFilterLexer.java
@@ -0,0 +1,19 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lexer.Lexer;
+import com.intellij.psi.impl.cache.impl.BaseFilterLexer;
+import com.intellij.psi.impl.cache.impl.OccurrenceConsumer;
+import com.intellij.psi.search.UsageSearchContext;
+
+public class CptFilterLexer extends BaseFilterLexer {
+ public CptFilterLexer(final Lexer originalLexer, final OccurrenceConsumer table) {
+ super(originalLexer, table);
+ }
+
+ @Override
+ public void advance() {
+ scanWordsInToken(UsageSearchContext.IN_COMMENTS, false, false);
+ advanceTodoItemCountsInToken();
+ myDelegate.advance();
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptFormattingModelBuilder.java b/src/de/endrullis/idea/postfixtemplates/language/CptFormattingModelBuilder.java
new file mode 100644
index 00000000..dc4d5beb
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptFormattingModelBuilder.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.formatting.*;
+import com.intellij.psi.codeStyle.CodeStyleSettings;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+import org.jetbrains.annotations.NotNull;
+
+public class CptFormattingModelBuilder implements FormattingModelBuilder {
+ @Override
+ public @NotNull FormattingModel createModel(@NotNull FormattingContext formattingContext) {
+ return FormattingModelProvider.createFormattingModelForPsiFile(formattingContext.getPsiElement().getContainingFile(),
+ new CptBlock(formattingContext.getPsiElement().getNode(),
+ Wrap.createWrap(WrapType.NONE, false),
+ Indent.getNoneIndent(),
+ Indent.getNoneIndent(),
+ Alignment.createAlignment(),
+ createSpaceBuilder(formattingContext.getCodeStyleSettings())),
+ formattingContext.getCodeStyleSettings());
+ }
+
+ private static SpacingBuilder createSpaceBuilder(CodeStyleSettings settings) {
+ return new SpacingBuilder(settings, CptLanguage.INSTANCE)
+ .around(CptTypes.SEPARATOR).spaceIf(settings.getCommonSettings(CptLanguage.INSTANCE).SPACE_AROUND_ASSIGNMENT_OPERATORS)
+ //.around(CptTypes.MAP).spaceIf(settings.getCommonSettings(CptLanguage.INSTANCE).SPACE_AROUND_LAMBDA_ARROW)
+ .before(CptTypes.MAPPING).spaces(2);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptIcons.java b/src/de/endrullis/idea/postfixtemplates/language/CptIcons.java
new file mode 100644
index 00000000..71188d07
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptIcons.java
@@ -0,0 +1,19 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.openapi.util.IconLoader;
+import de.endrullis.idea.postfixtemplates.ApplicationInterface;
+import lombok.experimental.UtilityClass;
+
+import javax.swing.*;
+
+/**
+ * CPT icons.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+@UtilityClass
+public class CptIcons {
+
+ public static final Icon FILE = IconLoader.getIcon("icons/cpt-icon.png", ApplicationInterface.class);
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptIndexer.java b/src/de/endrullis/idea/postfixtemplates/language/CptIndexer.java
new file mode 100644
index 00000000..3c42a62b
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptIndexer.java
@@ -0,0 +1,19 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lexer.Lexer;
+import com.intellij.psi.impl.cache.impl.OccurrenceConsumer;
+import com.intellij.psi.impl.cache.impl.id.LexerBasedIdIndexer;
+import org.jetbrains.annotations.NotNull;
+
+public class CptIndexer extends LexerBasedIdIndexer {
+
+ public static Lexer createIndexingLexer(OccurrenceConsumer consumer) {
+ return new CptFilterLexer(new CptLexerAdapter(), consumer);
+ }
+
+ @Override
+ public @NotNull Lexer createLexer(final @NotNull OccurrenceConsumer consumer) {
+ return createIndexingLexer(consumer);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptLang.java b/src/de/endrullis/idea/postfixtemplates/language/CptLang.java
new file mode 100644
index 00000000..42f03096
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptLang.java
@@ -0,0 +1,59 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import lombok.Getter;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Abstract CPT language definition.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+@Getter
+public abstract class CptLang {
+
+ private final String niceName;
+ private final String language;
+ private final Class extends CptLangAnnotator> annotatorClass;
+ /** Lazily instantiated CptLangAnnotator. */
+ private CptLangAnnotator annotator;
+
+ protected CptLang(String niceName, Class extends CptLangAnnotator> annotatorClass) {
+ this.niceName = niceName;
+ this.language = niceName.toLowerCase();
+ this.annotatorClass = annotatorClass;
+ }
+
+ public CptLangAnnotator getAnnotator() {
+ // instantiate annotator if not yet done
+ if (annotator == null) {
+ try {
+ annotator = annotatorClass.getConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ return annotator;
+ }
+
+ @Override
+ public int hashCode() {
+ return language.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof CptLang that) {
+ return this.language.equals(that.language);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return niceName;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptLangAnnotator.java b/src/de/endrullis/idea/postfixtemplates/language/CptLangAnnotator.java
new file mode 100644
index 00000000..c05b32c0
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptLangAnnotator.java
@@ -0,0 +1,19 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Abstract CPT language annotator.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public interface CptLangAnnotator {
+
+ boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className);
+
+ void completeMatchingType(@NotNull final CompletionParameters parameters, @NotNull final CompletionResultSet resultSet);
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptLanguage.java b/src/de/endrullis/idea/postfixtemplates/language/CptLanguage.java
new file mode 100644
index 00000000..01ccea12
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptLanguage.java
@@ -0,0 +1,18 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lang.Language;
+
+/**
+ * The CPT language.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class CptLanguage extends Language {
+
+ public static final CptLanguage INSTANCE = new CptLanguage();
+
+ private CptLanguage() {
+ super("CPT");
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptLanguageCodeStyleSettingsProvider.java b/src/de/endrullis/idea/postfixtemplates/language/CptLanguageCodeStyleSettingsProvider.java
new file mode 100644
index 00000000..6f39fcd5
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptLanguageCodeStyleSettingsProvider.java
@@ -0,0 +1,52 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.application.options.IndentOptionsEditor;
+import com.intellij.application.options.SmartIndentOptionsEditor;
+import com.intellij.lang.Language;
+import com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable;
+import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
+import org.jetbrains.annotations.NotNull;
+
+import static com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable.BlankLinesOption.KEEP_BLANK_LINES_IN_CODE;
+import static com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable.SpacingOption.SPACE_AROUND_ASSIGNMENT_OPERATORS;
+import static com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable.SpacingOption.SPACE_AROUND_LAMBDA_ARROW;
+import static com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable.WrappingOrBraceOption.ALIGN_GROUP_FIELD_DECLARATIONS;
+
+/**
+ * The code style settings provider for CPT.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class CptLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettingsProvider {
+
+ @NotNull
+ @Override
+ public Language getLanguage() {
+ return CptLanguage.INSTANCE;
+ }
+
+ @Override
+ public void customizeSettings(@NotNull CodeStyleSettingsCustomizable consumer, @NotNull SettingsType settingsType) {
+ if (settingsType == SettingsType.SPACING_SETTINGS) {
+ consumer.showStandardOptions(SPACE_AROUND_ASSIGNMENT_OPERATORS.name());
+ consumer.renameStandardOption(SPACE_AROUND_ASSIGNMENT_OPERATORS.name(), "Separator");
+ consumer.showStandardOptions(SPACE_AROUND_LAMBDA_ARROW.name());
+ consumer.renameStandardOption(SPACE_AROUND_LAMBDA_ARROW.name(), "Mapping arrow");
+ } else if (settingsType == SettingsType.WRAPPING_AND_BRACES_SETTINGS) {
+ consumer.showStandardOptions(ALIGN_GROUP_FIELD_DECLARATIONS.name());
+ } else if (settingsType == SettingsType.BLANK_LINES_SETTINGS) {
+ consumer.showStandardOptions(KEEP_BLANK_LINES_IN_CODE.name());
+ }
+ }
+
+ @Override
+ public String getCodeSample(@NotNull SettingsType settingsType) {
+ return CptUtil.getDefaultTemplates("java");
+ }
+
+ @Override
+ public IndentOptionsEditor getIndentOptionsEditor() {
+ return new SmartIndentOptionsEditor();
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptLexer.java b/src/de/endrullis/idea/postfixtemplates/language/CptLexer.java
new file mode 100644
index 00000000..59f7e06b
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptLexer.java
@@ -0,0 +1,636 @@
+/* The following code was generated by JFlex 1.7.0-SNAPSHOT tweaked for IntelliJ platform */
+
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lexer.FlexLexer;
+import com.intellij.psi.TokenType;
+import com.intellij.psi.tree.IElementType;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+
+
+/**
+ * This class is a scanner generated by
+ * JFlex 1.7.0-SNAPSHOT
+ * from the specification file Cpt.flex
+ */
+class CptLexer implements FlexLexer {
+
+ /** This character denotes the end of file */
+ public static final int YYEOF = -1;
+
+ /** initial size of the lookahead buffer */
+ private static final int ZZ_BUFFERSIZE = 16384;
+
+ /** lexical states */
+ public static final int YYINITIAL = 0;
+ public static final int WAITING_DESCRIPTION = 2;
+ public static final int WAITING_TEMPLATE_CODE = 4;
+ public static final int WAITING_TEMPLATE_CODE_CON = 6;
+ public static final int WAITING_TEMPLATE_ESC = 8;
+ public static final int WAITING_TEMPLATE_VAR_NAME = 10;
+ public static final int WAITING_TEMPLATE_VAR_NAME_NEXT = 12;
+ public static final int WAITING_TEMPLATE_VAR_EXPRESSION = 14;
+ public static final int WAITING_TEMPLATE_VAR_EXPRESSION_ESC = 16;
+ public static final int WAITING_TEMPLATE_VAR_VALUE = 18;
+ public static final int WAITING_TEMPLATE_VAR_VALUE_ESC = 20;
+
+ /**
+ * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
+ * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
+ * at the beginning of a line
+ * l is of the form l = 2*k, k a non negative integer
+ */
+ private static final int[] ZZ_LEXSTATE = {
+ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7,
+ 8, 8, 9, 9, 10, 10
+ };
+
+ /**
+ * Translates characters to character classes
+ * Chosen bits are [9, 6, 6]
+ * Total runtime size is 1696 bytes
+ */
+ public static int ZZ_CMAP(int ch) {
+ return ZZ_CMAP_A[(ZZ_CMAP_Y[ZZ_CMAP_Z[ch>>12]|((ch>>6)&0x3f)]<<6)|(ch&0x3f)];
+ }
+
+ /* The ZZ_CMAP_Z table has 272 entries */
+ static final char[] ZZ_CMAP_Z = zzUnpackCMap(
+ "\1\0\1\100\1\200\u010d\100");
+
+ /* The ZZ_CMAP_Y table has 192 entries */
+ static final char[] ZZ_CMAP_Y = zzUnpackCMap(
+ "\1\0\1\1\1\2\175\3\1\4\5\3\1\5\71\3");
+
+ /* The ZZ_CMAP_A table has 384 entries */
+ static final char[] ZZ_CMAP_A = zzUnpackCMap(
+ "\11\0\1\4\1\2\1\1\1\5\1\3\22\0\1\6\2\0\1\7\1\20\10\0\1\14\1\11\1\0\12\17\1"+
+ "\22\2\0\1\10\1\15\2\0\32\17\1\12\1\21\1\13\1\0\1\17\1\0\32\17\12\0\1\1\242"+
+ "\0\2\1\50\0\1\16\55\0");
+
+ /**
+ * Translates DFA states to action switch labels.
+ */
+ private static final int [] ZZ_ACTION = zzUnpackAction();
+
+ private static final String ZZ_ACTION_PACKED_0 =
+ "\13\0\1\1\1\2\1\3\1\4\1\5\1\6\1\7"+
+ "\1\1\1\10\1\5\1\11\2\12\2\13\2\14\2\15"+
+ "\1\16\4\14\2\17\1\20\1\21\1\22\2\23\1\24"+
+ "\1\25\3\23\2\26\1\27\3\26\1\30\1\2\1\12"+
+ "\1\2";
+
+ private static int [] zzUnpackAction() {
+ int [] result = new int[57];
+ int offset = 0;
+ offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
+ return result;
+ }
+
+ private static int zzUnpackAction(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int count = packed.charAt(i++);
+ int value = packed.charAt(i++);
+ do result[j++] = value; while (--count > 0);
+ }
+ return j;
+ }
+
+
+ /**
+ * Translates a state to a row index in the transition table
+ */
+ private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
+
+ private static final String ZZ_ROWMAP_PACKED_0 =
+ "\0\0\0\23\0\46\0\71\0\114\0\137\0\162\0\205"+
+ "\0\230\0\253\0\276\0\321\0\344\0\367\0\321\0\u010a"+
+ "\0\321\0\321\0\u011d\0\321\0\u0130\0\321\0\u0143\0\u0156"+
+ "\0\u0169\0\u017c\0\u018f\0\u01a2\0\u01b5\0\u01c8\0\321\0\u01db"+
+ "\0\u01ee\0\u0201\0\u0214\0\u0227\0\u023a\0\321\0\321\0\321"+
+ "\0\u024d\0\u0260\0\321\0\321\0\u0273\0\u0286\0\u0299\0\u02ac"+
+ "\0\u02bf\0\321\0\u02d2\0\u02e5\0\u02f8\0\u010a\0\u0156\0\u030b"+
+ "\0\u01a2";
+
+ private static int [] zzUnpackRowMap() {
+ int [] result = new int[57];
+ int offset = 0;
+ offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
+ return result;
+ }
+
+ private static int zzUnpackRowMap(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int high = packed.charAt(i++) << 16;
+ result[j++] = high | packed.charAt(i++);
+ }
+ return j;
+ }
+
+ /**
+ * The transition table of the DFA
+ */
+ private static final int [] ZZ_TRANS = zzUnpackTrans();
+
+ private static final String ZZ_TRANS_PACKED_0 =
+ "\1\14\6\15\1\16\1\17\1\20\1\21\1\22\1\23"+
+ "\1\14\1\24\1\25\1\26\1\14\1\17\1\27\1\30"+
+ "\1\15\1\30\1\31\1\15\1\32\14\27\1\33\1\34"+
+ "\1\15\1\34\1\35\1\15\1\36\11\33\1\26\1\37"+
+ "\2\33\1\34\1\15\1\34\1\40\1\15\1\40\11\33"+
+ "\1\26\1\37\1\33\1\41\2\42\1\43\3\42\14\41"+
+ "\1\44\1\45\4\15\1\45\11\44\1\46\3\14\6\15"+
+ "\11\14\1\47\1\14\1\50\1\51\1\52\4\15\1\52"+
+ "\11\51\1\47\1\53\1\54\1\55\2\56\1\57\3\56"+
+ "\14\55\1\60\1\61\4\15\1\61\11\60\1\47\1\62"+
+ "\1\54\1\63\2\64\1\65\3\64\14\63\24\0\6\15"+
+ "\14\0\2\16\2\0\17\16\11\0\1\25\5\0\1\66"+
+ "\20\0\1\24\16\0\1\25\5\0\1\25\3\0\2\27"+
+ "\1\0\2\27\1\0\16\27\1\67\1\15\2\67\1\15"+
+ "\1\67\15\27\1\70\1\15\1\70\1\31\1\15\1\31"+
+ "\14\27\1\0\3\15\1\32\1\15\1\32\14\0\2\33"+
+ "\1\0\2\33\1\0\12\33\2\0\2\33\1\71\1\15"+
+ "\2\71\1\15\1\71\11\33\2\0\2\33\1\40\1\15"+
+ "\1\40\1\35\1\15\1\35\11\33\2\0\1\33\1\0"+
+ "\3\15\1\36\1\15\1\36\14\0\1\33\1\40\1\15"+
+ "\2\40\1\15\1\40\11\33\2\0\1\33\4\0\1\41"+
+ "\1\0\1\41\15\0\3\15\1\42\1\15\1\42\15\0"+
+ "\1\15\1\42\1\15\1\42\1\15\1\42\14\0\2\44"+
+ "\4\0\12\44\3\0\1\44\1\45\4\15\1\45\11\44"+
+ "\3\0\2\51\4\0\12\51\3\0\1\51\1\52\4\15"+
+ "\1\52\11\51\7\0\1\55\1\0\1\55\15\0\3\15"+
+ "\1\56\1\15\1\56\15\0\1\15\1\56\1\15\1\56"+
+ "\1\15\1\56\14\0\2\60\4\0\12\60\3\0\1\60"+
+ "\1\61\4\15\1\61\11\60\7\0\1\63\1\0\1\63"+
+ "\15\0\3\15\1\64\1\15\1\64\15\0\1\15\1\64"+
+ "\1\15\1\64\1\15\1\64\14\0\1\27\1\70\1\15"+
+ "\2\70\1\15\1\70\14\27";
+
+ private static int [] zzUnpackTrans() {
+ int [] result = new int[798];
+ int offset = 0;
+ offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
+ return result;
+ }
+
+ private static int zzUnpackTrans(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int count = packed.charAt(i++);
+ int value = packed.charAt(i++);
+ value--;
+ do result[j++] = value; while (--count > 0);
+ }
+ return j;
+ }
+
+
+ /* error codes */
+ private static final int ZZ_UNKNOWN_ERROR = 0;
+ private static final int ZZ_NO_MATCH = 1;
+ private static final int ZZ_PUSHBACK_2BIG = 2;
+
+ /* error messages for the codes above */
+ private static final String[] ZZ_ERROR_MSG = {
+ "Unknown internal scanner error",
+ "Error: could not match input",
+ "Error: pushback value was too large"
+ };
+
+ /**
+ * ZZ_ATTRIBUTE[aState] contains the attributes of state aState
+ */
+ private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
+
+ private static final String ZZ_ATTRIBUTE_PACKED_0 =
+ "\13\0\1\11\2\1\1\11\1\1\2\11\1\1\1\11"+
+ "\1\1\1\11\10\1\1\11\6\1\3\11\2\1\2\11"+
+ "\5\1\1\11\7\1";
+
+ private static int [] zzUnpackAttribute() {
+ int [] result = new int[57];
+ int offset = 0;
+ offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
+ return result;
+ }
+
+ private static int zzUnpackAttribute(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int count = packed.charAt(i++);
+ int value = packed.charAt(i++);
+ do result[j++] = value; while (--count > 0);
+ }
+ return j;
+ }
+
+ /** the input device */
+ private final java.io.Reader zzReader;
+
+ /** the current state of the DFA */
+ private int zzState;
+
+ /** the current lexical state */
+ private int zzLexicalState = YYINITIAL;
+
+ /** this buffer contains the current text to be matched and is
+ the source of the yytext() string */
+ private CharSequence zzBuffer = "";
+
+ /** the textposition at the last accepting state */
+ private int zzMarkedPos;
+
+ /** the current text position in the buffer */
+ private int zzCurrentPos;
+
+ /** startRead marks the beginning of the yytext() string in the buffer */
+ private int zzStartRead;
+
+ /** endRead marks the last character in the buffer, that has been read
+ from input */
+ private int zzEndRead;
+
+ /**
+ * zzAtBOL == true <=> the scanner is currently at the beginning of a line
+ */
+ private boolean zzAtBOL = true;
+
+ /** zzAtEOF == true <=> the scanner is at the EOF */
+ private boolean zzAtEOF;
+
+ /** denotes if the user-EOF-code has already been executed */
+ private boolean zzEOFDone;
+
+
+ /**
+ * Creates a new scanner
+ *
+ * @param in the java.io.Reader to read input from.
+ */
+ CptLexer(java.io.Reader in) {
+ this.zzReader = in;
+ }
+
+
+ /**
+ * Unpacks the compressed character translation table.
+ *
+ * @param packed the packed character translation table
+ * @return the unpacked character translation table
+ */
+ private static char [] zzUnpackCMap(String packed) {
+ int size = 0;
+ for (int i = 0, length = packed.length(); i < length; i += 2) {
+ size += packed.charAt(i);
+ }
+ char[] map = new char[size];
+ int i = 0; /* index in packed string */
+ int j = 0; /* index in unpacked array */
+ while (i < packed.length()) {
+ int count = packed.charAt(i++);
+ char value = packed.charAt(i++);
+ do map[j++] = value; while (--count > 0);
+ }
+ return map;
+ }
+
+ public final int getTokenStart() {
+ return zzStartRead;
+ }
+
+ public final int getTokenEnd() {
+ return getTokenStart() + yylength();
+ }
+
+ public void reset(CharSequence buffer, int start, int end, int initialState) {
+ zzBuffer = buffer;
+ zzCurrentPos = zzMarkedPos = zzStartRead = start;
+ zzAtEOF = false;
+ zzAtBOL = true;
+ zzEndRead = end;
+ yybegin(initialState);
+ }
+
+ /**
+ * Refills the input buffer.
+ *
+ * @return false
, iff there was new input.
+ *
+ * @exception java.io.IOException if any I/O-Error occurs
+ */
+ private boolean zzRefill() throws java.io.IOException {
+ return true;
+ }
+
+
+ /**
+ * Returns the current lexical state.
+ */
+ public final int yystate() {
+ return zzLexicalState;
+ }
+
+
+ /**
+ * Enters a new lexical state
+ *
+ * @param newState the new lexical state
+ */
+ public final void yybegin(int newState) {
+ zzLexicalState = newState;
+ }
+
+
+ /**
+ * Returns the text matched by the current regular expression.
+ */
+ public final CharSequence yytext() {
+ return zzBuffer.subSequence(zzStartRead, zzMarkedPos);
+ }
+
+
+ /**
+ * Returns the character at position pos from the
+ * matched text.
+ *
+ * It is equivalent to yytext().charAt(pos), but faster
+ *
+ * @param pos the position of the character to fetch.
+ * A value from 0 to yylength()-1.
+ *
+ * @return the character at position pos
+ */
+ public final char yycharat(int pos) {
+ return zzBuffer.charAt(zzStartRead+pos);
+ }
+
+
+ /**
+ * Returns the length of the matched text region.
+ */
+ public final int yylength() {
+ return zzMarkedPos-zzStartRead;
+ }
+
+
+ /**
+ * Reports an error that occured while scanning.
+ *
+ * In a wellformed scanner (no or only correct usage of
+ * yypushback(int) and a match-all fallback rule) this method
+ * will only be called with things that "Can't Possibly Happen".
+ * If this method is called, something is seriously wrong
+ * (e.g. a JFlex bug producing a faulty scanner etc.).
+ *
+ * Usual syntax/scanner level error handling should be done
+ * in error fallback rules.
+ *
+ * @param errorCode the code of the errormessage to display
+ */
+ private void zzScanError(int errorCode) {
+ String message;
+ try {
+ message = ZZ_ERROR_MSG[errorCode];
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
+ }
+
+ throw new Error(message);
+ }
+
+
+ /**
+ * Pushes the specified amount of characters back into the input stream.
+ *
+ * They will be read again by then next call of the scanning method
+ *
+ * @param number the number of characters to be read again.
+ * This number must not be greater than yylength()!
+ */
+ public void yypushback(int number) {
+ if ( number > yylength() )
+ zzScanError(ZZ_PUSHBACK_2BIG);
+
+ zzMarkedPos -= number;
+ }
+
+
+ /**
+ * Contains user EOF-code, which will be executed exactly once,
+ * when the end of file is reached
+ */
+ private void zzDoEOF() {
+ if (!zzEOFDone) {
+ zzEOFDone = true;
+
+ }
+ }
+
+
+ /**
+ * Resumes scanning until the next regular expression is matched,
+ * the end of input is encountered or an I/O-Error occurs.
+ *
+ * @return the next token
+ * @exception java.io.IOException if any I/O-Error occurs
+ */
+ public IElementType advance() throws java.io.IOException {
+ int zzInput;
+ int zzAction;
+
+ // cached fields:
+ int zzCurrentPosL;
+ int zzMarkedPosL;
+ int zzEndReadL = zzEndRead;
+ CharSequence zzBufferL = zzBuffer;
+
+ int [] zzTransL = ZZ_TRANS;
+ int [] zzRowMapL = ZZ_ROWMAP;
+ int [] zzAttrL = ZZ_ATTRIBUTE;
+
+ while (true) {
+ zzMarkedPosL = zzMarkedPos;
+
+ zzAction = -1;
+
+ zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
+
+ zzState = ZZ_LEXSTATE[zzLexicalState];
+
+ // set up zzAction for empty match case:
+ int zzAttributes = zzAttrL[zzState];
+ if ( (zzAttributes & 1) == 1 ) {
+ zzAction = zzState;
+ }
+
+
+ zzForAction: {
+ while (true) {
+
+ if (zzCurrentPosL < zzEndReadL) {
+ zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/);
+ zzCurrentPosL += Character.charCount(zzInput);
+ }
+ else if (zzAtEOF) {
+ zzInput = YYEOF;
+ break zzForAction;
+ }
+ else {
+ // store back cached positions
+ zzCurrentPos = zzCurrentPosL;
+ zzMarkedPos = zzMarkedPosL;
+ boolean eof = zzRefill();
+ // get translated positions and possibly new buffer
+ zzCurrentPosL = zzCurrentPos;
+ zzMarkedPosL = zzMarkedPos;
+ zzBufferL = zzBuffer;
+ zzEndReadL = zzEndRead;
+ if (eof) {
+ zzInput = YYEOF;
+ break zzForAction;
+ }
+ else {
+ zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/);
+ zzCurrentPosL += Character.charCount(zzInput);
+ }
+ }
+ int zzNext = zzTransL[ zzRowMapL[zzState] + ZZ_CMAP(zzInput) ];
+ if (zzNext == -1) break zzForAction;
+ zzState = zzNext;
+
+ zzAttributes = zzAttrL[zzState];
+ if ( (zzAttributes & 1) == 1 ) {
+ zzAction = zzState;
+ zzMarkedPosL = zzCurrentPosL;
+ if ( (zzAttributes & 8) == 8 ) break zzForAction;
+ }
+
+ }
+ }
+
+ // store back cached position
+ zzMarkedPos = zzMarkedPosL;
+
+ if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
+ zzAtEOF = true;
+ zzDoEOF();
+ return null;
+ }
+ else {
+ switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
+ case 1:
+ { return TokenType.BAD_CHARACTER;
+ }
+ case 25: break;
+ case 2:
+ { yybegin(YYINITIAL); return TokenType.WHITE_SPACE;
+ }
+ case 26: break;
+ case 3:
+ { yybegin(YYINITIAL); return CptTypes.COMMENT;
+ }
+ case 27: break;
+ case 4:
+ { yybegin(WAITING_DESCRIPTION); return CptTypes.SEPARATOR;
+ }
+ case 28: break;
+ case 5:
+ { yybegin(YYINITIAL); return CptTypes.CLASS_NAME;
+ }
+ case 29: break;
+ case 6:
+ { yybegin(YYINITIAL); return CptTypes.BRACKET_OPEN;
+ }
+ case 30: break;
+ case 7:
+ { yybegin(YYINITIAL); return CptTypes.BRACKET_CLOSE;
+ }
+ case 31: break;
+ case 8:
+ { yybegin(WAITING_TEMPLATE_CODE); return CptTypes.MAP;
+ }
+ case 32: break;
+ case 9:
+ { yybegin(WAITING_TEMPLATE_VAR_NAME); return CptTypes.TEMPLATE_VARIABLE_START;
+ }
+ case 33: break;
+ case 10:
+ { yybegin(YYINITIAL); return CptTypes.TEMPLATE_DESCRIPTION;
+ }
+ case 34: break;
+ case 11:
+ { yybegin(WAITING_DESCRIPTION); return TokenType.WHITE_SPACE;
+ }
+ case 35: break;
+ case 12:
+ { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_CODE;
+ }
+ case 36: break;
+ case 13:
+ { yybegin(WAITING_TEMPLATE_CODE_CON); return TokenType.WHITE_SPACE;
+ }
+ case 37: break;
+ case 14:
+ { yybegin(WAITING_TEMPLATE_ESC); return CptTypes.TEMPLATE_ESCAPE;
+ }
+ case 38: break;
+ case 15:
+ { yybegin(WAITING_TEMPLATE_VAR_NAME_NEXT); return CptTypes.TEMPLATE_VARIABLE_NAME;
+ }
+ case 39: break;
+ case 16:
+ { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_ESCAPE;
+ }
+ case 40: break;
+ case 17:
+ { yybegin(WAITING_TEMPLATE_CODE_CON); return CptTypes.TEMPLATE_VARIABLE_END;
+ }
+ case 41: break;
+ case 18:
+ { yybegin(WAITING_TEMPLATE_VAR_EXPRESSION); return CptTypes.TEMPLATE_VARIABLE_SEPARATOR;
+ }
+ case 42: break;
+ case 19:
+ { yybegin(WAITING_TEMPLATE_VAR_EXPRESSION); return CptTypes.TEMPLATE_VARIABLE_EXPRESSION;
+ }
+ case 43: break;
+ case 20:
+ { yybegin(WAITING_TEMPLATE_VAR_EXPRESSION_ESC); return CptTypes.TEMPLATE_VARIABLE_EXPRESSION;
+ }
+ case 44: break;
+ case 21:
+ { yybegin(WAITING_TEMPLATE_VAR_VALUE); return CptTypes.TEMPLATE_VARIABLE_SEPARATOR;
+ }
+ case 45: break;
+ case 22:
+ { yybegin(WAITING_TEMPLATE_VAR_VALUE); return CptTypes.TEMPLATE_VARIABLE_VALUE;
+ }
+ case 46: break;
+ case 23:
+ { yybegin(WAITING_TEMPLATE_VAR_VALUE_ESC); return CptTypes.TEMPLATE_VARIABLE_VALUE;
+ }
+ case 47: break;
+ case 24:
+ { yybegin(YYINITIAL); return CptTypes.TEMPLATE_NAME;
+ }
+ case 48: break;
+ default:
+ zzScanError(ZZ_NO_MATCH);
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptLexerAdapter.java b/src/de/endrullis/idea/postfixtemplates/language/CptLexerAdapter.java
new file mode 100644
index 00000000..6e824ea3
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptLexerAdapter.java
@@ -0,0 +1,11 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lexer.FlexAdapter;
+
+public class CptLexerAdapter extends FlexAdapter {
+
+ public CptLexerAdapter() {
+ super(new CptLexer(null));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptLookupActionProvider.java b/src/de/endrullis/idea/postfixtemplates/language/CptLookupActionProvider.java
new file mode 100644
index 00000000..0a94be58
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptLookupActionProvider.java
@@ -0,0 +1,45 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.codeInsight.lookup.Lookup;
+import com.intellij.codeInsight.lookup.LookupActionProvider;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementAction;
+import com.intellij.codeInsight.template.postfix.completion.PostfixTemplateLookupElement;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplate;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.pom.Navigatable;
+import com.intellij.util.Consumer;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * This {@link LookupActionProvider} allows you to jump directly to a postfix template definition.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class CptLookupActionProvider implements LookupActionProvider {
+
+ @Override
+ public void fillActions(@NotNull LookupElement element, @NotNull Lookup lookup, @NotNull Consumer super @NotNull LookupElementAction> consumer) {
+ if (element instanceof final PostfixTemplateLookupElement templateLookupElement) {
+ final PostfixTemplate template = templateLookupElement.getPostfixTemplate();
+
+ if (template instanceof Navigatable && ((Navigatable) template).canNavigate()) {
+ consumer.consume(new LookupElementAction(PlatformIcons.EDIT, "Edit '" + template.getKey() + "' template") {
+ @Override
+ public Result performLookupAction() {
+ final Project project = lookup.getProject();
+ ApplicationManager.getApplication().invokeLater(() -> {
+ if (project.isDisposed()) return;
+
+ ((Navigatable) template).navigate(true);
+ });
+ return Result.HIDE_LOOKUP;
+ }
+ });
+ }
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptParserDefinition.java b/src/de/endrullis/idea/postfixtemplates/language/CptParserDefinition.java
new file mode 100644
index 00000000..4c2e8b3f
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptParserDefinition.java
@@ -0,0 +1,65 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.ParserDefinition;
+import com.intellij.lang.PsiParser;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.TokenType;
+import com.intellij.psi.tree.IFileElementType;
+import com.intellij.psi.tree.TokenSet;
+import de.endrullis.idea.postfixtemplates.language.parser.CptParser;
+import de.endrullis.idea.postfixtemplates.language.psi.CptFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+import org.jetbrains.annotations.NotNull;
+
+public class CptParserDefinition implements ParserDefinition {
+ public static final TokenSet WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE);
+ public static final TokenSet COMMENTS = TokenSet.create(CptTypes.COMMENT);
+
+ public static final IFileElementType FILE = new IFileElementType(CptLanguage.INSTANCE);
+
+ @NotNull
+ @Override
+ public Lexer createLexer(Project project) {
+ return new CptLexerAdapter();
+ }
+
+ @NotNull
+ @Override
+ public TokenSet getWhitespaceTokens() {
+ return WHITE_SPACES;
+ }
+
+ @NotNull
+ public TokenSet getCommentTokens() {
+ return COMMENTS;
+ }
+
+ @NotNull
+ public TokenSet getStringLiteralElements() {
+ return TokenSet.EMPTY;
+ }
+
+ @NotNull
+ public PsiParser createParser(final Project project) {
+ return new CptParser();
+ }
+
+ @Override
+ public @NotNull IFileElementType getFileNodeType() {
+ return FILE;
+ }
+
+ public @NotNull PsiFile createFile(@NotNull FileViewProvider viewProvider) {
+ return new CptFile(viewProvider);
+ }
+
+ @NotNull
+ public PsiElement createElement(ASTNode node) {
+ return CptTypes.Factory.createElement(node);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptReference.java b/src/de/endrullis/idea/postfixtemplates/language/CptReference.java
new file mode 100644
index 00000000..3a990c10
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptReference.java
@@ -0,0 +1,58 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.*;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CptReference extends PsiReferenceBase implements PsiPolyVariantReference {
+ private final String key;
+
+ public CptReference(@NotNull PsiElement element, TextRange textRange) {
+ super(element, textRange);
+ key = element.getText().substring(textRange.getStartOffset(), textRange.getEndOffset());
+ }
+
+ @NotNull
+ @Override
+ public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
+ Project project = myElement.getProject();
+ final List properties = CptUtil.findMappings(project, key);
+ List results = new ArrayList<>();
+ for (CptMapping property : properties) {
+ results.add(new PsiElementResolveResult(property));
+ }
+ return results.toArray(new ResolveResult[0]);
+ }
+
+ @Nullable
+ @Override
+ public PsiElement resolve() {
+ ResolveResult[] resolveResults = multiResolve(false);
+ return resolveResults.length == 1 ? resolveResults[0].getElement() : null;
+ }
+
+ @NotNull
+ @Override
+ public Object @NotNull [] getVariants() {
+ Project project = myElement.getProject();
+ List properties = CptUtil.findMappings(project);
+ List variants = new ArrayList<>();
+ for (final CptMapping property : properties) {
+ if (property.getMatchingClassName() != null && !property.getMatchingClassName().isEmpty()) {
+ variants.add(LookupElementBuilder.create(property).
+ withIcon(CptIcons.FILE).
+ withTypeText(property.getContainingFile().getName())
+ );
+ }
+ }
+ return variants.toArray();
+ }
+}
\ No newline at end of file
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptReferenceContributor.java b/src/de/endrullis/idea/postfixtemplates/language/CptReferenceContributor.java
new file mode 100644
index 00000000..868eb055
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptReferenceContributor.java
@@ -0,0 +1,31 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.openapi.util.TextRange;
+import com.intellij.patterns.PlatformPatterns;
+import com.intellij.psi.*;
+import com.intellij.util.ProcessingContext;
+import org.jetbrains.annotations.NotNull;
+
+public class CptReferenceContributor extends PsiReferenceContributor {
+ @Override
+ public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
+ // TODO
+ registrar.registerReferenceProvider(PlatformPatterns.psiElement(PsiLiteralExpression.class),
+ new PsiReferenceProvider() {
+ @NotNull
+ @Override
+ public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element,
+ @NotNull ProcessingContext
+ context) {
+ PsiLiteralExpression literalExpression = (PsiLiteralExpression) element;
+ String value = literalExpression.getValue() instanceof String ?
+ (String) literalExpression.getValue() : null;
+ if (value != null && value.startsWith("simple" + ":")) {
+ return new PsiReference[]{
+ new CptReference(element, new TextRange(8, value.length() + 1))};
+ }
+ return PsiReference.EMPTY_ARRAY;
+ }
+ });
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewElement.java b/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewElement.java
new file mode 100644
index 00000000..ec4a7750
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewElement.java
@@ -0,0 +1,74 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.ide.structureView.StructureViewTreeElement;
+import com.intellij.ide.util.treeView.smartTree.SortableTreeElement;
+import com.intellij.ide.util.treeView.smartTree.TreeElement;
+import com.intellij.navigation.ItemPresentation;
+import com.intellij.navigation.NavigationItem;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import de.endrullis.idea.postfixtemplates.language.psi.CptFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTemplate;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class CptStructureViewElement implements StructureViewTreeElement, SortableTreeElement {
+ private final PsiElement element;
+
+ public CptStructureViewElement(PsiElement element) {
+ this.element = element;
+ }
+
+ @Override
+ public Object getValue() {
+ return element;
+ }
+
+ @Override
+ public void navigate(boolean requestFocus) {
+ if (element instanceof NavigationItem) {
+ ((NavigationItem) element).navigate(requestFocus);
+ }
+ }
+
+ @Override
+ public boolean canNavigate() {
+ return element instanceof NavigationItem &&
+ ((NavigationItem) element).canNavigate();
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return element instanceof NavigationItem &&
+ ((NavigationItem) element).canNavigateToSource();
+ }
+
+ @Override
+ public @NotNull String getAlphaSortKey() {
+ return Objects.requireNonNull(element instanceof PsiNamedElement ? ((PsiNamedElement) element).getName() : null);
+ }
+
+ @Override
+ public @NotNull ItemPresentation getPresentation() {
+ return Objects.requireNonNull(element instanceof NavigationItem ? ((NavigationItem) element).getPresentation() : null);
+ }
+
+ @Override
+ public TreeElement @NotNull [] getChildren() {
+ if (element instanceof CptFile) {
+ CptTemplate[] templates = PsiTreeUtil.getChildrenOfType(element, CptTemplate.class);
+ assert templates != null;
+ List treeElements = new ArrayList<>(templates.length);
+ for (CptTemplate template : templates) {
+ treeElements.add(new CptStructureViewElement(template));
+ }
+ return treeElements.toArray(new TreeElement[0]);
+ } else {
+ return EMPTY_ARRAY;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewFactory.java b/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewFactory.java
new file mode 100644
index 00000000..e1cbf385
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewFactory.java
@@ -0,0 +1,24 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.ide.structureView.StructureViewBuilder;
+import com.intellij.ide.structureView.StructureViewModel;
+import com.intellij.ide.structureView.TreeBasedStructureViewBuilder;
+import com.intellij.lang.PsiStructureViewFactory;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class CptStructureViewFactory implements PsiStructureViewFactory {
+ @Nullable
+ @Override
+ public StructureViewBuilder getStructureViewBuilder(final @NotNull PsiFile psiFile) {
+ return new TreeBasedStructureViewBuilder() {
+ @NotNull
+ @Override
+ public StructureViewModel createStructureViewModel(@Nullable Editor editor) {
+ return new CptStructureViewModel(psiFile);
+ }
+ };
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewModel.java b/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewModel.java
new file mode 100644
index 00000000..874d0b56
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptStructureViewModel.java
@@ -0,0 +1,33 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.ide.structureView.StructureViewModel;
+import com.intellij.ide.structureView.StructureViewModelBase;
+import com.intellij.ide.structureView.StructureViewTreeElement;
+import com.intellij.ide.util.treeView.smartTree.Sorter;
+import com.intellij.psi.PsiFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptFile;
+import org.jetbrains.annotations.NotNull;
+
+public class CptStructureViewModel extends StructureViewModelBase implements
+ StructureViewModel.ElementInfoProvider {
+ public CptStructureViewModel(PsiFile psiFile) {
+ super(psiFile, new CptStructureViewElement(psiFile));
+ }
+
+ @NotNull
+ @Override
+ public Sorter @NotNull [] getSorters() {
+ return new Sorter[]{Sorter.ALPHA_SORTER};
+ }
+
+
+ @Override
+ public boolean isAlwaysShowsPlus(StructureViewTreeElement element) {
+ return false;
+ }
+
+ @Override
+ public boolean isAlwaysLeaf(StructureViewTreeElement element) {
+ return element instanceof CptFile;
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptSyntaxHighlighter.java b/src/de/endrullis/idea/postfixtemplates/language/CptSyntaxHighlighter.java
new file mode 100644
index 00000000..6041a7af
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptSyntaxHighlighter.java
@@ -0,0 +1,82 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.codeInsight.template.impl.TemplateColors;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
+import com.intellij.openapi.editor.HighlighterColors;
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase;
+import com.intellij.psi.TokenType;
+import com.intellij.psi.tree.IElementType;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTypes;
+import org.jetbrains.annotations.NotNull;
+
+import static com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey;
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._Set;
+
+public class CptSyntaxHighlighter extends SyntaxHighlighterBase {
+ public static final TextAttributesKey SEPARATOR =
+ createTextAttributesKey("CPT_SEPARATOR", DefaultLanguageHighlighterColors.OPERATION_SIGN);
+ public static final TextAttributesKey TEMPLATE_NAME =
+ createTextAttributesKey("CPT_TEMPLATE_NAME", DefaultLanguageHighlighterColors.KEYWORD);
+ public static final TextAttributesKey CLASS_NAME =
+ createTextAttributesKey("CPT_CLASS_NAME", DefaultLanguageHighlighterColors.CLASS_NAME);
+ public static final TextAttributesKey TEMPLATE_DESCRIPTION =
+ createTextAttributesKey("CPT_TEMPLATE_DESCRIPTION", DefaultLanguageHighlighterColors.STRING);
+ public static final TextAttributesKey TEMPLATE_CODE =
+ createTextAttributesKey("CPT_TEMPLATE_CODE", DefaultLanguageHighlighterColors.TEMPLATE_LANGUAGE_COLOR);
+ public static final TextAttributesKey TEMPLATE_ESCAPE =
+ createTextAttributesKey("CPT_TEMPLATE_ESCAPE", DefaultLanguageHighlighterColors.METADATA);
+ public static final TextAttributesKey TEMPLATE_VARIABLE_HOLDER =
+ createTextAttributesKey("CPT_PLACE_HOLDER", TemplateColors.TEMPLATE_VARIABLE_ATTRIBUTES);
+ public static final TextAttributesKey COMMENT =
+ createTextAttributesKey("CPT_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT);
+ public static final TextAttributesKey BAD_CHARACTER =
+ createTextAttributesKey("CPT_BAD_CHARACTER", HighlighterColors.BAD_CHARACTER);
+
+ private static final TextAttributesKey[] BAD_CHAR_KEYS = new TextAttributesKey[]{BAD_CHARACTER};
+ private static final TextAttributesKey[] SEPARATOR_KEYS = new TextAttributesKey[]{SEPARATOR};
+ private static final TextAttributesKey[] TEMPLATE_NAME_KEYS = new TextAttributesKey[]{TEMPLATE_NAME};
+ private static final TextAttributesKey[] CLASS_NAME_KEYS = new TextAttributesKey[]{CLASS_NAME};
+ private static final TextAttributesKey[] TEMPLATE_DESCRIPTION_KEYS = new TextAttributesKey[]{TEMPLATE_DESCRIPTION};
+ private static final TextAttributesKey[] TEMPLATE_CODE_KEYS = new TextAttributesKey[]{TEMPLATE_CODE};
+ private static final TextAttributesKey[] TEMPLATE_ESCAPE_KEYS = new TextAttributesKey[]{TEMPLATE_ESCAPE};
+ private static final TextAttributesKey[] TEMPLATE_VARIABLE_KEYS = new TextAttributesKey[]{TEMPLATE_CODE, TEMPLATE_VARIABLE_HOLDER};
+ private static final TextAttributesKey[] COMMENT_KEYS = new TextAttributesKey[]{COMMENT};
+ private static final TextAttributesKey[] EMPTY_KEYS = new TextAttributesKey[0];
+ private static final java.util.Set TEMPLATE_VARIABLE_PARTS = _Set(CptTypes.TEMPLATE_VARIABLE_START,
+ CptTypes.TEMPLATE_VARIABLE_END, CptTypes.TEMPLATE_VARIABLE_NAME, CptTypes.TEMPLATE_VARIABLE_EXPRESSION,
+ CptTypes.TEMPLATE_VARIABLE_VALUE, CptTypes.TEMPLATE_VARIABLE_SEPARATOR);
+
+ @NotNull
+ @Override
+ public Lexer getHighlightingLexer() {
+ return new CptLexerAdapter();
+ }
+
+ @NotNull
+ @Override
+ public TextAttributesKey @NotNull [] getTokenHighlights(IElementType tokenType) {
+ if (tokenType.equals(CptTypes.SEPARATOR) || tokenType.equals(CptTypes.MAP)) {
+ return SEPARATOR_KEYS;
+ } else if (tokenType.equals(CptTypes.TEMPLATE_NAME)) {
+ return TEMPLATE_NAME_KEYS;
+ } else if (tokenType.equals(CptTypes.CLASS_NAME)) {
+ return CLASS_NAME_KEYS;
+ } else if (tokenType.equals(CptTypes.TEMPLATE_DESCRIPTION)) {
+ return TEMPLATE_DESCRIPTION_KEYS;
+ } else if (tokenType.equals(CptTypes.TEMPLATE_CODE)) {
+ return TEMPLATE_CODE_KEYS;
+ } else if (TEMPLATE_VARIABLE_PARTS.contains(tokenType)) {
+ return TEMPLATE_VARIABLE_KEYS;
+ } else if (tokenType.equals(CptTypes.TEMPLATE_ESCAPE)) {
+ return TEMPLATE_ESCAPE_KEYS;
+ } else if (tokenType.equals(CptTypes.COMMENT)) {
+ return COMMENT_KEYS;
+ } else if (tokenType.equals(TokenType.BAD_CHARACTER)) {
+ return BAD_CHAR_KEYS;
+ } else {
+ return EMPTY_KEYS;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptSyntaxHighlighterFactory.java b/src/de/endrullis/idea/postfixtemplates/language/CptSyntaxHighlighterFactory.java
new file mode 100644
index 00000000..46b8e057
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptSyntaxHighlighterFactory.java
@@ -0,0 +1,15 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+public class CptSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
+ @NotNull
+ @Override
+ public SyntaxHighlighter getSyntaxHighlighter(Project project, VirtualFile virtualFile) {
+ return new CptSyntaxHighlighter();
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptTodoIndexer.java b/src/de/endrullis/idea/postfixtemplates/language/CptTodoIndexer.java
new file mode 100644
index 00000000..3d35266d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptTodoIndexer.java
@@ -0,0 +1,13 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.lexer.Lexer;
+import com.intellij.psi.impl.cache.impl.OccurrenceConsumer;
+import com.intellij.psi.impl.cache.impl.todo.LexerBasedTodoIndexer;
+import org.jetbrains.annotations.NotNull;
+
+public class CptTodoIndexer extends LexerBasedTodoIndexer {
+ @Override
+ public @NotNull Lexer createLexer(@NotNull OccurrenceConsumer consumer) {
+ return CptIndexer.createIndexingLexer(consumer);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/CptUtil.java b/src/de/endrullis/idea/postfixtemplates/language/CptUtil.java
new file mode 100644
index 00000000..d74b558c
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/CptUtil.java
@@ -0,0 +1,542 @@
+package de.endrullis.idea.postfixtemplates.language;
+
+import com.intellij.ide.DataManager;
+import com.intellij.ide.plugins.PluginManagerCore;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.options.ShowSettingsUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.search.FileTypeIndex;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.testFramework.LightVirtualFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.language.psi.CptTemplate;
+import de.endrullis.idea.postfixtemplates.languages.SupportedLanguages;
+import de.endrullis.idea.postfixtemplates.settings.*;
+import de.endrullis.idea.postfixtemplates.utils.MyNotifier;
+import de.endrullis.idea.postfixtemplates.utils.Tuple2;
+import lombok.val;
+import org.apache.commons.io.FileUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.*;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._List;
+import static de.endrullis.idea.postfixtemplates.utils.StringUtils.replace;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
+@SuppressWarnings("WeakerAccess")
+public class CptUtil {
+ public static final String PLUGIN_ID = "de.endrullis.idea.postfixtemplates";
+
+ private static String templatesPathString = null;
+
+ public static Project findProject(PsiElement element) {
+ PsiFile containingFile = element.getContainingFile();
+
+ if (!Objects.requireNonNullElse(containingFile, element).isValid()) {
+ return null;
+ }
+
+ return (containingFile == null ? element : containingFile).getProject();
+ }
+
+ public static Project findProject(@NotNull Component component) {
+ return CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(component));
+ }
+
+ public static List findMappings(Project project, String key) {
+ List result = null;
+
+ val virtualFiles = FileTypeIndex.getFiles(CptFileType.INSTANCE, GlobalSearchScope.allScope(project));
+
+ /*
+ Collection virtualFiles =
+ FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, CptFileType.INSTANCE,
+ GlobalSearchScope.allScope(project));
+ */
+
+ for (VirtualFile virtualFile : virtualFiles) {
+ CptFile cptFile = (CptFile) PsiManager.getInstance(project).findFile(virtualFile);
+
+ if (cptFile != null) {
+ CptMapping[] mappings = PsiTreeUtil.getChildrenOfType(cptFile, CptMapping.class);
+ if (mappings != null) {
+ for (CptMapping mapping : mappings) {
+ if (key.equals(mapping.getMatchingClassName())) {
+ if (result == null) {
+ result = new ArrayList<>();
+ }
+ result.add(mapping);
+ }
+ }
+ }
+ }
+ }
+ return result != null ? result : Collections.emptyList();
+ }
+
+ public static List findMappings(Project project) {
+ List result = new ArrayList<>();
+
+ val virtualFiles = FileTypeIndex.getFiles(CptFileType.INSTANCE, GlobalSearchScope.allScope(project));
+
+ /*
+ Collection virtualFiles =
+ FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, CptFileType.INSTANCE,
+ GlobalSearchScope.allScope(project));
+ */
+
+ for (VirtualFile virtualFile : virtualFiles) {
+ CptFile cptFile = (CptFile) PsiManager.getInstance(project).findFile(virtualFile);
+ if (cptFile != null) {
+ CptMapping[] mappings = PsiTreeUtil.getChildrenOfType(cptFile, CptMapping.class);
+ if (mappings != null) {
+ Collections.addAll(result, mappings);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the predefined plugin templates for the given language.
+ *
+ * @param language programming language
+ * @return predefined plugin templates for the given language
+ */
+ public static String getDefaultTemplates(String language) {
+ try (InputStream stream = CptUtil.class.getResourceAsStream("defaulttemplates/" + language + ".postfixTemplates")) {
+ assert stream != null;
+ return getContent(stream);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ /**
+ * Returns the content of the given file.
+ *
+ * @param file file
+ * @return content of the given file
+ */
+ @NotNull
+ public static String getContent(@NotNull File file) {
+ try {
+ return getContent(new FileInputStream(file));
+ } catch (IOException e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ /**
+ * Returns the content of the given input stream.
+ *
+ * @param stream input stream
+ * @return content of the given input stream
+ */
+ private static String getContent(@NotNull InputStream stream) throws IOException {
+ StringBuilder sb = new StringBuilder();
+
+ // convert system newlines into UNIX newlines, because IDEA works only with UNIX newlines
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = in.readLine()) != null) {
+ sb.append(line).append("\n");
+ }
+
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Returns the path of the CPT plugin settings directory.
+ *
+ * @return path of the CPT plugin settings directory
+ */
+ public static File getPluginPath() {
+ File path = Objects.requireNonNull(PluginManagerCore.getPlugin(PluginId.getId(CptUtil.PLUGIN_ID))).getPluginPath().toFile();
+
+ if (path.getName().endsWith(".jar")) {
+ path = new File(path.getParentFile(), path.getName().substring(0, path.getName().length() - 4));
+ }
+
+ return new File(path.getParentFile(), path.getName() + "_templates");
+ }
+
+ /**
+ * Returns the path "$CPT_PLUGIN_SETTINGS/templates".
+ *
+ * @return path "$CPT_PLUGIN_SETTINGS/templates"
+ */
+ public static File getTemplatesPath() {
+ val templates = new File(getPluginPath(), "templates");
+
+ if (!templates.exists()) {
+ templates.mkdirs();
+ }
+
+ return templates;
+ }
+
+ public static String getTemplatesPathString() {
+ if (templatesPathString != null) {
+ templatesPathString = getTemplatesPath().getAbsolutePath().replace('\\', '/');
+ }
+
+ return templatesPathString;
+ }
+
+ public static void createTemplateFile(@NotNull String language, @NotNull String filename, @NotNull String content) {
+ File file = new File(CptUtil.getTemplatesPath() + "/" + language, filename + ".postfixTemplates");
+
+ if (file.exists()) return;
+
+ //noinspection ResultOfMethodCallIgnored
+ file.getParentFile().mkdirs();
+
+ try {
+ FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException("Template file " + file.getAbsolutePath() + " could not be written", e);
+ }
+ }
+
+ public static void moveOldTemplateFile(@NotNull String language) {
+ if (SupportedLanguages.supportedLanguageIds.contains(language.toLowerCase())) {
+ File file = new File(CptUtil.getTemplatesPath(), language + ".postfixTemplates");
+
+ if (file.exists()) {
+ // move file to new position
+ val newFile = getTemplateFile(language, "oldUserTemplates");
+
+ file.renameTo(newFile);
+
+ LocalFileSystem.getInstance().refreshIoFiles(_List(newFile));
+ }
+ }
+ }
+
+ public static File getTemplateFile(@NotNull String language, @NotNull String fileName) {
+ val path = getTemplateDir(language);
+
+ return new File(path, fileName + ".postfixTemplates");
+ }
+
+ @NotNull
+ public static File getTemplateDir(@NotNull String language) {
+ final File dir = new File(getTemplatesPath(), language);
+
+ if (!dir.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ dir.mkdirs();
+ }
+
+ return dir;
+ }
+
+ @NotNull
+ public static File getWebTemplatesInfoFile() {
+ return new File(getTemplatesPath(), "webTemplateFiles.yaml");
+ }
+
+ public static List getTemplateFiles(@NotNull String language) {
+ return getTemplateFiles(language, f -> f.enabled);
+ }
+
+ public static List getEditableTemplateFiles(@NotNull String language) {
+ return getTemplateFiles(language, f -> f.enabled && f.url == null);
+ }
+
+ public static List getTemplateFiles(@NotNull String language, Predicate fileFilter) {
+ if (SupportedLanguages.supportedLanguageIds.contains(language.toLowerCase())) {
+ // eventually move old templates file to new directory
+ moveOldTemplateFile(language);
+
+ val filesFromDir = getTemplateFilesFromLanguageDir(language);
+
+ val settings = CptApplicationSettings.getInstance().getPluginSettings();
+
+ val vFiles = settings.getLangName2virtualFiles().getOrDefault(language, new ArrayList<>());
+ val allFilesFromConfig = vFiles.stream().map(f -> CptUtil.fixFilePath(f.file)).collect(Collectors.toSet());
+ val enabledFilesFromConfig = vFiles.stream().filter(fileFilter).map(f -> new File(f.file)).filter(f -> f.exists()).toList();
+
+ val remainingTemplateFilesFromDir = Arrays.stream(filesFromDir).filter(f -> !allFilesFromConfig.contains(CptUtil.fixFilePath(f.getAbsolutePath())));
+
+ // templateFilesFromConfig + remainingTemplateFilesFromDir
+ return Stream.concat(remainingTemplateFilesFromDir, enabledFilesFromConfig.stream()).collect(Collectors.toList());
+ } else {
+ return _List();
+ }
+ }
+
+ @NotNull
+ public static File[] getTemplateFilesFromLanguageDir(@NotNull String language) {
+ final File[] files = getTemplateDir(language).listFiles(f -> f.getName().endsWith(".postfixTemplates"));
+
+ if (files == null) {
+ return new File[0];
+ }
+
+ return files;
+ }
+
+ @Nullable
+ public static CptLang getLanguageOfTemplateFile(@NotNull VirtualFile vFile) {
+ val name = vFile.getNameWithoutExtension();
+
+ return Optional.ofNullable(
+ SupportedLanguages.getCptLang(name)
+ ).orElseGet(() -> {
+ val parent = getAbsoluteVirtualFile(vFile).getParent();
+ if (parent != null) {
+ return SupportedLanguages.getCptLang(parent.getName());
+ } else {
+ return null;
+ }
+ });
+ }
+
+ @NotNull
+ public static String getPath(@NotNull VirtualFile vFile) {
+ return fixFilePath(getAbsoluteVirtualFile(vFile).getPath());
+ }
+
+ @NotNull
+ public static String fixFilePath(@NotNull String filePath) {
+ return filePath.replace('\\', '/');
+ }
+
+ @NotNull
+ public static VirtualFile getAbsoluteVirtualFile(@NotNull VirtualFile vFile) {
+ if (vFile instanceof LightVirtualFile lvFile) {
+ val originalFile = lvFile.getOriginalFile();
+
+ if (originalFile != null) {
+ vFile = originalFile;
+ }
+ }
+
+ return vFile;
+ }
+
+ public static void openFileInEditor(@NotNull Project project, @NotNull File file) {
+ VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file);
+
+ if (vFile == null) {
+ LocalFileSystem.getInstance().refreshIoFiles(_List(file));
+ vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
+ }
+
+ assert vFile != null;
+
+ openFileInEditor(project, vFile);
+ }
+
+ public static void openFileInEditor(@NotNull Project project, @NotNull VirtualFile vFile) {
+ // open templates file in an editor
+ val openFileDescriptor = new OpenFileDescriptor(project, vFile);
+ openFileDescriptor.navigate(true);
+
+ //EditorFactory.getInstance().createViewer(document, project);
+ }
+
+ public static void openPluginSettings(Project project) {
+ ShowSettingsUtil.getInstance().showSettingsDialog(project, CptPluginConfigurable.class);
+ }
+
+ public static boolean isTemplateFile(@NotNull VirtualFile vFile, @NotNull String language) {
+ val settings = CptApplicationSettings.getInstance().getPluginSettings();
+
+ val path = getPath(vFile);
+ val lang = settings.getFile2langName().get(path);
+
+ return lang != null && lang.equals(language);
+ }
+
+ public static boolean isActiveTemplateFile(@NotNull VirtualFile vFile) {
+ val path = getPath(vFile);
+ return path.startsWith(getTemplatesPathString());
+ }
+
+ @Nullable
+ public static Tuple2 getLangAndVFile(@NotNull VirtualFile vFile) {
+ val settings = CptApplicationSettings.getInstance().getPluginSettings();
+
+ val path = getPath(vFile);
+
+ return settings.getFile2langAndVFile().get(path);
+ }
+
+ public static boolean isUserTemplateFile(@NotNull VirtualFile vFile) {
+ final Tuple2 langAndVFile = getLangAndVFile(vFile);
+
+ if (langAndVFile == null) {
+ return false;
+ }
+
+ return langAndVFile._2.isUserTemplateFile();
+ }
+
+ public static void processTemplates(Project project, VirtualFile vFile, BiConsumer action) {
+ val cptFile = (CptFile) PsiManager.getInstance(project).findFile(vFile);
+ if (cptFile != null) {
+ val cptTemplates = PsiTreeUtil.getChildrenOfType(cptFile, CptTemplate.class);
+ if (cptTemplates != null) {
+ for (CptTemplate cptTemplate : cptTemplates) {
+ for (CptMapping mapping : cptTemplate.getMappings().getMappingList()) {
+ action.accept(cptTemplate, mapping);
+ }
+ }
+ }
+ }
+ }
+
+ private static String applyReplacements(String templatesText, boolean preFilled) {
+ final String[] finalTemplatesText = new String[]{templatesText};
+
+ try (val bufferedReader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(CptUtil.class.getResourceAsStream("templatemapping/" + (preFilled ? "var" : "empty") + "Lambda.txt")), StandardCharsets.UTF_8))) {
+ bufferedReader.lines().filter(l -> l.contains("→")).map(line -> line.split("→")).forEach(split ->
+ finalTemplatesText[0] = replace(finalTemplatesText[0], split[0].trim(), split[1].trim())
+ );
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return finalTemplatesText[0];
+ }
+
+ /** Downloads/updates the web template info file. */
+ public static void downloadWebTemplatesInfoFile() throws IOException, URISyntaxException {
+ val url = new URI("https://raw.githubusercontent.com/xylo/intellij-postfix-templates/master/templates/webTemplateFiles.yaml").toURL();
+
+ val tmpFile = File.createTempFile("idea.cpt.webtemplates", null);
+
+ try (val stream = url.openStream()) {
+ val content = getContent(stream);
+
+ FileUtils.writeStringToFile(tmpFile, content, StandardCharsets.UTF_8);
+
+ Files.move(tmpFile.toPath(), getWebTemplatesInfoFile().toPath(), REPLACE_EXISTING);
+ }
+ }
+
+ /**
+ * Downloads/updates the given web template file.
+ *
+ * @return true if the file is new
+ */
+ public static boolean downloadWebTemplateFile(CptVirtualFile cptVirtualFile) throws IOException {
+ // TODO: debug code for issue #202
+ /*
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ }
+ */
+
+ val preFilled = CptApplicationSettings.getInstance().getPluginSettings().isVarLambdaStyle();
+
+ val tmpFile = File.createTempFile("idea.cpt." + cptVirtualFile.getName(), null);
+
+ try (val webStream = Objects.requireNonNull(cptVirtualFile.getUrl()).openStream()) {
+ val content = applyReplacements(getContent(webStream), preFilled);
+
+ FileUtils.writeStringToFile(tmpFile, content, StandardCharsets.UTF_8);
+
+ //Arrays.equals(Files.readAllBytes(tmpFile.toPath()), Files.readAllBytes(cptVirtualFile.getFile().toPath()));
+ boolean isNew = !cptVirtualFile.getFile().exists();
+ if (cptVirtualFile.getFile().exists()) {
+ cptVirtualFile.getFile().setWritable(true);
+ }
+ Files.move(tmpFile.toPath(), cptVirtualFile.getFile().toPath(), REPLACE_EXISTING);
+
+ if (cptVirtualFile.getId() != null) {
+ cptVirtualFile.getFile().setReadOnly();
+ }
+
+ return isNew;
+ }
+ }
+
+ /**
+ * Downloads the web templates info file if the last update was more than 1 day ago, and
+ * returns the web template files listed in the info file.
+ *
+ * @return returns the web template files listed in the info file
+ */
+ public static WebTemplateFile[] loadWebTemplateFiles(Project project) {
+ // download the web templates file only once a day
+ if (!getWebTemplatesInfoFile().exists() || new Date().getTime() - getWebTemplatesInfoFile().lastModified() > 1000 * 60 * 60 * 24) {
+ try {
+ downloadWebTemplatesInfoFile();
+ } catch (IOException | URISyntaxException e) {
+ //noinspection CallToPrintStackTrace
+ e.printStackTrace();
+ MyNotifier.notificationGroup
+ .createNotification("Failed to download postfix web templates. Please check your internet connection.", NotificationType.ERROR)
+ .notify(project);
+ }
+ }
+
+ try {
+ return WebTemplateFileLoader.load(getWebTemplatesInfoFile());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NotNull
+ public static VirtualFile getVirtualFile(@NotNull PsiElement element) {
+ return element.getContainingFile().getViewProvider().getVirtualFile();
+ }
+
+ public static void createTopPrioUserTemplateFile(String language, String fileName) {
+ createTemplateFile(language, fileName, getDefaultTemplates("example"));
+ val templateFile = getTemplateFile(language, fileName);
+
+ val pluginSettings = CptApplicationSettings.getInstance().getPluginSettings();
+
+ val langName2virtualFiles = pluginSettings.getLangName2virtualFiles();
+ val vFiles = langName2virtualFiles.computeIfAbsent(language, l -> new ArrayList<>());
+ vFiles.add(0, new CptPluginSettings.VFile(true, null, null, templateFile.getAbsolutePath()));
+
+ val newLangName2virtualFiles = langName2virtualFiles.entrySet().stream().collect(Collectors.toMap(
+ e -> e.getKey(),
+ e -> e.getValue().stream().map(f -> new CptPluginSettings.VFile(f.enabled, f.id, f.url, f.file.replace(CptUtil.getTemplatesPath().getAbsolutePath(), "${PLUGIN}"))).collect(Collectors.toList())
+ ));
+
+ val newPluginSettings = new CptPluginSettings(
+ pluginSettings.isVarLambdaStyle(),
+ pluginSettings.isUpdateWebTemplatesAutomatically(),
+ pluginSettings.isActivateNewWebTemplateFilesAutomatically(),
+ pluginSettings.getSettingsVersion(),
+ newLangName2virtualFiles
+ );
+
+ CptApplicationSettings.getInstance().setPluginSettingsExternally(newPluginSettings);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/psi/CptElementFactory.java b/src/de/endrullis/idea/postfixtemplates/language/psi/CptElementFactory.java
new file mode 100644
index 00000000..3b3b9dcb
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/psi/CptElementFactory.java
@@ -0,0 +1,25 @@
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiFileFactory;
+import de.endrullis.idea.postfixtemplates.language.CptFileType;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class CptElementFactory {
+ public static CptTemplate createTemplate(Project project, String name, String description) {
+ final CptFile file = createFile(project, "." + name + " : " + description);
+ return (CptTemplate) file.getFirstChild();
+ }
+
+ public static CptMapping createMapping(Project project, String name, String code) {
+ final CptFile file = createFile(project, name + " -> " + code);
+ return (CptMapping) file.getFirstChild();
+ }
+
+ public static CptFile createFile(Project project, String text) {
+ String name = "dummy.Cpt";
+ return (CptFile) PsiFileFactory.getInstance(project).
+ createFileFromText(name, CptFileType.INSTANCE, text);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/psi/CptElementType.java b/src/de/endrullis/idea/postfixtemplates/language/psi/CptElementType.java
new file mode 100644
index 00000000..724da15d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/psi/CptElementType.java
@@ -0,0 +1,12 @@
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import com.intellij.psi.tree.IElementType;
+import de.endrullis.idea.postfixtemplates.language.CptLanguage;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+public class CptElementType extends IElementType {
+ public CptElementType(@NotNull @NonNls String debugName) {
+ super(debugName, CptLanguage.INSTANCE);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/psi/CptFile.java b/src/de/endrullis/idea/postfixtemplates/language/psi/CptFile.java
new file mode 100644
index 00000000..58b8e119
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/psi/CptFile.java
@@ -0,0 +1,26 @@
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import com.intellij.extapi.psi.PsiFileBase;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.psi.FileViewProvider;
+import de.endrullis.idea.postfixtemplates.language.CptFileType;
+import de.endrullis.idea.postfixtemplates.language.CptLanguage;
+import org.jetbrains.annotations.NotNull;
+
+public class CptFile extends PsiFileBase {
+ public CptFile(@NotNull FileViewProvider viewProvider) {
+ super(viewProvider, CptLanguage.INSTANCE);
+ }
+
+ @NotNull
+ @Override
+ public FileType getFileType() {
+ return CptFileType.INSTANCE;
+ }
+
+ @Override
+ public String toString() {
+ return "Custom Postfix Templates File";
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/psi/CptNamedElement.java b/src/de/endrullis/idea/postfixtemplates/language/psi/CptNamedElement.java
new file mode 100644
index 00000000..0d017f29
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/psi/CptNamedElement.java
@@ -0,0 +1,6 @@
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import com.intellij.psi.PsiNameIdentifierOwner;
+
+public interface CptNamedElement extends PsiNameIdentifierOwner {
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/psi/CptTokenType.java b/src/de/endrullis/idea/postfixtemplates/language/psi/CptTokenType.java
new file mode 100644
index 00000000..73e5a819
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/psi/CptTokenType.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.language.psi;
+
+import com.intellij.psi.tree.IElementType;
+import de.endrullis.idea.postfixtemplates.language.CptLanguage;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+public class CptTokenType extends IElementType {
+ public CptTokenType(@NotNull @NonNls String debugName) {
+ super(debugName, CptLanguage.INSTANCE);
+ }
+
+ @Override
+ public String toString() {
+ return "CptTokenType." + super.toString();
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/psi/impl/CptNamedElementImpl.java b/src/de/endrullis/idea/postfixtemplates/language/psi/impl/CptNamedElementImpl.java
new file mode 100644
index 00000000..a867c7a1
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/psi/impl/CptNamedElementImpl.java
@@ -0,0 +1,12 @@
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import com.intellij.extapi.psi.ASTWrapperPsiElement;
+import com.intellij.lang.ASTNode;
+import de.endrullis.idea.postfixtemplates.language.psi.CptNamedElement;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class CptNamedElementImpl extends ASTWrapperPsiElement implements CptNamedElement {
+ protected CptNamedElementImpl(@NotNull ASTNode node) {
+ super(node);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/language/psi/impl/CptPsiImplUtil.java b/src/de/endrullis/idea/postfixtemplates/language/psi/impl/CptPsiImplUtil.java
new file mode 100644
index 00000000..c3fc897b
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/language/psi/impl/CptPsiImplUtil.java
@@ -0,0 +1,147 @@
+package de.endrullis.idea.postfixtemplates.language.psi.impl;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.navigation.ItemPresentation;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import de.endrullis.idea.postfixtemplates.language.CptIcons;
+import de.endrullis.idea.postfixtemplates.language.psi.*;
+import lombok.experimental.UtilityClass;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+@UtilityClass
+public class CptPsiImplUtil {
+
+ public static String getTemplateName(CptTemplate element) {
+ ASTNode keyNode = element.getNode().findChildByType(CptTypes.TEMPLATE_NAME);
+ if (keyNode != null) {
+ // IMPORTANT: Convert embedded escaped spaces to simple spaces
+ return keyNode.getText().replaceAll("\\\\ ", " ");
+ } else {
+ return null;
+ }
+ }
+
+ public static String getTemplateDescription(CptTemplate element) {
+ ASTNode valueNode = element.getNode().findChildByType(CptTypes.TEMPLATE_DESCRIPTION);
+ if (valueNode != null) {
+ return valueNode.getText();
+ } else {
+ return null;
+ }
+ }
+
+ public static String getMatchingClassName(CptMapping element) {
+ ASTNode keyNode = element.getNode().findChildByType(CptTypes.CLASS_NAME);
+ if (keyNode != null) {
+ // IMPORTANT: Convert embedded escaped spaces to simple spaces
+ return keyNode.getText().replaceAll("\\\\ ", " ");
+ } else {
+ return null;
+ }
+ }
+
+ public static String getConditionClassName(CptMapping element) {
+ ASTNode firstClassNode = element.getNode().findChildByType(CptTypes.CLASS_NAME);
+ if (firstClassNode != null) {
+ ASTNode secondClassNode = element.getNode().findChildByType(CptTypes.CLASS_NAME, firstClassNode.getTreeNext());
+ if (secondClassNode != null) {
+ // IMPORTANT: Convert embedded escaped spaces to simple spaces
+ return secondClassNode.getText().replaceAll("\\\\ ", " ");
+ }
+ }
+ return null;
+ }
+
+ public static String getReplacementString(CptMapping element) {
+ ASTNode valueNode = element.getNode().findChildByType(CptTypes.REPLACEMENT);
+ if (valueNode != null) {
+ return valueNode.getText();
+ } else {
+ return null;
+ }
+ }
+
+ public static String getName(CptTemplate element) {
+ return getTemplateName(element);
+ }
+
+ public static String getName(CptMapping element) {
+ return getMatchingClassName(element);
+ }
+
+ public static PsiElement setName(CptTemplate element, String newName) {
+ ASTNode keyNode = element.getNode().findChildByType(CptTypes.TEMPLATE_NAME);
+ if (keyNode != null) {
+ CptTemplate property = CptElementFactory.createTemplate(element.getProject(), newName, "");
+ ASTNode newKeyNode = property.getFirstChild().getNode();
+ element.getNode().replaceChild(keyNode, newKeyNode);
+ }
+ return element;
+ }
+
+ public static PsiElement setName(CptMapping element, String newName) {
+ ASTNode keyNode = element.getNode().findChildByType(CptTypes.CLASS_NAME);
+ if (keyNode != null) {
+ CptMapping property = CptElementFactory.createMapping(element.getProject(), newName, "");
+ ASTNode newKeyNode = property.getFirstChild().getNode();
+ element.getNode().replaceChild(keyNode, newKeyNode);
+ }
+ return element;
+ }
+
+ public static PsiElement getNameIdentifier(CptNamedElement element) {
+ ASTNode keyNode = element.getNode().findChildByType(CptTypes.CLASS_NAME);
+ if (keyNode != null) {
+ return keyNode.getPsi();
+ } else {
+ return null;
+ }
+ }
+
+ public static ItemPresentation getPresentation(final CptTemplate element) {
+ return new ItemPresentation() {
+ @Nullable
+ @Override
+ public String getPresentableText() {
+ return element.getTemplateName();
+ }
+
+ @Nullable
+ @Override
+ public String getLocationString() {
+ PsiFile containingFile = element.getContainingFile();
+ return containingFile == null ? null : containingFile.getName();
+ }
+
+ @Override
+ public Icon getIcon(boolean unused) {
+ return CptIcons.FILE;
+ }
+ };
+ }
+
+ public static ItemPresentation getPresentation(final CptMapping element) {
+ return new ItemPresentation() {
+ @Nullable
+ @Override
+ public String getPresentableText() {
+ return element.getMatchingClassName();
+ }
+
+ @Nullable
+ @Override
+ public String getLocationString() {
+ PsiFile containingFile = element.getContainingFile();
+ return containingFile == null ? null : containingFile.getName();
+ }
+
+ @Override
+ public Icon getIcon(boolean unused) {
+ return CptIcons.FILE;
+ }
+ };
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/SupportedLanguages.java b/src/de/endrullis/idea/postfixtemplates/languages/SupportedLanguages.java
new file mode 100644
index 00000000..ba6b0d94
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/SupportedLanguages.java
@@ -0,0 +1,81 @@
+package de.endrullis.idea.postfixtemplates.languages;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.languages.csharp.CsharpLang;
+import de.endrullis.idea.postfixtemplates.languages.dart.DartLang;
+import de.endrullis.idea.postfixtemplates.languages.go.GoLang;
+import de.endrullis.idea.postfixtemplates.languages.groovy.GroovyLang;
+import de.endrullis.idea.postfixtemplates.languages.java.JavaLang;
+import de.endrullis.idea.postfixtemplates.languages.javascript.JavaScriptLang;
+import de.endrullis.idea.postfixtemplates.languages.kotlin.KotlinLang;
+import de.endrullis.idea.postfixtemplates.languages.latex.LatexLang;
+import de.endrullis.idea.postfixtemplates.languages.php.PhpLang;
+import de.endrullis.idea.postfixtemplates.languages.python.PythonLang;
+import de.endrullis.idea.postfixtemplates.languages.ruby.RubyLang;
+import de.endrullis.idea.postfixtemplates.languages.scala.ScalaLang;
+import de.endrullis.idea.postfixtemplates.languages.sql.SqlLang;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static de.endrullis.idea.postfixtemplates.language.CptUtil.getVirtualFile;
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._List;
+
+/**
+ * All supported languages of this plugin.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class SupportedLanguages {
+
+ private static List getLangs() {
+ return _List(
+ new CsharpLang(),
+ new DartLang(),
+ new GoLang(),
+ new GroovyLang(),
+ new JavaLang(),
+ new JavaScriptLang(),
+ new KotlinLang(),
+ new PhpLang(),
+ new PythonLang(),
+ new RubyLang(),
+ new ScalaLang(),
+ new SqlLang(),
+ new LatexLang()
+ ).stream().sorted(Comparator.comparing(l -> l.getNiceName().toLowerCase())).collect(Collectors.toList());
+ }
+
+ public static final List supportedLanguages = getLangs();
+
+ public static final Set supportedLanguageIds = supportedLanguages.stream().map(cl -> cl.getLanguage()).collect(Collectors.toSet());
+
+ private static final HashMap languageToCptLang = new HashMap<>() {{
+ supportedLanguages.forEach(lang -> put(lang.getLanguage(), lang));
+ }};
+
+ @Nullable
+ public static CptLang getCptLang(final String language) {
+ return languageToCptLang.get(language);
+ }
+
+ @NotNull
+ public static Optional getCptLang(@NotNull final PsiElement element) {
+ return getCptLang(getVirtualFile(element));
+ }
+
+ @NotNull
+ public static Optional getCptLang(VirtualFile vFile) {
+ if (vFile == null) {
+ return Optional.empty();
+ }
+
+ return Optional.ofNullable(CptUtil.getLanguageOfTemplateFile(vFile));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpAnnotator.java
new file mode 100644
index 00000000..1bf7347d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpAnnotator.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.languages.csharp;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for C# CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class CsharpAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ resultSet.addElement(LookupElementBuilder.create(SpecialType.ANY.name()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpLang.java b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpLang.java
new file mode 100644
index 00000000..17479cee
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.csharp;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for C#.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class CsharpLang extends CptLang {
+
+ public CsharpLang() {
+ super("C#", CsharpAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpPostfixTemplateProvider.java
new file mode 100644
index 00000000..3a5613ed
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.csharp;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class CsharpPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "csharp";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.jetbrains.rider.ideaInterop.fileTypes.csharp.psi.CSharpDummyNode";
+ }
+
+ @NotNull
+ @Override
+ protected CustomCsharpStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return CsharpStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..b5a5df0e
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CsharpStringPostfixTemplateCreator.java
@@ -0,0 +1,19 @@
+package de.endrullis.idea.postfixtemplates.languages.csharp;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import lombok.experimental.UtilityClass;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+@UtilityClass
+class CsharpStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomCsharpStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomCsharpStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/csharp/CustomCsharpStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CustomCsharpStringPostfixTemplate.java
new file mode 100644
index 00000000..d5b24ee2
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/csharp/CustomCsharpStringPostfixTemplate.java
@@ -0,0 +1,43 @@
+package de.endrullis.idea.postfixtemplates.languages.csharp;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Custom postfix template for C#.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomCsharpStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ /** Contains predefined type-to-psiCondition mappings as well as cached mappings for individual types. */
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), e -> true);
+ }};
+
+ public CustomCsharpStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)));
+ }
+
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ val psiElementCondition = type2psiCondition.get(matchingClass);
+
+ // PyElementTypes.INTEGER_LITERAL_EXPRESSION
+ //TypeEvalContext.codeAnalysis(e.getProject(), e.getContainingFile()).getType((PyTypedElement) e)
+
+ if (psiElementCondition == null) {
+ //psiElementCondition = PythonPostfixTemplatesUtils.isCustomClass(matchingClass);
+ }
+
+ return psiElementCondition;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/dart/CustomDartStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/dart/CustomDartStringPostfixTemplate.java
new file mode 100644
index 00000000..cc022728
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/dart/CustomDartStringPostfixTemplate.java
@@ -0,0 +1,32 @@
+package de.endrullis.idea.postfixtemplates.languages.dart;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static de.endrullis.idea.postfixtemplates.languages.dart.DartPostfixTemplatesUtils.IS_ANY;
+
+@SuppressWarnings("WeakerAccess")
+public class CustomDartStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), IS_ANY);
+ }};
+
+ public CustomDartStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)));
+ }
+
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ return type2psiCondition.get(matchingClass);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/dart/DartAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartAnnotator.java
new file mode 100644
index 00000000..f11f578f
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartAnnotator.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.languages.dart;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Dart CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class DartAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ resultSet.addElement(LookupElementBuilder.create(SpecialType.ANY.name()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/dart/DartLang.java b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartLang.java
new file mode 100644
index 00000000..104b02e5
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.dart;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class DartLang extends CptLang {
+
+ public DartLang() {
+ super("Dart", DartAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/dart/DartPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartPostfixTemplateProvider.java
new file mode 100644
index 00000000..4540ba23
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartPostfixTemplateProvider.java
@@ -0,0 +1,28 @@
+package de.endrullis.idea.postfixtemplates.languages.dart;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class DartPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "dart";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.jetbrains.lang.dart.psi.DartExpression";
+ }
+
+ @NotNull
+ @Override
+ protected CustomDartStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return DartStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+}
+
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/dart/DartPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartPostfixTemplatesUtils.java
new file mode 100644
index 00000000..f6515661
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartPostfixTemplatesUtils.java
@@ -0,0 +1,10 @@
+package de.endrullis.idea.postfixtemplates.languages.dart;
+
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+
+class DartPostfixTemplatesUtils {
+
+ static final Condition IS_ANY = element -> true;
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/dart/DartStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..ff083f1b
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/dart/DartStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.dart;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class DartStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomDartStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomDartStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/go/CustomGoStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/go/CustomGoStringPostfixTemplate.java
new file mode 100755
index 00000000..b6626bb2
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/go/CustomGoStringPostfixTemplate.java
@@ -0,0 +1,118 @@
+package de.endrullis.idea.postfixtemplates.languages.go;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateExpressionSelector;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateExpressionSelectorBase;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiExpression;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.TokenType;
+import com.intellij.psi.util.PsiExpressionTrimRenderer;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ContainerUtil;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+import static de.endrullis.idea.postfixtemplates.languages.go.GoPostfixTemplatesUtils.*;
+
+/**
+ * Custom postfix template for Go.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomGoStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ static final Map> type2psiCondition = new HashMap<>() {{
+ put(GoSpecialType.ANY.name(), e -> true);
+ put(GoSpecialType.BOOLEAN.name(), IS_BOOL);
+ put(GoSpecialType.INT.name(), IS_INT);
+ put(GoSpecialType.INT64.name(), IS_INT64);
+ put(GoSpecialType.UINT.name(), IS_UINT);
+ put(GoSpecialType.FLOAT32.name(), IS_FLOAT32);
+ put(GoSpecialType.FLOAT64.name(), IS_FLOAT64);
+ put(GoSpecialType.FLOAT.name(), IS_FLOAT);
+ put(GoSpecialType.BYTESLICE.name(), IS_BYTESLICE);
+ put(GoSpecialType.ERROR.name(), IS_ERROR);
+ put(GoSpecialType.ARRAY.name(), IS_ARRAY);
+ put(GoSpecialType.COMPLEX.name(), IS_COMPLEX);
+ put(GoSpecialType.NIL.name(), IS_NIL);
+ put(GoSpecialType.STRING.name(), IS_STRING);
+ }};
+
+ public static @NotNull List collectExpressions(final PsiFile file,
+ final int offset) {
+ val elementAtCaret = file.findElementAt(offset);
+ val expressions = new ArrayList();
+
+ PsiElement expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiElement.class);
+
+ while (expression != null && !(expression instanceof PsiFile) && expression.getTextRange().getEndOffset() == elementAtCaret.getTextRange().getEndOffset()) {
+ final PsiElement finalExpression = expression;
+
+ if (expression.getPrevSibling() == null || expression.getPrevSibling().getNode().getElementType() == TokenType.WHITE_SPACE) {
+ if (expressions.stream().noneMatch(pe -> finalExpression.getTextRange().equals(pe.getTextRange()))) {
+ expressions.add(expression);
+ }
+ }
+
+ expression = expression.getParent();
+ }
+
+ // TODO: For an unknown reason this code completion works only with a single expression and not with multiple ones.
+ // TODO: Therefore we have to cut our list to a singleton list.
+ if (expressions.isEmpty()) {
+ return expressions;
+ }
+ ArrayList es = new ArrayList<>();
+ es.add(expressions.get(0));
+ return es;
+ }
+
+ public CustomGoStringPostfixTemplate(String clazz, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, selectorAllExpressionsWithCurrentOffset(getCondition(clazz)));
+ }
+
+ public static PostfixTemplateExpressionSelector selectorAllExpressionsWithCurrentOffset(final Condition additionalFilter) {
+ return new PostfixTemplateExpressionSelectorBase(additionalFilter) {
+ @Override
+ protected List getNonFilteredExpressions(@NotNull PsiElement context, @NotNull Document document, int offset) {
+ return new ArrayList<>(collectExpressions(context.getContainingFile(), Math.max(offset - 1, 0)));
+ }
+
+ @NotNull
+ @Override
+ public List getExpressions(@NotNull PsiElement context, @NotNull Document document, int offset) {
+ if (DumbService.getInstance(context.getProject()).isDumb()) return Collections.emptyList();
+
+ List expressions = super.getExpressions(context, document, offset);
+
+ if (!expressions.isEmpty()) return expressions;
+
+ final PsiExpression topmostExpression = null;
+ return ContainerUtil.filter(ContainerUtil.createMaybeSingletonList(topmostExpression), getFilters(offset));
+ }
+
+ @NotNull
+ @Override
+ public Function getRenderer() {
+ return element -> {
+ assert element instanceof PsiExpression;
+
+ return (new PsiExpressionTrimRenderer.RenderFunction()).fun((PsiExpression) element);
+ };
+ }
+ };
+ }
+
+ @NotNull
+ public static Condition getCondition(String clazz) {
+ return type2psiCondition.get(clazz);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/go/GoAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/go/GoAnnotator.java
new file mode 100755
index 00000000..0198faa5
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/go/GoAnnotator.java
@@ -0,0 +1,39 @@
+package de.endrullis.idea.postfixtemplates.languages.go;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Go CPTs.
+ *
+ * @author Philip Griggs (philipgriggs@gmail.com)
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class GoAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ for (String key : CustomGoStringPostfixTemplate.type2psiCondition.keySet()) {
+ put(key, true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull LeafPsiElement element, @NotNull String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ for (String key : CustomGoStringPostfixTemplate.type2psiCondition.keySet()) {
+ resultSet.addElement(LookupElementBuilder.create(key));
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/go/GoLang.java b/src/de/endrullis/idea/postfixtemplates/languages/go/GoLang.java
new file mode 100755
index 00000000..a64f2da2
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/go/GoLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.go;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Philip Griggs (philipgriggs@gmail.com)
+ */
+public class GoLang extends CptLang {
+
+ public GoLang() {
+ super("Go", GoAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/go/GoPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/go/GoPostfixTemplateProvider.java
new file mode 100755
index 00000000..0503cd35
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/go/GoPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.go;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class GoPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "go";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.goide.psi.GoExpression";
+ }
+
+ @NotNull
+ @Override
+ protected CustomGoStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomGoStringPostfixTemplate(matchingClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/go/GoPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/go/GoPostfixTemplatesUtils.java
new file mode 100755
index 00000000..f0c5c9af
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/go/GoPostfixTemplatesUtils.java
@@ -0,0 +1,120 @@
+package de.endrullis.idea.postfixtemplates.languages.go;
+
+import com.goide.psi.GoArrayOrSliceType;
+import com.goide.psi.GoExpression;
+import com.goide.psi.GoType;
+import com.goide.psi.impl.GoPsiImplUtil;
+import com.goide.psi.impl.GoTypeUtil;
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.lang.javascript.index.JavaScriptIndex;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Function;
+
+/**
+ * Utilities for PHP postfix templates.
+ *
+ * @author Philip Griggs (philipgriggs@gmail.com)
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class GoPostfixTemplatesUtils {
+
+ /*
+ static boolean isInstanceOf(@NotNull JavaScriptTypeHelper subType, @NotNull PhpType superType, PsiElement psiElement) {
+ JavaScriptTypeHelper.getInstance().getTypeForIndexing(psiElement);
+
+ return superType.isConvertibleFrom(subType, PhpIndex.getInstance(psiElement.getProject()));
+ }
+ */
+
+ static boolean isProjectClass(@NotNull String conditionClass, PsiElement e) {
+ return JavaScriptIndex.getInstance(e.getProject()).getClassByName(conditionClass, true) != null;
+ }
+
+ static void addCompletions(CompletionParameters parameters, CompletionResultSet resultSet) {
+ // TODO
+ }
+
+ private static boolean isBuiltinType(@Nullable GoType type) {
+ return GoPsiImplUtil.builtin(type != null ? type.getUnderlyingType((PsiElement) null) : null);
+ }
+
+ static final Condition IS_BOOL = goExp(element -> {
+ return GoTypeUtil.isBoolean(element.getGoType(null), element);
+ });
+
+ static final Condition IS_INT = goExp(element -> {
+ return GoTypeUtil.isIntegerType(element.getGoType(null), element);
+ });
+
+ static final Condition IS_INT64 = goExp(element -> {
+ GoType type = element.getGoType(null);
+ return type != null && type.getText().equals("int64");
+ });
+
+ static final Condition IS_UINT = goExp(element -> {
+ return GoTypeUtil.isUintType(element.getGoType(null), element);
+ });
+
+ static final Condition IS_FLOAT32 = goExp(element -> {
+ return GoTypeUtil.isFloat32(element.getGoType(null), element);
+ });
+
+ static final Condition IS_FLOAT64 = goExp(element -> {
+ return GoTypeUtil.isFloat64(element.getGoType(null), element);
+ });
+
+ static final Condition IS_FLOAT = goExp(element -> {
+ return nullTypeToFalse(element.getGoType(null), goType -> GoTypeUtil.isFloatType(goType, element));
+ });
+
+ static final Condition IS_BYTESLICE = goExp(element -> {
+ return GoTypeUtil.isByteSlice(element.getGoType(null), element);
+ });
+
+ static final Condition IS_ERROR = goExp(element -> {
+ return GoTypeUtil.isError(element.getGoType(null), element);
+ });
+
+ static final Condition IS_ARRAY = goExp(element -> {
+// return GoTypeUtil.isArray(element.getGoType(null));
+ GoType type = element.getGoType(null);
+ type = type != null ? type.getUnderlyingType(element) : null;
+ return type instanceof GoArrayOrSliceType;
+ });
+
+ static final Condition IS_COMPLEX = goExp(element -> {
+ return nullTypeToFalse(element.getGoType(null), goType -> GoTypeUtil.isComplexType(goType, element));
+ });
+
+ static final Condition IS_NIL = goExp(element -> {
+ return nullTypeToFalse(element.getGoType(null), goType -> GoTypeUtil.isAllowedComparingToNil(goType, element));
+ });
+
+ static final Condition IS_STRING = goExp(element -> {
+ return GoTypeUtil.isString(element.getGoType(null), element);
+ });
+
+ private static Condition goExp(Condition f) {
+ return expression -> {
+ if (expression instanceof GoExpression) {
+ return f.value((GoExpression) expression);
+ } else {
+ return false;
+ }
+ };
+ }
+
+ private static boolean nullTypeToFalse(GoType goType, Function f) {
+ if (goType == null) {
+ return false;
+ } else {
+ return f.apply(goType);
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/go/GoSpecialType.java b/src/de/endrullis/idea/postfixtemplates/languages/go/GoSpecialType.java
new file mode 100755
index 00000000..85128ffa
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/go/GoSpecialType.java
@@ -0,0 +1,13 @@
+package de.endrullis.idea.postfixtemplates.languages.go;
+
+/**
+ * Special types representing groups of Go types/classes.
+ *
+ * @author Philip Griggs (philipgriggs@gmail.com)
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public enum GoSpecialType {
+
+ ANY, BOOLEAN, STRING, INT, INT64, UINT, FLOAT, FLOAT32, FLOAT64, BYTESLICE, ERROR, COMPLEX, ARRAY, NIL
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/groovy/CustomGroovyStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/groovy/CustomGroovyStringPostfixTemplate.java
new file mode 100644
index 00000000..82774c09
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/groovy/CustomGroovyStringPostfixTemplate.java
@@ -0,0 +1,170 @@
+package de.endrullis.idea.postfixtemplates.languages.groovy;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateExpressionSelector;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateExpressionSelectorBase;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.*;
+import com.intellij.psi.tree.IElementType;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ContainerUtil;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyElementTypes;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
+
+import java.util.*;
+
+import static com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils.getTopmostExpression;
+import static de.endrullis.idea.postfixtemplates.languages.groovy.GroovyPostfixTemplatesUtils.*;
+import static de.endrullis.idea.postfixtemplates.languages.java.CustomJavaStringPostfixTemplate.withProjectClassCondition;
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._Set;
+
+/**
+ * Custom postfix template for Groovy.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomGroovyStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ /** Contains predefined type-to-psiCondition mappings as well as cached mappings for individual types. */
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(GroovyType.ANY.name(), e -> true);
+ //put(GroovyType.VOID.name(), IS_VOID);
+ //put(GroovyType.NON_VOID.name(), IS_NON_VOID);
+ put(GroovyType.ARRAY.name(), GroovyPostfixTemplatesUtils.IS_ARRAY);
+ put(GroovyType.BOOLEAN.name(), e -> e instanceof GrExpression && isCustomClass(((GrExpression) e).getType(), "java.lang.Boolean"));
+ put(GroovyType.ITERABLE_OR_ARRAY.name(), GroovyPostfixTemplatesUtils.IS_ITERABLE_OR_ARRAY);
+ //put(GroovyType.NOT_PRIMITIVE.name(), IS_NOT_PRIMITIVE);
+ put(GroovyType.NUMBER.name(), IS_DECIMAL_NUMBER);
+ put(GroovyType.BYTE.name(), isCertainNumberType(PsiTypes.byteType()));
+ put(GroovyType.SHORT.name(), isCertainNumberType(PsiTypes.shortType()));
+ put(GroovyType.CHAR.name(), isCertainNumberType(PsiTypes.charType()));
+ put(GroovyType.INT.name(), isCertainNumberType(PsiTypes.intType()));
+ put(GroovyType.LONG.name(), isCertainNumberType(PsiTypes.longType()));
+ put(GroovyType.FLOAT.name(), isCertainNumberType(PsiTypes.floatType()));
+ put(GroovyType.DOUBLE.name(), isCertainNumberType(PsiTypes.doubleType()));
+ //put(GroovyType.BYTE_LITERAL.name(), isCertainNumberLiteral(PsiType.BYTE));
+ //put(GroovyType.SHORT_LITERAL.name(), isCertainNumberLiteral(PsiType.SHORT));
+ //put(GroovyType.CHAR_LITERAL.name(), isCertainNumberLiteral(PsiType.CHAR));
+ //put(GroovyType.INT_LITERAL.name(), isCertainNumberLiteral(PsiType.INT));
+ //put(GroovyType.LONG_LITERAL.name(), isCertainNumberLiteral(PsiType.LONG));
+ //put(GroovyType.FLOAT_LITERAL.name(), isCertainNumberLiteral(PsiType.FLOAT));
+ //put(GroovyType.DOUBLE_LITERAL.name(), isCertainNumberLiteral(PsiType.DOUBLE));
+ //put(GroovyType.NUMBER_LITERAL.name(), IS_DECIMAL_NUMBER_LITERAL);
+ //put(GroovyType.STRING_LITERAL.name(), STRING_LITERAL);
+ put(GroovyType.CLASS.name(), e -> e instanceof GrExpression && isCustomClass(((GrExpression) e).getType(), "java.lang.Class"));
+ }};
+
+ private static final Set delimiterTokens = _Set(
+ TokenType.WHITE_SPACE,
+ GroovyElementTypes.NL,
+ GroovyElementTypes.T_LBRACE,
+ GroovyElementTypes.T_LBRACK,
+ GroovyElementTypes.T_COMMA,
+ GroovyElementTypes.T_COLON,
+ GroovyElementTypes.T_LPAREN
+ );
+
+ public static List collectExpressions(final PsiFile file,
+ final Document document,
+ final int offset,
+ boolean acceptVoid) {
+ val text = document.getCharsSequence();
+ int correctedOffset = offset;
+ int textLength = document.getTextLength();
+ if (offset >= textLength) {
+ correctedOffset = textLength - 1;
+ } else if (!Character.isJavaIdentifierPart(text.charAt(offset))) {
+ correctedOffset--;
+ }
+ if (correctedOffset < 0) {
+ correctedOffset = offset;
+ } else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) {
+ if (text.charAt(correctedOffset) == ';') {//initially caret on the end of line
+ correctedOffset--;
+ }
+ if (correctedOffset < 0 || text.charAt(correctedOffset) != ')') {
+ correctedOffset = offset;
+ }
+ }
+ final PsiElement elementAtCaret = file.findElementAt(correctedOffset);
+ final List expressions = new ArrayList<>();
+
+ PsiElement expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiElement.class);
+
+ while (expression != null && expression.getTextRange().getEndOffset() == elementAtCaret.getTextRange().getEndOffset()) {
+ //System.out.println(expression + " - " + expression.getText() + " - " + expression.getTextRange());
+ final PsiElement finalExpression = expression;
+
+ if (expression.getPrevSibling() == null || delimiterTokens.contains(expression.getPrevSibling().getNode().getElementType())) {
+ if (expressions.stream().noneMatch(pe -> finalExpression.getTextRange().equals(pe.getTextRange()))) {
+ expressions.add(expression);
+ }
+ } else {
+ //System.out.println("prevSilbing: " + expression.getPrevSibling().getNode().getElementType());
+ }
+
+ //expression = PsiTreeUtil.getParentOfType(expression, KtExpression.class);
+ expression = expression.getParent();
+ }
+
+ // TODO: For an unknown reason this code completion works only with a single expression and not with multiple ones.
+ // TODO: Therefore we have to cut our list to a singleton list.
+ if (expressions.isEmpty()) {
+ return expressions;
+ }
+ ArrayList es = new ArrayList<>();
+ es.add(expressions.get(0));
+ //es.add(expressions.get(expressions.size()-1));
+ return es;
+ }
+
+ public static PostfixTemplateExpressionSelector selectorAllExpressionsWithCurrentOffset(final Condition additionalFilter) {
+ return new PostfixTemplateExpressionSelectorBase(additionalFilter) {
+ @Override
+ protected List getNonFilteredExpressions(@NotNull PsiElement context, @NotNull Document document, int offset) {
+ return new ArrayList<>(collectExpressions(context.getContainingFile(), document, Math.max(offset - 1, 0), false));
+ }
+
+ @NotNull
+ @Override
+ public List getExpressions(@NotNull PsiElement context, @NotNull Document document, int offset) {
+ if (DumbService.getInstance(context.getProject()).isDumb()) return Collections.emptyList();
+
+ List expressions = super.getExpressions(context, document, offset);
+
+ if (!expressions.isEmpty()) return expressions;
+
+ return ContainerUtil.filter(ContainerUtil.createMaybeSingletonList(getTopmostExpression(context)), getFilters(offset));
+ }
+
+ @NotNull
+ @Override
+ public Function getRenderer() {
+ return JavaPostfixTemplatesUtils.getRenderer();
+ }
+ };
+ }
+
+ public CustomGroovyStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)));
+ }
+
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ Condition psiElementCondition = type2psiCondition.get(matchingClass);
+
+ if (psiElementCondition == null) {
+ psiElementCondition = GroovyPostfixTemplatesUtils.isCustomClass(matchingClass);
+ }
+
+ return withProjectClassCondition(conditionClass, psiElementCondition);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyAnnotator.java
new file mode 100644
index 00000000..9d00ddf9
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyAnnotator.java
@@ -0,0 +1,47 @@
+package de.endrullis.idea.postfixtemplates.languages.groovy;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import de.endrullis.idea.postfixtemplates.language.CptCompletionUtil;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Groovy CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class GroovyAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ for (GroovyType value : GroovyType.values()) {
+ put(value.name(), true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className) {
+ return className2exists.computeIfAbsent(className, name -> {
+ val project = element.getProject();
+ return JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) != null;
+ });
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ for (GroovyType value : GroovyType.values()) {
+ resultSet.addElement(LookupElementBuilder.create(value.name()));
+ }
+
+ CptCompletionUtil.addCompletions(parameters, resultSet);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyLang.java b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyLang.java
new file mode 100644
index 00000000..5e281a67
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.groovy;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Groovy.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class GroovyLang extends CptLang {
+
+ public GroovyLang() {
+ super("Groovy", GroovyAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyPostfixTemplateProvider.java
new file mode 100644
index 00000000..89d3a350
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.groovy;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class GroovyPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "groovy";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement";
+ }
+
+ @NotNull
+ @Override
+ protected CustomGroovyStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return GroovyStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyPostfixTemplatesUtils.java
new file mode 100644
index 00000000..5e4a569d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyPostfixTemplatesUtils.java
@@ -0,0 +1,136 @@
+package de.endrullis.idea.postfixtemplates.languages.groovy;
+
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.*;
+import com.intellij.psi.util.InheritanceUtil;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils.*;
+
+/**
+ * Some static additions to {@link com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils}.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+abstract class GroovyPostfixTemplatesUtils {
+
+ private GroovyPostfixTemplatesUtils() {
+ }
+
+ @NotNull
+ static Condition isCustomClass(String clazz) {
+ return element -> {
+ if (element instanceof GrExpression) {
+ return isCustomClass(((GrExpression) element).getType(), clazz);
+ } else {
+ return false;
+ }
+ };
+ }
+
+ static final Condition IS_ARRAY = element -> {
+ if (!(element instanceof GrExpression)) {
+ return false;
+ } else {
+ PsiType type = ((GrExpression) element).getType();
+ return isArray(type);
+ }
+ };
+
+ static final Condition IS_ITERABLE_OR_ARRAY = element -> {
+ if (!(element instanceof GrExpression)) {
+ return false;
+ } else {
+ PsiType type = ((GrExpression) element).getType();
+ return isArray(type) || isIterable(type);
+ }
+ };
+
+ static final Condition IS_DECIMAL_NUMBER =
+ element -> element instanceof GrExpression && isDecimalNumber(((GrExpression) element).getType());
+
+ static final Condition IS_DECIMAL_NUMBER_LITERAL =
+ element -> element instanceof PsiLiteralExpression && isDecimalNumber(((PsiLiteralExpression) element).getType());
+
+ public static final Condition IS_BOOLEAN = (element) -> {
+ return element instanceof PsiExpression && isBoolean(((PsiExpression)element).getType());
+ };
+
+ static final Condition IS_VOID =
+ element -> element instanceof GrExpression && isVoid(((GrExpression) element).getType());
+
+ static final Condition IS_NON_VOID =
+ element -> element instanceof GrExpression && isNonVoid(((GrExpression) element).getType());
+
+ static final Condition IS_ANY =
+ element -> true;
+
+ static final Condition IS_FIELD =
+ element -> element instanceof PsiField;
+
+ static final Condition IS_LOCAL_VARIABLE =
+ element -> element instanceof PsiLocalVariable;
+
+ static final Condition IS_VARIABLE =
+ element -> element instanceof PsiField || element instanceof PsiLocalVariable;
+
+ static final Condition IS_ASSIGNMENT =
+ element -> element instanceof PsiField || element instanceof PsiAssignmentExpression;
+
+ static Condition isCertainNumberType(@NotNull PsiType expectedType) {
+ return element -> element instanceof GrExpression && isCertainDecimalNumberType(((GrExpression) element).getType(), expectedType);
+ }
+
+ static Condition isCertainNumberLiteral(@NotNull PsiType expectedType) {
+ return element -> element instanceof PsiLiteralExpression && isCertainDecimalNumberType(((GrExpression) element).getType(), expectedType);
+ }
+
+ static Condition STRING_LITERAL = element -> {
+ return element instanceof PsiLiteralExpression && InheritanceUtil.isInheritor(((PsiLiteralExpression) element).getType(), "java.lang.String");
+ };
+
+ /**
+ * Contains byte, short, char, int, long, float, and double.
+ */
+ static final Set NUMERIC_TYPES = new HashSet<>(Arrays.asList(
+ PsiTypes.byteType(), PsiTypes.shortType(), PsiTypes.charType(), PsiTypes.intType(), PsiTypes.longType(), PsiTypes.floatType(), PsiTypes.doubleType())
+ );
+
+ @Contract("null,_ -> false")
+ static boolean isCustomClass(@Nullable PsiType type, @NotNull String clazz) {
+ return InheritanceUtil.isInheritor(type, clazz);
+ }
+
+ @Contract("null -> false")
+ static boolean isDecimalNumber(@Nullable PsiType type) {
+ if (type == null) {
+ return false;
+ }
+
+ return NUMERIC_TYPES.contains(type)
+ || NUMERIC_TYPES.contains(PsiPrimitiveType.getUnboxedType(type))
+ || isCustomClass(type, "java.math.BigDecimal");
+ }
+
+ @Contract("null,_ -> false")
+ static boolean isCertainDecimalNumberType(@Nullable PsiType type, @NotNull PsiType expectedType) {
+ if (type == null) {
+ return false;
+ }
+
+ return expectedType.equals(type) || expectedType.equals(PsiPrimitiveType.getUnboxedType(type));
+ }
+
+ @Contract("null -> false")
+ static boolean isVoid(@Nullable PsiType type) {
+ return PsiTypes.voidType().equals(type);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..2c66b3b4
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.groovy;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class GroovyStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomGroovyStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomGroovyStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyType.java b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyType.java
new file mode 100644
index 00000000..0f998a07
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/groovy/GroovyType.java
@@ -0,0 +1,18 @@
+package de.endrullis.idea.postfixtemplates.languages.groovy;
+
+/**
+ * Special types representing groups of Java types/classes.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public enum GroovyType {
+
+ ANY, ARRAY, BOOLEAN, ITERABLE_OR_ARRAY, NUMBER, CLASS,
+
+ //FIELD, LOCAL_VARIABLE, VARIABLE, ASSIGNMENT,
+
+ BYTE, SHORT, CHAR, INT, LONG, FLOAT, DOUBLE,
+ //BYTE_LITERAL, SHORT_LITERAL, CHAR_LITERAL, INT_LITERAL, LONG_LITERAL, FLOAT_LITERAL, DOUBLE_LITERAL,
+ //NUMBER_LITERAL, STRING_LITERAL
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/java/CustomJavaStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/java/CustomJavaStringPostfixTemplate.java
new file mode 100644
index 00000000..61e53fc8
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/java/CustomJavaStringPostfixTemplate.java
@@ -0,0 +1,291 @@
+package de.endrullis.idea.postfixtemplates.languages.java;
+
+import com.intellij.codeInsight.template.Template;
+import com.intellij.codeInsight.template.TemplateManager;
+import com.intellij.codeInsight.template.impl.TextExpression;
+import com.intellij.codeInsight.template.postfix.templates.*;
+import com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.OrderedSet;
+import de.endrullis.idea.postfixtemplates.templates.MyVariable;
+import de.endrullis.idea.postfixtemplates.templates.NavigatablePostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils.*;
+import static de.endrullis.idea.postfixtemplates.languages.java.MyJavaPostfixTemplatesUtils.IS_ITERABLE_OR_ARRAY;
+import static de.endrullis.idea.postfixtemplates.languages.java.MyJavaPostfixTemplatesUtils.*;
+import static de.endrullis.idea.postfixtemplates.settings.CustomPostfixTemplates.PREDEFINED_VARIABLES;
+import static de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateUtils.parseVariables;
+import static de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateUtils.removeVariableValues;
+import static de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate.addVariablesToTemplate;
+
+/**
+ * Custom postfix template for Java.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomJavaStringPostfixTemplate extends StringBasedPostfixTemplate implements NavigatablePostfixTemplate {
+
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), IS_ANY);
+ put(SpecialType.VOID.name(), IS_VOID);
+ put(SpecialType.NON_VOID.name(), IS_NON_VOID);
+ put(SpecialType.ARRAY.name(), IS_ARRAY);
+ put(SpecialType.BOOLEAN.name(), IS_BOOLEAN);
+ put(SpecialType.ITERABLE_OR_ARRAY.name(), IS_ITERABLE_OR_ARRAY);
+ put(SpecialType.NOT_PRIMITIVE.name(), IS_NOT_PRIMITIVE);
+ put(SpecialType.NUMBER.name(), IS_DECIMAL_NUMBER);
+ put(SpecialType.BYTE.name(), isCertainNumberType(PsiTypes.byteType()));
+ put(SpecialType.SHORT.name(), isCertainNumberType(PsiTypes.shortType()));
+ put(SpecialType.CHAR.name(), isCertainNumberType(PsiTypes.charType()));
+ put(SpecialType.INT.name(), isCertainNumberType(PsiTypes.intType()));
+ put(SpecialType.LONG.name(), isCertainNumberType(PsiTypes.longType()));
+ put(SpecialType.FLOAT.name(), isCertainNumberType(PsiTypes.floatType()));
+ put(SpecialType.DOUBLE.name(), isCertainNumberType(PsiTypes.doubleType()));
+ put(SpecialType.BYTE_LITERAL.name(), isCertainNumberLiteral(PsiTypes.byteType()));
+ put(SpecialType.SHORT_LITERAL.name(), isCertainNumberLiteral(PsiTypes.shortType()));
+ put(SpecialType.CHAR_LITERAL.name(), isCertainNumberLiteral(PsiTypes.charType()));
+ put(SpecialType.INT_LITERAL.name(), isCertainNumberLiteral(PsiTypes.intType()));
+ put(SpecialType.LONG_LITERAL.name(), isCertainNumberLiteral(PsiTypes.longType()));
+ put(SpecialType.FLOAT_LITERAL.name(), isCertainNumberLiteral(PsiTypes.floatType()));
+ put(SpecialType.DOUBLE_LITERAL.name(), isCertainNumberLiteral(PsiTypes.doubleType()));
+ put(SpecialType.NUMBER_LITERAL.name(), IS_DECIMAL_NUMBER_LITERAL);
+ put(SpecialType.STRING_LITERAL.name(), STRING_LITERAL);
+ put(SpecialType.CLASS.name(), IS_CLASS);
+ /*
+ put(SpecialType.FIELD.name(), IS_FIELD);
+ put(SpecialType.LOCAL_VARIABLE.name(), IS_LOCAL_VARIABLE);
+ put(SpecialType.VARIABLE.name(), IS_VARIABLE);
+ put(SpecialType.ASSIGNMENT.name(), IS_ASSIGNMENT);
+ */
+ }};
+
+ private final String template;
+ private final Set variables = new OrderedSet<>();
+ private final PsiElement psiElement;
+ private final boolean useStaticImports;
+
+ public static List collectExpressions(final PsiFile file,
+ final Document document,
+ final int offset,
+ boolean acceptVoid) {
+ CharSequence text = document.getCharsSequence();
+ int correctedOffset = offset;
+ int textLength = document.getTextLength();
+ if (offset >= textLength) {
+ correctedOffset = textLength - 1;
+ } else if (!Character.isJavaIdentifierPart(text.charAt(offset))) {
+ correctedOffset--;
+ }
+ if (correctedOffset < 0) {
+ correctedOffset = offset;
+ } else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) {
+ if (text.charAt(correctedOffset) == ';') {//initially caret on the end of line
+ correctedOffset--;
+ }
+ if (correctedOffset < 0 || text.charAt(correctedOffset) != ')') {
+ correctedOffset = offset;
+ }
+ }
+ final PsiElement elementAtCaret = file.findElementAt(correctedOffset);
+ final List expressions = new ArrayList<>();
+ /*for (PsiElement element : statementsInRange) {
+ if (element instanceof PsiExpressionStatement) {
+ final PsiExpression expression = ((PsiExpressionStatement)element).getExpression();
+ if (expression.getType() != PsiType.VOID) {
+ expressions.add(expression);
+ }
+ }
+ }*/
+ PsiExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class);
+ while (expression != null) {
+ if (!expressions.contains(expression) && !(expression instanceof PsiParenthesizedExpression) && !(expression instanceof PsiSuperExpression) &&
+ (acceptVoid || !PsiTypes.voidType().equals(expression.getType()))) {
+ if (expression instanceof PsiMethodReferenceExpression) {
+ expressions.add(expression);
+ } else if (!(expression instanceof PsiAssignmentExpression)) {
+ if (!(expression instanceof PsiReferenceExpression)) {
+ expressions.add(expression);
+ } else {
+ if (!(expression.getParent() instanceof PsiMethodCallExpression)) {
+ final PsiElement resolve = ((PsiReferenceExpression) expression).resolve();
+ if (!(resolve instanceof PsiClass) && !(resolve instanceof PsiPackage)) {
+ expressions.add(expression);
+ }
+ }
+ }
+ }
+ }
+ expression = PsiTreeUtil.getParentOfType(expression, PsiExpression.class);
+ }
+
+ /*
+ for (PsiElement psiElement : expressions) {
+ System.out.println("parent: " + psiElement + " at " + psiElement.getTextRange());
+ }
+ */
+
+ return expressions;
+ }
+
+ public static PostfixTemplateExpressionSelector selectorAllExpressionsWithCurrentOffset(final Condition additionalFilter) {
+ return new PostfixTemplateExpressionSelectorBase(additionalFilter) {
+ @Override
+ protected List getNonFilteredExpressions(@NotNull PsiElement context, @NotNull Document document, int offset) {
+ return new ArrayList<>(collectExpressions(context.getContainingFile(), document,
+ Math.max(offset - 1, 0), false));
+ }
+
+ @NotNull
+ @Override
+ public List getExpressions(@NotNull PsiElement context, @NotNull Document document, int offset) {
+ if (DumbService.getInstance(context.getProject()).isDumb()) return Collections.emptyList();
+
+ List expressions = super.getExpressions(context, document, offset);
+ if (!expressions.isEmpty()) return expressions;
+
+ return ContainerUtil.filter(ContainerUtil.createMaybeSingletonList(getTopmostExpression(context)), getFilters(offset));
+ }
+
+ @NotNull
+ @Override
+ public Function getRenderer() {
+ return JavaPostfixTemplatesUtils.getRenderer();
+ }
+ };
+ }
+
+ public CustomJavaStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name.substring(1), name, example, selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)), provider);
+ this.psiElement = psiElement;
+
+ useStaticImports = template.contains("[USE_STATIC_IMPORTS]");
+ template = template.replaceAll("\\[USE_STATIC_IMPORTS]", "");
+
+ List allVariables = parseVariables(template).stream().filter(v -> {
+ return !PREDEFINED_VARIABLES.contains(v.getName());
+ }).collect(Collectors.toList());
+
+ this.template = removeVariableValues(template, allVariables);
+
+ // filter out variable duplicates
+ Set foundVarNames = new HashSet<>();
+ for (MyVariable variable : allVariables) {
+ if (!foundVarNames.contains(variable.getName())) {
+ variables.add(variable);
+ foundVarNames.add(variable.getName());
+ }
+ }
+ }
+
+ @Override
+ protected PsiElement getElementToRemove(PsiElement expr) {
+ return expr;
+ }
+
+ @Override
+ public void setVariables(@NotNull Template template, @NotNull PsiElement psiElement) {
+ super.setVariables(template, psiElement);
+
+ addVariablesToTemplate(template, variables, psiElement.getProject(), this);
+ }
+
+ /**
+ * Returns a function that returns true if
+ *
+ * the PSI element satisfies the type condition regarding {@code matchingClass} and
+ * {@code conditionClass} is either {@code null} or available in the current module.
+ *
+ *
+ * @param matchingClass required type of the psi element to satisfy this condition
+ * @param conditionClass required class in the current module to satisfy this condition, or {@code null}
+ * @return PSI element condition
+ */
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ Condition psiElementCondition = type2psiCondition.get(matchingClass);
+
+ if (psiElementCondition == null) {
+ psiElementCondition = MyJavaPostfixTemplatesUtils.isCustomClass(matchingClass);
+ }
+
+ return withProjectClassCondition(conditionClass, psiElementCondition);
+ }
+
+ public static Condition withProjectClassCondition(@Nullable String conditionClass, Condition psiElementCondition) {
+ if (conditionClass == null) {
+ return psiElementCondition;
+ } else {
+ final Condition finalPsiElementCondition = psiElementCondition;
+
+ return psiElement -> {
+ if (finalPsiElementCondition.value(psiElement)) {
+ final Project project = psiElement.getProject();
+ PsiFile psiFile = psiElement.getContainingFile().getOriginalFile();
+ VirtualFile virtualFile = psiFile.getVirtualFile();
+ Module module = ProjectRootManager.getInstance(project).getFileIndex().getModuleForFile(virtualFile);
+
+ if (module != null) {
+ return JavaPsiFacade.getInstance(project).findClass(conditionClass, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, true)) != null;
+ } else {
+ return JavaPsiFacade.getInstance(project).findClass(conditionClass, GlobalSearchScope.projectScope(project)) != null;
+ }
+ } else {
+ return false;
+ }
+ };
+ }
+ }
+
+ @Nullable
+ @Override
+ public String getTemplateString(@NotNull PsiElement element) {
+ return template;
+ }
+
+ public PsiElement getNavigationElement() {
+ return psiElement;
+ }
+
+ @Override
+ public void expandForChooseExpression(@NotNull PsiElement expr, @NotNull Editor editor) {
+ Project project = expr.getProject();
+ Document document = editor.getDocument();
+ PsiElement elementForRemoving = getElementToRemove(expr);
+ document.deleteString(elementForRemoving.getTextRange().getStartOffset(), elementForRemoving.getTextRange().getEndOffset());
+ TemplateManager manager = TemplateManager.getInstance(project);
+
+ String templateString = getTemplateString(expr);
+ if (templateString == null) {
+ PostfixTemplatesUtils.showErrorHint(expr.getProject(), editor);
+ return;
+ }
+
+ Template template = createTemplate(manager, templateString);
+
+ if (useStaticImports) {
+ template.setValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE, true);
+ }
+
+ template.addVariable("expr", new TextExpression(expr.getText()), false);
+ setVariables(template, expr);
+ manager.startTemplate(editor, template);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/java/JavaAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/java/JavaAnnotator.java
new file mode 100644
index 00000000..d7d1f2c8
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/java/JavaAnnotator.java
@@ -0,0 +1,48 @@
+package de.endrullis.idea.postfixtemplates.languages.java;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import de.endrullis.idea.postfixtemplates.language.CptCompletionUtil;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Java CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class JavaAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ for (SpecialType specialType : SpecialType.values()) {
+ put(specialType.name(), true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull LeafPsiElement element, @NotNull String className) {
+ return className2exists.computeIfAbsent(className, name -> {
+ val project = element.getProject();
+ return JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) != null;
+ });
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ for (SpecialType specialType : SpecialType.values()) {
+ resultSet.addElement(LookupElementBuilder.create(specialType.name()));
+ }
+
+ CptCompletionUtil.addCompletions(parameters, resultSet);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/java/JavaLang.java b/src/de/endrullis/idea/postfixtemplates/languages/java/JavaLang.java
new file mode 100644
index 00000000..e35e2c7c
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/java/JavaLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.java;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class JavaLang extends CptLang {
+
+ public JavaLang() {
+ super("Java", JavaAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/java/JavaPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/java/JavaPostfixTemplateProvider.java
new file mode 100644
index 00000000..c0ba6b50
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/java/JavaPostfixTemplateProvider.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.languages.java;
+
+import com.intellij.codeInsight.completion.CompletionInitializationContext;
+import com.intellij.codeInsight.completion.JavaCompletionContributor;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class JavaPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "java";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.intellij.psi.PsiJavaReference";
+ }
+
+ @NotNull
+ @Override
+ protected CustomJavaStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomJavaStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+ protected boolean isSemicolonNeeded(@NotNull PsiFile file, @NotNull Editor editor) {
+ return JavaCompletionContributor.semicolonNeeded(file, CompletionInitializationContext.calcStartOffset(editor.getCaretModel().getCurrentCaret()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/java/MyJavaPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/java/MyJavaPostfixTemplatesUtils.java
new file mode 100644
index 00000000..9ffebdba
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/java/MyJavaPostfixTemplatesUtils.java
@@ -0,0 +1,125 @@
+package de.endrullis.idea.postfixtemplates.languages.java;
+
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.*;
+import com.intellij.psi.util.InheritanceUtil;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils.isArray;
+import static com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils.isIterable;
+
+/**
+ * Some static additions to {@link com.intellij.codeInsight.template.postfix.util.JavaPostfixTemplatesUtils}.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public abstract class MyJavaPostfixTemplatesUtils {
+
+ private MyJavaPostfixTemplatesUtils() {
+ }
+
+ @NotNull
+ public static Condition isCustomClass(String clazz) {
+ return element -> element instanceof PsiExpression && isCustomClass(((PsiExpression) element).getType(), clazz);
+ }
+
+ public static final Condition IS_ARRAY = element -> {
+ if (!(element instanceof PsiExpression)) {
+ return false;
+ } else {
+ PsiType type = ((PsiExpression) element).getType();
+ return isArray(type);
+ }
+ };
+
+ public static final Condition IS_ITERABLE_OR_ARRAY = element -> {
+ if (!(element instanceof PsiExpression)) {
+ return false;
+ } else {
+ PsiType type = ((PsiExpression) element).getType();
+ return isArray(type) || isIterable(type);
+ }
+ };
+
+ public static final Condition IS_DECIMAL_NUMBER =
+ element -> element instanceof PsiExpression && isDecimalNumber(((PsiExpression) element).getType());
+
+ public static final Condition IS_DECIMAL_NUMBER_LITERAL =
+ element -> element instanceof PsiLiteralExpression && isDecimalNumber(((PsiLiteralExpression) element).getType());
+
+ public static final Condition IS_VOID =
+ element -> element instanceof PsiExpression && isVoid(((PsiExpression) element).getType());
+
+ public static final Condition IS_ANY =
+ element -> true;
+
+ public static final Condition IS_FIELD =
+ element -> element instanceof PsiField;
+
+ public static final Condition IS_CLASS =
+ element -> element instanceof PsiReferenceExpression &&
+ ((PsiReferenceExpression) element).advancedResolve(true).getElement() instanceof PsiClass;
+
+ public static final Condition IS_LOCAL_VARIABLE =
+ element -> element instanceof PsiLocalVariable;
+
+ public static final Condition IS_VARIABLE =
+ element -> element instanceof PsiField || element instanceof PsiLocalVariable;
+
+ public static final Condition IS_ASSIGNMENT =
+ element -> element instanceof PsiField || element instanceof PsiAssignmentExpression;
+
+ public static Condition isCertainNumberType(@NotNull PsiType expectedType) {
+ return element -> element instanceof PsiExpression && isCertainDecimalNumberType(((PsiExpression) element).getType(), expectedType);
+ }
+
+ public static Condition isCertainNumberLiteral(@NotNull PsiType expectedType) {
+ return element -> element instanceof PsiLiteralExpression && isCertainDecimalNumberType(((PsiExpression) element).getType(), expectedType);
+ }
+
+ public static Condition STRING_LITERAL = element -> {
+ return element instanceof PsiLiteralExpression && InheritanceUtil.isInheritor(((PsiLiteralExpression) element).getType(), "java.lang.String");
+ };
+
+ /**
+ * Contains byte, short, char, int, long, float, and double.
+ */
+ public static final Set NUMERIC_TYPES = new HashSet<>(Arrays.asList(
+ PsiTypes.byteType(), PsiTypes.shortType(), PsiTypes.charType(), PsiTypes.intType(), PsiTypes.longType(), PsiTypes.floatType(), PsiTypes.doubleType())
+ );
+
+ @Contract("null,_ -> false")
+ public static boolean isCustomClass(@Nullable PsiType type, @NotNull String clazz) {
+ return InheritanceUtil.isInheritor(type, clazz);
+ }
+
+ @Contract("null -> false")
+ public static boolean isDecimalNumber(@Nullable PsiType type) {
+ if (type == null) {
+ return false;
+ }
+
+ return NUMERIC_TYPES.contains(type) || NUMERIC_TYPES.contains(PsiPrimitiveType.getUnboxedType(type));
+ }
+
+ @Contract("null,_ -> false")
+ public static boolean isCertainDecimalNumberType(@Nullable PsiType type, @NotNull PsiType expectedType) {
+ if (type == null) {
+ return false;
+ }
+
+ return expectedType.equals(type) || expectedType.equals(PsiPrimitiveType.getUnboxedType(type));
+ }
+
+ @Contract("null -> false")
+ public static boolean isVoid(@Nullable PsiType type) {
+ return PsiTypes.voidType().equals(type);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/javascript/CustomJavaScriptStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/javascript/CustomJavaScriptStringPostfixTemplate.java
new file mode 100644
index 00000000..9679e3d7
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/javascript/CustomJavaScriptStringPostfixTemplate.java
@@ -0,0 +1,23 @@
+package de.endrullis.idea.postfixtemplates.languages.javascript;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.lang.javascript.psi.JSExpressionStatement;
+import com.intellij.lang.javascript.template.postfix.JSPostfixTemplateUtils;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+
+/**
+ * Custom postfix template for JavaScript.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomJavaScriptStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ public CustomJavaScriptStringPostfixTemplate(String clazz, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, JSPostfixTemplateUtils.selectorTopmost());
+ }
+
+ protected PsiElement getElementToRemove(PsiElement expr) {
+ return expr.getParent() instanceof JSExpressionStatement ? expr : super.getElementToRemove(expr);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptAnnotator.java
new file mode 100644
index 00000000..d74eca9d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptAnnotator.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.languages.javascript;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for JavaScript CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class JavaScriptAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull LeafPsiElement element, @NotNull String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ resultSet.addElement(LookupElementBuilder.create(SpecialType.ANY.name()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptLang.java b/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptLang.java
new file mode 100644
index 00000000..6a360ff0
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.javascript;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class JavaScriptLang extends CptLang {
+
+ public JavaScriptLang() {
+ super("JavaScript", JavaScriptAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptPostfixTemplateProvider.java
new file mode 100644
index 00000000..93206b3d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/javascript/JavaScriptPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.javascript;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class JavaScriptPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "javascript";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.intellij.lang.javascript.template.postfix.JSPostfixTemplateUtils";
+ }
+
+ @NotNull
+ @Override
+ protected CustomJavaScriptStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomJavaScriptStringPostfixTemplate(matchingClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/kotlin/CustomKotlinStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/CustomKotlinStringPostfixTemplate.java
new file mode 100644
index 00000000..11e485ac
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/CustomKotlinStringPostfixTemplate.java
@@ -0,0 +1,112 @@
+package de.endrullis.idea.postfixtemplates.languages.kotlin;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.kotlin.KtNodeTypes;
+import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
+import org.jetbrains.kotlin.idea.codeInsight.postfix.KtPostfixTemplateProviderKt;
+import org.jetbrains.kotlin.psi.KtConstantExpression;
+import org.jetbrains.kotlin.psi.KtExpression;
+import org.jetbrains.kotlin.psi.KtNameReferenceExpression;
+import org.jetbrains.kotlin.psi.KtStringTemplateExpression;
+import org.jetbrains.kotlin.renderer.DescriptorRenderer;
+import org.jetbrains.kotlin.resolve.BindingContext;
+import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Custom postfix template for Kotlin.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomKotlinStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ private static final DescriptorRenderer typeRenderer = DescriptorRenderer.FQ_NAMES_IN_TYPES;
+
+ private static final Map> type2psiCondition = new HashMap>() {{
+ put(SpecialType.ANY.name(), e -> e instanceof KtExpression);
+ put(SpecialType.STRING_LITERAL.name(), e -> e instanceof KtStringTemplateExpression);
+ put(SpecialType.FLOAT_LITERAL.name(), e -> e instanceof KtConstantExpression && ((KtConstantExpression) e).getNode().getElementType() == KtNodeTypes.FLOAT_CONSTANT);
+ put(SpecialType.INT_LITERAL.name(), e -> e instanceof KtConstantExpression && ((KtConstantExpression) e).getNode().getElementType() == KtNodeTypes.INTEGER_CONSTANT);
+ put(SpecialType.CHAR_LITERAL.name(), e -> e instanceof KtConstantExpression && ((KtConstantExpression) e).getNode().getElementType() == KtNodeTypes.CHARACTER_CONSTANT);
+ }};
+
+ public CustomKotlinStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, KtPostfixTemplateProviderKt.createExpressionSelector(true, false, null));
+ }
+
+ @Override
+ protected PsiElement getElementToRemove(PsiElement expr) {
+ return expr;
+ }
+
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ final Condition psiElementCondition = type2psiCondition.get(matchingClass);
+
+ if (psiElementCondition != null) {
+ return psiElementCondition;
+ } else {
+ return psiElement -> classMatches(matchingClass, psiElement);
+ }
+
+ /*
+ if (conditionClass == null) {
+ return psiElementCondition;
+ } else {
+ final Condition finalPsiElementCondition = psiElementCondition;
+
+ return psiElement -> {
+ if (finalPsiElementCondition.value(psiElement)) {
+ final Project project = psiElement.getProject();
+ PsiFile psiFile = psiElement.getContainingFile().getOriginalFile();
+ VirtualFile virtualFile = psiFile.getVirtualFile();
+ Module module = ProjectRootManager.getInstance(project).getFileIndex().getModuleForFile(virtualFile);
+ assert module != null;
+ return JavaPsiFacade.getInstance(project).findClass(conditionClass, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, true)) != null;
+ } else {
+ return false;
+ }
+ };
+ }
+ return psiElement -> {
+ if (psiElement instanceof KtExpression) {
+ /*
+ final KtExpression ktExpression = (KtExpression) psiElement;
+ final BindingContext bindingContext = analyze(ktExpression, BodyResolveMode.PARTIAL);
+ final KotlinTypeInfo typeInfo = bindingContext.get(BindingContext.EXPRESSION_TYPE_INFO, ktExpression);
+ final KotlinType type = typeInfo.getType();
+ System.out.println(type);
+ System.out.println(type.getMemberScope().toString());
+ */
+ //bindingContext[BindingContext.EXPRESSION_TYPE_INFO, element];
+ //val expressionType = element.getType(bindingContext);
+ //return true;
+ //}
+ //return false;
+ //};
+ }
+
+ private static boolean classMatches(String matchingClass, PsiElement psiElement) {
+ if (psiElement instanceof final KtNameReferenceExpression ktRef) {
+ final BindingContext context = ResolutionUtils.analyze(ktRef);
+ final KotlinTypeInfo info = context.get(BindingContext.EXPRESSION_TYPE_INFO, ktRef);
+
+ if (info != null && info.getType() != null) {
+ final String fqdn = typeRenderer.renderType(info.getType());
+
+ return matchingClass.equals(fqdn);
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinAnnotator.java
new file mode 100644
index 00000000..c3125a46
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinAnnotator.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.languages.kotlin;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Kotlin CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class KotlinAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ className2exists.keySet().forEach(key -> resultSet.addElement(LookupElementBuilder.create(key)));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinLang.java b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinLang.java
new file mode 100644
index 00000000..45cfbfa3
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.kotlin;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class KotlinLang extends CptLang {
+
+ public KotlinLang() {
+ super("Kotlin", KotlinAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinPostfixTemplateProvider.java
new file mode 100644
index 00000000..6c51e79d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinPostfixTemplateProvider.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.languages.kotlin;
+
+import com.intellij.codeInsight.completion.CompletionInitializationContext;
+import com.intellij.codeInsight.completion.JavaCompletionContributor;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class KotlinPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "kotlin";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "org.jetbrains.kotlin.psi.KtElement";
+ }
+
+ @NotNull
+ @Override
+ protected CustomKotlinStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return KotlinStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+ protected boolean isSemicolonNeeded(@NotNull PsiFile file, @NotNull Editor editor) {
+ return JavaCompletionContributor.semicolonNeeded(file, CompletionInitializationContext.calcStartOffset(editor.getCaretModel().getCurrentCaret()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..9f0f3871
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/kotlin/KotlinStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.kotlin;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class KotlinStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomKotlinStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomKotlinStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/latex/CustomLatexStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/latex/CustomLatexStringPostfixTemplate.java
new file mode 100644
index 00000000..abf9ab99
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/latex/CustomLatexStringPostfixTemplate.java
@@ -0,0 +1,18 @@
+package de.endrullis.idea.postfixtemplates.languages.latex;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateExpressionSelector;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+
+/**
+ * Custom postfix template for Latex.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomLatexStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ public CustomLatexStringPostfixTemplate(String clazz, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement, PostfixTemplateExpressionSelector expressionSelector) {
+ super(name, example, template, provider, psiElement, expressionSelector);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexAnnotator.java
new file mode 100644
index 00000000..2b6145f2
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexAnnotator.java
@@ -0,0 +1,46 @@
+package de.endrullis.idea.postfixtemplates.languages.latex;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptCompletionUtil;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for JavaScript CPTs.
+ *
+ */
+public class LatexAnnotator implements CptLangAnnotator {
+
+ private final static SpecialType[] supportedTypes = new SpecialType[]{
+ SpecialType.ANY,
+ SpecialType.MATH,
+ SpecialType.TEXT,
+ };
+
+ private final Map className2exists = new HashMap<>() {{
+ for (SpecialType specialType : supportedTypes) {
+ put(specialType.name(), true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull LeafPsiElement element, @NotNull String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ for (SpecialType specialType : supportedTypes) {
+ resultSet.addElement(LookupElementBuilder.create(specialType.name()));
+ }
+
+ CptCompletionUtil.addCompletions(parameters, resultSet);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexLang.java b/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexLang.java
new file mode 100644
index 00000000..229ad9b9
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexLang.java
@@ -0,0 +1,15 @@
+package de.endrullis.idea.postfixtemplates.languages.latex;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Latex.
+ *
+ */
+public class LatexLang extends CptLang {
+
+ public LatexLang() {
+ super("Latex", LatexAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexPostfixTemplateProvider.java
new file mode 100644
index 00000000..51fedb07
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/latex/LatexPostfixTemplateProvider.java
@@ -0,0 +1,40 @@
+package de.endrullis.idea.postfixtemplates.languages.latex;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateExpressionSelector;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class LatexPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "latex";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "nl.hannahsten.texifyidea.editor.postfix.LatexPostfixExpressionSelector";
+ }
+
+ @NotNull
+ @Override
+ protected CustomLatexStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ try {
+ Class> expressionSelectorClazz = Class.forName(getPluginClassName());
+ boolean mathOnly = SpecialType.MATH.name().equals(matchingClass);
+ boolean textOnly = SpecialType.TEXT.name().equals(matchingClass);
+ PostfixTemplateExpressionSelector expressionSelector = ((PostfixTemplateExpressionSelector) expressionSelectorClazz.getDeclaredConstructor(boolean.class, boolean.class).newInstance(mathOnly, textOnly));
+ return new CustomLatexStringPostfixTemplate(matchingClass, templateName, description, template, provider, mapping, expressionSelector);
+ } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InstantiationException |
+ InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/php/CustomPhpStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/php/CustomPhpStringPostfixTemplate.java
new file mode 100644
index 00000000..5c7868ec
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/php/CustomPhpStringPostfixTemplate.java
@@ -0,0 +1,71 @@
+package de.endrullis.idea.postfixtemplates.languages.php;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.php.lang.psi.elements.PhpTypedElement;
+import com.jetbrains.php.lang.psi.resolve.types.PhpType;
+import com.jetbrains.php.postfixCompletion.PhpPostfixUtils;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static de.endrullis.idea.postfixtemplates.languages.php.PhpPostfixTemplatesUtils.isInstanceOf;
+import static de.endrullis.idea.postfixtemplates.languages.php.PhpPostfixTemplatesUtils.isProjectClass;
+
+/**
+ * Custom postfix template for PHP.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomPhpStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ /**
+ * Contains predefined type-to-psiCondition mappings as well as cached mappings for individual types.
+ */
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), e -> true);
+ for (PhpType phpType : PhpPostfixTemplatesUtils.PHP_TYPES) {
+ if (phpType.isNotExtendablePrimitiveType()) {
+ put(phpType.toString(), e -> e instanceof PhpTypedElement && ((PhpTypedElement) e).getType().getTypes().stream().anyMatch(s -> phpType.getTypes().contains(s)));
+ } else {
+ put(phpType.toString(), e -> e instanceof PhpTypedElement && isInstanceOf(((PhpTypedElement) e).getType(), phpType, e));
+ }
+ }
+ }};
+
+ public CustomPhpStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, PhpPostfixUtils.selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)));
+ }
+
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ Condition psiElementCondition = type2psiCondition.get(matchingClass);
+
+ if (psiElementCondition == null) {
+ val phpType = new PhpType().add(matchingClass);
+ psiElementCondition = e -> e instanceof PhpTypedElement && isInstanceOf(((PhpTypedElement) e).getType(), phpType, e);
+ // type2psiCondition.put(matchingClass, psiElementCondition);
+ }
+
+ if (conditionClass != null) {
+ val oldPsiElementCondition = psiElementCondition;
+ psiElementCondition = e -> {
+ if (isProjectClass(conditionClass, e)) {
+ return oldPsiElementCondition.value(e);
+ } else {
+ return false;
+ }
+ };
+ }
+
+ return psiElementCondition;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/php/PhpAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpAnnotator.java
new file mode 100644
index 00000000..4765d87c
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpAnnotator.java
@@ -0,0 +1,49 @@
+package de.endrullis.idea.postfixtemplates.languages.php;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import com.jetbrains.php.PhpIndex;
+import com.jetbrains.php.lang.psi.resolve.types.PhpType;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for PHP CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class PhpAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ for (PhpType phpType : PhpPostfixTemplatesUtils.PHP_TYPES) {
+ put(phpType.toString(), true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className) {
+ return className2exists.computeIfAbsent(className, name -> {
+ val project = element.getProject();
+ return PhpIndex.getInstance(project).getClassByName(className) != null;
+ });
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ resultSet.addElement(LookupElementBuilder.create(SpecialType.ANY.name()));
+ for (PhpType phpType : PhpPostfixTemplatesUtils.PHP_TYPES) {
+ resultSet.addElement(LookupElementBuilder.create(phpType.toString()));
+ }
+
+ PhpPostfixTemplatesUtils.addCompletions(parameters, resultSet);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/php/PhpLang.java b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpLang.java
new file mode 100644
index 00000000..1c2c29c8
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.php;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for PHP.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class PhpLang extends CptLang {
+
+ public PhpLang() {
+ super("PHP", PhpAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/php/PhpPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpPostfixTemplateProvider.java
new file mode 100644
index 00000000..793b4f5c
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.php;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class PhpPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "php";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.jetbrains.php.lang.psi.elements.PhpExpression";
+ }
+
+ @NotNull
+ @Override
+ protected CustomPhpStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return PhpStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/php/PhpPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpPostfixTemplatesUtils.java
new file mode 100644
index 00000000..70ecc4a3
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpPostfixTemplatesUtils.java
@@ -0,0 +1,59 @@
+package de.endrullis.idea.postfixtemplates.languages.php;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.php.PhpIndex;
+import com.jetbrains.php.lang.psi.resolve.types.PhpType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._Set;
+
+/**
+ * Utilities for PHP postfix templates.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class PhpPostfixTemplatesUtils {
+
+ static final Set PHP_TYPES = _Set(
+ PhpType.EMPTY,
+ //PhpType.MIXED,
+ PhpType.NULL,
+ PhpType.STRING,
+ PhpType.BOOLEAN,
+ PhpType.INT,
+ PhpType.FLOAT,
+ PhpType.OBJECT,
+ PhpType.CLOSURE,
+ PhpType.CALLABLE,
+ PhpType.RESOURCE,
+ PhpType.ARRAY,
+ PhpType.ITERABLE,
+ PhpType.NUMBER,
+ PhpType.VOID,
+ //PhpType.NUMERIC,
+ //PhpType.SCALAR,
+ //PhpType.FLOAT_INT,
+ PhpType.UNSET,
+ PhpType.STATIC,
+ PhpType.EXCEPTION,
+ PhpType.THROWABLE
+ //PhpType.$THIS
+ );
+
+ static boolean isInstanceOf(@NotNull PhpType subType, @NotNull PhpType superType, PsiElement psiElement) {
+ return superType.isConvertibleFrom(subType, PhpIndex.getInstance(psiElement.getProject()));
+ }
+
+ static boolean isProjectClass(@NotNull String conditionClass, PsiElement e) {
+ return PhpIndex.getInstance(e.getProject()).getClassByName(conditionClass) != null;
+ }
+
+ static void addCompletions(CompletionParameters parameters, CompletionResultSet resultSet) {
+ // TODO
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/php/PhpStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..de7a3ace
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/php/PhpStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.php;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class PhpStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomPhpStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomPhpStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/python/CustomPythonStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/python/CustomPythonStringPostfixTemplate.java
new file mode 100644
index 00000000..6980cbe9
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/python/CustomPythonStringPostfixTemplate.java
@@ -0,0 +1,58 @@
+package de.endrullis.idea.postfixtemplates.languages.python;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.codeInsight.postfix.PyPostfixUtils;
+import com.jetbrains.python.psi.PyTypedElement;
+import com.jetbrains.python.psi.types.PyType;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Custom postfix template for Python.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomPythonStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ /** Contains predefined type-to-psiCondition mappings as well as cached mappings for individual types. */
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), e -> true);
+ for (String pyType : PythonPostfixTemplatesUtils.PYTHON_TYPES) {
+ put(pyType, e -> {
+ if (e instanceof PyTypedElement) {
+ PyType type = TypeEvalContext.codeAnalysis(e.getProject(), e.getContainingFile()).getType((PyTypedElement) e);
+ return type != null && type.getName() != null && type.getName().equals(pyType);
+ } else {
+ return false;
+ }
+ });
+ }
+ }};
+
+ public CustomPythonStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, PyPostfixUtils.selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)));
+ }
+
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ val psiElementCondition = type2psiCondition.get(matchingClass);
+
+ // PyElementTypes.INTEGER_LITERAL_EXPRESSION
+ //TypeEvalContext.codeAnalysis(e.getProject(), e.getContainingFile()).getType((PyTypedElement) e)
+
+ if (psiElementCondition == null) {
+ //psiElementCondition = PythonPostfixTemplatesUtils.isCustomClass(matchingClass);
+ }
+
+ return psiElementCondition;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/python/PythonAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonAnnotator.java
new file mode 100644
index 00000000..f62c0bdb
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonAnnotator.java
@@ -0,0 +1,41 @@
+package de.endrullis.idea.postfixtemplates.languages.python;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Python CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class PythonAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ for (String pyType : PythonPostfixTemplatesUtils.PYTHON_TYPES) {
+ put(pyType, true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ resultSet.addElement(LookupElementBuilder.create(SpecialType.ANY.name()));
+ for (String pyType : PythonPostfixTemplatesUtils.PYTHON_TYPES) {
+ resultSet.addElement(LookupElementBuilder.create(pyType));
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/python/PythonLang.java b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonLang.java
new file mode 100644
index 00000000..0acce1bc
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.python;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Python.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class PythonLang extends CptLang {
+
+ public PythonLang() {
+ super("Python", PythonAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/python/PythonPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonPostfixTemplateProvider.java
new file mode 100644
index 00000000..768f6c69
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.python;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class PythonPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "python";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.jetbrains.python.psi.PyExpression";
+ }
+
+ @NotNull
+ @Override
+ protected CustomPythonStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return PythonStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/python/PythonPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonPostfixTemplatesUtils.java
new file mode 100644
index 00000000..47a687c3
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonPostfixTemplatesUtils.java
@@ -0,0 +1,57 @@
+package de.endrullis.idea.postfixtemplates.languages.python;
+
+import java.util.Set;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._Set;
+
+/**
+ * Utilities for Python postfix templates.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class PythonPostfixTemplatesUtils {
+
+ static final Set PYTHON_TYPES = _Set(
+ "object",
+ "list",
+ "dict",
+ "set",
+ "tuple",
+ "int",
+ "float",
+ "complex",
+ "str",
+ "unicode",
+ "bytes",
+ "bool",
+ "classmethod",
+ "staticmethod",
+ "type"
+ );
+
+ /*
+ static final Set PYTHON_TYPES = _Set(
+ PyBuiltinCache.getInstance(null).getBoolType()
+ );
+ */
+
+ /*
+ @NotNull
+ static Condition isCustomClass(String clazz) {
+ PyTypeProvider tp;
+ PyType t;
+ t.isBuiltin()
+ PyPsiFacade.getInstance(null).createClassByQName()
+ return e -> e instanceof PyTypedElement && TypeEvalContext.codeAnalysis(e.getProject(), e.getContainingFile()).getType((PyTypedElement) e);
+ }
+
+ private static boolean isCustomClass(SqlType sqlType, String type) {
+ return StringUtils.substringBefore(sqlType.getDisplayName(), "(").equals(type);
+ }
+
+ static Condition isCategory(SqlType.Category category) {
+ return element -> element instanceof SqlExpression && ((SqlExpression) element).getSqlType().getCategory().equals(category);
+ }
+ */
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/python/PythonStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..2b03f2a4
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/python/PythonStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.python;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class PythonStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomPythonStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomPythonStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/ruby/CustomRubyStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/ruby/CustomRubyStringPostfixTemplate.java
new file mode 100644
index 00000000..c6f26415
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/ruby/CustomRubyStringPostfixTemplate.java
@@ -0,0 +1,33 @@
+package de.endrullis.idea.postfixtemplates.languages.ruby;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Custom postfix template for Ruby.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomRubyStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ /** Contains predefined type-to-psiCondition mappings as well as cached mappings for individual types. */
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), e -> true);
+ }};
+
+ public CustomRubyStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)));
+ }
+
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ return type2psiCondition.get(matchingClass);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyAnnotator.java
new file mode 100644
index 00000000..49a73329
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyAnnotator.java
@@ -0,0 +1,36 @@
+package de.endrullis.idea.postfixtemplates.languages.ruby;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Ruby CPTs.
+ *
+ * @author YenTing Chen <solofat@gmail.com>
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class RubyAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull LeafPsiElement element, @NotNull String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ resultSet.addElement(LookupElementBuilder.create(SpecialType.ANY.name()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyLang.java b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyLang.java
new file mode 100644
index 00000000..fc9b6b71
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.ruby;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Ruby.
+ *
+ * @author YenTing Chen <solofat@gmail.com>
+ */
+public class RubyLang extends CptLang {
+
+ public RubyLang() {
+ super("Ruby", RubyAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyPostfixTemplateProvider.java
new file mode 100644
index 00000000..ebbe4d55
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.ruby;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class RubyPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "ruby";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "org.jetbrains.plugins.ruby.ruby.lang.psi.RubyExpressionCodeFragment";
+ }
+
+ @NotNull
+ @Override
+ protected CustomRubyStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return RubyStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..6a6a9c70
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/ruby/RubyStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.ruby;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author YenTing Chen <solofat@gmail.com>
+ */
+class RubyStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomRubyStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomRubyStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/scala/CustomScalaStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/scala/CustomScalaStringPostfixTemplate.java
new file mode 100644
index 00000000..fe82f89f
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/scala/CustomScalaStringPostfixTemplate.java
@@ -0,0 +1,191 @@
+package de.endrullis.idea.postfixtemplates.languages.scala;
+
+import com.intellij.codeInsight.template.Template;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.containers.OrderedSet;
+import de.endrullis.idea.postfixtemplates.templates.MyVariable;
+import de.endrullis.idea.postfixtemplates.templates.NavigatablePostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.scala.lang.completion.postfix.templates.ScalaStringBasedPostfixTemplate;
+import org.jetbrains.plugins.scala.lang.completion.postfix.templates.selector.AncestorSelector;
+import org.jetbrains.plugins.scala.lang.psi.ScImportsHolder;
+import org.jetbrains.plugins.scala.lang.psi.api.base.ScReference;
+
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static de.endrullis.idea.postfixtemplates.languages.java.CustomJavaStringPostfixTemplate.withProjectClassCondition;
+import static de.endrullis.idea.postfixtemplates.languages.scala.ScalaPostfixTemplatesUtils.*;
+import static de.endrullis.idea.postfixtemplates.settings.CustomPostfixTemplates.PREDEFINED_VARIABLES;
+import static de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateUtils.parseVariables;
+import static de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateUtils.removeVariableValues;
+import static de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate.addVariablesToTemplate;
+
+/**
+ * Custom postfix template for Scala.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomScalaStringPostfixTemplate extends ScalaStringBasedPostfixTemplate implements NavigatablePostfixTemplate {
+
+ static final Pattern IMPORT_PATTERN = Pattern.compile("\\[IMPORT ([^]]+)]");
+
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), e -> true);
+ put(SpecialType.VOID.name(), VOID);
+ put(SpecialType.NON_VOID.name(), NON_VOID);
+ //put(SpecialType.ARRAY.name(), IS_ARRAY);
+ put(SpecialType.BOOLEAN.name(), BOOLEAN);
+ //put(SpecialType.ITERABLE_OR_ARRAY.name(), IS_ITERABLE_OR_ARRAY);
+ put(SpecialType.NUMBER.name(), DECIMAL_NUMBER);
+ put(SpecialType.BYTE.name(), BYTE);
+ put(SpecialType.SHORT.name(), SHORT);
+ put(SpecialType.CHAR.name(), CHAR);
+ put(SpecialType.INT.name(), INT);
+ put(SpecialType.LONG.name(), LONG);
+ put(SpecialType.FLOAT.name(), FLOAT);
+ put(SpecialType.DOUBLE.name(), DOUBLE);
+ /*
+ put(SpecialType.BYTE_LITERAL.name(), isCertainNumberLiteral(PsiType.BYTE));
+ put(SpecialType.SHORT_LITERAL.name(), isCertainNumberLiteral(PsiType.SHORT));
+ put(SpecialType.CHAR_LITERAL.name(), isCertainNumberLiteral(PsiType.CHAR));
+ put(SpecialType.INT_LITERAL.name(), isCertainNumberLiteral(PsiType.INT));
+ put(SpecialType.LONG_LITERAL.name(), isCertainNumberLiteral(PsiType.LONG));
+ put(SpecialType.FLOAT_LITERAL.name(), isCertainNumberLiteral(PsiType.FLOAT));
+ put(SpecialType.DOUBLE_LITERAL.name(), isCertainNumberLiteral(PsiType.DOUBLE));
+ put(SpecialType.NUMBER_LITERAL.name(), IS_DECIMAL_NUMBER_LITERAL);
+ put(SpecialType.STRING_LITERAL.name(), STRING_LITERAL);
+ put(SpecialType.CLASS.name(), IS_CLASS);
+ /*
+ put(SpecialType.FIELD.name(), IS_FIELD);
+ put(SpecialType.LOCAL_VARIABLE.name(), IS_LOCAL_VARIABLE);
+ put(SpecialType.VARIABLE.name(), IS_VARIABLE);
+ put(SpecialType.ASSIGNMENT.name(), IS_ASSIGNMENT);
+ */
+ }};
+
+ private final String template;
+ private final Set variables = new OrderedSet<>();
+ private final PsiElement psiElement;
+ private final Set imports;
+
+ public CustomScalaStringPostfixTemplate(String matchingClass, String conditionClass, String templateName, String example, String template, PsiElement psiElement) {
+ super(templateName.substring(1), example, new AncestorSelector.SelectAllAncestors(getCondition(matchingClass, conditionClass)));
+ this.psiElement = psiElement;
+
+ imports = extractImport(template);
+ template = removeImports(template);
+ template = template.replaceAll("\\[USE_STATIC_IMPORTS]", "");
+
+ List allVariables = parseVariables(template).stream().filter(v -> {
+ return !PREDEFINED_VARIABLES.contains(v.getName());
+ }).collect(Collectors.toList());
+
+ this.template = removeVariableValues(template, allVariables);
+
+ // filter out variable duplicates
+ Set foundVarNames = new HashSet<>();
+ for (MyVariable variable : allVariables) {
+ if (!foundVarNames.contains(variable.getName())) {
+ variables.add(variable);
+ foundVarNames.add(variable.getName());
+ }
+ }
+ }
+
+ @Override
+ public void setVariables(@NotNull Template template, @NotNull PsiElement psiElement) {
+ super.setVariables(template, psiElement);
+
+ addVariablesToTemplate(template, variables, psiElement.getProject(), this);
+ }
+
+ /**
+ * Returns a function that returns true if
+ *
+ * the PSI element satisfies the type condition regarding {@code matchingClass} and
+ * {@code conditionClass} is either {@code null} or available in the current module.
+ *
+ *
+ * @param matchingClass required type of the psi element to satisfy this condition
+ * @param conditionClass required class in the current module to satisfy this condition, or {@code null}
+ * @return PSI element condition
+ */
+ @NotNull
+ private static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ Condition psiElementCondition = type2psiCondition.get(matchingClass);
+
+ if (psiElementCondition == null) {
+ psiElementCondition = ScalaPostfixTemplatesUtils.isDescendant(matchingClass);
+ }
+
+ return withProjectClassCondition(conditionClass, psiElementCondition);
+ }
+
+
+ @Nullable
+ @Override
+ public String getTemplateString(@NotNull PsiElement psiElement) {
+ return template;
+ }
+
+ @Override
+ public PsiElement getNavigationElement() {
+ return psiElement;
+ }
+
+ @Override
+ protected void prepareAndExpandForChooseExpression(@NotNull PsiElement expression, @NotNull Editor editor) {
+ super.prepareAndExpandForChooseExpression(expression, editor);
+ }
+
+ @Override
+ public void expandForChooseExpression(@NotNull PsiElement expr, @NotNull Editor editor) {
+ for (String anImport : imports) {
+ addImport(expr, anImport);
+ }
+
+ super.expandForChooseExpression(expr, editor);
+ }
+
+ private void addImport(@NotNull PsiElement expr, String qualifiedName) {
+ ScImportsHolder importHolder = ScImportsHolder.apply(expr, expr.getProject());
+
+ if (expr instanceof ScReference scReference) {
+ //TODO: Update for IDEA 22.1
+ //boolean imported = importHolder.getAllImportUsed().exists(i -> i.qualName().exists(n -> n.equals(qualifiedName)));
+ boolean imported = importHolder.getImportStatements().exists(s -> {
+ System.out.println("s = " + s);
+ return false;
+ });
+
+ if (!imported) {
+ importHolder.addImportForPath(qualifiedName, scReference);
+ }
+ }
+ }
+
+
+ static Set extractImport(String template) {
+ val matcher = IMPORT_PATTERN.matcher(template);
+ val imports = new HashSet();
+
+ while(matcher.find()) {
+ imports.add(matcher.group(1));
+ }
+
+ return imports;
+ }
+
+ static String removeImports(String template) {
+ return template.replaceAll(IMPORT_PATTERN.toString(), "");
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaAnnotator.java
new file mode 100644
index 00000000..31e953b7
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaAnnotator.java
@@ -0,0 +1,63 @@
+package de.endrullis.idea.postfixtemplates.languages.scala;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import de.endrullis.idea.postfixtemplates.language.CptCompletionUtil;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Java CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class ScalaAnnotator implements CptLangAnnotator {
+
+ private final static SpecialType[] supportedTypes = new SpecialType[]{
+ SpecialType.ANY,
+ SpecialType.VOID,
+ SpecialType.NON_VOID,
+ SpecialType.BOOLEAN,
+ SpecialType.NUMBER,
+ SpecialType.BYTE,
+ SpecialType.SHORT,
+ SpecialType.CHAR,
+ SpecialType.INT,
+ SpecialType.LONG,
+ SpecialType.FLOAT,
+ SpecialType.DOUBLE,
+ };
+
+ private final Map className2exists = new HashMap<>() {{
+ for (SpecialType specialType : supportedTypes) {
+ put(specialType.name(), true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull LeafPsiElement element, @NotNull String className) {
+ return className2exists.computeIfAbsent(className, name -> {
+ final Project project = element.getProject();
+ return JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)) != null;
+ });
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ for (SpecialType specialType : supportedTypes) {
+ resultSet.addElement(LookupElementBuilder.create(specialType.name()));
+ }
+
+ CptCompletionUtil.addCompletions(parameters, resultSet);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaLang.java b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaLang.java
new file mode 100644
index 00000000..ba55b78d
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.scala;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class ScalaLang extends CptLang {
+
+ public ScalaLang() {
+ super("Scala", ScalaAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaPostfixTemplateProvider.java
new file mode 100644
index 00000000..c4df4e79
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaPostfixTemplateProvider.java
@@ -0,0 +1,50 @@
+package de.endrullis.idea.postfixtemplates.languages.scala;
+
+import com.intellij.codeInsight.completion.CompletionInitializationContext;
+import com.intellij.codeInsight.completion.JavaCompletionContributor;
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.codeInsight.template.postfix.templates.StringBasedPostfixTemplate;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiFile;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class ScalaPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "scala";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "org.jetbrains.plugins.scala.lang.completion.postfix.templates.ScalaStringBasedPostfixTemplate";
+ }
+
+ @NotNull
+ @Override
+ protected StringBasedPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return ScalaStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template);
+ }
+
+ @Override
+ public void preExpand(@NotNull PsiFile file, @NotNull Editor editor) {
+ }
+
+ @Override
+ public void afterExpand(@NotNull PsiFile file, @NotNull Editor editor) {
+ }
+
+ @NotNull
+ @Override
+ public PsiFile preCheck(@NotNull PsiFile copyFile, @NotNull Editor realEditor, int currentOffset) {
+ return copyFile;
+ }
+
+ protected boolean isSemicolonNeeded(@NotNull PsiFile file, @NotNull Editor editor) {
+ return JavaCompletionContributor.semicolonNeeded(file, CompletionInitializationContext.calcStartOffset(editor.getCaretModel().getCurrentCaret()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaPostfixTemplatesUtils.java
new file mode 100644
index 00000000..6a102057
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaPostfixTemplatesUtils.java
@@ -0,0 +1,38 @@
+package de.endrullis.idea.postfixtemplates.languages.scala;
+
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.plugins.scala.lang.completion.postfix.templates.selector.AncestorSelector;
+import scala.jdk.javaapi.CollectionConverters;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class ScalaPostfixTemplatesUtils {
+
+ static final Condition BYTE = isDescendant("scala.Byte", "java.lang.Byte");
+ static final Condition CHAR = isDescendant("scala.Char", "java.lang.Character");
+ static final Condition DOUBLE = isDescendant("scala.Double", "java.lang.Double");
+ static final Condition FLOAT = isDescendant("scala.Float", "java.lang.Float");
+ static final Condition INT = isDescendant("scala.Int", "java.lang.Integer");
+ static final Condition LONG = isDescendant("scala.Long", "java.lang.Long");
+ static final Condition SHORT = isDescendant("scala.Short", "java.lang.Short");
+ static final Condition BOOLEAN = isDescendant("scala.Boolean", "java.lang.Boolean");
+ static final Condition VOID = isDescendant("scala.Unit", "java.lang.Void");
+ static final Condition NON_VOID = e -> !VOID.value(e);
+ static final Condition DECIMAL_NUMBER = e ->
+ Stream.of(BYTE, CHAR, DOUBLE, FLOAT, INT, LONG, SHORT).anyMatch(t -> t.value(e));
+
+ public static Condition isDescendant(String... classes) {
+ return isDescendant(Arrays.asList(classes));
+ }
+
+ public static Condition isDescendant(List classes) {
+ return AncestorSelector.isSameOrInheritor(CollectionConverters.asScala(classes).toSeq());
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..34f07e9b
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/scala/ScalaStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.scala;
+
+import com.intellij.codeInsight.template.postfix.templates.StringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class ScalaStringPostfixTemplateCreator {
+
+ @NotNull
+ static StringBasedPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template) {
+ return new CustomScalaStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/sql/CustomSqlStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/sql/CustomSqlStringPostfixTemplate.java
new file mode 100644
index 00000000..0ba76fe6
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/sql/CustomSqlStringPostfixTemplate.java
@@ -0,0 +1,46 @@
+package de.endrullis.idea.postfixtemplates.languages.sql;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.database.types.DasTypeCategory;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Custom postfix template for SQL.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomSqlStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ /** Contains predefined type-to-psiCondition mappings as well as cached mappings for individual types. */
+ private static final Map> type2psiCondition = new HashMap<>() {{
+ put(SpecialType.ANY.name(), e -> true);
+ for (DasTypeCategory category : DasTypeCategory.values()) {
+ put(category.name(), SqlPostfixTemplatesUtils.isCategory(category));
+ }
+ }};
+
+ public CustomSqlStringPostfixTemplate(String matchingClass, String conditionClass, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, selectorAllExpressionsWithCurrentOffset(getCondition(matchingClass, conditionClass)));
+ }
+
+ @NotNull
+ public static Condition getCondition(final @NotNull String matchingClass, final @Nullable String conditionClass) {
+ Condition psiElementCondition = type2psiCondition.get(matchingClass);
+
+ if (psiElementCondition == null) {
+ psiElementCondition = SqlPostfixTemplatesUtils.isCustomClass(matchingClass);
+ }
+
+ return psiElementCondition;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlAnnotator.java
new file mode 100644
index 00000000..268a3597
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlAnnotator.java
@@ -0,0 +1,41 @@
+package de.endrullis.idea.postfixtemplates.languages.sql;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.database.types.DasTypeCategory;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for SQL CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class SqlAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ for (DasTypeCategory category : DasTypeCategory.values()) {
+ put(category.name(), true);
+ }
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull final LeafPsiElement element, @NotNull final String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ for (String key : className2exists.keySet()) {
+ resultSet.addElement(LookupElementBuilder.create(key));
+ }
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlLang.java b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlLang.java
new file mode 100644
index 00000000..52050237
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.sql;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class SqlLang extends CptLang {
+
+ public SqlLang() {
+ super("SQL", SqlAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlPostfixTemplateProvider.java
new file mode 100644
index 00000000..10ecf595
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.sql;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class SqlPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "sql";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.intellij.sql.psi.SqlExpression";
+ }
+
+ @NotNull
+ @Override
+ protected CustomSqlStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return SqlStringPostfixTemplateCreator.createTemplate(mapping, matchingClass, conditionClass, templateName, description, template, provider);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlPostfixTemplatesUtils.java
new file mode 100644
index 00000000..632605a0
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlPostfixTemplatesUtils.java
@@ -0,0 +1,32 @@
+package de.endrullis.idea.postfixtemplates.languages.sql;
+
+import com.intellij.database.types.DasType;
+import com.intellij.database.types.DasTypeCategory;
+import com.intellij.openapi.util.Condition;
+import com.intellij.psi.PsiElement;
+import com.intellij.sql.psi.SqlExpression;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utilities for SQL postfix templates.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class SqlPostfixTemplatesUtils {
+
+ @NotNull
+ static Condition isCustomClass(String clazz) {
+ return element -> element instanceof SqlExpression && isCustomClass(((SqlExpression) element).getDasType(), clazz.toLowerCase());
+ }
+
+ private static boolean isCustomClass(DasType dasType, String type) {
+ return StringUtils.substringBefore(dasType.getDescription(), "(").equals(type);
+ }
+
+ static Condition isCategory(DasTypeCategory category) {
+ //return element -> element instanceof SqlExpression && ((SqlExpression) element).getDasType().getCategory().equals(category);
+ return e -> true;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlStringPostfixTemplateCreator.java b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlStringPostfixTemplateCreator.java
new file mode 100644
index 00000000..495e30a0
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/sql/SqlStringPostfixTemplateCreator.java
@@ -0,0 +1,17 @@
+package de.endrullis.idea.postfixtemplates.languages.sql;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class SqlStringPostfixTemplateCreator {
+
+ @NotNull
+ static CustomSqlStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomSqlStringPostfixTemplate(matchingClass, conditionClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/swift/CustomSwiftStringPostfixTemplate.java b/src/de/endrullis/idea/postfixtemplates/languages/swift/CustomSwiftStringPostfixTemplate.java
new file mode 100644
index 00000000..15d7a90b
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/swift/CustomSwiftStringPostfixTemplate.java
@@ -0,0 +1,23 @@
+package de.endrullis.idea.postfixtemplates.languages.swift;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import com.intellij.lang.javascript.psi.JSExpressionStatement;
+import com.intellij.lang.javascript.template.postfix.JSPostfixTemplateUtils;
+import com.intellij.psi.PsiElement;
+import de.endrullis.idea.postfixtemplates.templates.SimpleStringBasedPostfixTemplate;
+/* */
+/**
+ * Custom postfix template for Swift.
+ */
+@SuppressWarnings("WeakerAccess")
+public class CustomSwiftStringPostfixTemplate extends SimpleStringBasedPostfixTemplate {
+
+ public CustomSwiftStringPostfixTemplate(String clazz, String name, String example, String template, PostfixTemplateProvider provider, PsiElement psiElement) {
+ super(name, example, template, provider, psiElement, JSPostfixTemplateUtils.selectorTopmost());
+ }
+
+ protected PsiElement getElementToRemove(PsiElement expr) {
+ return expr.getParent() instanceof JSExpressionStatement ? expr : super.getElementToRemove(expr);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftAnnotator.java b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftAnnotator.java
new file mode 100644
index 00000000..7d1c453f
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftAnnotator.java
@@ -0,0 +1,35 @@
+package de.endrullis.idea.postfixtemplates.languages.swift;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.psi.impl.source.tree.LeafPsiElement;
+import de.endrullis.idea.postfixtemplates.language.CptLangAnnotator;
+import de.endrullis.idea.postfixtemplates.templates.SpecialType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Code annotator for Swift CPTs.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class SwiftAnnotator implements CptLangAnnotator {
+
+ private final Map className2exists = new HashMap<>() {{
+ put(SpecialType.ANY.name(), true);
+ }};
+
+ @Override
+ public boolean isMatchingType(@NotNull LeafPsiElement element, @NotNull String className) {
+ return className2exists.containsKey(className);
+ }
+
+ @Override
+ public void completeMatchingType(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet resultSet) {
+ resultSet.addElement(LookupElementBuilder.create(SpecialType.ANY.name()));
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftLang.java b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftLang.java
new file mode 100644
index 00000000..baac7562
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftLang.java
@@ -0,0 +1,16 @@
+package de.endrullis.idea.postfixtemplates.languages.swift;
+
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+
+/**
+ * Language definition for Java.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public class SwiftLang extends CptLang {
+
+ public SwiftLang() {
+ super("Swift", SwiftAnnotator.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftPostfixTemplateProvider.java b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftPostfixTemplateProvider.java
new file mode 100644
index 00000000..d1ccf1aa
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftPostfixTemplateProvider.java
@@ -0,0 +1,27 @@
+package de.endrullis.idea.postfixtemplates.languages.swift;
+
+import com.intellij.codeInsight.template.postfix.templates.PostfixTemplateProvider;
+import de.endrullis.idea.postfixtemplates.language.psi.CptMapping;
+import de.endrullis.idea.postfixtemplates.templates.CustomPostfixTemplateProvider;
+import org.jetbrains.annotations.NotNull;
+
+public class SwiftPostfixTemplateProvider extends CustomPostfixTemplateProvider {
+
+ @NotNull
+ @Override
+ protected String getLanguage() {
+ return "swift";
+ }
+
+ @Override
+ public String getPluginClassName() {
+ return "com.intellij.lang.swift.template.postfix.JSPostfixTemplateUtils";
+ }
+
+ @NotNull
+ @Override
+ protected CustomSwiftStringPostfixTemplate createTemplate(CptMapping mapping, String matchingClass, String conditionClass, String templateName, String description, String template, PostfixTemplateProvider provider) {
+ return new CustomSwiftStringPostfixTemplate(matchingClass, templateName, description, template, provider, mapping);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftPostfixTemplatesUtils.java b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftPostfixTemplatesUtils.java
new file mode 100644
index 00000000..3aaeafc0
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/languages/swift/SwiftPostfixTemplatesUtils.java
@@ -0,0 +1,32 @@
+package de.endrullis.idea.postfixtemplates.languages.swift;
+
+import com.intellij.codeInsight.completion.CompletionParameters;
+import com.intellij.codeInsight.completion.CompletionResultSet;
+import com.intellij.lang.javascript.index.JavaScriptIndex;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utilities for PHP postfix templates.
+ *
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+class SwiftPostfixTemplatesUtils {
+
+ /*
+ static boolean isInstanceOf(@NotNull JavaScriptTypeHelper subType, @NotNull PhpType superType, PsiElement psiElement) {
+ JavaScriptTypeHelper.getInstance().getTypeForIndexing(psiElement);
+
+ return superType.isConvertibleFrom(subType, PhpIndex.getInstance(psiElement.getProject()));
+ }
+ */
+
+ static boolean isProjectClass(@NotNull String conditionClass, PsiElement e) {
+ return JavaScriptIndex.getInstance(e.getProject()).getClassByName(conditionClass, true) != null;
+ }
+
+ static void addCompletions(CompletionParameters parameters, CompletionResultSet resultSet) {
+ // TODO
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/settings/AddTemplateFileDialog.form b/src/de/endrullis/idea/postfixtemplates/settings/AddTemplateFileDialog.form
new file mode 100644
index 00000000..72d6913f
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/settings/AddTemplateFileDialog.form
@@ -0,0 +1,71 @@
+
+
diff --git a/src/de/endrullis/idea/postfixtemplates/settings/AddTemplateFileDialog.java b/src/de/endrullis/idea/postfixtemplates/settings/AddTemplateFileDialog.java
new file mode 100644
index 00000000..e4c1d64e
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/settings/AddTemplateFileDialog.java
@@ -0,0 +1,211 @@
+package de.endrullis.idea.postfixtemplates.settings;
+
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.languages.SupportedLanguages;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._List;
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._Set;
+
+public class AddTemplateFileDialog extends DialogWrapper {
+ private final Project project;
+ private final CptLang lang;
+ private final CptVirtualFile cptVirtualFile;
+ private final Set otherFileNames;
+
+ private JPanel contentPane;
+ private JComboBox typeField;
+ private JTextField urlField;
+ private JButton fileChooserButton;
+ private JTextField nameField;
+ private JLabel urlLabel;
+
+ public AddTemplateFileDialog(Project project, @NotNull CptLang lang, @Nullable CptVirtualFile cptVirtualFile, @NotNull Set otherFileNames) {
+ super(project);
+ this.project = project;
+ this.lang = lang;
+ this.cptVirtualFile = cptVirtualFile;
+ this.otherFileNames = otherFileNames;
+
+ val url = cptVirtualFile != null ? cptVirtualFile.getUrl() : null;
+
+ setTitle(url == null ? "Add Template File" : "Edit Template File");
+
+ //setContentPane(contentPane);
+ //setModal(true);
+
+ typeField.setModel(new DefaultComboBoxModel<>(CptFileType.values()));
+ typeField.addActionListener(e -> {
+ val selectedItem = (CptFileType) typeField.getSelectedItem();
+ fileChooserButton.setVisible(selectedItem != CptFileType.Web);
+ urlLabel.setEnabled(selectedItem != CptFileType.LocalInPluginDir);
+ urlField.setEnabled(selectedItem != CptFileType.LocalInPluginDir);
+ fileChooserButton.setEnabled(selectedItem != CptFileType.LocalInPluginDir);
+ if (selectedItem == CptFileType.LocalInPluginDir) {
+ urlField.setText("");
+ }
+ });
+
+ fileChooserButton.setVisible(false);
+ fileChooserButton.addActionListener(e -> onChooseFile());
+
+ if (url == null) {
+ typeField.setSelectedItem(CptFileType.LocalInPluginDir);
+ } else if (url.getProtocol().equals("file")) {
+ typeField.setSelectedItem(CptFileType.LocalInFs);
+ } else {
+ typeField.setSelectedItem(CptFileType.Web);
+ }
+
+ if (url != null) {
+ urlField.setText(url.toString());
+ }
+
+ if (cptVirtualFile != null) {
+ nameField.setText(cptVirtualFile.getFile().getName().replace(".postfixTemplates", ""));
+ }
+
+ // call onCancel() on ESCAPE
+ //contentPane.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+
+ if (project != null) {
+ init();
+ }
+ }
+
+ private void onChooseFile() {
+ final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
+ private final List extensions = _List("postfixTemplates");
+
+ @Override
+ public boolean isFileSelectable(VirtualFile virtualFile) {
+ return virtualFile != null && extensions.contains(virtualFile.getExtension());
+ }
+
+ @Override
+ public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
+ return super.isFileVisible(file, showHiddenFiles) && (file.isDirectory() || isFileSelectable(file));
+ }
+ };
+
+ val virtualFile = FileChooser.chooseFile(descriptor, project, null);
+
+ if (virtualFile != null) {
+ urlField.setText(virtualFile.getUrl());
+ nameField.setText(virtualFile.getNameWithoutExtension());
+ }
+ }
+
+ public CptVirtualFile getCptVirtualFile() {
+ try {
+ URL oldUrl = null;
+ File oldFile = null;
+ boolean isNew = true;
+
+ if (cptVirtualFile != null) {
+ oldUrl = cptVirtualFile.getOldUrl();
+ if (oldUrl == null) {
+ oldUrl = cptVirtualFile.getUrl();
+ }
+ oldFile = cptVirtualFile.getOldFile();
+ if (oldFile == null) {
+ oldFile = cptVirtualFile.getFile();
+ }
+ isNew = cptVirtualFile.isNew();
+ }
+
+ val urlString = urlField.getText().trim().isEmpty() ? null : urlField.getText();
+ val newUrl = urlString != null ? new URI(urlString).toURL() : null;
+ val newFile = CptUtil.getTemplateFile(lang.getLanguage(), nameField.getText().trim());
+
+ val newCptVirtualFile = new CptVirtualFile(null, newUrl, newFile, isNew);
+
+ if (!isNew && newUrl != null && oldUrl != null && !newUrl.toString().equals(oldUrl.toString())) {
+ newCptVirtualFile.setOldUrl(oldUrl);
+ }
+ if (!isNew && oldFile != null && !FileUtil.filesEqual(newFile, oldFile)) {
+ newCptVirtualFile.setOldFile(oldFile);
+ }
+
+ return newCptVirtualFile;
+ } catch (MalformedURLException | URISyntaxException ignored) {
+ return null;
+ }
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return contentPane;
+ }
+
+ public static void main(String[] args) {
+ val dialog = new AddTemplateFileDialog(null, Objects.requireNonNull(SupportedLanguages.getCptLang("java")), null, _Set());
+
+ new JDialog() {{
+ getRootPane().setContentPane(dialog.createCenterPanel());
+ setSize(500, 100);
+ setVisible(true);
+ }};
+ }
+
+ @Nullable
+ @Override
+ protected ValidationInfo doValidate() {
+ val urlString = urlField.getText().trim();
+ if (typeField.getSelectedItem() == CptFileType.LocalInFs) {
+ try {
+ //noinspection ResultOfMethodCallIgnored
+ new URI(urlString).toURL();
+ } catch (URISyntaxException | MalformedURLException e) {
+ return new ValidationInfo("Please select an existing file.", fileChooserButton);
+ }
+ }
+
+ if (typeField.getSelectedItem() == CptFileType.Web) {
+ try {
+ //noinspection ResultOfMethodCallIgnored
+ new URI(urlString).toURL();
+ } catch (URISyntaxException | MalformedURLException e) {
+ return new ValidationInfo("Please enter a valid URL.", urlField);
+ }
+ }
+
+ final String name = nameField.getText().trim();
+ if (name.isEmpty()) {
+ return new ValidationInfo("Please enter a name for the local file.", nameField);
+ }
+ if (otherFileNames.contains(name)) {
+ return new ValidationInfo("This name is already used. Please choose another one.", nameField);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return nameField;
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/settings/CptApplicationSettings.java b/src/de/endrullis/idea/postfixtemplates/settings/CptApplicationSettings.java
new file mode 100644
index 00000000..a381fff7
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/settings/CptApplicationSettings.java
@@ -0,0 +1,150 @@
+package de.endrullis.idea.postfixtemplates.settings;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.util.messages.Topic;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import com.intellij.util.xmlb.annotations.Property;
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.languages.SupportedLanguages;
+import lombok.Getter;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static de.endrullis.idea.postfixtemplates.language.CptUtil.downloadWebTemplateFile;
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._List;
+
+@State(
+ name = "CustomPostfixTemplatesApplicationSettings",
+ storages = @Storage("customPostfixTemplates.xml")
+)
+public class CptApplicationSettings implements PersistentStateComponent, CptPluginSettings.Holder {
+
+ @Getter
+ private final State state = new State();
+
+ public CptApplicationSettings() {
+ }
+
+ @NotNull
+ public static CptApplicationSettings getInstance() {
+ return ApplicationManager.getApplication().getService(CptApplicationSettings.class);
+ }
+
+ @Override
+ public void loadState(@NotNull State state) {
+ XmlSerializerUtil.copyBean(state, this.state);
+ }
+
+ @Override
+ public void setPluginSettings(@NotNull CptPluginSettings settings) {
+ val oldVarLambdaStyle = state.pluginSettings.isVarLambdaStyle();
+
+ state.pluginSettings = settings;
+
+ val lambdaStyleChanged = oldVarLambdaStyle != settings.isVarLambdaStyle();
+
+ settingsChanged(lambdaStyleChanged);
+ }
+
+ public void setPluginSettingsExternally(@NotNull CptPluginSettings settings) {
+ CptPluginSettingsForm.resetLastTreeState();
+ setPluginSettings(settings);
+ }
+
+ /**
+ * This method is called after the user changed some settings and saved them.
+ *
+ * @param lambdaStyleChanged indicates if lambda style has changed
+ */
+ private void settingsChanged(boolean lambdaStyleChanged) {
+ // check changes and eventually update file tree
+ val lastTreeState = CptPluginSettingsForm.getLastTreeState();
+
+ if (lastTreeState != null) {
+ List changedFiles = new ArrayList<>();
+
+ for (CptLang cptLang : SupportedLanguages.supportedLanguages) {
+ val cptVirtualFiles = lastTreeState.getOrDefault(cptLang, _List());
+
+ for (CptVirtualFile cptVirtualFile : cptVirtualFiles) {
+ try {
+ boolean needsUpdate = false;
+
+ if (cptVirtualFile.getFile() != null) {
+ createParent(cptVirtualFile.getFile());
+ }
+
+ if (cptVirtualFile.isNew() || !cptVirtualFile.getFile().exists() || lambdaStyleChanged) {
+ //noinspection ResultOfMethodCallIgnored
+ cptVirtualFile.getFile().createNewFile();
+
+ needsUpdate = cptVirtualFile.getUrl() != null;
+ }
+ if (cptVirtualFile.fileHasChanged()) {
+ cptVirtualFile.getOldFile().renameTo(cptVirtualFile.getFile());
+ }
+ if (cptVirtualFile.urlHasChanged()) {
+ needsUpdate = true;
+ }
+
+ if (needsUpdate) {
+ downloadWebTemplateFile(cptVirtualFile);
+ changedFiles.add(cptVirtualFile.getFile());
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ val filesInTree = cptVirtualFiles.stream().map(f -> f.getFile().getAbsolutePath()).collect(Collectors.toSet());
+
+ // delete the files that has been removed from the tree
+ Arrays.stream(CptUtil.getTemplateFilesFromLanguageDir(cptLang.getLanguage()))
+ .filter(f -> !filesInTree.contains(f.getAbsolutePath()))
+ .forEach(f -> f.delete());
+ }
+
+ LocalFileSystem.getInstance().refreshIoFiles(changedFiles);
+ }
+
+ ApplicationManager.getApplication().getMessageBus().syncPublisher(SettingsChangedListener.TOPIC).onSettingsChange(this);
+ }
+
+ private void createParent(File file) {
+ file.getParentFile().mkdirs();
+ }
+
+ @NotNull
+ @Override
+ public CptPluginSettings getPluginSettings() {
+ state.pluginSettings.upgrade();
+ return state.pluginSettings;
+ }
+
+
+ public static class State {
+ @Property(surroundWithTag = false)
+ @NotNull
+ private CptPluginSettings pluginSettings = CptPluginSettings.DEFAULT;
+ }
+
+ public interface SettingsChangedListener {
+ Topic TOPIC = Topic.create("CustomPostfixTemplatesApplicationSettingsChanged", SettingsChangedListener.class);
+
+ void reloadTemplates();
+
+ void onSettingsChange(@NotNull CptApplicationSettings settings);
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/settings/CptFileType.java b/src/de/endrullis/idea/postfixtemplates/settings/CptFileType.java
new file mode 100644
index 00000000..4595849e
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/settings/CptFileType.java
@@ -0,0 +1,20 @@
+package de.endrullis.idea.postfixtemplates.settings;
+
+/**
+ * @author Stefan Endrullis <stefan@endrullis.de>
+ */
+public enum CptFileType {
+
+ LocalInPluginDir, LocalInFs, Web;
+
+ @Override
+ public String toString() {
+ return switch (this) {
+ case LocalInPluginDir -> "User template file (local file in plugin directory)";
+ case LocalInFs -> "Local file in filesystem";
+ case Web -> "Web template file (URL)";
+ };
+
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/settings/CptManagementTree.java b/src/de/endrullis/idea/postfixtemplates/settings/CptManagementTree.java
new file mode 100644
index 00000000..6314788a
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/settings/CptManagementTree.java
@@ -0,0 +1,569 @@
+package de.endrullis.idea.postfixtemplates.settings;
+
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.ListPopup;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.*;
+import com.intellij.util.Consumer;
+import com.intellij.util.ObjectUtils;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.util.ui.tree.TreeUtil;
+import de.endrullis.idea.postfixtemplates.language.CptLang;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.languages.SupportedLanguages;
+import de.endrullis.idea.postfixtemplates.utils.Tuple2;
+import lombok.val;
+import org.apache.commons.lang3.ArrayUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static de.endrullis.idea.postfixtemplates.language.CptUtil.downloadWebTemplateFile;
+import static de.endrullis.idea.postfixtemplates.language.CptUtil.findProject;
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils.$;
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils._List;
+
+public class CptManagementTree extends CheckboxTree implements Disposable {
+ @NotNull
+ private final DefaultTreeModel model;
+ @NotNull
+ private final CheckedTreeNode root;
+
+ private final boolean canAddFile = true;
+ private String lastFileId;
+ private Tuple2 nextMissingWtf;
+ private Iterator> missingWtfIter;
+
+ private final TreeSelectionListener treeSelectionListener;
+ private final DoubleClickListener doubleClickListener;
+
+ CptManagementTree() {
+ super(getRenderer(), new CheckedTreeNode(null));
+ //canAddFile = ContainerUtil.find(providerToLanguage.keySet(), p -> StringUtil.isNotEmpty(p.getPresentableName())) != null;
+ model = (DefaultTreeModel) getModel();
+ root = (CheckedTreeNode) model.getRoot();
+
+ treeSelectionListener = event -> selectionChanged();
+ getSelectionModel().addTreeSelectionListener(treeSelectionListener);
+ doubleClickListener = new DoubleClickListener() {
+ @Override
+ protected boolean onDoubleClick(@NotNull MouseEvent event) {
+ TreePath location = getClosestPathForLocation(event.getX(), event.getY());
+ return location != null && doubleClick(location.getLastPathComponent());
+ }
+ };
+ doubleClickListener.installOn(this);
+ setRootVisible(false);
+ setShowsRootHandles(true);
+ }
+
+ @Override
+ protected void onDoubleClick(CheckedTreeNode node) {
+ doubleClick(node);
+ }
+
+ private boolean doubleClick(@Nullable Object node) {
+ if (node instanceof FileTreeNode && isEditable(((FileTreeNode) node).getFile())) {
+ editFile((FileTreeNode) node);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void dispose() {
+ getSelectionModel().removeTreeSelectionListener(treeSelectionListener);
+ doubleClickListener.uninstall(this);
+ UIUtil.dispose(this);
+ }
+
+ @NotNull
+ private static CheckboxTreeCellRenderer getRenderer() {
+ return new CheckboxTreeCellRenderer() {
+ @Override
+ public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+ if (!(value instanceof CheckedTreeNode node)) return;
+
+ final Color background = selected ? UIUtil.getTreeSelectionBackground(true) : UIUtil.getTreeBackground();
+ FileTreeNode cptTreeNode = ObjectUtils.tryCast(node, FileTreeNode.class);
+
+ SimpleTextAttributes attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES;
+ if (cptTreeNode != null) {
+ if (cptTreeNode.getFile().isSelfMade()) {
+ // green
+ Color color = DefaultLanguageHighlighterColors.STRING.getDefaultAttributes().getForegroundColor();
+ getTextRenderer().append("[user] ", new SimpleTextAttributes(background, color, color, attributes.getStyle()));
+ } else if (cptTreeNode.getFile().isLocal()) {
+ // gray on white, yellow and black
+ Color color = DefaultLanguageHighlighterColors.METADATA.getDefaultAttributes().getForegroundColor();
+ getTextRenderer().append("[local] ", new SimpleTextAttributes(background, color, color, attributes.getStyle()));
+ } else {
+ // blue on white, orange on black
+ Color yellow = DefaultLanguageHighlighterColors.KEYWORD.getDefaultAttributes().getForegroundColor();
+ getTextRenderer().append("[web] ", new SimpleTextAttributes(background, yellow, yellow, attributes.getStyle()));
+ }
+
+ //Color fgColor = cptTreeNode.isChanged() || cptTreeNode.isNew() ? JBColor.BLUE : null;
+ Color fgColor = cptTreeNode.getFile().hasChanged() ? JBColor.BLUE : null;
+ attributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, fgColor);
+ }
+ getTextRenderer().append(StringUtil.notNullize(value.toString()),
+ new SimpleTextAttributes(background, attributes.getFgColor(), JBColor.RED, attributes.getStyle()));
+
+ if (cptTreeNode != null) {
+ URL url = cptTreeNode.getFile().getUrl();
+ if (url != null) {
+ getTextRenderer().append(" " + url, new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, JBColor.GRAY), false);
+ }
+ }
+ }
+ };
+ }
+
+ protected void selectionChanged() {
+
+ }
+
+ public void initTree(@NotNull Map> lang2files, boolean activateNewFiles) {
+ root.removeAllChildren();
+
+ val lang2webTemplateFiles = Arrays.stream(CptUtil.loadWebTemplateFiles(findProject(this))).collect(Collectors.groupingBy(f -> f.lang));
+
+ for (Map.Entry> entry : lang2files.entrySet()) {
+ val lang = entry.getKey();
+ val vFiles = entry.getValue().stream().filter(f -> new File(f.file).exists()).toList();
+ val langNode = findOrCreateLangNode(lang);
+ val vFileIds = vFiles.stream().map(f -> f.id).collect(Collectors.toSet());
+ val webTemplateFiles = lang2webTemplateFiles.getOrDefault(lang.getLanguage(), _List());
+ val id2webTemplateFile = webTemplateFiles.stream().collect(Collectors.toMap(e -> e.id, e -> e));
+ val previousId2missingWebTemplateFile = new ArrayList>();
+
+ for (int i = 0; i < webTemplateFiles.size(); i++) {
+ val f = webTemplateFiles.get(i);
+ if (!vFileIds.contains(f.id)) {
+ val previousId = i == 0 ? null : webTemplateFiles.get(i-1).id;
+ previousId2missingWebTemplateFile.add($(previousId, f));
+ }
+ }
+ missingWtfIter = previousId2missingWebTemplateFile.iterator();
+ nextMissingWtf = missingWtfIter.hasNext() ? missingWtfIter.next() : null;
+
+ lastFileId = null;
+
+ tryAddingMissingWebTemplateFiles(lang, langNode, activateNewFiles);
+
+ // add the other (old) nodes to the tree
+ for (CptPluginSettings.VFile vFile : vFiles) {
+ val webTemplateFile = id2webTemplateFile.get(vFile.id);
+ val fileId = vFile.getFile().replaceFirst(".*[/\\\\]", "").replace(".postfixTemplates", "");
+
+ // if the file has an ID which is no longer present in the web template files -> skip the file
+ if (vFile.id != null && webTemplateFile == null || vFile.id == null && id2webTemplateFile.containsKey(fileId)) {
+ continue;
+ }
+
+ URL url = null;
+ try {
+ url = vFile.id != null ? new URL(webTemplateFile.url) :
+ vFile.url != null ? new URL(vFile.url) : null;
+ } catch (MalformedURLException ignored) {
+ }
+ val cptFile = new CptVirtualFile(vFile.id, url, new File(vFile.file));
+ cptFile.setWebTemplateFile(webTemplateFile);
+ lastFileId = vFile.id;
+
+ val node = new FileTreeNode(lang, cptFile);
+ node.setChecked(vFile.enabled);
+
+ langNode.add(node);
+
+ tryAddingMissingWebTemplateFiles(lang, langNode, activateNewFiles);
+ }
+ }
+
+ model.nodeStructureChanged(root);
+ TreeUtil.expandAll(this);
+ }
+
+ private void tryAddingMissingWebTemplateFiles(CptLang lang, DefaultMutableTreeNode langNode, boolean activateNewFiles) {
+ if (nextMissingWtf != null) {
+ if (Objects.equals(nextMissingWtf._1, lastFileId)) {
+ WebTemplateFile webTemplateFile = nextMissingWtf._2;
+ try {
+ CptVirtualFile cptFile = new CptVirtualFile(webTemplateFile.id, new URL(webTemplateFile.url), CptUtil.getTemplateFile(lang.getLanguage(), webTemplateFile.id), true);
+ lastFileId = webTemplateFile.id;
+ downloadWebTemplateFile(cptFile);
+
+ FileTreeNode node = new FileTreeNode(lang, cptFile);
+ node.setChecked(activateNewFiles);
+
+ langNode.add(node);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ nextMissingWtf = missingWtfIter.hasNext() ? missingWtfIter.next() : null;
+
+ tryAddingMissingWebTemplateFiles(lang, langNode, activateNewFiles);
+ }
+ }
+ }
+
+ @Nullable
+ public CptVirtualFile getSelectedFile() {
+ TreePath path = getSelectionModel().getSelectionPath();
+ return getFileFromPath(path);
+ }
+
+ @Nullable
+ private static CptVirtualFile getFileFromPath(@Nullable TreePath path) {
+ if (path == null || !(path.getLastPathComponent() instanceof FileTreeNode)) {
+ return null;
+ }
+ return ((FileTreeNode) path.getLastPathComponent()).getFile();
+ }
+
+ public void selectFile(@NotNull final CptVirtualFile file) {
+ visitFileNodes(node -> {
+ if (file.equals(node.getFile())) {
+ TreeUtil.selectInTree(node, true, this, true);
+ }
+ });
+ }
+
+ private void visitFileNodes(@NotNull Consumer consumer) {
+ val languages = root.children();
+ while (languages.hasMoreElements()) {
+ CheckedTreeNode langNode = (CheckedTreeNode) languages.nextElement();
+ val fileNodes = langNode.children();
+ while (fileNodes.hasMoreElements()) {
+ Object fileNode = fileNodes.nextElement();
+ if (fileNode instanceof FileTreeNode) {
+ consumer.consume((FileTreeNode) fileNode);
+ }
+ }
+ }
+ }
+
+ public boolean canAddFile() {
+ return canAddFile;
+ }
+
+ public void addFile(@NotNull AnActionButton button) {
+ val group = new DefaultActionGroup() {{
+ for (CptLang lang : SupportedLanguages.supportedLanguages) {
+ add(new DumbAwareAction(lang.getNiceName()) {
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
+ val project = anActionEvent.getProject();
+ openFileEditDialog(project, lang, null);
+ }
+ });
+ }
+ }};
+
+ /*
+ for (Map.Entry entry : myProviderToLanguage.entrySet()) {
+ CptLang provider = entry.getKey();
+ String providerName = provider.getPresentableName();
+ if (StringUtil.isEmpty(providerName)) continue;
+ group.add(new DumbAwareAction(providerName) {
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ PostfixTemplateEditor editor = provider.createEditor(null);
+ if (editor != null) {
+ PostfixEditTemplateDialog dialog = new PostfixEditTemplateDialog(com.intellij.codeInsight.template.postfix.settings.PostfixTemplatesCheckboxTree.this, editor, providerName, null);
+ if (dialog.showAndGet()) {
+ String templateKey = dialog.getTemplateName();
+ String templateId = PostfixTemplatesUtils.generateTemplateId(templateKey, provider);
+ CptVirtualFile createdTemplate = editor.createTemplate(templateId, templateKey);
+
+ CptTreeNode createdNode = new CptTreeNode(createdTemplate, provider, true);
+ DefaultMutableTreeNode languageNode = findOrCreateLangNode(entry.getValue());
+ languageNode.add(createdNode);
+ myModel.nodeStructureChanged(languageNode);
+ TreeUtil.selectNode(com.intellij.codeInsight.template.postfix.settings.PostfixTemplatesCheckboxTree.this, createdNode);
+ }
+ }
+ }
+ });
+ }
+ */
+
+ DataContext context = DataManager.getInstance().getDataContext(button.getContextComponent());
+ ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup(null, group, context,
+ JBPopupFactory.ActionSelectionAid.ALPHA_NUMBERING, true, null);
+ popup.show(Objects.requireNonNull(button.getPreferredPopupPoint()));
+ }
+
+ public void openFileEditDialog(Project project, CptLang lang, FileTreeNode fileNode) {
+ val addNewNode = fileNode == null;
+ val cptVirtualFile = addNewNode ? null : fileNode.getFile();
+ val dialog = new AddTemplateFileDialog(project, lang, cptVirtualFile, getOtherFileNames(lang, fileNode));
+ dialog.show();
+
+ if (dialog.isOK()) {
+ val newCptVirtualFile = dialog.getCptVirtualFile();
+
+ // are we adding a new node?
+ if (addNewNode) {
+ val langNode = findOrCreateLangNode(lang);
+ val newNode = new FileTreeNode(lang, newCptVirtualFile);
+
+ langNode.add(newNode);
+ model.nodeStructureChanged(langNode);
+ TreeUtil.selectNode(this, newNode);
+ } else {
+ fileNode.setFile(newCptVirtualFile);
+ }
+ }
+ }
+
+ private Set getOtherFileNames(CptLang lang, FileTreeNode fileNode) {
+ val langNode = findOrCreateLangNode(lang);
+
+ return TreeUtil.nodeChildren(langNode)
+ .filter(n -> n != fileNode)
+ .map(n -> ((FileTreeNode) n).getFile().getName().replace(".postfixTemplates", ""))
+ .toSet();
+ }
+
+ public boolean canEditSelectedFile() {
+ TreePath[] selectionPaths = getSelectionModel().getSelectionPaths();
+ return (selectionPaths == null || selectionPaths.length <= 1) && isEditable(getSelectedFile());
+ }
+
+ public void editSelectedFile() {
+ TreePath path = getSelectionModel().getSelectionPath();
+ Object lastPathComponent = path.getLastPathComponent();
+ if (lastPathComponent instanceof FileTreeNode) {
+ editFile((FileTreeNode) lastPathComponent);
+ }
+ }
+
+ private void editFile(@NotNull FileTreeNode fileNode) {
+ val file = fileNode.getFile();
+
+ if (isEditable(file)) {
+ val project = CptUtil.findProject(this);
+ openFileEditDialog(project, fileNode.getLang(), fileNode);
+
+ model.nodeChanged(fileNode);
+
+ /*
+ PostfixTemplateEditor editor = lang.createEditor(fileToEdit);
+ if (editor == null) {
+ editor = new DefaultPostfixTemplateEditor(lang, fileToEdit);
+ }
+ String providerName = StringUtil.notNullize(lang.getPresentableName());
+ PostfixEditTemplateDialog dialog = new PostfixEditTemplateDialog(this, editor, providerName, fileToEdit);
+ if (dialog.showAndGet()) {
+ CptVirtualFile newTemplate = editor.createTemplate(file.getId(), dialog.getTemplateName());
+ if (newTemplate.equals(file)) {
+ return;
+ }
+ if (file.isBuiltin()) {
+ CptVirtualFile builtin = file instanceof PostfixChangedBuiltinTemplate
+ ? ((PostfixChangedBuiltinTemplate) file).getBuiltinTemplate()
+ : fileToEdit;
+ fileNode.setTemplate(new PostfixChangedBuiltinTemplate(newTemplate, builtin));
+ } else {
+ fileNode.setTemplate(newTemplate);
+ }
+ myModel.nodeStructureChanged(fileNode);
+ }
+ */
+ }
+ }
+
+ public boolean canRemoveSelectedFiles() {
+ TreePath[] paths = getSelectionModel().getSelectionPaths();
+ if (paths == null) {
+ return false;
+ }
+ for (TreePath path : paths) {
+ CptVirtualFile file = getFileFromPath(path);
+ if (isEditable(file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void removeSelectedFiles() {
+ TreePath[] paths = getSelectionModel().getSelectionPaths();
+ if (paths == null) {
+ return;
+ }
+ for (TreePath path : paths) {
+ FileTreeNode lastPathComponent = ObjectUtils.tryCast(path.getLastPathComponent(), FileTreeNode.class);
+
+ if (lastPathComponent == null) continue;
+
+ /*
+ val checkBox = new JCheckBox("Delete file from filesystem");
+ val dialog = new DialogWrapper(this, false) {
+ {
+ setTitle("Remove file?");
+ init();
+ }
+
+ @Override
+ protected JComponent createCenterPanel() {
+ return checkBox;
+ }
+
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return checkBox;
+ }
+ };
+ dialog.show();
+
+ if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
+ if (checkBox.isSelected()) {
+ CptVirtualFile file = lastPathComponent.getFile();
+ file.getFile().delete();
+ }
+ TreeUtil.removeLastPathComponent(this, path);
+ }
+ */
+
+ TreeUtil.removeLastPathComponent(this, path);
+ }
+ }
+
+ public void moveDownSelectedFiles() {
+ moveSelectedNodes(1);
+ }
+
+ public void moveUpSelectedFiles() {
+ moveSelectedNodes(-1);
+ }
+
+ private void moveSelectedNodes(int direction) {
+ val paths = getSelectionModel().getSelectionPaths();
+ if (paths == null || !canMoveSelectedFiles()) {
+ return;
+ }
+ val sortedPaths = Arrays.stream(paths).sorted(Comparator.comparing(path -> {
+ val fileNode = (MutableTreeNode) path.getLastPathComponent();
+ val parentNode = (MutableTreeNode) path.getParentPath().getLastPathComponent();
+
+ return model.getIndexOfChild(parentNode, fileNode);
+ })).toArray(i -> new TreePath[i]);
+
+ if (direction > 0) {
+ ArrayUtils.reverse(sortedPaths);
+ }
+
+ for (TreePath path : sortedPaths) {
+ val fileNode = (MutableTreeNode) path.getLastPathComponent();
+ val parentNode = (MutableTreeNode) path.getParentPath().getLastPathComponent();
+
+ if (getModel() instanceof DefaultTreeModel model) {
+ val index = model.getIndexOfChild(parentNode, fileNode) + direction;
+
+ if (index >= 0 && index < model.getChildCount(parentNode)) {
+ TreeUtil.removeLastPathComponent(this, path);
+ model.insertNodeInto(fileNode, parentNode, index);
+ }
+ }
+ }
+
+ getSelectionModel().setSelectionPaths(paths);
+ selectionChanged();
+ }
+
+ public boolean canMoveSelectedFiles() {
+ val paths = getSelectionPaths();
+ if (paths == null) {
+ return false;
+ }
+ if (Arrays.stream(paths).allMatch(path -> path.getLastPathComponent() instanceof FileTreeNode)) {
+ // ensure all file nodes have the same parent
+ return Arrays.stream(paths).map(path -> path.getParentPath().getLastPathComponent()).collect(Collectors.toSet()).size() == 1;
+ }
+ return false;
+ }
+
+ private static boolean isEditable(@Nullable CptVirtualFile file) {
+ return file != null && file.getId() == null;
+ }
+
+ @NotNull
+ private DefaultMutableTreeNode findOrCreateLangNode(CptLang lang) {
+ DefaultMutableTreeNode find = TreeUtil.findNode(root, n ->
+ n instanceof LangTreeNode && lang.equals(((LangTreeNode) n).getLang()));
+
+ if (find != null) {
+ return find;
+ }
+
+ CheckedTreeNode languageNode = new LangTreeNode(lang);
+ root.add(languageNode);
+
+ return languageNode;
+ }
+
+ @NotNull
+ HashMap> getState() {
+ HashMap> state = new HashMap<>();
+
+ visitFileNodes(n -> {
+ state.computeIfAbsent(n.getLang(), e -> new ArrayList<>()).add(n.getFile());
+ });
+
+ return state;
+ }
+
+ @NotNull
+ HashMap> getExport() {
+ HashMap> export = new HashMap<>();
+
+ val templatesPath = CptUtil.getTemplatesPath().getAbsolutePath();
+
+ visitFileNodes(n -> {
+ val url = n.getFile().getUrl() != null ? n.getFile().getUrl().toString() : null;
+ val filePath = n.getFile().getFile().getAbsolutePath().replace(templatesPath, "${PLUGIN}");
+ val vFile = new CptPluginSettings.VFile(n.isChecked(), n.getFile().getId(), url, filePath);
+ export.computeIfAbsent(n.getLang().getLanguage(), e -> new ArrayList<>()).add(vFile);
+ });
+
+ return export;
+ }
+
+ private static class LangTreeNode extends CheckedTreeNode {
+ @NotNull
+ private final CptLang lang;
+
+ LangTreeNode(@NotNull CptLang lang) {
+ super(lang.getNiceName());
+ this.lang = lang;
+ }
+
+ @NotNull
+ public CptLang getLang() {
+ return lang;
+ }
+ }
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/settings/CptPluginConfigurable.java b/src/de/endrullis/idea/postfixtemplates/settings/CptPluginConfigurable.java
new file mode 100644
index 00000000..c5a66337
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/settings/CptPluginConfigurable.java
@@ -0,0 +1,83 @@
+package de.endrullis.idea.postfixtemplates.settings;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.util.Disposer;
+import de.endrullis.idea.postfixtemplates.bundle.PostfixTemplatesBundle;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class CptPluginConfigurable implements SearchableConfigurable, Configurable.NoScroll {
+ @Nullable
+ private CptPluginSettingsForm form = null;
+
+ @NotNull
+ @Override
+ public String getId() {
+ return "Settings.CustomPostfixTemplates";
+ }
+
+ @Nullable
+ @Override
+ public Runnable enableSearch(String s) {
+ return null;
+ }
+
+ @Nls
+ @Override
+ public String getDisplayName() {
+ return PostfixTemplatesBundle.message("settings.plugin.name");
+ }
+
+ @Nullable
+ @Override
+ public String getHelpTopic() {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public JComponent createComponent() {
+ return getForm().getComponent();
+ }
+
+ @NotNull
+ public CptPluginSettingsForm getForm() {
+ if (form == null) {
+ form = new CptPluginSettingsForm();
+ }
+ return form;
+ }
+
+ @Override
+ public boolean isModified() {
+ return !getForm().getPluginSettings().equals(getPluginApplicationSettings().getPluginSettings());
+ }
+
+ @Override
+ public void apply() {
+ getPluginApplicationSettings().setPluginSettings(getForm().getPluginSettings());
+ }
+
+ @Override
+ public void reset() {
+ getForm().setPluginSettings(getPluginApplicationSettings().getPluginSettings());
+ }
+
+ @Override
+ public void disposeUIResources() {
+ if (form != null) {
+ Disposer.dispose(form);
+ }
+ form = null;
+ }
+
+ private CptApplicationSettings getPluginApplicationSettings(){
+ return ApplicationManager.getApplication().getService(CptApplicationSettings.class);
+ }
+
+}
diff --git a/src/de/endrullis/idea/postfixtemplates/settings/CptPluginSettings.java b/src/de/endrullis/idea/postfixtemplates/settings/CptPluginSettings.java
new file mode 100644
index 00000000..6d27cbcc
--- /dev/null
+++ b/src/de/endrullis/idea/postfixtemplates/settings/CptPluginSettings.java
@@ -0,0 +1,125 @@
+package de.endrullis.idea.postfixtemplates.settings;
+
+import com.intellij.util.xmlb.annotations.Attribute;
+import com.intellij.util.xmlb.annotations.MapAnnotation;
+import de.endrullis.idea.postfixtemplates.language.CptUtil;
+import de.endrullis.idea.postfixtemplates.utils.Tuple2;
+import lombok.*;
+import org.apache.commons.lang3.SystemUtils;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static de.endrullis.idea.postfixtemplates.utils.CollectionUtils.$;
+
+@EqualsAndHashCode
+@Getter
+public final class CptPluginSettings {
+ public static final CptPluginSettings DEFAULT = new CptPluginSettings();
+
+ @Attribute("VarLambdaStyle")
+ private final boolean varLambdaStyle;
+
+ @Attribute("UpdateWebTemplatesAutomatically")
+ private final boolean updateWebTemplatesAutomatically;
+
+ @Attribute("ActivateNewWebTemplateFilesAutomatically")
+ private final boolean activateNewWebTemplateFilesAutomatically;
+
+ @Attribute("SettingsVersion")
+ private final int settingsVersion;
+
+ @MapAnnotation()
+ private final Map> langName2virtualFiles;
+
+ private transient Map file2langName;
+ private transient Map> file2langAndVFile;
+
+ private CptPluginSettings() {
+ this(true, true, true, 1, new HashMap<>());
+ }
+
+ public CptPluginSettings(boolean varLambdaStyle,
+ boolean updateWebTemplatesAutomatically,
+ boolean activateNewWebTemplateFilesAutomatically,
+ int settingsVersion,
+ @NotNull Map> langName2virtualFiles) {
+
+ this.varLambdaStyle = varLambdaStyle;
+ this.updateWebTemplatesAutomatically = updateWebTemplatesAutomatically;
+ this.activateNewWebTemplateFilesAutomatically = activateNewWebTemplateFilesAutomatically;
+ this.settingsVersion = settingsVersion;
+ this.langName2virtualFiles = langName2virtualFiles;
+ }
+
+ void upgrade() {
+ }
+
+ public Map> getLangName2virtualFiles() {
+ return langName2virtualFiles.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> {
+ return e.getValue().stream().map(f -> new VFile(f.enabled, f.id, f.url, f.file.replace("${PLUGIN}", CptUtil.getTemplatesPath().getAbsolutePath()))).collect(Collectors.toList());
+ }));
+ }
+
+ public Map getFile2langName() {
+ if (file2langName == null) {
+ file2langName = new HashMap<>();
+ for (Map.Entry> entry : getLangName2virtualFiles().entrySet()) {
+ for (VFile vFile : entry.getValue()) {
+ file2langName.put(CptUtil.fixFilePath(vFile.getFile()), entry.getKey());
+ }
+ }
+ }
+
+ return file2langName;
+ }
+
+ public Map> getFile2langAndVFile() {
+ if (file2langAndVFile == null) {
+ file2langAndVFile = new HashMap<>();
+ for (Map.Entry