;;; cppsense.el --- Simple, heuristic, tag-based C++ coding assistant ;; Copyright (C) 2007 Tamas Patrovics ;; This file 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, or (at your option) ;; any later version. ;; This file 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 GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;; Needs a TAG table containing all files of the project. ;; ;; F1 is the only key to use. ;; ;; F1 on a symbol shows the definition of the symbol in a tooltip. ;; ;; Pressing F1 twice quickly on a symbol goes to the declaration of ;; the symbol. (Returning to the place before the jump can be done ;; with standard etags key M-*) ;; ;; F1 on an include line goes to the include file. ;; ;; F1 shows completions if it can offer any for the position before ;; point. ;; ;; F1 anywhere else goes to the source file from a header or vica ;; versa. ;; Tested on Emacs 22. ;; TODO ;; ;; - it thinks delete var; is a variable declaration ;; ;; - info about current class in tooltip ;; ;; - inherited members ;; ;; - private/public ;; ;; - inline functions ;; ;; - overloaded functions ;; ;; - highlight match in tooltip ;; ;; ;; - class matching regexp should be a constant or in a function ;; ;; Assumptions: ;; ;; - there is a TAG file ;; ;; - the TAG file contains all files of the project ;; ;; - classes are defined in a file with the same name ;; (require 'cl) (require 'etags) (defvar cppsense-filename-hash (make-hash-table :test 'equal)) (defvar cppsense-debug nil "*Debug") (defvar cppsense-last-cmd-time (current-time)) (setq cppsense-popup-threshold 0.3) (defconst cppsense-symbol-regexp "\\(\\<\\(\\sw\\|\\s_\\)+\\>\\)") (defvar cppsense-markers nil) (defvar cppsense-completion-frame nil) (defvar cppsense-selection-overlay nil) (defvar cppsense-completions-buffer "*cpssense-completions*") (defvar cppsense-enable-automatic-completion nil) (defvar cppsense-completion-symbol-beginning-position nil) (defvar cppsense-completion-editing-commands '(self-insert-command c-electric-backspace)) (defvar cppsense-header-extensions '("h" "hh")) (defvar cppsense-source-extensions '("cc" "cpp" "icc")) (defun cppsense-initialize () (interactive) (if tags-file-name (find-file-noselect tags-file-name) (call-interactively 'visit-tags-table)) (message "Collecting file names from TAGS file...") (clrhash cppsense-filename-hash) (with-current-buffer (get-file-buffer tags-file-name) (goto-char (point-min)) (while (search-forward "\f\n" nil t) (looking-at "\\(.*\\),[0-9]+") (let* ((file (match-string 1)) (name (file-name-nondirectory file)) (dir (file-name-directory file)) (directories (gethash name cppsense-filename-hash))) ;; remove / from the end od dir (setq dir (substring dir 0 -1)) (puthash name (if directories (append directories (list dir)) (list dir)) cppsense-filename-hash)))) (message "Done.")) (add-hook 'c++-mode-hook 'cppsense-setup) (defun cppsense-setup () (if (eq (hash-table-count cppsense-filename-hash) 0) (cppsense-initialize)) (if cppsense-enable-automatic-completion (add-hook 'post-command-hook 'cppsense-try-automatic-completion nil t)) (local-set-key (kbd "") 'cppsense-do-something-clever) (local-set-key (kbd "C-") 'cppsense-go-to-definition)) (defun cppsense-do-something-clever () (interactive) (let ((origin (cppsense-create-point-marker))) (unwind-protect (progn (setq cppsense-markers nil) (some (lambda (func) (funcall func)) '(cppsense-include-handler cppsense-symbol-handler cppsense-completion-handler cppsense-nothing-handler))) (cppsense-destroy-markers)) (require 'etags) (ring-insert find-tag-marker-ring origin))) (defun cppsense-go-to-definition () (interactive) (cppsense-do-something-clever)) (defun cppsense-include-handler () (if (save-excursion (beginning-of-line) (looking-at "#include \\(<\\|\"\\)\\(.*\\)\\(>\\|\"\\)")) (let* ((file (match-string 2)) (dirname (file-name-directory file)) (name (file-name-nondirectory file)) (path (if (file-readable-p name) file (let ((dirs (gethash name cppsense-filename-hash)) dir) (when dirs (setq dir (if dirname (progn ;; remove / (setq dirname (substring dirname 0 -1)) (some (lambda (d) (if (equal dirname (file-name-nondirectory d)) d)) dirs)) (if (not (cdr dirs)) (car dirs) ;; the directories are assumed to ;; be in the order of search, so ;; choosing the first one automatically (car dirs)))) ;; (cppsense-completing-read ;; (concat "There are more than one candidates. " ;; "Select which one to visit: ") ;; dirs)))) (if dir (concat dir "/" name))))))) (if (not path) (error "File %s not found." file) (find-file path) (message "File %s found." file))))) (defun cppsense-symbol-handler () (let* ((symbol-info (cppsense-get-symbol-info-at-point)) (marker (cdr (assoc 'declaration symbol-info)))) (when symbol-info (if (not marker) (error "Can't find declaration of symbol: %s" (cdr (assoc 'symbol symbol-info))) (if (or (eq this-command 'cppsense-go-to-definition) ; (and (eq last-command 'cppsense-do-something-clever) (let* ((oldtime cppsense-last-cmd-time) (newtime (setq cppsense-last-cmd-time (current-time))) (diff (time-to-seconds (time-subtract newtime oldtime)))) (< diff cppsense-popup-threshold))) (progn (switch-to-buffer (marker-buffer marker)) (goto-char marker)) (let ((message (cppsense-get-code-context-from-marker marker))) (if (sit-for cppsense-popup-threshold) (cppsense-show-popup-help message))))) (move-marker marker nil) t))) (defun cppsense-get-symbol-info-at-point () (when (and (not (eobp)) (let ((syntax (char-syntax (char-after)))) (or (eq syntax ?w) (eq syntax ?_)))) (let* ((symbol-begin (cppsense-get-symbol-begin-at-point)) (symbol-end (cppsense-get-symbol-end-at-point)) (symbol (buffer-substring-no-properties symbol-begin symbol-end)) (marker (some (lambda (func) (funcall func symbol symbol-begin symbol-end)) '(cppsense-find-symbol-with-heuristics cppsense-lookup-symbol-in-tag-file)))) (cppsense-debug (concat "cppsense-get-symbol-info-at-point: " "symbol:%s begin: %s end: %s marker: %s") symbol symbol-begin symbol-end marker) (list (cons 'symbol symbol) (cons 'begin symbol-begin) (cons 'end symbol-end) (cons 'declaration marker))))) (defun cppsense-get-declaration-marker-of-symbol-at-point () (assoc-default 'declaration (cppsense-get-symbol-info-at-point))) (defun cppsense-find-symbol-with-heuristics (symbol symbol-begin symbol-end) (if (cppsense-within-function) (cppsense-find-symbol-within-function symbol symbol-begin symbol-end) (if (cppsense-within-class) (save-excursion (c-beginning-of-defun) (when (looking-at (concat "class\\s-*" cppsense-symbol-regexp)) (goto-char (match-beginning 1)) (let* ((members (cppsense-get-members-of-class (cppsense-create-point-marker))) (marker (if members (some (lambda (member) (if (equal (plist-get member 'name) symbol) (plist-get member 'declaration))) members)))) (if marker (cppsense-get-member-function-definition symbol marker))))) ;; outside of a function (cppsense-try-class symbol)))) (defun cppsense-find-symbol-within-function (symbol symbol-begin symbol-end) (save-excursion (if (save-excursion (goto-char symbol-end) (looking-at "(")) ;; function call (if (cppsense-member-p symbol-begin) (cppsense-get-qualified-member-function symbol) ;; standalone function call. assume it's a member ;; function in the same file (goto-char (point-min)) (when (re-search-forward (concat "::\\<\\(" symbol "\\)\\>(") nil t) (goto-char (match-beginning 1)) (cppsense-debug (concat "cppsense-find-symbol-with-heuristics: " "member function in same file found: %s") symbol) (cppsense-create-point-marker))) ;; we don't know it what it is yet (if (cppsense-member-p symbol-begin) ;; qualified member variable (cppsense-get-qualified-member-info-at-point symbol) (c-beginning-of-defun) (if (or (re-search-forward (concat "\\s-\\(" symbol "\\)[,)]") (save-excursion (re-search-forward "^{")) t) (re-search-forward (concat "^\\s-*\\S-.*\\s-\\<\\(" symbol "\\)\\>\\s-*\\(;\\|[,=\[].*;\\)") (save-excursion (c-end-of-defun) (point)) t)) ;; it's a function argument or local variable (progn (goto-char (or (match-beginning 1) (match-beginning 0))) (cppsense-debug (concat "cppsense-find-symbol-with-heuristics: " "function arg or local variable found: %s") symbol) (cppsense-create-point-marker)) (or (cppsense-try-class symbol) (progn (cppsense-debug (concat "cppsense-find-symbol-with-heuristics: " "assume symbol is an unqualified member variable: %s") symbol) ;; extract object type from function definition (when (re-search-forward (concat cppsense-symbol-regexp "::") (save-excursion (search-forward "{" nil t) (forward-char -1) (forward-sexp) (point)) t) (goto-char (match-beginning 0)) (let ((decl (cppsense-get-declaration-marker-of-symbol-at-point))) (if decl (cppsense-get-member-of-class decl symbol))))))))))) (defun cppsense-member-p (symbol-begin) (or (and (eq (char-before symbol-begin) ?>) (eq (char-before (1- symbol-begin)) ?-)) (and (eq (char-before symbol-begin) ?:) (eq (char-before (1- symbol-begin)) ?:)) (eq (char-before symbol-begin) ?.))) (defun cppsense-get-qualified-member-function (function) (let ((marker (cppsense-get-qualified-member-info-at-point function))) (if marker (cppsense-get-member-function-definition function marker)))) (defun cppsense-get-member-function-definition (function marker) (with-current-buffer (marker-buffer marker) (save-excursion (goto-char marker) (c-beginning-of-defun) (if (not (cppsense-get-class-on-current-line)) (progn (message "Can't find class containing function %s" function) marker) (if (not (looking-at (concat "class\\s-+" cppsense-symbol-regexp))) (progn (message "Can't find class name containing function %s" function) marker) (let ((class (match-string 1)) (source-file (cppsense-get-other-file))) (if (not source-file) (progn (message "Can't find source file for class %s" class) marker) (with-current-buffer (find-file-noselect source-file) (goto-char (point-min)) (if (not (re-search-forward (concat "\\<" class "::\\(" function "\\)\\s-*(") nil t)) (progn (message "Can't find function %s in source file of class %s" function class) marker) (goto-char (match-beginning 1)) (cppsense-debug "Definition of function %s is found." function) (cppsense-create-point-marker)))))))))) (defun cppsense-get-qualified-member-info-at-point (membername) (let ((parent-decl (cppsense-get-parent-class))) (if parent-decl (cppsense-get-member-of-class parent-decl membername)))) (defun cppsense-get-parent-class () (save-excursion ;; go to preceding symbol (cppsense-go-to-beginning-of-symbol) (skip-syntax-backward ".") (forward-char -1) (let ((parent-decl (cppsense-get-declaration-marker-of-symbol-at-point))) (when parent-decl (with-current-buffer (marker-buffer parent-decl) (save-excursion (goto-char parent-decl) (unless (cppsense-get-class-on-current-line) ;; if not a class already then find out what it is (backward-word) (setq parent-decl (cppsense-get-declaration-marker-of-symbol-at-point))) parent-decl)))))) (defun cppsense-get-data-member-of-class (class-marker membername) (cppsense-get-member-of-class class-marker membername)) (defun cppsense-get-member-of-class (class-marker membername) (let ((members (cppsense-get-members-of-class class-marker))) (if members (or (some (lambda (member) (if (equal (plist-get member 'name) membername) (plist-get member 'declaration))) members) (progn (cppsense-debug (concat "cppsense-get-member-of-class: " "member not found: %s") membername) nil))))) (defun cppsense-get-members-of-class (marker &optional dont-garbage-collect-markers) (with-current-buffer (marker-buffer marker) (save-excursion (goto-char marker) (let ((class (cppsense-get-class-on-current-line))) (if (not class) (progn (message "Not a class or a struct: %s" (cppsense-get-symbol-name marker)) nil) (let* ((end (save-excursion (search-forward "{" nil t) (forward-char -1) (forward-sexp) (point))) (member-regexp (concat cppsense-symbol-regexp "\\(;" ; data member "\\|" ;; dont put ; at the ;; end, the function declaration ;; can be broken into multiple ;; lines "\\s-*(" ; function "\\)")) members) (while (re-search-forward member-regexp end t) (unless (or ;; skip constructors/destructors (equal (match-string 1) class) ;; skip friend classes (save-match-data (save-excursion (beginning-of-line) (looking-at "\\s-*friend class")))) (let* ((name (match-string-no-properties 1)) (declaration (save-excursion (save-match-data (goto-char (match-beginning 1)) (if dont-garbage-collect-markers (point-marker) (cppsense-create-point-marker))))) (info (list 'name name 'declaration declaration))) (when (save-excursion (goto-char (match-end 1)) (looking-at "\\s-*(")) (let ((definition (cppsense-get-member-function-definition name declaration))) (if dont-garbage-collect-markers (setq definition (copy-marker definition))) (setq info (append info (list 'definition definition))))) (push info members))) ;; avoid matching anything else on the same line (forward-line 1)) (cppsense-debug (concat "cppsense-get-members-of-class: " "members found: %s") members) members)))))) (defun cppsense-get-class-on-current-line () (save-excursion (beginning-of-line) (if (looking-at (concat "\\s-*\\(class\\|struct\\)\\s-+" cppsense-symbol-regexp)) (match-string 2)))) (defun cppsense-try-class (symbol) (let* ((file (concat symbol ".hh")) (dirs (gethash file cppsense-filename-hash))) (if dirs (with-current-buffer (find-file-noselect (concat (car dirs) "/" file)) (goto-char (point-min)) (when (re-search-forward (concat "^\\(class\\|struct\\)\\s-+\\<\\(" symbol "\\)\\>") nil t) (goto-char (match-beginning 2)) (cppsense-debug (concat "cppsense-try-class: " "class found: %s") symbol) (cppsense-create-point-marker)))))) (defun cppsense-lookup-symbol-in-tag-file (symbol symbol-begin symbol-end) (let (file text line pos) (with-current-buffer (get-file-buffer tags-file-name) (save-excursion (goto-char (point-min)) (when (let ((case-fold-search nil)) (search-forward (concat "\177" symbol "\001") nil t)) (setq text (buffer-substring-no-properties (line-beginning-position) (match-beginning 0))) (goto-char (match-end 0)) (looking-at "\\([0-9]+\\),\\([0-9]+\\)") (setq line (string-to-int (match-string 1))) (setq pos (string-to-int (match-string 2))) (re-search-backward "\f\n\\([^\n]+\\),[0-9]*\n") (setq file (match-string 1))))) (when file (save-excursion (with-current-buffer (find-file-noselect file) (etags-goto-tag-location (cons text (cons line pos))) (cppsense-debug "Found symbol %s in tag file." symbol) (cppsense-create-point-marker)))))) (defun cppsense-completion-handler () (if (not (bobp)) (let ((syntax (char-syntax (char-before)))) (if (save-excursion (cppsense-go-to-beginning-of-symbol) (setq cppsense-completion-symbol-beginning-position (point)) (cppsense-member-p (point))) (let* ((parent-decl (cppsense-get-parent-class))) (if (not parent-decl) (message "No completions available.") (setq cppsense-completion-candidates (cppsense-get-members-of-class parent-decl t)) (setq cppsense-completion-candidates (sort cppsense-completion-candidates (lambda (first second) (string< (downcase (plist-get first 'name)) (downcase (plist-get second 'name)))))) (when cppsense-completion-candidates (cppsense-show-completion-for-point))) t))))) (defun cppsense-show-completion-for-point () (let* ((width (max (+ 2 ; padding (apply 'max (mapcar (lambda (candidate) (length (plist-get candidate 'name))) cppsense-completion-candidates))) 15)) (height (max (min (length cppsense-completion-candidates) 20) 5)) (pixel-width (* width (frame-char-width))) (pixel-height (* ;; let's say the titlebar has the same ;; height as a text line (1+ height) (frame-char-height))) (position (cppsense-calculate-popup-position pixel-height pixel-width)) (frame-params (list (cons 'top (cdr position)) (cons 'left (car position)) (cons 'width width) (cons 'height height) (cons 'minibuffer nil) (cons 'menu-bar-lines 0) (cons 'tool-bar-lines 0) (cons 'title "Completions"))) (orig-frame (selected-frame))) (if (not cppsense-completion-frame) (setq cppsense-completion-frame (make-frame frame-params)) (modify-frame-parameters cppsense-completion-frame frame-params) (make-frame-visible cppsense-completion-frame)) (select-frame cppsense-completion-frame) (switch-to-buffer cppsense-completions-buffer) (setq cursor-type nil) (setq mode-line-format nil) (set-window-fringes nil 0 0) (if cppsense-selection-overlay ;; make sure the overlay belongs to the completion buffer if ;; it's newly created (move-overlay cppsense-selection-overlay (point-min) (point-min)) (setq cppsense-selection-overlay (make-overlay (point-min) (point-min))) (overlay-put cppsense-selection-overlay 'face 'highlight)) (redirect-frame-focus cppsense-completion-frame orig-frame) (select-frame orig-frame) (add-hook 'pre-command-hook 'cppsense-pre-command) (add-hook 'post-command-hook 'cppsense-post-command)) (setq cppsense-saved-keys nil) (dolist (binding `((,(kbd "") . cppsense-next-line) (,(kbd "") . cppsense-previous-line) (,(kbd "") . cppsense-next-page) (,(kbd "") . cppsense-previous-page) (,(kbd "") . cppsense-completion-cancel) (,(kbd "") . cppsense-completion-insert-selection))) (let ((key (car binding)) (command (cdr binding))) (push (cons key (lookup-key (current-local-map) key)) cppsense-saved-keys) (define-key (current-local-map) key command))) (cppsense-update-completion-list) (setq cppsense-completion-just-started t)) (defun cppsense-pre-command () (unless (or (eq this-command 'handle-switch-frame) (memq this-command cppsense-completion-editing-commands) (get this-command 'cppsense-allowed-during-completion)) (cppsense-completion-cleanup))) (defun cppsense-post-command () (if cppsense-completion-just-started (setq cppsense-completion-just-started nil) (if (or (< (point) cppsense-completion-symbol-beginning-position) (and (eq this-command 'self-insert-command) (let ((syntax (char-syntax (char-before)))) (not (or (eq syntax ?w) (eq syntax ?_)))))) (cppsense-completion-cleanup) (if (memq this-command cppsense-completion-editing-commands) (cppsense-update-completion-list))))) (defun cppsense-update-completion-list () (let* ((orig-frame (selected-frame)) (filter (if (eq (selected-frame) cppsense-completion-frame) "" (downcase (buffer-substring cppsense-completion-symbol-beginning-position (point))))) (len (length filter))) (unwind-protect (progn (select-frame cppsense-completion-frame) (switch-to-buffer cppsense-completions-buffer) (erase-buffer) (dolist (candidate cppsense-completion-candidates) (if (or (equal "" filter) (string-match filter (plist-get candidate 'name))) (insert (plist-get candidate 'name) "\n"))) (goto-char (point-min)) (cppsense-mark-current-line)) (select-frame orig-frame)))) (defun cppsense-completion-cleanup () (remove-hook 'pre-command-hook 'cppsense-pre-command) (remove-hook 'post-command-hook 'cppsense-post-command) (make-frame-invisible cppsense-completion-frame) (dolist (binding cppsense-saved-keys) (define-key (current-local-map) (car binding) (cdr binding))) (dolist (candidate cppsense-completion-candidates) (move-marker (plist-get candidate 'declaration) nil)) (tooltip-hide) (with-current-buffer cppsense-completions-buffer (setq cursor-type t) (kill-local-variable 'mode-line-format))) (defun cppsense-mark-current-line () (let ((orig-frame (selected-frame))) (unwind-protect (progn (select-frame cppsense-completion-frame) (move-overlay cppsense-selection-overlay (line-beginning-position) (1+ (line-end-position)))) (select-frame orig-frame)))) (defun cppsense-next-line () (interactive) (cppsense-move-selection (lambda () (forward-line 1)))) (put 'cppsense-next-line 'cppsense-allowed-during-completion t) (defun cppsense-previous-line () (interactive) (cppsense-move-selection (lambda () (forward-line -1)))) (put 'cppsense-previous-line 'cppsense-allowed-during-completion t) (defun cppsense-next-page () (interactive) (cppsense-move-selection (lambda () (condition-case nil (scroll-up) (end-of-buffer (goto-char (point-max))))))) (put 'cppsense-next-page 'cppsense-allowed-during-completion t) (defun cppsense-previous-page () (interactive) (cppsense-move-selection (lambda () (condition-case nil (scroll-down) (beginning-of-buffer (goto-char (point-min))))))) (put 'cppsense-previous-page 'cppsense-allowed-during-completion t) (defun cppsense-move-selection (func) (interactive) (tooltip-hide) (let ((orig-frame (selected-frame))) (unwind-protect (progn (select-frame cppsense-completion-frame) (funcall func) (if (eobp) (forward-line -1)) (cppsense-mark-current-line)) (select-frame orig-frame))) (with-current-buffer cppsense-completions-buffer (unless (eq (overlay-start cppsense-selection-overlay) (overlay-end cppsense-selection-overlay)) (let* ((selection (buffer-substring-no-properties (overlay-start cppsense-selection-overlay) (1- (overlay-end cppsense-selection-overlay)))) (marker (some (lambda (candidate) (if (equal selection (plist-get candidate 'name)) (or (plist-get candidate 'definition) (plist-get candidate 'declaration)))) cppsense-completion-candidates))) (if (and marker (sit-for 0.5)) (let ((orig-frame (selected-frame))) (unwind-protect (progn (select-frame cppsense-completion-frame) (save-excursion (end-of-line) (cppsense-show-popup-help (cppsense-get-code-context-from-marker marker)))) (select-frame orig-frame)))))))) (defun cppsense-completion-insert-selection () (interactive) (let ((p (point))) (cppsense-go-to-beginning-of-symbol) (delete-region (point) p)) (insert (with-current-buffer cppsense-completions-buffer (buffer-substring (overlay-start cppsense-selection-overlay) (1- (overlay-end cppsense-selection-overlay)))))) (defun cppsense-completion-cancel () ;; post command hook will take care of it (interactive)) (defun cppsense-try-automatic-completion () (if (and (or (eq this-command 'self-insert-command) (eq this-command 'c-electric-backspace)) cppsense-completion-frame (not (frame-visible-p cppsense-completion-frame)) (sit-for 0.5)) (cppsense-completion-handler))) (defun cppsense-nothing-handler () (let ((other-file (cppsense-get-other-file))) (if other-file (find-file other-file) (error "Can't find other file.")))) (defun cppsense-get-other-file () (let* ((file (file-name-nondirectory (buffer-file-name))) (name (file-name-sans-extension file)) (ext (file-name-extension file)) (other-extensions (cond ((member ext cppsense-header-extensions) cppsense-source-extensions) ((member ext cppsense-source-extensions) cppsense-header-extensions) (t (error "Unknown extension %s." ext))))) (some (lambda (ext) (let ((other-file (concat name "." ext))) (if (file-readable-p other-file) other-file (let ((directories (gethash other-file cppsense-filename-hash))) (when directories (if (second directories) (message (concat "There are more than one possible candidates. " "Choosing the first one."))) (concat (car directories) "/" other-file)))))) other-extensions))) (defun cppsense-completing-read (&rest args) "Same as completing-read, but completes list of candidates immediately." (let ((unread-command-events (cons ?\t unread-command-events))) (apply 'completing-read args))) (defun cppsense-debug (string &rest args) (if cppsense-debug (apply 'message string args))) (defun cppsense-color-string-background (oldstr color) (interactive) (let ((prevpos 0) pos (str (copy-sequence oldstr)) (continue t)) (while continue (setq pos (next-single-property-change prevpos 'face str)) (unless pos (setq pos (length str)) (setq continue nil)) (let ((face (get-text-property prevpos 'face str))) (put-text-property prevpos pos 'face (list (cons 'background-color color) (cons 'foreground-color (if face (face-foreground face)))) str)) (setq prevpos pos)) str)) (defun cppsense-show-popup-help (message) (let* ((old-propertize (symbol-function 'propertize)) (x-max-tooltip-size '(120 . 40)) (lines (split-string message "\n")) (tooltip-width (* (frame-char-width) (apply 'max (mapcar 'length lines)))) (tooltip-height (* (frame-char-height) (min (length lines) (cdr x-max-tooltip-size)))) (xy (cppsense-calculate-popup-position tooltip-height tooltip-width)) (tooltip-frame-parameters (append `((left . ,(car xy)) (top . ,(cdr xy))) tooltip-frame-parameters))) ;; move the mouse cursor from the way ;(set-mouse-position (selected-frame) 0 100) ;; the definition of `propertize' is substituted with a dummy ;; function temporarily, so that tooltip-show doesn't override the ;; properties of msg (fset 'propertize (lambda (string &rest properties) string)) (unwind-protect (tooltip-show message) (fset 'propertize old-propertize)))) (defun cppsense-calculate-popup-position (height width) (let* ((point-pos (posn-at-point)) (point-xy (posn-x-y point-pos)) (x (let ((x (+ (car point-xy) (frame-parameter nil 'left)))) (if (> (+ x width) (x-display-pixel-width)) (- (x-display-pixel-width) width 10) x))) (y (let* ((point-y (+ (cdr point-xy) (frame-parameter nil 'top))) (y (- point-y height))) (if (< y 0) (+ point-y (* 4 (frame-char-height))) y)))) (cons x y))) (defun cppsense-truncate-path (path &optional length) "If PATH is too long truncate some components from the beginning." (let ((maxlength (if length length 70))) (if (<= (length path) maxlength) path (let* ((components (reverse (split-string path "/"))) (tmppath (car components))) (setq components (cdr components)) (while (and components (< (length tmppath) maxlength)) (setq path tmppath) (setq tmppath (concat (car components) "/" tmppath)) (setq components (cdr components))) (concat ".../" path))))) (defun cppsense-within-function-or-class () (save-excursion (while (and (not (bobp)) (not (eq (char-after (line-beginning-position)) ?{)) (not (eq (char-after (line-beginning-position)) ?}))) (forward-line -1)) (eq (char-after (line-beginning-position)) ?{))) (defun cppsense-within-function () (and (cppsense-within-function-or-class) (save-excursion (c-beginning-of-defun) (not (looking-at "class"))))) (defun cppsense-within-class () (and (cppsense-within-function-or-class) (save-excursion (c-beginning-of-defun) (looking-at "class")))) (defun cppsense-get-symbol-name (marker) (with-current-buffer (marker-buffer marker) (save-excursion (goto-char marker) (if (looking-at cppsense-symbol-regexp) (match-string 1) (cppsense-debug "cppsense-get-symbol-name: no match: %s" marker))))) (defun cppsense-get-symbol-begin (marker) (with-current-buffer (marker-buffer marker) (save-excursion (goto-char marker) (cppsense-get-symbol-begin-at-point)))) (defun cppsense-get-symbol-end (marker) (with-current-buffer (marker-buffer marker) (save-excursion (goto-char marker) (cppsense-get-symbol-end-at-point)))) (defun cppsense-get-symbol-begin-at-point () (save-excursion (cppsense-go-to-beginning-of-symbol) (point))) (defun cppsense-go-to-beginning-of-symbol () (skip-syntax-backward "w_")) (defun cppsense-get-symbol-end-at-point () (save-excursion (cppsense-go-to-end-of-symbol) (point))) (defun cppsense-go-to-end-of-symbol () (skip-syntax-forward "w_")) (defun cppsense-create-point-marker () (let ((marker (point-marker))) (push marker cppsense-markers) marker)) (defun cppsense-destroy-markers () (dolist (marker cppsense-markers) (move-marker marker nil))) (defun cppsense-get-code-context-from-marker (marker) (with-current-buffer (marker-buffer marker) (save-excursion (goto-char marker) (concat (cppsense-color-string-background (concat (cppsense-truncate-path (buffer-file-name)) ":\n") "moccasin") "\n" (buffer-substring (save-excursion (forward-line -5) (point)) (line-beginning-position)) (cppsense-color-string-background (buffer-substring (line-beginning-position) (1+ (line-end-position))) "honeydew2") (buffer-substring (1+ (line-end-position)) (save-excursion (forward-line +5) (point))))))) (provide 'cppsense)