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