(eval-when-compile (require 'cl))
+(add-to-list 'debug-ignored-errors
+ "^Pseudo tooltip frontend cannot be used twice$")
+(add-to-list 'debug-ignored-errors "^Preview frontend cannot be used twice$")
+
(defgroup company nil
""
:group 'abbrev
:group 'company
:type 'integer)
-(defcustom company-backends '(company-elisp-completion)
+(defface company-preview
+ '((t :background "blue4"
+ :foreground "wheat"))
+ "*"
+ :group 'company)
+
+(defface company-preview-common
+ '((t :inherit company-preview
+ :foreground "red"))
+ "*"
+ :group 'company)
+
+(defface company-echo nil
+ "*"
+ :group 'company)
+
+(defface company-echo-common
+ '((((background dark)) (:foreground "firebrick1"))
+ (((background light)) (:background "firebrick4")))
+ "*"
+ :group 'company)
+
+(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"))
+ ;; 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))
+
+(defcustom company-frontends '(company-echo-frontend
+ company-pseudo-tooltip-unless-just-one-frontend
+ company-preview-if-just-one-frontend)
+ "*"
+ :set 'company-frontends-set
+ :group 'company
+ :type '(repeat (choice (const :tag "echo" company-echo-frontend)
+ (const :tag "pseudo tooltip"
+ company-pseudo-tooltip-frontend)
+ (const :tag "pseudo tooltip, multiple only"
+ company-pseudo-tooltip-unless-just-one-frontend)
+ (const :tag "preview" company-preview-frontend)
+ (const :tag "preview, unique only"
+ company-preview-if-just-one-frontend)
+ (function :tag "custom function" nil))))
+
+(defcustom company-backends '(company-elisp company-nxml company-css
+ company-semantic company-oddmuse company-ispell)
"*"
:group 'company
:type '(repeat (function :tag "function" nil)))
+(defcustom company-minimum-prefix-length 3
+ "*"
+ :group 'company
+ :type '(integer :tag "prefix length"))
+
+(defvar company-timer nil)
+
+(defun company-timer-set (variable value)
+ (set variable value)
+ (when company-timer (cancel-timer company-timer))
+ (when (numberp value)
+ (setq company-timer (run-with-idle-timer value t 'company-idle-begin))))
+
+(defcustom company-idle-delay .7
+ "*"
+ :set 'company-timer-set
+ :group 'company
+ :type '(choice (const :tag "never (nil)" nil)
+ (const :tag "immediate (t)" t)
+ (number :tag "seconds")))
+
;;; mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar company-mode-map
(define-key keymap (kbd "M-n") 'company-select-next)
(define-key keymap (kbd "M-p") 'company-select-previous)
(define-key keymap (kbd "M-<return>") 'company-complete-selection)
- (define-key keymap "\t" 'company-complete-common)
+ (define-key keymap "\t" 'company-complete)
keymap))
;;;###autoload
(if company-mode
(progn
(add-hook 'pre-command-hook 'company-pre-command nil t)
- (add-hook 'post-command-hook 'company-post-command nil t))
+ (add-hook 'post-command-hook 'company-post-command nil t)
+ (company-timer-set 'company-idle-delay
+ company-idle-delay))
(remove-hook 'pre-command-hook 'company-pre-command t)
(remove-hook 'post-command-hook 'company-post-command t)
- (company-cancel)))
+ (company-cancel)
+ (kill-local-variable 'company-point)))
;;; backends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(let ((pos (syntax-ppss)))
(or (nth 3 pos) (nth 4 pos) (nth 7 pos))))
-;;; elisp
-
-(defvar company-lisp-symbol-regexp
- "\\_<\\(\\sw\\|\\s_\\)+\\_>\\=")
-
-(defun company-grab-lisp-symbol ()
- (let ((prefix (or (company-grab company-lisp-symbol-regexp) "")))
- (unless (and (company-in-string-or-comment (- (point) (length prefix)))
- (/= (char-before (- (point) (length prefix))) ?`))
- prefix)))
-
-(defun company-elisp-completion (command &optional arg &rest ignored)
- (case command
- ('prefix (and (eq major-mode 'emacs-lisp-mode)
- (company-grab-lisp-symbol)))
- ('candidates (let ((completion-ignore-case nil))
- (all-completions arg obarray
- (lambda (symbol) (or (boundp symbol)
- (fboundp symbol))))))))
-
;;; completion mechanism ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar company-backend nil)
(defvar company-candidates nil)
(make-variable-buffer-local 'company-candidates)
+(defvar company-candidates-cache nil)
+(make-variable-buffer-local 'company-candidates-cache)
+
(defvar company-common nil)
(make-variable-buffer-local 'company-common)
(defvar company-point nil)
(make-variable-buffer-local 'company-point)
+(defvar company-disabled-backends nil)
+
(defsubst company-strip-prefix (str)
(substring str (length company-prefix)))
+(defsubst company-reformat (candidate)
+ ;; company-ispell needs this, because the results are always lower-case
+ ;; It's mory efficient to fix it only when they are displayed.
+ (concat company-prefix (substring candidate (length company-prefix))))
+
+(defsubst company-should-complete (prefix)
+ (and (eq company-idle-delay t)
+ (>= (length prefix) company-minimum-prefix-length)))
+
+(defsubst company-call-frontends (command)
+ (dolist (frontend company-frontends)
+ (funcall frontend command)))
+
+(defsubst company-calculate-candidates (prefix)
+ (or (setq company-candidates (cdr (assoc prefix company-candidates-cache)))
+ (let ((len (length prefix))
+ (completion-ignore-case (funcall company-backend 'ignore-case))
+ prev)
+ (dotimes (i len)
+ (when (setq prev (cdr (assoc (substring prefix 0 (- len i))
+ company-candidates-cache)))
+ (setq company-candidates (all-completions prefix prev))
+ (return t))))
+ (progn
+ (setq company-candidates (funcall company-backend 'candidates prefix))
+ (unless (funcall company-backend 'sorted)
+ (setq company-candidates (sort company-candidates 'string<)))))
+ (unless (assoc prefix company-candidates-cache)
+ (push (cons prefix company-candidates) company-candidates-cache))
+ (setq company-selection 0
+ company-prefix prefix)
+ (let ((completion-ignore-case (funcall company-backend 'ignore-case)))
+ (setq company-common (try-completion company-prefix company-candidates)))
+ (when (eq company-common t)
+ (setq company-candidates nil))
+ company-candidates)
+
+(defun company-idle-begin ()
+ (and company-mode
+ (not company-candidates)
+ (not (equal (point) company-point))
+ (let ((company-idle-delay t))
+ (company-begin)
+ (company-post-command))))
+
+(defun company-manual-begin ()
+ (and company-mode
+ (not company-candidates)
+ (let ((company-idle-delay t)
+ (company-minimum-prefix-length 0))
+ (company-begin)))
+ ;; Return non-nil if active.
+ company-candidates)
+
+(defun company-continue ()
+ (when company-candidates
+ (let ((new-prefix (funcall company-backend 'prefix)))
+ (unless (and (= (- (point) (length new-prefix))
+ (- company-point (length company-prefix)))
+ (or (equal company-prefix new-prefix)
+ (company-calculate-candidates new-prefix)))
+ (setq company-candidates nil)))))
+
(defun company-begin ()
- (let ((completion-ignore-case nil) ;; TODO: make this optional
- prefix)
- (dolist (backend company-backends)
- (when (setq prefix (funcall backend 'prefix))
- (setq company-backend backend
- company-prefix prefix
- company-candidates
- (funcall company-backend 'candidates prefix)
- company-common (try-completion prefix company-candidates)
- company-selection 0
- company-point (point))
- (return prefix)))
- (unless (and company-candidates
- (not (eq t company-common)))
- (company-cancel))))
+ (company-continue)
+ (unless company-candidates
+ (let (prefix)
+ (dolist (backend company-backends)
+ (unless (fboundp backend)
+ (ignore-errors (require backend nil t)))
+ (if (fboundp backend)
+ (when (setq prefix (funcall backend 'prefix))
+ (when (company-should-complete prefix)
+ (setq company-backend backend)
+ (company-calculate-candidates prefix))
+ (return prefix))
+ (unless (memq backend company-disabled-backends)
+ (push backend company-disabled-backends)
+ (message "Company back-end '%s' could not be initialized"
+ backend))))))
+ (if company-candidates
+ (progn
+ (setq company-point (point))
+ (company-call-frontends 'update))
+ (company-cancel)))
(defun company-cancel ()
(setq company-backend nil
company-prefix nil
company-candidates nil
+ company-candidates-cache nil
company-common nil
company-selection 0
company-selection-changed nil
company-point nil)
- (company-pseudo-tooltip-hide))
+ (company-call-frontends 'hide))
+
+(defun company-abort ()
+ (company-cancel)
+ ;; Don't start again, unless started manually.
+ (setq company-point (point)))
(defun company-pre-command ()
- (company-pseudo-tooltip-hide))
+ (when company-candidates
+ (company-call-frontends 'pre-command)))
(defun company-post-command ()
(unless (equal (point) company-point)
(company-begin))
(when company-candidates
- (company-pseudo-tooltip-show-at-point (- (point) (length company-prefix))
- company-candidates
- company-selection)))
+ (company-call-frontends 'post-command)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun company-select-next ()
(interactive)
- (setq company-selection (min (1- (length company-candidates))
- (1+ company-selection))
- company-selection-changed t))
+ (when (company-manual-begin)
+ (setq company-selection (min (1- (length company-candidates))
+ (1+ company-selection))
+ company-selection-changed t))
+ (company-call-frontends 'update))
(defun company-select-previous ()
(interactive)
- (setq company-selection (max 0 (1- company-selection))
- company-selection-changed t))
+ (when (company-manual-begin)
+ (setq company-selection (max 0 (1- company-selection))
+ company-selection-changed t))
+ (company-call-frontends 'update))
(defun company-complete-selection ()
(interactive)
- (insert (company-strip-prefix (nth company-selection company-candidates))))
+ (when (company-manual-begin)
+ (insert (company-strip-prefix (nth company-selection company-candidates)))
+ (company-abort)))
(defun company-complete-common ()
(interactive)
- (insert (company-strip-prefix company-common)))
+ (when (company-manual-begin)
+ (insert (company-strip-prefix company-common))))
+
+(defun company-complete ()
+ (interactive)
+ (when (company-manual-begin)
+ (if (or company-selection-changed
+ (eq last-command 'company-complete-common))
+ (call-interactively 'company-complete-selection)
+ (call-interactively 'company-complete-common)
+ (setq this-command 'company-complete-common))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar company-pseudo-tooltip-overlay nil)
(make-variable-buffer-local 'company-pseudo-tooltip-overlay)
+(defvar company-tooltip-offset 0)
+(make-variable-buffer-local 'company-tooltip-offset)
+
+(defun company-pseudo-tooltip-update-offset (selection num-lines limit)
+
+ (decf limit 2)
+ (setq company-tooltip-offset
+ (max (min selection company-tooltip-offset)
+ (- selection -1 limit)))
+
+ (when (<= company-tooltip-offset 1)
+ (incf limit)
+ (setq company-tooltip-offset 0))
+
+ (when (>= company-tooltip-offset (- num-lines limit 1))
+ (incf limit)
+ (when (= selection (1- num-lines))
+ (setq company-tooltip-offset (max (1- company-tooltip-offset) 0))))
+
+ limit)
+
;;; propertize
(defun company-fill-propertize (line width selected)
'company-tooltip-common)) line)
line)
-(defun company-fill-propertize-lines (column lines selection)
- (let ((width 0)
- (lines-copy lines)
- (len (min company-tooltip-limit (length lines)))
- new)
- (dotimes (i len)
- (setq width (max (length (pop lines-copy)) width)))
- (setq width (min width (- (window-width) column)))
- (dotimes (i len)
- (push (company-fill-propertize (pop lines) width (equal i selection))
- new))
- (nreverse new)))
-
;;; replace
(defun company-buffer-lines (beg end)
new
(company-safe-substring old (+ offset (length new)))))
-(defun company-modified-substring (beg end lines column)
- (let ((old (company-buffer-lines beg end))
- new)
+(defun company-replacement-string (old lines column nl)
+ (let (new)
;; Inject into old lines.
(while old
(push (company-modify-line (pop old) (pop lines) column) new))
;; Append whole new lines.
(while lines
(push (company-modify-line "" (pop lines) column) new))
- (concat (mapconcat 'identity (nreverse new) "\n")
+ (concat (when nl "\n")
+ (mapconcat 'identity (nreverse new) "\n")
"\n")))
+(defun company-create-lines (column lines selection)
+
+ (let ((limit (max company-tooltip-limit 3))
+ (len (length lines))
+ width
+ lines-copy
+ previous
+ remainder
+ new)
+
+ ;; Scroll to offset.
+ (setq limit (company-pseudo-tooltip-update-offset selection len limit))
+
+ (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))))
+
+ (decf selection company-tooltip-offset)
+ (setq width (min (length previous) (length remainder))
+ lines (nthcdr company-tooltip-offset lines)
+ len (min limit (length lines))
+ lines-copy lines)
+
+ (dotimes (i len)
+ (setq width (max (length (pop lines-copy)) width)))
+ (setq width (min width (- (window-width) column)))
+
+ (when previous
+ (push (propertize (company-safe-substring previous 0 width)
+ 'face 'company-tooltip)
+ new))
+
+ (dotimes (i len)
+ (push (company-fill-propertize (company-reformat (pop lines))
+ width (equal i selection))
+ new))
+
+ (when remainder
+ (push (propertize (company-safe-substring remainder 0 width)
+ 'face 'company-tooltip)
+ new))
+
+ (setq lines (nreverse new))))
+
;; show
-(defun company-pseudo-tooltip-show (row column lines &optional selection)
+(defun company-pseudo-tooltip-show (row column lines selection)
(company-pseudo-tooltip-hide)
(unless lines (error "No text provided"))
(save-excursion
- (setq lines (company-fill-propertize-lines column lines selection))
-
-
(move-to-column 0)
- (move-to-window-line row)
- (let ((beg (point))
- (end (save-excursion
- (move-to-window-line (min (window-height)
- (+ row company-tooltip-limit)))
- (point)))
- str)
+
+ (let* ((lines (company-create-lines column lines selection))
+ (nl (< (move-to-window-line row) row))
+ (beg (point))
+ (end (save-excursion
+ (move-to-window-line (min (window-height)
+ (+ row company-tooltip-limit)))
+ (point)))
+ (old-string (company-buffer-lines beg end))
+ str)
(setq company-pseudo-tooltip-overlay (make-overlay beg end))
- (overlay-put company-pseudo-tooltip-overlay 'before-string
- (company-modified-substring beg end lines column))
- (overlay-put company-pseudo-tooltip-overlay 'invisible t)
+ (overlay-put company-pseudo-tooltip-overlay 'company-old old-string)
+ (overlay-put company-pseudo-tooltip-overlay 'company-column column)
+ (overlay-put company-pseudo-tooltip-overlay 'company-nl nl)
+ (overlay-put company-pseudo-tooltip-overlay 'company-before
+ (company-replacement-string old-string lines column nl))
+
(overlay-put company-pseudo-tooltip-overlay 'window (selected-window)))))
-(defun company-pseudo-tooltip-show-at-point (pos text &optional selection)
+(defun company-pseudo-tooltip-show-at-point (pos)
(let ((col-row (posn-col-row (posn-at-point pos))))
- (company-pseudo-tooltip-show (1+ (cdr col-row))
- (car col-row) text selection)))
+ (company-pseudo-tooltip-show (1+ (cdr col-row)) (car col-row)
+ company-candidates company-selection)))
+
+(defun company-pseudo-tooltip-edit (lines selection)
+ (let* ((old-string (overlay-get company-pseudo-tooltip-overlay 'company-old))
+ (column (overlay-get company-pseudo-tooltip-overlay 'company-column))
+ (nl (overlay-get company-pseudo-tooltip-overlay 'company-nl))
+ (lines (company-create-lines column lines selection)))
+ (overlay-put company-pseudo-tooltip-overlay 'company-before
+ (company-replacement-string old-string lines column nl))))
(defun company-pseudo-tooltip-hide ()
(when company-pseudo-tooltip-overlay
(delete-overlay company-pseudo-tooltip-overlay)
(setq company-pseudo-tooltip-overlay nil)))
+(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 'before-string nil)))
+
+(defun company-pseudo-tooltip-unhide ()
+ (when company-pseudo-tooltip-overlay
+ (overlay-put company-pseudo-tooltip-overlay 'invisible t)
+ (overlay-put company-pseudo-tooltip-overlay 'before-string
+ (overlay-get company-pseudo-tooltip-overlay 'company-before))))
+
+(defun company-pseudo-tooltip-frontend (command)
+ (case command
+ ('pre-command (company-pseudo-tooltip-hide-temporarily))
+ ('post-command
+ (unless (overlayp company-pseudo-tooltip-overlay)
+ (company-pseudo-tooltip-show-at-point (- (point)
+ (length company-prefix))))
+ (company-pseudo-tooltip-unhide))
+ ('hide (company-pseudo-tooltip-hide)
+ (setq company-tooltip-offset 0))
+ ('update (when (overlayp company-pseudo-tooltip-overlay)
+ (company-pseudo-tooltip-edit company-candidates
+ company-selection)))))
+
+(defun company-pseudo-tooltip-unless-just-one-frontend (command)
+ (unless (and (eq command 'post-command)
+ (not (cdr company-candidates)))
+ (company-pseudo-tooltip-frontend command)))
+
+;;; overlay ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar company-preview-overlay nil)
+(make-variable-buffer-local 'company-preview-overlay)
+
+(defun company-preview-show-at-point (pos)
+ (company-preview-hide)
+
+ (setq company-preview-overlay (make-overlay pos pos))
+
+ (let ((completion (company-strip-prefix (nth company-selection
+ company-candidates))))
+ (and (equal pos (point))
+ (not (equal completion ""))
+ (add-text-properties 0 1 '(cursor t) completion))
+
+ (setq completion (propertize completion 'face 'company-preview))
+ (add-text-properties 0 (- (length company-common) (length company-prefix))
+ '(face company-preview-common) completion)
+
+ (overlay-put company-preview-overlay 'after-string completion)
+ (overlay-put company-preview-overlay 'window (selected-window))))
+
+(defun company-preview-hide ()
+ (when company-preview-overlay
+ (delete-overlay company-preview-overlay)
+ (setq company-preview-overlay nil)))
+
+(defun company-preview-frontend (command)
+ (case command
+ ('pre-command (company-preview-hide))
+ ('post-command (company-preview-show-at-point (point)))
+ ('hide (company-preview-hide))))
+
+(defun company-preview-if-just-one-frontend (command)
+ (unless (and (eq command 'post-command)
+ (cdr company-candidates))
+ (company-preview-frontend command)))
+
+;;; echo ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar company-echo-last-msg nil)
+(make-variable-buffer-local 'company-echo-last-msg)
+
+(defun company-echo-refresh ()
+ (let ((message-log-max nil))
+ (if company-echo-last-msg
+ (message "%s" company-echo-last-msg)
+ (message ""))))
+
+(defun company-echo-show (candidates)
+
+ ;; Roll to selection.
+ (setq candidates (nthcdr company-selection candidates))
+
+ (let ((limit (window-width (minibuffer-window)))
+ (len -1)
+ comp msg)
+ (while candidates
+ (setq comp (company-reformat (pop candidates))
+ len (+ len 1 (length comp)))
+ (if (>= len limit)
+ (setq candidates nil)
+ (setq comp (propertize comp 'face 'company-echo))
+ (add-text-properties 0 (length company-common)
+ '(face company-echo-common) comp)
+ (push comp msg)))
+
+ (setq company-echo-last-msg (mapconcat 'identity (nreverse msg) " "))
+ (company-echo-refresh)))
+
+(defun company-echo-frontend (command)
+ (case command
+ ('pre-command (company-echo-refresh))
+ ('post-command (company-echo-show company-candidates))
+ ('hide (setq company-echo-last-msg nil))))
+
(provide 'company)
;;; company.el ends here