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 [id://538603].
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 orig-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-message))))
;(setq message-displayer-cache nil)
;(defadvice message-displayer (around singleton-displayer)
; (let ((cached-function (assoc (ad-get-arg 1) message-displayer-cache)))
; (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 data."
(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 file.")
(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)
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
#!...
package main;
use strict;
use warnings;
use WWW::Mechanize ();
use File::Slurp qw( read_file write_file );
use Mail::Sendmail qw( sendmail );
use constant BALANCE_LOGIN =>
...;
use constant USERID => ...;
use constant USERPIN => ...;
use constant BALANCE_FILE => ...;
use constant BALANCE_URL =>
...;
use constant PHONENUMBER => ...;
use constant NOTIFY_URL =>
...;
my $old_balance = eval { return read_file(BALANCE_FILE) };
my $current_balance = eval {
my $agent = WWW::Mechanize->new( autocheck => 1 );
$agent->env_proxy();
$agent->get(BALANCE_LOGIN);
my $form = $agent->current_form;
$form->value( SignOnID => USERID );
$form->value( Password => USERPIN );
$agent->submit();
$agent->get(BALANCE_URL);
local ($_) = $agent->content =~ /\$([\d.,]+)/mx;
tr/,//d;
return $_;
};
exit unless length $current_balance;
write_file( BALANCE_FILE, $current_balance );
my $difference = $current_balance - $old_balance;
if ( 0 != $difference ) {
my $message = "\$$current_balance "
. (
$difference > 0
? "+$difference"
: $difference
);
print "$message\n";
my $agent = WWW::Mechanize->new;
$agent->get(NOTIFY_URL);
$agent->form_name('composeForm');
my $form = $agent->current_form;
$form->value( phoneNumber => PHONENUMBER );
$form->value( message => $message );
$agent->submit();
}⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊
(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-string)))))
(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.
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."Thanks. I updated it inline. I forgot that (mark-active) was an XEmacs compatibility macro I wrote for use with something else.
⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊
perlmonks.org content © perlmonks.org and calin, chb, diotalevi, educated_foo, sfink, swampyankee
prlmnks.org © 2006 edmund von der burg (eccles & toad)
v 0.03