0
|
1 ;;; cmdloop.el --- support functions for the top-level command loop.
|
|
2
|
|
3 ;; Copyright (C) 1992, 1993, 1994 Free Software Foundation, Inc.
|
|
4
|
|
5 ;; This file is part of XEmacs.
|
|
6
|
|
7 ;; XEmacs is free software; you can redistribute it and/or modify it
|
|
8 ;; under the terms of the GNU General Public License as published by
|
|
9 ;; the Free Software Foundation; either version 2, or (at your option)
|
|
10 ;; any later version.
|
|
11
|
|
12 ;; XEmacs is distributed in the hope that it will be useful, but
|
|
13 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
15 ;; General Public License for more details.
|
|
16
|
|
17 ;; You should have received a copy of the GNU General Public License
|
|
18 ;; along with XEmacs; see the file COPYING. If not, write to the Free
|
|
19 ;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
20
|
|
21 ;;; Synched up with: FSF 19.30. (Some of the stuff below is in FSF's subr.el.)
|
|
22
|
|
23 ;; Written by Richard Mlynarik 8-Jul-92
|
|
24
|
|
25 (defun recursion-depth ()
|
|
26 "Return the current depth in recursive edits."
|
|
27 (+ command-loop-level (minibuffer-depth)))
|
|
28
|
|
29 (defun top-level ()
|
|
30 "Exit all recursive editing levels."
|
|
31 (interactive)
|
|
32 (throw 'top-level nil))
|
|
33
|
|
34 (defun exit-recursive-edit ()
|
|
35 "Exit from the innermost recursive edit or minibuffer."
|
|
36 (interactive)
|
|
37 (if (> (recursion-depth) 0)
|
|
38 (throw 'exit nil))
|
|
39 (error "No recursive edit is in progress"))
|
|
40
|
|
41 (defun abort-recursive-edit ()
|
|
42 "Abort the command that requested this recursive edit or minibuffer input."
|
|
43 (interactive)
|
|
44 (if (> (recursion-depth) 0)
|
|
45 (throw 'exit t))
|
|
46 (error "No recursive edit is in progress"))
|
|
47
|
|
48 ;; (defun keyboard-quit ()
|
|
49 ;; "Signal a `quit' condition."
|
|
50 ;; (interactive)
|
|
51 ;; (deactivate-mark)
|
|
52 ;; (signal 'quit nil))
|
|
53
|
|
54 ;; moved here from pending-del.
|
|
55 (defun keyboard-quit ()
|
|
56 "Signal a `quit' condition.
|
|
57 If this character is typed while lisp code is executing, it will be treated
|
|
58 as an interrupt.
|
|
59 If this character is typed at top-level, this simply beeps.
|
|
60 If `zmacs-regions' is true, and the zmacs region is active, then this
|
|
61 key deactivates the region without beeping or signalling."
|
|
62 (interactive)
|
|
63 (if (and zmacs-regions (zmacs-deactivate-region))
|
|
64 ;; pseudo-zmacs compatibility: don't beep if this ^G is simply
|
|
65 ;; deactivating the region. If it is inactive, beep.
|
|
66 nil
|
|
67 (signal 'quit nil)))
|
|
68
|
|
69 (defvar buffer-quit-function nil
|
|
70 "Function to call to \"quit\" the current buffer, or nil if none.
|
|
71 \\[keyboard-escape-quit] calls this function when its more local actions
|
|
72 \(such as cancelling a prefix argument, minibuffer or region) do not apply.")
|
|
73
|
|
74 (defun keyboard-escape-quit ()
|
|
75 "Exit the current \"mode\" (in a generalized sense of the word).
|
|
76 This command can exit an interactive command such as `query-replace',
|
|
77 can clear out a prefix argument or a region,
|
|
78 can get out of the minibuffer or other recursive edit,
|
|
79 cancel the use of the current buffer (for special-purpose buffers),
|
|
80 or go back to just one window (by deleting all but the selected window)."
|
|
81 (interactive)
|
|
82 (cond ((eq last-command 'mode-exited) nil)
|
|
83 ((> (minibuffer-depth) 0)
|
|
84 (abort-recursive-edit))
|
|
85 (current-prefix-arg
|
|
86 nil)
|
|
87 ((region-active-p)
|
|
88 (zmacs-deactivate-region))
|
|
89 (buffer-quit-function
|
|
90 (funcall buffer-quit-function))
|
|
91 ((not (one-window-p t))
|
|
92 (delete-other-windows))))
|
|
93
|
|
94 ;;#### This should really be a ring of last errors.
|
|
95 (defvar last-error nil
|
|
96 "#### Document me.")
|
|
97
|
|
98 (defun command-error (error-object)
|
|
99 (let ((inhibit-quit t)
|
|
100 (debug-on-error nil)
|
|
101 (etype (car-safe error-object)))
|
|
102 (setq quit-flag nil)
|
|
103 (setq standard-output t)
|
|
104 (setq standard-input t)
|
|
105 (setq executing-kbd-macro nil)
|
|
106 (zmacs-deactivate-region)
|
|
107 (discard-input)
|
|
108
|
|
109 (setq last-error error-object)
|
|
110
|
|
111 (message nil)
|
|
112 (ding nil (cond ((eq etype 'undefined-keystroke-sequence)
|
|
113 (if (and (vectorp (nth 1 error-object))
|
|
114 (/= 0 (length (nth 1 error-object)))
|
|
115 (button-event-p (aref (nth 1 error-object) 0)))
|
|
116 'undefined-click
|
|
117 'undefined-key))
|
|
118 ((eq etype 'quit)
|
|
119 'quit)
|
|
120 ((memq etype '(end-of-buffer beginning-of-buffer))
|
|
121 'buffer-bound)
|
|
122 ((eq etype 'buffer-read-only)
|
|
123 'read-only)
|
|
124 (t 'command-error)))
|
|
125 (display-error error-object t)
|
|
126
|
|
127 (if (noninteractive)
|
|
128 (progn
|
|
129 (message "XEmacs exiting.")
|
|
130 (kill-emacs -1)))
|
|
131 t))
|
|
132
|
|
133 (defun describe-last-error ()
|
|
134 "Redisplay the last error-message. See the variable `last-error'."
|
|
135 (interactive)
|
|
136 (with-displaying-help-buffer
|
2
|
137 (lambda ()
|
|
138 (princ "Last error was:\n" standard-output)
|
|
139 (display-error last-error standard-output))))
|
0
|
140
|
|
141
|
|
142 ;;#### Must be done later in the loadup sequence
|
|
143 ;(define-key (symbol-function 'help-command) "e" 'describe-last-error)
|
|
144
|
|
145
|
|
146 (defun truncate-command-history-for-gc ()
|
|
147 (let ((tail (nthcdr 30 command-history)))
|
|
148 (if tail (setcdr tail nil)))
|
|
149 (let ((tail (nthcdr 30 values)))
|
|
150 (if tail (setcdr tail nil)))
|
|
151 )
|
|
152
|
|
153 (add-hook 'pre-gc-hook 'truncate-command-history-for-gc)
|
|
154
|
|
155
|
|
156 ;;;; Object-oriented programming at its finest
|
|
157
|
|
158 (defun display-error (error-object stream) ;(defgeneric report-condition ...)
|
|
159 "Display `error-object' on `stream' in a user-friendly way."
|
|
160 (funcall (or (let ((type (car-safe error-object)))
|
|
161 (catch 'error
|
|
162 (and (consp error-object)
|
|
163 (symbolp type)
|
|
164 ;;(stringp (get type 'error-message))
|
|
165 (consp (get type 'error-conditions))
|
|
166 (let ((tail (cdr error-object)))
|
|
167 (while (not (null tail))
|
|
168 (if (consp tail)
|
|
169 (setq tail (cdr tail))
|
|
170 (throw 'error nil)))
|
|
171 t)
|
|
172 ;; (check-type condition condition)
|
|
173 (get type 'error-conditions)
|
|
174 ;; Search class hierarchy
|
|
175 (let ((tail (get type 'error-conditions)))
|
|
176 (while (not (null tail))
|
|
177 (cond ((not (and (consp tail)
|
|
178 (symbolp (car tail))))
|
|
179 (throw 'error nil))
|
|
180 ((get (car tail) 'display-error)
|
|
181 (throw 'error (get (car tail)
|
|
182 'display-error)))
|
|
183 (t
|
|
184 (setq tail (cdr tail)))))
|
|
185 ;; Default method
|
|
186 #'(lambda (error-object stream)
|
|
187 (let ((type (car error-object))
|
|
188 (tail (cdr error-object))
|
|
189 (first t)
|
|
190 (print-message-label 'error))
|
|
191 (if (eq type 'error)
|
|
192 (progn (princ (car tail) stream)
|
|
193 (setq tail (cdr tail)))
|
|
194 (princ (or (gettext (get type 'error-message)) type)
|
|
195 stream))
|
|
196 (while tail
|
|
197 (princ (if first ": " ", ") stream)
|
|
198 (prin1 (car tail) stream)
|
|
199 (setq tail (cdr tail)
|
|
200 first nil))))))))
|
|
201 #'(lambda (error-object stream)
|
|
202 (princ (gettext "Peculiar error ") stream)
|
|
203 (prin1 error-object stream)))
|
|
204 error-object stream))
|
|
205
|
|
206 (put 'file-error 'display-error
|
|
207 #'(lambda (error-object stream)
|
|
208 (let ((tail (cdr error-object))
|
|
209 (first t))
|
|
210 (princ (car tail) stream)
|
|
211 (while (setq tail (cdr tail))
|
|
212 (princ (if first ": " ", ") stream)
|
|
213 (princ (car tail) stream)
|
|
214 (setq first nil)))))
|
|
215
|
|
216 (put 'undefined-keystroke-sequence 'display-error
|
|
217 #'(lambda (error-object stream)
|
|
218 (princ (key-description (car (cdr error-object))) stream)
|
|
219 ;; #### I18N3: doesn't localize properly.
|
|
220 (princ (gettext " not defined.") stream) ; doo dah, doo dah.
|
|
221 ))
|
|
222
|
|
223
|
|
224 (defvar teach-extended-commands-p t
|
|
225 "*If true, then `\\[execute-extended-command]' will teach you keybindings.
|
|
226 Any time you execute a command with \\[execute-extended-command] which has a
|
|
227 shorter keybinding, you will be shown the alternate binding before the
|
|
228 command executes. There is a short pause after displaying the binding,
|
|
229 before executing it; the length can be controlled by
|
|
230 `teach-extended-commands-timeout'.")
|
|
231
|
|
232 (defvar teach-extended-commands-timeout 2
|
|
233 "*How long to pause after displaying a keybinding before executing.
|
|
234 The value is measured in seconds. This only applies if
|
|
235 `teach-extended-commands-p' is true.")
|
|
236
|
|
237 ;That damn RMS went off and implemented something differently, after
|
|
238 ;we had already implemented it. We can't support both properly until
|
|
239 ;we have Lisp magic variables.
|
|
240 ;(defvar suggest-key-bindings t
|
|
241 ; "*FSFmacs equivalent of `teach-extended-commands-*'.
|
|
242 ;Provided for compatibility only.
|
|
243 ;Non-nil means show the equivalent key-binding when M-x command has one.
|
|
244 ;The value can be a length of time to show the message for.
|
|
245 ;If the value is non-nil and not a number, we wait 2 seconds.")
|
|
246 ;
|
|
247 ;(make-obsolete-variable 'suggest-key-bindings 'teach-extended-commands-p)
|
|
248
|
|
249 (defun execute-extended-command (prefix-arg)
|
|
250 "Read a command name from the minibuffer using 'completing-read'.
|
|
251 Then call the specified command using 'command-execute' and return its
|
|
252 return value. If the command asks for a prefix argument, supply the
|
|
253 value of the current raw prefix argument, or the value of PREFIX-ARG
|
|
254 when called from Lisp."
|
|
255 (interactive "P")
|
|
256 ;; Note: This doesn't hack "this-command-keys"
|
|
257 (let ((prefix-arg prefix-arg))
|
|
258 (setq this-command (read-command
|
|
259 ;; Note: this has the hard-wired
|
|
260 ;; "C-u" and "M-x" string bug in common
|
|
261 ;; with all GNU Emacs's.
|
|
262 ;; (i.e. it prints C-u and M-x regardless of
|
|
263 ;; whether some other keys were actually bound
|
|
264 ;; to `execute-extended-command' and
|
|
265 ;; `universal-argument'.
|
|
266 (cond ((eq prefix-arg '-)
|
|
267 "- M-x ")
|
|
268 ((equal prefix-arg '(4))
|
|
269 "C-u M-x ")
|
|
270 ((integerp prefix-arg)
|
|
271 (format "%d M-x " prefix-arg))
|
|
272 ((and (consp prefix-arg)
|
|
273 (integerp (car prefix-arg)))
|
|
274 (format "%d M-x " (car prefix-arg)))
|
|
275 (t
|
|
276 "M-x ")))))
|
|
277
|
|
278 (if (and teach-extended-commands-p (interactive-p))
|
|
279 (let ((keys (where-is-internal this-command)))
|
|
280 (if keys
|
|
281 (progn
|
|
282 (message "M-x %s (bound to key%s: %s)"
|
|
283 this-command
|
|
284 (if (cdr keys) "s" "")
|
|
285 (mapconcat 'key-description
|
|
286 (sort keys #'(lambda (x y)
|
|
287 (< (length x) (length y))))
|
|
288 ", "))
|
|
289 (sit-for teach-extended-commands-timeout)))))
|
|
290
|
|
291 (command-execute this-command t))
|
|
292
|
|
293
|
|
294 ;;; C code calls this; the underscores in the variable names are to avoid
|
|
295 ;;; cluttering the specbind namespace (lexical scope! lexical scope!)
|
|
296 ;;; Putting this in Lisp instead of C slows kbd macros by 50%.
|
|
297 ;(defun command-execute (_command &optional _record-flag)
|
|
298 ; "Execute CMD as an editor command.
|
|
299 ;CMD must be a symbol that satisfies the `commandp' predicate.
|
|
300 ;Optional second arg RECORD-FLAG non-nil
|
|
301 ;means unconditionally put this command in `command-history'.
|
|
302 ;Otherwise, that is done only if an arg is read using the minibuffer."
|
|
303 ; (let ((_prefix prefix-arg)
|
|
304 ; (_cmd (indirect-function _command)))
|
|
305 ; (setq prefix-arg nil
|
|
306 ; this-command _command
|
|
307 ; current-prefix-arg _prefix
|
|
308 ; zmacs-region-stays nil)
|
|
309 ; ;; #### debug_on_next_call = 0;
|
|
310 ; (cond ((and (symbolp _command)
|
|
311 ; (get _command 'disabled))
|
|
312 ; (run-hooks disabled-command-hook))
|
|
313 ; ((or (stringp _cmd) (vectorp _cmd))
|
|
314 ; ;; If requested, place the macro in the command history.
|
|
315 ; ;; For other sorts of commands, call-interactively takes
|
|
316 ; ;; care of this.
|
|
317 ; (if _record-flag
|
|
318 ; (setq command-history
|
|
319 ; (cons (list 'execute-kbd-macro _cmd _prefix)
|
|
320 ; command-history)))
|
|
321 ; (execute-kbd-macro _cmd _prefix))
|
|
322 ; (t
|
|
323 ; (call-interactively _command _record-flag)))))
|
|
324
|
|
325 (defun y-or-n-p-minibuf (prompt)
|
|
326 "Ask user a \"y or n\" question. Return t if answer is \"y\".
|
|
327 Takes one argument, which is the string to display to ask the question.
|
|
328 It should end in a space; `y-or-n-p' adds `(y or n) ' to it.
|
|
329 No confirmation of the answer is requested; a single character is enough.
|
|
330 Also accepts Space to mean yes, or Delete to mean no."
|
|
331 (save-excursion
|
|
332 (let* ((pre "")
|
|
333 (yn (gettext "(y or n) "))
|
|
334 ;; we need to translate the prompt ourselves because of the
|
|
335 ;; strange way we handle it.
|
|
336 (prompt (gettext prompt))
|
|
337 event)
|
|
338 (while (stringp yn)
|
|
339 (if (let ((cursor-in-echo-area t)
|
|
340 (inhibit-quit t))
|
|
341 (message "%s%s%s" pre prompt yn)
|
|
342 (setq event (next-command-event event))
|
|
343 (prog1
|
|
344 (or quit-flag (eq 'keyboard-quit (key-binding event)))
|
|
345 (setq quit-flag nil)))
|
|
346 (progn
|
|
347 (message "%s%s%s%s" pre prompt yn (single-key-description event))
|
|
348 (setq quit-flag nil)
|
|
349 (signal 'quit '())))
|
|
350 (let* ((keys (events-to-keys (vector event)))
|
|
351 (def (lookup-key query-replace-map keys)))
|
|
352 (cond ((eq def 'skip)
|
|
353 (message "%s%sNo" prompt yn)
|
|
354 (setq yn nil))
|
|
355 ((eq def 'act)
|
|
356 (message "%s%sYes" prompt yn)
|
|
357 (setq yn t))
|
|
358 ((eq def 'recenter)
|
|
359 (recenter))
|
|
360 ((or (eq def 'quit) (eq def 'exit-prefix))
|
|
361 (signal 'quit '()))
|
|
362 ((button-release-event-p event) ; ignore them
|
|
363 nil)
|
|
364 (t
|
|
365 (message "%s%s%s%s" pre prompt yn
|
|
366 (single-key-description event))
|
|
367 (ding nil 'y-or-n-p)
|
|
368 (discard-input)
|
|
369 (if (= (length pre) 0)
|
|
370 (setq pre (gettext "Please answer y or n. ")))))))
|
|
371 yn)))
|
|
372
|
|
373 (defun yes-or-no-p-minibuf (prompt)
|
|
374 "Ask user a yes-or-no question. Return t if answer is yes.
|
|
375 Takes one argument, which is the string to display to ask the question.
|
|
376 It should end in a space; `yes-or-no-p' adds `(yes or no) ' to it.
|
|
377 The user must confirm the answer with RET,
|
|
378 and can edit it until it has been confirmed."
|
|
379 (save-excursion
|
|
380 (let ((p (concat (gettext prompt) (gettext "(yes or no) ")))
|
|
381 (ans ""))
|
|
382 (while (stringp ans)
|
|
383 (setq ans (downcase (read-string p nil t))) ;no history
|
|
384 (cond ((string-equal ans (gettext "yes"))
|
|
385 (setq ans 't))
|
|
386 ((string-equal ans (gettext "no"))
|
|
387 (setq ans 'nil))
|
|
388 (t
|
|
389 (ding nil 'yes-or-no-p)
|
|
390 (discard-input)
|
|
391 (message "Please answer yes or no.")
|
|
392 (sleep-for 2))))
|
|
393 ans)))
|
|
394
|
|
395 ;; these may be redefined later, but make the original def easily encapsulable
|
|
396 (define-function 'yes-or-no-p 'yes-or-no-p-minibuf)
|
|
397 (define-function 'y-or-n-p 'y-or-n-p-minibuf)
|
|
398
|
|
399
|
|
400 (defun read-char ()
|
|
401 "Read a character from the command input (keyboard or macro).
|
|
402 If a mouse click or non-ASCII character is detected, an error is
|
|
403 signalled. The character typed is returned as an ASCII value. This
|
|
404 is most likely the wrong thing for you to be using: consider using
|
|
405 the `next-command-event' function instead."
|
|
406 (save-excursion
|
|
407 (let ((inhibit-quit t)
|
|
408 (event (next-command-event)))
|
|
409 (prog1 (or (event-to-character event)
|
|
410 ;; Kludge. If the event we read was a mouse-release,
|
|
411 ;; discard it and read the next one.
|
|
412 (if (button-release-event-p event)
|
|
413 (event-to-character (next-command-event event)))
|
|
414 (error "Key read has no ASCII equivalent %S" event))
|
|
415 ;; this is not necessary, but is marginally more efficient than GC.
|
|
416 (deallocate-event event)))))
|
|
417
|
|
418 (defun read-char-exclusive ()
|
|
419 "Read a character from the command input (keyboard or macro).
|
|
420 If a mouse click or non-ASCII character is detected, it is discarded.
|
|
421 The character typed is returned as an ASCII value. This is most likely
|
|
422 the wrong thing for you to be using: consider using the
|
|
423 `next-command-event' function instead."
|
|
424 (let ((inhibit-quit t)
|
|
425 event ch)
|
|
426 (while (progn
|
|
427 (setq event (next-command-event))
|
|
428 (setq ch (event-to-character event))
|
|
429 (deallocate-event event)
|
|
430 (null ch)))
|
|
431 ch))
|
|
432
|
|
433 (defun read-quoted-char (&optional prompt)
|
|
434 "Like `read-char', except that if the first character read is an octal
|
|
435 digit, we read up to two more octal digits and return the character
|
|
436 represented by the octal number consisting of those digits.
|
|
437 Optional argument PROMPT specifies a string to use to prompt the user."
|
|
438 (save-excursion
|
|
439 (let ((count 0) (code 0)
|
|
440 (prompt (and prompt (gettext prompt)))
|
|
441 char event)
|
|
442 (while (< count 3)
|
|
443 (let ((inhibit-quit (zerop count))
|
|
444 ;; Don't let C-h get the help message--only help function keys.
|
|
445 (help-char nil)
|
|
446 (help-form
|
|
447 "Type the special character you want to use,
|
|
448 or three octal digits representing its character code."))
|
|
449 (and prompt (display-message 'prompt (format "%s-" prompt)))
|
|
450 (setq event (next-command-event)
|
|
451 char (or (event-to-character event nil nil t)
|
|
452 (error "key read cannot be inserted in a buffer: %S"
|
|
453 event)))
|
|
454 (if inhibit-quit (setq quit-flag nil)))
|
|
455 (cond ((null char))
|
|
456 ((and (<= ?0 char) (<= char ?7))
|
|
457 (setq code (+ (* code 8) (- char ?0))
|
|
458 count (1+ count))
|
|
459 (and prompt (display-message
|
|
460 'prompt
|
|
461 (setq prompt (format "%s %c" prompt char)))))
|
|
462 ((> count 0)
|
|
463 (setq unread-command-event event
|
|
464 count 259))
|
|
465 (t (setq code char count 259))))
|
|
466 ;; Turn a meta-character into a character with the 0200 bit set.
|
|
467 (logior (if (/= (logand code ?\M-\^@) 0) 128 0)
|
|
468 (logand 255 code)))))
|
|
469
|
|
470 (defun momentary-string-display (string pos &optional exit-char message)
|
|
471 "Momentarily display STRING in the buffer at POS.
|
|
472 Display remains until next character is typed.
|
|
473 If the char is EXIT-CHAR (optional third arg, default is SPC) it is swallowed;
|
|
474 otherwise it is then available as input (as a command if nothing else).
|
|
475 Display MESSAGE (optional fourth arg) in the echo area.
|
|
476 If MESSAGE is nil, instructions to type EXIT-CHAR are displayed there."
|
|
477 (or exit-char (setq exit-char ?\ ))
|
|
478 (let ((buffer-read-only nil)
|
|
479 ;; Don't modify the undo list at all.
|
|
480 (buffer-undo-list t)
|
|
481 (modified (buffer-modified-p))
|
|
482 (name buffer-file-name)
|
|
483 insert-end)
|
|
484 (unwind-protect
|
|
485 (progn
|
|
486 (save-excursion
|
|
487 (goto-char pos)
|
|
488 ;; defeat file locking... don't try this at home, kids!
|
|
489 (setq buffer-file-name nil)
|
|
490 (insert-before-markers (gettext string))
|
|
491 (setq insert-end (point))
|
|
492 ;; If the message end is off frame, recenter now.
|
|
493 (if (> (window-end) insert-end)
|
|
494 (recenter (/ (window-height) 2)))
|
|
495 ;; If that pushed message start off the frame,
|
|
496 ;; scroll to start it at the top of the frame.
|
|
497 (move-to-window-line 0)
|
|
498 (if (> (point) pos)
|
|
499 (progn
|
|
500 (goto-char pos)
|
|
501 (recenter 0))))
|
|
502 (message (or message (gettext "Type %s to continue editing."))
|
|
503 (single-key-description exit-char))
|
|
504 (let ((event (save-excursion (next-command-event))))
|
|
505 (or (eq (event-to-character event) exit-char)
|
|
506 (setq unread-command-event event))))
|
|
507 (if insert-end
|
|
508 (save-excursion
|
|
509 (delete-region pos insert-end)))
|
|
510 (setq buffer-file-name name)
|
|
511 (set-buffer-modified-p modified))))
|
|
512
|
|
513 ;;; cmdloop.el ends here
|