11; ;; call-graph.el --- Generate call graph for c/c++ functions -*- lexical-binding : t ; -*-
22
3- ; ; Copyright (C) 2018-2025 by Huming Chen
3+ ; ; Copyright (C) 2018-2026 by Huming Chen
44
55; ; Author: Huming Chen <chenhuming@gmail.com>
66; ; URL: https://github.com/beacoder/call-graph
7- ; ; Version: 1.0.5
7+ ; ; Version: 1.0.6
88; ; Created: 2018-01-07
99; ; Keywords: programming, convenience
1010; ; Package-Requires: ((emacs "28.1") (tree-mode "1.0.0") (ivy "0.10.0") (beacon "1.3.4"))
5858; ; 1.0.3 Flash visited file location with beacon.
5959; ; 1.0.4 Disable arg number check for now.
6060; ; 1.0.5 Use Git as default search backend.
61+ ; ; 1.0.6 Fix all kinds of stability issue.
6162
6263; ;; Code:
6364
64- (require 'beacon )
6565(require 'cc-mode )
6666(require 'cl-lib )
6767(require 'desktop )
6868(require 'hierarchy )
69- (require 'ivy )
7069(require 'map )
70+ (require 'pulse )
7171(require 'seq )
7272(require 'subr-x )
7373(require 'tree-mode )
7979
8080(defgroup call-graph nil
8181 " Customization support for the `call-graph' ."
82- :version " 0. 1.0"
82+ :version " 1.0.5 "
8383 :group 'applications )
8484
8585(defcustom call-graph-initial-max-depth 2
@@ -210,8 +210,6 @@ When FUNC with args, match number of args as well."
210210 (short-func (call-graph--extract-method-name func)))
211211 (let ((location (concat file-name " :" line-nb-str))
212212 (caller nil )
213- (nb-of-func-args (call-graph--number-of-args (symbol-name func)))
214- (nb-of-reference-args nil )
215213 (short-fun-str (symbol-name short-func))
216214 (is-valid-reference t ))
217215 (with-temp-buffer
@@ -225,7 +223,7 @@ When FUNC with args, match number of args as well."
225223 (forward-line (1- line-nb))
226224 (call-graph--setq-local-mode-hook-nil data-mode)
227225 (setq imenu--index-alist nil )
228- (funcall data-mode)
226+ (delay-mode-hooks ( funcall data-mode) )
229227 (setq-local which-func-cleanup-function nil )
230228 (which-function-mode t )
231229 ; ; make sure reference contains a function call
@@ -240,14 +238,6 @@ When FUNC with args, match number of args as well."
240238 (setq is-valid-reference nil ))))))
241239 (when is-valid-reference
242240 (setq caller (call-graph--which-function))
243- ; ; disable arg number check for now
244- ; ; (setq nb-of-reference-args (call-graph--scan-func-args short-fun-str))
245- ; ; (if (and nb-of-func-args nb-of-reference-args)
246- ; ; ;; TODO: check if func has args with default value
247- ; ; ;; if not, we should use exact match here.
248- ; ; (when (= nb-of-reference-args nb-of-func-args) ; check func-args matches references-args
249- ; ; (setq caller (call-graph--which-function)))
250- ; ; (setq caller (call-graph--which-function)))
251241 (unless call-graph-display-func-args
252242 (setq caller (call-graph--extract-namespace-and-method caller)))))
253243 (when caller
@@ -375,10 +365,7 @@ If there's a string at point, use it instead of prompt."
375365(defun call-graph--trim-string (string )
376366 " Remove white spaces in beginning and ending of STRING.
377367White space here is any of: space, tab, Emacs newline (line feed, ASCII 10)."
378- (replace-regexp-in-string
379- " \\ `[ \t\n ]*" " "
380- (replace-regexp-in-string
381- " [ \t\n ]*\\ '" " " string)))
368+ (string-trim string))
382369
383370(defun call-graph--extract-namespace-and-method (full-func )
384371 " Given FULL-FUNC, return a namespace and method.
@@ -433,19 +420,17 @@ e.g: class::method(arg1, arg2) => class::method."
433420 (delete-region (point-min ) (point ))
434421 (goto-char (point-max ))
435422 (delete-region (search-backward " )" nil t ) (point-max ))
436- ; ; (message (buffer-string))
437423 (save-match-data ; ; save previous match-data and restore later
438424 ; ; Map over the elements of call-graph--pattern-replace-alist
439- ; ; (pattern, replace)
425+ ; ; Replace nested constructs iteratively until no more matches
440426 (dolist (pair call-graph--pattern-replace-alist)
441427 (let ((pattern (car pair))
442- (replace (cadr pair)))
443- (goto-char (point-min ))
444- (while (re-search-forward pattern nil t ) ; ; patttern exists
445- (goto-char (point-min )) ; ; start from begining
446- (while (re-search-forward pattern nil t ) ; ; start replacing
447- (replace-match replace t nil ))
448- (goto-char (point-min ))))) ; ; go over and do match-replace again
428+ (replace (cadr pair))
429+ (found t ))
430+ (while found
431+ (goto-char (point-min ))
432+ (setq found (re-search-forward pattern nil t ))
433+ (when found (replace-match replace t nil )))))
449434 ; ; all noise cleared, count number of args
450435 (let ((args-string (call-graph--trim-string (buffer-string ))))
451436 (cond ((string= " " args-string) 0 )
@@ -511,7 +496,9 @@ e.g: class::method(arg1, arg2) => class::method."
511496 (find-file-read-only-other-window file-name)
512497 (with-no-warnings (goto-char (point-min ))
513498 (forward-line (1- line-nb))
514- (beacon-blink))
499+ (if (fboundp 'beacon-blink )
500+ (beacon-blink)
501+ (pulse-momentary-highlight-one-line (point ))))
515502 (unless (member
516503 (buffer-name (window-buffer ))
517504 (cl-loop for buffer in call-graph--previous-buffers
@@ -543,15 +530,16 @@ e.g: class::method(arg1, arg2) => class::method."
543530
544531(defun call-graph--widget-depth-imp (tree &optional depth )
545532 " Return `DEPTH' of `TREE' ."
546- (if-let ((depth (or depth 0 ))
547- (is-valid-tree (tree-widget-p tree))
548- (is-tree-open (widget-get tree :open )))
549- (progn
550- ; ; (message "Depth of %s is %d" (widget-get (tree-widget-node tree) :tag) depth)
551- (seq-max
552- (seq-map (lambda (child ) (call-graph--widget-depth-imp child (1+ depth)))
553- (widget-get tree :children ))))
554- (if is-valid-tree depth (1- depth))))
533+ (let ((depth (or depth 0 ))
534+ (is-valid-tree (tree-widget-p tree)))
535+ (if (and is-valid-tree (widget-get tree :open ))
536+ (let ((children (widget-get tree :children )))
537+ (if children
538+ (seq-max
539+ (seq-map (lambda (child ) (call-graph--widget-depth-imp child (1+ depth)))
540+ children))
541+ depth))
542+ (if is-valid-tree depth (1- depth)))))
555543
556544(defun call-graph--save-caller-cache ()
557545 " Save caller cache by saving `call-graph--caller-cache-alist' in .emacs.desktop file."
@@ -638,7 +626,7 @@ CALCULATE-DEPTH is used to calculate actual depth."
638626 (lambda (tree-item _ )
639627 (let ((caller (symbol-name tree-item))
640628 (parent (or (hierarchy-parent call-graph--default-hierarchy tree-item) 'root-function )))
641- (insert (propertize caller 'caller-name tree-item 'callee-name parent 'intangible t ))))
629+ (insert (propertize caller 'caller-name tree-item 'callee-name parent))))
642630 (call-graph--get-buffer)))
643631 (when switch-buffer
644632 (switch-to-buffer-other-window hierarchy-buffer))
@@ -733,15 +721,20 @@ This works as a supplement, as `Global' sometimes fail to find caller."
733721(defun call-graph--forward-to-text ()
734722 " Forward to text with callee-name."
735723 (let ((is-end-of-line (= (point ) (line-end-position ))))
736- (while (not (get-text-property (point ) 'callee-name ))
724+ (while (and (not (get-text-property (point ) 'callee-name ))
725+ (if is-end-of-line
726+ (> (point ) (point-min ))
727+ (< (point ) (point-max ))))
737728 (if is-end-of-line (backward-char 1 )
738729 (forward-char )))))
739730
740731(defun call-graph--forward-to-button ()
741732 " Forward to button."
742733 (beginning-of-line )
743- (while (not (get-char-property (point ) 'button ))
744- (forward-char )))
734+ (let ((eol (line-end-position )))
735+ (while (and (not (get-char-property (point ) 'button ))
736+ (< (point ) eol))
737+ (forward-char ))))
745738
746739(defun call-graph-goto-file-at-point ()
747740 " Go to the occurrence on the current line."
@@ -775,13 +768,16 @@ This works as a supplement, as `Global' sometimes fail to find caller."
775768 (concat (symbol-name (call-graph--extract-method-name callee)) " <- " (symbol-name caller))))
776769 (locations (call-graph--get-func-caller-location call-graph callee caller))
777770 (has-many (> (seq-length locations) 1 )))
778- (ivy-read " Caller Locations:" locations
779- :action (lambda (func-location )
780- (while (not (equal func-location (car locations)))
781- (setq locations ; put selected location upfront
782- (nconc (cdr locations) (cons (car locations) ()))))
783- (setf (map-elt (call-graph--locations call-graph) func-caller-key) locations)
784- (call-graph--visit-function func-location))))))
771+ (let ((visit-action (lambda (func-location )
772+ (while (not (equal func-location (car locations)))
773+ (setq locations ; put selected location upfront
774+ (nconc (cdr locations) (cons (car locations) ()))))
775+ (setf (map-elt (call-graph--locations call-graph) func-caller-key) locations)
776+ (call-graph--visit-function func-location))))
777+ (if (fboundp 'ivy-read )
778+ (ivy-read " Caller Locations:" locations :action visit-action)
779+ (let ((loc (completing-read " Caller Locations: " locations nil t )))
780+ (funcall visit-action loc)))))))
785781
786782(defun call-graph-remove-single-caller ()
787783 " Within buffer <*call-graph*>, remove single caller at point."
@@ -877,13 +873,15 @@ With prefix argument, discard whole caller cache."
877873 (get-text-property (point ) 'caller-name )))
878874 (unless origin-caller-name
879875 (beginning-of-line )
880- (while (null (setq origin-caller-name
881- (get-text-property (point ) 'caller-name )))
876+ (while (and (null (setq origin-caller-name
877+ (get-text-property (point ) 'caller-name )))
878+ (< (point ) (line-end-position )))
882879 (forward-char )))
883880 (call-graph--create call-graph func depth)
884881 (goto-char origin-pos)
885- (while (null (equal (get-text-property (point ) 'caller-name )
886- origin-caller-name))
882+ (while (and (null (equal (get-text-property (point ) 'caller-name )
883+ origin-caller-name))
884+ (< (point ) (point-max )))
887885 (forward-char ))
888886 (call-graph--forward-to-button)
889887 (call-graph-display-file-at-point))))
@@ -898,14 +896,16 @@ With prefix argument, discard whole caller cache."
898896 list-of-parents parent-caller-name)
899897 (unless origin-caller-name
900898 (beginning-of-line )
901- (while (null (setq origin-caller-name
902- (get-text-property (point ) 'caller-name )))
899+ (while (and (null (setq origin-caller-name
900+ (get-text-property (point ) 'caller-name )))
901+ (< (point ) (line-end-position )))
903902 (forward-char )))
904903 (cl-pushnew origin-caller-name list-of-parents)
905904 (while (null (tree-mode-root-linep))
906905 (tree-mode-goto-parent 1 )
907- (while (null (setq parent-caller-name
908- (get-text-property (point ) 'caller-name )))
906+ (while (and (null (setq parent-caller-name
907+ (get-text-property (point ) 'caller-name )))
908+ (< (point ) (line-end-position )))
909909 (forward-char ))
910910 (cl-pushnew parent-caller-name list-of-parents))
911911 (goto-char (point-min ))
@@ -916,8 +916,9 @@ With prefix argument, discard whole caller cache."
916916 (tree-mode-expand-level 1 )))
917917 (goto-char origin-pos)
918918 (end-of-line )
919- (while (null (member (get-text-property (point ) 'caller-name )
920- list-of-parents))
919+ (while (and (null (member (get-text-property (point ) 'caller-name )
920+ list-of-parents))
921+ (> (point ) (point-min )))
921922 (forward-char -1 ))
922923 (call-graph--forward-to-button)
923924 (call-graph-display-file-at-point)
@@ -955,15 +956,13 @@ With prefix argument, discard whole caller cache."
955956 buffer-read-only t
956957 show-trailing-whitespace nil )
957958 (setq-local line-move-visual t )
958- (set (make-local-variable 'inhibit-point-motion-hooks ) nil )
959959 (hack-dir-local-variables-non-file-buffer )
960960 (make-local-variable 'text-property-default-nonsticky )
961961 (push (cons 'keymap t ) text-property-default-nonsticky)
962962 (when call-graph-display-file-other-window
963963 (add-hook 'widget-move-hook 'call-graph-display-file-at-point ))
964964 (setq desktop-globals-to-save
965- (add-to-list 'desktop-globals-to-save 'call-graph--caller-cache-alist ))
966- (run-mode-hooks ))
965+ (add-to-list 'desktop-globals-to-save 'call-graph--caller-cache-alist )))
967966
968967; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
969968; ; Tests
0 commit comments