TypeScript: step 6
[jackhill/mal.git] / cs / getline.cs
CommitLineData
afde2df0
JM
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
27using System;
28using System.Text;
29using System.IO;
30using System.Threading;
31using System.Reflection;
32
33namespace 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){
718887c3 898 string dir = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
afde2df0 899 //Console.WriteLine (dir);
f15b4021
JM
900// if (!Directory.Exists (dir)){
901// try {
902// Directory.CreateDirectory (dir);
903// } catch {
904// app = null;
905// }
906// }
907// if (app != null)
908// histfile = Path.Combine (dir, app) + ".history";
718887c3 909 histfile = Path.Combine (dir, ".mal-history");
afde2df0
JM
910 }
911
912 history = new string [size];
913 head = tail = cursor = 0;
914
915 if (File.Exists (histfile)){
916 using (StreamReader sr = File.OpenText (histfile)){
917 string line;
918
919 while ((line = sr.ReadLine ()) != null){
920 if (line != "")
921 Append (line);
922 }
923 }
924 }
925 }
926
927 public void Close ()
928 {
929 if (histfile == null)
930 return;
931
932 try {
933 using (StreamWriter sw = File.CreateText (histfile)){
934 int start = (count == history.Length) ? head : tail;
935 for (int i = start; i < start+count; i++){
936 int p = i % history.Length;
937 sw.WriteLine (history [p]);
938 }
939 }
940 } catch {
941 // ignore
942 }
943 }
944
945 //
946 // Appends a value to the history
947 //
948 public void Append (string s)
949 {
950 //Console.WriteLine ("APPENDING {0} head={1} tail={2}", s, head, tail);
951 history [head] = s;
952 head = (head+1) % history.Length;
953 if (head == tail)
954 tail = (tail+1 % history.Length);
955 if (count != history.Length)
956 count++;
957 //Console.WriteLine ("DONE: head={1} tail={2}", s, head, tail);
958 }
959
960 //
961 // Updates the current cursor location with the string,
962 // to support editing of history items. For the current
963 // line to participate, an Append must be done before.
964 //
965 public void Update (string s)
966 {
967 history [cursor] = s;
968 }
969
970 public void RemoveLast ()
971 {
972 head = head-1;
973 if (head < 0)
974 head = history.Length-1;
975 }
976
977 public void Accept (string s)
978 {
979 int t = head-1;
980 if (t < 0)
981 t = history.Length-1;
982
983 history [t] = s;
984 }
985
986 public bool PreviousAvailable ()
987 {
988 //Console.WriteLine ("h={0} t={1} cursor={2}", head, tail, cursor);
989 if (count == 0)
990 return false;
991 int next = cursor-1;
992 if (next < 0)
993 next = count-1;
994
995 if (next == head)
996 return false;
997
998 return true;
999 }
1000
1001 public bool NextAvailable ()
1002 {
1003 if (count == 0)
1004 return false;
1005 int next = (cursor + 1) % history.Length;
1006 if (next == head)
1007 return false;
1008 return true;
1009 }
1010
1011
1012 //
1013 // Returns: a string with the previous line contents, or
1014 // nul if there is no data in the history to move to.
1015 //
1016 public string Previous ()
1017 {
1018 if (!PreviousAvailable ())
1019 return null;
1020
1021 cursor--;
1022 if (cursor < 0)
1023 cursor = history.Length - 1;
1024
1025 return history [cursor];
1026 }
1027
1028 public string Next ()
1029 {
1030 if (!NextAvailable ())
1031 return null;
1032
1033 cursor = (cursor + 1) % history.Length;
1034 return history [cursor];
1035 }
1036
1037 public void CursorToEnd ()
1038 {
1039 if (head == tail)
1040 return;
1041
1042 cursor = head;
1043 }
1044
1045 public void Dump ()
1046 {
1047 Console.WriteLine ("Head={0} Tail={1} Cursor={2} count={3}", head, tail, cursor, count);
1048 for (int i = 0; i < history.Length;i++){
1049 Console.WriteLine (" {0} {1}: {2}", i == cursor ? "==>" : " ", i, history[i]);
1050 }
1051 //log.Flush ();
1052 }
1053
1054 public string SearchBackward (string term)
1055 {
1056 for (int i = 0; i < count; i++){
1057 int slot = cursor-i-1;
1058 if (slot < 0)
1059 slot = history.Length+slot;
1060 if (slot >= history.Length)
1061 slot = 0;
1062 if (history [slot] != null && history [slot].IndexOf (term) != -1){
1063 cursor = slot;
1064 return history [slot];
1065 }
1066 }
1067
1068 return null;
1069 }
1070
1071 }
1072 }
1073
1074#if DEMO
1075 class Demo {
1076 static void Main ()
1077 {
1078 LineEditor le = new LineEditor ("foo");
1079 string s;
1080
1081 while ((s = le.Edit ("shell> ", "")) != null){
1082 Console.WriteLine ("----> [{0}]", s);
1083 }
1084 }
1085 }
1086#endif
1087}