2926
|
1 /* |
|
2 |
|
3 Copyright (C) 1996, 1997 John W. Eaton |
|
4 |
|
5 This file is part of Octave. |
|
6 |
|
7 Octave is free software; you can redistribute it and/or modify it |
|
8 under the terms of the GNU General Public License as published by the |
|
9 Free Software Foundation; either version 2, or (at your option) any |
|
10 later version. |
|
11 |
|
12 Octave is distributed in the hope that it will be useful, but WITHOUT |
|
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
15 for more details. |
|
16 |
|
17 You should have received a copy of the GNU General Public License |
|
18 along with Octave; see the file COPYING. If not, write to the Free |
|
19 Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
20 |
|
21 */ |
|
22 |
|
23 #ifdef HAVE_CONFIG_H |
|
24 #include <config.h> |
|
25 #endif |
|
26 |
|
27 #include <cstring> |
|
28 #include <ctime> |
|
29 |
|
30 #include <string> |
|
31 |
|
32 #ifdef HAVE_UNISTD_H |
|
33 #ifdef HAVE_SYS_TYPES_H |
|
34 #include <sys/types.h> |
|
35 #endif |
|
36 #include <unistd.h> |
|
37 #endif |
|
38 |
|
39 #include "cmd-edit.h" |
|
40 #include "cmd-hist.h" |
|
41 #include "lo-error.h" |
|
42 #include "lo-utils.h" |
|
43 #include "oct-env.h" |
|
44 |
|
45 command_editor *command_editor::instance = 0; |
|
46 |
|
47 #if defined (USE_READLINE) |
|
48 |
|
49 #include <cstdio> |
|
50 #include <cstdlib> |
|
51 |
|
52 #include <readline/readline.h> |
|
53 |
|
54 // It would be nice if readline.h declared these, I think. |
|
55 |
|
56 extern "C" void rl_deprep_terminal (void); |
|
57 |
|
58 extern int rl_blink_matching_paren; |
|
59 |
|
60 extern int screenheight; |
|
61 |
|
62 extern int screenwidth; |
|
63 |
|
64 class |
|
65 gnu_readline : public command_editor |
|
66 { |
|
67 public: |
|
68 |
|
69 typedef command_editor::fcn fcn; |
|
70 |
|
71 gnu_readline (void); |
|
72 |
|
73 ~gnu_readline (void) { } |
|
74 |
|
75 void do_set_name (const string& n); |
|
76 |
|
77 string do_readline (const string& prompt); |
|
78 |
|
79 void do_set_input_stream (FILE *f); |
|
80 |
|
81 FILE *do_get_input_stream (void); |
|
82 |
|
83 void do_set_output_stream (FILE *f); |
|
84 |
|
85 FILE *do_get_output_stream (void); |
|
86 |
|
87 int do_terminal_rows (void); |
|
88 |
|
89 int do_terminal_cols (void); |
|
90 |
|
91 void do_clear_screen (void); |
|
92 |
|
93 string newline_chars (void); |
|
94 |
|
95 void do_restore_terminal_state (void); |
|
96 |
|
97 void do_blink_matching_paren (bool flag); |
|
98 |
|
99 void do_set_paren_string_delimiters (const string& s); |
|
100 |
|
101 void do_set_completion_append_character (char c); |
|
102 |
|
103 void do_set_attempted_completion_function (fcn f); |
|
104 |
|
105 void do_insert_text (const string& text); |
|
106 |
|
107 void do_newline (void); |
|
108 |
|
109 void do_clear_undo_list (void); |
|
110 |
|
111 void do_set_startup_hook (fcn f); |
|
112 |
|
113 void do_restore_startup_hook (void); |
|
114 |
|
115 static void operate_and_get_next (int, int); |
|
116 |
|
117 private: |
|
118 |
|
119 fcn previous_startup_hook; |
|
120 |
|
121 fcn attempted_completion_function; |
|
122 }; |
|
123 |
|
124 gnu_readline::gnu_readline () |
|
125 : command_editor (), previous_startup_hook (0), |
|
126 attempted_completion_function (0) |
|
127 { |
|
128 rl_initialize (); |
|
129 |
|
130 do_blink_matching_paren (true); |
|
131 |
|
132 // Bind operate-and-get-next. |
|
133 |
|
134 rl_add_defun ("operate-and-get-next", |
|
135 gnu_readline::operate_and_get_next, CTRL ('O')); |
|
136 |
|
137 // And the history search functions. |
|
138 |
|
139 rl_add_defun ("history-search-backward", |
|
140 rl_history_search_backward, META ('p')); |
|
141 |
|
142 rl_add_defun ("history-search-forward", |
|
143 rl_history_search_forward, META ('n')); |
|
144 } |
|
145 |
|
146 void |
|
147 gnu_readline::do_set_name (const string& n) |
|
148 { |
|
149 static char *nm = 0; |
|
150 |
|
151 delete [] nm; |
|
152 |
|
153 nm = strsave (n.c_str ()); |
|
154 |
|
155 rl_readline_name = nm; |
|
156 |
|
157 // Since we've already called rl_initialize, we need to re-read the |
|
158 // init file to take advantage of the conditional parsing feature |
|
159 // based on rl_readline_name; |
|
160 |
|
161 rl_re_read_init_file (); |
|
162 } |
|
163 |
|
164 string |
|
165 gnu_readline::do_readline (const string& prompt) |
|
166 { |
|
167 string retval; |
|
168 |
|
169 char *line = ::readline (prompt.c_str ()); |
|
170 |
|
171 if (line) |
|
172 { |
|
173 retval = line; |
|
174 |
|
175 free (line); |
|
176 } |
|
177 |
|
178 return retval; |
|
179 } |
|
180 |
|
181 void |
|
182 gnu_readline::do_set_input_stream (FILE *f) |
|
183 { |
|
184 rl_instream = f; |
|
185 } |
|
186 |
|
187 FILE * |
|
188 gnu_readline::do_get_input_stream (void) |
|
189 { |
|
190 return rl_instream; |
|
191 } |
|
192 |
|
193 void |
|
194 gnu_readline::do_set_output_stream (FILE *f) |
|
195 { |
|
196 rl_outstream = f; |
|
197 } |
|
198 |
|
199 FILE * |
|
200 gnu_readline::do_get_output_stream (void) |
|
201 { |
|
202 return rl_outstream; |
|
203 } |
|
204 |
|
205 // GNU readline handles SIGWINCH, so these values have a good chance |
|
206 // of being correct even if the window changes size (they may be |
|
207 // wrong if, for example, the luser changes the window size while the |
|
208 // pager is running, and the signal is handled by the pager instead of |
|
209 // us. |
|
210 |
|
211 int |
|
212 gnu_readline::do_terminal_rows (void) |
|
213 { |
|
214 return screenheight > 0 ? screenheight : 24; |
|
215 } |
|
216 |
|
217 int |
|
218 gnu_readline::do_terminal_cols (void) |
|
219 { |
|
220 return screenwidth > 0 ? screenwidth : 80; |
|
221 } |
|
222 |
|
223 void |
|
224 gnu_readline::do_clear_screen (void) |
|
225 { |
|
226 rl_clear_screen (); |
|
227 } |
|
228 |
|
229 string |
|
230 gnu_readline::newline_chars (void) |
|
231 { |
|
232 return "\r\n"; |
|
233 } |
|
234 |
|
235 void |
|
236 gnu_readline::do_restore_terminal_state (void) |
|
237 { |
|
238 rl_deprep_terminal (); |
|
239 } |
|
240 |
|
241 void |
|
242 gnu_readline::do_blink_matching_paren (bool flag) |
|
243 { |
|
244 rl_blink_matching_paren = flag ? 1 : 0; |
|
245 } |
|
246 |
|
247 void |
|
248 gnu_readline::do_set_paren_string_delimiters (const string& s) |
|
249 { |
|
250 static char *ss = 0; |
|
251 |
|
252 delete [] ss; |
|
253 |
|
254 ss = strsave (s.c_str ()); |
|
255 |
|
256 rl_paren_string_delimiters = ss; |
|
257 } |
|
258 |
|
259 void |
|
260 gnu_readline::do_set_completion_append_character (char c) |
|
261 { |
|
262 rl_completion_append_character = c; |
|
263 } |
|
264 |
|
265 void |
|
266 gnu_readline::do_set_attempted_completion_function (fcn f) |
|
267 { |
|
268 attempted_completion_function = f; |
|
269 } |
|
270 |
|
271 void |
|
272 gnu_readline::do_insert_text (const string& text) |
|
273 { |
|
274 rl_insert_text (text.c_str ()); |
|
275 } |
|
276 |
|
277 void |
|
278 gnu_readline::do_newline (void) |
|
279 { |
|
280 rl_newline (); |
|
281 } |
|
282 |
|
283 void |
|
284 gnu_readline::do_clear_undo_list () |
|
285 { |
|
286 if (rl_undo_list) |
|
287 { |
|
288 free_undo_list (); |
|
289 |
|
290 rl_undo_list = 0; |
|
291 } |
|
292 } |
|
293 |
|
294 void |
|
295 gnu_readline::do_set_startup_hook (fcn f) |
|
296 { |
|
297 previous_startup_hook = rl_startup_hook; |
|
298 |
|
299 rl_startup_hook = f; |
|
300 } |
|
301 |
|
302 void |
|
303 gnu_readline::do_restore_startup_hook (void) |
|
304 { |
|
305 rl_startup_hook = previous_startup_hook; |
|
306 } |
|
307 |
|
308 void |
|
309 gnu_readline::operate_and_get_next (int /* count */, int /* c */) |
|
310 { |
|
311 // Accept the current line. |
|
312 |
|
313 command_editor::newline (); |
|
314 |
|
315 // Find the current line, and find the next line to use. |
|
316 |
|
317 int x_where = command_history::where (); |
|
318 |
|
319 int x_length = command_history::length (); |
|
320 |
|
321 if ((command_history::is_stifled () |
|
322 && (x_length >= command_history::max_input_history ())) |
|
323 || (x_where >= x_length - 1)) |
|
324 command_history::set_mark (x_where); |
|
325 else |
|
326 command_history::set_mark (x_where + 1); |
|
327 |
|
328 command_editor::set_startup_hook (command_history::goto_mark); |
|
329 } |
|
330 |
|
331 #endif |
|
332 |
|
333 class |
|
334 default_command_editor : public command_editor |
|
335 { |
|
336 public: |
|
337 |
|
338 default_command_editor (void) |
|
339 : command_editor (), input_stream (stdin), output_stream (stdout) { } |
|
340 |
|
341 ~default_command_editor (void) { } |
|
342 |
|
343 string do_readline (const string& prompt); |
|
344 |
|
345 void do_set_input_stream (FILE *f); |
|
346 |
|
347 FILE *do_get_input_stream (void); |
|
348 |
|
349 void do_set_output_stream (FILE *f); |
|
350 |
|
351 FILE *do_get_output_stream (void); |
|
352 |
|
353 void do_insert_text (const string&); |
|
354 |
|
355 void do_newline (void); |
|
356 |
|
357 private: |
|
358 |
|
359 FILE *input_stream; |
|
360 |
|
361 FILE *output_stream; |
|
362 }; |
|
363 |
|
364 string |
|
365 default_command_editor::do_readline (const string& prompt) |
|
366 { |
|
367 fprintf (output_stream, prompt.c_str ()); |
|
368 fflush (output_stream); |
|
369 |
|
370 return octave_fgets (input_stream); |
|
371 } |
|
372 |
|
373 void |
|
374 default_command_editor::do_set_input_stream (FILE *f) |
|
375 { |
|
376 input_stream = f; |
|
377 } |
|
378 |
|
379 FILE * |
|
380 default_command_editor::do_get_input_stream (void) |
|
381 { |
|
382 return input_stream; |
|
383 } |
|
384 |
|
385 void |
|
386 default_command_editor::do_set_output_stream (FILE *f) |
|
387 { |
|
388 output_stream = f; |
|
389 } |
|
390 |
|
391 FILE * |
|
392 default_command_editor::do_get_output_stream (void) |
|
393 { |
|
394 return output_stream; |
|
395 } |
|
396 |
|
397 void |
|
398 default_command_editor::do_insert_text (const string&) |
|
399 { |
|
400 // XXX FIXME XXX |
|
401 } |
|
402 |
|
403 void |
|
404 default_command_editor::do_newline (void) |
|
405 { |
|
406 // XXX FIXME XXX |
|
407 } |
|
408 |
|
409 bool |
|
410 command_editor::instance_ok (void) |
|
411 { |
|
412 bool retval = true; |
|
413 |
|
414 if (! instance) |
|
415 make_command_editor (); |
|
416 |
|
417 if (! instance) |
|
418 { |
|
419 (*current_liboctave_error_handler) |
|
420 ("unable to create command history object!"); |
|
421 |
|
422 retval = false; |
|
423 } |
|
424 |
|
425 return retval; |
|
426 } |
|
427 |
|
428 void |
|
429 command_editor::make_command_editor (void) |
|
430 { |
|
431 #if defined (USE_READLINE) |
|
432 instance = new gnu_readline (); |
|
433 #else |
|
434 instance = new default_command_editor (); |
|
435 #endif |
|
436 } |
|
437 |
|
438 void |
|
439 command_editor::set_name (const string& n) |
|
440 { |
|
441 if (instance_ok ()) |
|
442 instance->do_set_name (n); |
|
443 } |
|
444 |
|
445 string |
|
446 command_editor::readline (const string& prompt) |
|
447 { |
|
448 return (instance_ok ()) |
|
449 ? instance->do_readline (prompt) : string (); |
|
450 } |
|
451 |
|
452 void |
|
453 command_editor::set_input_stream (FILE *f) |
|
454 { |
|
455 if (instance_ok ()) |
|
456 instance->do_set_input_stream (f); |
|
457 } |
|
458 |
|
459 FILE * |
|
460 command_editor::get_input_stream (void) |
|
461 { |
|
462 return (instance_ok ()) |
|
463 ? instance->do_get_input_stream () : 0; |
|
464 } |
|
465 |
|
466 void |
|
467 command_editor::set_output_stream (FILE *f) |
|
468 { |
|
469 if (instance_ok ()) |
|
470 instance->do_set_output_stream (f); |
|
471 } |
|
472 |
|
473 FILE * |
|
474 command_editor::get_output_stream (void) |
|
475 { |
|
476 return (instance_ok ()) |
|
477 ? instance->do_get_output_stream () : 0; |
|
478 } |
|
479 |
|
480 int |
|
481 command_editor::terminal_rows (void) |
|
482 { |
|
483 return (instance_ok ()) |
|
484 ? instance->do_terminal_rows () : -1; |
|
485 } |
|
486 |
|
487 int |
|
488 command_editor::terminal_cols (void) |
|
489 { |
|
490 return (instance_ok ()) |
|
491 ? instance->do_terminal_cols () : -1; |
|
492 } |
|
493 |
|
494 void |
|
495 command_editor::clear_screen (void) |
|
496 { |
|
497 if (instance_ok ()) |
|
498 instance->do_clear_screen (); |
|
499 } |
|
500 |
|
501 string |
|
502 command_editor::decode_prompt_string (const string& s) |
|
503 { |
|
504 return (instance_ok ()) |
|
505 ? instance->do_decode_prompt_string (s) : string (); |
|
506 } |
|
507 |
|
508 int |
|
509 command_editor::current_command_number (void) |
|
510 { |
|
511 return (instance_ok ()) |
|
512 ? instance->command_number : 0; |
|
513 } |
|
514 |
|
515 void |
|
516 command_editor::reset_current_command_number (int n) |
|
517 { |
|
518 if (instance_ok ()) |
|
519 instance->command_number = n; |
|
520 } |
|
521 |
|
522 void |
|
523 command_editor::restore_terminal_state (void) |
|
524 { |
|
525 if (instance_ok ()) |
|
526 instance->do_restore_terminal_state (); |
|
527 } |
|
528 |
|
529 void |
|
530 command_editor::blink_matching_paren (bool flag) |
|
531 { |
|
532 if (instance_ok ()) |
|
533 instance->do_blink_matching_paren (flag); |
|
534 } |
|
535 |
|
536 void |
|
537 command_editor::set_paren_string_delimiters (const string& s) |
|
538 { |
|
539 if (instance_ok ()) |
|
540 instance->do_set_paren_string_delimiters (s); |
|
541 } |
|
542 |
|
543 void |
|
544 command_editor::set_completion_append_character (char c) |
|
545 { |
|
546 if (instance_ok ()) |
|
547 instance->do_set_completion_append_character (c); |
|
548 } |
|
549 |
|
550 void |
|
551 command_editor::set_attempted_completion_function (fcn f) |
|
552 { |
|
553 if (instance_ok ()) |
|
554 instance->do_set_attempted_completion_function (f); |
|
555 } |
|
556 |
|
557 void |
|
558 command_editor::insert_text (const string& text) |
|
559 { |
|
560 if (instance_ok ()) |
|
561 instance->do_insert_text (text); |
|
562 } |
|
563 |
|
564 void |
|
565 command_editor::newline (void) |
|
566 { |
|
567 if (instance_ok ()) |
|
568 instance->do_newline (); |
|
569 } |
|
570 |
|
571 void |
|
572 command_editor::clear_undo_list (void) |
|
573 { |
|
574 if (instance_ok ()) |
|
575 instance->do_clear_undo_list (); |
|
576 } |
|
577 |
|
578 void |
|
579 command_editor::set_startup_hook (fcn f) |
|
580 { |
|
581 if (instance_ok ()) |
|
582 instance->do_set_startup_hook (f); |
|
583 } |
|
584 |
|
585 void |
|
586 command_editor::restore_startup_hook (void) |
|
587 { |
|
588 if (instance_ok ()) |
|
589 instance->do_restore_startup_hook (); |
|
590 } |
|
591 |
|
592 // Return a string which will be printed as a prompt. The string may |
|
593 // contain special characters which are decoded as follows: |
|
594 // |
|
595 // \t the time |
|
596 // \d the date |
|
597 // \n CRLF |
|
598 // \s the name of the shell (program) |
|
599 // \w the current working directory |
|
600 // \W the last element of PWD |
|
601 // \u your username |
|
602 // \h the hostname |
|
603 // \# the command number of this command |
|
604 // \! the history number of this command |
|
605 // \$ a $ or a # if you are root |
|
606 // \<octal> character code in octal |
|
607 // \\ a backslash |
|
608 |
|
609 string |
|
610 command_editor::do_decode_prompt_string (const string& s) |
|
611 { |
|
612 string result; |
|
613 string temp; |
|
614 size_t i = 0; |
|
615 size_t slen = s.length (); |
|
616 int c; |
|
617 |
|
618 while (i < slen) |
|
619 { |
|
620 c = s[i]; |
|
621 |
|
622 i++; |
|
623 |
|
624 if (c == '\\') |
|
625 { |
|
626 c = s[i]; |
|
627 |
|
628 switch (c) |
|
629 { |
|
630 case '0': |
|
631 case '1': |
|
632 case '2': |
|
633 case '3': |
|
634 case '4': |
|
635 case '5': |
|
636 case '6': |
|
637 case '7': |
|
638 // Maybe convert an octal number. |
|
639 { |
|
640 int n = read_octal (s.substr (i, 3)); |
|
641 |
|
642 temp = "\\"; |
|
643 |
|
644 if (n != -1) |
|
645 { |
|
646 i += 3; |
|
647 temp[0] = n; |
|
648 } |
|
649 |
|
650 c = 0; |
|
651 goto add_string; |
|
652 } |
|
653 |
|
654 case 't': |
|
655 case 'd': |
|
656 // Make the current time/date into a string. |
|
657 { |
|
658 time_t now = time (0); |
|
659 |
|
660 temp = ctime (&now); |
|
661 |
|
662 if (c == 't') |
|
663 { |
|
664 temp = temp.substr (11); |
|
665 temp.resize (8); |
|
666 } |
|
667 else |
|
668 temp.resize (10); |
|
669 |
|
670 goto add_string; |
|
671 } |
|
672 |
|
673 case 'n': |
|
674 { |
|
675 temp = newline_chars (); |
|
676 |
|
677 goto add_string; |
|
678 } |
|
679 |
|
680 case 's': |
|
681 { |
|
682 temp = octave_env::get_program_name (); |
|
683 temp = octave_env::base_pathname (temp); |
|
684 |
|
685 goto add_string; |
|
686 } |
|
687 |
|
688 case 'w': |
|
689 case 'W': |
|
690 { |
|
691 temp = octave_env::getcwd (); |
|
692 |
|
693 if (c == 'W') |
|
694 { |
|
695 size_t pos = temp.rfind ('/'); |
|
696 |
|
697 if (pos != NPOS && pos != 0) |
|
698 temp = temp.substr (pos + 1); |
|
699 } |
|
700 else |
|
701 temp = octave_env::polite_directory_format (temp); |
|
702 |
|
703 goto add_string; |
|
704 } |
|
705 |
|
706 case 'u': |
|
707 { |
|
708 temp = octave_env::get_user_name (); |
|
709 |
|
710 goto add_string; |
|
711 } |
|
712 |
|
713 case 'H': |
|
714 { |
|
715 temp = octave_env::get_host_name (); |
|
716 |
|
717 goto add_string; |
|
718 } |
|
719 |
|
720 case 'h': |
|
721 { |
|
722 temp = octave_env::get_host_name (); |
|
723 |
|
724 size_t pos = temp.find ('.'); |
|
725 |
|
726 if (pos != NPOS) |
|
727 temp.resize (pos); |
|
728 |
|
729 goto add_string; |
|
730 } |
|
731 |
|
732 case '#': |
|
733 { |
|
734 char number_buffer[128]; |
|
735 sprintf (number_buffer, "%d", command_number); |
|
736 temp = number_buffer; |
|
737 |
|
738 goto add_string; |
|
739 } |
|
740 |
|
741 case '!': |
|
742 { |
|
743 char number_buffer[128]; |
|
744 int num = command_history::current_number (); |
|
745 if (num > 0) |
|
746 sprintf (number_buffer, "%d", num); |
|
747 else |
|
748 strcpy (number_buffer, "!"); |
|
749 temp = number_buffer; |
|
750 |
|
751 goto add_string; |
|
752 } |
|
753 |
|
754 case '$': |
|
755 { |
|
756 temp = (::geteuid () == 0 ? "#" : "$"); |
|
757 |
|
758 goto add_string; |
|
759 } |
|
760 |
|
761 #if defined (USE_READLINE) |
|
762 case '[': |
|
763 case ']': |
|
764 { |
|
765 temp.resize (2); |
|
766 |
|
767 temp[0] = '\001'; |
|
768 temp[1] = ((c == '[') |
|
769 ? RL_PROMPT_START_IGNORE |
|
770 : RL_PROMPT_END_IGNORE); |
|
771 |
|
772 goto add_string; |
|
773 } |
|
774 #endif |
|
775 |
|
776 case '\\': |
|
777 { |
|
778 temp = "\\"; |
|
779 |
|
780 goto add_string; |
|
781 } |
|
782 |
|
783 default: |
|
784 { |
|
785 temp = "\\ "; |
|
786 temp[1] = c; |
|
787 |
|
788 goto add_string; |
|
789 } |
|
790 |
|
791 add_string: |
|
792 { |
|
793 if (c) |
|
794 i++; |
|
795 |
|
796 result.append (temp); |
|
797 |
|
798 break; |
|
799 } |
|
800 } |
|
801 } |
|
802 else |
|
803 result += c; |
|
804 } |
|
805 |
|
806 return result; |
|
807 } |
|
808 |
|
809 // Return the octal number parsed from STRING, or -1 to indicate that |
|
810 // the string contained a bad number. |
|
811 |
|
812 int |
|
813 command_editor::read_octal (const string& s) |
|
814 { |
|
815 int result = 0; |
|
816 int digits = 0; |
|
817 |
|
818 size_t i = 0; |
|
819 size_t slen = s.length (); |
|
820 |
|
821 while (i < slen && s[i] >= '0' && s[i] < '8') |
|
822 { |
|
823 digits++; |
|
824 result = (result * 8) + s[i] - '0'; |
|
825 i++; |
|
826 } |
|
827 |
|
828 if (! digits || result > 0777 || i < slen) |
|
829 result = -1; |
|
830 |
|
831 return result; |
|
832 } |
|
833 |
|
834 void |
|
835 command_editor::error (int err_num) |
|
836 { |
|
837 (*current_liboctave_error_handler) ("%s", strerror (err_num)); |
|
838 } |
|
839 |
|
840 void |
|
841 command_editor::error (const string& s) |
|
842 { |
|
843 (*current_liboctave_error_handler) ("%s", s.c_str ()); |
|
844 } |
|
845 |
|
846 /* |
|
847 ;;; Local Variables: *** |
|
848 ;;; mode: C++ *** |
|
849 ;;; End: *** |
|
850 */ |