#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1377 lines
49 KiB

// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Xml;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Editor.CodeCompletion;
using ICSharpCode.SharpDevelop.Project;
namespace ICSharpCode.XamlBinding
{
public static class CompletionDataHelper
{
static readonly List<ICompletionItem> standardElements = new List<ICompletionItem> {
new SpecialCompletionItem("!--"),
new SpecialCompletionItem("![CDATA["),
new SpecialCompletionItem("?")
};
// [XAML 2009]
static readonly List<string> xamlBuiltInTypes = new List<string> {
"Object", "Boolean", "Char", "String", "Decimal", "Single", "Double",
"Int16", "Int32", "Int64", "TimeSpan", "Uri", "Byte", "Array", "List", "Dictionary",
// This is no built in type, but a markup extension
"Reference"
};
static readonly List<ICompletionItem> standardAttributes = new List<ICompletionItem> {
new SpecialCompletionItem("xmlns:"),
new XamlCompletionItem("xml", "", "space"),
new XamlCompletionItem("xml", "", "lang")
};
public static readonly ReadOnlyCollection<string> XamlNamespaceAttributes = new List<string> {
"Class", "ClassModifier", "FieldModifier", "Name", "Subclass", "TypeArguments", "Uid", "Key"
}.AsReadOnly();
public static readonly ReadOnlyCollection<string> RootOnlyElements = new List<string> {
"Class", "ClassModifier", "Subclass"
}.AsReadOnly();
public static readonly ReadOnlyCollection<string> ChildOnlyElements = new List<string> {
"FieldModifier"
}.AsReadOnly();
static readonly List<ICompletionItem> emptyList = new List<ICompletionItem>();
/// <summary>
/// value: http://schemas.microsoft.com/winfx/2006/xaml
/// </summary>
public const string XamlNamespace = "http://schemas.microsoft.com/winfx/2006/xaml";
/// <summary>
/// values: http://schemas.microsoft.com/winfx/2006/xaml/presentation,
/// http://schemas.microsoft.com/netfx/2007/xaml/presentation
/// </summary>
public static readonly string[] WpfXamlNamespaces = new[] {
"http://schemas.microsoft.com/winfx/2006/xaml/presentation",
"http://schemas.microsoft.com/netfx/2007/xaml/presentation"
};
/// <summary>
/// value: http://schemas.openxmlformats.org/markup-compatibility/2006
/// </summary>
public const string MarkupCompatibilityNamespace = "http://schemas.openxmlformats.org/markup-compatibility/2006";
public const bool EnableXaml2009 = true;
public static XamlContext ResolveContext(string text, string fileName, int offset)
{
return ResolveContext(new StringTextBuffer(text), fileName, offset);
}
public static XamlContext ResolveContext(ITextBuffer fileContent, string fileName, int offset)
{
//using (new DebugTimerObject("ResolveContext")) {
XamlParser parser = string.IsNullOrEmpty(fileName) ? new XamlParser() : ParserService.GetParser(fileName) as XamlParser;
ParseInformation info = string.IsNullOrEmpty(fileName) ? null : ParserService.GetParseInformation(fileName);
using (parser.ParseAndLock(fileContent)) {
AXmlDocument document = parser.LastDocument;
AXmlObject currentData = document.GetChildAtOffset(offset);
string attribute = string.Empty, attributeValue = string.Empty;
bool inAttributeValue = false;
AttributeValue value = null;
bool isRoot = false;
bool wasAXmlElement = false;
int offsetFromValueStart = -1;
List<AXmlElement> ancestors = new List<AXmlElement>();
Dictionary<string, string> xmlns = new Dictionary<string, string>();
List<string> ignored = new List<string>();
string xamlNamespacePrefix = string.Empty;
var item = currentData;
while (item != document) {
if (item is AXmlElement) {
AXmlElement element = item as AXmlElement;
ancestors.Add(element);
foreach (var attr in element.Attributes) {
if (attr.IsNamespaceDeclaration) {
string prefix = (attr.Name == "xmlns") ? "" : attr.LocalName;
if (!xmlns.ContainsKey(prefix))
xmlns.Add(prefix, attr.Value);
}
if (attr.LocalName == "Ignorable" && attr.Namespace == MarkupCompatibilityNamespace)
ignored.AddRange(attr.Value.Split(' ', '\t'));
if (string.IsNullOrEmpty(xamlNamespacePrefix) && attr.Value == XamlNamespace)
xamlNamespacePrefix = attr.LocalName;
}
if (!wasAXmlElement && item.Parent is AXmlDocument)
isRoot = true;
wasAXmlElement = true;
}
item = item.Parent;
}
XamlContextDescription description = XamlContextDescription.None;
AXmlElement active = null;
AXmlElement parent = null;
if (currentData.Parent is AXmlTag) {
AXmlTag tag = currentData.Parent as AXmlTag;
if (tag.IsStartOrEmptyTag)
description = XamlContextDescription.InTag;
else if (tag.IsComment)
description = XamlContextDescription.InComment;
else if (tag.IsCData)
description = XamlContextDescription.InCData;
active = tag.Parent as AXmlElement;
}
if (currentData is AXmlAttribute) {
AXmlAttribute a = currentData as AXmlAttribute;
int valueStartOffset = a.StartOffset + (a.Name ?? "").Length + (a.EqualsSign ?? "").Length + 1;
attribute = a.Name;
attributeValue = a.Value;
value = MarkupExtensionParser.ParseValue(attributeValue);
inAttributeValue = offset >= valueStartOffset && offset < a.EndOffset;
if (inAttributeValue) {
offsetFromValueStart = offset - valueStartOffset;
description = XamlContextDescription.InAttributeValue;
if (value != null && !value.IsString)
description = XamlContextDescription.InMarkupExtension;
if (attributeValue.StartsWith("{}", StringComparison.Ordinal) && attributeValue.Length > 2)
description = XamlContextDescription.InAttributeValue;
} else
description = XamlContextDescription.InTag;
}
if (currentData is AXmlTag) {
AXmlTag tag = currentData as AXmlTag;
if (tag.IsStartOrEmptyTag || tag.IsEndTag)
description = XamlContextDescription.AtTag;
else if (tag.IsComment)
description = XamlContextDescription.InComment;
else if (tag.IsCData)
description = XamlContextDescription.InCData;
active = tag.Parent as AXmlElement;
}
if (active != ancestors.FirstOrDefault())
parent = ancestors.FirstOrDefault();
else
parent = ancestors.Skip(1).FirstOrDefault();
if (active == null)
active = parent;
var xAttribute = currentData as AXmlAttribute;
var context = new XamlContext() {
Description = description,
ActiveElement = (active == null) ? null : active.ToWrapper(),
ParentElement = (parent == null) ? null : parent.ToWrapper(),
Ancestors = ancestors.Select(ancestor => ancestor.ToWrapper()).ToList(),
Attribute = (xAttribute != null) ? xAttribute.ToWrapper() : null,
InRoot = isRoot,
AttributeValue = value,
RawAttributeValue = attributeValue,
ValueStartOffset = offsetFromValueStart,
XmlnsDefinitions = xmlns,
ParseInformation = info,
IgnoredXmlns = ignored.AsReadOnly(),
XamlNamespacePrefix = xamlNamespacePrefix
};
return context;
}
//}
}
public static XamlCompletionContext ResolveCompletionContext(ITextEditor editor, char typedValue)
{
var binding = editor.GetService(typeof(XamlLanguageBinding)) as XamlLanguageBinding;
if (binding == null)
throw new InvalidOperationException("Can only use ResolveCompletionContext with a XamlLanguageBinding.");
var context = new XamlCompletionContext(ResolveContext(editor.Document.CreateSnapshot(), editor.FileName, editor.Caret.Offset)) {
PressedKey = typedValue,
Editor = editor
};
return context;
}
public static XamlCompletionContext ResolveCompletionContext(ITextEditor editor, char typedValue, int offset)
{
var binding = editor.GetService(typeof(XamlLanguageBinding)) as XamlLanguageBinding;
if (binding == null)
throw new InvalidOperationException("Can only use ResolveCompletionContext with a XamlLanguageBinding.");
var context = new XamlCompletionContext(ResolveContext(editor.Document.CreateSnapshot(), editor.FileName, offset)) {
PressedKey = typedValue,
Editor = editor
};
return context;
}
static List<ICompletionItem> CreateAttributeList(XamlCompletionContext context, bool includeEvents)
{
ElementWrapper lastElement = context.ActiveElement;
if (context.ParseInformation == null)
return emptyList;
XamlCompilationUnit cu = context.ParseInformation.CompilationUnit as XamlCompilationUnit;
if (cu == null)
return emptyList;
IReturnType rt = cu.CreateType(lastElement.Namespace, lastElement.LocalName.Trim('.'));
var list = new List<ICompletionItem>();
string xamlPrefix = context.XamlNamespacePrefix;
string xKey = string.IsNullOrEmpty(xamlPrefix) ? "" : xamlPrefix + ":";
if (xamlBuiltInTypes.Concat(XamlNamespaceAttributes).Select(s => xKey + s).Contains(lastElement.Name))
return emptyList;
if (lastElement.LocalName.EndsWith(".", StringComparison.OrdinalIgnoreCase) || context.PressedKey == '.') {
if (rt == null)
return list;
string key = string.IsNullOrEmpty(lastElement.Prefix) ? "" : lastElement.Prefix + ":";
if (context.ParentElement != null && context.ParentElement.LocalName.StartsWith(lastElement.LocalName.TrimEnd('.'), StringComparison.OrdinalIgnoreCase)) {
AddAttributes(rt, list, includeEvents);
AddAttachedProperties(rt.GetUnderlyingClass(), list, key, lastElement.Name.Trim('.'));
} else
AddAttachedProperties(rt.GetUnderlyingClass(), list, key, lastElement.Name.Trim('.'));
} else {
if (rt == null) {
list.Add(new XamlCompletionItem(xamlPrefix, XamlNamespace, "Uid"));
} else {
AddAttributes(rt, list, includeEvents);
list.AddRange(GetListOfAttached(context, string.Empty, string.Empty, includeEvents, true));
foreach (string item in XamlNamespaceAttributes.Where(item => AllowedInElement(context.InRoot, item)))
list.Add(new XamlCompletionItem(xamlPrefix, XamlNamespace, item));
}
}
return list;
}
static void AddAttributes(IReturnType rt, IList<ICompletionItem> list, bool includeEvents)
{
if (rt == null)
return;
foreach (IProperty p in rt.GetProperties()) {
if (p.IsPublic && (p.IsPubliclySetable() || p.ReturnType.IsCollectionReturnType()))
list.Add(new XamlCodeCompletionItem(p));
}
if (includeEvents) {
foreach (IEvent e in rt.GetEvents()) {
if (e.IsPublic) {
list.Add(new XamlCodeCompletionItem(e));
}
}
}
}
static bool AllowedInElement(bool inRoot, string item)
{
return inRoot ? !ChildOnlyElements.Contains(item) : !RootOnlyElements.Contains(item);
}
public static IEnumerable<ICompletionItem> CreateListForXmlnsCompletion(IProjectContent projectContent)
{
List<XmlnsCompletionItem> list = new List<XmlnsCompletionItem>();
foreach (IProjectContent content in projectContent.ThreadSafeGetReferencedContents()) {
foreach (IAttribute att in content.GetAssemblyAttributes()) {
if (att.PositionalArguments.Count == 2
&& att.AttributeType.FullyQualifiedName == "System.Windows.Markup.XmlnsDefinitionAttribute") {
list.Add(new XmlnsCompletionItem(att.PositionalArguments[0] as string, true));
}
}
foreach (string @namespace in content.NamespaceNames) {
if (!string.IsNullOrEmpty(@namespace))
list.Add(new XmlnsCompletionItem(@namespace, content.AssemblyName));
}
}
foreach (string @namespace in projectContent.NamespaceNames) {
if (!string.IsNullOrEmpty(@namespace))
list.Add(new XmlnsCompletionItem(@namespace, false));
}
list.Add(new XmlnsCompletionItem(MarkupCompatibilityNamespace, true));
return list
.Distinct(new XmlnsEqualityComparer())
.OrderBy(item => item, new XmlnsComparer());
}
static string GetContentPropertyName(IReturnType type)
{
if (type == null)
return string.Empty;
IClass c = type.GetUnderlyingClass();
if (c == null)
return string.Empty;
IAttribute contentProperty = c.Attributes
.FirstOrDefault(attribute => attribute.AttributeType.FullyQualifiedName == "System.Windows.Markup.ContentPropertyAttribute");
if (contentProperty != null) {
return contentProperty.PositionalArguments.FirstOrDefault() as string
?? (contentProperty.NamedArguments.ContainsKey("Name") ? contentProperty.NamedArguments["Name"] as string : string.Empty);
}
return string.Empty;
}
public static IList<ICompletionItem> CreateElementList(XamlCompletionContext context, bool classesOnly, bool includeAbstract)
{
var items = GetClassesFromContext(context);
var result = new List<ICompletionItem>();
var last = context.ParentElement;
if (context.ParseInformation == null)
return emptyList;
XamlCompilationUnit cu = context.ParseInformation.CompilationUnit as XamlCompilationUnit;
IReturnType rt = null;
if (last != null && cu != null) {
if (!last.Name.Contains(".") || last.Name.EndsWith(".", StringComparison.OrdinalIgnoreCase)) {
rt = cu.CreateType(last.Namespace, last.LocalName.Trim('.'));
string contentPropertyName = GetContentPropertyName(rt);
if (!string.IsNullOrEmpty(contentPropertyName)) {
string fullName = last.Name + "." + contentPropertyName;
MemberResolveResult mrr = XamlResolver.Resolve(fullName, context) as MemberResolveResult;
if (mrr != null) {
rt = mrr.ResolvedType;
}
}
} else {
MemberResolveResult mrr = XamlResolver.Resolve(last.Name, context) as MemberResolveResult;
if (mrr != null) {
rt = mrr.ResolvedType;
}
}
}
bool isList = rt != null && rt.IsListReturnType();
bool parentAdded = false;
foreach (var ns in items) {
foreach (var c in ns.Value) {
if (includeAbstract) {
if (c.ClassType == ClassType.Class) {
if (c.IsStatic || c.DerivesFrom("System.Attribute"))
continue;
} else if (c.ClassType == ClassType.Interface) {
} else {
continue;
}
} else {
if (!(c.ClassType == ClassType.Class && c.IsAbstract == includeAbstract && !c.IsStatic &&
// TODO : use c.DefaultReturnType.GetConstructors(ctor => ctor.IsAccessible(context.ParseInformation.CompilationUnit.Classes.FirstOrDefault(), false)) after DOM rewrite
!c.DerivesFrom("System.Attribute") && (c.AddDefaultConstructorIfRequired || c.Methods.Any(m => m.IsConstructor && m.IsAccessible(context.ParseInformation.CompilationUnit.Classes.FirstOrDefault(), false)))))
continue;
}
if (last != null && isList) {
var possibleTypes = rt.GetMethods()
.Where(a => a.Parameters.Count == 1 && a.Name == "Add")
.Select(method => method.Parameters.First().ReturnType.GetUnderlyingClass()).Where(p => p != null);
if (!possibleTypes.Any(t => c.ClassInheritanceTreeClassesOnly.Any(c2 => c2.FullyQualifiedName == t.FullyQualifiedName)))
continue;
}
XamlCodeCompletionItem item = new XamlCodeCompletionItem(c, ns.Key);
parentAdded = parentAdded || (last != null && item.Text == last.Name);
result.Add(new XamlCodeCompletionItem(c, ns.Key));
}
}
if (!parentAdded && last != null && !last.Name.Contains(".")) {
IClass itemClass = cu.CreateType(last.Namespace, last.LocalName.Trim('.')).GetUnderlyingClass();
if (itemClass != null)
result.Add(new XamlCodeCompletionItem(itemClass, last.Prefix));
}
var xamlItems = XamlNamespaceAttributes.AsEnumerable();
if (EnableXaml2009)
xamlItems = xamlBuiltInTypes.Concat(XamlNamespaceAttributes);
foreach (string item in xamlItems)
result.Add(new XamlCompletionItem(context.XamlNamespacePrefix, XamlNamespace, item));
return result;
}
public static IEnumerable<ICompletionItem> GetAllTypes(XamlCompletionContext context)
{
var items = GetClassesFromContext(context);
foreach (var ns in items) {
foreach (var c in ns.Value) {
if (c.ClassType == ClassType.Class && !c.DerivesFrom("System.Attribute"))
yield return new XamlCodeCompletionItem(c, ns.Key);
}
}
}
public static IEnumerable<ICompletionItem> CreateListOfMarkupExtensions(XamlCompletionContext context)
{
var list = CreateElementList(context, true, false);
var neededItems = list.OfType<XamlCodeCompletionItem>()
.Where(i => (i.Entity as IClass).DerivesFrom("System.Windows.Markup.MarkupExtension"));
foreach (XamlCodeCompletionItem it in neededItems) {
string text = it.Text;
if (it.Text.EndsWith("Extension", StringComparison.Ordinal))
text = text.Remove(it.Text.Length - "Extension".Length);
it.Text = text;
}
return neededItems.Cast<ICompletionItem>().Add(new XamlCompletionItem(context.XamlNamespacePrefix, XamlNamespace, "Reference"));
}
public static XamlCompletionItemList CreateListForContext(XamlCompletionContext context)
{
XamlCompletionItemList list = new XamlCompletionItemList(context);
ParseInformation info = context.ParseInformation;
ITextEditor editor = context.Editor;
switch (context.Description) {
case XamlContextDescription.None:
if (context.Forced) {
list.Items.AddRange(standardElements);
list.Items.AddRange(CreateElementList(context, false, false));
AddClosingTagCompletion(context, list);
}
break;
case XamlContextDescription.AtTag:
if ((editor.Caret.Offset > 0 && editor.Document.GetCharAt(editor.Caret.Offset - 1) == '.') || context.PressedKey == '.') {
list.Items.AddRange(CreateAttributeList(context, false));
} else {
list.Items.AddRange(standardElements);
list.Items.AddRange(CreateElementList(context, false, false));
AddClosingTagCompletion(context, list);
}
break;
case XamlContextDescription.InTag:
DebugTimer.Start();
string word = context.Editor.GetWordBeforeCaretExtended();
if (context.PressedKey == '.' || word.Contains(".")) {
string ns = "";
int pos = word.IndexOf(':');
if (pos > -1)
ns = word.Substring(0, pos);
string element = word.Substring(pos + 1, word.Length - pos - 1);
string className = word;
int propertyStart = element.IndexOf('.');
if (propertyStart != -1) {
element = element.Substring(0, propertyStart).TrimEnd('.');
className = className.Substring(0, propertyStart + pos + 1).TrimEnd('.');
}
TypeResolveResult trr = XamlResolver.Resolve(className, context) as TypeResolveResult;
IClass typeClass = (trr != null && trr.ResolvedType != null) ? trr.ResolvedType.GetUnderlyingClass() : null;
if (typeClass != null && typeClass.HasAttached(true, true))
list.Items.AddRange(GetListOfAttached(context, element, ns, true, true));
} else {
QualifiedNameWithLocation last = context.ActiveElement.ToQualifiedName();
TypeResolveResult trr = XamlResolver.Resolve(last.Name, context) as TypeResolveResult;
IClass typeClass = (trr != null && trr.ResolvedType != null) ? trr.ResolvedType.GetUnderlyingClass() : null;
list.Items.AddRange(CreateAttributeList(context, true));
list.Items.AddRange(standardAttributes);
}
DebugTimer.Stop("CreateListForContext - InTag");
break;
case XamlContextDescription.InAttributeValue:
new XamlCodeCompletionBinding().CtrlSpace(editor);
break;
}
list.SortItems();
return list;
}
static void AddClosingTagCompletion(XamlCompletionContext context, XamlCompletionItemList list)
{
if (context.ParentElement != null && !context.InRoot) {
ResolveResult rr = XamlResolver.Resolve(context.ParentElement.Name, context);
TypeResolveResult trr = rr as TypeResolveResult;
MemberResolveResult mrr = rr as MemberResolveResult;
if (trr != null) {
if (trr.ResolvedClass == null)
return;
list.Items.Add(new XamlCodeCompletionItem("/" + context.ParentElement.Name, trr.ResolvedClass));
} else if (mrr != null) {
if (mrr.ResolvedMember == null)
return;
list.Items.Add(new XamlCodeCompletionItem("/" + context.ParentElement.Name, mrr.ResolvedMember));
}
}
}
public static IEnumerable<IInsightItem> CreateMarkupExtensionInsight(XamlCompletionContext context)
{
var markup = Utils.GetMarkupExtensionAtPosition(context.AttributeValue.ExtensionValue, context.ValueStartOffset);
var type = ResolveType(markup.ExtensionType, context) ?? ResolveType(markup.ExtensionType + "Extension", context);
if (type != null) {
var ctors = type.GetMethods()
.Where(m => m.IsPublic && m.IsConstructor && m.Parameters.Count >= markup.PositionalArguments.Count)
.OrderBy(m => m.Parameters.Count);
foreach (var ctor in ctors)
yield return new MarkupExtensionInsightItem(ctor);
}
}
public static ICompletionItemList CreateMarkupExtensionCompletion(XamlCompletionContext context)
{
var list = new XamlCompletionItemList(context);
string visibleValue = context.RawAttributeValue.Substring(0, Utils.MinMax(context.ValueStartOffset, 0, context.RawAttributeValue.Length));
if (context.PressedKey == '=')
visibleValue += "=";
// context.RawAttributeValue = visibleValue;
// context.AttributeValue = MarkupExtensionParser.ParseValue(visibleValue);
var markup = Utils.GetMarkupExtensionAtPosition(context.AttributeValue.ExtensionValue, context.ValueStartOffset);
var type = ResolveType(markup.ExtensionType, context) ?? ResolveType(markup.ExtensionType + "Extension", context);
if (type == null) {
list.Items.AddRange(CreateListOfMarkupExtensions(context));
list.PreselectionLength = markup.ExtensionType.Length;
} else {
if (markup.NamedArguments.Count == 0) {
if (DoPositionalArgsCompletion(list, context, markup, type))
DoNamedArgsCompletion(list, context, type, markup);
} else
DoNamedArgsCompletion(list, context, type, markup);
}
list.SortItems();
return list;
}
static void DoNamedArgsCompletion(XamlCompletionItemList list, XamlCompletionContext context, IReturnType type, MarkupExtensionInfo markup)
{
if (markup.NamedArguments.Count > 0 && !context.Editor.GetWordBeforeCaret().StartsWith(",", StringComparison.OrdinalIgnoreCase)) {
int lastStart = markup.NamedArguments.Max(i => i.Value.StartOffset);
var item = markup.NamedArguments.First(p => p.Value.StartOffset == lastStart);
if (context.RawAttributeValue.EndsWith("=", StringComparison.OrdinalIgnoreCase) ||
(item.Value.IsString && item.Value.StringValue.EndsWith(context.Editor.GetWordBeforeCaretExtended(), StringComparison.Ordinal))) {
MemberResolveResult mrr = XamlResolver.ResolveMember(item.Key, context) as MemberResolveResult;
if (mrr != null && mrr.ResolvedMember != null && mrr.ResolvedMember.ReturnType != null) {
IReturnType memberType = mrr.ResolvedMember.ReturnType;
list.Items.AddRange(MemberCompletion(context, memberType, string.Empty));
}
return;
}
}
list.Items.AddRange(type.GetProperties().Where(p => p.CanSet && p.IsPublic).Select(p => new XamlCodeCompletionItem(p.Name + "=", p)));
}
/// <remarks>returns true if elements from named args completion should be added afterwards.</remarks>
static bool DoPositionalArgsCompletion(XamlCompletionItemList list, XamlCompletionContext context, MarkupExtensionInfo markup, IReturnType type)
{
switch (type.FullyQualifiedName) {
case "System.Windows.Markup.ArrayExtension":
case "System.Windows.Markup.NullExtension":
// x:Null/x:Array does not need completion, ignore it
break;
case "System.Windows.Markup.StaticExtension":
if (context.AttributeValue.ExtensionValue.PositionalArguments.Count <= 1)
return DoStaticExtensionCompletion(list, context);
break;
case "System.Windows.Markup.TypeExtension":
if (context.AttributeValue.ExtensionValue.PositionalArguments.Count <= 1) {
list.Items.AddRange(GetClassesFromContext(context).FlattenToList());
AttributeValue selItem = Utils.GetMarkupExtensionAtPosition(context.AttributeValue.ExtensionValue, context.ValueStartOffset)
.PositionalArguments.LastOrDefault();
string word = context.Editor.GetWordBeforeCaret().TrimEnd();
if (selItem != null && selItem.IsString && word == selItem.StringValue) {
list.PreselectionLength = selItem.StringValue.Length;
}
}
break;
default:
var ctors = type.GetMethods()
.Where(m => m.IsPublic && m.IsConstructor && m.Parameters.Count >= markup.PositionalArguments.Count + 1);
if (context.Forced)
return true;
if (ctors.Any() || markup.PositionalArguments.Count == 0)
return false;
break;
}
return true;
}
static IEnumerable<ICompletionItem> FlattenToList(this IDictionary<string, IEnumerable<IClass>> data)
{
foreach (var item in data) {
foreach (IClass c in item.Value) {
yield return new XamlCodeCompletionItem(c, item.Key);
}
}
}
public static IEnumerable<IInsightItem> MemberInsight(MemberResolveResult result)
{
switch (result.ResolvedType.FullyQualifiedName) {
case "System.Windows.Thickness":
yield return new MemberInsightItem(result.ResolvedMember, "uniformLength");
yield return new MemberInsightItem(result.ResolvedMember, "left, top");
yield return new MemberInsightItem(result.ResolvedMember, "left, top, right, bottom");
break;
case "System.Windows.Size":
yield return new MemberInsightItem(result.ResolvedMember, "width, height");
break;
case "System.Windows.Point":
yield return new MemberInsightItem(result.ResolvedMember, "x, y");
break;
case "System.Windows.Rect":
yield return new MemberInsightItem(result.ResolvedMember, "x, y, width, height");
break;
}
}
public static string LookForTargetTypeValue(XamlCompletionContext context, out bool isExplicit, params string[] elementName) {
var ancestors = context.Ancestors;
isExplicit = false;
for (int i = 0; i < ancestors.Count; i++) {
if (ancestors[i].LocalName == "Style" && WpfXamlNamespaces.Contains(ancestors[i].Namespace)) {
isExplicit = true;
return ancestors[i].GetAttributeValue("TargetType") ?? string.Empty;
}
if (ancestors[i].Name.EndsWithAny(elementName.Select(s => "." + s + "s"), StringComparison.Ordinal)
&& !ancestors[i].Name.StartsWith("Style.", StringComparison.Ordinal)) {
return ancestors[i].Name.Remove(ancestors[i].Name.IndexOf('.'));
}
}
return null;
}
public static string GetTypeNameFromTypeExtension(MarkupExtensionInfo info, XamlCompletionContext context)
{
IReturnType type = CompletionDataHelper.ResolveType(info.ExtensionType, context)
?? CompletionDataHelper.ResolveType(info.ExtensionType + "Extension", context);
if (type == null || type.FullyQualifiedName != "System.Windows.Markup.TypeExtension")
return string.Empty;
var item = info.PositionalArguments.FirstOrDefault();
if (item != null && item.IsString) {
return item.StringValue;
} else {
if (info.NamedArguments.TryGetValue("typename", out item)) {
if (item.IsString)
return item.StringValue;
}
}
return string.Empty;
}
public static bool EndsWithAny(this string thisValue, IEnumerable<string> items, StringComparison comparison)
{
foreach (string item in items) {
if (thisValue.EndsWith(item, comparison))
return true;
}
return false;
}
public static bool EndsWithAny(this string thisValue, params char[] items)
{
foreach (char item in items) {
if (thisValue.EndsWith(item.ToString()))
return true;
}
return false;
}
static IReturnType GetType(XamlCompletionContext context, out bool isExplicit)
{
AttributeValue value = MarkupExtensionParser.ParseValue(LookForTargetTypeValue(context, out isExplicit, "Trigger", "Setter") ?? string.Empty);
IReturnType typeName = null;
string typeNameString = null;
if (!value.IsString) {
typeNameString = GetTypeNameFromTypeExtension(value.ExtensionValue, context);
typeName = CompletionDataHelper.ResolveType(typeNameString, context);
} else {
typeNameString = value.StringValue;
typeName = CompletionDataHelper.ResolveType(value.StringValue, context);
}
return typeName;
}
public static IEnumerable<ICompletionItem> MemberCompletion(XamlCompletionContext context, IReturnType type, string textPrefix)
{
if (type == null || type.GetUnderlyingClass() == null)
yield break;
var c = type.GetUnderlyingClass();
if (type is ConstructedReturnType && type.TypeArgumentCount > 0 && c.FullyQualifiedName == "System.Nullable") {
ConstructedReturnType rt = type as ConstructedReturnType;
string nullExtensionName = "Null";
if (!string.IsNullOrEmpty(context.XamlNamespacePrefix))
nullExtensionName = context.XamlNamespacePrefix + ":" + nullExtensionName;
yield return new SpecialCompletionItem("{" + nullExtensionName + "}");
c = rt.TypeArguments.First().GetUnderlyingClass();
if (c == null)
yield break;
}
bool isExplicit, showFull = false;
IReturnType typeName;
string valueBeforeCaret = (context.ValueStartOffset > 0) ?
context.RawAttributeValue.Substring(0, context.ValueStartOffset) : "";
switch (c.ClassType) {
case ClassType.Class:
switch (c.FullyQualifiedName) {
case "System.String":
// return nothing
break;
case "System.Type":
foreach (var item in CreateElementList(context, true, true))
yield return item;
break;
case "System.Windows.PropertyPath":
foreach (var item in CreatePropertyPathCompletion(context))
yield return item;
break;
case "System.Windows.DependencyProperty":
typeName = GetType(context, out isExplicit);
bool isReadOnly = context.ActiveElement.Name.EndsWith("Trigger");
if (!isExplicit && valueBeforeCaret.Contains("."))
showFull = true;
if (typeName != null) {
foreach (var item in typeName.GetDependencyProperties(true, !isExplicit, !isReadOnly, showFull))
yield return item;
}
break;
case "System.Windows.RoutedEvent":
typeName = GetType(context, out isExplicit);
if (!isExplicit && valueBeforeCaret.Contains("."))
showFull = true;
if (typeName != null) {
foreach (var item in typeName.GetRoutedEvents(true, !isExplicit, showFull))
yield return item;
}
break;
case "System.Windows.Media.FontFamily":
foreach (var font in Fonts.SystemFontFamilies)
yield return new SpecialValueCompletionItem(font.FamilyNames.First().Value);
break;
default:
if (context.Description == XamlContextDescription.InMarkupExtension) {
foreach (IField f in c.Fields)
yield return new XamlCodeCompletionItem(textPrefix + f.Name, f);
foreach (IProperty p in c.Properties.Where(pr => pr.IsPublic && pr.IsStatic && pr.CanGet))
yield return new XamlCodeCompletionItem(textPrefix + p.Name, p);
}
break;
}
break;
case ClassType.Enum:
foreach (IField f in c.Fields)
yield return new XamlCodeCompletionItem(textPrefix + f.Name, f);
foreach (IProperty p in c.Properties.Where(pr => pr.IsPublic && pr.IsStatic && pr.CanGet))
yield return new XamlCodeCompletionItem(textPrefix + p.Name, p);
break;
case ClassType.Struct:
switch (c.FullyQualifiedName) {
case "System.Boolean":
yield return new SpecialValueCompletionItem("True");
yield return new SpecialValueCompletionItem("False");
break;
case "System.Windows.GridLength":
yield return new SpecialValueCompletionItem("Auto");
yield return new SpecialValueCompletionItem("*");
break;
}
break;
case ClassType.Delegate:
foreach (var item in CreateEventCompletion(context, c))
yield return item;
break;
}
var classes = c.ProjectContent.Classes.Where(
cla => (cla.FullyQualifiedName == c.FullyQualifiedName + "s" ||
cla.FullyQualifiedName == c.FullyQualifiedName + "es"));
foreach (var coll in classes) {
foreach (var item in coll.Properties)
yield return new SpecialValueCompletionItem(item.Name);
foreach (var item in coll.Fields.Where(f => f.IsPublic && f.IsStatic && f.ReturnType.FullyQualifiedName == c.FullyQualifiedName))
yield return new SpecialValueCompletionItem(item.Name);
}
}
static IList<ICompletionItem> CreatePropertyPathCompletion(XamlCompletionContext context)
{
bool isExplicit;
IReturnType typeName = GetType(context, out isExplicit);
IList<ICompletionItem> list = new List<ICompletionItem>();
string value = context.ValueStartOffset > -1 ? context.RawAttributeValue.Substring(0, Math.Min(context.ValueStartOffset + 1, context.RawAttributeValue.Length)) : "";
if (value.EndsWithAny(']', ')'))
return list;
var segments = PropertyPathParser.Parse(value).ToList();
int completionStart;
bool isAtDot = false;
IReturnType propertyPathType = ResolvePropertyPath(segments, context, typeName, out completionStart);
if (completionStart < segments.Count) {
PropertyPathSegment seg = segments[completionStart];
switch (seg.Kind) {
case SegmentKind.ControlChar:
if (seg.Content == ".") {
AddAttributes(propertyPathType, list, false);
isAtDot = true;
}
break;
case SegmentKind.AttachedProperty:
AddAttributes(seg.Resolve(context, propertyPathType), list, false);
isAtDot = seg.Content.Contains(".");
break;
case SegmentKind.PropertyOrType:
AddAttributes(propertyPathType, list, false);
isAtDot = true;
break;
}
} else if (typeName != null) {
AddAttributes(typeName, list, false);
}
if (!isAtDot) {
foreach (var item in GetAllTypes(context))
list.Add(item);
}
return list;
}
static IReturnType ResolvePropertyPath(IList<PropertyPathSegment> segments, XamlCompletionContext context, IReturnType parentType, out int lastIndex)
{
IReturnType type = parentType;
for (lastIndex = 0; lastIndex < segments.Count - 1; lastIndex++) {
PropertyPathSegment segment = segments[lastIndex];
switch (segment.Kind) {
case SegmentKind.AttachedProperty:
// do we need to take account of previous results?
type = segment.Resolve(context, null);
break;
case SegmentKind.ControlChar:
if (segment.Content == "[" || segment.Content == "(" || segment.Content == "/")
return null;
return type;
case SegmentKind.PropertyOrType:
type = segment.Resolve(context, type);
break;
case SegmentKind.Indexer:
if (type != null) {
IProperty prop = type.GetProperties().FirstOrDefault(p => p.IsIndexer);
if (prop != null) {
type = prop.ReturnType;
}
}
break;
case SegmentKind.SourceTraversal:
// ignore
return null;
}
}
return type;
}
static IReturnType Resolve(this PropertyPathSegment segment, XamlCompletionContext context, IReturnType previousType)
{
if (segment.Kind == SegmentKind.SourceTraversal)
return previousType;
if (segment.Kind == SegmentKind.ControlChar)
return previousType;
string content = segment.Content;
if (segment.Kind == SegmentKind.AttachedProperty && content.StartsWith("(")) {
content = content.TrimStart('(');
if (content.Contains("."))
content = content.Remove(content.IndexOf('.'));
}
XamlContextDescription tmp = context.Description;
context.Description = XamlContextDescription.InTag;
ResolveResult rr = XamlResolver.Resolve(content, context);
IReturnType type = null;
if (rr is TypeResolveResult)
type = (rr as TypeResolveResult).ResolvedType;
if (previousType != null) {
IMember member = previousType.GetMemberByName(content);
if (member != null)
type = member.ReturnType;
} else {
if (rr is MemberResolveResult) {
MemberResolveResult mrr = rr as MemberResolveResult;
if (mrr.ResolvedMember != null)
type = mrr.ResolvedMember.ReturnType;
}
if (rr is TypeResolveResult)
type = (rr as TypeResolveResult).ResolvedType;
}
context.Description = tmp;
return type;
}
static IMember GetMemberByName(this IReturnType type, string name)
{
if (type == null)
throw new ArgumentNullException("type");
foreach (IMember member in type.GetFields()) {
if (member.Name == name)
return member;
}
foreach (IMember member in type.GetProperties()) {
if (member.Name == name)
return member;
}
return null;
}
static IEnumerable<ICompletionItem> CreateEventCompletion(XamlCompletionContext context, IClass c)
{
IMethod invoker = c.Methods.FirstOrDefault(method => method.Name == "Invoke");
if (invoker != null && context.ActiveElement != null) {
var item = context.ActiveElement;
var evt = ResolveAttribute(context.Attribute.ToQualifiedName(), context) as IEvent;
if (evt == null)
return Enumerable.Empty<ICompletionItem>();
int offset = XmlEditor.XmlParser.GetActiveElementStartIndex(context.Editor.Document.Text, context.Editor.Caret.Offset);
if (offset == -1)
return Enumerable.Empty<ICompletionItem>();
var loc = context.Editor.Document.OffsetToPosition(offset);
string name = context.ActiveElement.GetAttributeValue("Name");
if (string.IsNullOrEmpty(name))
name = context.ActiveElement.GetAttributeValue(XamlNamespace, "Name");
IList<ICompletionItem> list = new List<ICompletionItem>();
list.Add(new NewEventCompletionItem(evt, (string.IsNullOrEmpty(name) ? item.Name : name)));
return CompletionDataHelper.AddMatchingEventHandlers(context, invoker).Concat(list);
}
return Enumerable.Empty<ICompletionItem>();
}
static IMember ResolveAttribute(QualifiedNameWithLocation attribute, XamlCompletionContext context)
{
if (attribute == null)
return null;
return ResolveAttribute(attribute.FullXmlName, context);
}
static IMember ResolveAttribute(string attribute, XamlCompletionContext context)
{
MemberResolveResult mrr = XamlResolver.Resolve(attribute, context) as MemberResolveResult;
if (mrr == null)
return null;
return mrr.ResolvedMember;
}
static bool DoStaticExtensionCompletion(XamlCompletionItemList list, XamlCompletionContext context)
{
AttributeValue selItem = Utils.GetMarkupExtensionAtPosition(context.AttributeValue.ExtensionValue, context.ValueStartOffset)
.PositionalArguments.LastOrDefault();
if (context.PressedKey == '.') {
if (selItem != null && selItem.IsString) {
var rr = XamlResolver.Resolve(selItem.StringValue, context) as TypeResolveResult;
if (rr != null)
list.Items.AddRange(MemberCompletion(context, rr.ResolvedType, string.Empty));
return false;
}
} else {
if (selItem != null && selItem.IsString) {
int index = selItem.StringValue.IndexOf('.');
string s = (index > -1) ? selItem.StringValue.Substring(0, index) : selItem.StringValue;
var rr = XamlResolver.Resolve(s, context) as TypeResolveResult;
if (rr != null) {
list.Items.AddRange(MemberCompletion(context, rr.ResolvedType, (index == -1) ? "." : string.Empty));
list.PreselectionLength = (index > -1) ? selItem.StringValue.Length - index - 1 : 0;
return false;
} else
DoStaticTypeCompletion(selItem, list, context);
} else {
DoStaticTypeCompletion(selItem, list, context);
}
}
return true;
}
static void DoStaticTypeCompletion(AttributeValue selItem, XamlCompletionItemList list, XamlCompletionContext context)
{
var items = GetClassesFromContext(context);
foreach (var ns in items) {
list.Items.AddRange(ns.Value.Where(c => c.Fields.Any(f => f.IsStatic) || c.Properties.Any(p => p.IsStatic))
.Select(c => new XamlCodeCompletionItem(c, ns.Key)));
}
if (selItem != null && selItem.IsString) {
list.PreselectionLength = selItem.StringValue.Length;
}
}
public static IReturnType ResolveType(string typeName, XamlContext context)
{
if (context.ParseInformation == null)
return null;
XamlCompilationUnit cu = context.ParseInformation.CompilationUnit as XamlCompilationUnit;
if (cu == null)
return null;
string prefix = "";
int len = typeName.IndexOf(':');
string name = typeName;
if (len > 0) {
prefix = typeName.Substring(0, len);
name = typeName.Substring(len + 1, name.Length - len - 1);
}
string namespaceName = "";
if (context.XmlnsDefinitions.TryGetValue(prefix, out namespaceName)) {
IReturnType rt = cu.CreateType(namespaceName, name);
if (rt != null && rt.GetUnderlyingClass() != null)
return rt;
}
return null;
}
public static IEnumerable<ICompletionItem> AddMatchingEventHandlers(XamlCompletionContext context, IMethod delegateInvoker)
{
if (context.ParseInformation == null)
yield break;
var unit = context.ParseInformation.CompilationUnit;
var loc = context.Editor.Caret.Position;
IClass c = unit.GetInnermostClass(loc.Line, loc.Column);
if (c == null)
yield break;
CompoundClass compound = c.GetCompoundClass() as CompoundClass;
if (compound != null) {
foreach (IClass part in compound.Parts) {
foreach (IMethod m in part.Methods) {
if (m.Parameters.Count != delegateInvoker.Parameters.Count)
continue;
if ((m.ReturnType != null && delegateInvoker.ReturnType != null) && m.ReturnType.DotNetName != delegateInvoker.ReturnType.DotNetName)
continue;
bool equal = m.Parameters.SequenceEqual(delegateInvoker.Parameters, new ParameterComparer());
if (equal) {
yield return new XamlCodeCompletionItem(m);
}
}
}
}
}
public static bool Compare(this IParameter p1, IParameter p2)
{
return (p1.ReturnType.DotNetName == p2.ReturnType.DotNetName) &&
(p1.IsOut == p2.IsOut) && (p1.IsParams == p2.IsParams) && (p1.IsRef == p2.IsRef);
}
static IDictionary<string, IEnumerable<IClass>> GetClassesFromContext(XamlCompletionContext context)
{
var result = new Dictionary<string, IEnumerable<IClass>>();
if (context.ParseInformation == null)
return result;
IProjectContent pc = context.ProjectContent;
foreach (var ns in context.XmlnsDefinitions) {
result.Add(ns.Key, XamlCompilationUnit.GetNamespaceMembers(pc, ns.Value));
}
return result;
}
public static bool IsPubliclySetable(this IProperty thisValue)
{
return thisValue.CanSet &&
(thisValue.SetterModifiers == ModifierEnum.None ||
(thisValue.SetterModifiers & ModifierEnum.Public) == ModifierEnum.Public);
}
public static IEnumerable<ICompletionItem> GetDependencyProperties(this IReturnType type, bool excludeSuffix, bool addType, bool requiresSetable, bool showFull)
{
foreach (var field in type.GetFields()) {
if (field.ReturnType.FullyQualifiedName != "System.Windows.DependencyProperty")
continue;
if (field.Name.Length <= "Property".Length || !field.Name.EndsWith("Property", StringComparison.Ordinal))
continue;
string fieldName = field.Name.Remove(field.Name.Length - "Property".Length);
IProperty property = type.GetProperties().FirstOrDefault(p => p.Name == fieldName);
if (property == null)
continue;
if (requiresSetable && !property.IsPubliclySetable())
continue;
if (!excludeSuffix)
fieldName = field.Name;
if (showFull) {
addType = false;
fieldName = field.DeclaringType.Name + "." + fieldName;
}
yield return new XamlLazyValueCompletionItem(field, fieldName, addType);
}
}
public static IEnumerable<ICompletionItem> GetRoutedEvents(this IReturnType type, bool excludeSuffix, bool addType, bool showFull)
{
foreach (var field in type.GetFields()) {
if (field.ReturnType.FullyQualifiedName != "System.Windows.RoutedEvent")
continue;
if (field.Name.Length <= "Event".Length || !field.Name.EndsWith("Event", StringComparison.Ordinal))
continue;
string fieldName = field.Name.Remove(field.Name.Length - "Event".Length);
if (!type.GetEvents().Any(p => p.Name == fieldName))
continue;
if (!excludeSuffix)
fieldName = field.Name;
if (showFull) {
addType = false;
fieldName = field.DeclaringType.Name + "." + fieldName;
}
yield return new XamlLazyValueCompletionItem(field, fieldName, addType);
}
}
internal static List<ICompletionItem> GetListOfAttached(XamlCompletionContext context, string prefixClassName, string prefixNamespace, bool events, bool properties)
{
List<ICompletionItem> result = new List<ICompletionItem>();
if (context.ParseInformation == null)
return result;
IProjectContent pc = context.ProjectContent;
if (!string.IsNullOrEmpty(prefixClassName)) {
var ns = context.XmlnsDefinitions[prefixNamespace];
IClass c = XamlCompilationUnit.GetNamespaceMembers(pc, ns).FirstOrDefault(item => item.Name == prefixClassName);
if (c != null && c.ClassType == ClassType.Class) {
if (!c.ClassInheritanceTree.Any(b => b.FullyQualifiedName == "System.Attribute")) {
prefixNamespace = string.IsNullOrEmpty(prefixNamespace) ? prefixNamespace : prefixNamespace + ":";
if (properties)
AddAttachedProperties(c, result, prefixNamespace, prefixNamespace + prefixClassName);
if (events)
AddAttachedEvents(c, result, prefixNamespace, prefixNamespace + prefixClassName);
}
}
} else {
foreach (var ns in context.XmlnsDefinitions) {
string key = string.IsNullOrEmpty(ns.Key) ? "" : ns.Key + ":";
foreach (IClass c in XamlCompilationUnit.GetNamespaceMembers(pc, ns.Value)) {
if (c.ClassType != ClassType.Class)
continue;
if (c.HasAttached(properties, events))
result.Add(new XamlCodeCompletionItem(c, ns.Key));
}
}
}
return result;
}
public static void AddAttachedProperties(IClass c, List<ICompletionItem> result, string key, string prefix)
{
if (c == null)
return;
var attachedProperties = c.Fields.Where(f => f.IsAttached(true, false));
int prefixLength = (prefix.Length > 0) ? prefix.Length + 1 : 0;
result.AddRange(
attachedProperties.Select(
item => {
string property = item.Name.Remove(item.Name.Length - "Property".Length);
string name = key + c.Name + "." + item.Name.Remove(item.Name.Length - "Property".Length);
return new XamlCodeCompletionItem(name.Remove(0, prefixLength), new DefaultProperty(c, property) { ReturnType = GetAttachedPropertyType(item, c) });
}
)
);
}
static void AddAttachedEvents(IClass c, List<ICompletionItem> result, string key, string prefix)
{
var attachedEvents = c.Fields.Where(f => f.IsAttached(false, true));
int prefixLength = (prefix.Length > 0) ? prefix.Length + 1 : 0;
result.AddRange(
attachedEvents.Select(
item => {
string @event = item.Name.Remove(item.Name.Length - "Event".Length);
string name = key + c.Name + "." + item.Name.Remove(item.Name.Length - "Event".Length);
return new XamlCodeCompletionItem(name.Remove(0, prefixLength), new DefaultEvent(c, @event) { ReturnType = GetAttachedEventDelegateType(item, c) });
}
)
);
}
static IReturnType GetAttachedEventDelegateType(IField field, IClass c)
{
if (c == null || field == null)
return null;
string eventName = field.Name.Remove(field.Name.Length - "Event".Length);
IMethod method = c.Methods.FirstOrDefault(m => m.IsPublic && m.IsStatic && m.Parameters.Count == 2 && (m.Name == "Add" + eventName + "Handler" || m.Name == "Remove" + eventName + "Handler"));
if (method == null)
return null;
return method.Parameters[1].ReturnType;
}
static IReturnType GetAttachedPropertyType(IField field, IClass c)
{
if (c == null || field == null)
return null;
string propertyName = field.Name.Remove(field.Name.Length - "Property".Length);
IMethod method = c.Methods.FirstOrDefault(m => m.IsPublic && m.IsStatic && m.Name == "Get" + propertyName);
if (method == null)
return null;
return method.ReturnType;
}
static string GetEventNameFromMethod(IMethod m)
{
string mName = m.Name;
if (mName.StartsWith("Add", StringComparison.Ordinal))
mName = mName.Remove(0, 3);
else if (mName.StartsWith("Remove", StringComparison.Ordinal))
mName = mName.Remove(0, 6);
if (mName.EndsWith("Handler", StringComparison.Ordinal))
mName = mName.Remove(mName.Length - "Handler".Length);
return mName;
}
static string GetEventNameFromField(IField f)
{
string fName = f.Name;
if (fName.EndsWith("Event", StringComparison.Ordinal))
fName = fName.Remove(fName.Length - "Event".Length);
return fName;
}
static bool IsMethodFromEvent(IField f, IMethod m)
{
return GetEventNameFromField(f) == GetEventNameFromMethod(m);
}
}
}