Browse Source

Added Suggestion class

tznind 2 years ago
parent
commit
b30dd8e065

+ 11 - 3
Terminal.Gui/Core/Autocomplete/AppendAutocomplete.cs

@@ -52,8 +52,10 @@ namespace Terminal.Gui {
 			// draw it like its selected even though its not
 			Application.Driver.SetAttribute (new Attribute (Color.DarkGray, textField.ColorScheme.Focus.Background));
 			textField.Move (textField.Text.Length, 0);
-			
-			Application.Driver.AddStr (this.Suggestions.ElementAt(this.SelectedIdx));
+
+			var suggestion = this.Suggestions.ElementAt (this.SelectedIdx);
+			var fragment = suggestion.Replacement.Substring (suggestion.Remove);
+			Application.Driver.AddStr (fragment);
 		}
 
 		public AppendAutocomplete (TextField textField)
@@ -70,7 +72,13 @@ namespace Terminal.Gui {
 		internal bool AcceptSelectionIfAny ()
 		{
 			if (this.MakingSuggestion ()) {
-				textField.Text += this.Suggestions.ElementAt(this.SelectedIdx);
+
+				var insert = this.Suggestions.ElementAt (this.SelectedIdx);
+				var newText = textField.Text.ToString ();
+				newText = newText.Substring(0,newText.Length - insert.Remove);
+				newText += insert.Replacement;
+				textField.Text = newText;
+
 				this.MoveCursorToEnd ();
 
 				this.ClearSuggestions ();

+ 18 - 31
Terminal.Gui/Core/Autocomplete/AutocompleteBase.cs

@@ -15,7 +15,7 @@ namespace Terminal.Gui {
 		/// match with the current cursor position/text in the <see cref="HostControl"/>.
 		/// </summary>
 		/// <param name="columnOffset">The column offset. Current (zero - default), left (negative), right (positive).</param>
-		IEnumerable<string> GenerateSuggestions (List<Rune> currentLine, int idx);
+		IEnumerable<Suggestion> GenerateSuggestions (List<Rune> currentLine, int idx);
 
 		bool IsWordChar (Rune rune);
 
@@ -29,22 +29,23 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		public virtual List<string> AllSuggestions { get; set; } = new List<string> ();
 
-		public IEnumerable<string> GenerateSuggestions (List<Rune> currentLine, int idx)
+		public IEnumerable<Suggestion> GenerateSuggestions (List<Rune> currentLine, int idx)
 		{
 			// if there is nothing to pick from
 			if (AllSuggestions.Count == 0) {
-				return Enumerable.Empty<string>();
+				return Enumerable.Empty<Suggestion>();
 			}
 
 			var currentWord = IdxToWord (currentLine, idx);
 
 			if (string.IsNullOrWhiteSpace (currentWord)) {
-				return Enumerable.Empty<string>();
+				return Enumerable.Empty<Suggestion>();
 			} else {
 				return AllSuggestions.Where (o =>
 				o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) &&
 				!o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase)
-				).ToList ().AsReadOnly ();
+				).Select(o=>new Suggestion(currentWord.Length,o))
+					.ToList ().AsReadOnly ();
 
 			}
 		}
@@ -110,40 +111,32 @@ namespace Terminal.Gui {
 	}
 
 	public abstract class AutocompleteBase : IAutocomplete {
+		
+		/// <inheritdoc/>
 		public abstract View HostControl { get; set; }
+		/// <inheritdoc/>
 		public bool PopupInsideContainer { get; set; }
 		
 		public ISuggestionGenerator SuggestionGenerator {get;set;} = new SingleWordSuggestionGenerator();
 
-		/// <summary>
-		/// The maximum width of the autocomplete dropdown
-		/// </summary>
+		/// <inheritdoc/>
 		public virtual int MaxWidth { get; set; } = 10;
 
-		/// <summary>
-		/// The maximum number of visible rows in the autocomplete dropdown to render
-		/// </summary>
+		/// <inheritdoc/>
 		public virtual int MaxHeight { get; set; } = 6;
 
 		/// <inheritdoc/>
 
 
-		/// <summary>
-		/// True if the autocomplete should be considered open and visible
-		/// </summary>
+		/// <inheritdoc/>
 		public virtual bool Visible { get; set; }
 
-		/// <summary>
-		/// The strings that form the current list of suggestions to render
-		/// based on what the user has typed so far.
-		/// </summary>
-		public virtual ReadOnlyCollection<string> Suggestions { get; set; } = new ReadOnlyCollection<string> (new string [0]);
+		/// <inheritdoc/>
+		public virtual ReadOnlyCollection<Suggestion> Suggestions { get; set; } = new ReadOnlyCollection<Suggestion> (new Suggestion [0]);
 
 
 
-		/// <summary>
-		/// The currently selected index into <see cref="Suggestions"/> that the user has highlighted
-		/// </summary>
+		/// <inheritdoc/>
 		public virtual int SelectedIdx { get; set; }
 
 
@@ -167,20 +160,14 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public abstract void RenderOverlay (Point renderAt);
 
-		/// <summary>
-		/// Clears <see cref="Suggestions"/>
-		/// </summary>
+		/// <inheritdoc/>>
 		public virtual void ClearSuggestions ()
 		{
-			Suggestions = Enumerable.Empty<string> ().ToList ().AsReadOnly ();
+			Suggestions = Enumerable.Empty<Suggestion> ().ToList ().AsReadOnly ();
 		}
 
 
-		/// <summary>
-		/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
-		/// match with the current cursor position/text in the <see cref="HostControl"/>
-		/// </summary>
-		/// <param name="columnOffset">The column offset.</param>
+		/// <inheritdoc/>
 		public virtual void GenerateSuggestions (List<Rune> currentLine, int idx)
 		{
 			Suggestions = SuggestionGenerator.GenerateSuggestions(currentLine, idx).ToList().AsReadOnly();

+ 45 - 1
Terminal.Gui/Core/Autocomplete/IAutocomplete.cs

@@ -5,6 +5,44 @@ using Rune = System.Rune;
 
 namespace Terminal.Gui {
 
+	/// <summary>
+	/// A replacement suggestion made by <see cref="IAutocomplete"/>
+	/// </summary>
+	public class Suggestion {
+		/// <summary>
+		/// The number of characters to remove at the current cursor position
+		/// before adding the <see cref="Replacement"/>
+		/// </summary>
+		public int Remove { get; }
+
+		/// <summary>
+		/// The user visible description for the <see cref="Replacement"/>.  Typically
+		/// this would be the same as <see cref="Replacement"/> but may vary in advanced
+		/// use cases (e.g. Title= "ctor", Replacement = "MyClass()\n{\n}")
+		/// </summary>
+		public string Title {get;}
+
+		/// <summary>
+		/// The replacement text that will be added
+		/// </summary>
+		public string Replacement { get; }
+
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="Suggestion"/> class.
+		/// </summary>
+		/// <param name="remove"></param>
+		/// <param name="replacement"></param>
+		/// <param name="title">User visible title for the suggestion or null if the same
+		/// as <paramref name="replacement"/>.</param>
+		public Suggestion (int remove, string replacement, string title = null)
+		{
+			Remove = remove;
+			Replacement = replacement;
+			Title = title ?? replacement;
+		}
+	}
+
 	/// <summary>
 	/// Renders an overlay on another view at a given point that allows selecting
 	/// from a range of 'autocomplete' options.
@@ -40,7 +78,7 @@ namespace Terminal.Gui {
 		/// The strings that form the current list of suggestions to render
 		/// based on what the user has typed so far.
 		/// </summary>
-		ReadOnlyCollection<string> Suggestions { get; set; }
+		ReadOnlyCollection<Suggestion> Suggestions { get; set; }
 
 		/// <summary>
 		/// The currently selected index into <see cref="Suggestions"/> that the user has highlighted
@@ -102,6 +140,12 @@ namespace Terminal.Gui {
 
 		ISuggestionGenerator SuggestionGenerator {get;set;}
 
+
+		/// <summary>
+		/// Populates <see cref="Suggestions"/> with all <see cref="Suggestion"/> 
+		/// proposed by <see cref="SuggestionGenerator"/> at the given <paramref name="idx"/>
+		/// of <paramref name="currentLine"/>
+		/// </summary>
 		void GenerateSuggestions (List<Rune> currentLine, int idx);
 	}
 }

+ 12 - 21
Terminal.Gui/Core/Autocomplete/PopupAutocomplete.cs

@@ -194,7 +194,7 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			width = Math.Min (MaxWidth, toRender.Max (s => s.Length));
+			width = Math.Min (MaxWidth, toRender.Max (s => s.Title.Length));
 
 			if (PopupInsideContainer) {
 				// don't overspill horizontally, let's see if can be displayed on the left
@@ -242,7 +242,7 @@ namespace Terminal.Gui {
 
 				popup.Move (0, i);
 
-				var text = TextFormatter.ClipOrPad (toRender [i], width);
+				var text = TextFormatter.ClipOrPad (toRender [i].Title, width);
 
 				Application.Driver.AddStr (text);
 			}
@@ -339,7 +339,7 @@ namespace Terminal.Gui {
 				if (!Visible) {
 					return false;
 				}
-				
+
 				// TODO: Revisit this
 				//GenerateSuggestions ();
 
@@ -405,7 +405,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		
+
 
 		/// <summary>
 		/// Completes the autocomplete selection process.  Called when user hits the <see cref="SelectionKey"/>.
@@ -432,24 +432,15 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="accepted"></param>
 		/// <returns>True if the insertion was possible otherwise false</returns>
-		protected virtual bool InsertSelection (string accepted)
+		protected virtual bool InsertSelection (Suggestion accepted)
 		{
-			// TODO: Revisit this
-			/*var typedSoFar = GetCurrentWord () ?? "";
-
-			if (typedSoFar.Length < accepted.Length) {
-
-				// delete the text
-				for (int i = 0; i < typedSoFar.Length; i++) {
-					DeleteTextBackwards ();
-				}
-				*/
-
-				InsertText (accepted);
-				return true;
-			//}
+			// delete the text
+			for (int i = 0; i < accepted.Remove; i++) {
+				DeleteTextBackwards ();
+			}
 
-			//return false;
+			InsertText (accepted.Replacement);
+			return true;
 		}
 
 
@@ -459,7 +450,7 @@ namespace Terminal.Gui {
 		protected abstract void DeleteTextBackwards ();
 
 		/// <summary>
-		/// Inser the selected text in the <see cref="HostControl"/>.
+		/// Insert the selected text in the <see cref="HostControl"/>.
 		/// </summary>
 		/// <param name="accepted"></param>
 		protected abstract void InsertText (string accepted);

+ 5 - 2
UICatalog/Scenarios/Editor.cs

@@ -544,6 +544,9 @@ namespace UICatalog.Scenarios {
 
 		private MenuItem CreateAutocomplete ()
 		{
+			var singleWordGenerator = new SingleWordSuggestionGenerator ();
+			_textView.Autocomplete.SuggestionGenerator = singleWordGenerator;
+
 			var auto = new MenuItem ();
 			auto.Title = "Autocomplete";
 			auto.CheckType |= MenuItemCheckStyle.Checked;
@@ -551,13 +554,13 @@ namespace UICatalog.Scenarios {
 			auto.Action += () => {
 				if ((bool)(auto.Checked = !auto.Checked)) {
 					// setup autocomplete with all words currently in the editor
-					_textView.Autocomplete.AllSuggestions =
+					singleWordGenerator.AllSuggestions =
 
 					Regex.Matches (_textView.Text.ToString (), "\\w+")
 					.Select (s => s.Value)
 					.Distinct ().ToList ();
 				} else {
-					_textView.Autocomplete.AllSuggestions.Clear ();
+					singleWordGenerator.AllSuggestions.Clear ();
 
 				}
 			};

+ 3 - 1
UICatalog/Scenarios/SyntaxHighlighting.cs

@@ -138,7 +138,9 @@ namespace UICatalog.Scenarios {
 				keywords.Add ("union");
 				keywords.Add ("exists");
 
-				Autocomplete.AllSuggestions = keywords.ToList ();
+				Autocomplete.SuggestionGenerator = new SingleWordSuggestionGenerator () {
+					AllSuggestions = keywords.ToList ()
+				};
 
 				magenta = Driver.MakeAttribute (Color.Magenta, Color.Black);
 				blue = Driver.MakeAttribute (Color.Cyan, Color.Black);

+ 6 - 2
UICatalog/Scenarios/Text.cs

@@ -24,11 +24,15 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Percent (50) - 1,
 				Height = 2
 			};
+
+			var singleWordGenerator = new SingleWordSuggestionGenerator ();
+			textField.Autocomplete.SuggestionGenerator = singleWordGenerator;
+
 			textField.TextChanging += TextField_TextChanging;
 
 			void TextField_TextChanging (object sender, TextChangingEventArgs e)
 			{
-				textField.Autocomplete.AllSuggestions = Regex.Matches (e.NewText.ToString (), "\\w+")
+				singleWordGenerator.AllSuggestions = Regex.Matches (e.NewText.ToString (), "\\w+")
 					.Select (s => s.Value)
 					.Distinct ().ToList ();
 			}
@@ -58,7 +62,7 @@ namespace UICatalog.Scenarios {
 			// This shows how to enable autocomplete in TextView.
 			void TextView_DrawContent (object sender, DrawEventArgs e)
 			{
-				textView.Autocomplete.AllSuggestions = Regex.Matches (textView.Text.ToString (), "\\w+")
+				singleWordGenerator.AllSuggestions = Regex.Matches (textView.Text.ToString (), "\\w+")
 					.Select (s => s.Value)
 					.Distinct ().ToList ();
 			}

+ 3 - 1
UICatalog/Scenarios/TextViewAutocompletePopup.cs

@@ -26,6 +26,8 @@ namespace UICatalog.Scenarios {
 			var width = 20;
 			var text = " jamp jemp jimp jomp jump";
 
+
+
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					miMultiline =  new MenuItem ("_Multiline", "", () => Multiline()){CheckType = MenuItemCheckStyle.Checked},
@@ -121,7 +123,7 @@ namespace UICatalog.Scenarios {
 
 		private void SetAllSuggestions (TextView view)
 		{
-			view.Autocomplete.AllSuggestions = Regex.Matches (view.Text.ToString (), "\\w+")
+			((SingleWordSuggestionGenerator)view.Autocomplete).AllSuggestions = Regex.Matches (view.Text.ToString (), "\\w+")
 				.Select (s => s.Value)
 				.Distinct ().ToList ();
 		}

+ 7 - 4
UnitTests/Views/AutocompleteTests.cs

@@ -21,17 +21,20 @@ namespace Terminal.Gui.ViewTests {
 		public void Test_GenerateSuggestions_Simple ()
 		{
 			var ac = new TextViewAutocomplete ();
-			ac.AllSuggestions = new List<string> { "fish", "const", "Cobble" };
+			((SingleWordSuggestionGenerator)ac.SuggestionGenerator).AllSuggestions = new List<string> {
+				"fish",
+				"const",
+				"Cobble" };
 
 			var tv = new TextView ();
 			tv.InsertText ("co");
 
 			ac.HostControl = tv;
-			ac.GenerateSuggestions ();
+			ac.GenerateSuggestions (tv.Text.ToRuneList(),2);
 
 			Assert.Equal (2, ac.Suggestions.Count);
-			Assert.Equal ("const", ac.Suggestions [0]);
-			Assert.Equal ("Cobble", ac.Suggestions [1]);
+			Assert.Equal ("const", ac.Suggestions [0].Title);
+			Assert.Equal ("Cobble", ac.Suggestions [1].Title);
 		}
 
 		[Fact]