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.
1061 lines
36 KiB
1061 lines
36 KiB
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Collections.ObjectModel; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Threading.Tasks; |
|
using System.Windows; |
|
using System.Windows.Controls; |
|
using System.Windows.Media; |
|
using System.Xml; |
|
using System.Xml.Linq; |
|
using ICSharpCode.Core; |
|
using ICSharpCode.Core.Presentation; |
|
using ICSharpCode.NRefactory; |
|
using ICSharpCode.NRefactory.Editor; |
|
using ICSharpCode.NRefactory.TypeSystem; |
|
using ICSharpCode.SharpDevelop.Dom; |
|
using ICSharpCode.SharpDevelop.Parser; |
|
using ICSharpCode.SharpDevelop.Project; |
|
|
|
namespace ICSharpCode.SharpDevelop |
|
{ |
|
/// <summary> |
|
/// Extension methods used in SharpDevelop. |
|
/// </summary> |
|
public static class SharpDevelopExtensions |
|
{ |
|
#region RaiseEvent |
|
/// <summary> |
|
/// Raises the event. |
|
/// Does nothing if eventHandler is null. |
|
/// Because the event handler is passed as parameter, it is only fetched from the event field one time. |
|
/// This makes |
|
/// <code>MyEvent.RaiseEvent(x,y);</code> |
|
/// thread-safe |
|
/// whereas |
|
/// <code>if (MyEvent != null) MyEvent(x,y);</code> |
|
/// would not be safe. |
|
/// </summary> |
|
/// <remarks>Using this method is only thread-safe under the Microsoft .NET memory model, |
|
/// not under the less strict memory model in the CLI specification.</remarks> |
|
[Obsolete("Use 'event EventHandler MyEvent = delegate{};' instead")] |
|
public static void RaiseEvent(this EventHandler eventHandler, object sender, EventArgs e) |
|
{ |
|
if (eventHandler != null) { |
|
eventHandler(sender, e); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Raises the event. |
|
/// Does nothing if eventHandler is null. |
|
/// Because the event handler is passed as parameter, it is only fetched from the event field one time. |
|
/// This makes |
|
/// <code>MyEvent.RaiseEvent(x,y);</code> |
|
/// thread-safe |
|
/// whereas |
|
/// <code>if (MyEvent != null) MyEvent(x,y);</code> |
|
/// would not be safe. |
|
/// </summary> |
|
[Obsolete("Use 'event EventHandler MyEvent = delegate{};' instead")] |
|
public static void RaiseEvent<T>(this EventHandler<T> eventHandler, object sender, T e) where T : EventArgs |
|
{ |
|
if (eventHandler != null) { |
|
eventHandler(sender, e); |
|
} |
|
} |
|
#endregion |
|
|
|
#region Task Extensions |
|
/// <summary> |
|
/// If the task throws an exception, notifies the message service. |
|
/// Call this method on asynchronous tasks if you do not care about the result, but do not want |
|
/// unhandled exceptions to go unnoticed. |
|
/// </summary> |
|
public static void FireAndForget(this Task task) |
|
{ |
|
task.ContinueWith( |
|
t => { |
|
if (t.Exception != null) { |
|
if (t.Exception.InnerExceptions.Count == 1) |
|
Core.MessageService.ShowException(t.Exception.InnerExceptions[0]); |
|
else |
|
Core.MessageService.ShowException(t.Exception); |
|
} |
|
}, TaskContinuationOptions.OnlyOnFaulted); |
|
} |
|
#endregion |
|
|
|
#region CoerceValue |
|
/// <summary> |
|
/// Forces the value to stay between mininum and maximum. |
|
/// </summary> |
|
/// <returns>minimum, if value is less than minimum. |
|
/// Maximum, if value is greater than maximum. |
|
/// Otherwise, value.</returns> |
|
public static double CoerceValue(this double value, double minimum, double maximum) |
|
{ |
|
return Math.Max(Math.Min(value, maximum), minimum); |
|
} |
|
|
|
/// <summary> |
|
/// Forces the value to stay between mininum and maximum. |
|
/// </summary> |
|
/// <returns>minimum, if value is less than minimum. |
|
/// Maximum, if value is greater than maximum. |
|
/// Otherwise, value.</returns> |
|
public static int CoerceValue(this int value, int minimum, int maximum) |
|
{ |
|
return Math.Max(Math.Min(value, maximum), minimum); |
|
} |
|
#endregion |
|
|
|
#region Collections |
|
/// <summary> |
|
/// Obsolete. Please use a regular foreach loop instead. ForEach() is executed for its side-effects, and side-effects mix poorly with a functional programming style. |
|
/// </summary> |
|
//[Obsolete("Please use a regular foreach loop instead. ForEach() is executed for its side-effects, and side-effects mix poorly with a functional programming style.")] |
|
public static void ForEach<T>(this IEnumerable<T> input, Action<T> action) |
|
{ |
|
if (input == null) |
|
throw new ArgumentNullException("input"); |
|
foreach (T element in input) { |
|
action(element); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Adds all <paramref name="elements"/> to <paramref name="list"/>. |
|
/// </summary> |
|
public static void AddRange<T>(this ICollection<T> list, IEnumerable<T> elements) |
|
{ |
|
foreach (T o in elements) |
|
list.Add(o); |
|
} |
|
|
|
public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> arr) |
|
{ |
|
return new ReadOnlyCollection<T>(arr); |
|
} |
|
|
|
[Obsolete("This method seems to be unused now; all uses I've seen have been replaced with IReadOnlyList<T>")] |
|
public static ReadOnlyCollectionWrapper<T> AsReadOnly<T>(this ICollection<T> arr) |
|
{ |
|
return new ReadOnlyCollectionWrapper<T>(arr); |
|
} |
|
|
|
public static V GetOrDefault<K,V>(this IReadOnlyDictionary<K, V> dict, K key) |
|
{ |
|
V ret; |
|
dict.TryGetValue(key, out ret); |
|
return ret; |
|
} |
|
|
|
/// <summary> |
|
/// Searches a sorted list |
|
/// </summary> |
|
/// <param name="list">The list to search in</param> |
|
/// <param name="key">The key to search for</param> |
|
/// <param name="keySelector">Function that maps list items to their sort key</param> |
|
/// <param name="keyComparer">Comparer used for the sort</param> |
|
/// <returns>Returns the index of the element with the specified key. |
|
/// If no such element is found, this method returns a negative number that is the bitwise complement of the |
|
/// index where the element could be inserted while maintaining the order.</returns> |
|
public static int BinarySearch<T, K>(this IList<T> list, K key, Func<T, K> keySelector, IComparer<K> keyComparer = null) |
|
{ |
|
return BinarySearch(list, 0, list.Count, key, keySelector, keyComparer); |
|
} |
|
|
|
/// <summary> |
|
/// Searches a sorted list |
|
/// </summary> |
|
/// <param name="list">The list to search in</param> |
|
/// <param name="index">Starting index of the range to search</param> |
|
/// <param name="length">Length of the range to search</param> |
|
/// <param name="key">The key to search for</param> |
|
/// <param name="keySelector">Function that maps list items to their sort key</param> |
|
/// <param name="keyComparer">Comparer used for the sort</param> |
|
/// <returns>Returns the index of the element with the specified key. |
|
/// If no such element is found in the specified range, this method returns a negative number that is the bitwise complement of the |
|
/// index where the element could be inserted while maintaining the order.</returns> |
|
public static int BinarySearch<T, K>(this IList<T> list, int index, int length, K key, Func<T, K> keySelector, IComparer<K> keyComparer = null) |
|
{ |
|
if (keyComparer == null) |
|
keyComparer = Comparer<K>.Default; |
|
int low = index; |
|
int high = index + length - 1; |
|
while (low <= high) { |
|
int mid = low + (high - low >> 1); |
|
int r = keyComparer.Compare(keySelector(list[mid]), key); |
|
if (r == 0) { |
|
return mid; |
|
} else if (r < 0) { |
|
low = mid + 1; |
|
} else { |
|
high = mid - 1; |
|
} |
|
} |
|
return ~low; |
|
} |
|
|
|
/// <summary> |
|
/// Inserts an item into a sorted list. |
|
/// </summary> |
|
public static void OrderedInsert<T>(this IList<T> list, T item, IComparer<T> comparer) |
|
{ |
|
int pos = BinarySearch(list, item, x => x, comparer); |
|
if (pos < 0) |
|
pos = ~pos; |
|
list.Insert(pos, item); |
|
} |
|
|
|
/// <summary> |
|
/// Sorts the enumerable using the given comparer. |
|
/// </summary> |
|
public static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> input, IComparer<T> comparer) |
|
{ |
|
return Enumerable.OrderBy(input, e => e, comparer); |
|
} |
|
|
|
/// <summary> |
|
/// Converts a recursive data structure into a flat list. |
|
/// </summary> |
|
/// <param name="input">The root elements of the recursive data structure.</param> |
|
/// <param name="recursion">The function that gets the children of an element.</param> |
|
/// <returns>Iterator that enumerates the tree structure in preorder.</returns> |
|
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> input, Func<T, IEnumerable<T>> recursion) |
|
{ |
|
return ICSharpCode.NRefactory.Utils.TreeTraversal.PreOrder(input, recursion); |
|
} |
|
|
|
|
|
/// <summary> |
|
/// Creates an array containing a part of the array (similar to string.Substring). |
|
/// </summary> |
|
public static T[] Splice<T>(this T[] array, int startIndex) |
|
{ |
|
if (array == null) |
|
throw new ArgumentNullException("array"); |
|
return Splice(array, startIndex, array.Length - startIndex); |
|
} |
|
|
|
/// <summary> |
|
/// Creates an array containing a part of the array (similar to string.Substring). |
|
/// </summary> |
|
public static T[] Splice<T>(this T[] array, int startIndex, int length) |
|
{ |
|
if (array == null) |
|
throw new ArgumentNullException("array"); |
|
if (startIndex < 0 || startIndex > array.Length) |
|
throw new ArgumentOutOfRangeException("startIndex", startIndex, "Value must be between 0 and " + array.Length); |
|
if (length < 0 || length > array.Length - startIndex) |
|
throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (array.Length - startIndex)); |
|
T[] result = new T[length]; |
|
Array.Copy(array, startIndex, result, 0, length); |
|
return result; |
|
} |
|
|
|
public static IEnumerable<T> DistinctBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IEquatable<K> |
|
{ |
|
// Don't just use .Distinct(KeyComparer.Create(keySelector)) - that would evaluate the keySelector multiple times. |
|
var hashSet = new HashSet<K>(); |
|
foreach (var element in source) { |
|
if (hashSet.Add(keySelector(element))) { |
|
yield return element; |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Returns the minimum element. |
|
/// </summary> |
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception> |
|
public static T MinBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IComparable<K> |
|
{ |
|
return source.MinBy(keySelector, Comparer<K>.Default); |
|
} |
|
|
|
/// <summary> |
|
/// Returns the minimum element. |
|
/// </summary> |
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception> |
|
public static T MinBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> keyComparer) |
|
{ |
|
if (source == null) |
|
throw new ArgumentNullException("source"); |
|
if (keySelector == null) |
|
throw new ArgumentNullException("selector"); |
|
if (keyComparer == null) |
|
keyComparer = Comparer<K>.Default; |
|
using (var enumerator = source.GetEnumerator()) { |
|
if (!enumerator.MoveNext()) |
|
throw new InvalidOperationException("Sequence contains no elements"); |
|
T minElement = enumerator.Current; |
|
K minKey = keySelector(minElement); |
|
while (enumerator.MoveNext()) { |
|
T element = enumerator.Current; |
|
K key = keySelector(element); |
|
if (keyComparer.Compare(key, minKey) < 0) { |
|
minElement = element; |
|
minKey = key; |
|
} |
|
} |
|
return minElement; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Returns the maximum element. |
|
/// </summary> |
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception> |
|
public static T MaxBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IComparable<K> |
|
{ |
|
return source.MaxBy(keySelector, Comparer<K>.Default); |
|
} |
|
|
|
/// <summary> |
|
/// Returns the maximum element. |
|
/// </summary> |
|
/// <exception cref="InvalidOperationException">The input sequence is empty</exception> |
|
public static T MaxBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> keyComparer) |
|
{ |
|
if (source == null) |
|
throw new ArgumentNullException("source"); |
|
if (keySelector == null) |
|
throw new ArgumentNullException("selector"); |
|
if (keyComparer == null) |
|
keyComparer = Comparer<K>.Default; |
|
using (var enumerator = source.GetEnumerator()) { |
|
if (!enumerator.MoveNext()) |
|
throw new InvalidOperationException("Sequence contains no elements"); |
|
T maxElement = enumerator.Current; |
|
K maxKey = keySelector(maxElement); |
|
while (enumerator.MoveNext()) { |
|
T element = enumerator.Current; |
|
K key = keySelector(element); |
|
if (keyComparer.Compare(key, maxKey) > 0) { |
|
maxElement = element; |
|
maxKey = key; |
|
} |
|
} |
|
return maxElement; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Returns the index of the first element for which <paramref name="predicate"/> returns true. |
|
/// If none of the items in the list fits the <paramref name="predicate"/>, -1 is returned. |
|
/// </summary> |
|
public static int FindIndex<T>(this IList<T> list, Func<T, bool> predicate) |
|
{ |
|
for (int i = 0; i < list.Count; i++) { |
|
if (predicate(list[i])) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
/// <summary> |
|
/// Returns the index of the first element for which <paramref name="predicate"/> returns true. |
|
/// If none of the items in the list fits the <paramref name="predicate"/>, -1 is returned. |
|
/// </summary> |
|
public static int FindIndex<T>(this IReadOnlyList<T> list, Func<T, bool> predicate) |
|
{ |
|
for (int i = 0; i < list.Count; i++) { |
|
if (predicate(list[i])) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
/// <summary> |
|
/// Adds item to the list if the item is not null. |
|
/// </summary> |
|
public static void AddIfNotNull<T>(this IList<T> list, T itemToAdd) where T : class |
|
{ |
|
if (itemToAdd != null) |
|
list.Add(itemToAdd); |
|
} |
|
|
|
public static void RemoveAll<T>(this IList<T> list, Predicate<T> condition) |
|
{ |
|
if (list == null) |
|
throw new ArgumentNullException("list"); |
|
int i = 0; |
|
while (i < list.Count) { |
|
if (condition(list[i])) |
|
list.RemoveAt(i); |
|
else |
|
i++; |
|
} |
|
} |
|
#endregion |
|
|
|
#region NRefactory Type System Extensions |
|
/// <summary> |
|
/// Gets the project for which the specified compilation was created. |
|
/// Returns null if the compilation was not created using the SharpDevelop project system. |
|
/// </summary> |
|
public static IProject GetProject(this ICompilation compilation) |
|
{ |
|
if (compilation == null) |
|
throw new ArgumentNullException("compilation"); |
|
var snapshot = compilation.SolutionSnapshot as ISolutionSnapshotWithProjectMapping; |
|
if (snapshot != null) |
|
return snapshot.GetProject(compilation.MainAssembly); |
|
else |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the project for which the specified assembly was created. |
|
/// Returns null if the assembly was not created from a project. |
|
/// </summary> |
|
public static IProject GetProject(this IAssembly assembly) |
|
{ |
|
if (assembly == null) |
|
throw new ArgumentNullException("assembly"); |
|
var snapshot = assembly.Compilation.SolutionSnapshot as ISolutionSnapshotWithProjectMapping; |
|
if (snapshot == null) |
|
return null; |
|
return snapshot.GetProject(assembly); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the location of the assembly on disk. |
|
/// </summary> |
|
public static FileName GetReferenceAssemblyLocation(this IAssembly assembly) |
|
{ |
|
if (assembly == null) |
|
throw new ArgumentNullException("assembly"); |
|
return FileName.Create(assembly.UnresolvedAssembly.Location); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the location of the assembly on disk. |
|
/// If the specified assembly is a reference assembly, this method the location of the actual runtime assembly instead. |
|
/// </summary> |
|
/// <remarks> |
|
/// May return null if the assembly has no location. |
|
/// </remarks> |
|
public static FileName GetRuntimeAssemblyLocation(this IAssembly assembly) |
|
{ |
|
if (assembly == null) |
|
throw new ArgumentNullException("assembly"); |
|
IUnresolvedAssembly asm = assembly.UnresolvedAssembly; |
|
if (!(asm is IProjectContent)) { |
|
// assembly might be in the GAC |
|
var location = SD.GlobalAssemblyCache.FindAssemblyInNetGac(new DomAssemblyName(assembly.FullAssemblyName)); |
|
if (location != null) |
|
return location; |
|
} |
|
return FileName.Create(assembly.UnresolvedAssembly.Location); |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether the specified assembly is located in the Global Assembly Cache. |
|
/// </summary> |
|
public static bool IsGacAssembly(this IAssembly assembly) |
|
{ |
|
if (assembly == null) |
|
throw new ArgumentNullException("assembly"); |
|
return !(assembly.UnresolvedAssembly is IProjectContent) |
|
&& SD.GlobalAssemblyCache.FindAssemblyInNetGac(new DomAssemblyName(assembly.FullAssemblyName)) != null; |
|
} |
|
|
|
static readonly string[] dotnetFrameworkPublicKeys = { |
|
"b77a5c561934e089", // mscorlib, etc. |
|
"b03f5f7f11d50a3a", // System.Drawing, etc. |
|
"31bf3856ad364e35" // WPF |
|
}; |
|
|
|
/// <summary> |
|
/// Gets wether the specified assembly is part of the .net Framework. |
|
/// </summary> |
|
public static bool IsPartOfDotnetFramework(this IAssembly assembly) |
|
{ |
|
if (assembly == null) |
|
throw new ArgumentNullException("assembly"); |
|
if (assembly.UnresolvedAssembly is IProjectContent) |
|
return false; |
|
return dotnetFrameworkPublicKeys.Contains(new DomAssemblyName(assembly.FullAssemblyName).PublicKeyToken); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the ambience for the specified compilation. |
|
/// Never returns null. |
|
/// </summary> |
|
public static IAmbience GetAmbience(this ICompilation compilation) |
|
{ |
|
IProject p = compilation.GetProject(); |
|
if (p != null) |
|
return p.GetAmbience(); |
|
else |
|
return AmbienceService.GetCurrentAmbience(); |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the model instance for the given entity. |
|
/// May return null if there is no model for the specified entity. |
|
/// </summary> |
|
public static IEntityModel GetModel(this IEntity entity) |
|
{ |
|
if (entity == null) |
|
throw new ArgumentNullException("entity"); |
|
|
|
if (entity is ITypeDefinition) |
|
return GetModel((ITypeDefinition)entity); |
|
else if (entity is IMember) |
|
return GetModel((IMember)entity); |
|
else |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the model instance for the given assembly. |
|
/// May return null if there is no model for the specified assembly. |
|
/// </summary> |
|
public static IAssemblyModel GetModel(this IAssembly assembly) |
|
{ |
|
if (assembly == null) |
|
throw new ArgumentNullException("assembly"); |
|
|
|
IProject project = assembly.GetProject(); |
|
if (project != null) |
|
return project.AssemblyModel; |
|
|
|
try { |
|
return SD.AssemblyParserService.GetAssemblyModel(assembly.GetReferenceAssemblyLocation()); |
|
} catch (Exception) { |
|
// TODO: use the exact exception types that GetAssemblyModel() throws (+document them) |
|
// silently ignore errors when loading the assembly |
|
return null; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the model instance for the given type definition. |
|
/// May return null if there is no model for the specified type definition. |
|
/// </summary> |
|
public static ITypeDefinitionModel GetModel(this ITypeDefinition typeDefinition) |
|
{ |
|
if (typeDefinition == null) |
|
throw new ArgumentNullException("typeDefinition"); |
|
|
|
IAssemblyModel assembly = typeDefinition.ParentAssembly.GetModel(); |
|
if (assembly != null) |
|
return assembly.TopLevelTypeDefinitions[typeDefinition.FullTypeName]; |
|
else |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the model instance for the given member. |
|
/// May return null if there is no model for the specified member. |
|
/// </summary> |
|
public static IMemberModel GetModel(this IMember member) |
|
{ |
|
if (member == null) |
|
throw new ArgumentNullException("member"); |
|
|
|
if (member.DeclaringTypeDefinition == null) |
|
return null; |
|
|
|
var snapshot = member.Compilation.SolutionSnapshot as ISolutionSnapshotWithProjectMapping; |
|
if (snapshot == null) |
|
return null; |
|
|
|
ITypeDefinitionModel typeModel = GetModel(member.DeclaringTypeDefinition); |
|
if (typeModel == null) |
|
return null; |
|
|
|
foreach (var memberModel in typeModel.Members) { |
|
if (memberModel.Name == member.Name) { |
|
if (memberModel.Resolve() == member.MemberDefinition) { |
|
return memberModel; |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the model instance for the given unresolved entity. |
|
/// </summary> |
|
/// <param name="entity">The unresolved entity</param> |
|
/// <param name="project">The project in which the entity is defined. |
|
/// If this parameter is null, the project will be determined based on the file name.</param> |
|
/// <returns>The entity model if it could be found; or null otherwise.</returns> |
|
public static IEntityModel GetModel(this IUnresolvedEntity entity, IProject project = null) |
|
{ |
|
if (project == null) { |
|
if (entity.Region.FileName == null) |
|
return null; |
|
project = SD.ProjectService.FindProjectContainingFile(FileName.Create(entity.Region.FileName)); |
|
if (project == null) |
|
return null; |
|
} |
|
IUnresolvedTypeDefinition unresolvedTypeDefinition = entity as IUnresolvedTypeDefinition ?? entity.DeclaringTypeDefinition; |
|
if (unresolvedTypeDefinition == null) |
|
return null; |
|
ITypeDefinitionModel typeModel = project.AssemblyModel.TopLevelTypeDefinitions[unresolvedTypeDefinition.FullTypeName]; |
|
if (entity is IUnresolvedTypeDefinition || typeModel == null) |
|
return typeModel; |
|
|
|
ITypeDefinition typeDefinition = typeModel.Resolve(); |
|
IUnresolvedMember unresolvedMember = entity as IUnresolvedMember; |
|
if (typeDefinition == null || unresolvedMember == null) |
|
return null; |
|
|
|
IMember member = unresolvedMember.Resolve(new SimpleTypeResolveContext(typeDefinition)); |
|
if (member == null) |
|
return null; |
|
var snapshot = member.Compilation.SolutionSnapshot as ISolutionSnapshotWithProjectMapping; |
|
|
|
foreach (var memberModel in typeModel.Members) { |
|
if (memberModel.Name == unresolvedMember.Name) { |
|
if (memberModel.Resolve() == member.MemberDefinition) { |
|
return memberModel; |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
#endregion |
|
|
|
#region DPI independence |
|
public static Rect TransformToDevice(this Rect rect, Visual visual) |
|
{ |
|
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; |
|
return Rect.Transform(rect, matrix); |
|
} |
|
|
|
public static Rect TransformFromDevice(this Rect rect, Visual visual) |
|
{ |
|
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; |
|
return Rect.Transform(rect, matrix); |
|
} |
|
|
|
public static Size TransformToDevice(this Size size, Visual visual) |
|
{ |
|
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; |
|
return new Size(size.Width * matrix.M11, size.Height * matrix.M22); |
|
} |
|
|
|
public static Size TransformFromDevice(this Size size, Visual visual) |
|
{ |
|
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; |
|
return new Size(size.Width * matrix.M11, size.Height * matrix.M22); |
|
} |
|
|
|
public static Point TransformToDevice(this Point point, Visual visual) |
|
{ |
|
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; |
|
return matrix.Transform(point); |
|
} |
|
|
|
public static Point TransformFromDevice(this Point point, Visual visual) |
|
{ |
|
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; |
|
return matrix.Transform(point); |
|
} |
|
#endregion |
|
|
|
#region String extensions |
|
/// <summary> |
|
/// Removes <param name="stringToRemove" /> from the start of this string. |
|
/// Throws ArgumentException if this string does not start with <param name="stringToRemove" />. |
|
/// </summary> |
|
public static string RemoveFromStart(this string s, string stringToRemove) |
|
{ |
|
if (s == null) |
|
return null; |
|
if (string.IsNullOrEmpty(stringToRemove)) |
|
return s; |
|
if (!s.StartsWith(stringToRemove)) |
|
throw new ArgumentException(string.Format("{0} does not start with {1}", s, stringToRemove)); |
|
return s.Substring(stringToRemove.Length); |
|
} |
|
|
|
/// <summary> |
|
/// Removes <paramref name="stringToRemove" /> from the end of this string. |
|
/// Throws ArgumentException if this string does not end with <paramref name="stringToRemove" />. |
|
/// </summary> |
|
public static string RemoveFromEnd(this string s, string stringToRemove) |
|
{ |
|
if (s == null) return null; |
|
if (string.IsNullOrEmpty(stringToRemove)) |
|
return s; |
|
if (!s.EndsWith(stringToRemove)) |
|
throw new ArgumentException(string.Format("{0} does not end with {1}", s, stringToRemove)); |
|
return s.Substring(0, s.Length - stringToRemove.Length); |
|
} |
|
|
|
/// <summary> |
|
/// Trims the string from the first occurence of <paramref name="cutoffStart" /> to the end, including <paramref name="cutoffStart" />. |
|
/// If the string does not contain <paramref name="cutoffStart" />, just returns the original string. |
|
/// </summary> |
|
public static string CutoffEnd(this string s, string cutoffStart) |
|
{ |
|
if (s == null) return null; |
|
int pos = s.IndexOf(cutoffStart); |
|
if (pos != -1) { |
|
return s.Substring(0, pos); |
|
} else { |
|
return s; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Takes at most <param name="length" /> first characters from string. |
|
/// String can be null. |
|
/// </summary> |
|
public static string TakeStart(this string s, int length) |
|
{ |
|
if (string.IsNullOrEmpty(s) || length >= s.Length) |
|
return s; |
|
return s.Substring(0, length); |
|
} |
|
|
|
/// <summary> |
|
/// Takes at most <param name="length" /> first characters from string, and appends '...' if string is longer. |
|
/// String can be null. |
|
/// </summary> |
|
public static string TakeStartEllipsis(this string s, int length) |
|
{ |
|
if (string.IsNullOrEmpty(s) || length >= s.Length) |
|
return s; |
|
return s.Substring(0, length) + "..."; |
|
} |
|
|
|
/// <summary> |
|
/// Removes any character given in the array from the given string. |
|
/// </summary> |
|
public static string RemoveAny(this string s, params char[] chars) |
|
{ |
|
if (string.IsNullOrEmpty(s)) |
|
return s; |
|
var b = new StringBuilder(s); |
|
foreach (char ch in chars) { |
|
b.Replace(ch.ToString(), ""); |
|
} |
|
return b.ToString(); |
|
} |
|
|
|
public static string Replace(this string original, string pattern, string replacement, StringComparison comparisonType) |
|
{ |
|
if (original == null) |
|
throw new ArgumentNullException("original"); |
|
if (pattern == null) |
|
throw new ArgumentNullException("pattern"); |
|
if (pattern.Length == 0) |
|
throw new ArgumentException("String cannot be of zero length.", "pattern"); |
|
if (comparisonType != StringComparison.Ordinal && comparisonType != StringComparison.OrdinalIgnoreCase) |
|
throw new NotSupportedException("Currently only ordinal comparisons are implemented."); |
|
|
|
StringBuilder result = new StringBuilder(original.Length); |
|
int currentPos = 0; |
|
int nextMatch = original.IndexOf(pattern, comparisonType); |
|
while (nextMatch >= 0) { |
|
result.Append(original, currentPos, nextMatch - currentPos); |
|
// The following line restricts this method to ordinal comparisons: |
|
// for non-ordinal comparisons, the match length might be different than the pattern length. |
|
currentPos = nextMatch + pattern.Length; |
|
result.Append(replacement); |
|
|
|
nextMatch = original.IndexOf(pattern, currentPos, comparisonType); |
|
} |
|
|
|
result.Append(original, currentPos, original.Length - currentPos); |
|
return result.ToString(); |
|
} |
|
|
|
public static byte[] GetBytesWithPreamble(this Encoding encoding, string text) |
|
{ |
|
byte[] encodedText = encoding.GetBytes(text); |
|
byte[] bom = encoding.GetPreamble(); |
|
if (bom != null && bom.Length > 0) { |
|
byte[] result = new byte[bom.Length + encodedText.Length]; |
|
bom.CopyTo(result, 0); |
|
encodedText.CopyTo(result, bom.Length); |
|
return result; |
|
} else { |
|
return encodedText; |
|
} |
|
} |
|
|
|
public static int IndexOfAny(this string haystack, IEnumerable<string> needles, int startIndex, out int matchLength) |
|
{ |
|
if (haystack == null) |
|
throw new ArgumentNullException("haystack"); |
|
if (needles == null) |
|
throw new ArgumentNullException("needles"); |
|
int index = -1; |
|
matchLength = 0; |
|
foreach (var needle in needles) { |
|
int i = haystack.IndexOf(needle, startIndex, StringComparison.Ordinal); |
|
if (i != -1 && (index == -1 || index > i)) { |
|
index = i; |
|
matchLength = needle.Length; |
|
} |
|
} |
|
return index; |
|
} |
|
|
|
public static bool ContainsAny(this string haystack, IEnumerable<string> needles, int startIndex, out string match) |
|
{ |
|
if (haystack == null) |
|
throw new ArgumentNullException("haystack"); |
|
if (needles == null) |
|
throw new ArgumentNullException("needles"); |
|
int index = -1; |
|
match = null; |
|
foreach (var needle in needles) { |
|
int i = haystack.IndexOf(needle, startIndex, StringComparison.Ordinal); |
|
if (i != -1 && (index == -1 || index > i)) { |
|
index = i; |
|
match = needle; |
|
} |
|
} |
|
return index > -1; |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves a hash code for the specified string that is stable across |
|
/// multiple runs of SharpDevelop and .NET upgrades. |
|
/// |
|
/// Use this method instead of the normal <c>string.GetHashCode</c> if the hash code |
|
/// is persisted to disk. |
|
/// </summary> |
|
public static int GetStableHashCode(this string text) |
|
{ |
|
unchecked { |
|
int h = 0; |
|
foreach (char c in text) { |
|
h = (h << 5) - h + c; |
|
} |
|
return h; |
|
} |
|
} |
|
|
|
public static async Task CopyToAsync(this TextReader reader, TextWriter writer) |
|
{ |
|
char[] buffer = new char[2048]; |
|
int read; |
|
while ((read = await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) { |
|
writer.Write(buffer, 0, read); |
|
} |
|
} |
|
#endregion |
|
|
|
#region Service Provider Extensions |
|
/// <summary> |
|
/// Retrieves the service of type <c>T</c> from the provider. |
|
/// If the service cannot be found, this method returns <c>null</c>. |
|
/// </summary> |
|
public static T GetService<T>(this IServiceProvider provider) where T : class |
|
{ |
|
return (T)provider.GetService(typeof(T)); |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the service of type <c>T</c> from the provider. |
|
/// If the service cannot be found, a <see cref="ServiceNotFoundException"/> will be thrown. |
|
/// </summary> |
|
public static T GetRequiredService<T>(this IServiceProvider provider) where T : class |
|
{ |
|
return (T)GetRequiredService(provider, typeof(T)); |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the service of type <paramref name="serviceType"/> from the provider. |
|
/// If the service cannot be found, a <see cref="ServiceNotFoundException"/> will be thrown. |
|
/// </summary> |
|
public static object GetRequiredService(this IServiceProvider provider, Type serviceType) |
|
{ |
|
object service = provider.GetService(serviceType); |
|
if (service == null) |
|
throw new ServiceNotFoundException(serviceType); |
|
return service; |
|
} |
|
#endregion |
|
|
|
#region Resource Service Extensions |
|
/// <summary> |
|
/// Gets an <see cref="IImage"/> from a resource. |
|
/// </summary> |
|
/// <exception cref="ResourceNotFoundException">The resource with the specified name does not exist</exception> |
|
public static IImage GetImage(this IResourceService resourceService, string resourceName) |
|
{ |
|
if (resourceService == null) |
|
throw new ArgumentNullException("resourceService"); |
|
if (resourceName == null) |
|
throw new ArgumentNullException("resourceName"); |
|
return new ResourceServiceImage(resourceService, resourceName); |
|
} |
|
|
|
/// <summary> |
|
/// Gets an image source from a resource. |
|
/// </summary> |
|
/// <exception cref="ResourceNotFoundException">The resource with the specified name does not exist</exception> |
|
public static ImageSource GetImageSource(this IResourceService resourceService, string resourceName) |
|
{ |
|
if (resourceService == null) |
|
throw new ArgumentNullException("resourceService"); |
|
if (resourceName == null) |
|
throw new ArgumentNullException("resourceName"); |
|
return PresentationResourceService.GetBitmapSource(resourceName); |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new image for the image source. |
|
/// </summary> |
|
public static Image CreateImage(this IImage image) |
|
{ |
|
if (image == null) |
|
throw new ArgumentNullException("image"); |
|
return new Image { Source = image.ImageSource }; |
|
} |
|
#endregion |
|
|
|
#region XML extensions |
|
public static XElement FormatXml(this XElement element, int indentationLevel) |
|
{ |
|
StringWriter sw = new StringWriter(); |
|
using (XmlTextWriter xmlW = new XmlTextWriter(sw)) { |
|
if (SD.EditorControlService.GlobalOptions.ConvertTabsToSpaces) { |
|
xmlW.IndentChar = ' '; |
|
xmlW.Indentation = SD.EditorControlService.GlobalOptions.IndentationSize; |
|
} else { |
|
xmlW.Indentation = 1; |
|
xmlW.IndentChar = '\t'; |
|
} |
|
xmlW.Formatting = Formatting.Indented; |
|
element.WriteTo(xmlW); |
|
} |
|
string xmlText = sw.ToString(); |
|
xmlText = xmlText.Replace(sw.NewLine, sw.NewLine + GetIndentation(indentationLevel)); |
|
return XElement.Parse(xmlText, LoadOptions.PreserveWhitespace); |
|
} |
|
|
|
static string GetIndentation(int level) |
|
{ |
|
StringBuilder indentation = new StringBuilder(); |
|
for (int i = 0; i < level; i++) { |
|
indentation.Append(SD.EditorControlService.GlobalOptions.IndentationString); |
|
} |
|
return indentation.ToString(); |
|
} |
|
|
|
public static XElement AddWithIndentation(this XElement element, XElement newContent) |
|
{ |
|
int indentationLevel = 0; |
|
XElement tmp = element; |
|
while (tmp != null) { |
|
tmp = tmp.Parent; |
|
indentationLevel++; |
|
} |
|
if (!element.Nodes().Any()) { |
|
element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel - 1))); |
|
} |
|
XText whitespace = element.Nodes().Last() as XText; |
|
if (whitespace != null && string.IsNullOrWhiteSpace(whitespace.Value)) { |
|
whitespace.AddBeforeSelf(new XText(Environment.NewLine + GetIndentation(indentationLevel))); |
|
whitespace.AddBeforeSelf(newContent = FormatXml(newContent, indentationLevel)); |
|
} else { |
|
element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel))); |
|
element.Add(newContent = FormatXml(newContent, indentationLevel)); |
|
} |
|
return newContent; |
|
} |
|
|
|
public static XElement AddFirstWithIndentation(this XElement element, XElement newContent) |
|
{ |
|
int indentationLevel = 0; |
|
StringBuilder indentation = new StringBuilder(); |
|
XElement tmp = element; |
|
while (tmp != null) { |
|
tmp = tmp.Parent; |
|
indentationLevel++; |
|
indentation.Append(SD.EditorControlService.GlobalOptions.IndentationString); |
|
} |
|
if (!element.Nodes().Any()) { |
|
element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel - 1))); |
|
} |
|
element.AddFirst(newContent = FormatXml(newContent, indentationLevel)); |
|
element.AddFirst(new XText(Environment.NewLine + indentation.ToString())); |
|
return newContent; |
|
} |
|
#endregion |
|
|
|
#region Compatibility extension methods (to reduce merge conflicts SD4->SD5) |
|
public static string GetText(this IDocument document, TextLocation startPos, TextLocation endPos) |
|
{ |
|
int startOffset = document.GetOffset(startPos); |
|
return document.GetText(startOffset, document.GetOffset(endPos) - startOffset); |
|
} |
|
|
|
/// <summary> |
|
/// Obsolete. Use GetOffset() instead. |
|
/// </summary> |
|
public static int PositionToOffset(this IDocument document, int line, int column) |
|
{ |
|
return document.GetOffset(line, column); |
|
} |
|
|
|
/// <summary> |
|
/// Obsolete. Use GetLineByNumber() instead. |
|
/// </summary> |
|
public static IDocumentLine GetLine(this IDocument document, int lineNumber) |
|
{ |
|
return document.GetLineByNumber(lineNumber); |
|
} |
|
|
|
/// <summary> |
|
/// Obsolete. Use GetLineByOffset() instead. |
|
/// </summary> |
|
public static IDocumentLine GetLineForOffset(this IDocument document, int offset) |
|
{ |
|
return document.GetLineByOffset(offset); |
|
} |
|
#endregion |
|
|
|
#region IProject extensions |
|
/// <summary> |
|
/// Checks if the project's output is a .NET executable that can be run in a 32-bit process. |
|
/// </summary> |
|
public static bool IsPlatformTarget32BitOrAnyCPU(this IProject project) |
|
{ |
|
MSBuildBasedProject msbuildProject = project as MSBuildBasedProject; |
|
if (msbuildProject != null) { |
|
string platformTarget = msbuildProject.GetEvaluatedProperty("PlatformTarget"); |
|
return string.IsNullOrEmpty(platformTarget) || String.Equals(platformTarget, "x86", StringComparison.OrdinalIgnoreCase) |
|
|| String.Equals(platformTarget, "AnyCPU", StringComparison.OrdinalIgnoreCase); |
|
} |
|
return false; |
|
} |
|
#endregion |
|
} |
|
}
|
|
|