branch: externals/company commit fb9db076c9f968cdf32fc04af9c1abe410275267 Merge: d6df27f c1e8972 Author: Dmitry Gutov <dgu...@yandex.ru> Commit: GitHub <nore...@github.com>
Merge pull request #999 from kiennq/bug/company-selection-default company-selection: add company-selection-default to have better support for non-selection popup --- NEWS.md | 3 + company-tng.el | 125 ++++++++++++----------------- company.el | 225 ++++++++++++++++++++++++++++++----------------------- test/core-tests.el | 125 +++++++++++++++++++++++++++++ 4 files changed, 306 insertions(+), 172 deletions(-) diff --git a/NEWS.md b/NEWS.md index d93c873..b51c457 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,9 @@ ## Next +* `company-tng-mode` has been added and replace the manual method of enabling + `company-tng-frontend`. Also, `company-selection` can have null value to + indicate no selection. * `company-auto-complete` and `company-auto-complete-chars` have been renamed to `company-auto-commit` and `company-auto-commit-chars` respectively. * `company-clang` filters out duplicates diff --git a/company-tng.el b/company-tng.el index 404d436..d0096c1 100644 --- a/company-tng.el +++ b/company-tng.el @@ -37,16 +37,12 @@ ;; need to confirm the entry. ;; ;; Usage: +;; Enable`company-tng-mode' with: +;; (add-hook 'company-mode-hook 'company-tng-mode) ;; ;; To apply the default configuration for company-tng call ;; `company-tng-configure-default' from your init script. ;; -;; You can also configure company-tng manually: -;; -;; Add `company-tng-frontend' to `company-frontends': -;; -;; (add-to-list 'company-frontends 'company-tng-frontend) -;; ;; We recommend to bind TAB to `company-select-next', S-TAB to ;; `company-select-previous', and unbind RET and other now-unnecessary ;; keys from `company-active-map': @@ -105,25 +101,22 @@ confirm the selection and finish the completion." (show (let ((ov (make-overlay (point) (point)))) (setq company-tng--overlay ov) - (overlay-put ov 'priority 2)) - (advice-add 'company-select-next :before-until 'company-tng--allow-unselected) - (advice-add 'company-fill-propertize :filter-args 'company-tng--adjust-tooltip-highlight)) + (overlay-put ov 'priority 2))) (update - (let ((ov company-tng--overlay) - (selected (nth company-selection company-candidates)) - (prefix (length company-prefix))) + (let* ((ov company-tng--overlay) + (selected (and company-selection + (nth company-selection company-candidates))) + (prefix (length company-prefix))) (move-overlay ov (- (point) prefix) (point)) (overlay-put ov (if (= prefix 0) 'after-string 'display) - (and company-selection-changed selected)))) + selected))) (hide (when company-tng--overlay (delete-overlay company-tng--overlay) - (kill-local-variable 'company-tng--overlay)) - (advice-remove 'company-select-next 'company-tng--allow-unselected) - (advice-remove 'company-fill-propertize 'company-tng--adjust-tooltip-highlight)) + (kill-local-variable 'company-tng--overlay))) (pre-command - (when (and company-selection-changed + (when (and company-selection (not (company--company-command-p (this-command-keys)))) (company--unread-this-command-keys) (setq this-command 'company-complete-selection))))) @@ -133,65 +126,49 @@ confirm the selection and finish the completion." (defvar company-rtags-insert-arguments) (defvar lsp-enable-snippet) +(defgroup company-tng nil + "Company Tab and Go." + :group 'company) + +(defcustom company-tng-auto-configure t + "Automatically apply default configure when enable `company-tng-mode'." + :type 'boolean) + +;;;###autoload +(define-obsolete-function-alias 'company-tng-configure-default 'company-tng-mode "0.9.14" + "Applies the default configuration to enable company-tng.") + ;;;###autoload -(defun company-tng-configure-default () - "Applies the default configuration to enable company-tng." - (setq company-require-match nil) - (setq company-frontends '(company-tng-frontend - company-pseudo-tooltip-frontend - company-echo-metadata-frontend)) - (setq company-clang-insert-arguments nil - company-semantic-insert-arguments nil - company-rtags-insert-arguments nil - lsp-enable-snippet nil) - (advice-add #'eglot--snippet-expansion-fn :override #'ignore) - (let ((keymap company-active-map)) - (define-key keymap [return] nil) - (define-key keymap (kbd "RET") nil) - (define-key keymap [tab] 'company-select-next) - (define-key keymap (kbd "TAB") 'company-select-next) - (define-key keymap [backtab] 'company-select-previous) - (define-key keymap (kbd "S-TAB") 'company-select-previous))) - -(defun company-tng--allow-unselected (&optional arg) - "Advice `company-select-next' to allow for an 'unselected' -state. Unselected means that no user interaction took place on the -completion candidates and it's marked by setting -`company-selection-changed' to nil. This advice will call the underlying -`company-select-next' unless we need to transition to or from an unselected -state. - -Possible state transitions: -- (arg > 0) unselected -> first candidate selected -- (arg < 0) first candidate selected -> unselected -- (arg < 0 wrap-round) unselected -> last candidate selected -- (arg < 0 no wrap-round) unselected -> unselected - -There is no need to advice `company-select-previous' because it calls -`company-select-next' internally." +(define-minor-mode company-tng-mode + "This minor mode enables `company-tng-frontend'." + :init-value nil + :global t (cond - ;; Selecting next - ((or (not arg) (> arg 0)) - (unless company-selection-changed - (company-set-selection (1- (or arg 1)) 'force-update) - t)) - ;; Selecting previous - ((< arg 0) - (when (and company-selection-changed - (< (+ company-selection arg) 0)) - (company-set-selection 0) - (setq company-selection-changed nil) - (company-call-frontends 'update) - t) - ))) - -(defun company-tng--adjust-tooltip-highlight (args) - "Prevent the tooltip from highlighting the current selection if it wasn't -made explicitly (i.e. `company-selection-changed' is true)" - (unless company-selection-changed - ;; The 4th arg of `company-fill-propertize' is selected - (setf (nth 3 args) nil)) - args) + (company-tng-mode + (setq company-frontends + (add-to-list 'company-frontends 'company-tng-frontend)) + (when company-tng-auto-configure + (setq company-require-match nil) + (setq company-frontends '(company-tng-frontend + company-pseudo-tooltip-frontend + company-echo-metadata-frontend)) + (setq company-clang-insert-arguments nil + company-semantic-insert-arguments nil + company-rtags-insert-arguments nil + lsp-enable-snippet nil) + (advice-add #'eglot--snippet-expansion-fn :override #'ignore) + (let ((keymap company-active-map)) + (define-key keymap [return] nil) + (define-key keymap (kbd "RET") nil) + (define-key keymap [tab] 'company-select-next) + (define-key keymap (kbd "TAB") 'company-select-next) + (define-key keymap [backtab] 'company-select-previous) + (define-key keymap (kbd "S-TAB") 'company-select-previous))) + (setq company-selection-default nil)) + (t + (setq company-frontends + (delete 'company-tng-frontend company-frontends)) + (setq company-selection-default 0)))) (provide 'company-tng) ;;; company-tng.el ends here diff --git a/company.el b/company.el index 401a6a2..1dd389d 100644 --- a/company.el +++ b/company.el @@ -757,9 +757,10 @@ asynchronous call into synchronous.") (company-candidates (:eval (if (consp company-backend) - (company--group-lighter (nth company-selection - company-candidates) - company-lighter-base) + (when company-selection + (company--group-lighter (nth company-selection + company-candidates) + company-lighter-base)) (symbol-name company-backend))) company-lighter-base)) "Mode line lighter for Company. @@ -1108,7 +1109,9 @@ matches IDLE-BEGIN-AFTER-RE, return it wrapped in a cons." (defvar-local company-common nil) -(defvar-local company-selection 0) +(defvar company-selection-default 0 + "The default value for `company-selection'.") +(defvar-local company-selection company-selection-default) (defvar-local company-selection-changed nil) @@ -1192,10 +1195,20 @@ can retrieve meta-data for them." frontend (error-message-string err) command))))) (defun company-set-selection (selection &optional force-update) - (setq selection - (if company-selection-wrap-around - (mod selection company-candidates-length) - (max 0 (min (1- company-candidates-length) selection)))) + "Set SELECTION for company candidates. +This will update `company-selection' and related variable. +Only update when the current selection is changed, but optionally always +update if FORCE-UPDATE." + (when selection + (let* ((offset (if company-selection-default 0 1)) + (company-candidates-length + (+ company-candidates-length offset))) + (setq selection + (if company-selection-wrap-around + (mod selection company-candidates-length) + (max 0 (min (1- company-candidates-length) selection)))) + (setq selection (unless (< selection offset) + (- selection offset))))) (when (or force-update (not (equal selection company-selection))) (setq company-selection selection company-selection-changed t) @@ -1214,10 +1227,11 @@ can retrieve meta-data for them." (setq company-candidates-length (length candidates)) (if company-selection-changed ;; Try to restore the selection - (let ((selected (nth company-selection company-candidates))) - (setq company-selection 0 - company-candidates candidates) + (let ((selected (and company-selection + (nth company-selection company-candidates)))) + (setq company-candidates candidates) (when selected + (setq company-selection 0) (catch 'found (while candidates (let ((candidate (pop candidates))) @@ -1226,9 +1240,9 @@ can retrieve meta-data for them." (company-call-backend 'annotation selected))) (throw 'found t))) (cl-incf company-selection)) - (setq company-selection 0 + (setq company-selection company-selection-default company-selection-changed nil)))) - (setq company-selection 0 + (setq company-selection company-selection-default company-candidates candidates)) ;; Calculate common. (let ((completion-ignore-case (company-call-backend 'ignore-case))) @@ -1666,7 +1680,7 @@ prefix match (same case) will be prioritized." company-candidates-cache nil company-candidates-predicate nil company-common nil - company-selection 0 + company-selection company-selection-default company-selection-changed nil company--manual-action nil company--manual-prefix nil @@ -1871,11 +1885,12 @@ each one wraps a part of the input string." (company-update-candidates cc))) (defun company--search-update-string (new) - (let* ((pos (company--search new (nthcdr company-selection company-candidates)))) + (let* ((selection (or company-selection 0)) + (pos (company--search new (nthcdr selection company-candidates)))) (if (null pos) (ding) (setq company-search-string new) - (company-set-selection (+ company-selection pos) t)))) + (company-set-selection (+ selection pos) t)))) (defun company--search-assert-input () (company--search-assert-enabled) @@ -1886,24 +1901,25 @@ each one wraps a part of the input string." "Repeat the incremental search in completion candidates forward." (interactive) (company--search-assert-input) - (let ((pos (company--search company-search-string - (cdr (nthcdr company-selection - company-candidates))))) + (let* ((selection (or company-selection 0)) + (pos (company--search company-search-string + (cdr (nthcdr selection company-candidates))))) (if (null pos) (ding) - (company-set-selection (+ company-selection pos 1) t)))) + (company-set-selection (+ selection pos 1) t)))) (defun company-search-repeat-backward () "Repeat the incremental search in completion candidates backwards." (interactive) (company--search-assert-input) - (let ((pos (company--search company-search-string + (let* ((selection (or company-selection 0)) + (pos (company--search company-search-string (nthcdr (- company-candidates-length - company-selection) + selection) (reverse company-candidates))))) (if (null pos) (ding) - (company-set-selection (- company-selection pos 1) t)))) + (company-set-selection (- selection pos 1) t)))) (defun company-search-toggle-filtering () "Toggle `company-search-filtering'." @@ -2041,10 +2057,17 @@ followed by `company-search-toggle-filtering'." (defun company-select-next (&optional arg) "Select the next candidate in the list. -With ARG, move by that many elements." +With ARG, move by that many elements. +When `company-selection-default' is nil, add a special pseudo candidates +meant for no selection." (interactive "p") (when (company-manual-begin) - (company-set-selection (+ (or arg 1) company-selection)))) + (let ((selection (+ (or arg 1) + (or company-selection + company-selection-default + -1) + (if company-selection-default 0 1)))) + (company-set-selection selection)))) (defun company-select-previous (&optional arg) "Select the previous candidate in the list. @@ -2153,7 +2176,7 @@ With ARG, move by that many elements." (defun company-complete-selection () "Insert the selected candidate." (interactive) - (when (company-manual-begin) + (when (and (company-manual-begin) company-selection) (let ((result (nth company-selection company-candidates))) (company-finish result)))) @@ -2281,7 +2304,7 @@ character, stripping the modifiers. That character must be a digit." (defvar-local company-last-metadata nil) (defun company-fetch-metadata () - (let ((selected (nth company-selection company-candidates))) + (let ((selected (nth (or company-selection 0) company-candidates))) (unless (eq selected (car company-last-metadata)) (setq company-last-metadata (cons selected (company-call-backend 'meta selected)))) @@ -2332,9 +2355,10 @@ character, stripping the modifiers. That character must be a digit." (defun company-show-doc-buffer () "Temporarily show the documentation buffer for the selection." (interactive) - (let (other-window-scroll-buffer) + (let ((other-window-scroll-buffer) + (selection (or company-selection 0))) (company--electric-do - (let* ((selected (nth company-selection company-candidates)) + (let* ((selected (nth selection company-candidates)) (doc-buffer (or (company-call-backend 'doc-buffer selected) (user-error "No documentation available"))) start) @@ -2791,23 +2815,26 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (when (< len (+ company-tooltip-offset limit)) (setq company-tooltip-offset 0)) - ;; Scroll to offset. - (if (eq company-tooltip-offset-display 'lines) - (setq limit (company-tooltip--lines-update-offset selection len limit)) - (company-tooltip--simple-update-offset selection len limit)) + (let ((selection (or selection 0))) + ;; Scroll to offset. + (if (eq company-tooltip-offset-display 'lines) + (setq limit (company-tooltip--lines-update-offset selection len limit)) + (company-tooltip--simple-update-offset selection len limit)) + + (cond + ((eq company-tooltip-offset-display 'scrollbar) + (setq scrollbar-bounds (company--scrollbar-bounds company-tooltip-offset + limit len))) + ((eq company-tooltip-offset-display 'lines) + (when (> company-tooltip-offset 0) + (setq previous (format "...(%d)" company-tooltip-offset))) + (setq remainder (- len limit company-tooltip-offset) + remainder (when (> remainder 0) + (setq remainder (format "...(%d)" remainder))))))) + + (when selection + (cl-decf selection company-tooltip-offset)) - (cond - ((eq company-tooltip-offset-display 'scrollbar) - (setq scrollbar-bounds (company--scrollbar-bounds company-tooltip-offset - limit len))) - ((eq company-tooltip-offset-display 'lines) - (when (> company-tooltip-offset 0) - (setq previous (format "...(%d)" company-tooltip-offset))) - (setq remainder (- len limit company-tooltip-offset) - remainder (when (> remainder 0) - (setq remainder (format "...(%d)" remainder)))))) - - (cl-decf selection company-tooltip-offset) (setq width (max (length previous) (length remainder)) lines (nthcdr company-tooltip-offset company-candidates) len (min limit len) @@ -3114,8 +3141,10 @@ Delay is determined by `company-tooltip-idle-delay'." "`company-mode' frontend showing the selection as if it had been inserted." (pcase command (`pre-command (company-preview-hide)) - (`post-command (company-preview-show-at-point (point) - (nth company-selection company-candidates))) + (`post-command + (when company-selection + (company-preview-show-at-point (point) + (nth company-selection company-candidates)))) (`hide (company-preview-hide)))) (defun company-preview-if-just-one-frontend (command) @@ -3191,59 +3220,59 @@ Delay is determined by `company-tooltip-idle-delay'." (run-with-idle-timer company-echo-delay nil 'company-echo-show getter))) (defun company-echo-format () - - (let ((limit (window-body-width (minibuffer-window))) - (len -1) - ;; Roll to selection. - (candidates (nthcdr company-selection company-candidates)) - (i (if company-show-numbers company-selection 99999)) - comp msg) - - (while candidates - (setq comp (company-reformat (company--clean-string (pop candidates))) - len (+ len 1 (length comp))) - (if (< i 10) - ;; Add number. - (progn - (setq comp (propertize (format "%d: %s" i comp) - 'face 'company-echo)) - (cl-incf len 3) - (cl-incf i) - (add-text-properties 3 (+ 3 (string-width company-common)) - '(face company-echo-common) comp)) - (setq comp (propertize comp 'face 'company-echo)) - (add-text-properties 0 (string-width company-common) - '(face company-echo-common) comp)) - (if (>= len limit) - (setq candidates nil) - (push comp msg))) - - (mapconcat 'identity (nreverse msg) " "))) + (let ((selection (or company-selection 0))) + (let ((limit (window-body-width (minibuffer-window))) + (len -1) + ;; Roll to selection. + (candidates (nthcdr selection company-candidates)) + (i (if company-show-numbers selection 99999)) + comp msg) + + (while candidates + (setq comp (company-reformat (company--clean-string (pop candidates))) + len (+ len 1 (length comp))) + (if (< i 10) + ;; Add number. + (progn + (setq comp (propertize (format "%d: %s" i comp) + 'face 'company-echo)) + (cl-incf len 3) + (cl-incf i) + (add-text-properties 3 (+ 3 (string-width company-common)) + '(face company-echo-common) comp)) + (setq comp (propertize comp 'face 'company-echo)) + (add-text-properties 0 (string-width company-common) + '(face company-echo-common) comp)) + (if (>= len limit) + (setq candidates nil) + (push comp msg))) + + (mapconcat 'identity (nreverse msg) " ")))) (defun company-echo-strip-common-format () - - (let ((limit (window-body-width (minibuffer-window))) - (len (+ (length company-prefix) 2)) - ;; Roll to selection. - (candidates (nthcdr company-selection company-candidates)) - (i (if company-show-numbers company-selection 99999)) - msg comp) - - (while candidates - (setq comp (company-strip-prefix (pop candidates)) - len (+ len 2 (length comp))) - (when (< i 10) - ;; Add number. - (setq comp (format "%s (%d)" comp i)) - (cl-incf len 4) - (cl-incf i)) - (if (>= len limit) - (setq candidates nil) - (push (propertize comp 'face 'company-echo) msg))) - - (concat (propertize company-prefix 'face 'company-echo-common) "{" - (mapconcat 'identity (nreverse msg) ", ") - "}"))) + (let ((selection (or company-selection 0))) + (let ((limit (window-body-width (minibuffer-window))) + (len (+ (length company-prefix) 2)) + ;; Roll to selection. + (candidates (nthcdr selection company-candidates)) + (i (if company-show-numbers selection 99999)) + msg comp) + + (while candidates + (setq comp (company-strip-prefix (pop candidates)) + len (+ len 2 (length comp))) + (when (< i 10) + ;; Add number. + (setq comp (format "%s (%d)" comp i)) + (cl-incf len 4) + (cl-incf i)) + (if (>= len limit) + (setq candidates nil) + (push (propertize comp 'face 'company-echo) msg))) + + (concat (propertize company-prefix 'face 'company-echo-common) "{" + (mapconcat 'identity (nreverse msg) ", ") + "}")))) (defun company-echo-hide () (unless (equal company-echo-last-msg "") diff --git a/test/core-tests.el b/test/core-tests.el index a958f5f..05014bc 100644 --- a/test/core-tests.el +++ b/test/core-tests.el @@ -591,3 +591,128 @@ (set-window-buffer nil (current-buffer)) (should (= (company--column) 0)) (should (= (company--row) 2))))) + +(ert-deftest company-set-nil-selection () + (let ((company-selection 1) + (company-candidates-length 10) + (company-selection-changed nil) + (company-frontends nil)) + (company-set-selection nil) + (should (eq company-selection nil)) + (should (eq company-selection-changed t)))) + +(ert-deftest company-update-candidates-nil-selection () + (let ((company-selection nil) + (company-backend #'ignore) + company-candidates + company-candidates-length + company-candidates-cache + company-common + company-selection-default + (company-prefix "ab")) + (company-update-candidates '("abcd" "abcde" "abcdf")) + (should (null company-selection))) + + (let* ((company-selection 1) + (company-backend #'ignore) + (company-candidates '("abc" "abdc" "abe")) + company-candidates-length + company-candidates-cache + company-common + company-selection-default + (company-prefix "ab") + (company-selection-changed t)) + (company-update-candidates '("abcd" "abcde" "abcdf")) + (should (null company-selection)))) + +(ert-deftest company-select-next () + (cl-letf (((symbol-function 'company-manual-begin) (lambda () t)) + (company-selection 1) + (company-candidates-length 10) + (company-selection-default 0) + (company-selection-wrap-around nil) + (company-frontends nil)) + ;; Not wrap + (company-select-next 5) + (should (eq company-selection 6)) + + (company-select-next 5) + (should (eq company-selection 9)) + + (company-select-next -2) + (should (eq company-selection 7)) + + ;; Nil selection + (setq company-selection nil) + (company-select-next 5) + (should (eq company-selection 5)) + + (setq company-selection nil) + (company-select-next -1) + (should (eq company-selection 0)) + + ;; Wrap + (setq company-selection-wrap-around t) + (setq company-selection 7) + (company-select-next 5) + (should (eq company-selection 2)) + + ;; Nil selection + (setq company-selection nil) + (company-select-next 11) + (should (eq company-selection 1)) + + (setq company-selection nil) + (company-select-next -10) + (should (eq company-selection 0)))) + +(ert-deftest company-select-next-default-selection-nil () + (cl-letf (((symbol-function 'company-manual-begin) (lambda () t)) + (company-selection 1) + (company-candidates-length 10) + (company-selection-default nil) + (company-selection-wrap-around nil) + (company-frontends nil)) + ;; Not wrap + (company-select-next 5) + (should (eq company-selection 6)) + + (company-select-next 5) + (should (eq company-selection 9)) + + (company-select-next -10) + (should (eq company-selection nil)) + + ;; Nil selection + (setq company-selection nil) + (company-select-next 5) + (should (eq company-selection 4)) + + (setq company-selection nil) + (company-select-next -1) + (should (eq company-selection nil)) + + ;; Wrap + (setq company-selection-wrap-around t) + (setq company-selection 7) + (company-select-next 5) + (should (eq company-selection 1)) + + (setq company-selection 0) + (company-select-next -1) + (should (eq company-selection nil)) + + (setq company-selection 0) + (company-select-next -11) + (should (eq company-selection 0)) + + ;; Nil selection + (setq company-selection nil) + (company-select-next 11) + (should (eq company-selection nil)) + + (setq company-selection nil) + (company-select-next -10) + (should (eq company-selection 0)))) + +;;; core-tests.el ends here.