#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.
 
 
 
 
 
 

568 lines
19 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Media;
using System.Xml;
using System.Xml.Linq;
using ICSharpCode.Core.Presentation;
using ICSharpCode.Editor;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Parser;
using ICSharpCode.SharpDevelop.Project;
using WinForms = System.Windows.Forms;
namespace ICSharpCode.SharpDevelop
{
/// <summary>
/// Extension methods used in SharpDevelop.
/// </summary>
public static class ExtensionMethods
{
/// <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>
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>
public static void RaiseEvent<T>(this EventHandler<T> eventHandler, object sender, T e) where T : EventArgs
{
if (eventHandler != null) {
eventHandler(sender, e);
}
}
/// <summary>
/// Runs an action for all elements in the input.
/// </summary>
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);
}
public static ReadOnlyCollectionWrapper<T> AsReadOnly<T>(this ICollection<T> arr)
{
return new ReadOnlyCollectionWrapper<T>(arr);
}
public static IEnumerable<WinForms.Control> GetRecursive(this WinForms.Control.ControlCollection collection)
{
return collection.Cast<WinForms.Control>().Flatten(c => c.Controls.Cast<WinForms.Control>());
}
/// <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;
}
/// <summary>
/// Sets the Content property of the specified ControlControl to the specified content.
/// If the content is a Windows-Forms control, it is wrapped in a WindowsFormsHost.
/// If the content control already contains a WindowsFormsHost with that content,
/// the old WindowsFormsHost is kept.
/// When a WindowsFormsHost is replaced with another content, the host is disposed (but the control
/// inside the host isn't)
/// </summary>
public static void SetContent(this ContentControl contentControl, object content)
{
SetContent(contentControl, content, null);
}
public static void SetContent(this ContentPresenter contentControl, object content)
{
SetContent(contentControl, content, null);
}
public static void SetContent(this ContentControl contentControl, object content, object serviceObject)
{
if (contentControl == null)
throw new ArgumentNullException("contentControl");
// serviceObject = object implementing the old clipboard/undo interfaces
// to allow WinForms AddIns to handle WPF commands
var host = contentControl.Content as SDWindowsFormsHost;
if (host != null) {
if (host.Child == content) {
host.ServiceObject = serviceObject;
return;
}
host.Dispose();
}
if (content is WinForms.Control) {
contentControl.Content = new SDWindowsFormsHost {
Child = (WinForms.Control)content,
ServiceObject = serviceObject,
DisposeChild = false
};
} else if (content is string) {
contentControl.Content = new TextBlock {
Text = content.ToString(),
TextWrapping = TextWrapping.Wrap
};
} else {
contentControl.Content = content;
}
}
public static void SetContent(this ContentPresenter contentControl, object content, object serviceObject)
{
if (contentControl == null)
throw new ArgumentNullException("contentControl");
// serviceObject = object implementing the old clipboard/undo interfaces
// to allow WinForms AddIns to handle WPF commands
var host = contentControl.Content as SDWindowsFormsHost;
if (host != null) {
if (host.Child == content) {
host.ServiceObject = serviceObject;
return;
}
host.Dispose();
}
if (content is WinForms.Control) {
contentControl.Content = new SDWindowsFormsHost {
Child = (WinForms.Control)content,
ServiceObject = serviceObject,
DisposeChild = false
};
} else if (content is string) {
contentControl.Content = new TextBlock {
Text = content.ToString(),
TextWrapping = TextWrapping.Wrap
};
} else {
contentControl.Content = content;
}
}
#region System.Drawing <-> WPF conversions
public static System.Drawing.Point ToSystemDrawing(this Point p)
{
return new System.Drawing.Point((int)p.X, (int)p.Y);
}
public static System.Drawing.Size ToSystemDrawing(this Size s)
{
return new System.Drawing.Size((int)s.Width, (int)s.Height);
}
public static System.Drawing.Rectangle ToSystemDrawing(this Rect r)
{
return new System.Drawing.Rectangle(r.TopLeft.ToSystemDrawing(), r.Size.ToSystemDrawing());
}
public static System.Drawing.Color ToSystemDrawing(this System.Windows.Media.Color c)
{
return System.Drawing.Color.FromArgb(c.A, c.R, c.G, c.B);
}
public static Point ToWpf(this System.Drawing.Point p)
{
return new Point(p.X, p.Y);
}
public static Size ToWpf(this System.Drawing.Size s)
{
return new Size(s.Width, s.Height);
}
public static Rect ToWpf(this System.Drawing.Rectangle rect)
{
return new Rect(rect.Location.ToWpf(), rect.Size.ToWpf());
}
public static System.Windows.Media.Color ToWpf(this System.Drawing.Color c)
{
return System.Windows.Media.Color.FromArgb(c.A, c.R, c.G, c.B);
}
#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 new Point(point.X * matrix.M11, point.Y * matrix.M22);
}
public static Point TransformFromDevice(this Point point, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
return new Point(point.X * matrix.M11, point.Y * matrix.M22);
}
#endregion
/// <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) + "...";
}
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;
}
}
/// <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 };
}
/// <summary>
/// Translates a WinForms menu to WPF.
/// </summary>
public static ICollection TranslateToWpf(this ToolStripItem[] items)
{
return items.OfType<ToolStripMenuItem>().Select(item => TranslateMenuItemToWpf(item)).ToList();
}
static System.Windows.Controls.MenuItem TranslateMenuItemToWpf(ToolStripMenuItem item)
{
var r = new System.Windows.Controls.MenuItem();
r.Header = MenuService.ConvertLabel(item.Text);
r.InputGestureText = MenuService.ConvertKeys(item.ShortcutKeys);
//r.InputGestureText = new KeyGesture(Key.F6).GetDisplayStringForCulture(Thread.CurrentThread.CurrentUICulture);
if (item.ImageIndex >= 0)
r.Icon = ClassBrowserIconService.GetImageByIndex(item.ImageIndex).CreateImage();
if (item.DropDownItems.Count > 0) {
foreach (ToolStripMenuItem subItem in item.DropDownItems) {
r.Items.Add(TranslateMenuItemToWpf(subItem));
}
} else {
r.Click += delegate { item.PerformClick(); };
}
r.IsChecked = item.Checked;
return r;
}
/// <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>
/// Adds item to the list if the item is not null.
/// </summary>
public static void AddIfNotNull<T>(this IList<T> list, T itemToAdd)
{
if (itemToAdd != null)
list.Add(itemToAdd);
}
public static XElement FormatXml(this XElement element, int indentationLevel)
{
StringWriter sw = new StringWriter();
using (XmlTextWriter xmlW = new XmlTextWriter(sw)) {
if (EditorControlService.GlobalOptions.ConvertTabsToSpaces) {
xmlW.IndentChar = ' ';
xmlW.Indentation = 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(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(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;
}
#region Dom, AST, Editor, Document
public static string GetText(this IDocument document, TextLocation startPos, TextLocation endPos)
{
int startOffset = document.GetOffset(startPos);
return document.GetText(startOffset, document.GetOffset(endPos) - startOffset);
}
public static void ClearSelection(this ITextEditor editor)
{
editor.Select(editor.Document.GetOffset(editor.Caret.Location), 0);
}
/// <summary>
/// Gets the ambience for the specified project content.
/// Never returns null.
/// </summary>
public static IAmbience GetAmbience(this IProjectContent pc)
{
IProject p = ParserService.GetProject(pc);
if (p != null)
return p.GetAmbience();
else
return AmbienceService.GetCurrentAmbience();
}
#endregion
}
}