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