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