At work and elsewhere, I'm often called on to work on crapulous code. Stuff where it's difficult to see the extent of variables and whether two pieces of code have the same things tucked into them or not. I wrote an Emacs extension which marks up my source. The idea is that if $foo is used one line 10 and line 100, I'd like to be able to see that. Here's a first draft or prototype if you will. It's bare bones. You are expected to run this when in a buffer full of perl code. Save the buffer first.

Please, suggest things to me to help me make this more useful. Colors? What kind? On what? Blinking? The line? The variables? When it's selected? Smarter lines? If you've seen any task like this done anywhere else, tell me about it. I'm sure there are good ways to communicate this information back to the programmer. This node is partly about just posting the code so it's available and mostly about asking how to have the computer accomodate the human sitting at the keyboard.

All ideas are welcome.

Sample perl source from Random Darwin Award in plain text.

use strict; use WWW::Mechanize; >> my $agent = WWW::Mechanize->new( autocheck => 1 ); > > $agent->get('http://cgi.darwinawards.com/cgi/random.pl'); > >> my $content = $agent->content( format => "text" ); | >my $cr = chr 169; > |$content =~ s/.*\d\d\s+Urban Legend//s; > |$content =~ s/.*\d\d\s+Personal Account//s; > |$content =~ s/.*Reader Submission\s+Pending Acceptance//s; > >$content =~ s/\s*DarwinAwards\.com\s*$cr.*//s; > $content =~ s/.*?\([^\)]*?\d{2}[^\)]*\) //s; > $content =~ s/.*Darwin\s?Award\s?Nominee//si; > $content =~ s/.*Confirmed \S+\s?by Darwin//si; > $content =~ s/.*Honorable Mentions//s; > $content =~ s/submitted by.*//si; > $content =~ s/109876543210.*//s; > $content =~ s/^\s+//; | > print $content;

Here's how you can use the below code. It's nice if you save it into a separate file named something like /home/your-name/.site-lisp/b-xref.el. Add a bit of code to your ~/.emacs file and the file will be loaded automatically when you first call b-xref. You'll also notice that the name for what is being pointed to is displayed when you move the cursor or mouse over the arrows.

(add-to-list 'load-path "~/.site-lisp") (autoload 'b-xref "b-xref.el" "Cross-references perl symbols" t)

I haven't found it necessary to bind a key to this function so I just execute M-x b-xref after I've moved my cursor to the right line or selected some stuff.

(defun b-xref () (interactive) (let ((output-buffer-name (concat "*b-xref-" (buffer-name) "*"))) (let ((old-buffer (get-buffer output-buffer-name))) (if old-buffer (kill-buffer old-buffer))) (let ((point (point)) (mark (if mark-active (mark) (point))) (orig-buffer (current-buffer)) (buffer (get-buffer-create (concat "*b-xref-" (buffer-name) "*") +))) (save-excursion (save-restriction (widen) (copy-to-buffer buffer (point-min) (point-max)) (set-buffer buffer) (b-xref-mode) (buffer-disable-undo) (let ((jots (b-xref-summarize (sort (let ((xref (b-xref-buffer o +rig-buffer))) (b-xref-filter-lines xref (point->line (min point mark)) (point->line (max point mark)))) 'b-xref-alist->)))) (mapc 'b-xref-do-jots jots)) (set-buffer-modified-p nil) (toggle-read-only))) (display-buffer buffer)))) (defvar b-xref-bin "perl") (defvar b-xref-jot ">") (defvar b-xref-fill "|") (defvar b-xref-fill-space " ") (defvar b-xref-mode-map nil "Keymap for B::Xref major mode.") (if b-xref-mode-map nil (setq b-xref-mode-map (make-sparse-keymap))) (defun b-xref-mode () "Major mode for viewing B::Xref data overlayed on perl code. Special commands: \\{b-xref-mode-map}" (interactive) (kill-all-local-variables) (fundamental-mode) (setq major-mode 'b-xref-mode) (setq mode-name "B::Xref") (use-local-map b-xref-mode-map) (run-hooks 'b-xref-hook)) (defun point->line (point) (let ((line 1)) (save-excursion (goto-char (point-min)) (while (< (point) point) (incf line) (forward-line))) line)) ; (defun b-xref-summarize (xref) (loop with xref-output for (subname line pack type name event) in xref do (let ((key (list pack name))) (let ((pair (assoc key xref-output))) (if pair (let ((lines (cdr pair))) (or (member line lines) (nconc lines (list line)))) (push (cons key (list line)) xref-output)))) finally (return xref-output))) (defun b-xref-filter-lines (xref start end) (let ((to-find (loop for (subname line pack type name event) in xref when (and (>= line start) (<= line end)) collect (list pack name)))) (remove-if-not (lambda (i) (member (list (nth 2 i) (nth 4 i)) to-find)) xref))) (defun b-xref-list-> (a b) "Sorts a list so larger numbers go first, then shorter lists." (if (and (numberp (car a)) (numberp (car b))) (or (> (car a) (car b)) (and (= (car a) (car b)) (b-xref-list-> (cdr a) (cdr b)))) (and (null a) (not (null b))))) (defun b-xref-alist-> (a b) "Sorts the elements of an alist with `b-xref-list->'" (b-xref-list-> (cdr a) (cdr b))) ; (sort xref-output 'b-xref-alist->))))) (require 'cl) (defsubst min-list (list) (reduce 'min list)) (defsubst max-list (list) (reduce 'max list)) (defsubst line->point (line) (goto-line line) (point)) (defun b-xref-do-jots (pair) "Make space for jots and call `b-xref-jot-line' to place them." (string-rectangle (point-min) (progn (goto-char (point-max)) (beginning-of-line) (point)) b-xref-fill-space) (let ((name (concat (nth 0 (car pair)) ":" (nth 1 (car pair)))) (lines (cdr pair))) (let ((min-line (min-list lines)) (max-line (max-list lines))) (delete-rectangle (line->point min-line) (+ 1 (line->point max-line))) (string-rectangle (line->point min-line) (line->point max-line) b-xref-fill) (mapcar (lambda (l) (b-xref-jot-line l name)) lines)))) (defun b-xref-jot-line (line name) "Jot a note on LINE." (goto-char (line->point line)) (delete-char 1) (insert (propertize b-xref-jot 'help-echo name 'point-entered (message-displayer name)))) (defun message-displayer (message) (lexical-let ((lexical-message message)) (lambda (old-point new-point) (display-message-or-buffer lexical-m +essage)))) ;(setq message-displayer-cache nil) ;(defadvice message-displayer (around singleton-displayer) ; (let ((cached-function (assoc (ad-get-arg 1) message-displayer-cach +e))) ; (if (not cached-function) ; (push (cons (ad-get-arg 1) ad-do-it) message-displayer-cache)) ; (cdr (assoc (ad-get-arg 1) message-displayer-cache)))) ;(ad-activate 'message-displayer) (defun b-xref-buffer (buffer) "Runs a buffer through 'perl -MO=Xref,-raw' and returns the parsed d +ata." (save-excursion (save-restriction (set-buffer buffer) (widen) (goto-char (point-min)) (let ((perl (if (looking-at auto-mode-interpreter-regexp) (match-string 2) (or b-xref-bin "perl"))) (infile (if (buffer-modified-p) (error "TODO: Copy modified buffer to temp fil +e.") (buffer-file-name))) (buffer (generate-new-buffer "*b-xref-raw*"))) (let ((rc (call-process perl infile buffer nil "-MO=Xref,-raw" +))) (or (zerop rc) (error "%s exited with %d" perl rc))) (let ((xref-output (b-xref-read-raw buffer "-"))) (kill-buffer buffer) xref-output))))) (defun trim (str) (rtrim (ltrim str))) (defun ltrim (str) (replace-regexp-in-string "^ +" "" str)) (defun rtrim (str) (replace-regexp-in-string " +$" "" str)) (defun b-xref-read-raw (buffer filename) "Reads the output from 'perl -MO=Xref,-raw'." (save-excursion (save-restriction (set-buffer buffer) (widen) (goto-char (point-min)) (let (xref-output (xref-regexp (concat "^" (regexp-quote filename) (let ((pad (- 16 (length filename)))) (if (> pad 0) (make-string pad ? ) "")) " \\(............[^ \n]*\\)" " \\(.....[^ \n]*\\)" " \\(............[^ \n]*\\)" " \\(....[^ \n]*\\)" " \\(................[^ \n]*\\)" " \\([^\n]+\\)\n"))) (while (re-search-forward xref-regexp nil t) (or (bolp) (forward-line)) (let ((subname (trim (match-string 1))) (line (string-to-number (trim (match-string 2)))) (pack (trim (match-string 3))) (type (trim (match-string 4))) (name (trim (match-string 5))) (event (trim (match-string 6)))) (push (list subname line pack type name event) xref-output +))) xref-output)))) (provide 'b-xref)

Replies are listed 'Best First'.
Re: Emacs shows xrefs in code but needs an interface that's good for humans
by calin (Deacon) on Mar 27, 2006 at 09:38 UTC

    I use the built-in "occur" when I want to see where things are referenced. It's not smart but usually does its job.

    However, I'm just an amateur programmer and my needs of specialist tools may be limited. I don't have to deal with crappy code or work under pressure.

      Hey that's neat! Here's an example for anyone that's watching. I'd never seen occur before and had to try it out.

      Typing M-x occur "current_balance" gets me a new pane of info. If I move the cursor to any and hit enter, the source pane will automatically seek there.

      5 lines matching "current_balance" in buffer RBCUWatcher2. 23:my $current_balance = eval { 39:exit unless length $current_balance; 41:write_file( BALANCE_FILE, $current_balance ); 43:my $difference = $current_balance - $old_balance; 45: my $message = "\$$current_balance "

      ~/bin/BankBalanceWatcher

      ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      I've found this little gem makes occur quite a bit more useful:
      (defun isearch-occur () "Invoke `occur' from within isearch." (interactive) (let ((case-fold-search isearch-case-fold-search)) (occur (if isearch-regexp isearch-string (regexp-quote isearch-str +ing))))) (define-key isearch-mode-map (kbd "C-o") 'isearch-occur)
      Then you can isearch to the word you want, hit C-w to grab the rest of it, then C-o to get a list of occurrences.
Re: Emacs shows xrefs in code but needs an interface that's good for humans
by diotalevi (Canon) on Mar 27, 2006 at 23:35 UTC

    I described the extension to a co-worker over lunch and she said it was kind of like what Eclipse already does. Eclipse will use something that looks like a scrollbar but is clickable to indicate where the other references are. It's very cool. I don't know that Emacs even supports hacking up a new scroll-bar interface. Fooey.

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      Emacs pretty much supports hacking up just about anything. I wouldn't necessarily try it -- I think there's a 12 step program for people who get too deeply into emacs -- but speedbar is pretty handy, and I'd probably like emacs more if the Windows variant didn't randomly hang

      emc

      " The most likely way for the world to be destroyed, most experts agree, is by accident. That's where we come in; we're computer professionals. We cause accidents."
      —Nathaniel S. Borenstein
Re: Emacs shows xrefs in code but needs an interface that's good for humans
by chb (Deacon) on Mar 28, 2006 at 07:39 UTC
    Great idea, but my emacs (GNU Emacs 21.4.1) complains about (mark-active) in function b-xref, line 8: Symbols function definition is void: mark-active. It seems to be a variable here, when I change line 8 from (mark (if (mark-active) (mark) (point))) to (mark (if mark-active (mark) (point))) it works very nicely.

      Thanks. I updated it inline. I forgot that (mark-active) was an XEmacs compatibility macro I wrote for use with something else.

      ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: Emacs shows xrefs in code but needs an interface that's good for humans
by sfink (Deacon) on Mar 28, 2006 at 17:22 UTC
    I like the idea. Seems very promising. I've been wanting something vaguely like this for some time, although for a somewhat different purpose. My immediate disorganized thoughts/suggestions/requests are:

    1. When I first tried b-xref, I loaded up a random Perl file and ran M-x b-xref. It gave me a buffer containing exactly the same code. It took me a while to figure out that I had to put the point within the code to get anything out. I would recommend inserting a brief description of what the b-xref buffer is and how to use it at the top of the script.
    2. Local keybindings for navigating to next and previous occurrences of a variable.
    3. Function and/or keybinding to rename a variable (now it's a refactoring tool!)
    4. I don't understand the markup and message pairing. If I have ">>" at the beginning of a line, I seem to get: column0=$var1, column1=$var1, column2=$var2 (and at column2, point is past the > signs, so it's on an empty space). Is this off by one?
    5. Similarly, could putting the point on "|" also tell me what the variable is?
    6. Keybinding to pop up another buffer giving an occurs-like description of the full range of the variable's definitions. Or maybe a tooltip-like window that pops up when you hover?
    7. When I run b-xref with point on a variable, I'd like to go straight to that variable instance's jot.
    8. Putting the point or mouse over a variable should highlight all occurrences.
    9. Dispose of the separate buffer altogether? Have it mark up the actual buffer using colors (eg same markup scheme, but change the background colors of the leftmost columns, alternating horizontally between two colors. Invert foreground and background for '>', just set the background for '|'. Or something?) (Hmm... gud mode inserts text into the current buffer without mangling its contents; could you do the same?)
    10. Keybinding to dump out all "live" variables at any given line (byte position?)
    11. Do type inferencing, describe the type of each variable, and list available operations.
    12. Keybinding to dump out all global variables. Or global undeclared variables.
    13. Three points for each global variable you're in range of. One point per lexical. If the sum for a given line is above a threshold, have a keybinding to display a graphical animation of the code's original author being run over by a cement truck. Loop it.
    Yes, I know that several of those are... "nontrivial", I think is the word? But they're ideas, at least.