| 1 | ;;; -*- Log: hemlock.log; Package: Hemlock -*-
|
|---|
| 2 | ;;;
|
|---|
| 3 | ;;; **********************************************************************
|
|---|
| 4 | ;;; This code was written as part of the CMU Common Lisp project at
|
|---|
| 5 | ;;; Carnegie Mellon University, and has been placed in the public domain.
|
|---|
| 6 | ;;;
|
|---|
| 7 | #+CMU (ext:file-comment
|
|---|
| 8 | "$Header$")
|
|---|
| 9 | ;;;
|
|---|
| 10 | ;;; **********************************************************************
|
|---|
| 11 | ;;;
|
|---|
| 12 | ;;; File group stuff for Hemlock.
|
|---|
| 13 | ;;; Written by Skef Wholey and Rob MacLachlan.
|
|---|
| 14 | ;;;
|
|---|
| 15 | ;;; The "Compile Group" and "List Compile Group" commands in lispeval
|
|---|
| 16 | ;;; also know about groups.
|
|---|
| 17 | ;;;
|
|---|
| 18 | ;;; This file provides Hemlock commands for manipulating groups of files
|
|---|
| 19 | ;;; that make up a larger system. A file group is a set of files whose
|
|---|
| 20 | ;;; names are listed in some other file. At any given time one group of
|
|---|
| 21 | ;;; files is the Active group. The Select Group command makes a group the
|
|---|
| 22 | ;;; Active group, prompting for the name of a definition file if the group
|
|---|
| 23 | ;;; has not been selected before. Once a group has been selected once, the
|
|---|
| 24 | ;;; name of the definition file associated with that group is retained. If
|
|---|
| 25 | ;;; one wishes to change the name of the definition file after a group has
|
|---|
| 26 | ;;; been selected, one should call Select Group with a prefix argument.
|
|---|
| 27 |
|
|---|
| 28 | (in-package :hemlock)
|
|---|
| 29 |
|
|---|
| 30 | (defvar *file-groups* (make-string-table)
|
|---|
| 31 | "A string table of file groups.")
|
|---|
| 32 |
|
|---|
| 33 | (defvar *active-file-group* ()
|
|---|
| 34 | "The list of files in the currently active group.")
|
|---|
| 35 |
|
|---|
| 36 | (defvar *active-file-group-name* ()
|
|---|
| 37 | "The name of the currently active group.")
|
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 | |
|---|
| 41 |
|
|---|
| 42 | ;;;; Selecting the active group.
|
|---|
| 43 |
|
|---|
| 44 | (defcommand "Select Group" (p)
|
|---|
| 45 | "Makes a group the active group. With a prefix argument, changes the
|
|---|
| 46 | definition file associated with the group."
|
|---|
| 47 | "Makes a group the active group."
|
|---|
| 48 | (let* ((group-name
|
|---|
| 49 | (prompt-for-keyword
|
|---|
| 50 | (list *file-groups*)
|
|---|
| 51 | :must-exist nil
|
|---|
| 52 | :prompt "Select Group: "
|
|---|
| 53 | :help
|
|---|
| 54 | "Type the name of the file group you wish to become the active group."))
|
|---|
| 55 | (old (getstring group-name *file-groups*))
|
|---|
| 56 | (pathname
|
|---|
| 57 | (if (and old (not p))
|
|---|
| 58 | old
|
|---|
| 59 | (prompt-for-file :must-exist t
|
|---|
| 60 | :prompt "From File: "
|
|---|
| 61 | :default (merge-pathnames
|
|---|
| 62 | (make-pathname
|
|---|
| 63 | :name group-name
|
|---|
| 64 | :type "upd")
|
|---|
| 65 | (value pathname-defaults))))))
|
|---|
| 66 | (setq *active-file-group-name* group-name)
|
|---|
| 67 | (setq *active-file-group* (nreverse (read-file-group pathname nil)))
|
|---|
| 68 | (setf (getstring group-name *file-groups*) pathname)))
|
|---|
| 69 |
|
|---|
| 70 |
|
|---|
| 71 | ;;; READ-FILE-GROUP reads an Update format file and returns a list of pathnames
|
|---|
| 72 | ;;; of the files named in that file. This guy knows about @@ indirection and
|
|---|
| 73 | ;;; ignores empty lines and lines that begin with @ but not @@. A simpler
|
|---|
| 74 | ;;; scheme could be used for non-Spice implementations, but all this hair is
|
|---|
| 75 | ;;; probably useful, so Update format may as well be a standard for this sort
|
|---|
| 76 | ;;; of thing.
|
|---|
| 77 | ;;;
|
|---|
| 78 | (defun read-file-group (pathname tail)
|
|---|
| 79 | (with-open-file (file pathname)
|
|---|
| 80 | (do* ((name (read-line file nil nil) (read-line file nil nil))
|
|---|
| 81 | (length (if name (length name)) (if name (length name))))
|
|---|
| 82 | ((null name) tail)
|
|---|
| 83 | (declare (type (or simple-string null) name))
|
|---|
| 84 | (cond ((zerop length))
|
|---|
| 85 | ((char= (char name 0) #\@)
|
|---|
| 86 | (when (and (> length 1) (char= (char name 1) #\@))
|
|---|
| 87 | (setq tail (read-file-group
|
|---|
| 88 | (merge-pathnames (subseq name 2)
|
|---|
| 89 | pathname)
|
|---|
| 90 | tail))))
|
|---|
| 91 | (t
|
|---|
| 92 | (push (merge-pathnames (pathname name) pathname) tail))))))
|
|---|
| 93 |
|
|---|
| 94 |
|
|---|
| 95 | |
|---|
| 96 |
|
|---|
| 97 | ;;;; DO-ACTIVE-GROUP.
|
|---|
| 98 |
|
|---|
| 99 | (defhvar "Group Find File"
|
|---|
| 100 | "If true, group commands use \"Find File\" to read files, otherwise
|
|---|
| 101 | non-resident files are read into the \"Group Search\" buffer."
|
|---|
| 102 | :value nil)
|
|---|
| 103 |
|
|---|
| 104 | (defhvar "Group Save File Confirm"
|
|---|
| 105 | "If true, then the group commands will ask for confirmation before saving
|
|---|
| 106 | a modified file." :value t)
|
|---|
| 107 |
|
|---|
| 108 | (defmacro do-active-group (&rest forms)
|
|---|
| 109 | "This iterates over the active file group executing forms once for each
|
|---|
| 110 | file. When forms are executed, the file will be in the current buffer,
|
|---|
| 111 | and the point will be at the start of the file."
|
|---|
| 112 | (let ((n-buf (gensym))
|
|---|
| 113 | (n-start-buf (gensym))
|
|---|
| 114 | (n-save (gensym)))
|
|---|
| 115 | `(progn
|
|---|
| 116 | (unless *active-file-group*
|
|---|
| 117 | (editor-error "There is no active file group."))
|
|---|
| 118 |
|
|---|
| 119 | (let ((,n-start-buf (current-buffer))
|
|---|
| 120 | (,n-buf nil))
|
|---|
| 121 | (unwind-protect
|
|---|
| 122 | (dolist (file *active-file-group*)
|
|---|
| 123 | (catch 'file-not-found
|
|---|
| 124 | (setq ,n-buf (group-read-file file ,n-buf))
|
|---|
| 125 | (with-mark ((,n-save (current-point) :right-inserting))
|
|---|
| 126 | (unwind-protect
|
|---|
| 127 | (progn
|
|---|
| 128 | (buffer-start (current-point))
|
|---|
| 129 | ,@forms)
|
|---|
| 130 | (move-mark (current-point) ,n-save)))
|
|---|
| 131 | (group-save-file)))
|
|---|
| 132 | (if (member ,n-start-buf *buffer-list*)
|
|---|
| 133 | (setf (current-buffer) ,n-start-buf
|
|---|
| 134 | (window-buffer (current-window)) ,n-start-buf)
|
|---|
| 135 | (editor-error "Original buffer deleted!")))))))
|
|---|
| 136 |
|
|---|
| 137 | ;;; GROUP-READ-FILE reads in files for the group commands via DO-ACTIVE-GROUP.
|
|---|
| 138 | ;;; We use FIND-FILE-BUFFER, which creates a new buffer when the file hasn't
|
|---|
| 139 | ;;; already been read, to get files in, and then we delete the buffer if it is
|
|---|
| 140 | ;;; newly created and "Group Find File" is false. This lets FIND-FILE-BUFFER
|
|---|
| 141 | ;;; do all the work. We don't actually use the "Find File" command, so the
|
|---|
| 142 | ;;; buffer history isn't affected.
|
|---|
| 143 | ;;;
|
|---|
| 144 | ;;; Search-Buffer is any temporary search buffer left over from the last file
|
|---|
| 145 | ;;; that we want deleted. We don't do the deletion if the buffer is modified.
|
|---|
| 146 | ;;;
|
|---|
| 147 | (defun group-read-file (name search-buffer)
|
|---|
| 148 | (unless (probe-file name)
|
|---|
| 149 | (message "File ~A not found." name)
|
|---|
| 150 | (throw 'file-not-found nil))
|
|---|
| 151 | (multiple-value-bind (buffer created-p)
|
|---|
| 152 | (find-file-buffer name)
|
|---|
| 153 | (setf (current-buffer) buffer)
|
|---|
| 154 | (setf (window-buffer (current-window)) buffer)
|
|---|
| 155 |
|
|---|
| 156 | (when (and search-buffer (not (buffer-modified search-buffer)))
|
|---|
| 157 | (dolist (w (buffer-windows search-buffer))
|
|---|
| 158 | (setf (window-buffer w) (current-buffer)))
|
|---|
| 159 | (delete-buffer search-buffer))
|
|---|
| 160 |
|
|---|
| 161 | (if (and created-p (not (value group-find-file)))
|
|---|
| 162 | (current-buffer) nil)))
|
|---|
| 163 |
|
|---|
| 164 | ;;; GROUP-SAVE-FILE is used by DO-ACTIVE-GROUP.
|
|---|
| 165 | ;;;
|
|---|
| 166 | (defun group-save-file ()
|
|---|
| 167 | (let* ((buffer (current-buffer))
|
|---|
| 168 | (pn (buffer-pathname buffer))
|
|---|
| 169 | (name (namestring pn)))
|
|---|
| 170 | (when (and (buffer-modified buffer)
|
|---|
| 171 | (or (not (value group-save-file-confirm))
|
|---|
| 172 | (prompt-for-y-or-n
|
|---|
| 173 | :prompt (list "Save changes in ~A? " name)
|
|---|
| 174 | :default t)))
|
|---|
| 175 | (save-file-command ()))))
|
|---|
| 176 |
|
|---|
| 177 |
|
|---|
| 178 | |
|---|
| 179 |
|
|---|
| 180 | ;;;; Searching and Replacing commands.
|
|---|
| 181 |
|
|---|
| 182 | (defcommand "Group Search" (p)
|
|---|
| 183 | "Searches the active group for a specified string, which is prompted for."
|
|---|
| 184 | "Searches the active group for a specified string."
|
|---|
| 185 | (declare (ignore p))
|
|---|
| 186 | (let ((string (prompt-for-string :prompt "Group Search: "
|
|---|
| 187 | :help "String to search for in active file group"
|
|---|
| 188 | :default *last-search-string*)))
|
|---|
| 189 | (get-search-pattern string :forward)
|
|---|
| 190 | (do-active-group
|
|---|
| 191 | (do ((won (find-pattern (current-point) *last-search-pattern*)
|
|---|
| 192 | (find-pattern (current-point) *last-search-pattern*)))
|
|---|
| 193 | ((not won))
|
|---|
| 194 | (character-offset (current-point) won)
|
|---|
| 195 | (command-case
|
|---|
| 196 | (:prompt "Group Search: "
|
|---|
| 197 | :help "Type a character indicating the action to perform."
|
|---|
| 198 | :change-window nil)
|
|---|
| 199 | (:no "Search for the next occurrence.")
|
|---|
| 200 | (:do-all "Go on to the next file in the group."
|
|---|
| 201 | (return nil))
|
|---|
| 202 | ((:exit :yes) "Exit the search."
|
|---|
| 203 | (return-from group-search-command))
|
|---|
| 204 | (:recursive-edit "Enter a recursive edit."
|
|---|
| 205 | (do-recursive-edit)
|
|---|
| 206 | (get-search-pattern string :forward)))))
|
|---|
| 207 | (message "All files in group ~S searched." *active-file-group-name*)))
|
|---|
| 208 |
|
|---|
| 209 | (defcommand "Group Replace" (p)
|
|---|
| 210 | "Replaces one string with another in the active file group."
|
|---|
| 211 | "Replaces one string with another in the active file group."
|
|---|
| 212 | (declare (ignore p))
|
|---|
| 213 | (let* ((target (prompt-for-string :prompt "Group Replace: "
|
|---|
| 214 | :help "Target string"
|
|---|
| 215 | :default *last-search-string*))
|
|---|
| 216 | (replacement (prompt-for-string :prompt "With: "
|
|---|
| 217 | :help "Replacement string")))
|
|---|
| 218 | (do-active-group
|
|---|
| 219 | (query-replace-function nil target replacement
|
|---|
| 220 | "Group Replace on previous file" t))
|
|---|
| 221 | (message "Replacement done in all files in group ~S."
|
|---|
| 222 | *active-file-group-name*)))
|
|---|
| 223 |
|
|---|
| 224 | (defcommand "Group Query Replace" (p)
|
|---|
| 225 | "Query Replace for the active file group."
|
|---|
| 226 | "Query Replace for the active file group."
|
|---|
| 227 | (declare (ignore p))
|
|---|
| 228 | (let ((target (prompt-for-string :prompt "Group Query Replace: "
|
|---|
| 229 | :help "Target string"
|
|---|
| 230 | :default *last-search-string*)))
|
|---|
| 231 | (let ((replacement (prompt-for-string :prompt "With: "
|
|---|
| 232 | :help "Replacement string")))
|
|---|
| 233 | (do-active-group
|
|---|
| 234 | (unless (query-replace-function
|
|---|
| 235 | nil target replacement "Group Query Replace on previous file")
|
|---|
| 236 | (return nil)))
|
|---|
| 237 | (message "Replacement done in all files in group ~S."
|
|---|
| 238 | *active-file-group-name*))))
|
|---|