OCaml: put macro flag in metadata rather than special type field
[jackhill/mal.git] / vb / getline.cs
1 //
2 // getline.cs: A command line editor
3 //
4 // Authors:
5 // Miguel de Icaza (miguel@novell.com)
6 //
7 // Copyright 2008 Novell, Inc.
8 //
9 // Dual-licensed under the terms of the MIT X11 license or the
10 // Apache License 2.0
11 //
12 // USE -define:DEMO to build this as a standalone file and test it
13 //
14 // TODO:
15 // Enter an error (a = 1); Notice how the prompt is in the wrong line
16 // This is caused by Stderr not being tracked by System.Console.
17 // Completion support
18 // Why is Thread.Interrupt not working? Currently I resort to Abort which is too much.
19 //
20 // Limitations in System.Console:
21 // Console needs SIGWINCH support of some sort
22 // Console needs a way of updating its position after things have been written
23 // behind its back (P/Invoke puts for example).
24 // System.Console needs to get the DELETE character, and report accordingly.
25 //
26
27 using System;
28 using System.Text;
29 using System.IO;
30 using System.Threading;
31 using System.Reflection;
32
33 namespace Mono.Terminal {
34
35 public class LineEditor {
36
37 public class Completion {
38 public string [] Result;
39 public string Prefix;
40
41 public Completion (string prefix, string [] result)
42 {
43 Prefix = prefix;
44 Result = result;
45 }
46 }
47
48 public delegate Completion AutoCompleteHandler (string text, int pos);
49
50 //static StreamWriter log;
51
52 // The text being edited.
53 StringBuilder text;
54
55 // The text as it is rendered (replaces (char)1 with ^A on display for example).
56 StringBuilder rendered_text;
57
58 // The prompt specified, and the prompt shown to the user.
59 string prompt;
60 string shown_prompt;
61
62 // The current cursor position, indexes into "text", for an index
63 // into rendered_text, use TextToRenderPos
64 int cursor;
65
66 // The row where we started displaying data.
67 int home_row;
68
69 // The maximum length that has been displayed on the screen
70 int max_rendered;
71
72 // If we are done editing, this breaks the interactive loop
73 bool done = false;
74
75 // The thread where the Editing started taking place
76 Thread edit_thread;
77
78 // Our object that tracks history
79 History history;
80
81 // The contents of the kill buffer (cut/paste in Emacs parlance)
82 string kill_buffer = "";
83
84 // The string being searched for
85 string search;
86 string last_search;
87
88 // whether we are searching (-1= reverse; 0 = no; 1 = forward)
89 int searching;
90
91 // The position where we found the match.
92 int match_at;
93
94 // Used to implement the Kill semantics (multiple Alt-Ds accumulate)
95 KeyHandler last_handler;
96
97 delegate void KeyHandler ();
98
99 struct Handler {
100 public ConsoleKeyInfo CKI;
101 public KeyHandler KeyHandler;
102
103 public Handler (ConsoleKey key, KeyHandler h)
104 {
105 CKI = new ConsoleKeyInfo ((char) 0, key, false, false, false);
106 KeyHandler = h;
107 }
108
109 public Handler (char c, KeyHandler h)
110 {
111 KeyHandler = h;
112 // Use the "Zoom" as a flag that we only have a character.
113 CKI = new ConsoleKeyInfo (c, ConsoleKey.Zoom, false, false, false);
114 }
115
116 public Handler (ConsoleKeyInfo cki, KeyHandler h)
117 {
118 CKI = cki;
119 KeyHandler = h;
120 }
121
122 public static Handler Control (char c, KeyHandler h)
123 {
124 return new Handler ((char) (c - 'A' + 1), h);
125 }
126
127 public static Handler Alt (char c, ConsoleKey k, KeyHandler h)
128 {
129 ConsoleKeyInfo cki = new ConsoleKeyInfo ((char) c, k, false, true, false);
130 return new Handler (cki, h);
131 }
132 }
133
134 /// <summary>
135 /// Invoked when the user requests auto-completion using the tab character
136 /// </summary>
137 /// <remarks>
138 /// The result is null for no values found, an array with a single
139 /// string, in that case the string should be the text to be inserted
140 /// for example if the word at pos is "T", the result for a completion
141 /// of "ToString" should be "oString", not "ToString".
142 ///
143 /// When there are multiple results, the result should be the full
144 /// text
145 /// </remarks>
146 public AutoCompleteHandler AutoCompleteEvent;
147
148 static Handler [] handlers;
149
150 public LineEditor (string name) : this (name, 10) { }
151
152 public LineEditor (string name, int histsize)
153 {
154 handlers = new Handler [] {
155 new Handler (ConsoleKey.Home, CmdHome),
156 new Handler (ConsoleKey.End, CmdEnd),
157 new Handler (ConsoleKey.LeftArrow, CmdLeft),
158 new Handler (ConsoleKey.RightArrow, CmdRight),
159 new Handler (ConsoleKey.UpArrow, CmdHistoryPrev),
160 new Handler (ConsoleKey.DownArrow, CmdHistoryNext),
161 new Handler (ConsoleKey.Enter, CmdDone),
162 new Handler (ConsoleKey.Backspace, CmdBackspace),
163 new Handler (ConsoleKey.Delete, CmdDeleteChar),
164 new Handler (ConsoleKey.Tab, CmdTabOrComplete),
165
166 // Emacs keys
167 Handler.Control ('A', CmdHome),
168 Handler.Control ('E', CmdEnd),
169 Handler.Control ('B', CmdLeft),
170 Handler.Control ('F', CmdRight),
171 Handler.Control ('P', CmdHistoryPrev),
172 Handler.Control ('N', CmdHistoryNext),
173 Handler.Control ('K', CmdKillToEOF),
174 Handler.Control ('Y', CmdYank),
175 Handler.Control ('D', CmdDeleteChar),
176 Handler.Control ('L', CmdRefresh),
177 Handler.Control ('R', CmdReverseSearch),
178 Handler.Control ('G', delegate {} ),
179 Handler.Alt ('B', ConsoleKey.B, CmdBackwardWord),
180 Handler.Alt ('F', ConsoleKey.F, CmdForwardWord),
181
182 Handler.Alt ('D', ConsoleKey.D, CmdDeleteWord),
183 Handler.Alt ((char) 8, ConsoleKey.Backspace, CmdDeleteBackword),
184
185 // DEBUG
186 //Handler.Control ('T', CmdDebug),
187
188 // quote
189 Handler.Control ('Q', delegate { HandleChar (Console.ReadKey (true).KeyChar); })
190 };
191
192 rendered_text = new StringBuilder ();
193 text = new StringBuilder ();
194
195 history = new History (name, histsize);
196
197 //if (File.Exists ("log"))File.Delete ("log");
198 //log = File.CreateText ("log");
199 }
200
201 void CmdDebug ()
202 {
203 history.Dump ();
204 Console.WriteLine ();
205 Render ();
206 }
207
208 void Render ()
209 {
210 Console.Write (shown_prompt);
211 Console.Write (rendered_text);
212
213 int max = System.Math.Max (rendered_text.Length + shown_prompt.Length, max_rendered);
214
215 for (int i = rendered_text.Length + shown_prompt.Length; i < max_rendered; i++)
216 Console.Write (' ');
217 max_rendered = shown_prompt.Length + rendered_text.Length;
218
219 // Write one more to ensure that we always wrap around properly if we are at the
220 // end of a line.
221 Console.Write (' ');
222
223 UpdateHomeRow (max);
224 }
225
226 void UpdateHomeRow (int screenpos)
227 {
228 int lines = 1 + (screenpos / Console.WindowWidth);
229
230 home_row = Console.CursorTop - (lines - 1);
231 if (home_row < 0)
232 home_row = 0;
233 }
234
235
236 void RenderFrom (int pos)
237 {
238 int rpos = TextToRenderPos (pos);
239 int i;
240
241 for (i = rpos; i < rendered_text.Length; i++)
242 Console.Write (rendered_text [i]);
243
244 if ((shown_prompt.Length + rendered_text.Length) > max_rendered)
245 max_rendered = shown_prompt.Length + rendered_text.Length;
246 else {
247 int max_extra = max_rendered - shown_prompt.Length;
248 for (; i < max_extra; i++)
249 Console.Write (' ');
250 }
251 }
252
253 void ComputeRendered ()
254 {
255 rendered_text.Length = 0;
256
257 for (int i = 0; i < text.Length; i++){
258 int c = (int) text [i];
259 if (c < 26){
260 if (c == '\t')
261 rendered_text.Append (" ");
262 else {
263 rendered_text.Append ('^');
264 rendered_text.Append ((char) (c + (int) 'A' - 1));
265 }
266 } else
267 rendered_text.Append ((char)c);
268 }
269 }
270
271 int TextToRenderPos (int pos)
272 {
273 int p = 0;
274
275 for (int i = 0; i < pos; i++){
276 int c;
277
278 c = (int) text [i];
279
280 if (c < 26){
281 if (c == 9)
282 p += 4;
283 else
284 p += 2;
285 } else
286 p++;
287 }
288
289 return p;
290 }
291
292 int TextToScreenPos (int pos)
293 {
294 return shown_prompt.Length + TextToRenderPos (pos);
295 }
296
297 string Prompt {
298 get { return prompt; }
299 set { prompt = value; }
300 }
301
302 int LineCount {
303 get {
304 return (shown_prompt.Length + rendered_text.Length)/Console.WindowWidth;
305 }
306 }
307
308 void ForceCursor (int newpos)
309 {
310 cursor = newpos;
311
312 int actual_pos = shown_prompt.Length + TextToRenderPos (cursor);
313 int row = home_row + (actual_pos/Console.WindowWidth);
314 int col = actual_pos % Console.WindowWidth;
315
316 if (row >= Console.BufferHeight)
317 row = Console.BufferHeight-1;
318 Console.SetCursorPosition (col, row);
319
320 //log.WriteLine ("Going to cursor={0} row={1} col={2} actual={3} prompt={4} ttr={5} old={6}", newpos, row, col, actual_pos, prompt.Length, TextToRenderPos (cursor), cursor);
321 //log.Flush ();
322 }
323
324 void UpdateCursor (int newpos)
325 {
326 if (cursor == newpos)
327 return;
328
329 ForceCursor (newpos);
330 }
331
332 void InsertChar (char c)
333 {
334 int prev_lines = LineCount;
335 text = text.Insert (cursor, c);
336 ComputeRendered ();
337 if (prev_lines != LineCount){
338
339 Console.SetCursorPosition (0, home_row);
340 Render ();
341 ForceCursor (++cursor);
342 } else {
343 RenderFrom (cursor);
344 ForceCursor (++cursor);
345 UpdateHomeRow (TextToScreenPos (cursor));
346 }
347 }
348
349 //
350 // Commands
351 //
352 void CmdDone ()
353 {
354 done = true;
355 }
356
357 void CmdTabOrComplete ()
358 {
359 bool complete = false;
360
361 if (AutoCompleteEvent != null){
362 if (TabAtStartCompletes)
363 complete = true;
364 else {
365 for (int i = 0; i < cursor; i++){
366 if (!Char.IsWhiteSpace (text [i])){
367 complete = true;
368 break;
369 }
370 }
371 }
372
373 if (complete){
374 Completion completion = AutoCompleteEvent (text.ToString (), cursor);
375 string [] completions = completion.Result;
376 if (completions == null)
377 return;
378
379 int ncompletions = completions.Length;
380 if (ncompletions == 0)
381 return;
382
383 if (completions.Length == 1){
384 InsertTextAtCursor (completions [0]);
385 } else {
386 int last = -1;
387
388 for (int p = 0; p < completions [0].Length; p++){
389 char c = completions [0][p];
390
391
392 for (int i = 1; i < ncompletions; i++){
393 if (completions [i].Length < p)
394 goto mismatch;
395
396 if (completions [i][p] != c){
397 goto mismatch;
398 }
399 }
400 last = p;
401 }
402 mismatch:
403 if (last != -1){
404 InsertTextAtCursor (completions [0].Substring (0, last+1));
405 }
406 Console.WriteLine ();
407 foreach (string s in completions){
408 Console.Write (completion.Prefix);
409 Console.Write (s);
410 Console.Write (' ');
411 }
412 Console.WriteLine ();
413 Render ();
414 ForceCursor (cursor);
415 }
416 } else
417 HandleChar ('\t');
418 } else
419 HandleChar ('t');
420 }
421
422 void CmdHome ()
423 {
424 UpdateCursor (0);
425 }
426
427 void CmdEnd ()
428 {
429 UpdateCursor (text.Length);
430 }
431
432 void CmdLeft ()
433 {
434 if (cursor == 0)
435 return;
436
437 UpdateCursor (cursor-1);
438 }
439
440 void CmdBackwardWord ()
441 {
442 int p = WordBackward (cursor);
443 if (p == -1)
444 return;
445 UpdateCursor (p);
446 }
447
448 void CmdForwardWord ()
449 {
450 int p = WordForward (cursor);
451 if (p == -1)
452 return;
453 UpdateCursor (p);
454 }
455
456 void CmdRight ()
457 {
458 if (cursor == text.Length)
459 return;
460
461 UpdateCursor (cursor+1);
462 }
463
464 void RenderAfter (int p)
465 {
466 ForceCursor (p);
467 RenderFrom (p);
468 ForceCursor (cursor);
469 }
470
471 void CmdBackspace ()
472 {
473 if (cursor == 0)
474 return;
475
476 text.Remove (--cursor, 1);
477 ComputeRendered ();
478 RenderAfter (cursor);
479 }
480
481 void CmdDeleteChar ()
482 {
483 // If there is no input, this behaves like EOF
484 if (text.Length == 0){
485 done = true;
486 text = null;
487 Console.WriteLine ();
488 return;
489 }
490
491 if (cursor == text.Length)
492 return;
493 text.Remove (cursor, 1);
494 ComputeRendered ();
495 RenderAfter (cursor);
496 }
497
498 int WordForward (int p)
499 {
500 if (p >= text.Length)
501 return -1;
502
503 int i = p;
504 if (Char.IsPunctuation (text [p]) || Char.IsSymbol (text [p]) || Char.IsWhiteSpace (text[p])){
505 for (; i < text.Length; i++){
506 if (Char.IsLetterOrDigit (text [i]))
507 break;
508 }
509 for (; i < text.Length; i++){
510 if (!Char.IsLetterOrDigit (text [i]))
511 break;
512 }
513 } else {
514 for (; i < text.Length; i++){
515 if (!Char.IsLetterOrDigit (text [i]))
516 break;
517 }
518 }
519 if (i != p)
520 return i;
521 return -1;
522 }
523
524 int WordBackward (int p)
525 {
526 if (p == 0)
527 return -1;
528
529 int i = p-1;
530 if (i == 0)
531 return 0;
532
533 if (Char.IsPunctuation (text [i]) || Char.IsSymbol (text [i]) || Char.IsWhiteSpace (text[i])){
534 for (; i >= 0; i--){
535 if (Char.IsLetterOrDigit (text [i]))
536 break;
537 }
538 for (; i >= 0; i--){
539 if (!Char.IsLetterOrDigit (text[i]))
540 break;
541 }
542 } else {
543 for (; i >= 0; i--){
544 if (!Char.IsLetterOrDigit (text [i]))
545 break;
546 }
547 }
548 i++;
549
550 if (i != p)
551 return i;
552
553 return -1;
554 }
555
556 void CmdDeleteWord ()
557 {
558 int pos = WordForward (cursor);
559
560 if (pos == -1)
561 return;
562
563 string k = text.ToString (cursor, pos-cursor);
564
565 if (last_handler == CmdDeleteWord)
566 kill_buffer = kill_buffer + k;
567 else
568 kill_buffer = k;
569
570 text.Remove (cursor, pos-cursor);
571 ComputeRendered ();
572 RenderAfter (cursor);
573 }
574
575 void CmdDeleteBackword ()
576 {
577 int pos = WordBackward (cursor);
578 if (pos == -1)
579 return;
580
581 string k = text.ToString (pos, cursor-pos);
582
583 if (last_handler == CmdDeleteBackword)
584 kill_buffer = k + kill_buffer;
585 else
586 kill_buffer = k;
587
588 text.Remove (pos, cursor-pos);
589 ComputeRendered ();
590 RenderAfter (pos);
591 }
592
593 //
594 // Adds the current line to the history if needed
595 //
596 void HistoryUpdateLine ()
597 {
598 history.Update (text.ToString ());
599 }
600
601 void CmdHistoryPrev ()
602 {
603 if (!history.PreviousAvailable ())
604 return;
605
606 HistoryUpdateLine ();
607
608 SetText (history.Previous ());
609 }
610
611 void CmdHistoryNext ()
612 {
613 if (!history.NextAvailable())
614 return;
615
616 history.Update (text.ToString ());
617 SetText (history.Next ());
618
619 }
620
621 void CmdKillToEOF ()
622 {
623 kill_buffer = text.ToString (cursor, text.Length-cursor);
624 text.Length = cursor;
625 ComputeRendered ();
626 RenderAfter (cursor);
627 }
628
629 void CmdYank ()
630 {
631 InsertTextAtCursor (kill_buffer);
632 }
633
634 void InsertTextAtCursor (string str)
635 {
636 int prev_lines = LineCount;
637 text.Insert (cursor, str);
638 ComputeRendered ();
639 if (prev_lines != LineCount){
640 Console.SetCursorPosition (0, home_row);
641 Render ();
642 cursor += str.Length;
643 ForceCursor (cursor);
644 } else {
645 RenderFrom (cursor);
646 cursor += str.Length;
647 ForceCursor (cursor);
648 UpdateHomeRow (TextToScreenPos (cursor));
649 }
650 }
651
652 void SetSearchPrompt (string s)
653 {
654 SetPrompt ("(reverse-i-search)`" + s + "': ");
655 }
656
657 void ReverseSearch ()
658 {
659 int p;
660
661 if (cursor == text.Length){
662 // The cursor is at the end of the string
663
664 p = text.ToString ().LastIndexOf (search);
665 if (p != -1){
666 match_at = p;
667 cursor = p;
668 ForceCursor (cursor);
669 return;
670 }
671 } else {
672 // The cursor is somewhere in the middle of the string
673 int start = (cursor == match_at) ? cursor - 1 : cursor;
674 if (start != -1){
675 p = text.ToString ().LastIndexOf (search, start);
676 if (p != -1){
677 match_at = p;
678 cursor = p;
679 ForceCursor (cursor);
680 return;
681 }
682 }
683 }
684
685 // Need to search backwards in history
686 HistoryUpdateLine ();
687 string s = history.SearchBackward (search);
688 if (s != null){
689 match_at = -1;
690 SetText (s);
691 ReverseSearch ();
692 }
693 }
694
695 void CmdReverseSearch ()
696 {
697 if (searching == 0){
698 match_at = -1;
699 last_search = search;
700 searching = -1;
701 search = "";
702 SetSearchPrompt ("");
703 } else {
704 if (search == ""){
705 if (last_search != "" && last_search != null){
706 search = last_search;
707 SetSearchPrompt (search);
708
709 ReverseSearch ();
710 }
711 return;
712 }
713 ReverseSearch ();
714 }
715 }
716
717 void SearchAppend (char c)
718 {
719 search = search + c;
720 SetSearchPrompt (search);
721
722 //
723 // If the new typed data still matches the current text, stay here
724 //
725 if (cursor < text.Length){
726 string r = text.ToString (cursor, text.Length - cursor);
727 if (r.StartsWith (search))
728 return;
729 }
730
731 ReverseSearch ();
732 }
733
734 void CmdRefresh ()
735 {
736 Console.Clear ();
737 max_rendered = 0;
738 Render ();
739 ForceCursor (cursor);
740 }
741
742 void InterruptEdit (object sender, ConsoleCancelEventArgs a)
743 {
744 // Do not abort our program:
745 a.Cancel = true;
746
747 // Interrupt the editor
748 edit_thread.Abort();
749 }
750
751 void HandleChar (char c)
752 {
753 if (searching != 0)
754 SearchAppend (c);
755 else
756 InsertChar (c);
757 }
758
759 void EditLoop ()
760 {
761 ConsoleKeyInfo cki;
762
763 while (!done){
764 ConsoleModifiers mod;
765
766 cki = Console.ReadKey (true);
767 if (cki.Key == ConsoleKey.Escape){
768 cki = Console.ReadKey (true);
769
770 mod = ConsoleModifiers.Alt;
771 } else
772 mod = cki.Modifiers;
773
774 bool handled = false;
775
776 foreach (Handler handler in handlers){
777 ConsoleKeyInfo t = handler.CKI;
778
779 if (t.Key == cki.Key && t.Modifiers == mod){
780 handled = true;
781 handler.KeyHandler ();
782 last_handler = handler.KeyHandler;
783 break;
784 } else if (t.KeyChar == cki.KeyChar && t.Key == ConsoleKey.Zoom){
785 handled = true;
786 handler.KeyHandler ();
787 last_handler = handler.KeyHandler;
788 break;
789 }
790 }
791 if (handled){
792 if (searching != 0){
793 if (last_handler != CmdReverseSearch){
794 searching = 0;
795 SetPrompt (prompt);
796 }
797 }
798 continue;
799 }
800
801 if (cki.KeyChar != (char) 0)
802 HandleChar (cki.KeyChar);
803 }
804 }
805
806 void InitText (string initial)
807 {
808 text = new StringBuilder (initial);
809 ComputeRendered ();
810 cursor = text.Length;
811 Render ();
812 ForceCursor (cursor);
813 }
814
815 void SetText (string newtext)
816 {
817 Console.SetCursorPosition (0, home_row);
818 InitText (newtext);
819 }
820
821 void SetPrompt (string newprompt)
822 {
823 shown_prompt = newprompt;
824 Console.SetCursorPosition (0, home_row);
825 Render ();
826 ForceCursor (cursor);
827 }
828
829 public string Edit (string prompt, string initial)
830 {
831 edit_thread = Thread.CurrentThread;
832 searching = 0;
833 Console.CancelKeyPress += InterruptEdit;
834
835 done = false;
836 history.CursorToEnd ();
837 max_rendered = 0;
838
839 Prompt = prompt;
840 shown_prompt = prompt;
841 InitText (initial);
842 history.Append (initial);
843
844 do {
845 try {
846 EditLoop ();
847 } catch (ThreadAbortException){
848 searching = 0;
849 Thread.ResetAbort ();
850 Console.WriteLine ();
851 SetPrompt (prompt);
852 SetText ("");
853 }
854 } while (!done);
855 Console.WriteLine ();
856
857 Console.CancelKeyPress -= InterruptEdit;
858
859 if (text == null){
860 history.Close ();
861 return null;
862 }
863
864 string result = text.ToString ();
865 if (result != "")
866 history.Accept (result);
867 else
868 history.RemoveLast ();
869
870 return result;
871 }
872
873 public void SaveHistory ()
874 {
875 if (history != null) {
876 history.Close ();
877 }
878 }
879
880 public bool TabAtStartCompletes { get; set; }
881
882 //
883 // Emulates the bash-like behavior, where edits done to the
884 // history are recorded
885 //
886 class History {
887 string [] history;
888 int head, tail;
889 int cursor, count;
890 string histfile;
891
892 public History (string app, int size)
893 {
894 if (size < 1)
895 throw new ArgumentException ("size");
896
897 if (app != null){
898 string dir = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
899 //Console.WriteLine (dir);
900 /*
901 if (!Directory.Exists (dir)){
902 try {
903 Directory.CreateDirectory (dir);
904 } catch {
905 app = null;
906 }
907 }
908 if (app != null)
909 histfile = Path.Combine (dir, app) + ".history";
910 */
911 histfile = Path.Combine (dir, ".mal-history");
912 }
913
914 history = new string [size];
915 head = tail = cursor = 0;
916
917 if (File.Exists (histfile)){
918 using (StreamReader sr = File.OpenText (histfile)){
919 string line;
920
921 while ((line = sr.ReadLine ()) != null){
922 if (line != "")
923 Append (line);
924 }
925 }
926 }
927 }
928
929 public void Close ()
930 {
931 if (histfile == null)
932 return;
933
934 try {
935 using (StreamWriter sw = File.CreateText (histfile)){
936 int start = (count == history.Length) ? head : tail;
937 for (int i = start; i < start+count; i++){
938 int p = i % history.Length;
939 sw.WriteLine (history [p]);
940 }
941 }
942 } catch {
943 // ignore
944 }
945 }
946
947 //
948 // Appends a value to the history
949 //
950 public void Append (string s)
951 {
952 //Console.WriteLine ("APPENDING {0} head={1} tail={2}", s, head, tail);
953 history [head] = s;
954 head = (head+1) % history.Length;
955 if (head == tail)
956 tail = (tail+1 % history.Length);
957 if (count != history.Length)
958 count++;
959 //Console.WriteLine ("DONE: head={1} tail={2}", s, head, tail);
960 }
961
962 //
963 // Updates the current cursor location with the string,
964 // to support editing of history items. For the current
965 // line to participate, an Append must be done before.
966 //
967 public void Update (string s)
968 {
969 history [cursor] = s;
970 }
971
972 public void RemoveLast ()
973 {
974 head = head-1;
975 if (head < 0)
976 head = history.Length-1;
977 }
978
979 public void Accept (string s)
980 {
981 int t = head-1;
982 if (t < 0)
983 t = history.Length-1;
984
985 history [t] = s;
986 }
987
988 public bool PreviousAvailable ()
989 {
990 //Console.WriteLine ("h={0} t={1} cursor={2}", head, tail, cursor);
991 if (count == 0)
992 return false;
993 int next = cursor-1;
994 if (next < 0)
995 next = count-1;
996
997 if (next == head)
998 return false;
999
1000 return true;
1001 }
1002
1003 public bool NextAvailable ()
1004 {
1005 if (count == 0)
1006 return false;
1007 int next = (cursor + 1) % history.Length;
1008 if (next == head)
1009 return false;
1010 return true;
1011 }
1012
1013
1014 //
1015 // Returns: a string with the previous line contents, or
1016 // nul if there is no data in the history to move to.
1017 //
1018 public string Previous ()
1019 {
1020 if (!PreviousAvailable ())
1021 return null;
1022
1023 cursor--;
1024 if (cursor < 0)
1025 cursor = history.Length - 1;
1026
1027 return history [cursor];
1028 }
1029
1030 public string Next ()
1031 {
1032 if (!NextAvailable ())
1033 return null;
1034
1035 cursor = (cursor + 1) % history.Length;
1036 return history [cursor];
1037 }
1038
1039 public void CursorToEnd ()
1040 {
1041 if (head == tail)
1042 return;
1043
1044 cursor = head;
1045 }
1046
1047 public void Dump ()
1048 {
1049 Console.WriteLine ("Head={0} Tail={1} Cursor={2} count={3}", head, tail, cursor, count);
1050 for (int i = 0; i < history.Length;i++){
1051 Console.WriteLine (" {0} {1}: {2}", i == cursor ? "==>" : " ", i, history[i]);
1052 }
1053 //log.Flush ();
1054 }
1055
1056 public string SearchBackward (string term)
1057 {
1058 for (int i = 0; i < count; i++){
1059 int slot = cursor-i-1;
1060 if (slot < 0)
1061 slot = history.Length+slot;
1062 if (slot >= history.Length)
1063 slot = 0;
1064 if (history [slot] != null && history [slot].IndexOf (term) != -1){
1065 cursor = slot;
1066 return history [slot];
1067 }
1068 }
1069
1070 return null;
1071 }
1072
1073 }
1074 }
1075
1076 #if DEMO
1077 class Demo {
1078 static void Main ()
1079 {
1080 LineEditor le = new LineEditor ("foo");
1081 string s;
1082
1083 while ((s = le.Edit ("shell> ", "")) != null){
1084 Console.WriteLine ("----> [{0}]", s);
1085 }
1086 }
1087 }
1088 #endif
1089 }