emacs-config/.cask/26.3/elpa/company-irony-c-headers-20151018.909/company-irony-c-headers.el
2020-04-28 12:47:35 +02:00

265 lines
9.7 KiB
EmacsLisp

;;; company-irony-c-headers.el --- Company mode backend for C/C++ header files with Irony
;; Copyright (C) 2015 Yutian Li
;; Author: Yutian Li <hotpxless@gmail.com>
;; Version: 1.0.0
;; Package-Version: 20151018.909
;; URL: https://github.com/hotpxl/company-irony-c-headers
;; Keywords: c company
;; Package-Requires: ((cl-lib "0.5") (company "0.9.0") (irony "0.2.0"))
;; 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 <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This file provides `company-irony-c-headers`, a company backend
;; that completes C/C++ header files. Large chunks of code are taken
;; from
;; [company-c-headers](https://github.com/randomphrase/company-c-headers). It
;; also works with `irony-mode` to obtain compiler options.
;; Usage:
;; (require 'company-irony-c-headers)
;; ;; Load with `irony-mode` as a grouped backend
;; (eval-after-load 'company
;; '(add-to-list
;; 'company-backends '(company-irony-c-headers company-irony)))
;; When compiler options change, call
;; `company-irony-c-headers-reload-compiler-output` manually to
;; reload.
;;; Code:
(require 'cl-lib)
(require 'company)
(require 'irony)
(defvar company-irony-c-headers--compiler-executable "clang++"
"Compiler executable.")
(defun company-irony-c-headers--include-decl ()
"Match include syntax."
(rx
line-start
"#" (zero-or-more blank) "include"
(one-or-more blank)
(or (and "<" (submatch-n 1 (zero-or-more (not-char ?>))))
(and "\"" (submatch-n 2 (zero-or-more (not-char ?\")))))))
(defvar company-irony-c-headers--modes
'(c++-mode c-mode)
"Mode supported.")
(defun company-irony-c-headers--lang ()
"Get language."
(irony--lang-compile-option))
(defun company-irony-c-headers--default-compiler-options ()
"Get default compiler options to obtain include paths."
(append (company-irony-c-headers--lang) '("-v" "-E" "-")))
(defun company-irony-c-headers--user-compiler-options ()
"Get compiler options."
irony--compile-options)
(defun company-irony-c-headers--working-dir ()
"Get working directory."
(if irony--working-directory
(file-name-as-directory irony--working-directory)
default-directory))
(defvar-local company-irony-c-headers--compiler-output nil
"Compiler generated output for search paths.")
(defun company-irony-c-headers-reload-compiler-output ()
"Call compiler to get search paths."
(interactive)
(when (and company-irony-c-headers--compiler-executable
(company-irony-c-headers--working-dir))
(setq
company-irony-c-headers--compiler-output
(let ((uco (company-irony-c-headers--user-compiler-options))
(dco (company-irony-c-headers--default-compiler-options))
(default-directory (company-irony-c-headers--working-dir)))
(with-temp-buffer
(apply 'call-process
company-irony-c-headers--compiler-executable nil t nil
(append
uco
dco))
(goto-char (point-min))
(let (quote-directories
angle-directories
(start "#include \"...\" search starts here:")
(second-start "#include <...> search starts here:")
(stop "End of search list."))
(when (search-forward start nil t)
(forward-line 1)
(while (not (looking-at-p second-start))
;; Skip whitespace at the begining of the line.
(skip-chars-forward "[:blank:]" (point-at-eol))
(let ((p
(replace-regexp-in-string
"\\s-+(framework directory)"
"" (buffer-substring (point) (point-at-eol)))))
(push p quote-directories))
(forward-line 1))
(forward-line 1)
(while (not (or (looking-at-p stop) (eolp)))
;; Skip whitespace at the begining of the line.
(skip-chars-forward "[:blank:]" (point-at-eol))
(let ((p
(replace-regexp-in-string
"\\s-+(framework directory)"
"" (buffer-substring (point) (point-at-eol)))))
(push p quote-directories)
(push p angle-directories))
(forward-line 1)))
(list
(reverse quote-directories)
(reverse angle-directories))))))))
(defun company-irony-c-headers--search-paths ()
"Retrieve compiler search paths."
(unless company-irony-c-headers--compiler-output
(company-irony-c-headers-reload-compiler-output))
company-irony-c-headers--compiler-output)
(defun company-irony-c-headers--resolve-paths (paths)
"Resolve PATHS relative to working directory."
(let ((working-dir (company-irony-c-headers--working-dir)))
(mapcar
(lambda (i)
(file-name-as-directory
(expand-file-name i working-dir))) paths)))
(defun company-irony-c-headers--resolved-search-paths (q)
"Get resolved paths. Q indicates whether it is quoted."
(if q
(let ((cur-dir
(if (buffer-file-name)
(file-name-directory (buffer-file-name))
(file-name-as-directory (expand-file-name "")))))
(cons
cur-dir
(company-irony-c-headers--resolve-paths
(nth 0 (company-irony-c-headers--search-paths)))
))
(company-irony-c-headers--resolve-paths
(nth 1 (company-irony-c-headers--search-paths)))))
(defun company-irony-c-headers--prefix ()
"Find prefix for matching."
(if (looking-back
(company-irony-c-headers--include-decl) (line-beginning-position))
(let ((match
(if (match-string-no-properties 1)
(propertize (match-string-no-properties 1) 'quote nil)
(if (match-string-no-properties 2)
(propertize (match-string-no-properties 2) 'quote t)))))
(if (and (/= (length match) 0)
(= (aref match (1- (length match))) ?/))
(cons match t)
match))))
(defun company-irony-c-headers--candidates-for (prefix dir)
"Return a list of candidates for PREFIX in directory DIR."
(let* ((prefixdir (file-name-directory prefix))
(subdir (if prefixdir
(expand-file-name prefixdir dir)
dir))
(prefixfile (file-name-nondirectory prefix))
candidates)
;; Remove "." and "..".
(when (file-directory-p subdir)
(setq candidates
(cl-remove-if
(lambda (f)
(cl-member
(directory-file-name f) '("." "..") :test 'equal))
(file-name-all-completions prefixfile subdir)))
;; Sort candidates.
(setq candidates (sort candidates #'string<))
;; Add property.
(mapcar
(lambda (c)
(let ((real (if prefixdir
(concat prefixdir c)
c)))
(propertize
real
'directory subdir))) candidates))))
(defun company-irony-c-headers--candidates (prefix)
"Return candidates for PREFIX."
(let* ((quoted (get-text-property 0 'quote prefix))
(p (company-irony-c-headers--resolved-search-paths quoted))
candidates)
(mapc (lambda (i)
(when (file-directory-p i)
(setq
candidates
(append
candidates
(company-irony-c-headers--candidates-for prefix i)))
))
p)
(cl-delete-duplicates
candidates
:test 'string=
:from-end t)))
(defun company-irony-c-headers--meta (candidate)
"Return the metadata associated with CANDIDATE. Just the directory."
(get-text-property 0 'directory candidate))
(defun company-irony-c-headers--location (candidate)
"Return the location associated with CANDIDATE."
(cons (concat (file-name-as-directory (get-text-property 0 'directory candidate))
(file-name-nondirectory candidate))
1))
;;;###autoload
(defun company-irony-c-headers (command &optional arg &rest ignored)
"Company backend for C/C++ header files. Taking COMMAND ARG IGNORED."
(interactive (list 'interactive))
(cl-case command
(interactive (company-begin-backend 'company-irony-c-headers))
(prefix
(if (member major-mode company-irony-c-headers--modes)
(company-irony-c-headers--prefix)))
(init (company-irony-c-headers-reload-compiler-output))
(sorted t)
(candidates (company-irony-c-headers--candidates arg))
(location (company-irony-c-headers--location arg))
(meta (company-irony-c-headers--meta arg))
(post-completion
;; ARG here lost property. Need to rematch prefix.
(let ((matched (company-irony-c-headers--prefix)))
(if (consp matched)
(setq matched (car matched)))
(unless (equal matched (file-name-as-directory matched))
(if (get-text-property 0 'quote matched)
(if (looking-at "\"")
(forward-char)
(insert "\""))
(if (looking-at ">")
(forward-char)
(insert ">"))))))))
(provide 'company-irony-c-headers)
;;; company-irony-c-headers.el ends here