├── .gitignore ├── LICENSE ├── README.md ├── company-tabnine.el ├── fetch-binaries.sh ├── melpa-recipe ├── screenshot-deep-1.png ├── screenshot-deep-2.png ├── screenshot-deep-3.png ├── screenshot-deep-4.png ├── screenshot-deep-5.png └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | binaries/ 2 | *.elc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tommy Xiang 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 | # company-tabnine 2 | 3 | [![MELPA](https://melpa.org/packages/company-tabnine-badge.svg)](https://melpa.org/#/company-tabnine) 4 | 5 | [TabNine](https://tabnine.com/) is the all-language autocompleter. It uses machine learning to provide responsive, reliable, and relevant suggestions. 6 | 7 | `company-tabnine` provides TabNine completion backend for [company-mode](https://github.com/company-mode/company-mode). **It takes care of TabNine binaries**, so installation is easy. 8 | 9 | ![screenshot](screenshot.png) 10 | ![screenshot-deep-1](screenshot-deep-1.png) 11 | ![screenshot-deep-2](screenshot-deep-2.png) 12 | ![screenshot-deep-3](screenshot-deep-3.png) 13 | 14 | ## Installation 15 | 16 | 1. Make sure [company-mode](https://github.com/company-mode/company-mode) is installed and configured. 17 | 18 | 2. Install `company-tabnine`. This package is part of [MELPA](https://melpa.org). 19 | 20 | Note: See https://melpa.org/#/getting-started for MELPA usage. **Make sure to use the "bleeding-edge" repository instead of MELPA stable.** 21 | 22 | - With [use-package](https://github.com/jwiegley/use-package) 23 | 24 | Put the following in your config: 25 | 26 | ```emacs 27 | (use-package company-tabnine :ensure t) 28 | ``` 29 | 30 | - With `package.el` (built-in) 31 | 32 | Install the package: 33 | ```emacs 34 | M-x package-install RET company-tabnine RET 35 | ``` 36 | 37 | Put the following in your config: 38 | ```emacs 39 | (require 'company-tabnine) 40 | ``` 41 | 42 | 3. Add `company-tabnine` to `company-backends` 43 | ```emacs 44 | (add-to-list 'company-backends #'company-tabnine) 45 | ``` 46 | 47 | 4. Run `M-x company-tabnine-install-binary` to install the TabNine binary for your system. 48 | 49 | ## Recommended Configuration 50 | 51 | Below are some recommended `company-mode` configuration that works well with `company-tabnine`. 52 | 53 | ```emacs 54 | ;; Trigger completion immediately. 55 | (setq company-idle-delay 0) 56 | 57 | ;; Number the candidates (use M-1, M-2 etc to select completions). 58 | (setq company-show-numbers t) 59 | ``` 60 | 61 | ## Usage 62 | 63 | `company-tabnine` should work out of the box. 64 | 65 | See `M-x customize-group RET company-tabnine RET` for customizations. 66 | 67 | ### Auto-balance parentheses 68 | 69 | TabNine can automatically balance parentheses, by removing and adding closing parentheses after the cursor. See the examples [here](https://github.com/zxqfl/TabNine/blob/master/HowToWriteAClient.md). 70 | 71 | Note: The automatically-balancing happens in company's `post-completion` hook. However, `company-tng-frontend` actually suppresses this hook. In order to use automatic parentheses balancing, you need to manually call `company-complete-selection` or similar commands in this case, which will almost always happen if you do not use `company-tng-frontend`. 72 | 73 | ## Known Issues 74 | 75 | - `company-transformers` or plugins that use it (such as `company-flx-mode`) can interfere with TabNine's sorting. If this happens, put the following temporary workaround in your config: 76 | 77 | ```emacs 78 | ;; workaround for company-transformers 79 | (setq company-tabnine--disable-next-transform nil) 80 | (defun my-company--transform-candidates (func &rest args) 81 | (if (not company-tabnine--disable-next-transform) 82 | (apply func args) 83 | (setq company-tabnine--disable-next-transform nil) 84 | (car args))) 85 | 86 | (defun my-company-tabnine (func &rest args) 87 | (when (eq (car args) 'candidates) 88 | (setq company-tabnine--disable-next-transform t)) 89 | (apply func args)) 90 | 91 | (advice-add #'company--transform-candidates :around #'my-company--transform-candidates) 92 | (advice-add #'company-tabnine :around #'my-company-tabnine) 93 | ``` 94 | 95 | - Spacemacs configurations can override the settings for `company-backends`. 96 | 97 | - Conflict with ESS: See https://github.com/emacs-ess/ESS/issues/955 98 | 99 | - TabNine's local deep learning completion might be enabled by default. It is very CPU-intensive if your device can't handle it. You can check by typing "TabNine::config" in any buffer (your browser should then automatically open to TabNine's config page) and disable Deep TabNine Local (you will lose local deep learning completion). 100 | -------------------------------------------------------------------------------- /company-tabnine.el: -------------------------------------------------------------------------------- 1 | ;;; company-tabnine.el --- A company-mode backend for TabNine 2 | ;; 3 | ;; Copyright (c) 2018 Tommy Xiang 4 | ;; 5 | ;; Author: Tommy Xiang 6 | ;; Keywords: convenience 7 | ;; Version: 0.0.1 8 | ;; URL: https://github.com/TommyX12/company-tabnine/ 9 | ;; Package-Requires: ((emacs "25") (company "0.9.3") (cl-lib "0.5") (dash "2.16.0") (s "1.12.0")) 10 | ;; 11 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 12 | ;; of this software and associated documentation files (the "Software"), to deal 13 | ;; in the Software without restriction, including without limitation the rights 14 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | ;; copies of the Software, and to permit persons to whom the Software is 16 | ;; furnished to do so, subject to the following conditions: 17 | ;; 18 | ;; The above copyright notice and this permission notice shall be included in all 19 | ;; copies or substantial portions of the Software. 20 | ;; 21 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | ;; SOFTWARE. 28 | ;; 29 | ;;; Commentary: 30 | ;; 31 | ;; Description: 32 | ;; 33 | ;; TabNine(https://tabnine.com/) is the all-language autocompleter. 34 | ;; It uses machine learning to provide responsive, reliable, and relevant suggestions. 35 | ;; `company-tabnine' provides TabNine completion backend for `company-mode'(https://github.com/company-mode/company-mode). 36 | ;; It takes care of TabNine binaries, so installation is easy. 37 | ;; 38 | ;; Installation: 39 | ;; 40 | ;; 1. Make sure `company-mode' is installed and configured. 41 | ;; 2. Add `company-tabnine' to `company-backends': 42 | ;; 43 | ;; (add-to-list 'company-backends #'company-tabnine) 44 | ;; 45 | ;; 3. Run M-x company-tabnine-install-binary to install the TabNine binary for your system. 46 | ;; 47 | ;; Usage: 48 | ;; 49 | ;; `company-tabnine' should work out of the box. 50 | ;; See M-x customize-group RET company-tabnine RET for customizations. 51 | ;; 52 | ;; Recommended Configuration: 53 | ;; 54 | ;; - Trigger completion immediately. 55 | ;; 56 | ;; (setq company-idle-delay 0) 57 | ;; 58 | ;; - Number the candidates (use M-1, M-2 etc to select completions). 59 | ;; 60 | ;; (setq company-show-numbers t) 61 | ;; 62 | 63 | ;;; Code: 64 | 65 | ;; 66 | ;; Dependencies 67 | ;; 68 | 69 | (require 'cl-lib) 70 | (require 'company) 71 | (require 'company-template) 72 | (require 'dash) 73 | (require 'json) 74 | (require 's) 75 | (require 'url) 76 | 77 | 78 | ;; 79 | ;; Constants 80 | ;; 81 | 82 | (defconst company-tabnine--process-name "company-tabnine--process") 83 | (defconst company-tabnine--buffer-name "*company-tabnine-log*") 84 | (defconst company-tabnine--hooks-alist nil) 85 | (defconst company-tabnine--protocol-version "1.0.14") 86 | 87 | ;; tmp file put in company-tabnine-binaries-folder directory 88 | (defconst company-tabnine--version-tempfile "version") 89 | 90 | ;; current don't know how to use Prefetch and GetIdentifierRegex 91 | (defconst company-tabnine--method-autocomplete "Autocomplete") 92 | (defconst company-tabnine--method-prefetch "Prefetch") 93 | (defconst company-tabnine--method-getidentifierregex "GetIdentifierRegex") 94 | 95 | ;; 96 | ;; Macros 97 | ;; 98 | 99 | (defmacro company-tabnine-with-disabled (&rest body) 100 | "Run BODY with `company-tabnine' temporarily disabled. 101 | Useful when binding keys to temporarily query other completion backends." 102 | `(let ((company-tabnine--disabled t)) 103 | ,@body)) 104 | 105 | (defmacro company-tabnine--with-destructured-candidate 106 | (candidate &rest body) 107 | (declare (indent 1) (debug t)) 108 | `(let-alist ,candidate 109 | (setq type (company-tabnine--kind-to-type .kind)) 110 | (propertize 111 | .new_prefix 112 | 'old_suffix .old_suffix 113 | 'new_suffix .new_suffix 114 | 'kind .kind 115 | 'type type 116 | 'detail .detail 117 | 'annotation 118 | (concat (or .detail "") " " (or type ""))) 119 | ,@body)) 120 | 121 | (defun company-tabnine--filename-completer-p (extra-info) 122 | "Check whether candidate's EXTRA-INFO indicates a filename completion." 123 | (-contains? '("[File]" "[Dir]" "[File&Dir]") extra-info)) 124 | 125 | (defun company-tabnine--identifier-completer-p (extra-info) 126 | "Check if candidate's EXTRA-INFO indicates a identifier completion." 127 | (s-equals? "[ID]" extra-info)) 128 | 129 | ;; 130 | ;; Customization 131 | ;; 132 | 133 | (defgroup company-tabnine nil 134 | "Options for company-tabnine." 135 | :link '(url-link :tag "Github" "https://github.com/TommyX12/company-tabnine") 136 | :group 'company 137 | :prefix "company-tabnine-") 138 | 139 | (defcustom company-tabnine-max-num-results 10 140 | "Maximum number of results to show." 141 | :group 'company-tabnine 142 | :type 'integer) 143 | 144 | (defcustom company-tabnine-context-radius 3000 145 | "The number of chars before point to send for completion. 146 | 147 | Note that setting this too small will cause TabNine to not be able to read the entire license activation key." 148 | :group 'company-tabnine 149 | :type 'integer) 150 | 151 | (defcustom company-tabnine-context-radius-after 1000 152 | "The number of chars after point to send for completion." 153 | :group 'company-tabnine 154 | :type 'integer) 155 | 156 | (defcustom company-tabnine-max-restart-count 10 157 | "Maximum number of times TabNine can consecutively restart. 158 | This may be due to errors in or automatic server updates. 159 | Any successful completion will reset the consecutive count." 160 | :group 'company-tabnine 161 | :type 'integer) 162 | 163 | (defcustom company-tabnine-wait 0.25 164 | "Number of seconds to wait for TabNine to respond." 165 | :group 'company-tabnine 166 | :type 'float) 167 | 168 | (defcustom company-tabnine-always-trigger t 169 | "Whether to overload company's minimum prefix length. 170 | This allows completion to trigger on as much as possible. 171 | Default is t (strongly recommended)." 172 | :group 'company-tabnine 173 | :type 'boolean) 174 | 175 | (defcustom company-tabnine-no-continue nil 176 | "Whether to make company reset idle timer on all keystrokes. 177 | Only useful when `company-idle-delay' is not 0. 178 | Doing so improves performance by reducing number of calls to the completer, 179 | at the cost of less responsive completions." 180 | :group 'company-tabnine 181 | :type 'boolean) 182 | 183 | (defcustom company-tabnine-binaries-folder "~/.TabNine" 184 | "Path to TabNine binaries folder. 185 | `company-tabnine-install-binary' will use this directory." 186 | :group 'company-tabnine 187 | :type 'string) 188 | 189 | (defcustom company-tabnine-install-static-binary (file-exists-p "/etc/nixos/hardware-configuration.nix") 190 | "Whether to install the musl-linked static binary instead of 191 | the standard glibc-linked dynamic binary. 192 | Only useful on GNU/Linux. Automatically set if NixOS is detected." 193 | :group 'company-tabnine 194 | :type 'boolean) 195 | 196 | (defcustom company-tabnine-log-file-path nil 197 | "If non-nil, next TabNine restart will write debug log to this path." 198 | :group 'company-tabnine 199 | :type 'string) 200 | 201 | (defcustom company-tabnine-auto-balance t 202 | "Whether TabNine should insert balanced parentheses upon completion." 203 | :group 'company-tabnine 204 | :type 'boolean) 205 | 206 | ;; (defcustom company-tabnine-async t 207 | ;; "Whether or not to use async operations to fetch data." 208 | ;; :group 'company-tabnine 209 | ;; :type 'boolean) 210 | 211 | (defcustom company-tabnine-show-annotation t 212 | "Whether to show an annotation inline with the candidate." 213 | :group 'company-tabnine 214 | :type 'boolean) 215 | 216 | (defcustom company-tabnine-auto-fallback t 217 | "Whether to automatically fallback to other backends when TabNine has no candidates." 218 | :group 'company-tabnine 219 | :type 'boolean) 220 | 221 | (defcustom company-tabnine-use-native-json t 222 | "Whether to use native JSON when possible." 223 | :group 'company-tabnine 224 | :type 'boolean) 225 | 226 | (defcustom company-tabnine-insert-arguments t 227 | "When non-nil, insert function arguments as a template after completion. 228 | Only supported by modes in `company-tabnine--extended-features-modes'" 229 | :group 'company-tabnine 230 | :type 'boolean) 231 | 232 | 233 | ;; 234 | ;; Faces 235 | ;; 236 | 237 | ;; 238 | ;; Variables 239 | ;; 240 | 241 | (defvar company-tabnine-executable-args nil 242 | "Arguments passed to TabNine.") 243 | 244 | (defvar company-tabnine--process nil 245 | "TabNine server process.") 246 | 247 | (defvar company-tabnine--restart-count 0 248 | "Number of times TabNine server has restarted abnormally. 249 | Resets every time successful completion is returned.") 250 | 251 | (defvar company-tabnine--response nil 252 | "Temporarily stored TabNine server responses.") 253 | 254 | (defvar company-tabnine--disabled nil 255 | "Variable to temporarily disable company-tabnine and pass control to next backend.") 256 | 257 | (defvar company-tabnine--calling-continue nil 258 | "Flag for when `company-continue' is being called.") 259 | 260 | (defvar company-tabnine--response-chunks nil 261 | "The string to store response chunks from TabNine server.") 262 | 263 | ;; 264 | ;; Major mode definition 265 | ;; 266 | 267 | ;; 268 | ;; Global methods 269 | ;; 270 | 271 | (defun company-tabnine--prefix-candidate-p (candidate prefix) 272 | "Return t if CANDIDATE string begins with PREFIX." 273 | (let ((insertion-text (cdr (assq 'insertion_text candidate)))) 274 | (s-starts-with? prefix insertion-text t))) 275 | 276 | (defun company-tabnine--error-no-binaries () 277 | "Signal error for when TabNine binary is not found." 278 | (error "No TabNine binaries found. Run M-x company-tabnine-install-binary to download binaries")) 279 | 280 | (defun company-tabnine--get-target () 281 | "Return TabNine's system configuration. Used for finding the correct binary." 282 | (let* ((system-architecture (car (s-split "-" system-configuration))) 283 | (tabnine-architecture 284 | (cond 285 | ((or (string= system-architecture "aarch64") 286 | (and (eq system-type 'darwin) 287 | (string= system-architecture "x86_64") 288 | ;; Detect AArch64 running x86_64 Emacs 289 | (string= (shell-command-to-string "arch -arm64 uname -m") "arm64\n"))) 290 | "aarch64") 291 | ((or (string= system-architecture "arm") 292 | (and (eq system-type 'darwin) 293 | (string= system-architecture "x86_64") 294 | ;; Detect AArch64 running x86_64 Emacs 295 | (string= (shell-command-to-string "arch -arm64 uname -m") "arm64\n"))) 296 | "aarch64") 297 | ((string= system-architecture "x86_64") 298 | "x86_64") 299 | ((string-match system-architecture "i.86") 300 | "i686") 301 | (t 302 | (error "Unknown or unsupported architecture %s" system-architecture)))) 303 | 304 | (os 305 | (cond 306 | ((or (eq system-type 'ms-dos) 307 | (eq system-type 'windows-nt) 308 | (eq system-type 'cygwin)) 309 | "pc-windows-gnu") 310 | ((or (eq system-type 'darwin)) 311 | "apple-darwin") 312 | (company-tabnine-install-static-binary 313 | "unknown-linux-musl") 314 | (t 315 | "unknown-linux-gnu")))) 316 | 317 | (concat tabnine-architecture "-" os))) 318 | 319 | (defun company-tabnine--get-exe () 320 | "Return TabNine's binary file name. Used for finding the correct binary." 321 | (cond 322 | ((or (eq system-type 'ms-dos) 323 | (eq system-type 'windows-nt) 324 | (eq system-type 'cygwin)) 325 | "TabNine.exe") 326 | (t 327 | "TabNine"))) 328 | 329 | (defun company-tabnine--executable-path () 330 | "Find and return the path of the latest TabNine binary for the current system." 331 | (let ((parent company-tabnine-binaries-folder)) 332 | (if (file-directory-p parent) 333 | (let* ((children (->> (directory-files parent) 334 | (--remove (member it '("." ".."))) 335 | (--filter (file-directory-p 336 | (expand-file-name 337 | it 338 | (file-name-as-directory 339 | parent)))) 340 | (--filter (ignore-errors (version-to-list it))) 341 | (-non-nil))) 342 | (sorted (nreverse (sort children #'version<))) 343 | (target (company-tabnine--get-target)) 344 | (filename (company-tabnine--get-exe))) 345 | (cl-loop 346 | for ver in sorted 347 | for fullpath = (expand-file-name (format "%s/%s/%s" 348 | ver target filename) 349 | parent) 350 | if (and (file-exists-p fullpath) 351 | (file-regular-p fullpath)) 352 | return fullpath 353 | finally do (company-tabnine--error-no-binaries))) 354 | (company-tabnine--error-no-binaries)))) 355 | 356 | (defun company-tabnine-start-process () 357 | "Start TabNine process." 358 | (company-tabnine-kill-process) 359 | (let ((process-connection-type nil)) 360 | (setq company-tabnine--process 361 | (make-process 362 | :name company-tabnine--process-name 363 | :command (append 364 | (cons (company-tabnine--executable-path) 365 | (when company-tabnine-log-file-path 366 | (list 367 | "--log-file-path" 368 | (expand-file-name 369 | company-tabnine-log-file-path)))) 370 | (list "--client" "emacs") 371 | company-tabnine-executable-args) 372 | :coding 'utf-8 373 | :connection-type 'pipe 374 | :filter #'company-tabnine--process-filter 375 | :sentinel #'company-tabnine--process-sentinel 376 | :noquery t))) 377 | ;; hook setup 378 | (message "TabNine server started.") 379 | (dolist (hook company-tabnine--hooks-alist) 380 | (add-hook (car hook) (cdr hook)))) 381 | 382 | (defun company-tabnine-kill-process () 383 | "Kill TabNine process." 384 | (interactive) 385 | (when company-tabnine--process 386 | (let ((process company-tabnine--process)) 387 | (setq company-tabnine--process nil) ; this happens first so sentinel don't catch the kill 388 | (delete-process process))) 389 | ;; hook remove 390 | (dolist (hook company-tabnine--hooks-alist) 391 | (remove-hook (car hook) (cdr hook)))) 392 | 393 | (defun company-tabnine-send-request (request) 394 | "Send REQUEST to TabNine server. REQUEST needs to be JSON-serializable object." 395 | (when (null company-tabnine--process) 396 | (company-tabnine-start-process)) 397 | (when company-tabnine--process 398 | ;; TODO make sure utf-8 encoding works 399 | (let ((encoded (concat 400 | (if (and company-tabnine-use-native-json 401 | (fboundp 'json-serialize)) 402 | (json-serialize request 403 | :null-object nil 404 | :false-object json-false) 405 | (let ((json-null nil) 406 | (json-encoding-pretty-print nil)) 407 | (json-encode-list request))) 408 | "\n"))) 409 | (setq company-tabnine--response nil) 410 | (process-send-string company-tabnine--process encoded) 411 | (accept-process-output company-tabnine--process company-tabnine-wait)))) 412 | 413 | (defun company-tabnine--make-request (method) 414 | "Create request body for method METHOD and parameters PARAMS." 415 | (cond 416 | ((eq method 'autocomplete) 417 | (let* ((buffer-min 1) 418 | (buffer-max (1+ (buffer-size))) 419 | (before-point 420 | (max (point-min) (- (point) company-tabnine-context-radius))) 421 | (after-point 422 | (min (point-max) (+ (point) company-tabnine-context-radius-after)))) 423 | 424 | (list 425 | :version company-tabnine--protocol-version 426 | :request 427 | (list :Autocomplete 428 | (list 429 | :before (buffer-substring-no-properties before-point (point)) 430 | :after (buffer-substring-no-properties (point) after-point) 431 | :filename (or (buffer-file-name) nil) 432 | :region_includes_beginning (if (= before-point buffer-min) 433 | t json-false) 434 | :region_includes_end (if (= after-point buffer-max) 435 | t json-false) 436 | :max_num_results company-tabnine-max-num-results))))) 437 | 438 | ((eq method 'prefetch) 439 | (list 440 | :version company-tabnine--protocol-version 441 | :request 442 | (list :Prefetch 443 | (list 444 | :filename (or (buffer-file-name) nil) 445 | )))) 446 | ((eq method 'getidentifierregex) 447 | (list 448 | :version company-tabnine--protocol-version 449 | :request 450 | (list :GetIdentifierRegex 451 | (list 452 | :filename (or (buffer-file-name) nil) 453 | )))))) 454 | 455 | (defun company-tabnine-query () 456 | "Query TabNine server for auto-complete." 457 | (let ((request (company-tabnine--make-request 'autocomplete))) 458 | (company-tabnine-send-request request) 459 | )) 460 | 461 | (defun company-tabnine--decode (msg) 462 | "Decode TabNine server response MSG, and return the decoded object." 463 | (if (and company-tabnine-use-native-json 464 | (fboundp 'json-parse-string)) 465 | (ignore-errors 466 | (json-parse-string msg :object-type 'alist)) 467 | (let ((json-array-type 'list) 468 | (json-object-type 'alist)) 469 | (json-read-from-string msg)))) 470 | 471 | (defun company-tabnine--process-sentinel (process event) 472 | "Sentinel for TabNine server process. 473 | PROCESS is the process under watch, EVENT is the event occurred." 474 | (when (and company-tabnine--process 475 | (memq (process-status process) '(exit signal))) 476 | 477 | (message "TabNine process %s received event %s." 478 | (prin1-to-string process) 479 | (prin1-to-string event)) 480 | 481 | (if (>= company-tabnine--restart-count 482 | company-tabnine-max-restart-count) 483 | (progn 484 | (message "TabNine process restart limit reached.") 485 | (setq company-tabnine--process nil)) 486 | 487 | (message "Restarting TabNine process.") 488 | (company-tabnine-start-process) 489 | (setq company-tabnine--restart-count 490 | (1+ company-tabnine--restart-count))))) 491 | 492 | (defun company-tabnine--process-filter (process output) 493 | "Filter for TabNine server process. 494 | PROCESS is the process under watch, OUTPUT is the output received." 495 | (push output company-tabnine--response-chunks) 496 | (when (s-ends-with-p "\n" output) 497 | (let ((response 498 | (mapconcat #'identity 499 | (nreverse company-tabnine--response-chunks) 500 | nil))) 501 | (setq company-tabnine--response 502 | (company-tabnine--decode response) 503 | company-tabnine--response-chunks nil)))) 504 | 505 | (defun company-tabnine--prefix () 506 | "Prefix-command handler for the company backend." 507 | (if (or (and company-tabnine-no-continue 508 | company-tabnine--calling-continue) 509 | company-tabnine--disabled) 510 | nil 511 | (company-tabnine-query) 512 | (let ((prefix 513 | (and company-tabnine--response 514 | (> (length (alist-get 'results company-tabnine--response)) 0) 515 | (alist-get 'old_prefix company-tabnine--response)))) 516 | (unless (or prefix 517 | company-tabnine-auto-fallback) 518 | (setq prefix 'stop)) 519 | (if (and prefix 520 | company-tabnine-always-trigger) 521 | (cons prefix t) 522 | prefix)))) 523 | 524 | (defun company-tabnine--annotation(candidate) 525 | "Fetch the annotation text-property from a CANDIDATE string." 526 | (when company-tabnine-show-annotation 527 | (-if-let (annotation (get-text-property 0 'annotation candidate)) 528 | annotation 529 | (let ((kind (get-text-property 0 'kind candidate)) 530 | ;; (return-type (get-text-property 0 'return_type candidate)) 531 | (params (get-text-property 0 'params candidate))) 532 | (when kind 533 | (concat params 534 | ;; (when (s-present? return-type) 535 | ;; (s-prepend " -> " return-type)) 536 | (when (s-present? kind) 537 | (format " [%s]" kind)))))))) 538 | 539 | (defun company-tabnine--kind-to-type (kind) 540 | (pcase kind 541 | (1 "Text") 542 | (2 "Method") 543 | (3 "Function") 544 | (4 "Constructor") 545 | (5 "Field") 546 | (6 "Variable") 547 | (7 "Class") 548 | (8 "Interface") 549 | (9 "Module") 550 | (10 "Property" ) 551 | (11 "Unit" ) 552 | (12 "Value" ) 553 | (13 "Enum") 554 | (14 "Keyword" ) 555 | (15 "Snippet") 556 | (16 "Color") 557 | (17 "File") 558 | (18 "Reference") 559 | (19 "Folder") 560 | (20 "EnumMember") 561 | (21 "Constant") 562 | (22 "Struct") 563 | (23 "Event") 564 | (24 "Operator") 565 | (25 "TypeParameter"))) 566 | 567 | (defun company-tabnine--construct-candidate-generic (candidate) 568 | "Generic function to construct completion string from a CANDIDATE." 569 | (company-tabnine--with-destructured-candidate candidate)) 570 | 571 | (defun company-tabnine--construct-candidates (results construct-candidate-fn) 572 | "Use CONSTRUCT-CANDIDATE-FN to construct a list of candidates from RESULTS." 573 | (let ((completions (mapcar construct-candidate-fn results))) 574 | (when completions 575 | (setq company-tabnine--restart-count 0)) 576 | completions)) 577 | 578 | (defun company-tabnine--get-candidates (response) 579 | "Get candidates for RESPONSE." 580 | (company-tabnine--construct-candidates 581 | (alist-get 'results response) 582 | #'company-tabnine--construct-candidate-generic)) 583 | 584 | (defun company-tabnine--candidates (prefix) 585 | "Candidates-command handler for the company backend for PREFIX. 586 | 587 | Return completion candidates. Must be called after `company-tabnine-query'." 588 | (company-tabnine--get-candidates company-tabnine--response)) 589 | 590 | (defun company-tabnine--meta (candidate) 591 | "Return meta information for CANDIDATE. Currently used to display user messages." 592 | (if (null company-tabnine--response) 593 | nil 594 | (let ((meta (get-text-property 0 'meta candidate))) 595 | (if (stringp meta) 596 | (let ((meta-trimmed (s-trim meta))) 597 | meta-trimmed) 598 | 599 | (let ((messages (alist-get 'user_message company-tabnine--response))) 600 | (when messages 601 | (s-join " " messages))))))) 602 | 603 | (defun company-tabnine--post-completion (candidate) 604 | "Replace old suffix with new suffix for CANDIDATE." 605 | (when company-tabnine-auto-balance 606 | (let ((old_suffix (get-text-property 0 'old_suffix candidate)) 607 | (new_suffix (get-text-property 0 'new_suffix candidate))) 608 | (delete-region (point) 609 | (min (+ (point) (length old_suffix)) 610 | (point-max))) 611 | (when (stringp new_suffix) 612 | (save-excursion 613 | (insert new_suffix)))))) 614 | 615 | ;; 616 | ;; Interactive functions 617 | ;; 618 | 619 | (defun company-tabnine-restart-server () 620 | "Start/Restart TabNine server." 621 | (interactive) 622 | (company-tabnine-start-process)) 623 | 624 | (defun company-tabnine-install-binary () 625 | "Install TabNine binary into `company-tabnine-binaries-folder'." 626 | (interactive) 627 | (let ((version-tempfile (concat 628 | (file-name-as-directory 629 | company-tabnine-binaries-folder) 630 | company-tabnine--version-tempfile)) 631 | (target (company-tabnine--get-target)) 632 | (exe (company-tabnine--get-exe)) 633 | (binaries-dir company-tabnine-binaries-folder)) 634 | (message version-tempfile) 635 | (message "Getting current version...") 636 | (make-directory (file-name-directory version-tempfile) t) 637 | (url-copy-file "https://update.tabnine.com/bundles/version" version-tempfile t) 638 | (let ((version (s-trim (with-temp-buffer (insert-file-contents version-tempfile) (buffer-string))))) 639 | (when (= (length version) 0) 640 | (error "TabNine installation failed. Please try again")) 641 | (message "Current version is %s" version) 642 | (let* ((url (concat "https://update.tabnine.com/bundles/" version "/" target "/TabNine.zip")) 643 | (version-directory (file-name-as-directory 644 | (concat 645 | (file-name-as-directory 646 | (concat (file-name-as-directory binaries-dir) version))))) 647 | (target-directory (file-name-as-directory (concat version-directory target) )) 648 | (bundle-path (concat version-directory (format "%s.zip" target))) 649 | (target-path (concat target-directory exe))) 650 | (message "Installing at %s. Downloading %s ..." target-path url) 651 | (make-directory target-directory t) 652 | (url-copy-file url bundle-path t) 653 | (condition-case ex 654 | (let ((default-directory target-directory)) 655 | (if (or (eq system-type 'ms-dos) 656 | (eq system-type 'windows-nt) 657 | (eq system-type 'cygwin)) 658 | (shell-command (format "tar -xf %s" (expand-file-name bundle-path))) 659 | (shell-command (format "unzip -o %s -d %s" 660 | (expand-file-name bundle-path) 661 | (expand-file-name target-directory))))) 662 | ('error 663 | (error "Unable to unzip automatically. Please go to [%s] and unzip the content of [%s] into [%s/]." 664 | (expand-file-name version-directory) 665 | (file-name-nondirectory bundle-path) 666 | (file-name-sans-extension (file-name-nondirectory bundle-path))))) 667 | (mapc (lambda (filename) 668 | (set-file-modes (concat target-directory filename) (string-to-number "744" 8))) 669 | (--remove (member it '("." "..")) (directory-files target-directory))) 670 | (delete-file bundle-path) 671 | (delete-file version-tempfile) 672 | (message "TabNine installation complete."))))) 673 | 674 | (defun company-tabnine-call-other-backends () 675 | "Invoke company completion but disable TabNine once, passing query to other backends in `company-backends'. 676 | 677 | This is actually obsolete, since `company-other-backend' does the same." 678 | (interactive) 679 | (company-tabnine-with-disabled 680 | (company-abort) 681 | (company-auto-begin))) 682 | 683 | ;;;###autoload 684 | (defun company-tabnine (command &optional arg &rest ignored) 685 | "`company-mode' backend for TabNine. 686 | 687 | See documentation of `company-backends' for details." 688 | (interactive (list 'interactive)) 689 | (cl-case command 690 | (interactive (company-begin-backend 'company-tabnine)) 691 | (prefix (company-tabnine--prefix)) 692 | (candidates (company-tabnine--candidates arg)) 693 | ;; TODO: should we use async or not? 694 | ;; '(:async . (lambda (callback) 695 | ;; (funcall callback (company-tabnine--candidates) arg)))) 696 | (meta (company-tabnine--meta arg)) 697 | (annotation (company-tabnine--annotation arg)) 698 | (post-completion (company-tabnine--post-completion arg)) 699 | (no-cache t) 700 | (sorted t))) 701 | 702 | ;; 703 | ;; Advices 704 | ;; 705 | 706 | (defun company-tabnine--continue-advice (func &rest args) 707 | "Advice for `company--continue'." 708 | (let ((company-tabnine--calling-continue t)) 709 | (apply func args))) 710 | 711 | (advice-add #'company--continue :around #'company-tabnine--continue-advice) 712 | 713 | (defun company-tabnine--insert-candidate-advice (func &rest args) 714 | "Advice for `company--insert-candidate'." 715 | (if company-tabnine-auto-balance 716 | (let ((smartparens-mode nil)) 717 | (apply func args)) 718 | (apply func args))) 719 | 720 | ;; `smartparens' will add an advice on `company--insert-candidate' in order to 721 | ;; add closing parenthesis. 722 | ;; If TabNine takes care of parentheses, we disable smartparens temporarily. 723 | (eval-after-load 'smartparens 724 | '(advice-add #'company--insert-candidate 725 | :around #'company-tabnine--insert-candidate-advice)) 726 | 727 | ;; 728 | ;; Hooks 729 | ;; 730 | 731 | 732 | (provide 'company-tabnine) 733 | 734 | ;;; company-tabnine.el ends here 735 | -------------------------------------------------------------------------------- /fetch-binaries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # This script downloads the binaries for the most recent version of TabNine. 5 | 6 | version="$(curl -sS https://update.tabnine.com/bundles/version)" 7 | targets='i686-pc-windows-gnu 8 | i686-unknown-linux-musl 9 | x86_64-apple-darwin 10 | aarch64-apple-darwin 11 | x86_64-pc-windows-gnu 12 | x86_64-unknown-linux-musl' 13 | 14 | rm -rf ./binaries 15 | 16 | echo "$targets" | while read target 17 | do 18 | mkdir -p binaries/$version/$target 19 | path=$version/$target 20 | echo "downloading $path" 21 | curl -sS https://update.tabnine.com/bundles/$path/TabNine.zip > binaries/$path/TabNine.zip 22 | unzip -o binaries/$path/TabNine.zip -d binaries/$path 23 | rm binaries/$path/TabNine.zip 24 | chmod +x binaries/$path/* 25 | done 26 | -------------------------------------------------------------------------------- /melpa-recipe: -------------------------------------------------------------------------------- 1 | (company-tabnine :repo "TommyX12/company-tabnine" :fetcher github) -------------------------------------------------------------------------------- /screenshot-deep-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TommyX12/company-tabnine/083d290c19b874f2ae139c77a726932e618e29b2/screenshot-deep-1.png -------------------------------------------------------------------------------- /screenshot-deep-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TommyX12/company-tabnine/083d290c19b874f2ae139c77a726932e618e29b2/screenshot-deep-2.png -------------------------------------------------------------------------------- /screenshot-deep-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TommyX12/company-tabnine/083d290c19b874f2ae139c77a726932e618e29b2/screenshot-deep-3.png -------------------------------------------------------------------------------- /screenshot-deep-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TommyX12/company-tabnine/083d290c19b874f2ae139c77a726932e618e29b2/screenshot-deep-4.png -------------------------------------------------------------------------------- /screenshot-deep-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TommyX12/company-tabnine/083d290c19b874f2ae139c77a726932e618e29b2/screenshot-deep-5.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TommyX12/company-tabnine/083d290c19b874f2ae139c77a726932e618e29b2/screenshot.png --------------------------------------------------------------------------------