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