├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── after └── plugin │ └── cmp-tabnine.lua ├── doc └── cmp-tabnine.txt ├── install.ps1 ├── install.sh ├── lua └── cmp_tabnine │ ├── compare.lua │ ├── config.lua │ ├── init.lua │ └── source.lua └── stylua.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: autogen 2 | on: 3 | push: 4 | branches: [main] 5 | paths: 6 | - "lua/**.lua" 7 | - "examples/**.lua" 8 | - "stylua.toml" 9 | - "README.md" 10 | - ".github/workflows/*.yml" 11 | 12 | # Cancel any in-progress CI runs for a PR if it is updated 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 15 | cancel-in-progress: true 16 | 17 | 18 | jobs: 19 | autogen: 20 | runs-on: ubuntu-20.04 21 | timeout-minutes: 10 22 | permissions: 23 | contents: write 24 | pull-requests: write 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: panvimdoc 28 | uses: kdheepak/panvimdoc@v3.0.3 29 | with: 30 | vimdoc: cmp-tabnine 31 | description: TabNine source for nvim-cmp 32 | - name: Apply stylua 33 | uses: JohnnyMorganz/stylua-action@v2.0.0 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | args: --config-path=stylua.toml lua/ 37 | version: 0.14.1 38 | - name: Push changes 39 | if: ${{ !env.ACT }} 40 | uses: stefanzweifel/git-auto-commit-action@v4 41 | with: 42 | commit_message: "chore(build): auto-generate vimdoc / stylua" 43 | commit_user_name: "github-actions[bot]" 44 | commit_user_email: "github-actions[bot]@users.noreply.github.com" 45 | commit_author: "github-actions[bot] " 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /binaries 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 tzachar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmp-tabnine 2 | Tabnine source for [hrsh7th/nvim-cmp](https://github.com/hrsh7th/nvim-cmp) 3 | 4 | # Install 5 | 6 | ## Dependencies 7 | 8 | On Linux and Mac, you will need `curl` and `unzip` in your `$PATH`. 9 | 10 | On windows, you just need powershell. If you get a `PSSecurityException` while 11 | trying to install, try the following command in powershell: 12 | 13 | ``` 14 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 15 | ``` 16 | 17 | For more information, see [about_Execution_Policies](https:/go.microsoft.com/fwlink/?LinkID=135170). 18 | 19 | ## Using a plugin manager 20 | 21 | Using plug: 22 | ```viml 23 | Plug 'tzachar/cmp-tabnine', { 'do': './install.sh' } 24 | ``` 25 | 26 | Using plug on windows: 27 | ```viml 28 | Plug 'tzachar/cmp-tabnine', { 'do': 'powershell ./install.ps1' } 29 | ``` 30 | 31 | Using [Lazy](https://github.com/folke/lazy.nvim/): 32 | ```lua 33 | return require("lazy").setup({ 34 | { 35 | 'tzachar/cmp-tabnine', 36 | build = './install.sh', 37 | dependencies = 'hrsh7th/nvim-cmp', 38 | }}) 39 | ``` 40 | 41 | Using [Packer](https://github.com/wbthomason/packer.nvim/): 42 | ```lua 43 | return require("packer").startup( 44 | function(use) 45 | use "hrsh7th/nvim-cmp" --completion 46 | use {'tzachar/cmp-tabnine', run='./install.sh', requires = 'hrsh7th/nvim-cmp'} 47 | end 48 | ) 49 | ``` 50 | Using [Packer](https://github.com/wbthomason/packer.nvim/) on windows: 51 | ```lua 52 | return require("packer").startup( 53 | function(use) 54 | use "hrsh7th/nvim-cmp" --completion 55 | use {'tzachar/cmp-tabnine', after = "nvim-cmp", run='powershell ./install.ps1', requires = 'hrsh7th/nvim-cmp'} 56 | end 57 | ) 58 | ``` 59 | 60 | 61 | And later, enable the plugin: 62 | 63 | ```lua 64 | require'cmp'.setup { 65 | sources = { 66 | { name = 'cmp_tabnine' }, 67 | }, 68 | } 69 | ``` 70 | ## Using NvChad 71 | see [this issue](https://github.com/tzachar/cmp-tabnine/issues/47) 72 | 73 | # Setup 74 | 75 | ```lua 76 | local tabnine = require('cmp_tabnine.config') 77 | 78 | tabnine:setup({ 79 | max_lines = 1000, 80 | max_num_results = 20, 81 | sort = true, 82 | run_on_every_keystroke = true, 83 | snippet_placeholder = '..', 84 | ignored_file_types = { 85 | -- default is not to ignore 86 | -- uncomment to ignore in lua: 87 | -- lua = true 88 | }, 89 | show_prediction_strength = false, 90 | min_percent = 0 91 | }) 92 | ``` 93 | 94 | Please note the use of `:` instead of a `.` 95 | 96 | ## Configure Tabnine or Log in to Your Account 97 | 98 | On [Tabnine Hub](#More-Commands) 99 | 100 | ## `max_lines` 101 | 102 | How many lines of buffer context to pass to TabNine 103 | 104 | ## `max_num_results` 105 | 106 | How many results to return 107 | 108 | ## `sort` 109 | 110 | Sort results by returned priority 111 | 112 | 113 | ## `run_on_every_keystroke` 114 | 115 | Generate new completion items on every keystroke. For more info, check out [#18](https://github.com/tzachar/cmp-tabnine//issues/18) 116 | 117 | ## `snippet_placeholder` 118 | 119 | Indicates where the cursor will be placed in case a completion item is a 120 | snippet. Any string is accepted. 121 | 122 | For this to work properly, you need to setup snippet support for `nvim-cmp`. 123 | 124 | ## `ignored_file_types` `(table: )` 125 | Which file types to ignore. For example: 126 | ```lua 127 | ignored_file_types = { 128 | html = true; 129 | } 130 | ``` 131 | will make `cmp-tabnine` not offer completions when `vim.bo.filetype` is `html`. 132 | 133 | ## `min_percent` 134 | 135 | Eliminate items with a percentage less than `min_percent`. 136 | 137 | # Pretty Printing Menu Items 138 | 139 | You can use the following to pretty print the completion menu (requires 140 | [lspkind](https://github.com/onsails/lspkind-nvim) and patched fonts 141 | (https://www.nerdfonts.com)): 142 | 143 | ```lua 144 | local lspkind = require('lspkind') 145 | 146 | local source_mapping = { 147 | buffer = "[Buffer]", 148 | nvim_lsp = "[LSP]", 149 | nvim_lua = "[Lua]", 150 | cmp_tabnine = "[TN]", 151 | path = "[Path]", 152 | } 153 | 154 | require'cmp'.setup { 155 | sources = { 156 | { name = 'cmp_tabnine' }, 157 | }, 158 | formatting = { 159 | format = function(entry, vim_item) 160 | -- if you have lspkind installed, you can use it like 161 | -- in the following line: 162 | vim_item.kind = lspkind.symbolic(vim_item.kind, {mode = "symbol"}) 163 | vim_item.menu = source_mapping[entry.source.name] 164 | if entry.source.name == "cmp_tabnine" then 165 | local detail = (entry.completion_item.labelDetails or {}).detail 166 | vim_item.kind = "" 167 | if detail and detail:find('.*%%.*') then 168 | vim_item.kind = vim_item.kind .. ' ' .. detail 169 | end 170 | 171 | if (entry.completion_item.data or {}).multiline then 172 | vim_item.kind = vim_item.kind .. ' ' .. '[ML]' 173 | end 174 | end 175 | local maxwidth = 80 176 | vim_item.abbr = string.sub(vim_item.abbr, 1, maxwidth) 177 | return vim_item 178 | end, 179 | }, 180 | } 181 | ``` 182 | 183 | # Customize cmp highlight group 184 | 185 | The highlight group is `CmpItemKindTabNine`, you can change it by: 186 | 187 | ```lua 188 | vim.api.nvim_set_hl(0, "CmpItemKindTabNine", {fg ="#6CC644"}) 189 | ``` 190 | 191 | # Sorting 192 | 193 | `cmp-tabnine` adds a priority entry to each completion item, 194 | which can be used to override `cmp`'s default sorting order: 195 | 196 | 197 | ```lua 198 | local compare = require('cmp.config.compare') 199 | cmp.setup({ 200 | sorting = { 201 | priority_weight = 2, 202 | comparators = { 203 | require('cmp_tabnine.compare'), 204 | compare.offset, 205 | compare.exact, 206 | compare.score, 207 | compare.recently_used, 208 | compare.kind, 209 | compare.sort_text, 210 | compare.length, 211 | compare.order, 212 | }, 213 | }, 214 | }) 215 | ``` 216 | 217 | # Prefetch 218 | 219 | TabNine supports prefetching files, preprocessing them before users ask for 220 | completions. Prefetching is supported through a command: 221 | 222 | `:CmpTabninePrefetch file_path` 223 | 224 | and also directly using lua: 225 | 226 | ```lua 227 | require('cmp_tabnine'):prefetch(file_path) 228 | ``` 229 | 230 | The lua api can be used to prefetch a project, or a file on open: 231 | 232 | ```lua 233 | local prefetch = vim.api.nvim_create_augroup("prefetch", {clear = true}) 234 | 235 | vim.api.nvim_create_autocmd('BufRead', { 236 | group = prefetch, 237 | pattern = '*.py', 238 | callback = function() 239 | require('cmp_tabnine'):prefetch(vim.fn.expand('%:p')) 240 | end 241 | }) 242 | ``` 243 | 244 | # Multi-Line suggestions 245 | 246 | TabNine supports multi-line suggestions in Pro mode. If a suggestions is multi-line, we add 247 | the `entry.completion_item.data.detail.multiline` flag to the completion entry 248 | and the entire suggestion to the `documentation` property of the entry, such 249 | that `cmp` will display the suggested lines in the documentation panel. 250 | 251 | To enable multi-line completions, you should (a) have a Pro account and (b) 252 | select either the hybrid or cloud completion models in the TabNine Hub. 253 | 254 | Moreover, TabNine tends to suggest multi-line completions only on a new line 255 | (usually after a comment describing what you are expecting to get). The easiest 256 | way to trigger the completion is by manually invoking cmp on a new line. 257 | 258 | Support for multi-line completions in cmp works only from version 4.4.213 of 259 | TabNine (see [this issue](https://github.com/codota/tabnine-nvim/issues/6#issuecomment-1364655503)). 260 | 261 | # More Commands 262 | 263 | - `:CmpTabnineHub`: Open Tabnine Hub 264 | - `:CmpTabnineHubUrl`: Show the link to Tabnine Hub 265 | -------------------------------------------------------------------------------- /after/plugin/cmp-tabnine.lua: -------------------------------------------------------------------------------- 1 | require('cmp_tabnine').setup() 2 | -------------------------------------------------------------------------------- /doc/cmp-tabnine.txt: -------------------------------------------------------------------------------- 1 | *cmp-tabnine.txt* TabNine source for nvim-cmp 2 | 3 | ============================================================================== 4 | Table of Contents *cmp-tabnine-table-of-contents* 5 | 6 | 1. cmp-tabnine |cmp-tabnine-cmp-tabnine| 7 | 2. Install |cmp-tabnine-install| 8 | - Dependencies |cmp-tabnine-install-dependencies| 9 | - Using a plugin manager |cmp-tabnine-install-using-a-plugin-manager| 10 | - Using NvChad |cmp-tabnine-install-using-nvchad| 11 | 3. Setup |cmp-tabnine-setup| 12 | - Configure Tabnine or Log in to Your Account|cmp-tabnine-setup-configure-tabnine-or-log-in-to-your-account| 13 | - max_lines |cmp-tabnine-setup-max_lines| 14 | - max_num_results |cmp-tabnine-setup-max_num_results| 15 | - sort |cmp-tabnine-setup-sort| 16 | - run_on_every_keystroke |cmp-tabnine-setup-run_on_every_keystroke| 17 | - snippet_placeholder |cmp-tabnine-setup-snippet_placeholder| 18 | - ignored_file_types (table: )|cmp-tabnine-setup-ignored_file_types-(table:-)| 19 | - min_percent |cmp-tabnine-setup-min_percent| 20 | 4. Pretty Printing Menu Items |cmp-tabnine-pretty-printing-menu-items| 21 | 5. Customize cmp highlight group |cmp-tabnine-customize-cmp-highlight-group| 22 | 6. Sorting |cmp-tabnine-sorting| 23 | 7. Prefetch |cmp-tabnine-prefetch| 24 | 8. Multi-Line suggestions |cmp-tabnine-multi-line-suggestions| 25 | 9. More Commands |cmp-tabnine-more-commands| 26 | 27 | ============================================================================== 28 | 1. cmp-tabnine *cmp-tabnine-cmp-tabnine* 29 | 30 | Tabnine source for hrsh7th/nvim-cmp 31 | 32 | 33 | ============================================================================== 34 | 2. Install *cmp-tabnine-install* 35 | 36 | 37 | DEPENDENCIES *cmp-tabnine-install-dependencies* 38 | 39 | On Linux and Mac, you will need `curl` and `unzip` in your `$PATH`. 40 | 41 | On windows, you just need powershell. If you get a `PSSecurityException` 42 | whiletrying to install, try the following command in powershell: 43 | 44 | > 45 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 46 | < 47 | 48 | For more information, see about_Execution_Policies 49 | . 50 | 51 | 52 | USING A PLUGIN MANAGER *cmp-tabnine-install-using-a-plugin-manager* 53 | 54 | Using plug:`viml Plug 'tzachar/cmp-tabnine', { 'do': './install.sh' }` 55 | 56 | Using plug on windows:`viml Plug 'tzachar/cmp-tabnine', { 'do': 'powershell 57 | ./install.ps1' }` 58 | 59 | Using Lazy :`lua return 60 | require("lazy").setup({ { 'tzachar/cmp-tabnine', build = './install.sh', 61 | dependencies = 'hrsh7th/nvim-cmp', }})` 62 | 63 | Using Packer :`lua return 64 | require("packer").startup( function(use) use "hrsh7th/nvim-cmp" --completion 65 | use {'tzachar/cmp-tabnine', run='./install.sh', requires = 'hrsh7th/nvim-cmp'} 66 | end )`Using Packer on windows:`lua 67 | return require("packer").startup( function(use) use "hrsh7th/nvim-cmp" 68 | --completion use {'tzachar/cmp-tabnine', after = "nvim-cmp", run='powershell 69 | ./install.ps1', requires = 'hrsh7th/nvim-cmp'} end )` 70 | 71 | And later, enable the plugin: 72 | 73 | >lua 74 | require'cmp'.setup { 75 | sources = { 76 | { name = 'cmp_tabnine' }, 77 | }, 78 | } 79 | < 80 | 81 | 82 | USING NVCHAD *cmp-tabnine-install-using-nvchad* 83 | 84 | see this issue 85 | 86 | 87 | ============================================================================== 88 | 3. Setup *cmp-tabnine-setup* 89 | 90 | >lua 91 | local tabnine = require('cmp_tabnine.config') 92 | 93 | tabnine:setup({ 94 | max_lines = 1000, 95 | max_num_results = 20, 96 | sort = true, 97 | run_on_every_keystroke = true, 98 | snippet_placeholder = '..', 99 | ignored_file_types = { 100 | -- default is not to ignore 101 | -- uncomment to ignore in lua: 102 | -- lua = true 103 | }, 104 | show_prediction_strength = false, 105 | min_percent = 0 106 | }) 107 | < 108 | 109 | Please note the use of `:` instead of a `.` 110 | 111 | 112 | CONFIGURE TABNINE OR LOG IN TO YOUR ACCOUNT*cmp-tabnine-setup-configure-tabnine-or-log-in-to-your-account* 113 | 114 | On |cmp-tabnine-tabnine-hub| 115 | 116 | 117 | MAX_LINES *cmp-tabnine-setup-max_lines* 118 | 119 | How many lines of buffer context to pass to TabNine 120 | 121 | 122 | MAX_NUM_RESULTS *cmp-tabnine-setup-max_num_results* 123 | 124 | How many results to return 125 | 126 | 127 | SORT *cmp-tabnine-setup-sort* 128 | 129 | Sort results by returned priority 130 | 131 | 132 | RUN_ON_EVERY_KEYSTROKE *cmp-tabnine-setup-run_on_every_keystroke* 133 | 134 | Generate new completion items on every keystroke. For more info, check out #18 135 | 136 | 137 | 138 | SNIPPET_PLACEHOLDER *cmp-tabnine-setup-snippet_placeholder* 139 | 140 | Indicates where the cursor will be placed in case a completion item is 141 | asnippet. Any string is accepted. 142 | 143 | For this to work properly, you need to setup snippet support for `nvim-cmp`. 144 | 145 | 146 | IGNORED_FILE_TYPES (TABLE: )*cmp-tabnine-setup-ignored_file_types-(table:-)* 147 | 148 | Which file types to ignore. For example: 149 | 150 | >lua 151 | ignored_file_types = { 152 | html = true; 153 | } 154 | < 155 | 156 | will make `cmp-tabnine` not offer completions when `vim.bo.filetype` is `html`. 157 | 158 | 159 | MIN_PERCENT *cmp-tabnine-setup-min_percent* 160 | 161 | Eliminate items with a percentage less than `min_percent`. 162 | 163 | 164 | ============================================================================== 165 | 4. Pretty Printing Menu Items *cmp-tabnine-pretty-printing-menu-items* 166 | 167 | You can use the following to pretty print the completion menu (requireslspkind 168 | and patched 169 | fonts(https://www.nerdfonts.com)): 170 | 171 | >lua 172 | local lspkind = require('lspkind') 173 | 174 | local source_mapping = { 175 | buffer = "[Buffer]", 176 | nvim_lsp = "[LSP]", 177 | nvim_lua = "[Lua]", 178 | cmp_tabnine = "[TN]", 179 | path = "[Path]", 180 | } 181 | 182 | require'cmp'.setup { 183 | sources = { 184 | { name = 'cmp_tabnine' }, 185 | }, 186 | formatting = { 187 | format = function(entry, vim_item) 188 | -- if you have lspkind installed, you can use it like 189 | -- in the following line: 190 | vim_item.kind = lspkind.symbolic(vim_item.kind, {mode = "symbol"}) 191 | vim_item.menu = source_mapping[entry.source.name] 192 | if entry.source.name == "cmp_tabnine" then 193 | local detail = (entry.completion_item.labelDetails or {}).detail 194 | vim_item.kind = "" 195 | if detail and detail:find('.*%%.*') then 196 | vim_item.kind = vim_item.kind .. ' ' .. detail 197 | end 198 | 199 | if (entry.completion_item.data or {}).multiline then 200 | vim_item.kind = vim_item.kind .. ' ' .. '[ML]' 201 | end 202 | end 203 | local maxwidth = 80 204 | vim_item.abbr = string.sub(vim_item.abbr, 1, maxwidth) 205 | return vim_item 206 | end, 207 | }, 208 | } 209 | < 210 | 211 | 212 | ============================================================================== 213 | 5. Customize cmp highlight group *cmp-tabnine-customize-cmp-highlight-group* 214 | 215 | The highlight group is `CmpItemKindTabNine`, you can change it by: 216 | 217 | >lua 218 | vim.api.nvim_set_hl(0, "CmpItemKindTabNine", {fg ="#6CC644"}) 219 | < 220 | 221 | 222 | ============================================================================== 223 | 6. Sorting *cmp-tabnine-sorting* 224 | 225 | `cmp-tabnine` adds a priority entry to each completion item,which can be used 226 | to override `cmp`’s default sorting order: 227 | 228 | >lua 229 | local compare = require('cmp.config.compare') 230 | cmp.setup({ 231 | sorting = { 232 | priority_weight = 2, 233 | comparators = { 234 | require('cmp_tabnine.compare'), 235 | compare.offset, 236 | compare.exact, 237 | compare.score, 238 | compare.recently_used, 239 | compare.kind, 240 | compare.sort_text, 241 | compare.length, 242 | compare.order, 243 | }, 244 | }, 245 | }) 246 | < 247 | 248 | 249 | ============================================================================== 250 | 7. Prefetch *cmp-tabnine-prefetch* 251 | 252 | TabNine supports prefetching files, preprocessing them before users ask 253 | forcompletions. Prefetching is supported through a command: 254 | 255 | `:CmpTabninePrefetch file_path` 256 | 257 | and also directly using lua: 258 | 259 | >lua 260 | require('cmp_tabnine'):prefetch(file_path) 261 | < 262 | 263 | The lua api can be used to prefetch a project, or a file on open: 264 | 265 | >lua 266 | local prefetch = vim.api.nvim_create_augroup("prefetch", {clear = true}) 267 | 268 | vim.api.nvim_create_autocmd('BufRead', { 269 | group = prefetch, 270 | pattern = '*.py', 271 | callback = function() 272 | require('cmp_tabnine'):prefetch(vim.fn.expand('%:p')) 273 | end 274 | }) 275 | < 276 | 277 | 278 | ============================================================================== 279 | 8. Multi-Line suggestions *cmp-tabnine-multi-line-suggestions* 280 | 281 | TabNine supports multi-line suggestions in Pro mode. If a suggestions is 282 | multi-line, we addthe `entry.completion_item.data.detail.multiline` flag to the 283 | completion entryand the entire suggestion to the `documentation` property of 284 | the entry, suchthat `cmp` will display the suggested lines in the documentation 285 | panel. 286 | 287 | To enable multi-line completions, you should (a) have a Pro account and 288 | (b)select either the hybrid or cloud completion models in the TabNine Hub. 289 | 290 | Moreover, TabNine tends to suggest multi-line completions only on a new 291 | line(usually after a comment describing what you are expecting to get). The 292 | easiestway to trigger the completion is by manually invoking cmp on a new line. 293 | 294 | Support for multi-line completions in cmp works only from version 4.4.213 295 | ofTabNine (see this issue 296 | ). 297 | 298 | 299 | ============================================================================== 300 | 9. More Commands *cmp-tabnine-more-commands* 301 | 302 | 303 | - `:CmpTabnineHub`: Open Tabnine Hub 304 | - `:CmpTabnineHubUrl`: Show the link to Tabnine Hub 305 | 306 | Generated by panvimdoc 307 | 308 | vim:tw=78:ts=8:noet:ft=help:norl: 309 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | $version = iwr https://update.tabnine.com/bundles/version -UseBasicParsing 2 | $version = $version.content 3 | 4 | if([environment]::Is64bitOperatingSystem){ 5 | $platform = 'x86_64-pc-windows-gnu' 6 | } 7 | else{ 8 | $platform = 'i686-pc-windows-gnu' 9 | } 10 | 11 | $path = "$version/$platform" 12 | 13 | iwr "https://update.tabnine.com/bundles/$path/TabNine.zip" -OutFile TabNine.zip 14 | 15 | if(!(Test-Path ./binaries)){ 16 | mkdir binaries 17 | } 18 | 19 | if(!(Test-Path "./binaries/$path")){ 20 | mkdir "binaries/$path" 21 | } 22 | 23 | expand-archive Tabnine.zip -destinationpath "./binaries/$path" -force 24 | Remove-Item -Recurse "./TabNine.zip" 25 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Based on https://github.com/codota/TabNine/blob/master/dl_binaries.sh 4 | # Download latest TabNine binaries 5 | set -o errexit 6 | set -o pipefail 7 | set -x 8 | 9 | version=${version:-$(curl -sS https://update.tabnine.com/bundles/version)} 10 | 11 | case $(uname -s) in 12 | "Darwin") 13 | if [ "$(uname -m)" == "arm64" ]; then 14 | platform="aarch64-apple-darwin" 15 | else 16 | platform="$(uname -m)-apple-darwin" 17 | fi 18 | ;; 19 | "Linux") 20 | platform="$(uname -m)-unknown-linux-musl" 21 | ;; 22 | *"MINGW64"*) 23 | platform="$(uname -m)-pc-windows-gnu" 24 | ;; 25 | esac 26 | 27 | # we want the binary to reside inside our plugin's dir 28 | cd "$(dirname "$0")" 29 | path="${version}/${platform}" 30 | 31 | curl "https://update.tabnine.com/bundles/${path}/TabNine.zip" --create-dirs -o "binaries/${path}/TabNine.zip" 32 | unzip -o "binaries/${path}/TabNine.zip" -d "binaries/${path}" 33 | rm -rf "binaries/${path}/TabNine.zip" 34 | 35 | if [[ "$(uname -s)" != *"MINGW64"* ]]; then 36 | chmod +x "binaries/$path/"* 37 | fi 38 | -------------------------------------------------------------------------------- /lua/cmp_tabnine/compare.lua: -------------------------------------------------------------------------------- 1 | return function(entry1, entry2) 2 | if entry1.source.name == 'cmp_tabnine' and entry2.source.name == 'cmp_tabnine' then 3 | if not entry1.completion_item.priority then 4 | return false 5 | elseif not entry2.completion_item.priority then 6 | return true 7 | else 8 | return (entry1.completion_item.priority > entry2.completion_item.priority) 9 | end 10 | end 11 | 12 | if entry1.source.name == 'cmp_tabnine' and entry2.source.name ~= 'cmp_tabnine' then 13 | return true 14 | elseif entry1.source.name ~= 'cmp_tabnine' and entry2.source.name == 'cmp_tabnine' then 15 | return false 16 | else 17 | return nil 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lua/cmp_tabnine/config.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local conf_defaults = { 4 | max_lines = 1000, 5 | max_num_results = 20, 6 | sort = true, 7 | priority = 5000, 8 | min_percent = 0, 9 | run_on_every_keystroke = true, 10 | snippet_placeholder = '..', 11 | ignored_file_types = { -- default is not to ignore 12 | -- uncomment to ignore in lua: 13 | -- lua = true 14 | }, 15 | } 16 | 17 | function M:setup(params) 18 | if params == nil then 19 | vim.api.nvim_err_writeln('Bad call to cmp_tabnine.config.setup; Make sure to use setup:(params) -- note the use of a colon (:)') 20 | params = self or {} 21 | end 22 | for k, v in pairs(params or {}) do 23 | conf_defaults[k] = v 24 | end 25 | end 26 | 27 | function M:get(what) 28 | return conf_defaults[what] 29 | end 30 | 31 | return M 32 | -------------------------------------------------------------------------------- /lua/cmp_tabnine/init.lua: -------------------------------------------------------------------------------- 1 | local cmp = require('cmp') 2 | local source = require('cmp_tabnine.source') 3 | 4 | local M = {} 5 | 6 | M.prefetch = function(self, file_path, count) 7 | count = (count or 1) 8 | if self.tabnine_source == nil and count < 5 then 9 | -- not initialized yet 10 | vim.schedule(function() 11 | self:prefetch(file_path, count + 1) 12 | end) 13 | else 14 | self.tabnine_source:prefetch(file_path) 15 | end 16 | end 17 | 18 | M.setup = function() 19 | vim.schedule(function() 20 | M.tabnine_source = source.new() 21 | cmp.register_source('cmp_tabnine', M.tabnine_source) 22 | 23 | if vim.api.nvim_create_user_command ~= nil then 24 | vim.api.nvim_create_user_command('CmpTabnineHub', function() 25 | M.tabnine_source:open_tabnine_hub(false) 26 | end, { force = true }) 27 | 28 | vim.api.nvim_create_user_command('CmpTabnineHubUrl', function() 29 | vim.notify(M.tabnine_source:get_hub_url()) 30 | end, { force = true }) 31 | 32 | vim.api.nvim_create_user_command('CmpTabninePrefetch', function(args) 33 | M:prefetch(args.args) 34 | end, { force = true, nargs = 1, complete = 'file' }) 35 | else 36 | -- set self to nil to use latest source 37 | vim.cmd([[command! CmpTabnineHub lua require('cmp_tabnine.source').open_tabnine_hub(nil, false)]]) 38 | vim.cmd([[command! CmpTabnineHubUrl lua print(require('cmp_tabnine.source').get_hub_url(nil))]]) 39 | vim.cmd([[command! -nargs=1 -complete=file CmpTabninePrefetch lua require('cmp_tabnine'):prefetch()]]) 40 | end 41 | end) 42 | end 43 | 44 | return M 45 | -------------------------------------------------------------------------------- /lua/cmp_tabnine/source.lua: -------------------------------------------------------------------------------- 1 | local cmp = require('cmp') 2 | local api = vim.api 3 | local fn = vim.fn 4 | local conf = require('cmp_tabnine.config') 5 | 6 | local function dump(...) 7 | local objects = vim.tbl_map(vim.inspect, { ... }) 8 | print(unpack(objects)) 9 | end 10 | 11 | local function json_decode(data) 12 | local status, result = pcall(vim.fn.json_decode, data) 13 | if status then 14 | return result 15 | else 16 | return nil, result 17 | end 18 | end 19 | 20 | local function is_win() 21 | return package.config:sub(1, 1) == '\\' 22 | end 23 | 24 | local function get_path_separator() 25 | if is_win() then 26 | return '\\' 27 | end 28 | return '/' 29 | end 30 | 31 | local function escape_tabstop_sign(str) 32 | return str:gsub('%$', '\\$') 33 | end 34 | 35 | local function build_snippet(prefix, placeholder, suffix, add_final_tabstop) 36 | local snippet = escape_tabstop_sign(prefix) .. placeholder .. escape_tabstop_sign(suffix) 37 | if add_final_tabstop then 38 | return snippet .. '$0' 39 | else 40 | return snippet 41 | end 42 | end 43 | 44 | local function script_path() 45 | local str = debug.getinfo(2, 'S').source:sub(2) 46 | if is_win() then 47 | str = str:gsub('/', '\\') 48 | end 49 | return str:match('(.*' .. get_path_separator() .. ')') 50 | end 51 | 52 | local function get_parent_dir(path) 53 | local separator = get_path_separator() 54 | local pattern = '^(.+)' .. separator 55 | -- if path has separator at end, remove it 56 | path = path:gsub(separator .. '*$', '') 57 | local parent_dir = path:match(pattern) .. separator 58 | return parent_dir 59 | end 60 | 61 | -- do this once on init, otherwise on restart this dows not work 62 | local binaries_folder = get_parent_dir(get_parent_dir(script_path())) .. 'binaries' 63 | 64 | -- this function is taken from https://github.com/yasuoka/stralnumcmp/blob/master/stralnumcmp.lua 65 | local function stralnumcmp(a, b) 66 | local a0, b0, an, bn, as, bs, c 67 | a0 = a 68 | b0 = b 69 | while a:len() > 0 and b:len() > 0 do 70 | an = a:match('^%d+') 71 | bn = b:match('^%d+') 72 | as = an or a:match('^%D+') 73 | bs = bn or b:match('^%D+') 74 | 75 | if an and bn then 76 | c = tonumber(an) - tonumber(bn) 77 | else 78 | c = (as < bs) and -1 or ((as > bs) and 1 or 0) 79 | end 80 | if c ~= 0 then 81 | return c 82 | end 83 | a = a:sub((an and an:len() or as:len()) + 1) 84 | b = b:sub((bn and bn:len() or bs:len()) + 1) 85 | end 86 | return (a0:len() - b0:len()) 87 | end 88 | 89 | -- locate the binary here, as expand is relative to the calling script name 90 | local function binary() 91 | local versions_folders = fn.globpath(binaries_folder, '*', false, true) 92 | local versions = {} 93 | for _, path in ipairs(versions_folders) do 94 | for version in string.gmatch(path, '([0-9.]+)$') do 95 | if version then 96 | table.insert(versions, { path = path, version = version }) 97 | end 98 | end 99 | end 100 | table.sort(versions, function(a, b) 101 | return stralnumcmp(a.version, b.version) < 0 102 | end) 103 | local latest = versions[#versions] 104 | if not latest then 105 | vim.notify(string.format('cmp-tabnine: Cannot find installed TabNine. Please run install.%s', (is_win() and 'ps1' or 'sh'))) 106 | return 107 | end 108 | 109 | local platform = nil 110 | 111 | if fn.has('win64') == 1 then 112 | platform = 'x86_64-pc-windows-gnu' 113 | elseif fn.has('win32') == 1 then 114 | platform = 'i686-pc-windows-gnu' 115 | else 116 | local arch, _ = string.gsub(fn.system({ 'uname', '-m' }), '\n$', '') 117 | if fn.has('mac') == 1 then 118 | if arch == 'arm64' then 119 | platform = 'aarch64-apple-darwin' 120 | else 121 | platform = 'x86_64-apple-darwin' 122 | end 123 | elseif fn.has('unix') == 1 then 124 | platform = arch .. '-unknown-linux-musl' 125 | end 126 | end 127 | return latest.path .. '/' .. platform .. '/' .. 'TabNine', latest.version 128 | end 129 | 130 | local Source = { 131 | job = 0, 132 | pending = {}, 133 | -- cache the hub url. Set every time on_exit is called, assuming it wont 134 | -- change till next run of the tabnine process 135 | hub_url = 'Unknown', 136 | } 137 | local last_instance = nil 138 | 139 | function Source.new() 140 | last_instance = setmetatable({}, { __index = Source }) 141 | last_instance:on_exit(0) 142 | return last_instance 143 | end 144 | 145 | function Source.get_hub_url(self) 146 | if self == nil then 147 | -- this happens when nvim < 0.7 and vim.api.nvim_add_user_command does not exist 148 | self = last_instance 149 | end 150 | return self.hub_url 151 | end 152 | 153 | function Source.open_tabnine_hub(self, quiet) 154 | local req = {} 155 | req.version = self.tabnine_version 156 | req.request = { 157 | Configuration = { 158 | quiet = quiet, 159 | }, 160 | } 161 | 162 | if self == nil then 163 | -- this happens when nvim < 0.7 and vim.api.nvim_add_user_command does not exist 164 | self = last_instance 165 | end 166 | pcall(fn.chansend, self.job, fn.json_encode(req) .. '\n') 167 | end 168 | 169 | function Source.is_available(self) 170 | return (self.job ~= 0) 171 | end 172 | 173 | function Source.get_debug_name() 174 | return 'TabNine' 175 | end 176 | 177 | function Source._do_complete(self, ctx) 178 | if self.job == 0 then 179 | return 180 | end 181 | local max_lines = conf:get('max_lines') 182 | local cursor = ctx.context.cursor 183 | local cur_line = ctx.context.cursor_line 184 | -- properly handle utf8 185 | -- local cur_line_before = string.sub(cur_line, 1, cursor.col - 1) 186 | local cur_line_before = vim.fn.strpart(cur_line, 0, math.max(cursor.col - 1, 0), 1) 187 | 188 | -- properly handle utf8 189 | -- local cur_line_after = string.sub(cur_line, cursor.col) -- include current character 190 | local cur_line_after = vim.fn.strpart(cur_line, math.max(cursor.col - 1, 0), vim.fn.strdisplaywidth(cur_line), 1) -- include current character 191 | 192 | local region_includes_beginning = false 193 | local region_includes_end = false 194 | if cursor.line - max_lines <= 1 then 195 | region_includes_beginning = true 196 | end 197 | if cursor.line + max_lines >= fn['line']('$') then 198 | region_includes_end = true 199 | end 200 | 201 | local lines_before = api.nvim_buf_get_lines(0, math.max(0, cursor.line - max_lines), cursor.line, false) 202 | table.insert(lines_before, cur_line_before) 203 | local before = table.concat(lines_before, '\n') 204 | 205 | local lines_after = api.nvim_buf_get_lines(0, cursor.line + 1, cursor.line + max_lines, false) 206 | table.insert(lines_after, 1, cur_line_after) 207 | local after = table.concat(lines_after, '\n') 208 | 209 | local req = {} 210 | req.version = self.tabnine_version 211 | req.request = { 212 | Autocomplete = { 213 | before = before, 214 | after = after, 215 | region_includes_beginning = region_includes_beginning, 216 | region_includes_end = region_includes_end, 217 | filename = vim.uri_from_bufnr(0):gsub('file://', ''), 218 | max_num_results = conf:get('max_num_results'), 219 | correlation_id = ctx.context.id, 220 | line = cursor.line, 221 | offset = #before + 1, 222 | character = cursor.col, 223 | indentation_size = (api.nvim_get_option_value('tabstop', { buf = 0 }) or 4), 224 | }, 225 | } 226 | 227 | -- fn.chansend(Source.job, fn.json_encode(req) .. "\n") 228 | -- if there is an error, e.g., the channel is dead, we expect on_exit will be 229 | -- called in the future and restart the server 230 | -- we use pcall as we do not want to spam the user with error messages 231 | pcall(fn.chansend, self.job, fn.json_encode(req) .. '\n') 232 | end 233 | 234 | function Source.prefetch(self, file_path) 235 | local req = {} 236 | req.version = self.tabnine_version 237 | req.request = { 238 | Prefetch = { 239 | filename = file_path, 240 | -- filename = vim.uri_from_bufnr(0):gsub('file://', ''), 241 | }, 242 | } 243 | 244 | pcall(fn.chansend, self.job, fn.json_encode(req) .. '\n') 245 | end 246 | 247 | --- complete 248 | function Source.complete(self, ctx, callback) 249 | if conf:get('ignored_file_types')[vim.bo.filetype] then 250 | callback() 251 | return 252 | end 253 | self.pending[ctx.context.id] = { ctx = ctx, callback = callback, job = self.job } 254 | self:_do_complete(ctx) 255 | end 256 | 257 | function Source.on_exit(self, job, code) 258 | if job ~= self.job then 259 | return 260 | end 261 | -- restart.. 262 | if code == 143 then 263 | -- nvim is exiting. do not restart 264 | return 265 | end 266 | 267 | local bin, version = binary() 268 | if not bin then 269 | return 270 | end 271 | self.tabnine_version = version 272 | self.pending = {} 273 | self.job = fn.jobstart({ 274 | bin, 275 | '--client', 276 | 'nvim', 277 | '--client-metadata', 278 | 'pluginVersion=' .. version, 279 | }, { 280 | on_stderr = nil, 281 | on_exit = function(j, c, _) 282 | self:on_exit(j, c) 283 | end, 284 | on_stdout = function(_, data, _) 285 | self:on_stdout(data) 286 | end, 287 | }) 288 | 289 | -- fire off a hub request to get the url 290 | self:open_tabnine_hub(true) 291 | end 292 | 293 | function Source.on_stdout(self, data) 294 | -- { 295 | -- "old_prefix": "wo", 296 | -- "results": [ 297 | -- { 298 | -- "new_prefix": "world", 299 | -- "old_suffix": "", 300 | -- "new_suffix": "", 301 | -- "detail": "64%" 302 | -- } 303 | -- ], 304 | -- "user_message": [], 305 | -- "docs": [] 306 | -- } 307 | local base_priority = conf:get('priority') 308 | 309 | for _, jd in ipairs(data) do 310 | if jd ~= nil and jd ~= '' and jd ~= 'null' then 311 | local response = (json_decode(jd) or {}) 312 | local id = response.correlation_id 313 | if response == nil then 314 | dump('TabNine: json decode error: ', jd) 315 | elseif (response.message or ''):find('http://127.0.0.1') then 316 | self.hub_url = response.message:match('.*(http://127.0.0.1.*)') 317 | elseif id == nil then 318 | -- dump('TabNine: No correlation id: ', jd) 319 | -- ignore this message 320 | elseif self.pending[id] == nil then 321 | dump('TabNine: unknown message: ', jd) 322 | elseif self.pending[id].job ~= self.job then 323 | -- a message from an old job. skip it 324 | else 325 | local ctx = self.pending[id].ctx 326 | local callback = self.pending[id].callback 327 | self.pending[id] = nil 328 | 329 | local cursor = ctx.context.cursor 330 | 331 | local items = {} 332 | local old_prefix = response.old_prefix 333 | local results = response.results 334 | 335 | if results ~= nil then 336 | for _, result in ipairs(results) do 337 | local newText = result.new_prefix .. result.new_suffix 338 | 339 | if newText:find('.*\n.*') then 340 | -- this is a multi line completion. 341 | -- remove leading newlines 342 | newText = newText:gsub('^\n', '') 343 | end 344 | 345 | local old_suffix = result.old_suffix 346 | if string.sub(old_suffix, -1) == '\n' then 347 | old_suffix = string.sub(old_suffix, 1, -2) 348 | end 349 | 350 | local range = { 351 | start = { line = cursor.line, character = cursor.col - #old_prefix - 1 }, 352 | ['end'] = { line = cursor.line, character = cursor.col + #old_suffix - 1 }, 353 | } 354 | 355 | local item = { 356 | label = newText, 357 | -- removing filterText, as it interacts badly with multiline 358 | -- filterText = newText, 359 | data = result, 360 | textEdit = { 361 | newText = newText, 362 | insert = range, -- May be better to exclude the trailing part of old_suffix since it's 'replaced'? 363 | replace = range, 364 | }, 365 | sortText = newText, 366 | dup = 0, 367 | cmp = { 368 | kind_text = 'TabNine', 369 | kind_hl_group = 'CmpItemKindTabNine', 370 | }, 371 | } 372 | -- This is a hack fix for cmp not displaying items of TabNine::config_dir, version, etc. because their 373 | -- completion items get scores of 0 in the matching algorithm 374 | -- I don't think this is needed anymore 375 | -- if #old_prefix == 0 then 376 | -- item['filterText'] = string.sub(ctx.context.cursor_before_line, ctx.offset) .. newText 377 | -- end 378 | 379 | if #result.new_suffix > 0 then 380 | item['insertTextFormat'] = cmp.lsp.InsertTextFormat.Snippet 381 | item['label'] = build_snippet(result.new_prefix, conf:get('snippet_placeholder'), result.new_suffix, false) 382 | item['textEdit'].newText = build_snippet(result.new_prefix, '$1', result.new_suffix, true) 383 | end 384 | 385 | item['labelDetails'] = { 386 | detail = (result.completion_metadata or {}).detail or nil, 387 | } 388 | if result.completion_metadata ~= nil then 389 | local percent = tonumber(string.sub(result.completion_metadata.detail, 0, -2)) 390 | if percent ~= nil then 391 | if percent <= conf:get('min_percent') then 392 | goto continue 393 | end 394 | item['priority'] = base_priority + percent * 0.001 395 | item['sortText'] = string.format('%02d', 100 - percent) .. item['sortText'] 396 | end 397 | end 398 | 399 | if result.kind then 400 | item['kind'] = result.kind 401 | end 402 | 403 | if result.documentation then 404 | item['documentation'] = { 405 | kind = cmp.lsp.MarkupKind.Markdown, 406 | value = '```' .. (vim.filetype.match({ buf = 0 }) or '') .. '\n' .. result.documentation .. '\n```', 407 | } 408 | end 409 | 410 | if result.new_prefix:find('.*\n.*') then 411 | item['data']['multiline'] = true 412 | item['documentation'] = { 413 | kind = cmp.lsp.MarkupKind.Markdown, 414 | value = '```' .. (vim.filetype.match({ buf = 0 }) or '') .. '\n' .. newText .. '\n```', 415 | } 416 | end 417 | 418 | if result.deprecated then 419 | item['deprecated'] = result.deprecated 420 | end 421 | table.insert(items, item) 422 | end 423 | ::continue:: 424 | else 425 | dump('no results:', jd) 426 | end 427 | 428 | -- sort by returned importance b4 limiting number of results 429 | table.sort(items, function(a, b) 430 | if not a.priority then 431 | return false 432 | elseif not b.priority then 433 | return true 434 | else 435 | return (a.priority > b.priority) 436 | end 437 | end) 438 | 439 | items = { unpack(items, 1, conf:get('max_num_results')) } 440 | callback({ 441 | items = items, 442 | isIncomplete = conf:get('run_on_every_keystroke'), 443 | }) 444 | end 445 | end 446 | end 447 | end 448 | 449 | return Source 450 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 2 3 | column_width = 1200 4 | quote_style = "AutoPreferSingle" 5 | --------------------------------------------------------------------------------