diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj index c6e8f8ac82..87cde8b6bd 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj @@ -73,6 +73,7 @@ + Always diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs index df0ecb0de1..aae3d942c7 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs @@ -19,7 +19,7 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets /// /// A code snippet. /// - public class CodeSnippet : INotifyPropertyChanged, IEquatable + public class CodeSnippet : AbstractFreezable, INotifyPropertyChanged { string name = string.Empty, description = string.Empty, text = string.Empty, keyword = string.Empty; @@ -38,6 +38,7 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets public string Name { get { return name; } set { + CheckBeforeMutation(); if (name != value) { name = value ?? string.Empty; OnPropertyChanged("Name"); @@ -48,6 +49,7 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets public string Text { get { return text; } set { + CheckBeforeMutation(); if (text != value) { text = value ?? string.Empty; OnPropertyChanged("Text"); @@ -58,6 +60,7 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets public string Description { get { return description; } set { + CheckBeforeMutation(); if (description != value) { description = value ?? string.Empty; OnPropertyChanged("Description"); @@ -76,6 +79,7 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets public string Keyword { get { return keyword; } set { + CheckBeforeMutation(); if (keyword != value) { keyword = value ?? string.Empty; OnPropertyChanged("Keyword"); @@ -226,41 +230,12 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets } } - public override int GetHashCode() - { - int hashCode = 0; - unchecked { - if (name != null) - hashCode += 1000000007 * name.GetHashCode(); - if (description != null) - hashCode += 1000000009 * description.GetHashCode(); - if (text != null) - hashCode += 1000000021 * text.GetHashCode(); - if (keyword != null) - hashCode += 1000000033 * keyword.GetHashCode(); - } - return hashCode; - } - - public override bool Equals(object obj) - { - CodeSnippet other = obj as CodeSnippet; - return Equals(other); - } - - public bool Equals(CodeSnippet other) - { - if (other == null) - return false; - return this.name == other.name && this.description == other.description && this.text == other.text && this.keyword == other.keyword; - } - /// /// Reports the snippet usage to UDC /// internal void TrackUsage(string activationMethod) { - bool isUserModified = !SnippetManager.defaultSnippets.Any(g => g.Snippets.Contains(this)); + bool isUserModified = !SnippetManager.Instance.defaultSnippets.Any(g => g.Snippets.Contains(this, CodeSnippetComparer.Instance)); Core.AnalyticsMonitorService.TrackFeature(typeof(CodeSnippet), isUserModified ? "usersnippet" : Name, activationMethod); } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetComparer.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetComparer.cs new file mode 100644 index 0000000000..0eed621795 --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetComparer.cs @@ -0,0 +1,30 @@ +// 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; + +namespace ICSharpCode.AvalonEdit.AddIn.Snippets +{ + /// + /// Compares code snippets. + /// + public class CodeSnippetComparer : IEqualityComparer + { + public static readonly CodeSnippetComparer Instance = new CodeSnippetComparer(); + + public bool Equals(CodeSnippet x, CodeSnippet y) + { + if (x == y) + return true; + if (x == null || y == null) + return false; + return x.Name == y.Name && x.Description == y.Description && x.Text == y.Text && x.Keyword == y.Keyword; + } + + public int GetHashCode(CodeSnippet obj) + { + return obj != null ? obj.Name.GetHashCode() : 0; + } + } +} diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs index 816f5e622c..ac8d2a8319 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs @@ -4,14 +4,27 @@ using System; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Dom; namespace ICSharpCode.AvalonEdit.AddIn.Snippets { /// /// A group of snippets (for a specific file extension). /// - public class CodeSnippetGroup : INotifyPropertyChanged + public class CodeSnippetGroup : AbstractFreezable, INotifyPropertyChanged { + public CodeSnippetGroup() + { + } + + public CodeSnippetGroup(CodeSnippetGroup g) + { + this.Extensions = g.Extensions; + this.Snippets.AddRange(g.Snippets.Select(s => new CodeSnippet(s))); + } + string extensions = ""; ObservableCollection snippets = new ObservableCollection(); @@ -19,11 +32,20 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets get { return snippets; } } + protected override void FreezeInternal() + { + base.FreezeInternal(); + foreach (var snippet in this.snippets) + snippet.Freeze(); + this.snippets.CollectionChanged += delegate { throw new NotSupportedException(); }; + } + public string Extensions { get { return extensions; } set { if (value == null) throw new ArgumentNullException(); + CheckBeforeMutation(); if (extensions != value) { extensions = value; OnPropertyChanged("Extensions"); diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs index e251361f04..6724a5b01f 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs @@ -18,7 +18,7 @@ namespace ICSharpCode.AvalonEdit.AddIn.Snippets public sealed class SnippetManager { readonly object lockObj = new object(); - internal static readonly List defaultSnippets = new List { + internal readonly List defaultSnippets = new List { new CodeSnippetGroup { Extensions = ".cs", Snippets = { @@ -304,6 +304,8 @@ End Property${Caret}", private SnippetManager() { + foreach (var g in defaultSnippets) + g.Freeze(); snippetElementProviders = AddInTree.BuildItems("/SharpDevelop/ViewContent/AvalonEdit/SnippetElementProviders", null, false); } @@ -314,6 +316,10 @@ End Property${Caret}", { var savedSnippets = PropertyService.Get("CodeSnippets", new List()); + // HACK: clone all groups to ensure we use instances independent from the PropertyService + // this can be removed in SD5 where PropertyService.Get deserializes a new instance on every call. + savedSnippets = savedSnippets.Select(g => new CodeSnippetGroup(g)).ToList(); + foreach (var group in savedSnippets) { var defaultGroup = defaultSnippets.FirstOrDefault(i => i.Extensions == group.Extensions); if (defaultGroup != null) { @@ -329,7 +335,7 @@ End Property${Caret}", } foreach (var group in defaultSnippets.Except(savedSnippets, new ByMemberComparer(g => g.Extensions))) { - savedSnippets.Add(group); + savedSnippets.Add(new CodeSnippetGroup(group)); } return savedSnippets; @@ -371,7 +377,7 @@ End Property${Caret}", IEnumerable saveSnippets = group.Snippets; if (defaultGroup != null) { - saveSnippets = group.Snippets.Except(defaultGroup.Snippets); + saveSnippets = group.Snippets.Except(defaultGroup.Snippets, CodeSnippetComparer.Instance); } // save all groups, even if they're empty @@ -391,8 +397,11 @@ End Property${Caret}", public ReadOnlyCollection ActiveGroups { get { lock (lockObj) { - if (activeGroups == null) + if (activeGroups == null) { activeGroups = LoadGroups().AsReadOnly(); + foreach (var g in activeGroups) + g.Freeze(); + } return activeGroups; } }