├── .editorconfig ├── .github └── workflows │ └── unit_tests.yml ├── LICENSE ├── README.md ├── lua ├── nt-cpp-tools.lua └── nt-cpp-tools │ ├── buffer_writer.lua │ ├── config.lua │ ├── internal.lua │ ├── output_handlers.lua │ ├── preview_printer.lua │ └── util.lua ├── plugin └── nt-cpp-tools.vim ├── queries └── cpp │ ├── concrete_implement.scm │ ├── outside_class_def.scm │ └── special_function_detectors.scm ├── scripts ├── minimal_setup.lua └── run_tests.sh └── test ├── implement_functions.txt └── test_spec.lua /.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/workflows/unit_tests.yml: -------------------------------------------------------------------------------- 1 | 2 | # This is a basic workflow to help you get started with Actions 3 | 4 | name: CI 5 | 6 | # Controls when the workflow will run 7 | on: 8 | # Triggers the workflow on push or pull request events but only for the master branch 9 | push: 10 | branches: [ master ] 11 | pull_request: 12 | types: [opened, synchronize, reopened, ready_for_review] 13 | branches: 14 | - 'master' 15 | schedule: 16 | - cron: '0 9 * * *' 17 | workflow_dispatch: 18 | 19 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 20 | jobs: 21 | unit_test: 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | nvim_version : [stable, nightly] 26 | 27 | # The type of runner that the job will run on 28 | runs-on: ubuntu-latest 29 | 30 | # Steps represent a sequence of tasks that will be executed as part of the job 31 | steps: 32 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 33 | - uses: actions/checkout@v2 34 | - name: Test Dependencies 35 | run: | 36 | sudo apt install libfuse2 37 | mkdir -p ~/.local/share/nvim/site/pack/plenary.nvim/start 38 | cd ~/.local/share/nvim/site/pack/plenary.nvim/start 39 | git clone https://github.com/nvim-lua/plenary.nvim 40 | mkdir -p ~/.local/share/nvim/site/pack/nvim-treesitter/start 41 | cd ~/.local/share/nvim/site/pack/nvim-treesitter/start 42 | git clone https://github.com/nvim-treesitter/nvim-treesitter.git 43 | 44 | - name: Setup nvim and tree sitter 45 | env: 46 | NVIM_TAG: ${{ matrix.nvim_version }} 47 | TREE_SITTER_CLI_TAG: v0.20.4 48 | run: | 49 | wget -O - https://github.com/tree-sitter/tree-sitter/releases/download/${TREE_SITTER_CLI_TAG}/tree-sitter-linux-x64.gz | gunzip -c > tree-sitter 50 | sudo cp ./tree-sitter /usr/bin/tree-sitter 51 | sudo chmod uog+rwx /usr/bin/tree-sitter 52 | wget https://github.com/neovim/neovim/releases/download/${NVIM_TAG}/nvim-linux-x86_64.appimage -O nvim.appimage 53 | chmod u+x nvim.appimage 54 | mkdir -p ~/.local/share/nvim/site/pack/nvim-treesitter-cpp-tools/start 55 | ln -s $(pwd) ~/.local/share/nvim/site/pack/nvim-treesitter-cpp-tools/start 56 | sudo cp ./nvim.appimage /usr/bin/nvim 57 | sudo chmod uog+rwx /usr/bin/nvim 58 | - name: Install treesitter parsers 59 | run: nvim --headless -c "TSInstallSync cpp" -c "q" 60 | 61 | - name: Run Test 62 | run: ./scripts/run_tests.sh 63 | -------------------------------------------------------------------------------- /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 | # nt-cpp-tools 2 | ![Unit Tests](https://github.com/badhi/nvim-treesitter-cpp-tools/actions/workflows/unit_tests.yml/badge.svg) 3 | 4 | Experimental treesitter based neovim plugin to create intelligent implementations for C++ 5 | 6 | ## Features 7 | 8 | 1. Out-of class member function implementation 9 | 2. Concrete class implement from Abstract class or Interface 10 | 3. Add missing functions to obey Rule of 3 11 | 4. Add missing functions to obey Rule of 5 12 | 13 | ## Install 14 | 15 | Using [paq](https://github.com/savq/paq-nvim) 16 | 17 | ```lua 18 | require "paq" { 19 | "nvim-treesitter/nvim-treesitter", 20 | "Badhi/nvim-treesitter-cpp-tools", 21 | } 22 | ``` 23 | 24 | Using [packer.nvim](https://github.com/wbthomason/packer.nvim) 25 | 26 | ```lua 27 | use { 28 | requires = { "nvim-treesitter/nvim-treesitter" }, 29 | "Badhi/nvim-treesitter-cpp-tools", 30 | } 31 | ``` 32 | 33 | Using [lazy.nvim](https://github.com/folke/lazy.nvim) 34 | ```lua 35 | { 36 | "Badhi/nvim-treesitter-cpp-tools", 37 | dependencies = { "nvim-treesitter/nvim-treesitter" }, 38 | -- Optional: Configuration 39 | opts = function() 40 | local options = { 41 | preview = { 42 | quit = "q", -- optional keymapping for quit preview 43 | accept = "", -- optional keymapping for accept preview 44 | }, 45 | header_extension = "h", -- optional 46 | source_extension = "cpp", -- optional 47 | custom_define_class_function_commands = { -- optional 48 | TSCppImplWrite = { 49 | output_handle = require("nt-cpp-tools.output_handlers").get_add_to_cpp(), 50 | }, 51 | --[[ 52 | = { 53 | output_handle = function (str, context) 54 | -- string contains the class implementation 55 | -- do whatever you want to do with it 56 | end 57 | } 58 | ]] 59 | }, 60 | } 61 | return options 62 | end, 63 | -- End configuration 64 | config = true, 65 | } 66 | ``` 67 | 68 | ## Setup 69 | 70 | **For [lazy.nvim](https://github.com/folke/lazy.nvim), see [Install](#install)** 71 | 72 | Add the following config to your init script 73 | 74 | > **Note:** The config for this plugin, included in the `treesitter.config` is now moved to an independent config. Please make required changes 75 | 76 | ```lua 77 | require 'nt-cpp-tools'.setup({ 78 | preview = { 79 | quit = 'q', -- optional keymapping for quit preview 80 | accept = '' -- optional keymapping for accept preview 81 | }, 82 | header_extension = 'h', -- optional 83 | source_extension = 'cxx', -- optional 84 | custom_define_class_function_commands = { -- optional 85 | TSCppImplWrite = { 86 | output_handle = require'nt-cpp-tools.output_handlers'.get_add_to_cpp() 87 | } 88 | --[[ 89 | = { 90 | output_handle = function (str, context) 91 | -- string contains the class implementation 92 | -- do whatever you want to do with it 93 | end 94 | } 95 | ]] 96 | } 97 | }) 98 | ``` 99 | 100 | ## Usage 101 | 102 | * Select the range of the class using visual mode 103 | * Use below commands 104 | 105 | | Command | Feature | 106 | | ----------- | ----------- | 107 | | `TSCppDefineClassFunc` | Implement out of class member functions

*subset of functions can be implemented by selecting required function declarations using visual mode or simply keeping the cursor on the function declaration before calling the command*

Supported special features
1. Templates (with default args)
2. Function arguments with default values
3. Nested classes
(check [test_cases](https://github.com/Badhi/nvim-treesitter-cpp-tools/blob/master/test/implement_functions.txt) for tested examples)| 108 | | `TSCppMakeConcreteClass` | Create a concrete class implementing all the pure virtual functions | 109 | | `TSCppRuleOf3` | Adds the missing function declarations to the class to obey the Rule of 3 (if eligible) | 110 | | `TSCppRuleOf5` | Adds the missing function declarations to the class to obey the Rule of 5 (if eligible) | 111 | 112 | 113 | ## Example 114 | 115 | 1. `TSCppDefineClassFunc` 116 | 117 | ![TSImplementFunc](https://user-images.githubusercontent.com/10277051/152277748-d7c0204a-b54e-4ae1-90ac-b1e4cbd51ba5.gif) 118 | 119 | 2. `TSCppMakeConcreteClass` 120 | 121 | ![TSConcreteClass](https://user-images.githubusercontent.com/10277051/152278222-d20e34f0-542d-451e-ae16-646f68e9f72f.gif) 122 | 123 | 3. `TSCppRuleOf3` 124 | 125 | ![TSRuleOf3](https://user-images.githubusercontent.com/10277051/152277800-a2573916-5e8a-4f3a-804f-88f6f6994281.gif) 126 | 127 | -------------------------------------------------------------------------------- /lua/nt-cpp-tools.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local init_done = false 4 | 5 | --TODO reuse the function provided by the treesitter utils 6 | local function setup_commands(commands) 7 | for command_name, def in pairs(commands) do 8 | local f_args = def.f_args or "" 9 | local call_fn = string.format( 10 | "lua require'nt-cpp-tools.internal'.commands.%s['run'](%s)", 11 | command_name, 12 | f_args 13 | ) 14 | local parts = vim.iter({ 15 | "command!", 16 | def.args, 17 | command_name, 18 | call_fn, 19 | }):flatten():totable() 20 | vim.api.nvim_command(table.concat(parts, " ")) 21 | end 22 | end 23 | 24 | local function get_copy(table) 25 | local ret = {} 26 | for k, v in pairs(table) do 27 | if type(v) == 'table' then 28 | ret[k] = get_copy(v) 29 | else 30 | ret[k] = v 31 | end 32 | end 33 | return ret 34 | end 35 | 36 | function M.setup(user_config) 37 | if init_done then 38 | return 39 | end 40 | init_done = true 41 | 42 | 43 | vim.cmd([[ hi def TSCppHighlight guifg=#808080 ctermfg=244 ]]) 44 | 45 | local config = require 'nt-cpp-tools.config'.init(user_config) 46 | if config.custom_define_class_function_commands then 47 | local internal = require"nt-cpp-tools.internal" 48 | local default_command = internal.commands.TSCppDefineClassFunc 49 | for command_name, command in pairs(config.custom_define_class_function_commands) do 50 | local value = get_copy(default_command) 51 | value['run'] = function (s, e) internal.imp_func(s, e, command.output_handle) end 52 | internal.commands[command_name] = value 53 | end 54 | end 55 | setup_commands(require"nt-cpp-tools.internal".commands) 56 | end 57 | 58 | 59 | return M 60 | -------------------------------------------------------------------------------- /lua/nt-cpp-tools/buffer_writer.lua: -------------------------------------------------------------------------------- 1 | local uv = vim.loop 2 | local validate = vim.validate 3 | local api = vim.api 4 | 5 | local M = {} 6 | local function get_lines(bufnr, rows) 7 | rows = type(rows) == "table" and rows or { rows } 8 | 9 | ---@private 10 | local function buf_lines() 11 | local lines = {} 12 | for _, row in pairs(rows) do 13 | lines[row] = (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1] 14 | end 15 | return lines 16 | end 17 | 18 | local uri = vim.uri_from_bufnr(bufnr) 19 | 20 | -- load the buffer if this is not a file uri 21 | -- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds. 22 | if uri:sub(1, 4) ~= "file" then 23 | vim.fn.bufload(bufnr) 24 | return buf_lines() 25 | end 26 | 27 | -- use loaded buffers if available 28 | if vim.fn.bufloaded(bufnr) == 1 then 29 | return buf_lines() 30 | end 31 | 32 | local filename = api.nvim_buf_get_name(bufnr) 33 | 34 | -- get the data from the file 35 | local fd = uv.fs_open(filename, "r", 438) 36 | if not fd then return "" end 37 | local stat = uv.fs_fstat(fd) 38 | local data = uv.fs_read(fd, stat.size, 0) 39 | uv.fs_close(fd) 40 | 41 | local lines = {} -- rows we need to retrieve 42 | local need = 0 -- keep track of how many unique rows we need 43 | for _, row in pairs(rows) do 44 | if not lines[row] then 45 | need = need + 1 46 | end 47 | lines[row] = true 48 | end 49 | 50 | local found = 0 51 | local lnum = 0 52 | 53 | for line in string.gmatch(data, "([^\n]*)\n?") do 54 | if lines[lnum] == true then 55 | lines[lnum] = line 56 | found = found + 1 57 | if found == need then break end 58 | end 59 | lnum = lnum + 1 60 | end 61 | 62 | -- change any lines we didn't find to the empty string 63 | for i, line in pairs(lines) do 64 | if line == true then 65 | lines[i] = "" 66 | end 67 | end 68 | return lines 69 | end 70 | 71 | local function get_line(bufnr, row) 72 | return get_lines(bufnr, { row })[row] 73 | end 74 | 75 | local function get_line_byte_from_position(bufnr, position, offset_encoding) 76 | -- LSP's line and characters are 0-indexed 77 | -- Vim's line and columns are 1-indexed 78 | local col = position.character 79 | -- When on the first character, we can ignore the difference between byte and 80 | -- character 81 | if col > 0 then 82 | local line = get_line(bufnr, position.line) 83 | local ok, result 84 | 85 | if offset_encoding == "utf-16" or not offset_encoding then 86 | ok, result = pcall(vim.str_byteindex, line, col, true) 87 | elseif offset_encoding == "utf-32" then 88 | ok, result = pcall(vim.str_byteindex, line, col, false) 89 | end 90 | 91 | if ok then 92 | return result 93 | end 94 | return math.min(#line, col) 95 | end 96 | return col 97 | end 98 | 99 | function M.apply_text_edits(text_edits, bufnr) 100 | validate { 101 | text_edits = { text_edits, 't', false }; 102 | bufnr = { bufnr, 'number', false }; 103 | } 104 | if not next(text_edits) then return end 105 | if not api.nvim_buf_is_loaded(bufnr) then 106 | vim.fn.bufload(bufnr) 107 | end 108 | api.nvim_buf_set_option(bufnr, 'buflisted', true) 109 | 110 | -- Fix reversed range and indexing each text_edits 111 | local index = 0 112 | text_edits = vim.tbl_map(function(text_edit) 113 | index = index + 1 114 | text_edit._index = index 115 | 116 | if text_edit.range.start.line > text_edit.range['end'].line or text_edit.range.start.line == text_edit.range['end'].line and text_edit.range.start.character > text_edit.range['end'].character then 117 | local start = text_edit.range.start 118 | text_edit.range.start = text_edit.range['end'] 119 | text_edit.range['end'] = start 120 | end 121 | return text_edit 122 | end, text_edits) 123 | 124 | -- Sort text_edits 125 | table.sort(text_edits, function(a, b) 126 | if a.range.start.line ~= b.range.start.line then 127 | return a.range.start.line > b.range.start.line 128 | end 129 | if a.range.start.character ~= b.range.start.character then 130 | return a.range.start.character > b.range.start.character 131 | end 132 | if a._index ~= b._index then 133 | return a._index > b._index 134 | end 135 | end) 136 | 137 | -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. 138 | local has_eol_text_edit = false 139 | local max = vim.api.nvim_buf_line_count(bufnr) 140 | -- TODO handle offset_encoding 141 | local _, len = vim.str_utfindex(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') 142 | text_edits = vim.tbl_map(function(text_edit) 143 | if max <= text_edit.range.start.line then 144 | text_edit.range.start.line = max - 1 145 | text_edit.range.start.character = len 146 | text_edit.newText = '\n' .. text_edit.newText 147 | has_eol_text_edit = true 148 | end 149 | if max <= text_edit.range['end'].line then 150 | text_edit.range['end'].line = max - 1 151 | text_edit.range['end'].character = len 152 | has_eol_text_edit = true 153 | end 154 | return text_edit 155 | end, text_edits) 156 | 157 | -- Some LSP servers are depending on the VSCode behavior. 158 | -- The VSCode will re-locate the cursor position after applying TextEdit so we also do it. 159 | local is_current_buf = vim.api.nvim_get_current_buf() == bufnr 160 | local cursor = (function() 161 | if not is_current_buf then 162 | return { 163 | row = -1, 164 | col = -1, 165 | } 166 | end 167 | local cursor = vim.api.nvim_win_get_cursor(0) 168 | return { 169 | row = cursor[1] - 1, 170 | col = cursor[2], 171 | } 172 | end)() 173 | 174 | -- Apply text edits. 175 | local is_cursor_fixed = false 176 | for _, text_edit in ipairs(text_edits) do 177 | local e = { 178 | start_row = text_edit.range.start.line, 179 | start_col = get_line_byte_from_position(bufnr, text_edit.range.start), 180 | end_row = text_edit.range['end'].line, 181 | end_col = get_line_byte_from_position(bufnr, text_edit.range['end']), 182 | text = vim.split(text_edit.newText, '\n', true), 183 | } 184 | vim.api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text) 185 | 186 | local row_count = (e.end_row - e.start_row) + 1 187 | if e.end_row < cursor.row then 188 | cursor.row = cursor.row + (#e.text - row_count) 189 | is_cursor_fixed = true 190 | elseif e.end_row == cursor.row and e.end_col <= cursor.col then 191 | cursor.row = cursor.row + (#e.text - row_count) 192 | cursor.col = #e.text[#e.text] + (cursor.col - e.end_col) 193 | if #e.text == 1 then 194 | cursor.col = cursor.col + e.start_col 195 | end 196 | is_cursor_fixed = true 197 | end 198 | end 199 | 200 | if is_cursor_fixed then 201 | local is_valid_cursor = true 202 | is_valid_cursor = is_valid_cursor and cursor.row < vim.api.nvim_buf_line_count(bufnr) 203 | is_valid_cursor = is_valid_cursor and cursor.col <= #(vim.api.nvim_buf_get_lines(bufnr, cursor.row, cursor.row + 1, false)[1] or '') 204 | if is_valid_cursor then 205 | vim.api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col }) 206 | end 207 | end 208 | 209 | -- Remove final line if needed 210 | local fix_eol = has_eol_text_edit 211 | fix_eol = fix_eol and api.nvim_buf_get_option(bufnr, 'fixeol') 212 | fix_eol = fix_eol and (vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') == '' 213 | if fix_eol then 214 | vim.api.nvim_buf_set_lines(bufnr, -2, -1, false, {}) 215 | end 216 | end 217 | 218 | return M 219 | -------------------------------------------------------------------------------- /lua/nt-cpp-tools/config.lua: -------------------------------------------------------------------------------- 1 | local queries = require "nvim-treesitter.query" 2 | 3 | local default_config = { 4 | enable = false, 5 | preview = { 6 | quit = 'q', 7 | accept = '' 8 | }, 9 | header_extension = 'h', 10 | source_extension = 'cpp', 11 | is_supported = function(lang) 12 | return queries.get_query(lang, 'query') ~= nil 13 | end 14 | } 15 | 16 | local M = {} 17 | 18 | function M.init(user_cfg) 19 | user_cfg = user_cfg or {} 20 | default_config = vim.tbl_deep_extend('force', default_config, user_cfg) 21 | return default_config 22 | end 23 | 24 | function M.get_cfg() 25 | return default_config 26 | end 27 | 28 | return M 29 | -------------------------------------------------------------------------------- /lua/nt-cpp-tools/internal.lua: -------------------------------------------------------------------------------- 1 | local ts_utils = require("nvim-treesitter.ts_utils") 2 | local ts_query = require("nvim-treesitter.query") 3 | local parsers = require("nvim-treesitter.parsers") 4 | local previewer = require("nt-cpp-tools.preview_printer") 5 | local output_handlers = require("nt-cpp-tools.output_handlers") 6 | local util = require("nt-cpp-tools.util") 7 | 8 | local M = {} 9 | 10 | local function get_node_text(node, bufnr) 11 | if not node then 12 | return {} 13 | end 14 | 15 | bufnr = bufnr or vim.api.nvim_get_current_buf() 16 | 17 | local txtStr = vim.treesitter.get_node_text(node, bufnr) 18 | local txt = {} 19 | 20 | for str in string.gmatch(txtStr, "([^\n]+)") do 21 | table.insert(txt, str) 22 | end 23 | return txt 24 | end 25 | 26 | local function run_on_nodes(query, runner, sel_start_row, sel_end_row) 27 | local bufnr = 0 28 | local ft = vim.api.nvim_buf_get_option(bufnr, 'ft') 29 | 30 | local parser = parsers.get_parser(bufnr, ft) 31 | local root = parser:parse()[1]:root() 32 | 33 | local matches = query:iter_matches(root, bufnr, sel_start_row, sel_end_row + 1) 34 | 35 | while true do 36 | local pattern, match = matches() 37 | if pattern == nil then 38 | break 39 | end 40 | if type(match[1][1]) == 'userdata' then -- fix for 6913c5e1 in nvim 41 | for _, m in pairs(match) do 42 | runner(query.captures, m) 43 | end 44 | else 45 | runner(query.captures, match) 46 | end 47 | end 48 | 49 | return true 50 | end 51 | 52 | 53 | local function t2s(txt) 54 | local value 55 | for id, line in pairs(txt) do 56 | if line ~= '' then 57 | value = (id == 1 and line or value .. '\n' .. line) 58 | end 59 | end 60 | return value 61 | end 62 | 63 | local function get_default_values_locations(t) 64 | local positions = {} 65 | local child_count = t:child_count() 66 | -- inorder to remove strings easier, 67 | -- doing reverse order 68 | for j = child_count-1, 0, -1 do 69 | local child = t:child(j) 70 | if child:type() == 'optional_parameter_declaration' then 71 | local _, _, start_row, start_col = child:field('declarator')[1]:range() 72 | local _, _, end_row, end_col = child:field('default_value')[1]:range() 73 | table.insert(positions, 74 | { start_row = start_row, 75 | start_col = start_col, 76 | end_row = end_row, 77 | end_col = end_col 78 | } 79 | ) 80 | end 81 | end 82 | return positions 83 | end 84 | 85 | local function remove_entries_and_get_node_string(node, entries) 86 | -- we expect entries to be sorted from end to begining when 87 | -- considering a row so that changing the statement will not 88 | -- mess up the indexes of the entries 89 | local base_row_offset, base_col_offset, _, _ = node:range() 90 | local txt = get_node_text(node) 91 | for _, entry in pairs(entries) do 92 | entry.start_row = entry.start_row - base_row_offset + 1 93 | entry.end_row = entry.end_row - base_row_offset + 1 94 | -- start row is trimmed to the tagged other rows are not 95 | local column_offset = entry.start_row > 1 and 0 or base_col_offset 96 | if entry.start_row == entry.end_row then 97 | local line = txt[entry.start_row] 98 | local s = line:sub(1, entry.start_col - column_offset) 99 | local e = line:sub(entry.end_col - column_offset + 1) 100 | txt[entry.start_row] = s .. e 101 | else 102 | txt[entry.start_row] = txt[entry.start_row]:sub(1, entry.start_col - column_offset) 103 | -- we will just mark the rows in between as empty since deleting will 104 | -- mess up locations of following entries 105 | for l = entry.start_row + 1, entry.end_row - 1, 1 do 106 | txt[l] = '' 107 | end 108 | 109 | local tail_txt = txt[entry.end_row] 110 | local indent_start, indent_end = tail_txt:find('^ *') 111 | local indent_str = string.format('%' .. (indent_end - indent_start) .. 's', ' ') 112 | 113 | -- no need to add column offset since we know end_row is not trimmed 114 | txt[entry.end_row] = indent_str .. tail_txt:sub(entry.end_col + 1) 115 | end 116 | end 117 | return txt 118 | end 119 | 120 | local function check_get_template_info(node) 121 | if node:parent():type() ~= 'template_declaration' then 122 | return nil, nil 123 | end 124 | 125 | local typename_names = {} 126 | local remove_entries = {} 127 | 128 | local template_param_list = node:parent():field('parameters')[1] 129 | local parameters_count = template_param_list:named_child_count() 130 | for param_id = parameters_count - 1, 0, -1 do 131 | local param_node = template_param_list:named_child(param_id) 132 | if param_node:type() == 'type_parameter_declaration' then 133 | table.insert(typename_names, 134 | t2s(get_node_text(param_node:named_child(0)))) 135 | elseif param_node:type() == 'optional_type_parameter_declaration' then 136 | local type_identifier = param_node:field('name')[1] 137 | table.insert(typename_names, 138 | t2s(get_node_text(type_identifier))) 139 | local _, _, start_row, start_col = type_identifier:range() 140 | local _, _, end_row, end_col = param_node:field('default_type')[1]:range() 141 | table.insert(remove_entries, 142 | { start_row = start_row, 143 | start_col = start_col, 144 | end_row = end_row, 145 | end_col = end_col 146 | } 147 | ) 148 | end 149 | end 150 | return t2s(remove_entries_and_get_node_string(template_param_list, remove_entries)), 151 | typename_names 152 | end 153 | 154 | 155 | -- supports both reference return type and non reference return type 156 | -- and no return type member functions 157 | local function get_member_function_data(node) 158 | local result = {template = '', ret_type = '', fun_dec = '', class_details = nil} 159 | 160 | result.template, _ = check_get_template_info(node) 161 | result.template = result.template and 'template ' .. result.template 162 | 163 | local return_node = node:field('type')[1] 164 | local function_dec_node = node:field('declarator')[1] 165 | 166 | if next(node:field('default_value')) ~= nil then -- pure virtual 167 | return nil 168 | end 169 | 170 | result.ret_type = t2s(get_node_text(return_node)) -- return tye 171 | local node_child_count = node:named_child_count() 172 | for c = 0, node_child_count - 1, 1 do 173 | local child = node:named_child(c) 174 | if child:type() == 'type_qualifier' then -- return constness 175 | result.ret_type = t2s(get_node_text(child)) .. ' ' .. result.ret_type 176 | break 177 | end 178 | end 179 | 180 | if function_dec_node:type() == 'reference_declarator' or 181 | function_dec_node:type() == 'pointer_declarator' then 182 | result.ret_type = result.ret_type .. 183 | (function_dec_node:type() == 'reference_declarator' and '&' or '*') 184 | function_dec_node = function_dec_node:named_child(0) 185 | end 186 | 187 | result.fun_dec = t2s(get_node_text(function_dec_node:field('declarator')[1])) 188 | 189 | local fun_params = function_dec_node:field('parameters')[1] 190 | result.fun_dec = result.fun_dec .. t2s(remove_entries_and_get_node_string(fun_params, 191 | get_default_values_locations(fun_params))) 192 | 193 | local fun_dec_child_count = function_dec_node:named_child_count() 194 | for c = 0, fun_dec_child_count - 1, 1 do 195 | local child = function_dec_node:named_child(c) 196 | if child:type() == 'type_qualifier' or child:type() == 'noexcept' then -- function constness or noexcept 197 | result.fun_dec = result.fun_dec .. ' ' .. t2s(get_node_text(child)) 198 | end 199 | if child:type() == 'trailing_return_type' then 200 | result.fun_dec = result.fun_dec .. ' ' .. t2s(get_node_text(child)) 201 | end 202 | end 203 | return result 204 | end 205 | 206 | local function get_nth_parent(node, n) 207 | local parent = node 208 | for _ = 0 , n , 1 do 209 | parent = parent:parent() 210 | if not parent then return nil end 211 | end 212 | return parent 213 | end 214 | 215 | local function find_class_details(member_node, member_data) 216 | member_data.class_details = {} 217 | local end_row 218 | 219 | if member_node:parent():type() == 'template_declaration' then 220 | member_node = member_node:parent() 221 | end 222 | -- print(member_node:parent():type()) 223 | 224 | -- If global function, member node is the highest, no class data available 225 | -- but function requires the scope end row to return 226 | if member_node:parent():type() == 'translation_unit' or 227 | member_node:parent():type() == 'preproc_ifdef' or 228 | ( member_node:parent():parent() ~= nil and 229 | member_node:parent():parent():type() == 'namespace_definition') then 230 | _, _, end_row, _ = member_node:range() 231 | return end_row 232 | end 233 | 234 | -- the function could be a template, therefore going an extra parent higher 235 | local class_node = member_node:parent():parent() 236 | 237 | while class_node and 238 | (class_node:type() == 'class_specifier' or 239 | class_node:type() == 'struct_specifier' or 240 | class_node:type() == 'union_specifier' ) do 241 | local class_data = {} 242 | class_data.name = t2s(get_node_text(class_node:field('name')[1])) 243 | 244 | local template_statement, params = check_get_template_info(class_node) 245 | if template_statement then 246 | class_data.class_template_statement = 'template ' .. template_statement 247 | for i = #params, 1, -1 do 248 | local val = params[i] 249 | class_data.class_template_params = (i == #params and '<' or 250 | class_data.class_template_params .. ',') .. val 251 | end 252 | class_data.class_template_params = class_data.class_template_params .. '>' 253 | end 254 | 255 | _, _, end_row, _ = class_node:range() 256 | table.insert(member_data.class_details, class_data) 257 | 258 | class_node = get_nth_parent(class_node, 2) 259 | end 260 | return end_row 261 | end 262 | 263 | function M.imp_func(range_start, range_end, custom_cb) 264 | range_start = range_start - 1 265 | range_end = range_end - 1 266 | 267 | local query = ts_query.get_query('cpp', 'outside_class_def') 268 | 269 | local e_row 270 | local results = {} 271 | local runner = function(captures, match) 272 | for cid, node in pairs(match) do 273 | local cap_str = captures[cid] 274 | if cap_str == 'member_function' then 275 | local fun_start, _, fun_end, _ = node:range() 276 | if fun_end >= range_start and fun_start <= range_end then 277 | local member_data = get_member_function_data(node) 278 | if member_data then 279 | e_row = find_class_details(node, member_data) 280 | table.insert(results, member_data) 281 | end 282 | end 283 | end 284 | end 285 | end 286 | 287 | if not run_on_nodes(query, runner, range_start, range_end) then 288 | return 289 | end 290 | 291 | local output = '' 292 | for _, fun in ipairs(results) do 293 | if fun.fun_dec ~= '' then 294 | 295 | local classes_name 296 | local classes_template_statemets 297 | 298 | if fun.class_details then 299 | for h = #fun.class_details, 1, -1 do 300 | local templ_class_name = fun.class_details[h].name .. 301 | (fun.class_details[h].class_template_params or '') .. '::' 302 | classes_name = (h == #fun.class_details) and templ_class_name or classes_name .. templ_class_name 303 | if fun.class_details[h].class_template_statement then 304 | if not classes_template_statemets then 305 | classes_template_statemets = fun.class_details[h].class_template_statement 306 | else 307 | classes_template_statemets = classes_template_statemets .. ' ' 308 | .. fun.class_details[h].class_template_statement 309 | end 310 | end 311 | end 312 | end 313 | 314 | local template_statements 315 | if classes_template_statemets and fun.template then 316 | template_statements = classes_template_statemets .. ' ' .. fun.template 317 | elseif classes_template_statemets then 318 | template_statements = classes_template_statemets 319 | elseif fun.template then 320 | template_statements = fun.template 321 | end 322 | 323 | output = output .. (template_statements and template_statements .. '\n' or '') .. 324 | (fun.ret_type and fun.ret_type .. ' ' or '' ) .. 325 | (classes_name and classes_name or '') 326 | .. fun.fun_dec .. '\n{\n}\n' 327 | end 328 | end 329 | 330 | if output ~= '' then 331 | local context = {class_end_row = e_row} 332 | if custom_cb then 333 | custom_cb(output, context) 334 | else 335 | output_handlers.get_preview_and_apply()(output, context) 336 | end 337 | end 338 | 339 | end 340 | 341 | function M.concrete_class_imp(range_start, range_end) 342 | range_start = range_start - 1 343 | range_end = range_end - 1 344 | 345 | local query = ts_query.get_query('cpp', 'concrete_implement') 346 | local base_class = '' 347 | local results = {} 348 | local e_row 349 | local runner = function(captures, matches) 350 | for p, node in pairs(matches) do 351 | local cap_str = captures[p] 352 | local value = '' 353 | for id, line in pairs(get_node_text(node)) do 354 | value = (id == 1 and line or value .. '\n' .. line) 355 | end 356 | 357 | if cap_str == 'base_class_name' then 358 | base_class = value 359 | elseif cap_str == 'class' then 360 | _, _, e_row, _ = node:range() 361 | elseif cap_str == 'virtual' then 362 | results[#results+1] = value:gsub('^virtual', ''):gsub([[= *0]], 'override') 363 | end 364 | end 365 | end 366 | 367 | if not run_on_nodes(query, runner, range_start, range_end) then 368 | return 369 | end 370 | 371 | if #results == 0 then 372 | vim.notify('No virtual functions detected to implement') 373 | return 374 | end 375 | 376 | local class_name = vim.fn.input("New Name: ", base_class .. "Impl") 377 | local class = string.format('class %s : public %s\n{\npublic:\n', class_name, base_class) 378 | for _, imp in ipairs(results) do 379 | class = class .. imp .. '\n' 380 | end 381 | class = class .. '};' 382 | 383 | output_handlers.get_preview_and_apply()(class, {class_end_row = e_row}) 384 | end 385 | 386 | function M.rule_of_5(limit_at_3, range_start, range_end) 387 | range_start = range_start - 1 388 | range_end = range_end - 1 389 | 390 | local query = ts_query.get_query('cpp', 'special_function_detectors') 391 | 392 | local checkers = { 393 | destructor = false, 394 | copy_constructor = false, 395 | copy_assignment = false, 396 | move_constructor = false, 397 | move_assignment = false 398 | } 399 | 400 | local entry_location 401 | local class_name 402 | 403 | local entry_location_update = function (start_row, start_col) 404 | if entry_location == nil or entry_location.start_row < start_row then 405 | entry_location = { start_row = start_row + 1 , start_col = start_col } 406 | end 407 | end 408 | 409 | local runner = function(captures, matches) 410 | for p, node in pairs(matches) do 411 | local cap_str = captures[p] 412 | local value = '' 413 | for id, line in pairs(get_node_text(node)) do 414 | value = (id == 1 and line or value .. '\n' .. line) 415 | end 416 | local start_row, start_col, _, _ = node:range() 417 | 418 | if cap_str == "class_name" then 419 | class_name = value 420 | elseif cap_str == "destructor" then 421 | checkers.destructor = true 422 | entry_location_update(start_row, start_col) 423 | elseif cap_str == "assignment_operator_reference_declarator" then 424 | checkers.copy_assignment = true 425 | entry_location_update(start_row, start_col) 426 | elseif cap_str == "copy_construct_function_declarator" then 427 | checkers.copy_constructor = true 428 | entry_location_update(start_row, start_col) 429 | elseif not limit_at_3 then 430 | if cap_str == "move_assignment_operator_reference_declarator" then 431 | checkers.move_assignment = true 432 | entry_location_update(start_row, start_col) 433 | elseif cap_str == "move_construct_function_declarator" then 434 | checkers.move_constructor = true 435 | entry_location_update(start_row, start_col) 436 | end 437 | end 438 | end 439 | end 440 | 441 | if not run_on_nodes(query, runner, range_start, range_end) then 442 | return 443 | end 444 | 445 | local skip_rule_of_3 = (checkers.copy_assignment and checkers.copy_constructor and checkers.destructor) or 446 | (not checkers.copy_assignment and not checkers.copy_constructor and not checkers.destructor) 447 | 448 | local skip_rule_of_5 = ( ( checkers.copy_assignment and checkers.copy_constructor and checkers.destructor and 449 | checkers.move_assignment and checkers.move_constructor ) or 450 | (not checkers.copy_assignment and not checkers.copy_constructor and not checkers.destructor and 451 | not checkers.move_assignment and not checkers.move_constructor) ) 452 | 453 | if limit_at_3 and skip_rule_of_3 then 454 | local notifyMsg = [[ No change needed since either non or all of the following is implemented 455 | - destructor 456 | - copy constructor 457 | - assignment constructor 458 | ]] 459 | vim.notify(notifyMsg) 460 | return 461 | end 462 | 463 | if not limit_at_3 and skip_rule_of_5 then 464 | local notifyMsg = [[ No change needed since either non or all of the following is implemented 465 | - destructor 466 | - copy constructor 467 | - assignment constructor 468 | - move costructor 469 | - move assignment 470 | ]] 471 | vim.notify(notifyMsg) 472 | return 473 | end 474 | 475 | local add_txt_below_existing_def = function (txt) 476 | util.add_text_edit(txt, entry_location.start_row, entry_location.start_col) 477 | entry_location.start_row = entry_location.start_row + 1 478 | end 479 | 480 | -- We are first adding a empty string on the required line which is of length start_col since 481 | -- lsp text edit cannot add strings beyond already edited region 482 | -- TODO need a stable method of handling this entry 483 | 484 | local newLine = string.format('%' .. (entry_location.start_col + 1) .. 's', '\n') 485 | 486 | if not checkers.copy_assignment then 487 | util.add_text_edit(newLine, entry_location.start_row, 0) 488 | local txt = class_name .. '& operator=(const ' .. class_name .. '&);' 489 | add_txt_below_existing_def(txt) 490 | end 491 | 492 | if not checkers.copy_constructor then 493 | util.add_text_edit(newLine, entry_location.start_row, 0) 494 | local txt = class_name .. '(const ' .. class_name .. '&);' 495 | add_txt_below_existing_def(txt) 496 | end 497 | 498 | if not checkers.destructor then 499 | util.add_text_edit(newLine, entry_location.start_row, 0) 500 | local txt = '~' .. class_name .. '();' 501 | add_txt_below_existing_def(txt) 502 | end 503 | 504 | if not limit_at_3 then 505 | if not checkers.move_assignment then 506 | util.add_text_edit(newLine, entry_location.start_row, 0) 507 | local txt = class_name .. '& operator=(' .. class_name .. '&&) noexcept;' 508 | add_txt_below_existing_def(txt) 509 | end 510 | 511 | if not checkers.move_constructor then 512 | util.add_text_edit(newLine, entry_location.start_row, 0) 513 | local txt = class_name .. '(const ' .. class_name .. '&&) noexcept;' 514 | add_txt_below_existing_def(txt) 515 | end 516 | end 517 | end 518 | 519 | function M.attach(bufnr, lang) 520 | print("attach") 521 | end 522 | 523 | function M.detach(bufnr) 524 | print("dattach") 525 | end 526 | 527 | M.commands = { 528 | TSCppDefineClassFunc = { 529 | run = M.imp_func, 530 | f_args = ", ", 531 | args = { 532 | "-range" 533 | } 534 | }, 535 | TSCppMakeConcreteClass = { 536 | run = M.concrete_class_imp, 537 | f_args = ", ", 538 | args = { 539 | "-range" 540 | } 541 | }, 542 | TSCppRuleOf3 = { 543 | run = function (s, e) M.rule_of_5(true, s, e) end, 544 | f_args = ", ", 545 | args = { 546 | "-range" 547 | } 548 | }, 549 | TSCppRuleOf5 = { 550 | run = function (s, e) M.rule_of_5(false, s, e) end, 551 | f_args = ", ", 552 | args = { 553 | "-range" 554 | } 555 | }, 556 | } 557 | 558 | return M 559 | -------------------------------------------------------------------------------- /lua/nt-cpp-tools/output_handlers.lua: -------------------------------------------------------------------------------- 1 | local util = require("nt-cpp-tools.util") 2 | local configs = require("nt-cpp-tools.config") 3 | local previewer = require("nt-cpp-tools.preview_printer") 4 | 5 | local M = {} 6 | 7 | 8 | local function preview_and_apply(output, context) 9 | local on_preview_succces = function (row) 10 | util.add_text_edit(output, row, 0) 11 | end 12 | 13 | previewer.start_preview(output, context.class_end_row + 1, on_preview_succces) 14 | end 15 | 16 | function M.get_preview_and_apply(_) 17 | return preview_and_apply 18 | end 19 | 20 | local function add_to_cpp(output, _) 21 | local config = configs.get_cfg() 22 | local file_name = vim.fn.expand('%:r') 23 | vim.api.nvim_command('vsp ' .. file_name .. 24 | '.' .. config.source_extension) 25 | util.add_text_edit(output, 1, 0) 26 | end 27 | 28 | function M.get_add_to_cpp(_) 29 | return add_to_cpp 30 | end 31 | 32 | return M 33 | -------------------------------------------------------------------------------- /lua/nt-cpp-tools/preview_printer.lua: -------------------------------------------------------------------------------- 1 | local configs = require "nt-cpp-tools.config" 2 | 3 | local M = {} 4 | 5 | local mark_id 6 | local last_buffer 7 | local ns_id 8 | local result 9 | local on_accept_callbck 10 | 11 | local function remove_virt_text() 12 | if mark_id and vim.api.nvim_buf_is_valid(last_buffer) then 13 | vim.api.nvim_buf_del_extmark(last_buffer, ns_id, mark_id) 14 | end 15 | end 16 | 17 | local function draw_virtual_text(txt, row) 18 | remove_virt_text() 19 | 20 | if txt then 21 | result = {} 22 | for line in txt:gmatch('[^\n]+') do 23 | result[#result+1] = {{line, 'TSCppHighlight'}} 24 | end 25 | end 26 | 27 | last_buffer = vim.fn.bufnr('%') 28 | ns_id = vim.api.nvim_create_namespace('TSCppTools') 29 | 30 | local line_num = row - 1 31 | local col_num = 0 32 | 33 | local opts = { 34 | id = 1, 35 | virt_lines = result 36 | } 37 | 38 | mark_id = vim.api.nvim_buf_set_extmark(last_buffer, ns_id, line_num, col_num, opts) 39 | end 40 | 41 | 42 | local function end_preview() 43 | local config = configs.get_cfg() 44 | vim.api.nvim_del_keymap('n', config.preview.quit) 45 | vim.api.nvim_del_keymap('n', config.preview.accept) 46 | remove_virt_text() 47 | vim.cmd( [[autocmd! TSCppTools *]]) 48 | end 49 | 50 | function M.flush_and_end_preview() 51 | end_preview() 52 | on_accept_callbck = nil 53 | end 54 | 55 | function M.accept_and_end_preview() 56 | end_preview() 57 | on_accept_callbck(vim.api.nvim_win_get_cursor(0)[1]) 58 | on_accept_callbck = nil 59 | end 60 | 61 | function M.on_cursor_moved() 62 | draw_virtual_text(nil, vim.api.nvim_win_get_cursor(0)[1]) 63 | end 64 | 65 | function M.start_preview(txt, insert_row, on_accept_cb) 66 | on_accept_callbck = on_accept_cb 67 | local keymap_config = { silent = true, noremap = true } 68 | 69 | local config = configs.get_cfg() 70 | 71 | vim.api.nvim_set_keymap('n', config.preview.quit, 72 | ":lua require'nt-cpp-tools.preview_printer'.flush_and_end_preview()", 73 | keymap_config) 74 | vim.api.nvim_set_keymap('n', config.preview.accept, 75 | ":lua require'nt-cpp-tools.preview_printer'.accept_and_end_preview()", 76 | keymap_config) 77 | 78 | draw_virtual_text(txt, vim.api.nvim_win_get_cursor(0)[1]) 79 | 80 | vim.cmd( 81 | [[ 82 | augroup TSCppTools 83 | autocmd! CursorMoved * lua require'nt-cpp-tools.preview_printer'.on_cursor_moved() 84 | augroup END 85 | ]] 86 | ) 87 | 88 | vim.api.nvim_win_set_cursor(0, {insert_row, 0}) 89 | M.on_cursor_moved() 90 | end 91 | 92 | return M 93 | -------------------------------------------------------------------------------- /lua/nt-cpp-tools/util.lua: -------------------------------------------------------------------------------- 1 | local buffer_writer = require("nt-cpp-tools.buffer_writer") 2 | 3 | local M = {} 4 | function M.add_text_edit(text, start_row, start_col) 5 | local edit = {} 6 | table.insert(edit, { 7 | range = { 8 | start = { line = start_row, character = start_col}, 9 | ["end"] = { line = start_row, character = start_col} 10 | }, 11 | newText = text 12 | }) 13 | buffer_writer.apply_text_edits(edit, 0) 14 | end 15 | 16 | return M 17 | -------------------------------------------------------------------------------- /plugin/nt-cpp-tools.vim: -------------------------------------------------------------------------------- 1 | lua require("nt-cpp-tools") 2 | -------------------------------------------------------------------------------- /queries/cpp/concrete_implement.scm: -------------------------------------------------------------------------------- 1 | (class_specifier 2 | name: (type_identifier) @base_class_name 3 | body: (field_declaration_list 4 | (access_specifier) 5 | (field_declaration 6 | (virtual) 7 | type: (_)* 8 | declarator: (_)* 9 | default_value: (number_literal) 10 | )@virtual 11 | ) 12 | ) @class 13 | -------------------------------------------------------------------------------- /queries/cpp/outside_class_def.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (field_declaration 3 | [ 4 | (function_declarator) 5 | (reference_declarator 6 | (function_declarator)) 7 | (pointer_declarator 8 | (function_declarator)) 9 | (trailing_return_type) 10 | ] 11 | ) 12 | (declaration 13 | [ 14 | (function_declarator) 15 | (reference_declarator 16 | (function_declarator)) 17 | (pointer_declarator 18 | (function_declarator)) 19 | (trailing_return_type) 20 | ] 21 | ) 22 | ]@member_function 23 | -------------------------------------------------------------------------------- /queries/cpp/special_function_detectors.scm: -------------------------------------------------------------------------------- 1 | ;; destructor filter detector 2 | (class_specifier 3 | name: (type_identifier) @class_name 4 | body: (field_declaration_list 5 | (declaration 6 | (function_declarator 7 | (destructor_name) @destructor 8 | ) 9 | ) 10 | ) 11 | ) @class 12 | 13 | ;assignment constructor detector 14 | (class_specifier 15 | name: (type_identifier) @class_name 16 | body: (field_declaration_list 17 | (field_declaration 18 | (reference_declarator 19 | (function_declarator 20 | (operator_name 21 | "operator" "=" 22 | ) 23 | (parameter_list 24 | (parameter_declaration 25 | (abstract_reference_declarator "&") 26 | ) 27 | ) 28 | ) 29 | ) @assignment_operator_reference_declarator 30 | ) 31 | ) 32 | ) @class 33 | 34 | ;move assignment constructor detector 35 | (class_specifier 36 | name: (type_identifier) @class_name 37 | body: (field_declaration_list 38 | (field_declaration 39 | (reference_declarator 40 | (function_declarator 41 | (operator_name 42 | "operator" "=" 43 | ) 44 | (parameter_list 45 | (parameter_declaration 46 | (abstract_reference_declarator "&&") 47 | ) 48 | ) 49 | ) 50 | ) @move_assignment_operator_reference_declarator 51 | ) 52 | ) 53 | ) @class 54 | 55 | ;copy construct detector 56 | (class_specifier 57 | name: (type_identifier) @class_name 58 | body: (field_declaration_list 59 | (declaration 60 | (function_declarator 61 | (parameter_list 62 | (parameter_declaration 63 | type: (type_identifier) @copy_construct_args 64 | (#eq? @copy_construct_args @class_name) 65 | declarator: (abstract_reference_declarator "&") 66 | ) 67 | ) 68 | )@copy_construct_function_declarator ;since we need the coordinates 69 | ) 70 | ) 71 | ) @class 72 | 73 | ;move construct detector 74 | (class_specifier 75 | name: (type_identifier) @class_name 76 | body: (field_declaration_list 77 | (declaration 78 | (function_declarator 79 | (parameter_list 80 | (parameter_declaration 81 | type: (type_identifier) @copy_construct_args 82 | (#eq? @copy_construct_args @class_name) 83 | declarator: (abstract_reference_declarator "&&") 84 | ) 85 | ) 86 | )@move_construct_function_declarator ;since we need the coordinates 87 | ) 88 | ) 89 | ) @class 90 | -------------------------------------------------------------------------------- /scripts/minimal_setup.lua: -------------------------------------------------------------------------------- 1 | vim.cmd [[set runtimepath+=.]] 2 | vim.cmd [[set runtimepath+=/home/bhashith/lazy/nvim-treesitter/]] 3 | vim.cmd [[set runtimepath+=/home/bhashith/lazy/plenary.nvim/]] 4 | vim.cmd [[runtime! plugin/plenary.vim]] 5 | vim.cmd [[runtime! plugin/nvim-treesitter.lua]] 6 | vim.cmd [[runtime! plugin/nt-cpp-tools.vim]] 7 | 8 | vim.o.swapfile = false 9 | vim.bo.swapfile = false 10 | vim.o.filetype = 'cpp' 11 | 12 | require("nvim-treesitter.configs").setup { 13 | } 14 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | nvim --headless --noplugin -u ./scripts/minimal_setup.lua -c "PlenaryBustedDirectory ./ { minimal_init = './scripts/minimal_setup.lua'}" 3 | -------------------------------------------------------------------------------- /test/implement_functions.txt: -------------------------------------------------------------------------------- 1 | ============================================ 2 | def_constructor 3 | ============================================ 4 | class C 5 | { 6 | public: 7 | C(); 8 | }; 9 | ----------------------------- 10 | C::C() 11 | { 12 | } 13 | ---------------------------- 14 | 4,4 15 | 16 | ============================================ 17 | destructor 18 | ============================================ 19 | class C 20 | { 21 | public: 22 | ~C(); 23 | }; 24 | ----------------------------- 25 | C::~C() 26 | { 27 | } 28 | ---------------------------- 29 | 4,4 30 | 31 | ============================================ 32 | copy_constructor 33 | ============================================ 34 | class C 35 | { 36 | public: 37 | C(const C&); 38 | }; 39 | ----------------------------- 40 | C::C(const C&) 41 | { 42 | } 43 | ---------------------------- 44 | 4,4 45 | 46 | ============================================ 47 | copy_assignment 48 | ============================================ 49 | class C 50 | { 51 | public: 52 | C& operator=(const C&); 53 | }; 54 | ----------------------------- 55 | C& C::operator=(const C&) 56 | { 57 | } 58 | ---------------------------- 59 | 4,4 60 | 61 | =========================================== 62 | with_return_type 63 | =========================================== 64 | class C 65 | { 66 | public: 67 | void test(); 68 | }; 69 | ------------------------------------- 70 | void C::test() 71 | { 72 | } 73 | ------------------------------------- 74 | 4, 4 75 | 76 | =========================================== 77 | with_trailing_return_type 78 | =========================================== 79 | class C 80 | { 81 | public: 82 | auto test() -> void; 83 | }; 84 | ------------------------------------- 85 | auto C::test() -> void 86 | { 87 | } 88 | ------------------------------------- 89 | 4, 4 90 | 91 | =========================================== 92 | with_return_type_and_args 93 | =========================================== 94 | class C 95 | { 96 | public: 97 | void test(int i); 98 | }; 99 | ------------------------------------- 100 | void C::test(int i) 101 | { 102 | } 103 | ------------------------------------- 104 | 4, 4 105 | 106 | =========================================== 107 | with_trailing_return_type_and_args 108 | =========================================== 109 | class C 110 | { 111 | public: 112 | auto test(int i) -> void; 113 | }; 114 | ------------------------------------- 115 | auto C::test(int i) -> void 116 | { 117 | } 118 | ------------------------------------- 119 | 4, 4 120 | 121 | ================================= 122 | with_ref_return_type_and_args 123 | =============================== 124 | #include 125 | class C 126 | { 127 | public: 128 | std::vector& test(int i); 129 | }; 130 | ------------------------- 131 | std::vector& C::test(int i) 132 | { 133 | } 134 | ---------------------------- 135 | 5,5 136 | 137 | ================================= 138 | with_trailing_ref_return_type_and_args 139 | =============================== 140 | #include 141 | class C 142 | { 143 | public: 144 | auto test(int i) -> std::vector&; 145 | }; 146 | ------------------------- 147 | auto C::test(int i) -> std::vector& 148 | { 149 | } 150 | ---------------------------- 151 | 5,5 152 | 153 | ==================================== 154 | default_value 155 | ================================= 156 | #include 157 | class C 158 | { 159 | public: 160 | std::vector& test(int i = 1); 161 | }; 162 | ------------------------------ 163 | std::vector& C::test(int i) 164 | { 165 | } 166 | ----------------------------- 167 | 5,5 168 | 169 | ==================================== 170 | default_value_trailing_return_type 171 | ================================= 172 | #include 173 | class C 174 | { 175 | public: 176 | auto test(int i = 1) -> std::vector&; 177 | }; 178 | ------------------------------ 179 | auto C::test(int i) -> std::vector& 180 | { 181 | } 182 | ----------------------------- 183 | 5,5 184 | 185 | ============================================ 186 | const_func_const_reference_return 187 | ============================================ 188 | #include 189 | class C 190 | { 191 | public: 192 | const std::vector& j() const; 193 | }; 194 | ----------------------------- 195 | const std::vector& C::j() const 196 | { 197 | } 198 | ---------------------------- 199 | 5,5 200 | 201 | ============================================ 202 | const_func_const_reference_return_trailing_return_type 203 | ============================================ 204 | #include 205 | class C 206 | { 207 | public: 208 | auto j() const -> const std::vector&; 209 | }; 210 | ----------------------------- 211 | auto C::j() const -> const std::vector& 212 | { 213 | } 214 | ---------------------------- 215 | 5,5 216 | 217 | ============================================ 218 | const_func_const_reference_return_override 219 | ============================================ 220 | #include 221 | class A 222 | { 223 | public: 224 | virtual const std::vector& j() const = 0; 225 | }; 226 | class C : public A 227 | { 228 | public: 229 | const std::vector& j() const override; 230 | }; 231 | ----------------------------- 232 | const std::vector& C::j() const 233 | { 234 | } 235 | ---------------------------- 236 | 10,10 237 | 238 | ============================================ 239 | const_func_const_reference_trailing_return_override 240 | ============================================ 241 | #include 242 | class A 243 | { 244 | public: 245 | virtual auto j() const -> const std::vector& = 0; 246 | }; 247 | class C : public A 248 | { 249 | public: 250 | auto j() const -> const std::vector& override; 251 | }; 252 | ----------------------------- 253 | auto C::j() const -> const std::vector& 254 | { 255 | } 256 | ---------------------------- 257 | 10,10 258 | 259 | ============================================ 260 | const_noexcept_with_trailing_return_const_override 261 | ============================================ 262 | #include 263 | class A 264 | { 265 | public: 266 | virtual auto test2() const noexcept -> const std::vector& = 0; 267 | }; 268 | class C : public A 269 | { 270 | public: 271 | auto test2() const noexcept -> const std::vector& override; 272 | }; 273 | ----------------------------- 274 | auto C::test2() const noexcept -> const std::vector& 275 | { 276 | } 277 | ----------------------------- 278 | 10,10 279 | 280 | ============================================ 281 | class_template 282 | ============================================ 283 | #include 284 | template 285 | class C 286 | { 287 | public: 288 | const std::vector& j() const; 289 | }; 290 | ----------------------------- 291 | template 292 | const std::vector& C::j() const 293 | { 294 | } 295 | ---------------------------- 296 | 6,6 297 | 298 | ============================================ 299 | class_template_function_template 300 | ============================================ 301 | #include 302 | template 303 | class C 304 | { 305 | public: 306 | template 307 | const std::vector& j(K l, H f) const; 308 | }; 309 | ----------------------------- 310 | template template 311 | const std::vector& C::j(K l, H f) const 312 | { 313 | } 314 | ---------------------------- 315 | 7,7 316 | 317 | ============================================ 318 | class_template_function_template_with_default_values 319 | ============================================ 320 | #include 321 | template 322 | class C 323 | { 324 | public: 325 | template 326 | const std::vector& j(K l, H f) const; 327 | }; 328 | ----------------------------- 329 | template template 330 | const std::vector& C::j(K l, H f) const 331 | { 332 | } 333 | ---------------------------- 334 | 7,7 335 | 336 | ============================================ 337 | normal_nested_class 338 | ============================================ 339 | class C 340 | { 341 | public: 342 | class J 343 | { 344 | public: 345 | void test(); 346 | }; 347 | }; 348 | ----------------------------- 349 | void C::J::test() 350 | { 351 | } 352 | ---------------------------- 353 | 7, 7 354 | 355 | 356 | ============================================ 357 | template_nested_class_tempalte_class 358 | ============================================ 359 | template 360 | class C 361 | { 362 | public: 363 | template 364 | class J 365 | { 366 | public: 367 | void test(); 368 | }; 369 | }; 370 | ----------------------------- 371 | template template 372 | void C::J::test() 373 | { 374 | } 375 | ---------------------------- 376 | 9, 9 377 | 378 | 379 | ============================================ 380 | template_nested_class_tempalte_class_template_fun 381 | ============================================ 382 | template 383 | class C 384 | { 385 | public: 386 | template 387 | class J 388 | { 389 | public: 390 | template 391 | void test(S s); 392 | }; 393 | }; 394 | ----------------------------- 395 | template template template 396 | void C::J::test(S s) 397 | { 398 | } 399 | ---------------------------- 400 | 10, 10 401 | 402 | 403 | ============================================ 404 | template_nested_class_template_class_template_fun_with_normal 405 | ============================================ 406 | template 407 | class C 408 | { 409 | public: 410 | template 411 | class J 412 | { 413 | public: 414 | template 415 | void test(S s); 416 | void test2(); 417 | }; 418 | void test4(); 419 | }; 420 | ----------------------------- 421 | template template template 422 | void C::J::test(S s) 423 | { 424 | } 425 | template template 426 | void C::J::test2() 427 | { 428 | } 429 | template 430 | void C::test4() 431 | { 432 | } 433 | 434 | ---------------------------- 435 | 1, 14 436 | 437 | ============================================ 438 | nested_class_template_class_with_constructor 439 | ============================================ 440 | template 441 | class C 442 | { 443 | public: 444 | class J 445 | { 446 | public: 447 | void J(T s); 448 | void test2(); 449 | }; 450 | void test4(); 451 | }; 452 | ----------------------------- 453 | template 454 | void C::J::J(T s) 455 | { 456 | } 457 | 458 | ---------------------------- 459 | 8,8 460 | 461 | ============================================ 462 | nested_template_class_class_with_constructor 463 | ============================================ 464 | class C 465 | { 466 | public: 467 | template 468 | class J 469 | { 470 | public: 471 | void J(T s); 472 | void test2(); 473 | }; 474 | void test4(); 475 | }; 476 | ----------------------------- 477 | template 478 | void C::J::J(T s) 479 | { 480 | } 481 | 482 | ---------------------------- 483 | 8,8 484 | 485 | ============================================ 486 | variable_with_functions 487 | ============================================ 488 | class C 489 | { 490 | public: 491 | void test(); 492 | int& test2(); 493 | private: 494 | int a; 495 | int& b; 496 | }; 497 | ----------------------------- 498 | void C::test() 499 | { 500 | } 501 | int& C::test2() 502 | { 503 | } 504 | ---------------------------- 505 | 1,9 506 | 507 | ============================================ 508 | variable_with_functions_templates 509 | ============================================ 510 | template 511 | class C 512 | { 513 | public: 514 | void test(); 515 | int& test2(); 516 | private: 517 | int a; 518 | T& b; 519 | }; 520 | ----------------------------- 521 | template 522 | void C::test() 523 | { 524 | } 525 | template 526 | int& C::test2() 527 | { 528 | } 529 | ---------------------------- 530 | 1,10 531 | 532 | ============================================ 533 | pointer_return 534 | ============================================ 535 | class C 536 | { 537 | public: 538 | int* test2(); 539 | }; 540 | ----------------------------- 541 | int* C::test2() 542 | { 543 | } 544 | ---------------------------- 545 | 4,4 546 | 547 | 548 | ============================================ 549 | pointer_return_template_class 550 | ============================================ 551 | template 552 | class C 553 | { 554 | public: 555 | int* test2(); 556 | }; 557 | ----------------------------- 558 | template 559 | int* C::test2() 560 | { 561 | } 562 | ---------------------------- 563 | 5,5 564 | 565 | 566 | ============================================ 567 | pointer_return_template_function 568 | ============================================ 569 | class C 570 | { 571 | public: 572 | template 573 | int* test2(); 574 | }; 575 | ----------------------------- 576 | template 577 | int* C::test2() 578 | { 579 | } 580 | ---------------------------- 581 | 5,5 582 | 583 | 584 | ============================================ 585 | vector_pointer_return_template_function 586 | ============================================ 587 | #include 588 | class C 589 | { 590 | public: 591 | template 592 | std::vector* test2(); 593 | }; 594 | ----------------------------- 595 | template 596 | std::vector* C::test2() 597 | { 598 | } 599 | ---------------------------- 600 | 6,6 601 | 602 | ============================================ 603 | vector_pointer_return_template_function 604 | ============================================ 605 | #include 606 | class C 607 | { 608 | public: 609 | template 610 | auto test2() -> std::vector*; 611 | }; 612 | ----------------------------- 613 | template 614 | auto C::test2() -> std::vector* 615 | { 616 | } 617 | ---------------------------- 618 | 6,6 619 | 620 | ============================================ 621 | noexcept_with_template 622 | ============================================ 623 | class C 624 | { 625 | public: 626 | template 627 | int* test2() noexcept; 628 | }; 629 | ----------------------------- 630 | template 631 | int* C::test2() noexcept 632 | { 633 | } 634 | ----------------------------- 635 | 5,5 636 | 637 | ============================================ 638 | noexcept_with_template_trailing_return 639 | ============================================ 640 | class C 641 | { 642 | public: 643 | template 644 | auto test2() noexcept -> int*; 645 | }; 646 | ----------------------------- 647 | template 648 | auto C::test2() noexcept -> int* 649 | { 650 | } 651 | ----------------------------- 652 | 5,5 653 | 654 | ============================================ 655 | const_noexcept_with_template 656 | ============================================ 657 | class C 658 | { 659 | public: 660 | template 661 | int* test2() const noexcept; 662 | }; 663 | ----------------------------- 664 | template 665 | int* C::test2() const noexcept 666 | { 667 | } 668 | ----------------------------- 669 | 5,5 670 | 671 | ============================================ 672 | const_noexcept_with_template_trailing_return 673 | ============================================ 674 | class C 675 | { 676 | public: 677 | template 678 | auto test2() const noexcept -> int*; 679 | }; 680 | ----------------------------- 681 | template 682 | auto C::test2() const noexcept -> int* 683 | { 684 | } 685 | ----------------------------- 686 | 5,5 687 | 688 | ============================================ 689 | simple_global_functions 690 | ============================================ 691 | void test(); 692 | ----------------------------- 693 | void test() 694 | { 695 | } 696 | ----------------------------- 697 | 1,1 698 | 699 | ============================================ 700 | global_functions_with_args 701 | ============================================ 702 | void test(int a, int b, std::vector d); 703 | ----------------------------- 704 | void test(int a, int b, std::vector d) 705 | { 706 | } 707 | ----------------------------- 708 | 1,1 709 | 710 | ============================================ 711 | global_functions_with_args_with_return 712 | ============================================ 713 | std::map test(int a, int b, std::vector d); 714 | ----------------------------- 715 | std::map test(int a, int b, std::vector d) 716 | { 717 | } 718 | ----------------------------- 719 | 1,1 720 | 721 | ============================================ 722 | global_functions_with_args_with_trailing_return 723 | ============================================ 724 | auto test(int a, int b, std::vector d) -> std::map; 725 | ----------------------------- 726 | auto test(int a, int b, std::vector d) -> std::map 727 | { 728 | } 729 | ----------------------------- 730 | 1,1 731 | 732 | ============================================ 733 | template_global_functions_with_args 734 | ============================================ 735 | template 736 | void test(int a, int b, std::vector d); 737 | ----------------------------- 738 | template 739 | void test(int a, int b, std::vector d) 740 | { 741 | } 742 | ----------------------------- 743 | 1,2 744 | ============================================ 745 | template_global_functions_with_args_with_return 746 | ============================================ 747 | template 748 | std::map test(int a, int b, std::vector d); 749 | ----------------------------- 750 | template 751 | std::map test(int a, int b, std::vector d) 752 | { 753 | } 754 | ----------------------------- 755 | 1,2 756 | ============================================ 757 | template_global_functions_with_args_with_trailing_return 758 | ============================================ 759 | template 760 | auto test(int a, int b, std::vector d) -> std::map; 761 | ----------------------------- 762 | template 763 | auto test(int a, int b, std::vector d) -> std::map 764 | { 765 | } 766 | ----------------------------- 767 | 1,2 768 | ============================================ 769 | template_global_functions 770 | ============================================ 771 | template 772 | void test(); 773 | ----------------------------- 774 | template 775 | void test() 776 | { 777 | } 778 | ----------------------------- 779 | 1,2 780 | ============================================ 781 | global_functions_w_header_guard 782 | ============================================ 783 | #ifndef TEST_H 784 | #define TEST_H 785 | void test(); 786 | #endif 787 | ----------------------------- 788 | void test() 789 | { 790 | } 791 | ----------------------------- 792 | 3,3 793 | ============================================ 794 | global_functions_w_namespace 795 | ============================================ 796 | namespace t{ 797 | void test(); 798 | } 799 | ----------------------------- 800 | void test() 801 | { 802 | } 803 | ----------------------------- 804 | 2,2 805 | -------------------------------------------------------------------------------- /test/test_spec.lua: -------------------------------------------------------------------------------- 1 | local mock = require('luassert.mock') 2 | local match = require 'luassert.match' 3 | local buf_writer = require 'nt-cpp-tools.buffer_writer' 4 | 5 | local function lsp_txt(_, arguments, _) 6 | local expected = arguments[1] 7 | local test_name = arguments[2] 8 | return function (value) 9 | local received = value[1].newText 10 | received = received:gsub('\n*$', '') 11 | expected = expected:gsub('\n*$', '') 12 | if expected ~= received then 13 | vim.api.nvim_command('w! ' .. test_name) 14 | print('expected ' .. expected:gsub('\n', 'L')) 15 | print('received ' .. received:gsub('\n', 'L')) 16 | return false 17 | else 18 | return true 19 | end 20 | end 21 | end 22 | 23 | assert:register("matcher", "lsp_txt", lsp_txt) 24 | 25 | 26 | local function read_test_file(filename, scenario_list) 27 | local file = io.open(filename, 'r') 28 | 29 | while true do 30 | local line = file:read() 31 | 32 | if not line then 33 | break 34 | end 35 | 36 | if line:find('^=+$') then 37 | local test_case = {name = nil, input = nil, expected = nil, range = nil} 38 | line = file:read() 39 | test_case.name = line 40 | 41 | line = file:read() 42 | assert(line:find('^=+$') ~= nil, 'expected ===== line') 43 | 44 | line = file:read() 45 | while line:find('^-+$') == nil do 46 | if test_case.input then 47 | test_case.input = test_case.input .. '\n' .. line 48 | else 49 | test_case.input = line 50 | end 51 | line = file:read() 52 | end 53 | 54 | line = file:read() 55 | while line:find('^-+$') == nil do 56 | if test_case.expected then 57 | test_case.expected = test_case.expected .. '\n' .. line 58 | else 59 | test_case.expected = line 60 | end 61 | line = file:read() 62 | end 63 | 64 | line = file:read() 65 | local r_start, r_end = line:gmatch('(%d+), *(%d+)')() 66 | test_case.range = {tonumber(r_start), tonumber(r_end)} 67 | 68 | table.insert(scenario_list, test_case) 69 | end 70 | end 71 | end 72 | 73 | describe("implement_functions", function() 74 | local text_edit 75 | 76 | local scenrio_list = {} 77 | read_test_file("test/implement_functions.txt", scenrio_list) 78 | 79 | local write = function(txt) 80 | local tbl = {} 81 | for v in txt:gmatch("[^\r\n]+") do 82 | table.insert(tbl, v) 83 | end 84 | vim.api.nvim_put(tbl, 'c', true, true) 85 | end 86 | 87 | local run_test = function (input, location, expected, test_name) 88 | text_edit = mock(buf_writer, true) 89 | vim.api.nvim_command('normal ggdG') 90 | write(input) 91 | require'nt-cpp-tools.internal'.imp_func(location[1], location[2]) 92 | require'nt-cpp-tools.preview_printer'.accept_and_end_preview() 93 | assert.stub(text_edit.apply_text_edits).was_called_with(match.lsp_txt(expected, test_name), 0) 94 | mock.revert(text_edit) 95 | end 96 | 97 | before_each(function() 98 | end) 99 | 100 | for k, s in pairs(scenrio_list) do 101 | local test_name = s.name or ("test_" .. k) 102 | it(test_name, function() 103 | run_test(s.input, s.range, s.expected, test_name) 104 | end) 105 | end 106 | 107 | after_each(function () 108 | end) 109 | 110 | end) 111 | --------------------------------------------------------------------------------