summaryrefslogtreecommitdiff
path: root/emacs.d/elpa/company-20201014.2251/company-cmake.el
blob: 5a05c751eda6f66e5583f658a976d6bc736c84bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
;;; company-cmake.el --- company-mode completion backend for CMake

;; Copyright (C) 2013-2014, 2017-2018  Free Software Foundation, Inc.

;; Author: Chen Bin <chenbin DOT sh AT gmail>
;; Version: 0.2

;; 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:
;;
;; company-cmake offers completions for module names, variable names and
;; commands used by CMake.  And their descriptions.

;;; Code:

(require 'company)
(require 'cl-lib)

(defgroup company-cmake nil
  "Completion backend for CMake."
  :group 'company)

(defcustom company-cmake-executable
  (executable-find "cmake")
  "Location of cmake executable."
  :type 'file)

(defvar company-cmake-executable-arguments
  '("--help-command-list"
    "--help-module-list"
    "--help-property-list"
    "--help-variable-list")
  "The arguments we pass to cmake, separately.
They affect which types of symbols we get completion candidates for.")

(defvar company-cmake--completion-pattern
  "^\\(%s[a-zA-Z0-9_<>]%s\\)$"
  "Regexp to match the candidates.")

(defvar company-cmake-modes '(cmake-mode)
  "Major modes in which cmake may complete.")

(defvar company-cmake--candidates-cache nil
  "Cache for the raw candidates.")

(defvar company-cmake--meta-command-cache nil
  "Cache for command arguments to retrieve descriptions for the candidates.")

(defun company-cmake--replace-tags (rlt)
  (setq rlt (replace-regexp-in-string
             "\\(.*?\\(IS_GNU\\)?\\)<LANG>\\(.*\\)"
             (lambda (_match)
               (mapconcat 'identity
                          (if (match-beginning 2)
                              '("\\1CXX\\3" "\\1C\\3" "\\1G77\\3")
                            '("\\1CXX\\3" "\\1C\\3" "\\1Fortran\\3"))
                          "\n"))
             rlt t))
  (setq rlt (replace-regexp-in-string
             "\\(.*\\)<CONFIG>\\(.*\\)"
             (mapconcat 'identity '("\\1DEBUG\\2" "\\1RELEASE\\2"
                                    "\\1RELWITHDEBINFO\\2" "\\1MINSIZEREL\\2")
                        "\n")
             rlt))
  rlt)

(defun company-cmake--fill-candidates-cache (arg)
  "Fill candidates cache if needed."
  (let (rlt)
    (unless company-cmake--candidates-cache
      (setq company-cmake--candidates-cache (make-hash-table :test 'equal)))

    ;; If hash is empty, fill it.
    (unless (gethash arg company-cmake--candidates-cache)
      (with-temp-buffer
        (let ((res (call-process company-cmake-executable nil t nil arg)))
          (unless (zerop res)
            (message "cmake executable exited with error=%d" res)))
        (setq rlt (buffer-string)))
      (setq rlt (company-cmake--replace-tags rlt))
      (puthash arg rlt company-cmake--candidates-cache))
    ))

(defun company-cmake--parse (prefix content cmd)
  (let ((start 0)
        (pattern (format company-cmake--completion-pattern
                         (regexp-quote prefix)
                         (if (zerop (length prefix)) "+" "*")))
        (lines (split-string content "\n"))
        match
        rlt)
    (dolist (line lines)
      (when (string-match pattern line)
        (let ((match (match-string 1 line)))
          (when match
            (puthash match cmd company-cmake--meta-command-cache)
            (push match rlt)))))
    rlt))

(defun company-cmake--candidates (prefix)
  (let (results
        cmd-opts
        str)

    (unless company-cmake--meta-command-cache
      (setq company-cmake--meta-command-cache (make-hash-table :test 'equal)))

    (dolist (arg company-cmake-executable-arguments)
      (company-cmake--fill-candidates-cache arg)
      (setq cmd-opts (replace-regexp-in-string "-list$" "" arg) )

      (setq str (gethash arg company-cmake--candidates-cache))
      (when str
        (setq results (nconc results
                             (company-cmake--parse prefix str cmd-opts)))))
    results))

(defun company-cmake--unexpand-candidate (candidate)
  (cond
   ((string-match "^CMAKE_\\(C\\|CXX\\|Fortran\\)\\(_.*\\)$" candidate)
    (setq candidate (concat "CMAKE_<LANG>" (match-string 2 candidate))))

   ;; C flags
   ((string-match "^\\(.*_\\)IS_GNU\\(C\\|CXX\\|G77\\)$" candidate)
    (setq candidate (concat (match-string 1 candidate) "IS_GNU<LANG>")))

   ;; C flags
   ((string-match "^\\(.*_\\)OVERRIDE_\\(C\\|CXX\\|Fortran\\)$" candidate)
    (setq candidate (concat (match-string 1 candidate) "OVERRIDE_<LANG>")))

   ((string-match "^\\(.*\\)\\(_DEBUG\\|_RELEASE\\|_RELWITHDEBINFO\\|_MINSIZEREL\\)\\(.*\\)$" candidate)
    (setq candidate (concat (match-string 1 candidate)
                            "_<CONFIG>"
                            (match-string 3 candidate)))))
  candidate)

(defun company-cmake--meta (candidate)
  (let ((cmd-opts (gethash candidate company-cmake--meta-command-cache))
        result)
    (setq candidate (company-cmake--unexpand-candidate candidate))

    ;; Don't cache the documentation of every candidate (command)
    ;; Cache in this case will cost too much memory.
    (with-temp-buffer
      (call-process company-cmake-executable nil t nil cmd-opts candidate)
      ;; Go to the third line, trim it and return the result.
      ;; Tested with cmake 2.8.9.
      (goto-char (point-min))
      (forward-line 2)
      (setq result (buffer-substring-no-properties (line-beginning-position)
                                                   (line-end-position)))
      (setq result (replace-regexp-in-string "^[ \t\n\r]+" "" result))
      result)))

(defun company-cmake--doc-buffer (candidate)
  (let ((cmd-opts (gethash candidate company-cmake--meta-command-cache)))

    (setq candidate (company-cmake--unexpand-candidate candidate))
    (with-temp-buffer
      (call-process company-cmake-executable nil t nil cmd-opts candidate)
      ;; Go to the third line, trim it and return the doc buffer.
      ;; Tested with cmake 2.8.9.
      (goto-char (point-min))
      (forward-line 2)
      (company-doc-buffer
       (buffer-substring-no-properties (line-beginning-position)
                                       (point-max))))))

(defun company-cmake-prefix-dollar-brace-p ()
  "Test if the current symbol follows ${."
  (save-excursion
    (skip-syntax-backward "w_")
    (and (eq (char-before (point)) ?\{)
         (eq (char-before (1- (point))) ?$))))

(defun company-cmake (command &optional arg &rest ignored)
  "`company-mode' completion backend for CMake.
CMake is a cross-platform, open-source make system."
  (interactive (list 'interactive))
  (cl-case command
    (interactive (company-begin-backend 'company-cmake))
    (init (when (memq major-mode company-cmake-modes)
            (unless company-cmake-executable
              (error "Company found no cmake executable"))))
    (prefix (and (memq major-mode company-cmake-modes)
                 (or (not (company-in-string-or-comment))
                     (company-cmake-prefix-dollar-brace-p))
                 (company-grab-symbol)))
    (candidates (company-cmake--candidates arg))
    (meta (company-cmake--meta arg))
    (doc-buffer (company-cmake--doc-buffer arg))
    ))

(provide 'company-cmake)
;;; company-cmake.el ends here