X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/ca8d5c56f29527250cfa33a31fcabc99f7f93427..089d51a0c3289ac17864724f966e75f7a7fec6ab:/packages/company/company.el diff --git a/packages/company/company.el b/packages/company/company.el index 632b7dc64..4c7e16066 100644 --- a/packages/company/company.el +++ b/packages/company/company.el @@ -1,11 +1,11 @@ ;;; company.el --- Modular text completion framework -*- lexical-binding: t -*- -;; Copyright (C) 2009-2014 Free Software Foundation, Inc. +;; Copyright (C) 2009-2015 Free Software Foundation, Inc. ;; Author: Nikolaj Schumacher ;; Maintainer: Dmitry Gutov ;; URL: http://company-mode.github.io/ -;; Version: 0.8.0 +;; Version: 0.8.9 ;; Keywords: abbrev, convenience, matching ;; Package-Requires: ((emacs "24.1") (cl-lib "0.5")) @@ -58,11 +58,6 @@ ;; enrich gtags with dabbrev-code results (to emulate local variables). ;; To do this, add a list with both back-ends as an element in company-backends. ;; -;; Known Issues: -;; When point is at the very end of the buffer, the pseudo-tooltip appears very -;; wrong, unless company is allowed to temporarily insert a fake newline. -;; This behavior is enabled by `company-end-of-buffer-workaround'. -;; ;;; Change Log: ;; ;; See NEWS.md in the repository. @@ -81,6 +76,19 @@ (add-to-list 'debug-ignored-errors "^Cannot complete at point$") (add-to-list 'debug-ignored-errors "^No other back-end$") +;;; Compatibility +(eval-and-compile + ;; `defvar-local' for Emacs 24.2 and below + (unless (fboundp 'defvar-local) + (defmacro defvar-local (var val &optional docstring) + "Define VAR as a buffer-local variable with default value VAL. +Like `defvar' but additionally marks the variable as being automatically +buffer-local wherever it is set." + (declare (debug defvar) (doc-string 3)) + `(progn + (defvar ,var ,val ,docstring) + (make-variable-buffer-local ',var))))) + (defgroup company nil "Extensible inline text completion mechanism" :group 'abbrev @@ -104,6 +112,10 @@ (t (:background "green"))) "Face used for the selection in the tooltip.") +(defface company-tooltip-search + '((default :inherit company-tooltip-selection)) + "Face used for the search string in the tooltip.") + (defface company-tooltip-mouse '((default :inherit highlight)) "Face used for the tooltip item under the mouse.") @@ -180,23 +192,22 @@ "Face used for the common part of completions in the echo area.") (defun company-frontends-set (variable value) - ;; uniquify - (let ((remainder value)) - (setcdr remainder (delq (car remainder) (cdr remainder)))) - (and (memq 'company-pseudo-tooltip-unless-just-one-frontend value) - (memq 'company-pseudo-tooltip-frontend value) - (error "Pseudo tooltip frontend cannot be used twice")) - (and (memq 'company-preview-if-just-one-frontend value) - (memq 'company-preview-frontend value) - (error "Preview frontend cannot be used twice")) - (and (memq 'company-echo value) - (memq 'company-echo-metadata-frontend value) - (error "Echo area cannot be used twice")) - ;; preview must come last - (dolist (f '(company-preview-if-just-one-frontend company-preview-frontend)) - (when (memq f value) - (setq value (append (delq f value) (list f))))) - (set variable value)) + ;; Uniquify. + (let ((value (delete-dups (copy-sequence value)))) + (and (memq 'company-pseudo-tooltip-unless-just-one-frontend value) + (memq 'company-pseudo-tooltip-frontend value) + (error "Pseudo tooltip frontend cannot be used twice")) + (and (memq 'company-preview-if-just-one-frontend value) + (memq 'company-preview-frontend value) + (error "Preview frontend cannot be used twice")) + (and (memq 'company-echo value) + (memq 'company-echo-metadata-frontend value) + (error "Echo area cannot be used twice")) + ;; Preview must come last. + (dolist (f '(company-preview-if-just-one-frontend company-preview-frontend)) + (when (cdr (memq f value)) + (setq value (append (delq f value) (list f))))) + (set variable value))) (defcustom company-frontends '(company-pseudo-tooltip-unless-just-one-frontend company-preview-if-just-one-frontend @@ -236,7 +247,7 @@ The visualized data is stored in `company-prefix', `company-candidates', (function :tag "custom function" nil)))) (defcustom company-tooltip-limit 10 - "The maximum number of candidates in the tooltip" + "The maximum number of candidates in the tooltip." :type 'integer) (defcustom company-tooltip-minimum 6 @@ -247,7 +258,8 @@ If this many lines are not available, prefer to display the tooltip above." (defcustom company-tooltip-minimum-width 0 "The minimum width of the tooltip's inner area. This doesn't include the margins and the scroll bar." - :type 'integer) + :type 'integer + :package-version '(company . "0.8.0")) (defcustom company-tooltip-margin 1 "Width of margin columns to show around the toolip." @@ -262,7 +274,13 @@ This doesn't include the margins and the scroll bar." (defcustom company-tooltip-align-annotations nil "When non-nil, align annotations to the right tooltip border." - :type 'boolean) + :type 'boolean + :package-version '(company . "0.7.1")) + +(defcustom company-tooltip-flip-when-above nil + "Whether to flip the tooltip when it's above the current line." + :type 'boolean + :package-version '(company . "0.8.1")) (defvar company-safe-backends '((company-abbrev . "Abbrev") @@ -309,6 +327,11 @@ This doesn't include the margins and the scroll bar." company-oddmuse company-files company-dabbrev) "The list of active back-ends (completion engines). +Only one back-end is used at a time. The choice depends on the order of +the items in this list, and on the values they return in response to the +`prefix' command (see below). But a back-end can also be a \"grouped\" +one (see below). + `company-begin-backend' can be used to start a specific back-end, `company-other-backend' will skip to the next matching back-end in the list. @@ -317,20 +340,20 @@ The first argument is the command requested from the back-end. It is one of the following: `prefix': The back-end should return the text to be completed. It must be -text immediately before point. Returning nil passes control to the next -back-end. The function should return `stop' if it should complete but -cannot \(e.g. if it is in the middle of a string\). Instead of a string, -the back-end may return a cons where car is the prefix and cdr is used in -`company-minimum-prefix-length' test. It must be either number or t, and -in the latter case the test automatically succeeds. +text immediately before point. Returning nil from this command passes +control to the next back-end. The function should return `stop' if it +should complete but cannot (e.g. if it is in the middle of a string). +Instead of a string, the back-end may return a cons where car is the prefix +and cdr is used in `company-minimum-prefix-length' test. It must be either +number or t, and in the latter case the test automatically succeeds. `candidates': The second argument is the prefix to be completed. The return value should be a list of candidates that match the prefix. Non-prefix matches are also supported (candidates that don't start with the prefix, but match it in some backend-defined way). Backends that use this -feature must disable cache (return t to `no-cache') and should also respond -to `match'. +feature must disable cache (return t to `no-cache') and might also want to +respond to `match'. Optional commands: @@ -361,10 +384,10 @@ be kept if they have different annotations. For that to work properly, backends should store the related information on candidates using text properties. -`match': The second argument is a completion candidate. Backends that -provide non-prefix completions should return the position of the end of -text in the candidate that matches `prefix'. It will be used when -rendering the popup. +`match': The second argument is a completion candidate. Return the index +after the end of text matching `prefix' within the candidate string. It +will be used when rendering the popup. This command only makes sense for +backends that provide non-prefix completion. `require-match': If this returns t, the user is not allowed to enter anything not offered as a candidate. Use with care! The default value nil @@ -426,12 +449,16 @@ even if the back-end uses the asynchronous calling convention." (put 'company-backends 'safe-local-variable 'company-safe-backends-p) (defcustom company-transformers nil - "Functions to change the list of candidates received from backends, -after sorting and removal of duplicates (if appropriate). -Each function gets called with the return value of the previous one." + "Functions to change the list of candidates received from backends. + +Each function gets called with the return value of the previous one. +The first one gets passed the list of candidates, already sorted and +without duplicates." :type '(choice (const :tag "None" nil) (const :tag "Sort by occurrence" (company-sort-by-occurrence)) + (const :tag "Sort by back-end importance" + (company-sort-by-backend-importance)) (repeat :tag "User defined" (function)))) (defcustom company-completion-started-hook nil @@ -462,7 +489,8 @@ back-end, consider using the `post-completion' command instead." "If enabled, cancel a manually started completion when the prefix gets shorter than both `company-minimum-prefix-length' and the length of the prefix it was started from." - :type 'boolean) + :type 'boolean + :package-version '(company . "0.8.0")) (defcustom company-require-match 'company-explicit-action-p "If enabled, disallow non-matching input. @@ -517,22 +545,29 @@ A character that is part of a valid candidate never triggers auto-completion." (defcustom company-idle-delay .5 "The idle delay in seconds until completion starts automatically. -A value of nil means no idle completion, t means show candidates -immediately when a prefix of `company-minimum-prefix-length' is reached." +The prefix still has to satisfy `company-minimum-prefix-length' before that +happens. The value of nil means no idle completion." :type '(choice (const :tag "never (nil)" nil) - (const :tag "immediate (t)" t) + (const :tag "immediate (0)" 0) (number :tag "seconds"))) -(defcustom company-begin-commands '(self-insert-command org-self-insert-command) +(defcustom company-begin-commands '(self-insert-command + org-self-insert-command + orgtbl-self-insert-command + c-scope-operator + c-electric-colon + c-electric-lt-gt + c-electric-slash) "A list of commands after which idle completion is allowed. -If this is t, it can show completions after any command. See -`company-idle-delay'. +If this is t, it can show completions after any command except a few from a +pre-defined list. See `company-idle-delay'. Alternatively, any command with a non-nil `company-begin' property is treated as if it was on this list." :type '(choice (const :tag "Any command" t) (const :tag "Self insert command" '(self-insert-command)) - (repeat :tag "Commands" function))) + (repeat :tag "Commands" function)) + :package-version '(company . "0.8.4")) (defcustom company-continue-commands '(not save-buffer save-some-buffers save-buffers-kill-terminal @@ -559,10 +594,6 @@ commands in the `company-' namespace, abort completion." :type '(choice (const :tag "off" nil) (const :tag "on" t))) -(defvar company-end-of-buffer-workaround t - "Work around a visualization bug when completing at the end of the buffer. -The work-around consists of adding a newline.") - (defvar company-async-wait 0.03 "Pause between checks to see if the value's been set when turning an asynchronous call into synchronous.") @@ -583,6 +614,8 @@ asynchronous call into synchronous.") (define-key keymap (kbd "M-p") 'company-select-previous) (define-key keymap (kbd "") 'company-select-next-or-abort) (define-key keymap (kbd "") 'company-select-previous-or-abort) + (define-key keymap [remap scroll-up-command] 'company-next-page) + (define-key keymap [remap scroll-down-command] 'company-previous-page) (define-key keymap [down-mouse-1] 'ignore) (define-key keymap [down-mouse-3] 'ignore) (define-key keymap [mouse-1] 'company-complete-mouse) @@ -594,14 +627,13 @@ asynchronous call into synchronous.") (define-key keymap [tab] 'company-complete-common) (define-key keymap (kbd "TAB") 'company-complete-common) (define-key keymap (kbd "") 'company-show-doc-buffer) + (define-key keymap (kbd "C-h") 'company-show-doc-buffer) (define-key keymap "\C-w" 'company-show-location) (define-key keymap "\C-s" 'company-search-candidates) (define-key keymap "\C-\M-s" 'company-filter-candidates) (dotimes (i 10) - (define-key keymap (vector (+ (aref (kbd "M-0") 0) i)) - `(lambda () (interactive) (company-complete-number ,i)))) - - keymap) + (define-key keymap (read-kbd-macro (format "M-%d" i)) 'company-complete-number)) + keymap) "Keymap that is enabled during an active completion.") (defvar company--disabled-backends nil) @@ -632,8 +664,7 @@ asynchronous call into synchronous.") (defvar company-default-lighter " company") -(defvar company-lighter company-default-lighter) -(make-variable-buffer-local 'company-lighter) +(defvar-local company-lighter company-default-lighter) ;;;###autoload (define-minor-mode company-mode @@ -663,6 +694,9 @@ keymap during active completions (`company-active-map'): nil company-lighter company-mode-map (if company-mode (progn + (when (eq company-idle-delay t) + (setq company-idle-delay 0) + (warn "Setting `company-idle-delay' to t is deprecated. Set it to 0 instead.")) (add-hook 'pre-command-hook 'company-pre-command nil t) (add-hook 'post-command-hook 'company-post-command nil t) (mapc 'company-init-backend company-backends)) @@ -707,8 +741,7 @@ means that `company-mode' is always turned on except in `message-mode' buffers." ;;; keymaps ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-my-keymap nil) -(make-variable-buffer-local 'company-my-keymap) +(defvar-local company-my-keymap nil) (defvar company-emulation-alist '((t . nil))) @@ -733,37 +766,35 @@ means that `company-mode' is always turned on except in `message-mode' buffers." ;; Hack: ;; Emacs calculates the active keymaps before reading the event. That means we ;; cannot change the keymap from a timer. So we send a bogus command. -;; XXX: Seems not to be needed anymore in Emacs 24.4 +;; XXX: Even in Emacs 24.4, seems to be needed in the terminal. (defun company-ignore () (interactive) (setq this-command last-command)) -(global-set-key '[31415926] 'company-ignore) +(global-set-key '[company-dummy-event] 'company-ignore) (defun company-input-noop () - (push 31415926 unread-command-events)) + (push 'company-dummy-event unread-command-events)) -(defun company--column (&optional pos) - (save-excursion - (when pos (goto-char pos)) - (save-restriction - (+ (save-excursion - (vertical-motion 0) - (narrow-to-region (point) (point-max)) - (let ((prefix (get-text-property (point) 'line-prefix))) - (if prefix (length prefix) 0))) - (current-column))))) +(defun company--posn-col-row (posn) + (let ((col (car (posn-col-row posn))) + ;; `posn-col-row' doesn't work well with lines of different height. + ;; `posn-actual-col-row' doesn't handle multiple-width characters. + (row (cdr (posn-actual-col-row posn)))) + (when (and header-line-format (version< emacs-version "24.3.93.3")) + ;; http://debbugs.gnu.org/18384 + (cl-decf row)) + (cons (+ col (window-hscroll)) row))) + +(defun company--col-row (&optional pos) + (company--posn-col-row (posn-at-point pos))) (defun company--row (&optional pos) - (save-excursion - (when pos (goto-char pos)) - (count-screen-lines (window-start) - (progn (vertical-motion 0) (point))))) + (cdr (company--col-row pos))) ;;; backends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-backend nil) -(make-variable-buffer-local 'company-backend) +(defvar-local company-backend nil) (defun company-grab (regexp &optional expression limit) (when (looking-back regexp limit) @@ -822,7 +853,7 @@ means that `company-mode' is always turned on except in `message-mode' buffers." res)))) (defun company-call-backend-raw (&rest args) - (condition-case err + (condition-case-unless-debug err (if (functionp company-backend) (apply company-backend args) (apply #'company--multi-backend-adapter company-backend args)) @@ -858,7 +889,8 @@ means that `company-mode' is always turned on except in `message-mode' buffers." (defun company--multi-backend-adapter-candidates (backends prefix) (let ((pairs (cl-loop for backend in (cdr backends) - when (equal (funcall backend 'prefix) + when (equal (company--prefix-str + (funcall backend 'prefix)) prefix) collect (cons (funcall backend 'candidates prefix) (let ((b backend)) @@ -867,7 +899,7 @@ means that `company-mode' is always turned on except in `message-mode' buffers." (lambda (str) (propertize str 'company-backend b)) candidates))))))) - (when (equal (funcall (car backends) 'prefix) prefix) + (when (equal (company--prefix-str (funcall (car backends) 'prefix)) prefix) ;; Small perf optimization: don't tag the candidates received ;; from the first backend in the group. (push (cons (funcall (car backends) 'candidates prefix) @@ -906,54 +938,42 @@ means that `company-mode' is always turned on except in `message-mode' buffers." (setcar cell (funcall mapper res)) (funcall finisher))))))))))))) +(defun company--prefix-str (prefix) + (or (car-safe prefix) prefix)) + ;;; completion mechanism ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-prefix nil) -(make-variable-buffer-local 'company-prefix) +(defvar-local company-prefix nil) -(defvar company-candidates nil) -(make-variable-buffer-local 'company-candidates) +(defvar-local company-candidates nil) -(defvar company-candidates-length nil) -(make-variable-buffer-local 'company-candidates-length) +(defvar-local company-candidates-length nil) -(defvar company-candidates-cache nil) -(make-variable-buffer-local 'company-candidates-cache) +(defvar-local company-candidates-cache nil) -(defvar company-candidates-predicate nil) -(make-variable-buffer-local 'company-candidates-predicate) +(defvar-local company-candidates-predicate nil) -(defvar company-common nil) -(make-variable-buffer-local 'company-common) +(defvar-local company-common nil) -(defvar company-selection 0) -(make-variable-buffer-local 'company-selection) +(defvar-local company-selection 0) -(defvar company-selection-changed nil) -(make-variable-buffer-local 'company-selection-changed) +(defvar-local company-selection-changed nil) -(defvar company--manual-action nil +(defvar-local company--manual-action nil "Non-nil, if manual completion took place.") -(make-variable-buffer-local 'company--manual-action) -(defvar company--manual-prefix nil) -(make-variable-buffer-local 'company--manual-prefix) +(defvar-local company--manual-prefix nil) (defvar company--auto-completion nil "Non-nil when current candidate is being inserted automatically. Controlled by `company-auto-complete'.") -(defvar company--point-max nil) -(make-variable-buffer-local 'company--point-max) +(defvar-local company--point-max nil) -(defvar company-point nil) -(make-variable-buffer-local 'company-point) +(defvar-local company-point nil) (defvar company-timer nil) -(defvar company-added-newline nil) -(make-variable-buffer-local 'company-added-newline) - (defsubst company-strip-prefix (str) (substring str (length company-prefix))) @@ -962,8 +982,9 @@ Controlled by `company-auto-complete'.") ;; XXX: Return value we check here is subject to change. (if (eq (company-call-backend 'ignore-case) 'keep-prefix) (insert (company-strip-prefix candidate)) - (delete-region (- (point) (length company-prefix)) (point)) - (insert candidate))) + (unless (equal company-prefix candidate) + (delete-region (- (point) (length company-prefix)) (point)) + (insert candidate)))) (defmacro company-with-candidate-inserted (candidate &rest body) "Evaluate BODY with CANDIDATE temporarily inserted. @@ -976,7 +997,8 @@ can retrieve meta-data for them." (company--insert-candidate ,candidate) (unwind-protect (progn ,@body) - (delete-region company-point (point))))) + (delete-region company-point (point)) + (set-buffer-modified-p modified-p)))) (defun company-explicit-action-p () "Return whether explicit completion action was taken by the user." @@ -992,15 +1014,12 @@ can retrieve meta-data for them." candidate)) (defun company--should-complete () - (and (not (or buffer-read-only overriding-terminal-local-map + (and (eq company-idle-delay 'now) + (not (or buffer-read-only overriding-terminal-local-map overriding-local-map)) ;; Check if in the middle of entering a key combination. (or (equal (this-command-keys-vector) []) (not (keymapp (key-binding (this-command-keys-vector))))) - (eq company-idle-delay t) - (or (eq t company-begin-commands) - (memq this-command company-begin-commands) - (and (symbolp this-command) (get this-command 'company-begin))) (not (and transient-mark-mode mark-active)))) (defun company--should-continue () @@ -1010,11 +1029,12 @@ can retrieve meta-data for them." (not (memq this-command (cdr company-continue-commands))) (or (memq this-command company-begin-commands) (memq this-command company-continue-commands) - (string-match-p "\\`company-" (symbol-name this-command)))))) + (and (symbolp this-command) + (string-match-p "\\`company-" (symbol-name this-command))))))) (defun company-call-frontends (command) (dolist (frontend company-frontends) - (condition-case err + (condition-case-unless-debug err (funcall frontend command) (error (error "Company: Front-end %s error \"%s\" on command %s" frontend (error-message-string err) command))))) @@ -1025,16 +1045,19 @@ can retrieve meta-data for them." (mod selection company-candidates-length) (max 0 (min (1- company-candidates-length) selection)))) (when (or force-update (not (equal selection company-selection))) + (company--update-group-lighter (nth selection company-candidates)) (setq company-selection selection company-selection-changed t) (company-call-frontends 'update))) -(defun company-apply-predicate (candidates predicate) - (let (new) - (dolist (c candidates) - (when (funcall predicate c) - (push c new))) - (nreverse new))) +(defun company--update-group-lighter (candidate) + (when (listp company-backend) + (let ((backend (or (get-text-property 0 'company-backend candidate) + (car company-backend)))) + (when (and backend (symbolp backend)) + (let ((name (replace-regexp-in-string "company-\\|-company" "" + (symbol-name backend)))) + (setq company-lighter (format " company-<%s>" name))))))) (defun company-update-candidates (candidates) (setq company-candidates-length (length candidates)) @@ -1052,21 +1075,17 @@ can retrieve meta-data for them." company-selection))))) (setq company-selection 0 company-candidates candidates)) - ;; Save in cache: - (push (cons company-prefix company-candidates) company-candidates-cache) ;; Calculate common. (let ((completion-ignore-case (company-call-backend 'ignore-case))) ;; We want to support non-prefix completion, so filtering is the ;; responsibility of each respective backend, not ours. ;; On the other hand, we don't want to replace non-prefix input in - ;; `company-complete-common'. + ;; `company-complete-common', unless there's only one candidate. (setq company-common (if (cdr company-candidates) - (let ((common (try-completion company-prefix company-candidates))) - (if (eq common t) - ;; Mulple equal strings, probably with different - ;; annotations. - company-prefix + (let ((common (try-completion "" company-candidates))) + (when (string-prefix-p company-prefix common + completion-ignore-case) common)) (car company-candidates))))) @@ -1083,11 +1102,14 @@ can retrieve meta-data for them." company-candidates-cache))) (setq candidates (all-completions prefix prev)) (cl-return t))))) - ;; no cache match, call back-end - (setq candidates - (company--process-candidates - (company--fetch-candidates prefix)))) - (setq candidates (company--transform-candidates candidates)) + (progn + ;; No cache match, call the backend. + (setq candidates (company--preprocess-candidates + (company--fetch-candidates prefix))) + ;; Save in cache. + (push (cons prefix candidates) company-candidates-cache))) + ;; Only now apply the predicate and transformers. + (setq candidates (company--postprocess-candidates candidates)) (when candidates (if (or (cdr candidates) (not (eq t (compare-strings (car candidates) nil nil @@ -1112,13 +1134,13 @@ can retrieve meta-data for them." (cdr c) (lambda (candidates) (if (not (and candidates (eq res 'done))) - ;; Fetcher called us right back. + ;; There's no completions to display, + ;; or the fetcher called us back right away. (setq res candidates) (setq company-backend backend company-candidates-cache (list (cons prefix - (company--process-candidates - candidates)))) + (company--preprocess-candidates candidates)))) (company-idle-begin buf win tick pt))))) ;; FIXME: Relying on the fact that the callers ;; will interpret nil as "do nothing" is shaky. @@ -1126,33 +1148,40 @@ can retrieve meta-data for them." (or res (progn (setq res 'done) nil))))) -(defun company--process-candidates (candidates) - (when company-candidates-predicate - (setq candidates - (company-apply-predicate candidates - company-candidates-predicate))) +(defun company--preprocess-candidates (candidates) (unless (company-call-backend 'sorted) (setq candidates (sort candidates 'string<))) (when (company-call-backend 'duplicates) (company--strip-duplicates candidates)) candidates) +(defun company--postprocess-candidates (candidates) + (when (or company-candidates-predicate company-transformers) + (setq candidates (copy-sequence candidates))) + (when company-candidates-predicate + (setq candidates (cl-delete-if-not company-candidates-predicate candidates))) + (company--transform-candidates candidates)) + (defun company--strip-duplicates (candidates) - (let ((c2 candidates)) + (let ((c2 candidates) + (annos 'unk)) (while c2 (setcdr c2 - (let ((str (car c2)) - (anno 'unk)) - (pop c2) + (let ((str (pop c2))) (while (let ((str2 (car c2))) (if (not (equal str str2)) - nil - (when (eq anno 'unk) - (setq anno (company-call-backend - 'annotation str))) - (equal anno - (company-call-backend - 'annotation str2)))) + (progn + (setq annos 'unk) + nil) + (when (eq annos 'unk) + (setq annos (list (company-call-backend + 'annotation str)))) + (let ((anno2 (company-call-backend + 'annotation str2))) + (if (member anno2 annos) + t + (push anno2 annos) + nil)))) (pop c2)) c2))))) @@ -1162,68 +1191,115 @@ can retrieve meta-data for them." (setq c (funcall tr c))) c)) +(defcustom company-occurrence-weight-function + #'company-occurrence-prefer-closest-above + "Function to weigh matches in `company-sort-by-occurrence'. +It's called with three arguments: cursor position, the beginning and the +end of the match." + :type '(choice + (const :tag "First above point, then below point" + company-occurrence-prefer-closest-above) + (const :tag "Prefer closest in any direction" + company-occurrence-prefer-any-closest))) + +(defun company-occurrence-prefer-closest-above (pos match-beg match-end) + "Give priority to the matches above point, then those below point." + (if (< match-beg pos) + (- pos match-end) + (- match-beg (window-start)))) + +(defun company-occurrence-prefer-any-closest (pos _match-beg match-end) + "Give priority to the matches closest to the point." + (abs (- pos match-end))) + (defun company-sort-by-occurrence (candidates) "Sort CANDIDATES according to their occurrences. Searches for each in the currently visible part of the current buffer and -gives priority to the closest ones above point, then closest ones below -point. The rest of the list is appended unchanged. +prioritizes the matches according to `company-occurrence-weight-function'. +The rest of the list is appended unchanged. Keywords and function definition names are ignored." - (let* (occurs + (let* ((w-start (window-start)) + (w-end (window-end)) + (start-point (point)) + occurs (noccurs - (cl-delete-if - (lambda (candidate) - (when (or - (save-excursion - (progn (forward-line 0) - (search-backward candidate (window-start) t))) - (save-excursion - (search-forward candidate (window-end) t))) - (let ((beg (match-beginning 0)) - (end (match-end 0))) - (when (save-excursion - (goto-char end) - (and (not (memq (get-text-property (point) 'face) - '(font-lock-function-name-face - font-lock-keyword-face))) - (let* ((prefix (company-call-backend 'prefix)) - (prefix (or (car-safe prefix) prefix))) - (and (stringp prefix) - (= (length prefix) (- end beg)))))) - (push (cons candidate (if (< beg (point)) - (- (point) end) - (- beg (window-start)))) - occurs) - t)))) - candidates))) + (save-excursion + (cl-delete-if + (lambda (candidate) + (when (catch 'done + (goto-char w-start) + (while (search-forward candidate w-end t) + (when (and (not (eq (point) start-point)) + (save-match-data + (company--occurrence-predicate))) + (throw 'done t)))) + (push + (cons candidate + (funcall company-occurrence-weight-function + start-point + (match-beginning 0) + (match-end 0))) + occurs) + t)) + candidates)))) (nconc (mapcar #'car (sort occurs (lambda (e1 e2) (<= (cdr e1) (cdr e2))))) noccurs))) +(defun company--occurrence-predicate () + (let ((beg (match-beginning 0)) + (end (match-end 0))) + (save-excursion + (goto-char end) + (and (not (memq (get-text-property (1- (point)) 'face) + '(font-lock-function-name-face + font-lock-keyword-face))) + (let ((prefix (company--prefix-str + (company-call-backend 'prefix)))) + (and (stringp prefix) + (= (length prefix) (- end beg)))))))) + +(defun company-sort-by-backend-importance (candidates) + "Sort CANDIDATES as two priority groups. +If `company-backend' is a function, do nothing. If it's a list, move +candidates from back-ends before keyword `:with' to the front. Candidates +from the rest of the back-ends in the group, if any, will be left at the end." + (if (functionp company-backend) + candidates + (let ((low-priority (cdr (memq :with company-backend)))) + (if (null low-priority) + candidates + (sort candidates + (lambda (c1 c2) + (and + (let ((b2 (get-text-property 0 'company-backend c2))) + (and b2 (memq b2 low-priority))) + (let ((b1 (get-text-property 0 'company-backend c1))) + (or (not b1) (not (memq b1 low-priority))))))))))) + (defun company-idle-begin (buf win tick pos) (and (eq buf (current-buffer)) (eq win (selected-window)) (eq tick (buffer-chars-modified-tick)) (eq pos (point)) (when (company-auto-begin) - (when (version< emacs-version "24.3.50") - (company-input-noop)) - (company-post-command)))) + (company-input-noop) + (let ((this-command 'company-idle-begin)) + (company-post-command))))) (defun company-auto-begin () (and company-mode (not company-candidates) - (let ((company-idle-delay t) - (company-begin-commands t)) + (let ((company-idle-delay 'now)) (condition-case-unless-debug err - (company-begin) + (progn + (company--perform) + ;; Return non-nil if active. + company-candidates) (error (message "Company: An error occurred in auto-begin") (message "%s" (error-message-string err)) (company-cancel)) - (quit (company-cancel))))) - (unless company-candidates - (setq company-backend nil)) - ;; Return non-nil if active. - company-candidates) + (quit (company-cancel)))))) (defun company-manual-begin () (interactive) @@ -1231,7 +1307,8 @@ Keywords and function definition names are ignored." (setq company--manual-action t) (unwind-protect (let ((company-minimum-prefix-length 0)) - (company-auto-begin)) + (or company-candidates + (company-auto-begin))) (unless company-candidates (setq company--manual-action nil)))) @@ -1280,7 +1357,7 @@ Keywords and function definition names are ignored." company-point) company-prefix))) -(defun company--continue-failed () +(defun company--continue-failed (new-prefix) (let ((input (buffer-substring-no-properties (point) company-point))) (cond ((company-auto-complete-p input) @@ -1290,19 +1367,26 @@ Keywords and function definition names are ignored." (let ((company--auto-completion t)) (company-complete-selection)) nil)) + ((and (or (not (company-require-match-p)) + ;; Don't require match if the new prefix + ;; doesn't continue the old one, and the latter was a match. + (not (stringp new-prefix)) + (<= (length new-prefix) (length company-prefix))) + (member company-prefix company-candidates)) + ;; Last input was a success, + ;; but we're treating it as an abort + input anyway, + ;; like the `unique' case below. + (company-cancel 'non-unique)) ((company-require-match-p) - ;; wrong incremental input, but required match + ;; Wrong incremental input, but required match. (delete-char (- (length input))) (ding) (message "Matching input is required") company-candidates) - ((equal company-prefix (car company-candidates)) - ;; last input was actually success - (company-cancel company-prefix)) (t (company-cancel))))) (defun company--good-prefix-p (prefix) - (and (stringp (or (car-safe prefix) prefix)) ;excludes 'stop + (and (stringp (company--prefix-str prefix)) ;excludes 'stop (or (eq (cdr-safe prefix) t) (let ((len (or (cdr-safe prefix) (length prefix)))) (if company--manual-prefix @@ -1318,14 +1402,17 @@ Keywords and function definition names are ignored." (setq company-candidates-cache nil)) (let* ((new-prefix (company-call-backend 'prefix)) (c (when (and (company--good-prefix-p new-prefix) - (setq new-prefix (or (car-safe new-prefix) new-prefix)) + (setq new-prefix (company--prefix-str new-prefix)) (= (- (point) (length new-prefix)) (- company-point (length company-prefix)))) (company-calculate-candidates new-prefix)))) (cond ((eq c t) ;; t means complete/unique. - (company-cancel new-prefix)) + ;; Handle it like completion was aborted, to differentiate from user + ;; calling one of Company's commands to insert the candidate, + ;; not to trigger template expansion, etc. + (company-cancel 'unique)) ((consp c) ;; incremental match (setq company-prefix new-prefix) @@ -1333,7 +1420,7 @@ Keywords and function definition names are ignored." c) ((not (company--incremental-p)) (company-cancel)) - (t (company--continue-failed))))) + (t (company--continue-failed new-prefix))))) (defun company--begin-new () (let (prefix c) @@ -1352,33 +1439,29 @@ Keywords and function definition names are ignored." (company--multi-backend-adapter backend 'prefix))) (when prefix (when (company--good-prefix-p prefix) - (setq prefix (or (car-safe prefix) prefix) + (setq company-prefix (company--prefix-str prefix) company-backend backend - c (company-calculate-candidates prefix)) + c (company-calculate-candidates company-prefix)) ;; t means complete/unique. We don't start, so no hooks. (if (not (consp c)) (when company--manual-action (message "No completion found")) - (setq company-prefix prefix) (when company--manual-action (setq company--manual-prefix prefix)) - (when (symbolp backend) - (setq company-lighter (concat " " (symbol-name backend)))) + (if (symbolp backend) + (setq company-lighter (concat " " (symbol-name backend))) + (company--update-group-lighter (car c))) (company-update-candidates c) (run-hook-with-args 'company-completion-started-hook (company-explicit-action-p)) (company-call-frontends 'show))) (cl-return c))))) -(defun company-begin () +(defun company--perform () (or (and company-candidates (company--continue)) (and (company--should-complete) (company--begin-new))) - (when company-candidates - (let ((modified (buffer-modified-p))) - (when (and company-end-of-buffer-workaround (eobp)) - (save-excursion (insert "\n")) - (setq company-added-newline - (or modified (buffer-chars-modified-tick))))) + (if (not company-candidates) + (setq company-backend nil) (setq company-point (point) company--point-max (point-max)) (company-ensure-emulation-alist) @@ -1386,62 +1469,50 @@ Keywords and function definition names are ignored." (company-call-frontends 'update))) (defun company-cancel (&optional result) - (and company-added-newline - (> (point-max) (point-min)) - (let ((tick (buffer-chars-modified-tick))) - (delete-region (1- (point-max)) (point-max)) - (equal tick company-added-newline)) - ;; Only set unmodified when tick remained the same since insert, - ;; and the buffer wasn't modified before. - (set-buffer-modified-p nil)) - (when company-prefix - (if (stringp result) - (progn - (company-call-backend 'pre-completion result) - (run-hook-with-args 'company-completion-finished-hook result) - (company-call-backend 'post-completion result)) - (run-hook-with-args 'company-completion-cancelled-hook result))) - (setq company-added-newline nil - company-backend nil - company-prefix nil - company-candidates nil - company-candidates-length nil - company-candidates-cache nil - company-candidates-predicate nil - company-common nil - company-selection 0 - company-selection-changed nil - company--manual-action nil - company--manual-prefix nil - company-lighter company-default-lighter - company--point-max nil - company-point nil) - (when company-timer - (cancel-timer company-timer)) - (company-search-mode 0) - (company-call-frontends 'hide) - (company-enable-overriding-keymap nil) + (unwind-protect + (when company-prefix + (if (stringp result) + (progn + (company-call-backend 'pre-completion result) + (run-hook-with-args 'company-completion-finished-hook result) + (company-call-backend 'post-completion result)) + (run-hook-with-args 'company-completion-cancelled-hook result))) + (setq company-backend nil + company-prefix nil + company-candidates nil + company-candidates-length nil + company-candidates-cache nil + company-candidates-predicate nil + company-common nil + company-selection 0 + company-selection-changed nil + company--manual-action nil + company--manual-prefix nil + company-lighter company-default-lighter + company--point-max nil + company-point nil) + (when company-timer + (cancel-timer company-timer)) + (company-search-mode 0) + (company-call-frontends 'hide) + (company-enable-overriding-keymap nil)) ;; Make return value explicit. nil) (defun company-abort () (interactive) - (company-cancel t) - ;; Don't start again, unless started manually. - (setq company-point (point))) + (company-cancel 'abort)) (defun company-finish (result) (company--insert-candidate result) - (company-cancel result) - ;; Don't start again, unless started manually. - (setq company-point (point))) + (company-cancel result)) (defsubst company-keep (command) (and (symbolp command) (get command 'company-keep))) (defun company-pre-command () (unless (company-keep this-command) - (condition-case err + (condition-case-unless-debug err (when company-candidates (company-call-frontends 'pre-command) (unless (company--should-continue) @@ -1455,17 +1526,23 @@ Keywords and function definition names are ignored." (company-uninstall-map)) (defun company-post-command () + (when (null this-command) + ;; Happens when the user presses `C-g' while inside + ;; `flyspell-post-command-hook', for example. + ;; Or any other `post-command-hook' function that can call `sit-for', + ;; or any quittable timer function. + (company-abort) + (setq this-command 'company-abort)) (unless (company-keep this-command) - (condition-case err + (condition-case-unless-debug err (progn (unless (equal (point) company-point) - (company-begin)) + (let (company-idle-delay) ; Against misbehavior while debugging. + (company--perform))) (if company-candidates (company-call-frontends 'post-command) (and (numberp company-idle-delay) - (or (eq t company-begin-commands) - (memq this-command company-begin-commands)) - (not (equal (point) company-point)) + (company--should-begin) (setq company-timer (run-with-timer company-idle-delay nil 'company-idle-begin @@ -1476,21 +1553,34 @@ Keywords and function definition names are ignored." (company-cancel)))) (company-install-map)) +(defvar company--begin-inhibit-commands '(company-abort + company-complete-mouse + company-complete + company-complete-common + company-complete-selection + company-complete-number) + "List of commands after which idle completion is (still) disabled when +`company-begin-commands' is t.") + +(defun company--should-begin () + (if (eq t company-begin-commands) + (not (memq this-command company--begin-inhibit-commands)) + (or + (memq this-command company-begin-commands) + (and (symbolp this-command) (get this-command 'company-begin))))) + ;;; search ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-search-string nil) -(make-variable-buffer-local 'company-search-string) +(defvar-local company-search-string "") -(defvar company-search-lighter " Search: \"\"") -(make-variable-buffer-local 'company-search-lighter) +(defvar-local company-search-lighter " Search: \"\"") -(defvar company-search-old-map nil) -(make-variable-buffer-local 'company-search-old-map) +(defvar-local company-search-filtering nil + "Non-nil to filter the completion candidates by the search string") -(defvar company-search-old-selection 0) -(make-variable-buffer-local 'company-search-old-selection) +(defvar-local company--search-old-selection 0) -(defun company-search (text lines) +(defun company--search (text lines) (let ((quoted (regexp-quote text)) (i 0)) (cl-dolist (line lines) @@ -1498,24 +1588,51 @@ Keywords and function definition names are ignored." (cl-return i)) (cl-incf i)))) +(defun company-search-keypad () + (interactive) + (let* ((name (symbol-name last-command-event)) + (last-command-event (aref name (1- (length name))))) + (company-search-printing-char))) + (defun company-search-printing-char () (interactive) - (company-search-assert-enabled) - (setq company-search-string - (concat (or company-search-string "") (string last-command-event)) - company-search-lighter (concat " Search: \"" company-search-string - "\"")) - (let ((pos (company-search company-search-string - (nthcdr company-selection company-candidates)))) + (company--search-assert-enabled) + (let ((ss (concat company-search-string (string last-command-event)))) + (when company-search-filtering + (company--search-update-predicate ss)) + (company--search-update-string ss))) + +(defun company--search-update-predicate (&optional ss) + (let* ((company-candidates-predicate + (and (not (string= ss "")) + company-search-filtering + (lambda (candidate) (string-match ss candidate)))) + (cc (company-calculate-candidates company-prefix))) + (unless cc (error "No match")) + (company-update-candidates cc))) + +(defun company--search-update-string (new) + (let* ((pos (company--search new (nthcdr company-selection company-candidates)))) (if (null pos) (ding) + (setq company-search-string new + company-search-lighter (format " %s: \"%s\"" + (if company-search-filtering + "Filter" + "Search") + new)) (company-set-selection (+ company-selection pos) t)))) +(defun company--search-assert-input () + (company--search-assert-enabled) + (when (string= company-search-string "") + (error "Empty search string"))) + (defun company-search-repeat-forward () "Repeat the incremental search in completion candidates forward." (interactive) - (company-search-assert-enabled) - (let ((pos (company-search company-search-string + (company--search-assert-input) + (let ((pos (company--search company-search-string (cdr (nthcdr company-selection company-candidates))))) (if (null pos) @@ -1525,8 +1642,8 @@ Keywords and function definition names are ignored." (defun company-search-repeat-backward () "Repeat the incremental search in completion candidates backwards." (interactive) - (company-search-assert-enabled) - (let ((pos (company-search company-search-string + (company--search-assert-input) + (let ((pos (company--search company-search-string (nthcdr (- company-candidates-length company-selection) (reverse company-candidates))))) @@ -1534,47 +1651,38 @@ Keywords and function definition names are ignored." (ding) (company-set-selection (- company-selection pos 1) t)))) -(defun company-create-match-predicate () - (setq company-candidates-predicate - `(lambda (candidate) - ,(if company-candidates-predicate - `(and (string-match ,company-search-string candidate) - (funcall ,company-candidates-predicate - candidate)) - `(string-match ,company-search-string candidate)))) - (company-update-candidates - (company-apply-predicate company-candidates company-candidates-predicate)) - ;; Invalidate cache. - (setq company-candidates-cache (cons company-prefix company-candidates))) - -(defun company-filter-printing-char () +(defun company-search-toggle-filtering () + "Toggle `company-search-filtering'." (interactive) - (company-search-assert-enabled) - (company-search-printing-char) - (company-create-match-predicate) - (company-call-frontends 'update)) - -(defun company-search-kill-others () - "Limit the completion candidates to the ones matching the search string." - (interactive) - (company-search-assert-enabled) - (company-create-match-predicate) - (company-search-mode 0) - (company-call-frontends 'update)) + (company--search-assert-enabled) + (setq company-search-filtering (not company-search-filtering)) + (let ((ss company-search-string)) + (company--search-update-predicate ss) + (company--search-update-string ss))) (defun company-search-abort () "Abort searching the completion candidates." (interactive) - (company-search-assert-enabled) - (company-set-selection company-search-old-selection t) - (company-search-mode 0)) + (company--search-assert-enabled) + (company-search-mode 0) + (company-set-selection company--search-old-selection t)) (defun company-search-other-char () (interactive) - (company-search-assert-enabled) + (company--search-assert-enabled) (company-search-mode 0) (company--unread-last-input)) +(defun company-search-delete-char () + (interactive) + (company--search-assert-enabled) + (if (string= company-search-string "") + (ding) + (let ((ss (substring company-search-string 0 -1))) + (when company-search-filtering + (company--search-update-predicate ss)) + (company--search-update-string ss)))) + (defvar company-search-map (let ((i 0) (keymap (make-keymap))) @@ -1595,17 +1703,22 @@ Keywords and function definition names are ignored." (while (< i 256) (define-key keymap (vector i) 'company-search-printing-char) (cl-incf i)) + (dotimes (i 10) + (define-key keymap (read (format "[kp-%s]" i)) 'company-search-keypad)) (let ((meta-map (make-sparse-keymap))) (define-key keymap (char-to-string meta-prefix-char) meta-map) (define-key keymap [escape] meta-map)) (define-key keymap (vector meta-prefix-char t) 'company-search-other-char) + (define-key keymap (kbd "M-n") 'company-select-next) + (define-key keymap (kbd "M-p") 'company-select-previous) (define-key keymap "\e\e\e" 'company-search-other-char) - (define-key keymap [escape escape escape] 'company-search-other-char) - + (define-key keymap [escape escape escape] 'company-search-other-char) + (define-key keymap (kbd "DEL") 'company-search-delete-char) + (define-key keymap [backspace] 'company-search-delete-char) (define-key keymap "\C-g" 'company-search-abort) (define-key keymap "\C-s" 'company-search-repeat-forward) (define-key keymap "\C-r" 'company-search-repeat-backward) - (define-key keymap "\C-o" 'company-search-kill-others) + (define-key keymap "\C-o" 'company-search-toggle-filtering) keymap) "Keymap used for incrementally searching the completion candidates.") @@ -1617,15 +1730,19 @@ Don't start this directly, use `company-search-candidates' or (if company-search-mode (if (company-manual-begin) (progn - (setq company-search-old-selection company-selection) + (setq company--search-old-selection company-selection) (company-call-frontends 'update)) (setq company-search-mode nil)) (kill-local-variable 'company-search-string) (kill-local-variable 'company-search-lighter) - (kill-local-variable 'company-search-old-selection) + (kill-local-variable 'company-search-filtering) + (kill-local-variable 'company--search-old-selection) + (when company-backend + (company--search-update-predicate "") + (company-call-frontends 'update)) (company-enable-overriding-keymap company-active-map))) -(defun company-search-assert-enabled () +(defun company--search-assert-enabled () (company-assert-enabled) (unless company-search-mode (company-uninstall-map) @@ -1638,11 +1755,12 @@ Don't start this directly, use `company-search-candidates' or - `company-search-repeat-forward' (\\[company-search-repeat-forward]) - `company-search-repeat-backward' (\\[company-search-repeat-backward]) - `company-search-abort' (\\[company-search-abort]) +- `company-search-delete-char' (\\[company-search-delete-char]) Regular characters are appended to the search string. -The command `company-search-kill-others' (\\[company-search-kill-others]) -uses the search string to limit the completion candidates." +The command `company-search-toggle-filtering' (\\[company-search-toggle-filtering]) +uses the search string to filter the completion candidates." (interactive) (company-search-mode 1) (company-enable-overriding-keymap company-search-map)) @@ -1658,10 +1776,10 @@ uses the search string to limit the completion candidates." (defun company-filter-candidates () "Start filtering the completion candidates incrementally. This works the same way as `company-search-candidates' immediately -followed by `company-search-kill-others' after each input." +followed by `company-search-toggle-filtering'." (interactive) (company-search-mode 1) - (company-enable-overriding-keymap company-filter-map)) + (setq company-search-filtering t)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1695,6 +1813,20 @@ and invoke the normal binding." (company-abort) (company--unread-last-input))) +(defun company-next-page () + "Select the candidate one page further." + (interactive) + (when (company-manual-begin) + (company-set-selection (+ company-selection + company-tooltip-limit)))) + +(defun company-previous-page () + "Select the candidate one page earlier." + (interactive) + (when (company-manual-begin) + (company-set-selection (- company-selection + company-tooltip-limit)))) + (defvar company-pseudo-tooltip-overlay) (defvar company-tooltip-offset) @@ -1714,14 +1846,7 @@ and invoke the normal binding." (>= evt-row (+ row height))))))) (defun company--event-col-row (event) - (let* ((col-row (posn-actual-col-row (event-start event))) - (col (car col-row)) - (row (cdr col-row))) - (cl-incf col (window-hscroll)) - (and header-line-format - (version< "24" emacs-version) - (cl-decf row)) - (cons col row))) + (company--posn-col-row (event-start event))) (defun company-select-mouse (event) "Select the candidate picked by the mouse." @@ -1772,6 +1897,16 @@ and invoke the normal binding." (when company-common (company--insert-candidate company-common))))) +(defun company-complete-common-or-cycle () + "Insert the common part of all candidates, or select the next one." + (interactive) + (when (company-manual-begin) + (let ((tick (buffer-chars-modified-tick))) + (call-interactively 'company-complete-common) + (when (eq tick (buffer-chars-modified-tick)) + (let ((company-selection-wrap-around t)) + (call-interactively 'company-select-next)))))) + (defun company-complete () "Insert the common part of all candidates or the current selection. The first time this is called, the common part is inserted, the second @@ -1786,14 +1921,26 @@ inserted." (setq this-command 'company-complete-common)))) (defun company-complete-number (n) - "Insert the Nth candidate. + "Insert the Nth candidate visible in the tooltip. To show the number next to the candidates in some back-ends, enable -`company-show-numbers'." +`company-show-numbers'. When called interactively, uses the last typed +character, stripping the modifiers. That character must be a digit." + (interactive + (list (let* ((type (event-basic-type last-command-event)) + (char (if (characterp type) + ;; Number on the main row. + type + ;; Keypad number, if bound directly. + (car (last (string-to-list (symbol-name type)))))) + (n (- char ?0))) + (if (zerop n) 10 n)))) (when (company-manual-begin) - (and (< n 1) (> n company-candidates-length) + (and (or (< n 1) (> n (- company-candidates-length + company-tooltip-offset))) (error "No candidate number %d" n)) (cl-decf n) - (company-finish (nth n company-candidates)))) + (company-finish (nth (+ n company-tooltip-offset) + company-candidates)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1828,8 +1975,7 @@ To show the number next to the candidates in some back-ends, enable ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-last-metadata nil) -(make-variable-buffer-local 'company-last-metadata) +(defvar-local company-last-metadata nil) (defun company-fetch-metadata () (let ((selected (nth company-selection company-candidates))) @@ -1904,8 +2050,7 @@ To show the number next to the candidates in some back-ends, enable ;;; package functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-callback nil) -(make-variable-buffer-local 'company-callback) +(defvar-local company-callback nil) (defun company-remove-callback (&optional ignored) (remove-hook 'company-completion-finished-hook company-callback t) @@ -1943,13 +2088,13 @@ Example: \(company-begin-with '\(\"foo\" \"foobar\" \"foobarbaz\"\)\)" (company-begin-backend (lambda (command &optional arg &rest ignored) (pcase command - (`prefix - (when (equal (point) (marker-position begin-marker)) - (buffer-substring (- (point) (or prefix-length 0)) (point)))) - (`candidates - (all-completions arg candidates)) - (`require-match - require-match))) + (`prefix + (when (equal (point) (marker-position begin-marker)) + (buffer-substring (- (point) (or prefix-length 0)) (point)))) + (`candidates + (all-completions arg candidates)) + (`require-match + require-match))) callback))) (defun company-version (&optional show-version) @@ -1966,11 +2111,9 @@ If SHOW-VERSION is non-nil, show the version in the echo area." ;;; pseudo-tooltip ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-pseudo-tooltip-overlay nil) -(make-variable-buffer-local 'company-pseudo-tooltip-overlay) +(defvar-local company-pseudo-tooltip-overlay nil) -(defvar company-tooltip-offset 0) -(make-variable-buffer-local 'company-tooltip-offset) +(defvar-local company-tooltip-offset 0) (defun company-tooltip--lines-update-offset (selection num-lines limit) (cl-decf limit 2) @@ -2019,8 +2162,10 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (defun company-fill-propertize (value annotation width selected left right) (let* ((margin (length left)) - (common (+ (or (company-call-backend 'match value) - (length company-common)) margin)) + (common (or (company-call-backend 'match value) + (if company-common + (string-width company-common) + 0))) (ann-ralign company-tooltip-align-annotations) (ann-truncate (< width (+ (length value) (length annotation) @@ -2044,6 +2189,7 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (- width (length annotation))) annotation)) right))) + (setq common (+ (min common width) margin)) (setq width (+ width margin (length right))) (add-text-properties 0 width '(face company-tooltip @@ -2059,17 +2205,13 @@ If SHOW-VERSION is non-nil, show the version in the echo area." mouse-face company-tooltip-mouse) line)) (when selected - (if (and company-search-string + (if (and (not (string= company-search-string "")) (string-match (regexp-quote company-search-string) value (length company-prefix))) (let ((beg (+ margin (match-beginning 0))) (end (+ margin (match-end 0)))) - (add-text-properties beg end '(face company-tooltip-selection) - line) - (when (< beg common) - (add-text-properties beg common - '(face company-tooltip-common-selection) - line))) + (add-text-properties beg end '(face company-tooltip-search) + line)) (add-text-properties 0 width '(face company-tooltip-selection mouse-face company-tooltip-selection) line) @@ -2079,12 +2221,32 @@ If SHOW-VERSION is non-nil, show the version in the echo area." line))) line)) +(defun company--clean-string (str) + (replace-regexp-in-string + "\\([^[:graph:] ]\\)\\|\\(\ufeff\\)\\|[[:multibyte:]]" + (lambda (match) + (cond + ((match-beginning 1) + ;; FIXME: Better char for 'non-printable'? + ;; We shouldn't get any of these, but sometimes we might. + "\u2017") + ((match-beginning 2) + ;; Zero-width non-breakable space. + "") + ((> (string-width match) 1) + (concat + (make-string (1- (string-width match)) ?\ufeff) + match)) + (t match))) + str)) + ;;; replace (defun company-buffer-lines (beg end) (goto-char beg) - (let (lines) - (while (and (= 1 (vertical-motion 1)) + (let (lines lines-moved) + (while (and (not (eobp)) ; http://debbugs.gnu.org/19553 + (> (setq lines-moved (vertical-motion 1)) 0) (<= (point) end)) (let ((bound (min end (1- (point))))) ;; A visual line can contain several physical lines (e.g. with outline's @@ -2095,6 +2257,10 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (re-search-forward "$" bound 'move) (point))) lines)) + ;; One physical line can be displayed as several visual ones as well: + ;; add empty strings to the list, to even the count. + (dotimes (_ (1- lines-moved)) + (push "" lines)) (setq beg (point))) (unless (eq beg end) (push (buffer-substring beg end) lines)) @@ -2110,9 +2276,36 @@ If SHOW-VERSION is non-nil, show the version in the echo area." limit (length lst))) +(defsubst company--window-height () + (if (fboundp 'window-screen-lines) + (floor (window-screen-lines)) + (window-body-height))) + +(defun company--window-width () + (let ((ww (window-body-width))) + ;; Account for the line continuation column. + (when (zerop (cadr (window-fringes))) + (cl-decf ww)) + (unless (or (display-graphic-p) + (version< "24.3.1" emacs-version)) + ;; Emacs 24.3 and earlier included margins + ;; in window-width when in TTY. + (cl-decf ww + (let ((margins (window-margins))) + (+ (or (car margins) 0) + (or (cdr margins) 0))))) + (when (and word-wrap + (version< emacs-version "24.4.51.5")) + ;; http://debbugs.gnu.org/18384 + (cl-decf ww)) + ww)) + (defun company--replacement-string (lines old column nl &optional align-top) (cl-decf column company-tooltip-margin) + (when (and align-top company-tooltip-flip-when-above) + (setq lines (reverse lines))) + (let ((width (length (car lines))) (remaining-cols (- (+ (company--window-width) (window-hscroll)) column))) @@ -2131,17 +2324,20 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (while old (push (company-modify-line (pop old) (company--offset-line (pop lines) offset) - column) new)) + column) + new)) ;; Append whole new lines. (while lines (push (concat (company-space-string column) (company--offset-line (pop lines) offset)) new)) - (let ((str (concat (when nl "\n") + (let ((str (concat (when nl " ") + "\n" (mapconcat 'identity (nreverse new) "\n") "\n"))) (font-lock-append-text-property 0 (length str) 'face 'default str) + (when nl (put-text-property 0 1 'cursor t str)) str))) (defun company--offset-line (line offset) @@ -2151,7 +2347,6 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (defun company--create-lines (selection limit) (let ((len company-candidates-length) - (numbered 99999) (window-width (company--window-width)) lines width @@ -2193,9 +2388,12 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (dotimes (_ len) (let* ((value (pop lines-copy)) (annotation (company-call-backend 'annotation value))) - (when (and annotation company-tooltip-align-annotations) - ;; `lisp-completion-at-point' adds a space. - (setq annotation (comment-string-strip annotation t nil))) + (setq value (company--clean-string (company-reformat value))) + (when annotation + (when company-tooltip-align-annotations + ;; `lisp-completion-at-point' adds a space. + (setq annotation (comment-string-strip annotation t nil))) + (setq annotation (company--clean-string annotation))) (push (cons value annotation) items) (setq width (max (+ (length value) (if (and annotation company-tooltip-align-annotations) @@ -2205,22 +2403,19 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (setq width (min window-width (max company-tooltip-minimum-width - (if (and company-show-numbers - (< company-tooltip-offset 10)) + (if company-show-numbers (+ 2 width) width)))) - ;; number can make tooltip too long - (when company-show-numbers - (setq numbered company-tooltip-offset)) - - (let ((items (nreverse items)) new) + (let ((items (nreverse items)) + (numbered (if company-show-numbers 0 99999)) + new) (when previous (push (company--scrollpos-line previous width) new)) (dotimes (i len) (let* ((item (pop items)) - (str (company-reformat (car item))) + (str (car item)) (annotation (cdr item)) (right (company-space-string company-tooltip-margin)) (width width)) @@ -2260,31 +2455,15 @@ If SHOW-VERSION is non-nil, show the version in the echo area." (propertize (concat (company-space-string company-tooltip-margin) (company-safe-substring text 0 width) (company-space-string company-tooltip-margin)) - 'face 'company-tooltip)) + 'face 'company-tooltip)) ;; show -(defsubst company--window-inner-height () - (let ((edges (window-inside-edges))) - (- (nth 3 edges) (nth 1 edges)))) - -(defsubst company--window-width () - (- (window-width) - (cond - ((display-graphic-p) 0) - ;; Account for the line continuation column. - ((version< "24.3.1" emacs-version) 1) - ;; Emacs 24.3 and earlier included margins - ;; in window-width when in TTY. - (t (1+ (let ((margins (window-margins))) - (+ (or (car margins) 0) - (or (cdr margins) 0)))))))) - (defun company--pseudo-tooltip-height () "Calculate the appropriate tooltip height. Returns a negative number if the tooltip should be displayed above point." (let* ((lines (company--row)) - (below (- (company--window-inner-height) 1 lines))) + (below (- (company--window-height) 1 lines))) (if (and (< below (min company-tooltip-minimum company-candidates-length)) (> lines below)) (- (max 3 (min company-tooltip-limit lines))) @@ -2306,7 +2485,7 @@ Returns a negative number if the tooltip should be displayed above point." (end (save-excursion (move-to-window-line (+ row (abs height))) (point))) - (ov (make-overlay beg end)) + (ov (make-overlay (if nl beg (1- beg)) end nil t)) (args (list (mapcar 'company-plainify (company-buffer-lines beg end)) column nl above))) @@ -2315,23 +2494,27 @@ Returns a negative number if the tooltip should be displayed above point." (overlay-put ov 'company-replacement-args args) (let ((lines (company--create-lines selection (abs height)))) - (overlay-put ov 'company-after + (overlay-put ov 'company-display (apply 'company--replacement-string lines args)) (overlay-put ov 'company-width (string-width (car lines)))) (overlay-put ov 'company-column column) (overlay-put ov 'company-height height))))) -(defun company-pseudo-tooltip-show-at-point (pos) - (let ((row (company--row pos)) - (col (company--column pos))) - (company-pseudo-tooltip-show (1+ row) col company-selection))) +(defun company-pseudo-tooltip-show-at-point (pos column-offset) + (let* ((col-row (company--col-row pos)) + (col (- (car col-row) column-offset))) + (when (< col 0) (setq col 0)) + (company-pseudo-tooltip-show (1+ (cdr col-row)) col company-selection))) (defun company-pseudo-tooltip-edit (selection) - (let ((height (overlay-get company-pseudo-tooltip-overlay 'company-height))) - (overlay-put company-pseudo-tooltip-overlay 'company-after + (let* ((height (overlay-get company-pseudo-tooltip-overlay 'company-height)) + (lines (company--create-lines selection (abs height)))) + (overlay-put company-pseudo-tooltip-overlay 'company-width + (string-width (car lines))) + (overlay-put company-pseudo-tooltip-overlay 'company-display (apply 'company--replacement-string - (company--create-lines selection (abs height)) + lines (overlay-get company-pseudo-tooltip-overlay 'company-replacement-args))))) @@ -2343,44 +2526,51 @@ Returns a negative number if the tooltip should be displayed above point." (defun company-pseudo-tooltip-hide-temporarily () (when (overlayp company-pseudo-tooltip-overlay) (overlay-put company-pseudo-tooltip-overlay 'invisible nil) - (overlay-put company-pseudo-tooltip-overlay 'line-prefix nil) (overlay-put company-pseudo-tooltip-overlay 'after-string nil))) (defun company-pseudo-tooltip-unhide () (when company-pseudo-tooltip-overlay - (overlay-put company-pseudo-tooltip-overlay 'invisible t) - ;; Beat outline's folding overlays, at least. - (overlay-put company-pseudo-tooltip-overlay 'priority 1) - ;; No (extra) prefix for the first line. - (overlay-put company-pseudo-tooltip-overlay 'line-prefix "") - (overlay-put company-pseudo-tooltip-overlay 'after-string - (overlay-get company-pseudo-tooltip-overlay 'company-after)) - (overlay-put company-pseudo-tooltip-overlay 'window (selected-window)))) + (let* ((ov company-pseudo-tooltip-overlay) + (disp (overlay-get ov 'company-display))) + ;; Beat outline's folding overlays, at least. + (overlay-put ov 'priority 1) + ;; `display' could be better (http://debbugs.gnu.org/18285), but it + ;; doesn't work when the overlay is empty, which is what happens at eob. + ;; It also seems to interact badly with `cursor'. + ;; We deal with priorities by having the overlay start before the newline. + (overlay-put ov 'after-string disp) + (overlay-put ov 'invisible t) + (overlay-put ov 'window (selected-window))))) (defun company-pseudo-tooltip-guard () - (buffer-substring-no-properties - (point) (overlay-start company-pseudo-tooltip-overlay))) + (cons + (save-excursion (beginning-of-visual-line)) + (let ((ov company-pseudo-tooltip-overlay) + (overhang (save-excursion (end-of-visual-line) + (- (line-end-position) (point))))) + (when (>= (overlay-get ov 'company-height) 0) + (cons + (buffer-substring-no-properties (point) (overlay-start ov)) + (when (>= overhang 0) overhang)))))) (defun company-pseudo-tooltip-frontend (command) "`company-mode' front-end similar to a tooltip but based on overlays." (cl-case command (pre-command (company-pseudo-tooltip-hide-temporarily)) (post-command - (let ((old-height (if (overlayp company-pseudo-tooltip-overlay) - (overlay-get company-pseudo-tooltip-overlay - 'company-height) - 0)) - (new-height (company--pseudo-tooltip-height))) - (unless (and (>= (* old-height new-height) 0) - (>= (abs old-height) (abs new-height)) - (equal (company-pseudo-tooltip-guard) - (overlay-get company-pseudo-tooltip-overlay - 'company-guard))) - ;; Redraw needed. - (company-pseudo-tooltip-show-at-point (- (point) - (length company-prefix))) - (overlay-put company-pseudo-tooltip-overlay - 'company-guard (company-pseudo-tooltip-guard)))) + (unless (when (overlayp company-pseudo-tooltip-overlay) + (let* ((ov company-pseudo-tooltip-overlay) + (old-height (overlay-get ov 'company-height)) + (new-height (company--pseudo-tooltip-height))) + (and + (>= (* old-height new-height) 0) + (>= (abs old-height) (abs new-height)) + (equal (company-pseudo-tooltip-guard) + (overlay-get ov 'company-guard))))) + ;; Redraw needed. + (company-pseudo-tooltip-show-at-point (point) (length company-prefix)) + (overlay-put company-pseudo-tooltip-overlay + 'company-guard (company-pseudo-tooltip-guard))) (company-pseudo-tooltip-unhide)) (hide (company-pseudo-tooltip-hide) (setq company-tooltip-offset 0)) @@ -2395,14 +2585,11 @@ Returns a negative number if the tooltip should be displayed above point." ;;; overlay ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-preview-overlay nil) -(make-variable-buffer-local 'company-preview-overlay) +(defvar-local company-preview-overlay nil) (defun company-preview-show-at-point (pos) (company-preview-hide) - (setq company-preview-overlay (make-overlay pos (1+ pos))) - (let ((completion (nth company-selection company-candidates))) (setq completion (propertize completion 'face 'company-preview)) (add-text-properties 0 (length company-common) @@ -2420,12 +2607,26 @@ Returns a negative number if the tooltip should be displayed above point." (and (equal pos (point)) (not (equal completion "")) - (add-text-properties 0 1 '(cursor t) completion)) - - (overlay-put company-preview-overlay 'display - (concat completion (unless (eq pos (point-max)) - (buffer-substring pos (1+ pos))))) - (overlay-put company-preview-overlay 'window (selected-window)))) + (add-text-properties 0 1 '(cursor 1) completion)) + + (let* ((beg pos) + (pto company-pseudo-tooltip-overlay) + (ptf-workaround (and + pto + (char-before pos) + (eq pos (overlay-start pto))))) + ;; Try to accomodate for the pseudo-tooltip overlay, + ;; which may start at the same position if it's at eol. + (when ptf-workaround + (cl-decf beg) + (setq completion (concat (buffer-substring beg pos) completion))) + + (setq company-preview-overlay (make-overlay beg pos)) + + (let ((ov company-preview-overlay)) + (overlay-put ov (if ptf-workaround 'display 'after-string) + completion) + (overlay-put ov 'window (selected-window)))))) (defun company-preview-hide () (when company-preview-overlay @@ -2448,13 +2649,12 @@ Returns a negative number if the tooltip should be displayed above point." (defun company--show-inline-p () (and (not (cdr company-candidates)) company-common - (string-prefix-p company-prefix company-common - (company-call-backend 'ignore-case)))) + (or (eq (company-call-backend 'ignore-case) 'keep-prefix) + (string-prefix-p company-prefix company-common)))) ;;; echo ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar company-echo-last-msg nil) -(make-variable-buffer-local 'company-echo-last-msg) +(defvar-local company-echo-last-msg nil) (defvar company-echo-timer nil) @@ -2479,7 +2679,7 @@ Returns a negative number if the tooltip should be displayed above point." (defun company-echo-format () - (let ((limit (window-width (minibuffer-window))) + (let ((limit (window-body-width (minibuffer-window))) (len -1) ;; Roll to selection. (candidates (nthcdr company-selection company-candidates)) @@ -2509,7 +2709,7 @@ Returns a negative number if the tooltip should be displayed above point." (defun company-echo-strip-common-format () - (let ((limit (window-width (minibuffer-window))) + (let ((limit (window-body-width (minibuffer-window))) (len (+ (length company-prefix) 2)) ;; Roll to selection. (candidates (nthcdr company-selection company-candidates))