]> code.delx.au - gnu-emacs-elpa/blobdiff - company.el
Added nxml-mode support.
[gnu-emacs-elpa] / company.el
index a5e5251e2580b045e9207e42d86882b01e8c43b8..d4fd307ed2b58634f8ebfbaf9fdf482d7c84bc5f 100644 (file)
@@ -1,5 +1,9 @@
 (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)
   "*"
   :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
   (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-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-offset (display-limit)
+  (let ((offset (- company-selection display-limit -1)))
+    (max offset 0)))
+
+(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)))
+
+(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-or-cancel ()
+  (when company-candidates
+    (let ((old-point (- company-point (length company-prefix)))
+          (company-idle-delay t)
+          (company-minimum-prefix-length 0))
+      ;; TODO: Make more efficient.
+      (setq company-candidates nil)
+      (company-begin)
+      (unless (and company-candidates
+                   (equal old-point (- company-point (length company-prefix))))
+        (company-cancel))
+      company-candidates)))
+
 (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))))
+  (or (company-continue-or-cancel)
+      (let ((completion-ignore-case nil) ;; TODO: make this optional
+            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-prefix prefix
+                        company-candidates
+                        (funcall company-backend 'candidates prefix)
+                        company-common
+                        (try-completion prefix company-candidates)
+                        company-selection 0
+                        company-point (point))
+                  (unless (funcall company-backend 'sorted)
+                    (setq company-candidates
+                          (sort company-candidates 'string<)))
+                  (company-call-frontends 'update))
+                (return prefix))
+            (unless (memq backend company-disabled-backends)
+              (push backend company-disabled-backends)
+              (message "Company back-end '%s' could not be initialized"
+                       backend))))
+        (unless (and company-candidates
+                     (not (eq t company-common)))
+          (company-cancel)))))
 
 (defun company-cancel ()
   (setq company-backend 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)))
 
 (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)))
 
 (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))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
           new
           (company-safe-substring old (+ offset (length new)))))
 
-(defun company-modified-substring (beg end lines column)
+(defun company-modified-substring (beg end lines column nl)
   (let ((old (company-buffer-lines beg end))
         new)
     ;; Inject into old lines.
     ;; 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")))
 
 ;; 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
 
+    ;; Scroll to offset.
+    (let ((offset (company-offset company-tooltip-limit)))
+      (setq lines (nthcdr offset lines))
+      (decf selection offset))
+
     (setq lines (company-fill-propertize-lines column lines selection))
 
 
     (move-to-column 0)
-    (move-to-window-line row)
-    (let ((beg (point))
+
+    (let ((nl (< (move-to-window-line row) row))
+          (beg (point))
           (end (save-excursion
                  (move-to-window-line (min (window-height)
                                            (+ row company-tooltip-limit)))
       (setq company-pseudo-tooltip-overlay (make-overlay beg end))
 
       (overlay-put company-pseudo-tooltip-overlay 'before-string
-                   (company-modified-substring beg end lines column))
+                   (company-modified-substring beg end lines column nl))
       (overlay-put company-pseudo-tooltip-overlay 'invisible t)
       (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-hide ()
   (when company-pseudo-tooltip-overlay
     (delete-overlay company-pseudo-tooltip-overlay)
     (setq company-pseudo-tooltip-overlay nil)))
 
+(defun company-pseudo-tooltip-frontend (command)
+  (case command
+    ('pre-command (company-pseudo-tooltip-hide))
+    ('post-command (company-pseudo-tooltip-show-at-point
+                    (- (point) (length company-prefix))))
+    ('hide (company-pseudo-tooltip-hide))))
+
+(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 (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