using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Gui.CompletionWindow;
using OSGeo.MapGuide.MaestroAPI;
using OSGeo.MapGuide.MaestroAPI.Schema;
using OSGeo.MapGuide.ObjectModels.Capabilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace Maestro.Editors.Common.Expression
{
//NOTE:
//
//It seems the auto-complete capabilities of the ICSharpCode.TextEditor assume object-oriented languages or languages
//that involve a member access operator (., -> or anything similar), so we have to use a custom ICompletionData that
//compensates for the lack of such contexts
//
//NOTE/TODO:
//Auto-completions are currently case-sensitive and will only trigger on the correct case.
internal class FdoExpressionCompletionDataProvider : ICompletionDataProvider
{
private ClassDefinition _klass;
private FdoProviderCapabilities _caps;
public FdoExpressionCompletionDataProvider(ClassDefinition cls, FdoProviderCapabilities caps)
{
_klass = cls;
_caps = caps;
this.DefaultIndex = 0;
this.PreSelection = null;
this.ImageList = new System.Windows.Forms.ImageList();
this.ImageList.Images.Add(Properties.Resources.block);
this.ImageList.Images.Add(Properties.Resources.property);
this.ImageList.Images.Add(Properties.Resources.funnel);
}
public System.Windows.Forms.ImageList ImageList
{
get;
private set;
}
public string PreSelection
{
get;
private set;
}
public int DefaultIndex
{
get;
private set;
}
public bool InsertSpace
{
get;
set;
}
public CompletionDataProviderKeyResult ProcessKey(char key)
{
CompletionDataProviderKeyResult res;
if (key == ' ' && this.InsertSpace)
{
this.InsertSpace = false; // insert space only once
res = CompletionDataProviderKeyResult.BeforeStartKey;
}
else if (char.IsLetterOrDigit(key) || key == '_')
{
this.InsertSpace = false; // don't insert space if user types normally
res = CompletionDataProviderKeyResult.NormalKey;
}
else
{
// do not reset insertSpace when doing an insertion!
res = CompletionDataProviderKeyResult.InsertionKey;
}
return res;
}
public bool InsertAction(ICompletionData data, TextArea textArea, int insertionOffset, char key)
{
if (this.InsertSpace)
{
textArea.Document.Insert(insertionOffset++, " ");
}
textArea.Caret.Position = textArea.Document.OffsetToPosition(insertionOffset);
var res = data.InsertAction(textArea, key);
var fdoComp = (FdoCompletionData)data;
if (fdoComp.ImageIndex == 0 && fdoComp.AppendText.Length > 2) //Function and not an empty function call
{
//Rewind caret so it is at the start of the function call (at first parameter)
var offset = textArea.Caret.Offset;
offset -= (fdoComp.AppendText.Length - 1);
textArea.Caret.Position = textArea.Document.OffsetToPosition(offset);
textArea.SelectionManager.ClearSelection();
textArea.SelectionManager.SetSelection(textArea.Caret.Position, textArea.Document.OffsetToPosition(offset + (fdoComp.HighlightLength - 1)));
}
return res;
}
///
/// Gets the line of text up to the cursor position.
///
string GetLineText(TextArea textArea)
{
LineSegment lineSegment = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset);
return textArea.Document.GetText(lineSegment);
}
public ICompletionData[] GenerateCompletionData(string fileName, TextArea textArea, char charTyped)
{
return GenerateCompletionData((GetLineText(textArea) + charTyped).Trim());
}
class FdoCompletionData : DefaultCompletionData
{
private string _insertText;
private string _appendText;
private int _highlightLength = 0;
public int HighlightLength { get { return _highlightLength; } }
public string InsertText { get { return _insertText; } }
public string AppendText { get { return _appendText; } }
public FdoCompletionData(string prefix, string text, string description, int imageIndex)
: base(text, description, imageIndex)
{
_insertText = text.Substring(prefix.Length);
_appendText = string.Empty;
}
public FdoCompletionData(string prefix, string text, string description, string appendText, int highlightLength, int imageIndex)
: this(prefix, text, description, imageIndex)
{
_appendText = appendText;
_highlightLength = highlightLength;
}
public override bool InsertAction(TextArea textArea, char ch)
{
textArea.InsertString(_insertText + _appendText);
return false;
}
}
private ICompletionData[] GenerateCompletionData(string line)
{
Debug.WriteLine("FDO auto-complete: " + line);
List items = new List();
string name = GetName(line);
if (!String.IsNullOrEmpty(name))
{
try
{
foreach (var func in GetMatchingFdoFunctions(name))
{
var member = CreateFdoFunctionDescriptor(func);
int highlightLength = 0;
if (func.ArgumentDefinitionList.Count > 0)
{
highlightLength = func.ArgumentDefinitionList[0].Name.Length + 2; // [ and ]
}
items.Add(new FdoCompletionData(name, member.Name, member.Description, member.AppendText, highlightLength, 0));
}
foreach (var member in GetMatchingClassProperties(name))
{
items.Add(new FdoCompletionData(name, member.Name, member.Description, 1));
}
foreach (var member in GetMatchingFdoConditions(name))
{
if (string.IsNullOrEmpty(member.AppendText))
items.Add(new FdoCompletionData(name, member.Name, member.Description, 2));
else
items.Add(new FdoCompletionData(name, member.Name, member.Description, member.AppendText, member.AppendText.Length - 1, 2));
}
foreach (var member in GetMatchingFdoOperators(name))
{
if (string.IsNullOrEmpty(member.AppendText))
items.Add(new FdoCompletionData(name, member.Name, member.Description, 2));
else
items.Add(new FdoCompletionData(name, member.Name, member.Description, member.AppendText, 0, 2));
}
items.Sort((a, b) => { return a.Text.CompareTo(b.Text); });
}
catch
{
// Do nothing.
}
}
return items.ToArray();
}
class Descriptor
{
public string Name;
public string Description;
public string AppendText;
}
private IEnumerable GetMatchingFdoFunctions(string name)
{
foreach (var func in _caps.Expression.FunctionDefinitionList.Concat(Utility.GetStylizationFunctions()))
{
if (func.Name.StartsWith(name))
yield return func;
}
}
private IEnumerable GetMatchingFdoConditions(string name)
{
foreach (var cond in _caps.Filter.Condition)
{
if (cond.ToString().ToUpper().StartsWith(name))
{
var desc = CreateFdoConditionDescriptor(cond);
if (desc != null)
yield return desc;
}
}
}
private Descriptor CreateFdoConditionDescriptor(FdoProviderCapabilitiesFilterType cond)
{
if (cond == FdoProviderCapabilitiesFilterType.Null)
{
return new Descriptor()
{
Name = cond.ToString().ToUpper(),
Description = "[property] NULL" //NOXLATE
};
}
else if (cond == FdoProviderCapabilitiesFilterType.In)
{
return new Descriptor()
{
Name = cond.ToString().ToUpper(),
Description = "[property] IN ([value1], [value2], ..., [valueN])", //NOXLATE
AppendText = " ([value1], [value2])" //NOXLATE
};
}
else if (cond == FdoProviderCapabilitiesFilterType.Like)
{
return new Descriptor()
{
Name = cond.ToString().ToUpper(),
Description = "[property] LIKE [string value]", //NOXLATE
AppendText = " [string value]" //NOXLATE
};
}
return null; //Handled by operators
}
private static Descriptor CreateBinaryDistanceOperator(string opName)
{
return new Descriptor()
{
Name = opName.ToUpper(),
Description = "[property] " + opName + " [number]", //NOXLATE
AppendText = " [number]" //NOXLATE
};
}
private static Descriptor CreateBinarySpatialOperator(string opName)
{
return new Descriptor()
{
Name = opName.ToUpper(),
Description = "[geometry] " + opName + " GeomFromText('geometry wkt')", //NOXLATE
AppendText = " GeomFromText('geometry wkt')" //NOXLATE
};
}
private IEnumerable GetMatchingFdoOperators(string name)
{
foreach (var op in _caps.Filter.Distance)
{
var opName = op.ToString().ToUpper();
if (opName.StartsWith(name))
yield return CreateBinaryDistanceOperator(opName);
}
foreach (var op in _caps.Filter.Spatial)
{
var opName = op.ToString().ToUpper();
if (opName.StartsWith(name))
yield return CreateBinarySpatialOperator(opName);
}
}
private IEnumerable GetMatchingClassProperties(string name)
{
foreach (var prop in _klass.Properties)
{
if (prop.Name.StartsWith(name))
yield return CreatePropertyDescriptor(prop);
}
}
private Descriptor CreateFdoFunctionDescriptor(FdoProviderCapabilitiesExpressionFunctionDefinition func)
{
var desc = new Descriptor();
desc.Name = func.Name;
string fmt = "{0}({1})"; //NOXLATE
List args = new List();
foreach (FdoProviderCapabilitiesExpressionFunctionDefinitionArgumentDefinition argDef in func.ArgumentDefinitionList)
{
args.Add(argDef.Name.Trim());
}
string argsStr = StringifyFunctionArgs(args);
string expr = string.Format(fmt, func.Name, argsStr); //NOXLATE
desc.Description = string.Format(Strings.ExprEditorFunctionDesc, expr, func.Description, func.ReturnType, Environment.NewLine);
desc.AppendText = "(" + argsStr + ")";
return desc;
}
internal static string StringifyFunctionArgs(List args)
{
string argsStr = args.Count > 0 ? "[" + string.Join("], [", args.ToArray()) + "]" : string.Empty; //NOXLATE
return argsStr;
}
private static Descriptor CreatePropertyDescriptor(PropertyDefinition prop)
{
var desc = new Descriptor();
desc.Name = prop.Name;
if (prop.Type == PropertyDefinitionType.Geometry)
{
var g = (GeometricPropertyDefinition)prop;
desc.Description = string.Format(Strings.FsPreview_GeometryPropertyNodeTooltip,
g.Name,
g.Description,
g.GeometryTypesToString(),
g.IsReadOnly,
g.HasElevation,
g.HasMeasure,
g.SpatialContextAssociation,
Environment.NewLine);
}
else if (prop.Type == PropertyDefinitionType.Data)
{
var d = (DataPropertyDefinition)prop;
desc.Description = string.Format(Strings.FsPreview_DataPropertyNodeTooltip,
d.Name,
d.Description,
d.DataType.ToString(),
d.IsNullable,
d.IsReadOnly,
d.Length,
d.Precision,
d.Scale,
Environment.NewLine);
}
else if (prop.Type == PropertyDefinitionType.Raster)
{
var r = (RasterPropertyDefinition)prop;
desc.Description = string.Format(Strings.FsPreview_RasterPropertyNodeTooltip,
r.Name,
r.Description,
r.IsNullable,
r.DefaultImageXSize,
r.DefaultImageYSize,
r.SpatialContextAssociation,
Environment.NewLine);
}
else
{
desc.Description = string.Format(Strings.FsPreview_GenericPropertyTooltip,
prop.Name,
prop.Type.ToString(),
Environment.NewLine);
}
return desc;
}
private string GetName(string text)
{
int startIndex = text.LastIndexOfAny(new char[] { ' ', '+', '/', '*', '-', '%', '=', '>', '<', '&', '|', '^', '~', '(', ',', ')' }); //NOXLATE
string res = text.Substring(startIndex + 1);
Debug.WriteLine("Evaluating FDO auto-complete options for: " + res);
return res;
}
}
}