;;; sgrep.el --- run sgrep and parse the output ;; Copyright 2005 Timo Korvola. ;;; Commentary: ;; An interface to the sgrep search tool. This was not adapted from ;; `compile' because I did not want to use a shell command: it is ;; easier if the shell does not get to mangle the sgrep expression. ;; Also, I wanted to access both the beginnings and ;; ends of sgrep matches, something `compile' is apparently not able ;; to do. ;; Searching is currently limited to a single file. This is just a ;; limitation of how the `sgrep' function takes its parameters; ;; everything else should be able to cope with multiple files (but not ;; with sgrep stream mode). ;; Note that sgrep does not know what a line is. It reports matches ;; as character positions. These become outdated even more quickly ;; than line numbers if you edit the file. Perhaps one day I will ;; learn to use extents and do this right. ;;; Code: (defvar sgrep-output-format "%f:(%i,%j):%r\n" "Output format which will be passed to sgrep as the -o option. Must end with newline and `sgrep-parse-regexp' must to be able to parse the output.") (defvar sgrep-parse-regexp "^\\(.*\\):(\\([0-9]*\\),\\([0-9]*\\)):" "Regexp to match sgrep output. Should start with ^. See also `sgrep-output-format'. This regexp can be used both for moving to the next or previous sgrep match and for parsing: \\1 should pick the filename, \\2 the start position and \\3 the end position. Positions are relative to the file and zero-based (sgrep likes it that way).") (defun sgrep-goto-match () "Go to the sgrep match on the line under point. Point must be in the sgrep buffer on a line with `sgrep-parse-regexp' will match. Point will be in another window at the start of the sgrep match and mark at the end." (interactive) (save-excursion (beginning-of-line) (unless (looking-at sgrep-parse-regexp) (error "Cannot parse sgrep match"))) (let ((file (match-string 1)) (start (+ 1 (string-to-int (match-string 2)))) (end (+ 2 (string-to-int (match-string 3))))) (find-file-other-window file) (goto-char start) (push-mark end))) (defun sgrep-next-match () "Go to the next sgrep match." (interactive) (forward-char) (search-forward-regexp sgrep-parse-regexp) (beginning-of-line)) (defun sgrep-previous-match () "Go to the previous sgrep match." (interactive) (search-backward-regexp sgrep-parse-regexp)) (defvar sgrep-mode-map (let ((map (make-sparse-keymap))) (define-key map "n" 'sgrep-next-match) (define-key map "p" 'sgrep-previous-match) (define-key map "\C-m" 'sgrep-goto-match) map) "Keymap for `sgrep-mode'.") (defvar sgrep-mode-hook nil "List of hook functions run by `sgrep-mode'.") (defun sgrep-mode () "Major mode for sgrep output. \\{sgrep-mode-map} Runs `sgrep-mode-hook'." (interactive) (kill-all-local-variables) (use-local-map sgrep-mode-map) (setq major-mode 'sgrep-mode mode-name "Sgrep") (run-hooks sgrep-mode-hook)) (defun sgrep (exp file) "Run sgrep to search for EXP in FILE. See sgrep(1) for the syntax of EXP." (interactive "sExpression: \nfFile: ") (save-some-buffers) (let ((exit-status (let ((buf (get-buffer-create "*sgrep*")) (dirname (file-name-directory file)) (basename (file-name-nondirectory file))) (save-excursion (set-buffer buf) (erase-buffer) (sgrep-mode) (display-buffer buf) (cd dirname) (call-process "sgrep" nil t t "-o" sgrep-output-format exp basename))))) (case exit-status ((0) (message "Done.")) ((1) (message "No match.")) ((2) (error "Syntax or I/O error in sgrep")) (t (error "Abnormal exit by sgrep (%s)" exit-status))))) (provide 'sgrep)