]> rtime.felk.cvut.cz Git - sojka/company-mode.git/blobdiff - company.el
Allowed scrolling of the documentation window.
[sojka/company-mode.git] / company.el
index e3867b84fb3175239e9147c94703c4083cd4a68b..91cdfc1e97f2af91342a50cf4005daa684f2295c 100644 (file)
 
 (defcustom company-backends '(company-elisp company-nxml company-css
                               company-semantic company-oddmuse
-                              company-dabbrev)
+                              company-files company-dabbrev)
   "*"
   :group 'company
   :type '(repeat (function :tag "function" nil)))
 
 ;;; mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
-(defvar company-current-map (make-sparse-keymap))
-
 (defvar company-mode-map (make-sparse-keymap))
 
 (defvar company-active-map
   (let ((keymap (make-sparse-keymap)))
-    (set-keymap-parent keymap company-mode-map)
     (define-key keymap (kbd "M-n") 'company-select-next)
     (define-key keymap (kbd "M-p") 'company-select-previous)
     (define-key keymap "\C-m" 'company-complete-selection)
     (define-key keymap "\t" 'company-complete-common)
     (define-key keymap (kbd "<f1>") 'company-show-doc-buffer)
+    (define-key keymap "\C-s" 'company-search-candidates)
     keymap))
 
 ;;;###autoload
 (define-minor-mode company-mode
   ""
-  nil " comp" nil
+  nil " comp" company-mode-map
   (if company-mode
       (progn
-        (add-to-list 'minor-mode-overriding-map-alist
-                     (cons 'company-mode company-current-map))
         (add-hook 'pre-command-hook 'company-pre-command nil t)
         (add-hook 'post-command-hook 'company-post-command nil t)
         (company-timer-set 'company-idle-delay
     (company-cancel)
     (kill-local-variable 'company-point)))
 
+;;; keymaps ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar company-overriding-keymap-bound nil)
+(make-variable-buffer-local 'company-overriding-keymap-bound)
+
+(defvar company-old-keymap nil)
+(make-variable-buffer-local 'company-old-keymap)
+
+(defvar company-my-keymap nil)
+(make-variable-buffer-local 'company-my-keymap)
+
+(defsubst company-enable-overriding-keymap (keymap)
+  (setq company-my-keymap keymap)
+  (when company-overriding-keymap-bound
+    (company-uninstall-map)))
+
+(defun company-install-map ()
+  (unless (or company-overriding-keymap-bound
+              (null company-my-keymap))
+    (setq company-old-keymap overriding-terminal-local-map
+          overriding-terminal-local-map company-my-keymap
+          company-overriding-keymap-bound t)))
+
+(defun company-uninstall-map ()
+  (when (and company-overriding-keymap-bound
+             (eq overriding-terminal-local-map company-my-keymap))
+    (setq overriding-terminal-local-map company-old-keymap
+          company-overriding-keymap-bound nil)))
+
+;; 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.
+(defun company-ignore ()
+  (interactive))
+
+(global-set-key '[31415926] 'company-ignore)
+
+(defun company-input-noop ()
+  (push 31415926 unread-command-events))
+
 ;;; backends ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (defun company-grab (regexp &optional expression)
 (defvar company-candidates-cache nil)
 (make-variable-buffer-local 'company-candidates-cache)
 
+(defvar company-candidates-predicate nil)
+(make-variable-buffer-local 'company-candidates-predicate)
+
 (defvar company-common nil)
 (make-variable-buffer-local 'company-common)
 
   (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)
+(defsubst company-set-selection (selection &optional force-update)
+  (setq selection (max 0 (min (1- (length company-candidates)) selection)))
+  (when (or force-update (not (equal selection company-selection)))
+    (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-candidates (candidates)
+  (if (> company-selection 0)
+      ;; Try to restore the selection
+      (let ((selected (nth company-selection company-candidates)))
+        (setq company-selection 0
+              company-candidates candidates)
+        (when selected
+          (while (and candidates (string< (pop candidates) selected))
+            (incf company-selection))
+          (unless candidates
+            ;; Make sure selection isn't out of bounds.
+            (setq company-selection (min (1- (length company-candidates))
+                                         company-selection)))))
+    (setq company-selection 0
+          company-candidates candidates))
+  ;; Calculate common.
   (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))
+    (setq company-candidates nil)))
+
+(defsubst company-calculate-candidates (prefix)
+  (setq company-prefix prefix)
+  (company-update-candidates
+   (or (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)))
+             (return (all-completions prefix prev)))))
+       (let ((candidates (funcall company-backend 'candidates prefix)))
+         (and company-candidates-predicate
+              (setq candidates
+                    (company-apply-predicate candidates
+                                             company-candidates-predicate)))
+         (unless (funcall company-backend 'sorted)
+           (setq candidates (sort candidates 'string<)))
+         candidates)))
+  (unless (assoc prefix company-candidates-cache)
+    (push (cons prefix company-candidates) company-candidates-cache))
   company-candidates)
 
 (defun company-idle-begin ()
        (not (equal (point) company-point))
        (let ((company-idle-delay t))
          (company-begin)
-         (company-post-command))))
+         (when company-candidates
+           (company-input-noop)
+           (company-post-command)))))
 
 (defun company-manual-begin ()
   (and company-mode
 
 (defun company-continue ()
   (when company-candidates
+    (when (funcall company-backend 'no-cache)
+      ;; Don't complete existing candidates, fetch new ones.
+      (setq company-candidates-cache nil))
     (let ((new-prefix (funcall company-backend 'prefix)))
       (unless (and (= (- (point) (length new-prefix))
                       (- company-point (length company-prefix)))
         (setq company-candidates nil)))))
 
 (defun company-begin ()
-  (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 (or buffer-read-only overriding-terminal-local-map overriding-local-map)
+      ;; Don't complete in these cases.
+      (setq company-candidates nil)
+    (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))
-        (set-keymap-parent company-current-map company-active-map)
+        (company-enable-overriding-keymap company-active-map)
         (company-call-frontends 'update))
     (company-cancel)))
 
         company-prefix nil
         company-candidates nil
         company-candidates-cache nil
+        company-candidates-predicate nil
         company-common nil
         company-selection 0
         company-selection-changed nil
         company-point nil)
+  (company-search-mode 0)
   (company-call-frontends 'hide)
-  (set-keymap-parent company-current-map company-mode-map))
+  (company-enable-overriding-keymap nil))
 
 (defun company-abort ()
   (company-cancel)
           (company-call-frontends 'pre-command))
       (error (message "Company: An error occurred in pre-command")
              (message "%s" (error-message-string err))
-             (company-cancel)))))
+             (company-cancel))))
+  (company-uninstall-map))
 
 (defun company-post-command ()
   (unless (eq this-command 'company-show-doc-buffer)
             (company-call-frontends 'post-command)))
       (error (message "Company: An error occurred in post-command")
              (message "%s" (error-message-string err))
-             (company-cancel)))))
+             (company-cancel))))
+  (company-install-map))
+
+;;; search ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defvar company-search-string nil)
+(make-variable-buffer-local 'company-search-string)
+
+(defvar company-search-lighter " Search: \"\"")
+(make-variable-buffer-local 'company-search-lighter)
+
+(defvar company-search-old-map nil)
+(make-variable-buffer-local 'company-search-old-map)
+
+(defvar company-search-old-selection 0)
+(make-variable-buffer-local 'company-search-old-selection)
+
+(defun company-search (text lines)
+  (let ((quoted (regexp-quote text))
+        (i 0))
+    (dolist (line lines)
+      (when (string-match quoted line (length company-prefix))
+        (return i))
+      (incf i))))
+
+(defun company-search-printing-char ()
+  (interactive)
+  (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))))
+    (if (null pos)
+        (ding)
+      (company-set-selection (+ company-selection pos) t))))
+
+(defun company-search-repeat-forward ()
+  (interactive)
+  (let ((pos (company-search company-search-string
+                              (cdr (nthcdr company-selection
+                                           company-candidates)))))
+    (if (null pos)
+        (ding)
+      (company-set-selection (+ company-selection pos 1) t))))
+
+(defun company-search-repeat-backward ()
+  (interactive)
+  (let ((pos (company-search company-search-string
+                              (nthcdr (- (length company-candidates)
+                                         company-selection)
+                                      (reverse company-candidates)))))
+    (if (null pos)
+        (ding)
+      (company-set-selection (- company-selection pos 1) t))))
+
+(defun company-search-kill-others ()
+  (interactive)
+  (let ((predicate `(lambda (candidate)
+                      (string-match ,company-search-string candidate))))
+    (setq company-candidates-predicate predicate)
+    (company-update-candidates (company-apply-predicate company-candidates
+                                                        predicate))
+    (company-search-mode 0)
+    (company-call-frontends 'update)))
+
+(defun company-search-abort ()
+  (interactive)
+  (company-set-selection company-search-old-selection t)
+  (company-search-mode 0))
+
+(defun company-search-other-char ()
+  (interactive)
+  (company-search-mode 0)
+  (when last-input-event
+    (clear-this-command-keys t)
+    (setq unread-command-events (list last-input-event))))
+
+(defvar company-search-map
+  (let ((i 0)
+        (keymap (make-keymap)))
+    (set-char-table-range (nth 1 keymap) (cons #x100 (max-char))
+                          'company-search-printing-char)
+    (define-key keymap [t] 'company-search-other-char)
+    (while (< i ?\s)
+      (define-key keymap (make-string 1 i) 'company-search-other-char)
+      (incf i))
+    (while (< i 256)
+      (define-key keymap (vector i) 'company-search-printing-char)
+      (incf i))
+    (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 "\e\e\e" 'company-search-other-char)
+    (define-key keymap  [escape escape escape] 'company-search-other-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)
+    keymap))
+
+(define-minor-mode company-search-mode
+  ""
+  nil company-search-lighter nil
+  (if company-search-mode
+      (if (company-manual-begin)
+          (progn
+            (setq company-search-old-selection company-selection)
+            (company-enable-overriding-keymap company-search-map)
+            (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)
+    (company-enable-overriding-keymap company-active-map)))
+
+(defun company-search-candidates ()
+  (interactive)
+  (company-search-mode 1))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (defun company-select-next ()
   (interactive)
   (when (company-manual-begin)
-    (setq company-selection (min (1- (length company-candidates))
-                                 (1+ company-selection))
-          company-selection-changed t))
-  (company-call-frontends 'update))
+    (company-set-selection (1+ company-selection))))
 
 (defun company-select-previous ()
   (interactive)
   (when (company-manual-begin)
-    (setq company-selection (max 0 (1- company-selection))
-          company-selection-changed t))
-  (company-call-frontends 'update))
+    (company-set-selection (1- company-selection))))
 
 (defun company-complete-selection ()
   (interactive)
   (interactive)
   (when company-candidates
     (save-window-excursion
-      (let* ((selected (nth company-selection company-candidates))
+      (let* ((height (window-height))
+             (row (cdr (posn-col-row (posn-at-point))))
+             (selected (nth company-selection company-candidates))
              (buffer (funcall company-backend 'doc-buffer selected)))
         (if (not buffer)
             (error "No documentation available.")
           (display-buffer buffer)
-          (read-event)
+          (and (< (window-height) height)
+               (< (- (window-height) row 2) company-tooltip-limit)
+               (recenter (- (window-height) row 2)))
+          (while (eq 'scroll-other-window
+                     (key-binding (vector (list (read-event)))))
+            (scroll-other-window))
           (when last-input-event
             (clear-this-command-keys t)
             (setq unread-command-events (list last-input-event))))))))
   (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))))
+      (decf company-tooltip-offset)
+      (when (<= company-tooltip-offset 1)
+        (setq company-tooltip-offset 0)
+        (incf limit))))
 
   limit)
 
 
 (defun company-fill-propertize (line width selected)
   (setq line (company-safe-substring line 0 width))
-  (add-text-properties 0 width
-                       (list 'face (if selected
-                                       'company-tooltip-selection
-                                     'company-tooltip)) line)
+  (add-text-properties 0 width (list 'face 'company-tooltip) line)
   (add-text-properties 0 (length company-common)
-                       (list 'face (if selected
-                                       'company-tooltip-common-selection
-                                     'company-tooltip-common)) line)
+                       (list 'face 'company-tooltip-common) line)
+  (when selected
+    (if (and company-search-string
+             (string-match (regexp-quote company-search-string) line
+                           (length company-prefix)))
+        (progn
+          (add-text-properties (match-beginning 0) (match-end 0)
+                               '(face company-tooltip-selection) line)
+          (when (< (match-beginning 0) (length company-common))
+            (add-text-properties (match-beginning 0) (length company-common)
+                                 '(face company-tooltip-common-selection)
+                                 line)))
+      (add-text-properties 0 width '(face company-tooltip-selection) line)
+      (add-text-properties 0 (length company-common)
+                           (list 'face 'company-tooltip-common-selection)
+                           line)))
   line)
 
 ;;; replace
   (goto-char beg)
   (let ((row (cdr (posn-col-row (posn-at-point))))
         lines)
-    (while (< (point) end)
-      (move-to-window-line (incf row))
+    (while (and (equal (move-to-window-line (incf row)) row)
+                (<= (point) end))
       (push (buffer-substring beg (min end (1- (point)))) lines)
       (setq beg (point)))
+    (unless (eq beg end)
+      (push (buffer-substring beg end) lines))
     (nreverse lines)))
 
 (defun company-modify-line (old new offset)
             (mapconcat 'identity (nreverse new) "\n")
             "\n")))
 
-(defun company-create-lines (column lines selection)
+(defun company-create-lines (column lines selection limit)
 
-  (let ((limit (max company-tooltip-limit 3))
-        (len (length lines))
+  (let ((len (length lines))
         width
         lines-copy
         previous
 
 ;; show
 
+(defsubst company-pseudo-tooltip-height ()
+  "Calculate the appropriate tooltip height."
+  (max 3 (min company-tooltip-limit
+              (- (window-height) (cdr (posn-col-row (posn-at-point))) 2))))
+
 (defun company-pseudo-tooltip-show (row column lines selection)
   (company-pseudo-tooltip-hide)
   (unless lines (error "No text provided"))
 
     (move-to-column 0)
 
-    (let* ((lines (company-create-lines column lines selection))
+    (let* ((height (company-pseudo-tooltip-height))
+           (lines (company-create-lines column lines selection height))
            (nl (< (move-to-window-line row) row))
            (beg (point))
            (end (save-excursion
-                  (move-to-window-line (min (window-height)
-                                            (+ row company-tooltip-limit)))
+                  (move-to-window-line (+ row height))
                   (point)))
            (old-string (company-buffer-lines beg end))
            str)
       (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 'company-height height)
 
       (overlay-put company-pseudo-tooltip-overlay 'window (selected-window)))))
 
   (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)))
+         (height (overlay-get company-pseudo-tooltip-overlay 'company-height))
+         (lines (company-create-lines column lines selection height)))
     (overlay-put company-pseudo-tooltip-overlay 'company-before
                  (company-replacement-string old-string lines column nl))))
 
   (case command
     ('pre-command (company-pseudo-tooltip-hide-temporarily))
     ('post-command
-     (unless (overlayp company-pseudo-tooltip-overlay)
+     (unless (and (overlayp company-pseudo-tooltip-overlay)
+                  (equal (overlay-get company-pseudo-tooltip-overlay
+                                      'company-height)
+                         (company-pseudo-tooltip-height)))
+       ;; Redraw needed.
        (company-pseudo-tooltip-show-at-point (- (point)
                                                 (length company-prefix))))
      (company-pseudo-tooltip-unhide))