;;; irony-completion.el --- irony-mode completion interface ;; Copyright (C) 2012-2014 Guillaume Papin ;; Author: Guillaume Papin ;; Keywords: c, convenience, tools ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; Handle the search of completion points, the triggering of the ;; completion when needed and the "parsing" of completion results. ;;; Code: (require 'irony) (require 'irony-snippet) (require 'cl-lib) ;; ;; Customizable variables ;; (defgroup irony-completion nil "Irony's completion interface." :group 'irony) (defcustom irony-completion-trigger-commands '(self-insert-command newline-and-indent c-context-line-break c-scope-operator ;; electric commands c-electric-backspace c-electric-brace c-electric-colon c-electric-lt-gt c-electric-paren c-electric-pound c-electric-semi&comma c-electric-slash c-electric-star) "List of commands to watch for asynchronous completion triggering." :type '(repeat function) :group 'irony-completion) ;; ;; Internal variables ;; (defvar-local irony-completion--context nil) (defvar-local irony-completion--context-tick 0) (defvar-local irony-completion--request-callbacks nil) (defvar-local irony-completion--request-tick 0) (defvar-local irony-completion--candidates nil) (defvar-local irony-completion--candidates-tick 0) ;; ;; Utility functions ;; (defun irony-completion-symbol-bounds () (let ((pt (point)) (syntax (syntax-ppss))) ;; no prefix for strings or comments ;; TODO: Use fontlock faces instead? at least ;; #warning In the middle of a warning| ;; will be handled properly but things like links when ;; `goto-address-prog-mode' is enabled will mess up things: ;; #error see bug report XY: http://example.com/XY (unless (or (nth 3 syntax) ;skip strings (nth 4 syntax)) ;skip comments (save-excursion (skip-chars-backward "_a-zA-Z0-9") (let ((ch (char-after))) (unless (and ch (>= ch ?0) (<= ch ?9)) ;skip numbers (when (eq (char-before) ?~) (backward-char)) (setq pt (point)) (skip-chars-forward "_a-zA-Z0-9~") (cons pt (point)))))))) (defun irony-completion-beginning-of-symbol () (car (irony-completion-symbol-bounds))) (defun irony-completion-end-of-symbol () (cdr (irony-completion-symbol-bounds))) (defsubst irony-completion--skip-whitespace-backwards () ;;(skip-syntax-backward "-") doesn't seem to care about newlines (skip-chars-backward " \t\n\r")) (defun irony-completion--context-pos () (irony--awhen (irony-completion-beginning-of-symbol) (save-excursion (goto-char it) (irony-completion--skip-whitespace-backwards) (point)))) ;; ;; Functions ;; (defun irony-completion--enter () (add-hook 'post-command-hook 'irony-completion--post-command nil t) (add-hook 'completion-at-point-functions 'irony-completion-at-point nil t)) (defun irony-completion--exit () (remove-hook 'post-command-hook 'irony-completion--post-command t) (remove-hook 'completion-at-point-functions 'irony-completion-at-point t) (setq irony-completion--context nil irony-completion--candidates nil irony-completion--context-tick 0 irony-completion--request-tick 0 irony-completion--request-callbacks nil irony-completion--candidates-tick 0)) (defun irony-completion--post-command () (when (and (memq this-command irony-completion-trigger-commands) (irony-completion--update-context) (irony-completion-at-trigger-point-p)) (irony-completion--send-request))) (defun irony-completion--update-context () "Update the completion context variables based on the current position. Return t if the context has been updated, nil otherwise." (let ((ctx (irony-completion--context-pos))) (if (eq ctx irony-completion--context) nil (setq irony-completion--context ctx irony-completion--candidates nil irony-completion--context-tick (1+ irony-completion--context-tick)) (unless irony-completion--context ;; when there is no context, assume that the candidates are available ;; even though they are nil irony-completion--request-tick irony-completion--context-tick irony-completion--request-callbacks nil irony-completion--candidates nil irony-completion--candidates-tick irony-completion--context-tick) t))) (defun irony-completion--post-complete-yas-snippet (str placeholders) (let ((ph-count 0) (from 0) to snippet) (while (setq to (car placeholders) snippet (concat snippet (substring str from to) (format "${%d:%s}" (cl-incf ph-count) (substring str (car placeholders) (cadr placeholders)))) from (cadr placeholders) placeholders (cddr placeholders))) ;; handle the remaining non-snippet string, if any. (concat snippet (substring str from) "$0"))) ;; ;; Interface with irony-server ;; (defun irony-completion--send-request () (let (line column) (save-excursion (goto-char (irony-completion-beginning-of-symbol)) ;; `position-bytes' to handle multibytes and 'multicolumns' (i.e ;; tabulations) characters properly (irony--without-narrowing (setq line (line-number-at-pos) column (1+ (- (position-bytes (point)) (position-bytes (point-at-bol))))))) (setq irony-completion--request-callbacks nil irony-completion--request-tick irony-completion--context-tick) (irony--send-parse-request "complete" (list 'irony-completion--request-handler irony-completion--context-tick) (number-to-string line) (number-to-string column)))) (defun irony-completion--request-handler (candidates tick) (when (eq tick irony-completion--context-tick) (setq irony-completion--candidates-tick tick irony-completion--candidates candidates) (mapc 'funcall irony-completion--request-callbacks))) (defun irony-completion--still-completing-p () (unless (irony-completion-candidates-available-p) (eq irony-completion--request-tick irony-completion--context-tick))) ;; ;; Irony Completion Interface ;; (defun irony-completion-typed-text (candidate) (nth 0 candidate)) (defun irony-completion-priority (candidate) (nth 1 candidate)) (defun irony-completion-type (candidate) (nth 2 candidate)) (defun irony-completion-brief (candidate) (nth 3 candidate)) (defun irony-completion-annotation (candidate) (substring (nth 4 candidate) (nth 5 candidate))) (defun irony-completion-post-comp-str (candidate) (car (nth 6 candidate))) (defun irony-completion-post-comp-placeholders (candidate) (cdr (nth 6 candidate))) (defun irony-completion-candidates-available-p () (and (eq (irony-completion--context-pos) irony-completion--context) (eq irony-completion--candidates-tick irony-completion--context-tick))) (defun irony-completion-candidates () "Return the list of candidates at point, if available. Use the function `irony-completion-candidates-available-p' to know if the candidate list is available. A candidate is composed of the following elements: 0. The typed text. Multiple candidates can share the same string because of overloaded functions, default arguments, etc. 1. The priority. 2. The [result-]type of the candidate, if any. 3. If non-nil, contains the Doxygen brief documentation of the candidate. 4. The signature of the candidate excluding the result-type which is available separately. Example: \"foo(int a, int b) const\" 5. The annotation start, a 0-based index in the prototype string. 6. Post-completion data. The text to insert followed by 0 or more indices. These indices work by pairs and describe ranges of placeholder text. Example: (\"(int a, int b)\" 1 6 8 13)" (and (irony-completion-candidates-available-p) irony-completion--candidates)) (defun irony-completion-candidates-async (callback) "Call CALLBACK when asynchronous completion is available. Note that: - If the candidates are already available, CALLBACK is called immediately. - In some circumstances, CALLBACK may not be called. i.e: irony-server crashes, ..." (irony-completion--update-context) (if (irony-completion-candidates-available-p) (funcall callback) (when irony-completion--context (unless (irony-completion--still-completing-p) (irony-completion--send-request)) (setq irony-completion--request-callbacks (cons callback irony-completion--request-callbacks))))) (defun irony-completion-post-complete (candidate) (let ((str (irony-completion-post-comp-str candidate)) (placeholders (irony-completion-post-comp-placeholders candidate))) (if (and placeholders (irony-snippet-available-p)) (irony-snippet-expand (irony-completion--post-complete-yas-snippet str placeholders)) (insert (substring str 0 (car placeholders)))))) (defun irony-completion-at-trigger-point-p () (when (eq (point) (irony-completion-beginning-of-symbol)) (save-excursion (cond ;; use `re-search-backward' so that the cursor is moved just before the ;; member access, if any ((re-search-backward (format "%s\\=" (regexp-opt '("." ;object member access "->" ;pointer member access "::"))) ;scope operator (point-at-bol) t) (unless ;; ignore most common uses of '.' where it's not a member access (and (eq (char-after) ?.) (or ;; include statements: #include (looking-back "^#\\s-*include\\s-+[<\"][^>\"]*" (point-at-bol)) ;; floating point numbers (not thorough, see: ;; http://en.cppreference.com/w/cpp/language/floating_literal) (looking-back "[^_a-zA-Z0-9][[:digit:]]+" (point-at-bol)))) ;; except the above exceptions we use a "whitelist" for the places ;; where it looks like a member access (irony-completion--skip-whitespace-backwards) (or ;; after brackets consider it's a member access so things like ;; 'getFoo().|' match (memq (char-before) (list ?\) ?\] ?} ?>)) ;; identifiers but ignoring some keywords ;; ;; handle only a subset of template parameter packs, where the ;; ellipsis is preceded by a keyword, in situation like: ;; template class X {...}; ;; template