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

Last change on this file since 12859 was 12859, checked in by gz, 10 years ago

Make sure that all hemlock functions defined outside of hemlock are in the hemlock-ext package, to make it easier to keep track of them

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