+ static void SaveExcursion (Action code)
+ {
+ var saved_col = Console.CursorLeft;
+ var saved_row = Console.CursorTop;
+ var saved_fore = Console.ForegroundColor;
+ var saved_back = Console.BackgroundColor;
+
+ code ();
+
+ Console.CursorLeft = saved_col;
+ Console.CursorTop = saved_row;
+ if (unix_reset_colors != null){
+ unix_raw_output.Write (unix_reset_colors, 0, unix_reset_colors.Length);
+ } else {
+ Console.ForegroundColor = saved_fore;
+ Console.BackgroundColor = saved_back;
+ }
+ }
+
+ class CompletionState {
+ public string Prefix;
+ public string [] Completions;
+ public int Col, Row, Width, Height;
+ int selected_item, top_item;
+
+ public CompletionState (int col, int row, int width, int height)
+ {
+ Col = col;
+ Row = row;
+ Width = width;
+ Height = height;
+
+ if (Col < 0)
+ throw new ArgumentException ("Cannot be less than zero" + Col, "Col");
+ if (Row < 0)
+ throw new ArgumentException ("Cannot be less than zero", "Row");
+ if (Width < 1)
+ throw new ArgumentException ("Cannot be less than one", "Width");
+ if (Height < 1)
+ throw new ArgumentException ("Cannot be less than one", "Height");
+
+ }
+
+ void DrawSelection ()
+ {
+ for (int r = 0; r < Height; r++){
+ int item_idx = top_item + r;
+ bool selected = (item_idx == selected_item);
+
+ Console.ForegroundColor = selected ? ConsoleColor.Black : ConsoleColor.Gray;
+ Console.BackgroundColor = selected ? ConsoleColor.Cyan : ConsoleColor.Blue;
+
+ var item = Prefix + Completions [item_idx];
+ if (item.Length > Width)
+ item = item.Substring (0, Width);
+
+ Console.CursorLeft = Col;
+ Console.CursorTop = Row + r;
+ Console.Write (item);
+ for (int space = item.Length; space <= Width; space++)
+ Console.Write (" ");
+ }
+ }
+
+ public string Current {
+ get {
+ return Completions [selected_item];
+ }
+ }
+
+ public void Show ()
+ {
+ SaveExcursion (DrawSelection);
+ }
+
+ public void SelectNext ()
+ {
+ if (selected_item+1 < Completions.Length){
+ selected_item++;
+ if (selected_item - top_item >= Height)
+ top_item++;
+ SaveExcursion (DrawSelection);
+ }
+ }
+
+ public void SelectPrevious ()
+ {
+ if (selected_item > 0){
+ selected_item--;
+ if (selected_item < top_item)
+ top_item = selected_item;
+ SaveExcursion (DrawSelection);
+ }
+ }
+
+ void Clear ()
+ {
+ for (int r = 0; r < Height; r++){
+ Console.CursorLeft = Col;
+ Console.CursorTop = Row + r;
+ for (int space = 0; space <= Width; space++)
+ Console.Write (" ");
+ }
+ }
+
+ public void Remove ()
+ {
+ SaveExcursion (Clear);
+ }
+ }
+
+ void ShowCompletions (string prefix, string [] completions)
+ {
+ // Ensure we have space, determine window size
+ int window_height = System.Math.Min (completions.Length, Console.WindowHeight/5);
+ int target_line = Console.WindowHeight-window_height-1;
+ if (Console.CursorTop > target_line){
+ var saved_left = Console.CursorLeft;
+ var delta = Console.CursorTop-target_line;
+ Console.CursorLeft = 0;
+ Console.CursorTop = Console.WindowHeight-1;
+ for (int i = 0; i < delta+1; i++){
+ for (int c = Console.WindowWidth; c > 0; c--)
+ Console.Write (" "); // To debug use ("{0}", i%10);
+ }
+ Console.CursorTop = target_line;
+ Console.CursorLeft = 0;
+ Render ();
+ }
+
+ const int MaxWidth = 50;
+ int window_width = 12;
+ int plen = prefix.Length;
+ foreach (var s in completions)
+ window_width = System.Math.Max (plen + s.Length, window_width);
+ window_width = System.Math.Min (window_width, MaxWidth);
+
+ if (current_completion == null){
+ int left = Console.CursorLeft-prefix.Length;
+
+ if (left + window_width + 1 >= Console.WindowWidth)
+ left = Console.WindowWidth-window_width-1;
+
+ current_completion = new CompletionState (left, Console.CursorTop+1, window_width, window_height) {
+ Prefix = prefix,
+ Completions = completions,
+ };
+ } else {
+ current_completion.Prefix = prefix;
+ current_completion.Completions = completions;
+ }
+ current_completion.Show ();
+ Console.CursorLeft = 0;
+ }
+
+ void HideCompletions ()
+ {
+ if (current_completion == null)
+ return;
+ current_completion.Remove ();
+ current_completion = null;
+ }
+
+ //
+ // Triggers the completion engine, if insertBestMatch is true, then this will
+ // insert the best match found, this behaves like the shell "tab" which will
+ // complete as much as possible given the options.
+ //
+ void Complete ()
+ {
+ Completion completion = AutoCompleteEvent (text.ToString (), cursor);
+ string [] completions = completion.Result;
+ if (completions == null){
+ HideCompletions ();
+ return;
+ }
+
+ int ncompletions = completions.Length;
+ if (ncompletions == 0){
+ HideCompletions ();
+ return;
+ }
+
+ if (completions.Length == 1){
+ InsertTextAtCursor (completions [0]);
+ HideCompletions ();
+ } else {
+ int last = -1;
+
+ for (int p = 0; p < completions [0].Length; p++){
+ char c = completions [0][p];
+
+
+ for (int i = 1; i < ncompletions; i++){
+ if (completions [i].Length < p)
+ goto mismatch;
+
+ if (completions [i][p] != c){
+ goto mismatch;
+ }
+ }
+ last = p;
+ }
+ mismatch:
+ var prefix = completion.Prefix;
+ if (last != -1){
+ InsertTextAtCursor (completions [0].Substring (0, last+1));
+
+ // Adjust the completions to skip the common prefix
+ prefix += completions [0].Substring (0, last+1);
+ for (int i = 0; i < completions.Length; i++)
+ completions [i] = completions [i].Substring (last+1);
+ }
+ ShowCompletions (prefix, completions);
+ Render ();
+ ForceCursor (cursor);
+ }
+ }
+
+ //
+ // When the user has triggered a completion window, this will try to update
+ // the contents of it. The completion window is assumed to be hidden at this
+ // point
+ //
+ void UpdateCompletionWindow ()
+ {
+ if (current_completion != null)
+ throw new Exception ("This method should only be called if the window has been hidden");
+
+ Completion completion = AutoCompleteEvent (text.ToString (), cursor);
+ string [] completions = completion.Result;
+ if (completions == null)
+ return;
+
+ int ncompletions = completions.Length;
+ if (ncompletions == 0)
+ return;
+
+ ShowCompletions (completion.Prefix, completion.Result);
+ Render ();
+ ForceCursor (cursor);
+ }
+
+