;;; find-command --- find the source of the command-at-point DWIM-style ;;; ;;; Commentary: ;;; ;;; This file: ;;; ;;; http://user.it.uu.se/~embe8573/conf/emacs-init/ide/find-command.el ;;; ;;; Evaluate this to go to the install instructions: ;;; ;;; (progn (search-forward "How to get it to work:") (recenter 0)) ;;; ;;; When programming, this is a common ;;; process/situation: ;;; ;;; i) In order have modular, well-organized and ;;; manageable code, one splits the code up into ;;; several files based on some ;;; common denominator. ;;; ;;; ii) But do that every day, one ends up with ;;; hundreds of files in squiggly ;;; nested directories. ;;; ;;; iii) Because all code gets tangled up and ;;; interdependent, which is a good thing, a bad ;;; thing nevertheless is that working with such ;;; code implies jumping between code files ;;; constantly looking for some function that is ;;; used from some other function, in another ;;; file, in order to find out what is going on. ;;; ;;; There are many creative ways to navigate between ;;; *files* (i.e., not individual function ;;; definitions). Here is what I use for Emacs and ;;; zsh: ;;; ;;; http://user.it.uu.se/~embe8573/conf/emacs-init/navigate-fs-keys.el ;;; http://user.it.uu.se/~embe8573/conf/.zsh/navigate-fs ;;; ;;; As you see, paths and commands are setup ;;; explicitly, which isn't necessarily bad. However, ;;; this program attempts something more ambitious: ;;; ;;; 1) Instead of (only) jumping between *files*, ;;; the program should jump to the file (if ;;; necessary) *and*, in particular, it should ;;; find the sought-after function definition. ;;; ;;; 2) Also, there shouldn't be any metadata - ;;; neither setup manually as with "navigate-fs", ;;; nor automatically generated metadata (which ;;; is another common way to tackle this problem ;;; by the way). The program should *only* use ;;; the source, just as Luke - realistically - ;;; only used the force when he blew up the ;;; Death Star. ;;; ;;; 3) The program should do this (find a function ;;; definition) transparently of mode, ;;; do-what-I-mean style. If the source is Elisp, ;;; and the function is `lore-and-legend', it ;;; shouldn't take you to a zsh function tho it ;;; might share the name of lore and legend. ;;; ;;; 4) The interface should be ultra-fast, involving ;;; a minimal of short, close keystrokes. ;;; ;;; One way to use it should be to position point ;;; at the start of a function name, hit ;;; a keystroke, and the program should take you ;;; wherever you need to go. ;;; ;;; Another way should be: hit a mode-specific ;;; keystroke and type the function name. ;;; ;;; (The actual shortcuts are left to the user to ;;; define. Let's just say, without them, this ;;; package isn't half as fun. Or good. ;;; For example, the author has `C-o f' for ;;; `find-command-dwim' and `C-o z' for ;;; `find-command-zsh'.) ;;; ;;; How to get it to work: ;;; ;;; a) For the `thing-at-point' interface to work, you ;;; need the function `get-search-string' from: ;;; ;;; http://user.it.uu.se/~embe8573/conf/emacs-init/get-search-string.el ;;; ;;; b) For the `find-command-zsh' to work, you need: ;;; ;;; http://user.it.uu.se/~embe8573/conf/.zsh/find-command ;;; ;;; The environmental variable COMMAND_FILE must ;;; be set; with zsh, for Emacs to see it, set it ;;; in ~/.zshenv, e.g. ;;; ;;; export COMMAND_FILE=~/.some-file ;;; ;;; Bugs, issues, misnomers, possible confusion... ;;; ;;; - For the program to work with the Emacs ;;; source (i.e., not only the user's init file ;;; defuns), the Emacs source must be obtained. ;;; On a Debian system and Emacs 24, the code ;;; is in the package emacs24-el. But getting ;;; it isn't enough; one needs also unpack its ;;; contents - every file, from x.el.gz into ;;; plain x.el. Again on a Debian system and ;;; Emacs 24, this can be done in ;;; /usr/share/emacs/24.4/lisp on all .gz ;;; files recursively. ;;; ;;; - The word "command" in `find-command-elisp' ;;; is not in the Emacs sense (i.e., ;;; interactive functions only) - ;;; non-interactive functions can be found as ;;; well with this tool. ;;; ;;; - For the program to work, one has to write ;;; the target code so that the regexps will ;;; match it. The author simply made it work ;;; the way he writes it in (so far) zsh and ;;; Elisp, neither ways are radical in any way. ;;; Because there is no parsing here; only ;;; regexps are at work. The user might as well ;;; consider this as motivation to use clear ;;; style when coding. :) ;;; ;;; Code: (require 'cl-macs) (require 'window-new) ; get this here: http://user.it.uu.se/~embe8573/conf/emacs-init/window.el (require 'get-search-string) ; don't have this? evaluate me: (goto-line 86) (defun file-to-string (file) "Put the contents of FILE into a string and return it." (interactive "Ffile: ") (with-temp-buffer (insert-file-contents file) (buffer-string) )) (defun shell-command-silent (command) "Execute a shell COMMAND with no output to the echo-area." (process-file shell-file-name nil ; INFILE nil ; BUFFER nil ; DISPLAY shell-command-switch command) ) (defun find-command-dwim () "Find the command at point, or, if none, prompt the user. The current `major-mode' determines where to look for the command." (interactive) (cl-case major-mode (sh-mode (find-command-zsh)) (emacs-lisp-mode (find-command-elisp)) (t (message "`%s' does not compute - DIY, man!" major-mode)) )) (defalias 'find-command 'find-command-dwim) (defun find-command-elisp (&optional command) "Find the source for an Emacs Lisp COMMAND. Actually anything that can be found with `find-lisp-object-file-name' is OK." (interactive) (let*((cmd (intern (or command (get-search-string "Elisp command")))) (file (find-lisp-object-file-name cmd (symbol-function cmd))) ) (when file (find-file file) (goto-char (point-min)) (when (search-forward-regexp (format "defun %s " cmd) (point-max) t) ; NOERROR (beginning-of-line-at-top) )))) ;; This command uses 'find-zsh-command' in: ;; ~/.zsh/find-command ;; http://user.it.uu.se/~embe8573/conf/.zsh/find-command ;; COMMAND_FILE is set in: ;; ~/.zshenv ;; http://user.it.uu.se/~embe8573/conf/.zshenv (defun find-command-zsh (&optional command) "Find the source for a zsh COMMAND. This requires an external zsh script to work." (interactive) (let*((cmd (or command (get-search-string "zsh command"))) (search-command (format "find-zsh-command %s" cmd)) (file-data-path (getenv "COMMAND_FILE")) (erase-data-command (format "echo -n > %s" file-data-path)) ) (shell-command-silent erase-data-command) (shell-command-silent search-command) (message search-command) (let ((file (file-to-string file-data-path)) (case-fold-search nil) ; i.e., case sensitive search (cmd-search-string (format "%s ()" cmd)) ) (unless (string= file "") (find-file file) (goto-char (point-min)) (when (search-forward-regexp cmd-search-string (point-max) t) ; NOERROR (beginning-of-line-at-top) ))))) ;; test: ;; ;; (find-command-elisp "find-command-dwim") ;; (find-command-elisp "no-command") ;; ;; (find-command-zsh "find-zsh-command") ;; (find-command-zsh "no-command") (provide 'find-command) ;;; find-command.el ends here