├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── blank-issue.md │ └── bug_report.md ├── .gitignore ├── CONTRIBUTING.md ├── Documentation_Images ├── C_Example.png ├── OpenTOC.png ├── TreeSitterPlayground_C_Example.png ├── demo.gif └── typescript_demo.gif ├── LICENSE ├── README.md ├── lua ├── tree-setter.lua └── tree-setter │ ├── logger.lua │ ├── main.lua │ └── setter.lua ├── plugin └── tree-setter.vim └── queries ├── c └── tsetter.scm ├── cpp └── tsetter.scm ├── go └── tsetter.scm ├── java └── tsetter.scm ├── javascript └── tsetter.scm ├── php └── tsetter.scm ├── python └── tsetter.scm ├── rust └── tsetter.scm └── typescript └── tsetter.scm /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | 11 | [{Makefile,**/Makefile,runtime/doc/*.txt}] 12 | indent_style = tab 13 | indent_size = 8 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/blank-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Blank Issue 3 | about: If no issue-template suits your needs, pick this one. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Example code 11 | 12 | 13 | # Context 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Notes 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for wanting to contribute to *tree-setter*! 2 | There are different ways to contribute to this project. Pick up the section from 3 | the table of contents if the title suits your intention. Here's a little image 4 | of how to open the table of contents: 5 | 6 | ![TOC Image](./Documentation_Images/OpenTOC.png) 7 | 8 | # Write Queries 9 | ## General 10 | Thank you for wanting to write some queries in order to fill up more cases where 11 | a semicolon/comma/double point has to be set! :) 12 | This should be a little guide on how to write them, which should help you to 13 | improve `tree-setter`! 14 | 15 | ### File Structure 16 | Let's start with the file structure first, so you know where to write the 17 | queries: 18 | 19 | ``` 20 | tree-setter 21 | └── queries 22 |    ├── c 23 |    │   └── tsetter.scm 24 |    ├── cpp 25 |    │   └── tsetter.scm 26 |    └── lua 27 |    └── tsetter.scm 28 | ``` 29 | 30 | The interesting directory is `tree-setter/queries` which includes all queries 31 | for their appropriate language. Each directory (for the language) has a 32 | `tsetter.scm` file. They *have* to be named as that since `tree-setter` assumes 33 | that the files are named like that! To sum it up: 34 | 35 | 1. Look if a directory with the language name exists or not 36 | - If yes => Navigate to it 37 | - Otherwise => Create it 38 | 2. Look if there's already a `tsetter.scm` file 39 | - If yes => Open it in ~~your favourite editor~~ neovim! 40 | - Otherwise => Create it! 41 | 42 | ### Writing queries 43 | #### Crash-Course 44 | So now we're getting into the interesting part! 45 | I'm using C as the example language here since it's pretty mature. If you want 46 | to see more details about the queries (of C), open 47 | `tree-setter/queries/c/tsetter.scm` in ~~your text-editor~~ neovim (hint, it's 48 | probably worth it since they include some comments which should make it 49 | understandable) ;). 50 | 51 | Let's take a look at the following query code: 52 | 53 | ```scheme 54 | (declaration 55 | type: (_) 56 | declarator: (_) @semicolon 57 | ) 58 | ``` 59 | 60 | We can see a code-tree-structure like code. If you take a look into the tsetter 61 | file of the C language, you'll see that I picked the first query of it. 62 | But how did I find out that the query has to look like this to let TreeSitter know that this is a declaration? Well, I'm using 63 | [nvim-treesitter/playground](https://github.com/nvim-treesitter/playground) for 64 | that. Let's create a new C-file and open up the playground! It'll look like 65 | this: 66 | 67 | ![C query example](./Documentation_Images/TreeSitterPlayground_C_Example.png) 68 | 69 | As you can see, there's a similar structure on the playground: 70 | 71 | ```scheme 72 | declaration [1, 4] - [1, 10] 73 | type: primitive_type [1, 4] - [1, 7] 74 | declarator: identifier [1, 8] - [1, 9] 75 | ``` 76 | 77 | very nice! So all we need to do is just write this query down as it's 78 | displayed in the playground. 79 | 80 | **Note:** Make sure that you removed the semicolon because sometimes TreeSitter parses 81 | the query *differently* if there's a semicolon or not! 82 | 83 | `(_)` are used, because according to the 84 | [docs](https://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes) 85 | we can create anonymous nodes. But why? Well, `type` needn't be always a 86 | `primitive_type` (here an `int`). It could also be a char or something else, we 87 | don't know. So we are using an anonymous node! 88 | 89 | Ok, but how does the module know if it should add a semicolon, comma or a double 90 | point? Well, we are doing this by adding this `@`-thing which is called a 91 | "predicate". Just write after the `@` which character has to be added. If there 92 | should be a comma instead of a semicolon, then write `@comma` instead. There are 93 | the following predicates for this module which you can use: 94 | 95 | - `@semicolon` 96 | - `@comma` 97 | - `@double_points` 98 | - `@skip` 99 | - `@equals` 100 | 101 | Each predicate refers to their appropriate character as the name says. So if 102 | there should be a comma after a declaration instead, then you can write it as 103 | follows: 104 | 105 | ```scheme 106 | (declaration 107 | type: (_) 108 | declarator: (_) @comma 109 | ) 110 | ``` 111 | 112 | This will place a comma after a declaration instead of a semicolon: 113 | 114 | ![C example with a semicolon](./Documentation_Images/C_Example.png) 115 | 116 | One "exception" is the `@skip` predicate. As the name says, you say TreeSitter, and it should _not_* check in the current query if a query matches. This happens 117 | for example in the following case (also described in the last lines of the 118 | `tsetter.scm` file in C): 119 | 120 | ```c 121 | if (test() 122 | ``` 123 | 124 | without the last query from the C queries, `tree-setter` would add a semicolon 125 | after `test()` if you would hit the enter key now! This is not what we want! So 126 | `tree-setter` should skip this part, that's the usage of this `@skip`. 127 | 128 | By the way: It *is* important *where* you but the predicate, because 129 | `tree-setter` will put the semicolon, etc. on the place of the predicate! 130 | 131 | Now you should be able to write some queries for your language now! :) 132 | Please follow the 133 | [query-code-styles](./CONTRIBUTING.md#query-code-style) (below) 134 | to make it better to maintain and better to understand before creating a pull 135 | request ;) 136 | 137 | ### Tips 138 | #### General 139 | If you want to know more on how to write queries, then you can read it from the 140 | [official 141 | docs](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax). 142 | This should explain you some more features and tricks on how to write them :) 143 | 144 | I also recommend to read through `:h lua-treesitter-query` which explains it 145 | partially as well. 146 | 147 | 148 | #### "Weird" queries 149 | Some queries might be pretty problematic... Look at this query for example (also 150 | from the query file of C): 151 | 152 | ```scheme 153 | ((ERROR 154 | (call_expression 155 | function: (identifier) 156 | arguments: (argument_list) 157 | ) 158 | ) @semicolon) 159 | ``` 160 | 161 | This query is used to indicate a user-function which is called. Yes it might 162 | look weird, but that's how TreeSitter evaluates the code if the semicolon is 163 | missing, so keep an eye on the playground what it's displaying! 164 | 165 | ## Query-Code-Style 166 | Please write the queries in the following style: 167 | 168 | ```scheme 169 | 170 | ;; 171 | ;; Example(s): 172 | ;; 173 | ;; 174 | ;; 176 | 177 | ``` 178 | 179 | Here's an example of a C query of `tree-setter/queries/c/tsetter.scm`: 180 | 181 | ```scheme 182 | ;; =================== 183 | ;; Action Queries 184 | ;; =================== 185 | ;; -------------- 186 | ;; Variables 187 | ;; -------------- 188 | ;; For known declarations and initialisations 189 | ;; Example: 190 | ;; char var_name 191 | ;; int var_name = 10 192 | 193 | ;; Somehow `long` can't be seen as a declaration first, only if the semicolon is 194 | ;; added, so we have to use the query below for these cases. 195 | (declaration 196 | type: (_) 197 | declarator: (_) @semicolon 198 | ) 199 | ``` 200 | 201 | # Expanding/Improving tree-setter code 202 | So this is gonna be about the backend of `tree-setter`. You'll get a rough 203 | overview of how the code works in order to be able to extend the code! So let's 204 | start with the file structure first! 205 | 206 | ## File structure 207 | Here is the File structure with the most important files and a little description 208 | for them on the right: 209 | 210 | ``` 211 | tree-setter 212 | ├── CONTRIBUTING.md The document you're reading 213 | ├── Documentation_Images All images which are used in the documentation 214 | ├── lua The "heart" directory of this module 215 | │   ├── tree-setter 216 | │   │   ├── main.lua This file includes the main functions of the 217 | │ │ │ module like checking if any queries match for 218 | │ │ │ the current file or not and looking if the user 219 | │ │ │ hit enter. 220 | │   │   └── setter.lua This file holds only one function, which will add 221 | │ │ the appropriate character to the given line. 222 | │   └── tree-setter.lua Includes the entry point for a treesitter module 223 | │ (here: tree-setter) 224 | ├── plugin 225 | │   └── tree-setter.vim Nothing special here, it just calls the 226 | │ preparation function for the treesitter module 227 | └── queries As explained in the previous "chapter" this 228 | directory includes all queries for the given 229 | filetype 230 | ``` 231 | 232 | So let's move on to the steps! 233 | 234 | ## General 235 | When the user starts neovim, the init function in 236 | `tree-setter/lua/tree-setter.lua` is called which will look, if we have a 237 | query for the current language and looks, where the main-entry-point of the 238 | module is. Here it's `tree-setter/lua/tree-setter/main.lua`. 239 | 240 | So now we're mostly in the `tree-setter/lua/tree-setter/main.lua` file. 241 | TreeSitter will call the `TreeSetter.attach()` function which will load the 242 | appropriate query for our current language and prepares the autocommands. 243 | 244 | `tree-setter` needs to check if the user hits the enter-key. But how can we 245 | check, if the user pressed the enter key? Well, if the user pressed the enter 246 | key, then the cursor will move one line down. That's how `tree-setter` tries to 247 | detect if the enter-key is pressed without mapping any keys! 248 | 249 | The `TreeSetter.main()` function will check if the enter-key is hit, if yes, the 250 | next function comes in: `TreeSetter.add_character()`. This just picks up the 251 | node of the current cursor position and the parent to get a range to test which 252 | queries match or not. 253 | 254 | If we found a match, we're looking which kind of character we need to add 255 | according to their predicate name like `@semicolon` or `@comma`. `@skip` will 256 | stop the process which tests which queries matches. 257 | 258 | In general that's it. Take a look into the comments on the code, to get a more 259 | detailed explanation. I hope that it roughly helped you to understand the 260 | backend. Feel free to ask by creating a new issue :) 261 | -------------------------------------------------------------------------------- /Documentation_Images/C_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filNaj/tree-setter/ab9064720f0b0a001f6dca7b6eff30335618ad83/Documentation_Images/C_Example.png -------------------------------------------------------------------------------- /Documentation_Images/OpenTOC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filNaj/tree-setter/ab9064720f0b0a001f6dca7b6eff30335618ad83/Documentation_Images/OpenTOC.png -------------------------------------------------------------------------------- /Documentation_Images/TreeSitterPlayground_C_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filNaj/tree-setter/ab9064720f0b0a001f6dca7b6eff30335618ad83/Documentation_Images/TreeSitterPlayground_C_Example.png -------------------------------------------------------------------------------- /Documentation_Images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filNaj/tree-setter/ab9064720f0b0a001f6dca7b6eff30335618ad83/Documentation_Images/demo.gif -------------------------------------------------------------------------------- /Documentation_Images/typescript_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filNaj/tree-setter/ab9064720f0b0a001f6dca7b6eff30335618ad83/Documentation_Images/typescript_demo.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TreeSetter 2 | Never type an equals `=` or a semicolon `;` again! 3 | 4 | TreeSetter is an [nvim-treesitter-module](https://github.com/nvim-treesitter/module-template) which automatically adds an equals sign (`=`) and semicolons (`;`) at the appropriate place whenever you hit enter (for `;`) or the space bar (for `=`). This will help you to not look at your keyboard and type faster. The insertion of commas (`,`) and double points (`:`) is under development. 5 | 6 | This plugin is a fork of TornaxO7/tree-setter, but the original repo is incomplete and has been archived. 7 | 8 | **Note:** The plugin should work well most of the time, however since it's still very young, it is very possible that it has some bugs. 9 | 10 | **Languages:** 11 | It supports many languages. Go inside the **queries** folder to see if your language is supported. 12 | 13 | # How it works 14 | Simply press on the space bar to insert an equals sign (`=`) at the appropriate place e.g. after a variable declaration `int x = 1;` or `x = 1`. For Java and C, press enter at the end of the line to insert a semicolon (`;`). 15 | 16 | **TypeScript** demo: 17 | 18 | ![demonstration](./Documentation_Images/typescript_demo.gif) 19 | 20 | 21 | **Java**: 22 | 23 | ![demonstration](./Documentation_Images/demo.gif) 24 | 25 | # Installation 26 | **Vim-plug:** 27 | ``` bash 28 | Plug 'filNaj/tree-setter' 29 | ``` 30 | 31 | **Packer:** 32 | ``` bash 33 | use 'filNaj/tree-setter' 34 | ``` 35 | 36 | Add `tree_setter` (and not `tree_sitter` !) into your `treesitter` settings: 37 | 38 | ```lua 39 | require('nvim-treesitter.configs').setup { 40 | -- your other modules ... 41 | 42 | tree_setter = { 43 | enable = true 44 | }, 45 | 46 | -- your other modules ... 47 | } 48 | ``` 49 | 50 | # Contributing 51 | TREESETTER IS SEEKING CONTRIBUTORS TO HELP ADVANCE THE PROJECT'S GROWTH. 52 | 53 | 54 | Take a look into the [CONTRIBUTING.md](https://github.com/filNaj/tree-setter/blob/master/CONTRIBUTING.md) file for that ;) 55 | Credit to TornaxO7. 56 | 57 | 58 | ### TODO 59 | - [ ] Add colon `:` automatically, e.g. for switch cases. 60 | - [ ] Add commas `,` automatically, e.g. for lists or dictionaries. 61 | -------------------------------------------------------------------------------- /lua/tree-setter.lua: -------------------------------------------------------------------------------- 1 | local queries = require("nvim-treesitter.query") 2 | 3 | local M = {} 4 | 5 | function M.init() 6 | require("nvim-treesitter").define_modules({ 7 | tree_setter = { 8 | -- the file with the "main-code" of the module 9 | module_path = "tree-setter.main", 10 | 11 | -- Look if the module supports the current language by looking up, 12 | -- if there's an appropriate query file to it. For example if we're 13 | -- currently editing a C file, then this function looks if there's a 14 | -- "tree-setter/queries/c/tsetter.scm" file. 15 | is_supported = function(lang) 16 | return queries.get_query(lang, 'tsetter') ~= nil 17 | end 18 | } 19 | }) 20 | end 21 | 22 | return M 23 | -------------------------------------------------------------------------------- /lua/tree-setter/logger.lua: -------------------------------------------------------------------------------- 1 | local header = "[Tree-Setter]: " 2 | 3 | local logger = { 4 | error = function(message) 5 | vim.notify(header .. message, vim.log.levels.ERROR) 6 | end, 7 | info = function(message) 8 | vim.notify(header .. message, vim.log.levels.INFO) 9 | end, 10 | } 11 | 12 | return logger 13 | -------------------------------------------------------------------------------- /lua/tree-setter/main.lua: -------------------------------------------------------------------------------- 1 | -- ================= 2 | -- Requirements 3 | -- ================= 4 | local queries = require("vim.treesitter.query") 5 | local ts_utils = require("nvim-treesitter.ts_utils") 6 | 7 | -- all functions, which can modify the buffer, like adding the semicolons and 8 | -- commas 9 | local setter = require("tree-setter.setter") 10 | 11 | -- ===================== 12 | -- Global variables 13 | -- ===================== 14 | -- this variable is also known as `local M = {}` which includes the stuff of the 15 | -- module 16 | local TreeSetter = {} 17 | 18 | -- includes the queries which match with the current filetype 19 | local query 20 | 21 | -- this variable stores the last line num where the cursor was. 22 | -- It's mainly used as a control variable to check, if the cursor moved down or 23 | -- not. Take a look into the "TreeSitter.main()` function to see its usage in 24 | -- action. 25 | local last_line_num = 0 26 | local last_col_num = 0 27 | 28 | -- ============== 29 | -- Functions 30 | -- ============== 31 | -- The main-entry point. Here we are checking the movement of the user and look 32 | -- if we need to look if we should add a semicolon/comma/... or not. 33 | function TreeSetter.main() 34 | local line_num = vim.api.nvim_win_get_cursor(0)[1] 35 | local col_num = vim.api.nvim_win_get_cursor(0)[2] 36 | 37 | -- if user is still on the same line, then add an eqauls sing '=' where 38 | -- necessary. 39 | if last_line_num == line_num then 40 | -- the below condition will check if the cursor is going to the left 41 | -- to delete characters. In that case, don't insert the `=`. This condition 42 | -- is necessary since we're still on the same line and we don't want an `=` 43 | -- to be inserted when deleting characters. 44 | if col_num >= last_col_num then 45 | TreeSetter.add_character(true) 46 | end 47 | end 48 | 49 | -- look if the user pressed the enter key by checking if the line number 50 | -- increased. If yes, look if we have to add the semicolon/comma/etc. or 51 | -- not. 52 | if last_line_num < line_num then 53 | TreeSetter.add_character(false) 54 | end 55 | 56 | -- refresh the old cursor position 57 | last_line_num = line_num 58 | last_col_num = col_num 59 | end 60 | 61 | 62 | function TreeSetter.add_character(is_equals) 63 | -- get the relevant nodes to be able to judge the current case (if we need 64 | -- to add a semicolon/comma/... or not) 65 | local curr_node = ts_utils.get_node_at_cursor(0) 66 | if not curr_node then 67 | return 68 | end 69 | 70 | local parent_node = curr_node:parent() 71 | if not parent_node then 72 | parent_node = curr_node 73 | end 74 | 75 | -- Reduce the searching-range on the size of the parent node (and not the 76 | -- whole buffer) 77 | local start_row, _, end_row, _ = parent_node:range() 78 | -- since the end row is end-*exclusive*, we have to increase the end row by 79 | -- one 80 | end_row = end_row + 1 81 | 82 | -- iterate through all matched queries from the given range 83 | for _, match, _ in query:iter_matches(parent_node, 0, start_row, end_row) do 84 | for id, node in pairs(match) do 85 | 86 | -- get the "coordinations" of our current line, where we have to 87 | -- lookup if we should add a semicolon or not. 88 | local char_start_row, _, _, char_end_column = node:range() 89 | 90 | -- get the type of character which we should add. 91 | -- So for example if we have "@semicolon" in our query, than 92 | -- "character_type" will be "semicolon", so we know that there 93 | -- should be a semicolon at the end of the line 94 | local character_type = query.captures[id] 95 | 96 | -- so look first, if we reached an "exception" which have the 97 | -- "@skip" predicate. 98 | if character_type == "skip" then 99 | return 100 | end 101 | 102 | -- character types: @equals, @semicolon... you can add your own 103 | if is_equals == true then 104 | if character_type == 'equals' then 105 | setter.set_character(0, char_start_row, char_end_column, '=') 106 | elseif character_type == 'double_points' then 107 | setter.set_character(0, char_start_row, char_end_column, ':') 108 | end 109 | elseif is_equals == false then 110 | -- Add the given character to the given line 111 | if character_type == 'semicolon' then 112 | setter.set_character(0, char_start_row, char_end_column, ';') 113 | elseif character_type == 'comma' then 114 | setter.set_character(0, char_start_row, char_end_column, ',') 115 | elseif character_type == 'double_points' then 116 | setter.set_character(0, char_start_row, char_end_column, ':') 117 | end 118 | end 119 | end 120 | end 121 | end 122 | 123 | 124 | function TreeSetter.attach(bufnr, lang) 125 | query = queries.get(lang, "tsetter") 126 | 127 | vim.cmd([[ 128 | augroup TreeSetter 129 | autocmd! 130 | autocmd CursorMovedI * lua require("tree-setter.main").main() 131 | augroup END 132 | ]]) 133 | end 134 | 135 | function TreeSetter.detach(bufnr) end 136 | 137 | return TreeSetter 138 | -------------------------------------------------------------------------------- /lua/tree-setter/setter.lua: -------------------------------------------------------------------------------- 1 | -- ===================== 2 | -- Global variables 3 | -- ===================== 4 | local Setter = {} 5 | 6 | -- ============== 7 | -- Functions 8 | -- ============== 9 | -- 10 | -- What does it do? 11 | -- This function adds the given character to the end of the line. 12 | -- 13 | -- Parameters: 14 | -- @bufnr: The buffer number where to change it (0 for current) 15 | -- @line_num: The line number where the character should be added to 16 | -- @character: The character which should be added to the line (if it's not 17 | -- there yet) 18 | -- 19 | function Setter.set_character(bufnr, line_num, end_column, character) 20 | -- since we're changing the previous line (after hitting enter) vim 21 | -- will move the indentation of the current line as well. This 22 | -- variable stores the indent of the previous line which will be 23 | -- added after adding the given line with the semicolon/comma/double 24 | -- point. 25 | -- We are doing `line_num + 1` because remember: Lua indexes start with 1! 26 | -- So if `line_num` is 1, we are referring to the first line! 27 | local indent_fix = (' '):rep(vim.fn.indent(line_num + 1)) 28 | 29 | -- We have an exception if the character is ':', because suppose you write 30 | -- something like this ("|" represents the cursor): 31 | -- 32 | -- case 5:| 33 | -- 34 | -- If you hit enter now, than your cursor should land like this: 35 | -- 36 | -- case 5: 37 | -- | 38 | -- 39 | -- and not this: 40 | -- 41 | -- case 5: 42 | -- | 43 | -- 44 | -- so we have to add the indent given by the `shiftwidth` option 45 | -- as well! 46 | if character == ':' then 47 | indent_fix = indent_fix .. (' '):rep(vim.o.shiftwidth) 48 | end 49 | 50 | -- get the last character to know if there's already the needed 51 | -- character or not 52 | local line = vim.api.nvim_buf_get_lines(0, line_num, line_num + 1, false)[1] 53 | 54 | -- make sure that no character is added after the semicolon 55 | if line:sub(-1) == ';' then 56 | return -- Do nothing 57 | end 58 | 59 | 60 | -- Insert a semicolon after calling a custom function 61 | -- Example: func(args). It checks for a closing parenthesis. 62 | local trimmed_line = line:sub(1, end_column):gsub('^%s+', '') -- Trim leading spaces 63 | -- If the character to be added is a semicolon and the line is not empty, 64 | -- and the line ends with a closing parenthesis, insert a semicolon. 65 | if character == ';' and trimmed_line ~= '' and trimmed_line:sub(-1) == ')' then 66 | vim.api.nvim_buf_set_lines(0, line_num, line_num + 2, false, 67 | {line .. character, indent_fix}) 68 | return 69 | end 70 | 71 | 72 | -- Adjust cursor position after inserting '=', place it before the cursor 73 | -- This part is necessary since the only way to check if the user has 74 | -- pressed on the space bar is by checking if he hasn't changed lines. 75 | -- And since after inserting the `=` the user is still on the same line 76 | -- there is a strange behaviour like multiple '=' are inserted. 77 | if character == '=' then 78 | -- if line already contains '=', don't add another one 79 | -- also check if the last character isn't a space character 80 | -- then don't add anything. 81 | if line:find('=') or line:find(',') or line:sub(-1) ~= ' ' then 82 | return 83 | end 84 | 85 | character = character .. " " -- this will add a space after the equals e.g. '= ' 86 | 87 | -- Update the line by inserting the character at the new cursor position 88 | local updated_line = line:sub(1, end_column) .. character 89 | vim.api.nvim_buf_set_lines(0, line_num, line_num + 1, false, {updated_line}) 90 | 91 | -- Move the cursor back to the original position 92 | vim.api.nvim_win_set_cursor(0, {line_num + 1, end_column + 2}) 93 | 94 | return 95 | end 96 | 97 | 98 | if character == ':' then 99 | if line:find(':') or line:sub(-1) ~= ' ' then 100 | return 101 | end 102 | 103 | character = character .. " " -- this will add a space after the equals e.g. '= ' 104 | local updated_line = line:sub(1, end_column-1) .. character 105 | vim.api.nvim_buf_set_lines(0, line_num, line_num + 1, false, {updated_line}) 106 | vim.api.nvim_win_set_cursor(0, {line_num + 1, end_column + 2}) 107 | return 108 | end 109 | 110 | 111 | -- in this part, we're looking at the certain index where the 112 | -- semicolon/comma/... should be, for example if there's already one. 113 | -- We have two cases which for the following two example cases 114 | -- 115 | -- 1. example case: 116 | -- 117 | -- for (int a = 0; a < 10; a++) 118 | -- 119 | -- 2. example case: 120 | -- 121 | -- int a 122 | -- 123 | local wanted_character 124 | if end_column + 1 < line:len() then 125 | -- This is for case 1. 126 | -- First sub: 127 | -- Go to the part of the query 128 | -- Second sub: 129 | -- Pick up the last character of the query 130 | wanted_character = line:sub(end_column, end_column + 1):sub(-1) 131 | else 132 | -- In this case the query is the has the last part of the line as well 133 | -- so we can just pick up the last character of the whole line (see 134 | -- example case 2) 135 | wanted_character = line:sub(-1) 136 | end 137 | 138 | -- is our character already placed? If not => Place it! 139 | -- 140 | -- The second condition is used, to check cases like this: 141 | -- 142 | 143 | -- for (int var = 0; var < 10; var++) 144 | -- 145 | -- Without the second condition, we'd let `var++` enter this condition, 146 | -- which would add a semicolon after the `)`. 147 | if (wanted_character ~= character) and (wanted_character ~= ')') then 148 | -- we need the "+ 2" here, because: 149 | -- 1. The column-index is *exclusive* => + 1 150 | -- 2. We need to set even the next line with our new indentation => + 1 151 | vim.api.nvim_buf_set_lines(0, line_num, line_num + 2, false, 152 | {line .. character, indent_fix}) 153 | end 154 | end 155 | 156 | return Setter 157 | -------------------------------------------------------------------------------- /plugin/tree-setter.vim: -------------------------------------------------------------------------------- 1 | lua require("tree-setter").init() 2 | -------------------------------------------------------------------------------- /queries/c/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; =================== 2 | ;; Action Queries 3 | ;; =================== 4 | ;; -------------- 5 | ;; Variables 6 | ;; -------------- 7 | ;; For known declarations and initialisations 8 | ;; Example: 9 | ;; char var_name 10 | ;; int var_name = 10 11 | 12 | ;; Somehow `long` can't be seen as a declaration first, only if the semicolon is 13 | ;; added, so we have to use the query below for these cases. 14 | (declaration 15 | type: (_) 16 | declarator: (_) @equals @semicolon 17 | ) 18 | ; (declaration 19 | ; type: (_) 20 | ; declarator: (_) @semicolon 21 | ; ) 22 | 23 | ;; Query for "special" variable declaration like the `long` type as descriped 24 | ;; above. 25 | ;; Example: 26 | ;; long var_name 27 | ((sized_type_specifier 28 | type: (_) 29 | ) @equals @semicolon) 30 | 31 | ;; Query for assignments. 32 | ;; Example: 33 | ;; var = 10 34 | ((assignment_expression 35 | left: (_) 36 | right: (_) 37 | ) @semicolon) 38 | 39 | ;; Adds '=' for assignments 40 | ((identifier) @equals) 41 | 42 | 43 | ;; -------------- 44 | ;; Functions 45 | ;; -------------- 46 | ;; This query is mainly used for custom function-calls 47 | ;; Example: 48 | ;; my_func() 49 | ;; This query might look weird for a function call but the query looks like 50 | ;; this if we just write 'function_call()'. Don't believe me? Try it out by 51 | ;; adding this for example in a C file: 52 | ;; 53 | ;; int main() { 54 | ;; function_call() 55 | ;; } 56 | ;; 57 | ;; and open the TreeSitterPlayground afterwards ;) 58 | ((ERROR 59 | (call_expression 60 | function: (identifier) 61 | arguments: (argument_list) 62 | ) 63 | ) @semicolon) 64 | 65 | ;; This is used for known functions like 66 | ;; printf("welp") 67 | ;; Somehow the query above doesn't hit for `printf` for example, that's why we 68 | ;; need this query as well. 69 | ((expression_statement 70 | (call_expression 71 | function: (identifier) 72 | arguments: (argument_list) 73 | ) 74 | ) @semicolon) 75 | 76 | ;; ---------------- 77 | ;; Switch-Case 78 | ;; ---------------- 79 | ;; Query for case statements like 80 | ;; case 1 81 | ((case_statement 82 | value: (_) 83 | ) @double_points) 84 | 85 | ;; ----------- 86 | ;; Macros 87 | ;; ----------- 88 | ;; Places a semicolon after a macro call like "free()" 89 | ((macro_type_specifier 90 | name: (_) 91 | type: (_) 92 | ) @semicolon) 93 | 94 | ;; ---------------------- 95 | ;; Other expressions 96 | ;; ---------------------- 97 | ;; Small updates, for example like 98 | ;; integer-- 99 | ;; or integer++ 100 | ((update_expression 101 | argument: (_) 102 | ) @semicolon) 103 | 104 | ;; Query for break statements 105 | ;; Treesitter sees a line with "break", only if there's already a semicolon! 106 | ;; Otherwise it'll display it as "ERROR" so we need to compare it on our own if 107 | ;; it's a break statement. 108 | (ERROR "break" @semicolon) 109 | 110 | ;; Well... just return statements... like 111 | ;; return 0 112 | ((return_statement 113 | (_) 114 | ) @semicolon) 115 | 116 | ;; ========== 117 | ;; Skips 118 | ;; ========== 119 | ;; If we are in a condition, than musn't add a semicolon in it! For example 120 | ;; if (test() 121 | ;; So here are all "exception" cases. We have the query `ERROR` here, because if 122 | ;; we have a condition like 123 | ;; if (... 124 | ;; Then we're having `ERROR` instead of `if_statement` since treesitter can't 125 | ;; detect it as an if-statement. 126 | (ERROR ["if" "while" "for"] "(" @skip) 127 | -------------------------------------------------------------------------------- /queries/cpp/tsetter.scm: -------------------------------------------------------------------------------- 1 | ; inherits: c 2 | ;; Since C and C++ have a similiar syntax, we're including the queries of here 3 | ;; as well. 4 | -------------------------------------------------------------------------------- /queries/go/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; -------------- 2 | ;; Variables 3 | ;; -------------- 4 | ;; Go variable declaration 5 | ;; Example: 6 | ;; var a = 1 7 | ;; var b = "hello" 8 | (var_declaration 9 | (var_spec 10 | name: (identifier) 11 | type: (type_identifier) @equals 12 | )) 13 | -------------------------------------------------------------------------------- /queries/java/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; local variables 2 | ;; String s = ""; 3 | ; Equals for variable declaration 4 | ; int x = 5 | (local_variable_declaration 6 | declarator: (_ 7 | name: (_) @equals 8 | ) @semicolon 9 | ) 10 | 11 | ;; fields 12 | ;; private Map data; 13 | (field_declaration 14 | declarator: (_) @semicolon 15 | ) 16 | 17 | ;; assignments 18 | ;; this.data = new HashMap<>(); 19 | ((expression_statement 20 | (assignment_expression 21 | left: (_) 22 | right: (_) 23 | ) 24 | ) @semicolon) 25 | 26 | ;; functions calls 27 | ;; Ex: func() 28 | ((expression_statement 29 | (_) 30 | ) @semicolon) 31 | 32 | ((ERROR 33 | (method_invocation 34 | name: (identifier) 35 | arguments: (argument_list) 36 | ) 37 | ) @semicolon) 38 | 39 | ;; methods 40 | ;; this.client.configure(config); 41 | ((expression_statement 42 | (method_invocation 43 | object: (_) 44 | name: (_) 45 | arguments: (_) 46 | ) 47 | ) @semicolon) 48 | 49 | ;; return statements 50 | ;; return 0; 51 | ((return_statement 52 | (_) 53 | ) @semicolon) 54 | 55 | ;; package declaration 56 | ;; package example; 57 | ((package_declaration 58 | (_) 59 | ) @semicolon) 60 | 61 | ;; import 62 | ;; import java.util.Map; 63 | ((import_declaration 64 | (_) 65 | ) @semicolon) 66 | -------------------------------------------------------------------------------- /queries/javascript/tsetter.scm: -------------------------------------------------------------------------------- 1 | (variable_declarator 2 | name: (_) @equals 3 | ) 4 | 5 | 6 | ((variable_declarator) @equals) 7 | -------------------------------------------------------------------------------- /queries/php/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; -------------- 2 | ;; Variables 3 | ;; -------------- 4 | ;; PHP variable declaration 5 | ;; Example: 6 | ;; $foo = 1 7 | ;; $bar = "hello" 8 | (expression_statement 9 | (variable_name) @equals 10 | ) 11 | 12 | ;; -------------- 13 | ;; Associative Arrays 14 | ;; -------------- 15 | ;; PHP Associative arrays 16 | ;; Example: 17 | ;; $foo['bar'] = 0 18 | (expression_statement 19 | (subscript_expression) @equals 20 | ) 21 | 22 | 23 | ;; -------------- 24 | ;; Statement/Expressions 25 | ;; -------------- 26 | ;; PHP variable declaration 27 | ;; Example: 28 | ;; $foo = 1; 29 | ((expression_statement) @semicolon) 30 | -------------------------------------------------------------------------------- /queries/python/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; -------------- 2 | ;; Variables 3 | ;; -------------- 4 | ;; Skip async function declarations 5 | ;; Example: 6 | ;; async def 7 | (expression_statement 8 | (identifier) @skip 9 | (#lua-match? @skip "^async%s*$") 10 | ) 11 | 12 | ;; Skip await expressions 13 | ;; Example: 14 | ;; await func() 15 | (expression_statement 16 | (identifier) @skip 17 | (#lua-match? @skip "^await%s*$") 18 | ) 19 | 20 | ;; Skip match expressions 21 | ;; Example: 22 | ;; match foo: 23 | (expression_statement 24 | (identifier) @skip 25 | (#lua-match? @skip "^match%s*$") 26 | ) 27 | 28 | ;; For declarations and initialisations 29 | ;; Example: 30 | ;; var_name = 10 31 | (expression_statement 32 | (identifier) @equals 33 | ) 34 | 35 | ;; For declarations using the 'self' keyword 36 | ;; Example: 37 | ;; self.x = 12 38 | (expression_statement 39 | (attribute) @equals 40 | ) 41 | 42 | ;; For index declarations 43 | ;; Example: 44 | ;; foo = ["bar"] 45 | ;; foo[0] 46 | (expression_statement 47 | (subscript) @equals 48 | ) 49 | 50 | 51 | ;; ==================== 52 | ;; Lists and Dicts 53 | ;; ==================== 54 | ;; This query is used for multiline lists and dicts like these: 55 | ;; 56 | ;; int_list = [ my_dict = { 57 | ;; 1, 1 : "value1", 58 | ;; 2, 2 : "value2", 59 | ;; 3 2 : "value3" 60 | ;; ] } 61 | ;; But this can also be used for writing something like this: 62 | ;; 63 | ;; int_list [ my_dict { 64 | ;; 1, 1 : "value1", 65 | ;; 2, 2 : "value2", 66 | ;; 3 2 : "value3" 67 | ;; ] } 68 | (ERROR 69 | (identifier) ["[" "{"] (_) @comma 70 | ) 71 | 72 | 73 | ;; ======================== 74 | ;; Classes and Methods 75 | ;; ======================== 76 | ;; This is used for class declarations like 77 | ;; 78 | ;; class TestClass 79 | ((ERROR "class" (identifier) .) @double_points) 80 | 81 | ;; Used for class methods like 82 | ;; 83 | ;; def test(self) 84 | ( 85 | ("def") 86 | (identifier) 87 | (parameters 88 | (identifier)* ;; it doesn't care how long the parameter list is 89 | ")" 90 | @double_points 91 | ) 92 | ) 93 | -------------------------------------------------------------------------------- /queries/rust/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; ============== 2 | ;; Variables 3 | ;; ============== 4 | ;; A normal let declaration like 5 | ;; 6 | ;; let variable = 10 7 | (let_declaration 8 | pattern: (identifier) 9 | value: (_) @semicolon 10 | ) 11 | 12 | ;; ============== 13 | ;; Functions 14 | ;; ============== 15 | ;; A normal function call like 16 | ;; 17 | ;; test() 18 | (call_expression 19 | function: (identifier) 20 | arguments: (arguments) @semicolon 21 | ) 22 | -------------------------------------------------------------------------------- /queries/typescript/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; -------------- 2 | ;; Variables 3 | ;; -------------- 4 | ;; For typed variables 5 | ;; Example: 6 | ;; let x: string = "Hello"; 7 | ((predefined_type) @equals) 8 | 9 | (variable_declarator 10 | name: (_) @double_points 11 | ) 12 | --------------------------------------------------------------------------------