legacy-dotfiles/emacs.d/elisp/c-eldoc.el
2011-04-16 19:39:49 +02:00

304 lines
12 KiB
EmacsLisp

;;; 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