├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── blank-issue.md │ └── bug_report.md ├── CONTRIBUTING.md ├── Documentation_Images ├── C_Example.png ├── OpenTOC.png ├── TreeSitterPlayground_C_Example.png └── 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 ├── python └── tsetter.scm └── rust └── 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 | -------------------------------------------------------------------------------- /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 | ### Filestructure 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 into 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 into 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 found out that the query has to look like this in order to let 63 | TreeSitter know that this is a declaration? Well, I'm using 64 | [nvim-treesitter/playground](https://github.com/nvim-treesitter/playground) for 65 | that. Let's create a new C-file and open up the playground! It'll look like 66 | this: 67 | 68 | ![C query example](./Documentation_Images/TreeSitterPlayground_C_Example.png) 69 | 70 | As you can see, there's a similiar structure on the playground: 71 | 72 | ```scheme 73 | declaration [1, 4] - [1, 10] 74 | type: primitive_type [1, 4] - [1, 7] 75 | declarator: identifier [1, 8] - [1, 9] 76 | ``` 77 | 78 | very nice! So all we need to do, is just writing this query down as it's 79 | displayed in the playground. 80 | 81 | **Note:** Make sure that you removed the semicolon, because sometimes TreeSitter parses 82 | the query *differently* if there's a semicolon or not! 83 | 84 | `(_)` are used, because according to the 85 | [docs](https://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes) 86 | we can create anonymous nodes. But why? Well, `type` needn't to be always a 87 | `primitve_type` (here an `int`). It could also be a char or something else, we 88 | don't know. So we are using an anonymous node! 89 | 90 | Ok, but how does the module know if it should add a semicolon, comma or a double 91 | point? Well, we are doing this by adding this `@`-thing which is called a 92 | "predicate". Just write after the `@` which character has to be add. If there 93 | should be a comma instead of a semicolon, then write `@comma` instead. There are 94 | four different predicates for this module which you can use: 95 | 96 | - `@semicolon` 97 | - `@comma` 98 | - `@double_points` 99 | - `@skip` 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, than 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, 117 | that it should *not* check in the current query if a query matches. This happens 118 | for example in the following case (also described in the last lines of the 119 | `tsetter.scm` file in C): 120 | 121 | ```c 122 | if (test() 123 | ``` 124 | 125 | without the last query from the C queries, `tree-setter` would add a semicolon 126 | after `test()` if you would hit the enter key now! This is not what we want! So 127 | `tree-setter` should skip this part, that's the usage of this `@skip`. 128 | 129 | By the way: It *is* important *where* you but the predicate, because 130 | `tree-setter` will put the semicolon, etc. on the place of the predicate! 131 | 132 | Now you should be able to write some queries for your language now! :) 133 | Please follow the 134 | [query-code-styles](./CONTRIBUTING.md#query-code-style) (below) 135 | to make it better to maintain and better to understand before creating a pull 136 | request ;) 137 | 138 | ### Tips 139 | #### General 140 | If you want to know more on how to write queries, than you can read it from the 141 | [official 142 | docs](https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax). 143 | This should explain you some more features and tricks on how to write them :) 144 | 145 | I also recommend to read through `:h lua-treesitter-query` which explains it 146 | partially as well. 147 | 148 | 149 | #### "Weird" queries 150 | Some queries might be pretty problematic... Look at this query for example (also 151 | from the query file of C): 152 | 153 | ```scheme 154 | ((ERROR 155 | (call_expression 156 | function: (identifier) 157 | arguments: (argument_list) 158 | ) 159 | ) @semicolon) 160 | ``` 161 | 162 | This query is used to indicate a user-function which is called. Yes it might 163 | look weird, but that's how TreeSitter evaluates the code if the semicolon is 164 | missing, so keep an eye on the playground what it's displaying! 165 | 166 | ## Query-Code-Style 167 | Please write the queries in the following style: 168 | 169 | ```scheme 170 | 171 | ;; 172 | ;; Example(s): 173 | ;; 174 | ;; 175 | ;; 177 | 178 | ``` 179 | 180 | Here's an example of a C query of `tree-setter/queries/c/tsetter.scm`: 181 | 182 | ```scheme 183 | ;; =================== 184 | ;; Action Queries 185 | ;; =================== 186 | ;; -------------- 187 | ;; Variables 188 | ;; -------------- 189 | ;; For known declarations and initialisations 190 | ;; Example: 191 | ;; char var_name 192 | ;; int var_name = 10 193 | 194 | ;; Somehow `long` can't be seen as a declaration first, only if the semicolon is 195 | ;; added, so we have to use the query below for these cases. 196 | (declaration 197 | type: (_) 198 | declarator: (_) @semicolon 199 | ) 200 | ``` 201 | 202 | # Expanding/Improving tree-setter code 203 | So this is gonna be about the backend of `tree-setter`. You'll get a rough 204 | overview of how the code works in order to be able to extend the code! So let's 205 | start with the filestructure first! 206 | 207 | ## Filestructure 208 | Here is the Filestructure with the most important files and a little description 209 | for them on the right: 210 | 211 | ``` 212 | tree-setter 213 | ├── CONTRIBUTING.md The document you're reading 214 | ├── Documentation_Images All images which are used in the documentation 215 | ├── lua The "heart" directory of this module 216 | │   ├── tree-setter 217 | │   │   ├── main.lua This file includes the main functions of the 218 | │ │ │ module like checking if any queries match for 219 | │ │ │ the current file or not and looking if the user 220 | │ │ │ hit enter. 221 | │   │   └── setter.lua This file holds only one function, which will add 222 | │ │ the appropriate character to the given line. 223 | │   └── tree-setter.lua Includes the entry point for a treesitter module 224 | │ (here: tree-setter) 225 | ├── plugin 226 | │   └── tree-setter.vim Nothing special here, it just calls the 227 | │ preparation function for the treesitter module 228 | └── queries As explained in the previous "chapter" this 229 | directory includes all queries for the given 230 | filetype 231 | ``` 232 | 233 | So let's move on to the steps! 234 | 235 | ## General 236 | When the user starts neovim, the init function in 237 | `tree-setter/lua/tree-setter.lua` is called which will look, if we have a 238 | query for the current language and looks, where the main-entry-point of the 239 | module is. Here it's `tree-setter/lua/tree-setter/main.lua`. 240 | 241 | So now we're mostly in the `tree-setter/lua/tree-setter/main.lua` file. 242 | TreeSitter will call the `TreeSetter.attach()` function which will load the 243 | appropriate query for our current language and prepares the autocommands. 244 | 245 | `tree-setter` needs to check if the user hits the enter-key. But how can we 246 | check, if the user pressed the enter key? Well, if the user pressed the enter 247 | key, than the cursor will move one line down. That's how `tree-setter` tries to 248 | detect if the enter-key is pressed without mapping any keys! 249 | 250 | The `TreeSetter.main()` function will check if the enter-key is hit, if yes, the 251 | next function comes in: `TreeSetter.add_character()`. This just picks up the 252 | node of the current cursor position and the parent to get a range to test which 253 | queries match or not. 254 | 255 | If we found a match, we're looking which kind of character we need to add 256 | according to their predicate name like `@semicolon` or @comma`. `@skip` will 257 | stop the process which tests which queries matches. 258 | 259 | In general that's it. Take a look into the comments of the code, to get a more 260 | detailed explanation. I hope that it roughly helped you to understand the 261 | backend. Feel free to ask by creating a new issue :) 262 | -------------------------------------------------------------------------------- /Documentation_Images/C_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TornaxO7/tree-setter/2ff171f2a8a41a41994e57a32772967b69ff5125/Documentation_Images/C_Example.png -------------------------------------------------------------------------------- /Documentation_Images/OpenTOC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TornaxO7/tree-setter/2ff171f2a8a41a41994e57a32772967b69ff5125/Documentation_Images/OpenTOC.png -------------------------------------------------------------------------------- /Documentation_Images/TreeSitterPlayground_C_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TornaxO7/tree-setter/2ff171f2a8a41a41994e57a32772967b69ff5125/Documentation_Images/TreeSitterPlayground_C_Example.png -------------------------------------------------------------------------------- /Documentation_Images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TornaxO7/tree-setter/2ff171f2a8a41a41994e57a32772967b69ff5125/Documentation_Images/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 | # WARNING 2 | This module is still in a VEEERY young state and works only (partially, let's 3 | rather say *barely* workking) for the 4 | C programming language! So be prepared for a lot of bugs if you're trying it 5 | out! If you want to know a little bit more, then you can read [this 6 | issue-message](https://github.com/TornaxO7/tree-setter/issues/1#issuecomment-1025161228). 7 | 8 | # TreeSetter 9 | TreeSetter is a 10 | [nvim-treesitter-module](https://github.com/nvim-treesitter/module-template) 11 | which **adds semicolons (`;`), commas (`,`) and double points (`:`) automatically** 12 | for you, if you hit enter at the end of a line! 13 | 14 | # Demo 15 | ![demonstration](./Documentation_Images/demo.gif) 16 | 17 | As you can see from in the key-screen-bar 18 | [screenkey](https://gitlab.com/screenkey/screenkey) I almost never pressed the 19 | `;` key. I just needed to write the line of code, what I wanted and pressed the 20 | `` key. The semicolon was added automatically. 21 | 22 | # Installation 23 | With [vim-plug](https://github.com/junegunn/vim-plug): 24 | 25 | ```vim 26 | Plug 'TornaxO7/tree-setter' 27 | ``` 28 | 29 | Add this into your `treesitter`-settings: 30 | ```lua 31 | require('nvim-treesitter.configs').setup { 32 | -- your other modules ... 33 | 34 | tree_setter = { 35 | enable = true 36 | }, 37 | 38 | -- your other modules ... 39 | } 40 | ``` 41 | 42 | # Contributing 43 | Take a look into the [CONTRIBUTING.md](./CONTRIBUTING.md) file for that ;) 44 | 45 | # Other information 46 | ## Why so less commits? 47 | The problem is, that treesitter gives different results if the syntax is wrong 48 | which makes it really hard to write the queries. So we have to wait until it 49 | stabilizes that. 50 | -------------------------------------------------------------------------------- /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 | 27 | -- ============== 28 | -- Functions 29 | -- ============== 30 | function TreeSetter.add_character() 31 | -- get the relevant nodes to be able to judge the current case (if we need 32 | -- to add a semicolon/comma/... or not) 33 | local curr_node = ts_utils.get_node_at_cursor(0) 34 | if not curr_node then 35 | return 36 | end 37 | 38 | local parent_node = curr_node:parent() 39 | if not parent_node then 40 | parent_node = curr_node 41 | end 42 | 43 | -- Reduce the searching-range on the size of the parent node (and not the 44 | -- whole buffer) 45 | local start_row, _, end_row, _ = parent_node:range() 46 | -- since the end row is end-*exclusive*, we have to increase the end row by 47 | -- one 48 | end_row = end_row + 1 49 | 50 | -- iterate through all matched queries from the given range 51 | for _, match, _ in query:iter_matches(parent_node, 0, start_row, end_row) do 52 | for id, node in pairs(match) do 53 | 54 | -- get the "coordinations" of our current line, where we have to 55 | -- lookup if we should add a semicolon or not. 56 | local char_start_row, _, _, char_end_column = node:range() 57 | 58 | -- get the type of character which we should add. 59 | -- So for example if we have "@semicolon" in our query, than 60 | -- "character_type" will be "semicolon", so we know that there 61 | -- should be a semicolon at the end of the line 62 | local character_type = query.captures[id] 63 | 64 | -- so look first, if we reached an "exception" which have the 65 | -- "@skip" predicate. 66 | if character_type == "skip" then 67 | return 68 | end 69 | 70 | -- Add the given character to the given line 71 | if character_type == 'semicolon' then 72 | setter.set_character(0, char_start_row, char_end_column, ';') 73 | elseif character_type == 'comma' then 74 | setter.set_character(0, char_start_row, char_end_column, ',') 75 | elseif character_type == 'double_points' then 76 | setter.set_character(0, char_start_row, char_end_column, ':') 77 | end 78 | end 79 | end 80 | end 81 | 82 | -- The main-entry point. Here we are checking the movement of the user and look 83 | -- if we need to look if we should add a semicolon/comma/... or not. 84 | function TreeSetter.main() 85 | local line_num = vim.api.nvim_win_get_cursor(0)[1] 86 | 87 | -- look if the user pressed the enter key by checking if the line number 88 | -- increased. If yes, look if we have to add the semicolon/comma/etc. or 89 | -- not. 90 | if last_line_num < line_num then 91 | TreeSetter.add_character() 92 | end 93 | 94 | -- refresh the old cursor position 95 | last_line_num = line_num 96 | end 97 | 98 | function TreeSetter.attach(bufnr, lang) 99 | query = queries.get_query(lang, "tsetter") 100 | 101 | vim.cmd([[ 102 | augroup TreeSetter 103 | autocmd! 104 | autocmd CursorMovedI * lua require("tree-setter.main").main() 105 | augroup END 106 | ]]) 107 | end 108 | 109 | function TreeSetter.detach(bufnr) end 110 | 111 | return TreeSetter 112 | -------------------------------------------------------------------------------- /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 | -- in this part, we're looking at the certain index where the 54 | -- semicolon/comma/... should be, for example if there's already one. 55 | -- We have two cases which for the following two example cases 56 | -- 57 | -- 1. example case: 58 | -- 59 | -- for (int a = 0; a < 10; a++) 60 | -- 61 | -- 2. example case: 62 | -- 63 | -- int a 64 | -- 65 | local wanted_character 66 | if end_column + 1 < line:len() then 67 | -- This is for case 1. 68 | -- First sub: 69 | -- Go to the part of the query 70 | -- Second sub: 71 | -- Pick up the last character of the query 72 | wanted_character = line:sub(end_column, end_column + 1):sub(-1) 73 | else 74 | -- In this case the query is the has the last part of the line as well 75 | -- so we can just pick up the last character of the whole line (see 76 | -- example case 2) 77 | wanted_character = line:sub(-1) 78 | end 79 | 80 | -- is our character already placed? If not => Place it! 81 | -- 82 | -- The second condition is used, to check cases like this: 83 | -- 84 | 85 | -- for (int var = 0; var < 10; var++) 86 | -- 87 | -- Without the second condition, we'd let `var++` enter this condition, 88 | -- which would add a semicolon after the `)`. 89 | if (wanted_character ~= character) and (wanted_character ~= ')') then 90 | -- we need the "+ 2" here, because: 91 | -- 1. The column-index is *exclusive* => + 1 92 | -- 2. We need to set even the next line with our new indentation => + 1 93 | vim.api.nvim_buf_set_lines(0, line_num, line_num + 2, false, 94 | {line .. character, indent_fix}) 95 | end 96 | end 97 | 98 | return Setter 99 | -------------------------------------------------------------------------------- /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: (_) @semicolon 17 | ) 18 | 19 | ;; Query for "special" variable declaration like the `long` type as descriped 20 | ;; above. 21 | ;; Example: 22 | ;; long var_name 23 | ((sized_type_specifier 24 | type: (_) 25 | ) @semicolon) 26 | 27 | ;; Query for assignments. 28 | ;; Example: 29 | ;; var = 10 30 | ((assignment_expression 31 | left: (identifier) 32 | right: (_) 33 | ) @semicolon) 34 | 35 | ;; -------------- 36 | ;; Functions 37 | ;; -------------- 38 | ;; This query is mainly used for custom function-calls 39 | ;; Example: 40 | ;; my_func() 41 | ;; This query might look weird for a function call but the query looks like 42 | ;; this if we just write 'function_call()'. Don't believe me? Try it out by 43 | ;; adding this for example in a C file: 44 | ;; 45 | ;; int main() { 46 | ;; function_call() 47 | ;; } 48 | ;; 49 | ;; and open the TreeSitterPlayground afterwards ;) 50 | ((ERROR 51 | (call_expression 52 | function: (identifier) 53 | arguments: (argument_list) 54 | ) 55 | ) @semicolon) 56 | 57 | ;; This is used for known functions like 58 | ;; printf("welp") 59 | ;; Somehow the query above doesn't hit for `printf` for example, that's why we 60 | ;; need this query as well. 61 | ((expression_statement 62 | (call_expression 63 | function: (identifier) 64 | arguments: (argument_list) 65 | ) 66 | ) @semicolon) 67 | 68 | ;; ---------------- 69 | ;; Switch-Case 70 | ;; ---------------- 71 | ;; Query for case statements like 72 | ;; case 1 73 | ((case_statement 74 | value: (_) 75 | ) @double_points) 76 | 77 | ;; ----------- 78 | ;; Macros 79 | ;; ----------- 80 | ;; Places a semicolon after a macro call like "free()" 81 | ((macro_type_specifier 82 | name: (_) 83 | type: (_) 84 | ) @semicolon) 85 | 86 | ;; ---------------------- 87 | ;; Other expressions 88 | ;; ---------------------- 89 | ;; Small updates, for example like 90 | ;; integer-- 91 | ;; or integer++ 92 | ((update_expression 93 | argument: (_) 94 | ) @semicolon) 95 | 96 | ;; Query for break statements 97 | ;; Treesitter sees a line with "break", only if there's already a semicolon! 98 | ;; Otherwise it'll display it as "ERROR" so we need to compare it on our own if 99 | ;; it's a break statement. 100 | (ERROR "break" @semicolon) 101 | 102 | ;; Well... just return statements... like 103 | ;; return 0 104 | ((return_statement 105 | (_) 106 | ) @semicolon) 107 | 108 | ;; ========== 109 | ;; Skips 110 | ;; ========== 111 | ;; If we are in a condition, than musn't add a semicolon in it! For example 112 | ;; if (test() 113 | ;; So here are all "exception" cases. We have the query `ERROR` here, because if 114 | ;; we have a condition like 115 | ;; if (... 116 | ;; Then we're having `ERROR` instead of `if_statement` since treesitter can't 117 | ;; detect it as an if-statement. 118 | (ERROR ["if" "while" "for"] "(" @skip) 119 | -------------------------------------------------------------------------------- /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/python/tsetter.scm: -------------------------------------------------------------------------------- 1 | ;; ==================== 2 | ;; Lists and Dicts 3 | ;; ==================== 4 | ;; This query is used for multiline lists and dicts like these: 5 | ;; 6 | ;; int_list = [ my_dict = { 7 | ;; 1, 1 : "value1", 8 | ;; 2, 2 : "value2", 9 | ;; 3 2 : "value3" 10 | ;; ] } 11 | ;; But this can also be used for writing something like this: 12 | ;; 13 | ;; int_list [ my_dict { 14 | ;; 1, 1 : "value1", 15 | ;; 2, 2 : "value2", 16 | ;; 3 2 : "value3" 17 | ;; ] } 18 | (ERROR 19 | (identifier) ["[" "{"] (_) @comma 20 | ) 21 | 22 | 23 | ;; ======================== 24 | ;; Classes and Methods 25 | ;; ======================== 26 | ;; This is used for class declarations like 27 | ;; 28 | ;; class TestClass 29 | ((ERROR "class" (identifier) .) @double_points) 30 | 31 | ;; Used for class methods like 32 | ;; 33 | ;; def test(self) 34 | ( 35 | ("def") 36 | (identifier) 37 | (parameters 38 | (identifier)* ;; it doesn't care how long the parameter list is 39 | ")" 40 | @double_points 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------