How to build a Editor with smart tips
In this article, I will try to use AvalonEdit control to create a C# Editor.
- Support method nested
- Support method overload
My EditorControl
This control contains avalonEdit:TextEditor
, we can do more things in this editor. Assume that this control named TextEditor
, we must subscribe to TextEditor.TextArea.TextEntered event with OnTextEntered
method.
private void OnTextEntered(object sender, TextCompositionEventArgs e)
{
if (e.Text == ".")
{
// Open code completion after the user has pressed dot:
myCompletionWindow = new CompletionWindow(TextEditor.TextArea);
var datas = myCompletionWindow.CompletionList.CompletionData;
var docText = TextEditor.TextArea.Document.Text;
if (docText.EndsWith($"{AnalyzeAccess.ANALYZE}{e.Text}"))
{
AddCustomizedCompletionData(datas, typeof(IAnalyzeFunctionalityProvider));
myCompletionWindow.Show();
}
else if (docText.EndsWith($"{AnalyzeAccess.CALCULATE}{e.Text}"))
{
AddCustomizedCompletionData(datas, typeof(ICalculateFunctionalityProvider));
myCompletionWindow.Show();
}
else { }
return;
}
if (myCompletionWindow != null)
{
var result = GetParameterIndex();
if (result == -1)
{
if (insightWindow != null)
{
insightWindow.Close();
insightWindow = null;
}
}
else if (result == 0)
{
insightWindow = new OverloadInsightWindow(TextEditor.TextArea);
selectedDatas.Push(myCompletionWindow.CompletionList.SelectedItem);
insightWindow.Provider = methodDics[selectedDatas.Peek().Text];
insightWindow.Show();
}
else // result == 1
{
insightWindow = new OverloadInsightWindow(TextEditor.TextArea);
insightWindow.Provider = methodDics[selectedDatas.Pop().Text];
insightWindow.Show();
}
}
}
private void AddCustomizedCompletionData(IList<ICompletionData> datas, Type type)
{
methodDics.Clear();
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.OrderBy((m) => m.Name).ToArray();
for (int j = 0; j < methods.Count(); j++)
{
var method = methods[j];
if (j > 0 && method.Name == methods[j - 1].Name) // method overload
{
var preMP = methodDics[method.Name];
preMP.Items.Add(new InsightItem(method));
continue;
}
var desc = GetMethodDisplayName(method);
var mp = new DefaultMethodProvider(desc);
mp.Items.Add(new InsightItem(method));
methodDics.Add(method.Name, mp);
var data = new CustomizedCompletionData(method.Name, mp.Items)
{
Desc = desc
};
datas.Add(data);
}
}
CustomizedCompletionData
This class displayed after entered '.'
public class CustomizedCompletionData : ICompletionData
{
public CustomizedCompletionData(string text, IList<InsightItem> insights)
{
Text = text;
overloadedData = insights;
}
readonly IList<InsightItem> overloadedData = new List<InsightItem>();
public IEnumerable<InsightItem> OverloadedData
{
get { return overloadedData; }
}
public ImageSource Image => null;
public string Text { get; private set; }
// Use this property if you want to show a fancy UIElement in the list.
public object Content => Text;
public string Desc { get; set; }
private string description;
public object Description
{
get
{
if (description == null)
{
description = string.Empty;
if (overloadedData.Count > 1)
{
description = " (+" + OverloadedData.Count() + " overloads)";
}
description = $"{Desc}{description}";
}
return description;
}
}
public double Priority => 1;
public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs)
{
textArea.Document.Replace(completionSegment, Text);
}
}
DefaultMethodProvider
This class used for aid OverloadInsightWindow. By this class, we can implement method overload.
public class DefaultMethodProvider : IOverloadProvider
{
public DefaultMethodProvider(string headerText)
{
HeaderText = headerText;
Items = new List<InsightItem>();
}
public IList<InsightItem> Items { get; internal set; }
/// <summary>
/// the summary of Method
/// </summary>
public string Documentation { get; set; } = string.Empty;
/// <summary>
/// Display method's name and parameters information
/// </summary>
public string HeaderText { get; set; }
private int selectedIndex;
/// <summary>
/// Gets the text 'SelectedIndex of Count'
/// </summary>
public int SelectedIndex
{
get { return selectedIndex; }
set
{
selectedIndex = value;
if (selectedIndex >= Count)
selectedIndex = Count - 1;
if (selectedIndex < 0)
selectedIndex = 0;
OnPropertyChanged(nameof(SelectedIndex));
OnPropertyChanged(nameof(CurrentIndexText));
OnPropertyChanged(nameof(CurrentHeader));
OnPropertyChanged(nameof(CurrentContent));
}
}
/// <summary>
/// Gets the number of overloads.
/// </summary>
public int Count => Items.Count;
/// <summary>
/// Override method
/// </summary>
public string CurrentIndexText
{
get { return (selectedIndex + 1).ToString() + " of " + this.Count.ToString(); }
}
/// <summary>
/// Gets the current header.
/// </summary>
public object CurrentHeader
{
get { return Items[selectedIndex].Header; }
}
/// <summary>
/// Method'summary
/// </summary>
public object CurrentContent
{
get { return Items[selectedIndex].Content; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var args = new PropertyChangedEventArgs(propertyName);
if (PropertyChanged != null)
PropertyChanged(this, args);
}
InsightItem
This class map to Method. This class contains more information about Method.
public sealed class InsightItem
{
public readonly MethodInfo MethodInfo;
public InsightItem(MethodInfo mi)
{
this.MethodInfo = mi;
}
private TextBlock header;
public object Header
{
get
{
if (header == null)
header = new TextBlock() { Text = GetMethodDisplayName(MethodInfo) };
return header;
}
}
/// <summary>
/// Method'summary
/// </summary>
public object Content
{
get { return Documentation; }
}
public string Documentation
{
get
{
return string.Empty;
}
}
private string GetMethodDisplayName(MethodInfo method)
{
var sb = new StringBuilder();
sb.Append($"{method.ReturnType.Name} {method.DeclaringType.Name}.{method.Name}(");
for (int i = 0; i < method.GetParameters().Length; i++)
{
var paraInfo = method.GetParameters()[i];
var paraType = paraInfo.ParameterType.Name;
var paraName = paraInfo.Name;
if (i == method.GetParameters().Length - 1)
{
sb.Append($"{paraType} {paraName}");
}
else
{
sb.Append($"{paraType} {paraName}, ");
}
}
sb.Append(")");
return sb.ToString();
}
}