source: trunk/source/cocoa-ide/hemlock/src/morecoms.lisp @ 8428

Last change on this file since 8428 was 8428, checked in by gz, 12 years ago

Merge of the 'event-ide' branch. Hemlock's thread model has been changed
so that Hemlock commands now run in the Cocoa event thread -- see the
Hemlock file view.lisp for an overview.

IDE compilation has also been reorganized. Hemlock is now more fully
integrated into the IDE and cannot be compiled separately, sorry.

The hemlock-ext package has been repurposed to contain all interfaces
to window-system specific functionality.

There are also many many assorted other changes, cleanups and fixes.

The Hemlock documentation (Hemlock Command Implementor's Manual) in
http://trac.clozure.com/openmcl/wiki now correctly reflects the
implementation, although it doesn't (yet) describe the integration
with Cocoa or the threading model.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.0 KB
Line 
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;;; Written by Bill Chiles and Rob MacLachlan.
13;;;
14;;; Even more commands...
15
16(in-package :hemlock)
17
18(defhvar "Region Query Size"
19  "A number-of-lines threshold that destructive, undoable region commands
20   should ask the user about when the indicated region is too big."
21  :value 30)
22
23(defun check-region-query-size (region)
24  "Checks the number of lines in region against \"Region Query Size\" and
25   asks the user if the region crosses this threshold.  If the user responds
26   negatively, then an editor error is signaled."
27  (let ((threshold (or (value region-query-size) 0)))
28    (if (and (plusp threshold)
29             (>= (count-lines region) threshold)
30             (not (prompt-for-y-or-n
31                   :prompt "Region size exceeds \"Region Query Size\".  Confirm: "
32                   :must-exist t)))
33        (editor-error))))
34
35;;; Do nothing, but do it well ...
36(defcommand "Do Nothing" (p)
37  "Do nothing."
38  "Absolutely nothing."
39  (declare (ignore p)))
40
41
42(defcommand "Abort Command" (p)
43  "Abort reading a command in current view"
44  "Aborts c-q, multi-key commands (e.g. c-x), prefix translation (e.g.
45ESC as Meta-), prefix arguments (e.g. c-u), ephemeral modes such as
46i-search, and prompted input (e.g. m-x)"
47  (declare (ignore p))
48  (abort-to-toplevel))
49
50;;;; Casing commands...
51
52(defcommand "Uppercase Word" (p)
53  "Uppercase a word at point.
54   With prefix argument uppercase that many words."
55  "Uppercase p words at the point."
56  (filter-words p (current-point) #'string-upcase))
57
58(defcommand "Lowercase Word" (p)
59  "Uppercase a word at point.
60   With prefix argument uppercase that many words."
61  "Uppercase p words at the point."
62  (filter-words p (current-point) #'string-downcase))
63
64;;; FILTER-WORDS implements "Uppercase Word" and "Lowercase Word".
65;;;
66(defun filter-words (p point function)
67  (let ((arg (or p 1)))
68    (with-mark ((mark point))
69      (if (word-offset (if (minusp arg) mark point) arg)
70          (filter-region function (region mark point))
71          (editor-error "Not enough words.")))))
72
73;;; "Capitalize Word" is different than uppercasing and lowercasing because
74;;; the differences between Hemlock's notion of what a word is and Common
75;;; Lisp's notion are too annoying.
76;;;
77(defcommand "Capitalize Word" (p)
78  "Lowercase a word capitalizing the first character.  With a prefix
79  argument, capitalize that many words.  A negative argument capitalizes
80  words before the point, but leaves the point where it was."
81  "Capitalize p words at the point."
82  (let ((point (current-point))
83        (arg (or p 1)))
84    (with-mark ((start point :left-inserting)
85                (end point))
86      (when (minusp arg)
87        (unless (word-offset start arg) (editor-error "No previous word.")))
88      (do ((region (region start end))
89           (cnt (abs arg) (1- cnt)))
90          ((zerop cnt) (move-mark point end))
91        (unless (find-attribute start :word-delimiter #'zerop)
92          (editor-error "No next word."))
93        (move-mark end start)
94        (find-attribute end :word-delimiter)
95        (loop
96          (when (mark= start end)
97            (move-mark point end)
98            (editor-error "No alphabetic characters in word."))
99          (when (alpha-char-p (next-character start)) (return))
100          (character-offset start 1))
101        (setf (next-character start) (char-upcase (next-character start)))
102        (hi::buffer-note-modification (current-buffer) start 1)
103        (mark-after start)
104        (filter-region #'string-downcase region)))))
105
106(defcommand "Uppercase Region" (p)
107  "Uppercase words from point to mark."
108  "Uppercase words from point to mark."
109  (declare (ignore p))
110  (twiddle-region (current-region) #'string-upcase "Uppercase Region"))
111
112(defcommand "Lowercase Region" (p)
113  "Lowercase words from point to mark."
114  "Lowercase words from point to mark."
115  (declare (ignore p))
116  (twiddle-region (current-region) #'string-downcase "Lowercase Region"))
117
118;;; TWIDDLE-REGION implements "Uppercase Region" and "Lowercase Region".
119;;;
120(defun twiddle-region (region function name)
121  (let* (;; don't delete marks start and end since undo stuff will.
122         (start (copy-mark (region-start region) :left-inserting))
123         (end (copy-mark (region-end region) :left-inserting)))
124    (let* ((region (region start end))
125           (undo-region (copy-region region)))
126      (filter-region function region)
127      (make-region-undo :twiddle name region undo-region))))
128
129
130
131;;;; More stuff.
132
133(defcommand "Delete Previous Character Expanding Tabs" (p)
134  "Delete the previous character.
135  When deleting a tab pretend it is the equivalent number of spaces.
136  With prefix argument, do it that many times."
137  "Delete the P previous characters, expanding tabs into spaces."
138  (let* ((buffer (current-buffer))
139         (region (hi::%buffer-current-region buffer)))
140    (if region
141      (delete-region region)
142      (let ((point (current-point))
143            (n (or p 1)))
144        (when (minusp n)
145          (editor-error "Delete Previous Character Expanding Tabs only accepts ~
146                     positive arguments."))
147        ;; Pre-calculate the number of characters that need to be deleted
148        ;; and any remaining white space filling, allowing modification to
149        ;; be avoided if there are not enough characters to delete.
150        (let ((errorp nil)
151              (del 0)
152              (fill 0))
153          (with-mark ((mark point))
154            (dotimes (i n)
155              (if (> fill 0)
156                (decf fill)
157                (let ((prev (previous-character mark)))
158                  (cond ((and prev (char= prev #\tab))
159                         (let ((pos (mark-column mark)))
160                           (mark-before mark)
161                           (incf fill (- pos (mark-column mark) 1)))
162                         (incf del))
163                        ((mark-before mark)
164                         (incf del))
165                        (t
166                         (setq errorp t)
167                         (return)))))))
168          (cond ((and (not errorp) (kill-characters point (- del)))
169                 (with-mark ((mark point :left-inserting))
170                   (dotimes (i fill)
171                     (insert-character mark #\space))))
172                (t
173                 (editor-error "There were not ~D characters before point." n))))))))
174
175
176(defvar *scope-table*
177  (list (make-string-table :initial-contents
178                           '(("Global" . :global)
179                             ("Buffer" . :buffer)
180                             ("Mode" . :mode)))))
181
182(defun prompt-for-place (prompt help)
183  (multiple-value-bind (word val)
184                       (prompt-for-keyword :tables *scope-table*
185                                           :prompt prompt
186                                           :help help :default "Global")
187    (declare (ignore word))
188    (case val
189      (:buffer
190       (values :buffer (prompt-for-buffer :help "Buffer to be local to."
191                                          :default (current-buffer))))
192      (:mode
193       (values :mode (prompt-for-keyword 
194                      :tables (list *mode-names*)
195                      :prompt "Mode: "
196                      :help "Mode to be local to."
197                      :default (buffer-major-mode (current-buffer)))))
198      (:global :global))))
199
200(defcommand "Bind Key" (p)
201  "Bind a command to a key.
202  The command, key and place to make the binding are prompted for."
203  "Prompt for stuff to do a bind-key."
204  (declare (ignore p))
205  (multiple-value-call #'bind-key 
206    (values (prompt-for-keyword
207             :tables (list *command-names*)
208             :prompt "Command to bind: "
209             :help "Name of command to bind to a key."))
210    (values (prompt-for-key 
211             :must-exist nil
212             :prompt "Bind to: "
213             :help "Key to bind command to, confirm to complete."))
214    (prompt-for-place "Kind of binding: "
215                      "The kind of binding to make.")))
216
217(defcommand "Delete Key Binding" (p)
218  "Delete a key binding.
219  The key and place to remove the binding are prompted for."
220  "Prompt for stuff to do a delete-key-binding."
221  (declare (ignore p))
222  (let ((key (prompt-for-key 
223              :must-exist nil
224              :prompt "Delete binding: "
225              :help "Key to delete binding from.")))
226    (multiple-value-bind (kind where)
227                         (prompt-for-place "Kind of binding: "
228                                           "The kind of binding to make.")
229      (unless (get-command key kind where) 
230        (editor-error "No such binding: ~S" key))
231      (delete-key-binding key kind where))))
232
233
234(defcommand "Set Variable" (p)
235  "Prompt for a Hemlock variable and a new value."
236  "Prompt for a Hemlock variable and a new value."
237  (declare (ignore p))
238  (multiple-value-bind (name var)
239                       (prompt-for-variable
240                        :prompt "Variable: "
241                        :help "The name of a variable to set.")
242    (declare (ignore name))
243    (setf (variable-value var)
244          (handle-lisp-errors
245           (eval (prompt-for-expression
246                  :prompt "Value: "
247                  :help "Expression to evaluate for new value."))))))
248
249(defcommand "Defhvar" (p)
250  "Define a hemlock variable in some location.  If the named variable exists
251   currently, its documentation is propagated to the new instance, but this
252   never prompts for documentation."
253  "Define a hemlock variable in some location."
254  (declare (ignore p))
255  (let* ((name (nstring-capitalize (prompt-for-variable :must-exist nil)))
256         (var (string-to-variable name))
257         (doc (if (hemlock-bound-p var)
258                  (variable-documentation var)
259                  ""))
260         (hooks (if (hemlock-bound-p var) (variable-hooks var)))
261         (val (prompt-for-expression :prompt "Variable value: "
262                                     :help "Value for the variable.")))
263    (multiple-value-bind
264        (kind where)
265        (prompt-for-place
266         "Kind of binding: "
267         "Whether the variable is global, mode, or buffer specific.")
268      (if (eq kind :global)
269          (defhvar name doc :value val :hooks hooks)
270          (defhvar name doc kind where :value val :hooks hooks)))))
271
272
273;;; TRANSPOSE REGIONS uses CURRENT-REGION to signal an error if the current
274;;; region is not active and to get start2 and end2 in proper order.  Delete1,
275;;; delete2, and delete3 are necessary since we are possibly ROTATEF'ing the
276;;; locals end1/start1, start1/start2, and end1/end2, and we need to know which
277;;; marks to dispose of at the end of all this stuff.  When we actually get to
278;;; swapping the regions, we must delete both up front if they both are to be
279;;; deleted since we don't know what kind of marks are in start1, start2, end1,
280;;; and end2, and the marks will be moving around unpredictably as we insert
281;;; text at them.  We copy point into ipoint for insertion purposes since one
282;;; of our four marks is the point.
283;;;
284(defcommand "Transpose Regions" (p)
285  "Transpose two regions with endpoints defined by the mark stack and point.
286   To use:  mark start of region1, mark end of region1, mark start of region2,
287   and place point at end of region2.  Invoking this immediately following
288   one use will put the regions back, but you will have to activate the
289   current region."
290  "Transpose two regions with endpoints defined by the mark stack and point."
291  (declare (ignore p))
292  (unless (>= (ring-length (value buffer-mark-ring)) 3)
293    (editor-error "Need two marked regions to do Transpose Regions."))
294  (let* ((region (current-region))
295         (end2 (region-end region))
296         (start2 (region-start region))
297         (delete1 (pop-buffer-mark))
298         (end1 (pop-buffer-mark))
299         (delete2 end1)
300         (start1 (pop-buffer-mark))
301         (delete3 start1))
302    ;;get marks in the right order, to simplify the code that follows
303    (unless (mark<= start1 end1) (rotatef start1 end1))
304    (unless (mark<= start1 start2)
305      (rotatef start1 start2)
306      (rotatef end1 end2))
307    ;;order now guaranteed:  <Buffer Start> start1 end1 start2 end2 <Buffer End>
308    (unless (mark<= end1 start2)
309      (editor-error "Can't transpose overlapping regions."))
310    (let* ((adjacent-p (mark= end1 start2))
311           (region1 (delete-and-save-region (region start1 end1)))
312           (region2 (unless adjacent-p
313                      (delete-and-save-region (region start2 end2))))
314           (point (current-point)))
315      (with-mark ((ipoint point :left-inserting))
316        (let ((save-end2-loc (push-new-buffer-mark end2)))
317          (ninsert-region (move-mark ipoint end2) region1)
318          (push-new-buffer-mark ipoint)
319          (cond (adjacent-p
320                 (push-new-buffer-mark start2)
321                 (move-mark point save-end2-loc))
322                (t (push-new-buffer-mark end1)
323                   (ninsert-region (move-mark ipoint end1) region2)
324                   (move-mark point ipoint))))))
325    (delete-mark delete1)
326    (delete-mark delete2)
327    (delete-mark delete3)))
328
329
330(defcommand "Goto Absolute Line" (p)
331  "Goes to the indicated line, if you counted them starting at the beginning
332   of the buffer with the number one.  If a prefix argument is supplied, that
333   is the line number; otherwise, the user is prompted."
334  "Go to a user perceived line number."
335  (let ((p (or p (prompt-for-expression
336                  :prompt "Line number: "
337                  :help "Enter an absolute line number to goto."))))
338    (unless (and (integerp p) (plusp p))
339      (editor-error "Must supply a positive integer."))
340    (let ((point (current-point)))
341      (with-mark ((m point))
342        (unless (line-offset (buffer-start m) (1- p) 0)
343          (editor-error "Not enough lines in buffer."))
344        (move-mark point m)))))
345
346(defcommand "Goto Absolute Position" (p)
347  "Goes to the indicated character position, if you counted them
348   starting at the beginning of the buffer with the number zero.  If a
349   prefix argument is supplied, that is the line number; otherwise, the
350  user is prompted."
351  "Go to a user perceived character position."
352  (let ((p (or p (prompt-for-expression
353                  :prompt "Character Position: "
354                  :help "Enter an absolute character position to goto."))))
355    (unless (and (integerp p) (not (minusp p)))
356      (editor-error "Must supply a non-negatige integer."))
357    (let ((point (current-point-unless-selection)))
358      (when point
359        (unless (move-to-absolute-position point p)
360          (buffer-end point))))))
361
362(defcommand "What Cursor Position" (p)
363  "Print info on current point position"
364  "Print info on current point position"
365  (declare (ignore p))
366  (let* ((point (current-point))
367         (line-number (do* ((l 1 (1+ l))
368                            (mark-line (line-previous (mark-line point)) (line-previous mark-line)))
369                           ((null mark-line) l)))
370         (charpos (mark-charpos point))
371         (abspos (mark-absolute-position point))
372         (char (next-character point))
373         (size (count-characters (buffer-region (current-buffer)))))
374    (message "Char: ~s point = ~d of ~d(~d%) line ~d column ~d"
375             char abspos size (round (/ (* 100 abspos) size)) line-number charpos)))
376
377(defcommand "Count Lines" (p)
378  "Display number of lines in the region."
379  "Display number of lines in the region."
380  (declare (ignore p))
381  (multiple-value-bind (region activep) (get-count-region)
382    (message "~:[After point~;Active region~]: ~A lines"
383             activep (count-lines region))))
384
385(defcommand "Count Words" (p)
386  "Prints in the Echo Area the number of words in the region
387   between the point and the mark by using word-offset. The
388   argument is ignored."
389  "Prints Number of Words in the Region"
390  (declare (ignore p))
391  (multiple-value-bind (region activep) (get-count-region)
392    (let ((end-mark (region-end region)))
393      (with-mark ((beg-mark (region-start region)))
394        (let ((word-count 0))
395          (loop
396            (when (mark>= beg-mark end-mark)
397              (return))
398            (unless (word-offset beg-mark 1)
399              (return))
400            (incf word-count))
401          (message "~:[After point~;Active region~]: ~D Word~:P"
402                   activep word-count))))))
403
404;;; GET-COUNT-REGION -- Internal Interface.
405;;;
406;;; Returns the active region or the region between point and end-of-buffer.
407;;; As a second value, it returns whether the region was active.
408;;;
409;;; Some searching commands use this routine.
410;;;
411(defun get-count-region ()
412  (if (region-active-p)
413      (values (current-region) t)
414      (values (region (current-point) (buffer-end-mark (current-buffer)))
415              nil)))
416
417
418
419;;;; Some modes:
420
421(defcommand "Fundamental Mode" (p)
422  "Put the current buffer into \"Fundamental\" mode."
423  "Put the current buffer into \"Fundamental\" mode."
424  (declare (ignore p))
425  (setf (buffer-major-mode (current-buffer)) "Fundamental"))
426
427;;;
428;;; Text mode.
429;;;
430
431(defmode "Text" :major-p t)
432
433(defcommand "Text Mode" (p)
434  "Put the current buffer into \"Text\" mode."
435  "Put the current buffer into \"Text\" mode."
436  (declare (ignore p))
437  (setf (buffer-major-mode (current-buffer)) "Text"))
438
439;;;
440;;; Caps-lock mode.
441;;;
442
443(defmode "CAPS-LOCK")
444
445(defcommand "Caps Lock Mode" (p)
446  "Simulate having a CAPS LOCK key.  Toggle CAPS-LOCK mode.  Zero or a
447   negative argument turns it off, while a positive argument turns it
448   on."
449  "Simulate having a CAPS LOCK key.  Toggle CAPS-LOCK mode.  Zero or a
450   negative argument turns it off, while a positive argument turns it
451   on."
452  (setf (buffer-minor-mode (current-buffer) "CAPS-LOCK")
453        (if p
454            (plusp p)
455            (not (buffer-minor-mode (current-buffer) "CAPS-LOCK")))))
456
457(defcommand "Self Insert Caps Lock" (p)
458  "Insert the last character typed, or the argument number of them.
459   If the last character was an alphabetic character, then insert its
460   capital form."
461  "Insert the last character typed, or the argument number of them.
462   If the last character was an alphabetic character, then insert its
463   capital form."
464  (let ((char (char-upcase (last-char-typed))))
465    (if (and p (> p 1))
466        (insert-string (current-point) (make-string p :initial-element char))
467        (insert-character (current-point) char))))
Note: See TracBrowser for help on using the repository browser.