summaryrefslogtreecommitdiffstats
path: root/emacs.d/elisp/c-eldoc.el
blob: 732d00d713f9084d6dad50685e55c3702e40123b (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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
;;; c-eldoc.el --- helpful description of the arguments to C functions

;; Copyright (C) 2004 Paul Pogonyshev
;; Copyright (C) 2004, 2005 Matt Strange
;; Copyright (C) 2010 Nathaniel Flath

;; This file is NOT a part of GNU Emacs

;; 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 2 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, write to the Free Software
;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
;; USA

;;; Commentary:

;; To enable: put the following in your .emacs file:
;; 
;; (add-hook 'c-mode-hook 'c-turn-on-eldoc-mode)

;; Nathaniel has submitted a caching patch to make this workable on large projects "like the emacs
;; codebase"
;; v0.5 01/02/2010

;; Provides helpful description of the arguments to C functions.
;; Uses child process grep and preprocessor commands for speed.
;; v0.4 01/16/2005

;; Your improvements are appreciated: I am no longer maintaining this code
;; m_strange at mail dot utexas dot edu.  Instead, direct all requests to
;; flat0103@gmail.com

;;; Code:

(require 'eldoc)
;; without this, you can't compile this file and have it work properly
;; since the `c-save-buffer-state' macro needs to be known as such
(require 'cc-defs)
(require 'cl)

;; make sure that the opening parenthesis in C will work
(eldoc-add-command 'c-electric-paren)

;;if cache.el isn't loaded, define the cache functions
(unless (fboundp 'cache-make-cache)
  (defun* cache-make-cache (init-fun test-fun cleanup-fun
                                     &optional &key
                                     (test #'eql)
                                     (size 65)
                                     (rehash-size 1.5)
                                     (rehash-threshold 0.8)
                                     (weakness nil))
    "Creates a cached hash table.  This is a hash table where
elements expire at some condition, as specified by init-fun and
test-fun.  The three arguments do as follows:

init-fun is a function that is called when a new item is inserted
into the cache.

test-fun is a function that is called when an item in the cache
is looked up.  It takes one argument, and will be passed the
result of init-fun that was generated when the item was inserted
into the cache.

cleanup-fun is called when an item is removed from the hash
table.  It takes one argument, the value of the key-value pair
being deleted.

Note that values are only deleted from the cache when accessed.

This will return a list of 4 elements: a has table and the 3
arguments.  All hash-table functions will work on the car of this
list, although if accessed directly the lookups will return a pair
(value, (init-fun)).

The keyword arguments are the same as for make-hash-table and are applied
to the created hash table."
  (list (make-hash-table :test test
                         :size size
                         :rehash-size rehash-size
                         :rehash-threshold rehash-threshold
                         :weakness weakness) init-fun test-fun cleanup-fun))

  (defun cache-gethash (key cache)
    "Retrieve the value corresponding to key from cache."
    (let ((keyval (gethash key (car cache) )))
      (if keyval
          (let ((val (car keyval))
                (info (cdr keyval)))
            (if (funcall (caddr cache) info)
                (progn
                  (remhash key (car cache))
                  (funcall (cadddr cache) val)
                  nil)
              val)))))
   
  (defun cache-puthash (key val cache)
    "Puts the key-val pair into cache."
    (puthash key
             (cons val (funcall (cadr cache)))
             (car cache))))
         

;; if you've got a non-GNU preprocessor with funny options, set these
;; variables to fix it
(defvar c-eldoc-cpp-macro-arguments "-dD -w -P")
(defvar c-eldoc-cpp-normal-arguments "-w -P")
(defvar c-eldoc-cpp-command "/lib/cpp ")
(defvar c-eldoc-includes
  "`pkg-config gtk+-2.0 --cflags` -I./ -I../ "
  "List of commonly used packages/include directories - For
  example, SDL or OpenGL.  This shouldn't slow down cpp, even if
  you've got a lot of them.")

(defvar c-eldoc-reserved-words
  (list "if" "else" "switch" "while" "for" "sizeof")
  "List of commands that eldoc will not check.")


(defvar c-eldoc-buffer-regenerate-time
  120
  "Time to keep a preprocessed buffer around.")

(defun c-eldoc-time-diff (t1 t2)
  "Return the difference between the two times, in seconds.
T1 and T2 are time values (as returned by `current-time' for example)."
  ;; Pacify byte-compiler with `symbol-function'.
  (time-to-seconds (subtract-time t1 t2)))

(defun c-eldoc-time-difference (old-time)
  "Returns whether or not old-time is less than c-eldoc-buffer-regenerate-time seconds ago."
  (> (c-eldoc-time-diff (current-time) old-time) c-eldoc-buffer-regenerate-time))

(defun c-eldoc-cleanup (preprocessed-buffer)
  (kill-buffer preprocessed-buffer))

(defvar c-eldoc-buffers
  (cache-make-cache #'current-time #'c-eldoc-time-difference #'c-eldoc-cleanup)
  "Cache of buffer->preprocessed file used to speed up finding arguments")

(defun c-turn-on-eldoc-mode ()
  "Enable c-eldoc-mode"
  (interactive)
  (set (make-local-variable 'eldoc-documentation-function)
       'c-eldoc-print-current-symbol-info)
  (turn-on-eldoc-mode))

;; call the preprocessor on the current file
;;
;; run cpp the first time to get macro declarations, the second time
;; to get normal function declarations
(defun c-eldoc-get-buffer (function-name)
  "Call the preprocessor on the current file"
;; run the first time for macros
  (let ((output-buffer (cache-gethash (current-buffer) c-eldoc-buffers)))
    (if output-buffer output-buffer
      (let* ((this-name (concat "*" buffer-file-name "-preprocessed*"))
             (preprocessor-command (concat c-eldoc-cpp-command " "
                                           c-eldoc-cpp-macro-arguments " "
                                           c-eldoc-includes " "
                                           buffer-file-name))
             (cur-buffer (current-buffer))
             (output-buffer (generate-new-buffer this-name)))
        (bury-buffer output-buffer)
        (call-process-shell-command preprocessor-command nil output-buffer nil)
        ;; run the second time for normal functions
        (setq preprocessor-command (concat c-eldoc-cpp-command " "
                                           c-eldoc-cpp-normal-arguments " "
                                           c-eldoc-includes " "
                                           buffer-file-name))
        (call-process-shell-command preprocessor-command nil output-buffer nil)
        (cache-puthash cur-buffer output-buffer c-eldoc-buffers)
        output-buffer))))

(defun c-eldoc-function-and-argument (&optional limit)
  "Finds the current function and position in argument list."
  (let* ((literal-limits (c-literal-limits))
         (literal-type (c-literal-type literal-limits)))
    (save-excursion
      ;; if this is a string, move out to function domain
      (when (eq literal-type 'string)
        (goto-char (car literal-limits))
        (setq literal-type nil))
      (if literal-type
          nil
        (c-save-buffer-state ((argument-index 1))
          (while (or (eq (c-forward-token-2 -1 t limit) 0)
                     (when (eq (char-before) ?\[)
                       (backward-char)
                       t))
            (when (eq (char-after) ?,)
              (setq argument-index (1+ argument-index))))
          (c-backward-syntactic-ws)
          (when (eq (char-before) ?\()
            (backward-char)
            (c-forward-token-2 -1)
            (when (looking-at "[a-zA-Z_][a-zA-Z_0-9]*")
              (cons (buffer-substring-no-properties
                     (match-beginning 0) (match-end 0))
                    argument-index))))))))

(defun c-eldoc-format-arguments-string (arguments index)
  "Formats the argument list of a function."
  (let ((paren-pos (string-match "(" arguments))
        (pos 0))
    (when paren-pos
      (setq arguments (replace-regexp-in-string "\\\\?[[:space:]\\\n]"
                                                " "
                                                (substring arguments paren-pos))
            arguments (replace-regexp-in-string "\\s-+" " " arguments)
            arguments (replace-regexp-in-string " *, *" ", " arguments)
            arguments (replace-regexp-in-string "( +" "(" arguments)
            arguments (replace-regexp-in-string " +)" ")" arguments))
      ;; find the correct argument to highlight, taking `...'
      ;; arguments into account
      (while (and (> index 1)
                  pos
                  (not (string= (substring arguments (+ pos 2) (+ pos 6))
                                "...)")))
        (setq pos (string-match "," arguments (1+ pos))
              index (1- index)))
      ;; embolden the current argument
      (when (and pos
                 (setq pos (string-match "[^ ,()]" arguments pos)))
        (add-text-properties pos (string-match "[,)]" arguments pos)
                             '(face bold) arguments))
      arguments)))

(defun c-eldoc-print-current-symbol-info ()
  "Returns documentation string for the current symbol." 
  (let* ((current-function-cons (c-eldoc-function-and-argument (- (point) 1000)))
         (current-function (car current-function-cons))
         (current-function-regexp (concat "[ \t\n]+[*]*" current-function "[ \t\n]*("))
         (current-macro-regexp (concat "#define[ \t\n]+[*]*" current-function "[ \t\n]*("))
         (current-buffer (current-buffer))
         (tag-buffer)
         (function-name-point)
         (arguments)
         (type-face 'font-lock-type-face))
    (when (and current-function
               (not (member current-function c-eldoc-reserved-words)))
      (when (setq tag-buffer (c-eldoc-get-buffer current-function))
        ;; setup the buffer
        (set-buffer tag-buffer)
        (goto-char (point-min))
        (prog1
            ;; protected regexp search
            (when (condition-case nil
                      (progn
                        (if (not (re-search-forward current-macro-regexp (point-max) t))
                            (re-search-forward current-function-regexp))
                        t)
                    (error (prog1 nil
                             (message "Function doesn't exist..."))))
              ;; move outside arguments list
              (search-backward "(")
              (c-skip-ws-backward)
              (setq function-name-point (point))
              (forward-sexp)
              (setq arguments (buffer-substring-no-properties
                               function-name-point (point)))
              (goto-char function-name-point)
              (backward-char (length current-function))
              (c-skip-ws-backward)
              (setq function-name-point (point))
              (search-backward-regexp "[};/#]" (point-min) t)
              ;; check for macros
              (if (= (char-after) ?#)
                  (let ((is-define (looking-at "#[[:space:]]*define"))
                        (preprocessor-point (point)))
                    (while (prog2 (end-of-line)
                               (= (char-before) ?\\)
                             (forward-char)))
                    (when (and is-define (> (point) function-name-point))
                      (goto-char preprocessor-point)
                      (setq type-face 'font-lock-preprocessor-face)))
                (forward-char)
                (when (looking-back "//")
                  (end-of-line)))
              (c-skip-ws-forward)
              ;; colorize
              (concat (propertize (buffer-substring-no-properties
                                   (point)
                                   function-name-point)
                                  'face type-face)
                      " "
                      (propertize current-function
                                  'face 'font-lock-function-name-face)
                      " "
                      (c-eldoc-format-arguments-string arguments
                                                       (cdr current-function-cons))))
          (set-buffer current-buffer))))))

(provide 'c-eldoc)
;;; c-eldoc.el ends here