From 4a85fa660cefb52beab4a664523a931647e3fc91 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 14 Oct 2009 22:30:05 +0000 Subject: [PATCH] Added snippet support to SharpDevelop (replaces 'code templates'). git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5069 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- AddIns/ICSharpCode.SharpDevelop.addin | 26 -- .../AvalonEdit.Sample/AvalonEdit.Sample.sln | 20 ++ .../Project/Src/CSharpCompletionBinding.cs | 8 +- .../AvalonEdit.AddIn/AvalonEdit.AddIn.addin | 6 + .../AvalonEdit.AddIn/AvalonEdit.AddIn.csproj | 11 + .../AvalonEdit.AddIn/Src/CodeEditor.cs | 19 +- .../AvalonEdit.AddIn/Src/CodeEditorAdapter.cs | 14 +- .../AvalonEdit.AddIn/Src/CodeEditorView.cs | 61 +++- .../Src/SharpDevelopCompletionWindow.cs | 17 +- .../Src/Snippets/CodeSnippet.cs | 192 +++++++++++++ .../Src/Snippets/CodeSnippetGroup.cs | 47 ++++ .../Src/Snippets/SnippetCompletionItem.cs | 94 +++++++ .../Src/Snippets/SnippetManager.cs | 153 ++++++++++ .../Src/Snippets/SnippetOptionPanel.cs | 110 ++++++++ .../Src/Snippets/SnippetOptionPanel.xaml | 122 ++++++++ .../CodeCompletion/CompletionWindow.cs | 36 --- .../CodeCompletion/CompletionWindowBase.cs | 51 +++- .../ICSharpCode.AvalonEdit/Editing/Caret.cs | 32 ++- .../Editing/TextArea.cs | 47 +++- .../Editing/TextAreaInputHandler.cs | 48 ++++ .../Rendering/TextView.cs | 14 +- .../Snippets/InsertionContext.cs | 3 +- .../Snippets/Snippet.cs | 2 + .../Snippets/SnippetBoundElement.cs | 13 + .../Snippets/SnippetContainerElement.cs | 15 + .../Snippets/SnippetElement.cs | 10 + .../Snippets/SnippetInputHandler.cs | 34 ++- .../Snippets/SnippetReplaceableTextElement.cs | 9 +- .../Snippets/SnippetTextElement.cs | 7 + .../Project/ICSharpCode.SharpDevelop.csproj | 10 +- .../Project/Resources/CodeTemplatePanel.xfrm | 108 -------- .../Src/Commands/CustomStringTagProvider.cs | 2 +- .../AvalonEdit/AvalonEditTextEditorAdapter.cs | 9 +- .../CtrlSpaceCompletionItemProvider.cs | 45 +-- .../CodeCompletion/ICompletionItemList.cs | 8 +- .../TemplateCompletionItemProvider.cs | 69 ----- .../Base/Project/Src/Editor/ITextEditor.cs | 5 + .../OptionPanels/EditTemplateDialog.cs | 22 +- .../Src/Internal/Templates/CodeTemplate.cs | 91 ------ .../Internal/Templates/CodeTemplateGroup.cs | 87 ------ .../Internal/Templates/CodeTemplateLoader.cs | 119 -------- .../Base/Project/Src/TextEditor/Actions.cs | 11 - .../Gui/Editor/SharpDevelopTextAreaControl.cs | 51 +--- .../Gui/OptionPanels/CodeTemplatePanel.cs | 262 ------------------ .../Src/TextEditor/Gui/TextEditorAdapter.cs | 5 + .../Src/Services/StringParser/StringParser.cs | 8 +- .../Project/Src/Refactoring/CodeGenerator.cs | 15 + .../Src/Refactoring/VBNetCodeGenerator.cs | 5 + 48 files changed, 1155 insertions(+), 998 deletions(-) create mode 100644 samples/AvalonEdit.Sample/AvalonEdit.Sample.sln create mode 100644 src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs create mode 100644 src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs create mode 100644 src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetCompletionItem.cs create mode 100644 src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs create mode 100644 src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.cs create mode 100644 src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.xaml delete mode 100644 src/Main/Base/Project/Resources/CodeTemplatePanel.xfrm delete mode 100644 src/Main/Base/Project/Src/Editor/CodeCompletion/TemplateCompletionItemProvider.cs delete mode 100644 src/Main/Base/Project/Src/Internal/Templates/CodeTemplate.cs delete mode 100644 src/Main/Base/Project/Src/Internal/Templates/CodeTemplateGroup.cs delete mode 100644 src/Main/Base/Project/Src/Internal/Templates/CodeTemplateLoader.cs delete mode 100644 src/Main/Base/Project/Src/TextEditor/Gui/OptionPanels/CodeTemplatePanel.cs diff --git a/AddIns/ICSharpCode.SharpDevelop.addin b/AddIns/ICSharpCode.SharpDevelop.addin index 266dea4525..f4109fc880 100644 --- a/AddIns/ICSharpCode.SharpDevelop.addin +++ b/AddIns/ICSharpCode.SharpDevelop.addin @@ -167,16 +167,6 @@ class = "ICSharpCode.SharpDevelop.Project.LoadSolution" extensions = "*.sln"/> - - - - @@ -1718,12 +1708,6 @@ - - - - - - - - - - - - 0) { // allow code completion when overwriting an identifier - cursor = editor.SelectionStart; - int endOffset = cursor + editor.SelectionLength; + int endOffset = editor.SelectionStart + editor.SelectionLength; // but block code completion when overwriting only part of an identifier if (endOffset < editor.Document.TextLength && char.IsLetterOrDigit(editor.Document.GetCharAt(endOffset))) return CodeCompletionKeyPressResult.None; + editor.Document.Remove(editor.SelectionStart, editor.SelectionLength); - editor.Caret.Offset = cursor; + // Read caret position again after removal - this is required because the document might change in other + // locations, too (e.g. bound elements in snippets). + cursor = editor.Caret.Offset; } char prevChar = cursor > 1 ? editor.Document.GetCharAt(cursor - 1) : ' '; bool afterUnderscore = prevChar == '_'; diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.addin b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.addin index bfa6f64990..dcd36be26d 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.addin +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.addin @@ -75,4 +75,10 @@ command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertLeadingSpacesToTabs"/> + + + + diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj index 85ebe9f556..2564f6f2f4 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.csproj @@ -78,6 +78,13 @@ + + + + + + Code + Code @@ -90,6 +97,7 @@ + {6C55B776-26D4-4DB3-A6AB-87E783B2F3D1} ICSharpCode.AvalonEdit @@ -121,6 +129,9 @@ False + + SnippetOptionPanel.cs + QuickClassBrowser.cs diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index 828a736cab..b301c418a6 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -73,14 +73,8 @@ namespace ICSharpCode.AvalonEdit.AddIn } private set { if (document != value) { - if (document != null) - document.UpdateFinished -= DocumentUpdateFinished; - document = value; - if (document != null) - document.UpdateFinished += DocumentUpdateFinished; - if (DocumentChanged != null) { DocumentChanged(this, EventArgs.Empty); } @@ -287,23 +281,12 @@ namespace ICSharpCode.AvalonEdit.AddIn } public event EventHandler CaretPositionChanged; - bool caretPositionWasChanged; void TextAreaCaretPositionChanged(object sender, EventArgs e) { Debug.Assert(sender is Caret); + Debug.Assert(!document.IsInUpdate); if (sender == this.ActiveTextEditor.TextArea.Caret) { - if (document.IsInUpdate) - caretPositionWasChanged = true; - else - HandleCaretPositionChange(); - } - } - - void DocumentUpdateFinished(object sender, EventArgs e) - { - if (caretPositionWasChanged) { - caretPositionWasChanged = false; HandleCaretPositionChange(); } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorAdapter.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorAdapter.cs index 6e8875994b..e71c8e8c7f 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorAdapter.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorAdapter.cs @@ -7,8 +7,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using ICSharpCode.AvalonEdit.AddIn.Snippets; using ICSharpCode.AvalonEdit.Indentation; using ICSharpCode.Core; using ICSharpCode.SharpDevelop; @@ -25,7 +27,7 @@ namespace ICSharpCode.AvalonEdit.AddIn { readonly CodeEditor codeEditor; - public CodeEditorAdapter(CodeEditor codeEditor, TextEditor textEditor) : base(textEditor) + public CodeEditorAdapter(CodeEditor codeEditor, CodeEditorView textEditor) : base(textEditor) { if (codeEditor == null) throw new ArgumentNullException("codeEditor"); @@ -94,5 +96,15 @@ namespace ICSharpCode.AvalonEdit.AddIn public override ITextEditor PrimaryView { get { return codeEditor.PrimaryTextEditorAdapter; } } + + public override IEnumerable GetSnippets() + { + CodeSnippetGroup g = SnippetManager.Instance.FindGroup(Path.GetExtension(this.FileName)); + if (g != null) { + return g.Snippets.Select(s => s.CreateCompletionItem(this)); + } else { + return base.GetSnippets(); + } + } } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs index 762c73f857..98c5e3c171 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs @@ -1,19 +1,20 @@ -/* - * Created by SharpDevelop. - * User: daniel - * Date: 31.08.2009 - * Time: 14:55 - * - * To change this template use Tools | Options | Coding | Edit Standard Headers. - */ +// +// +// +// +// $Revision$ +// + using System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; +using ICSharpCode.AvalonEdit.AddIn.Snippets; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.SharpDevelop; @@ -41,6 +42,50 @@ namespace ICSharpCode.AvalonEdit.AddIn this.MouseHoverStopped += TextEditorMouseHoverStopped; this.MouseLeave += TextEditorMouseLeave; this.TextArea.TextView.MouseDown += TextViewMouseDown; + + var editingKeyBindings = this.TextArea.DefaultInputHandler.Editing.InputBindings.OfType(); + var tabBinding = editingKeyBindings.Single(b => b.Key == Key.Tab && b.Modifiers == ModifierKeys.None); + tabBinding.Command = new CustomTabCommand(this, tabBinding.Command); + } + + sealed class CustomTabCommand : ICommand + { + CodeEditorView editor; + ICommand baseCommand; + + public CustomTabCommand(CodeEditorView editor, ICommand baseCommand) + { + this.editor = editor; + this.baseCommand = baseCommand; + } + + public event EventHandler CanExecuteChanged { + add {} + remove {} + } + + public bool CanExecute(object parameter) + { + return true; + } + + public void Execute(object parameter) + { + if (editor.SelectionLength == 0) { + int wordStart = DocumentUtilitites.FindPrevWordStart(editor.Adapter.Document, editor.CaretOffset); + if (wordStart > 0) { + string word = editor.Adapter.Document.GetText(wordStart, editor.CaretOffset - wordStart); + CodeSnippet snippet = SnippetManager.Instance.FindSnippet(Path.GetExtension(editor.Adapter.FileName), + word); + if (snippet != null) { + editor.Adapter.Document.Remove(wordStart, editor.CaretOffset - wordStart); + snippet.CreateAvalonEditSnippet(editor.Adapter).Insert(editor.TextArea); + return; + } + } + } + baseCommand.Execute(parameter); + } } #region Help diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs index 110ce2fabb..ed0a03c4a1 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs @@ -114,10 +114,20 @@ namespace ICSharpCode.AvalonEdit.AddIn } } + /// + /// Completion item that supports complex content and description. + /// + public interface IFancyCompletionItem : ICompletionItem + { + object Content { get; } + new object Description { get; } + } + sealed class CodeCompletionDataAdapter : ICompletionData { readonly SharpDevelopCompletionWindow window; readonly ICompletionItem item; + readonly IFancyCompletionItem fancyCompletionItem; public CodeCompletionDataAdapter(SharpDevelopCompletionWindow window, ICompletionItem item) { @@ -127,6 +137,7 @@ namespace ICSharpCode.AvalonEdit.AddIn throw new ArgumentNullException("item"); this.window = window; this.item = item; + this.fancyCompletionItem = item as IFancyCompletionItem; } public ICompletionItem Item { @@ -138,12 +149,14 @@ namespace ICSharpCode.AvalonEdit.AddIn } public object Content { - get { return item.Text; } + get { + return (fancyCompletionItem != null) ? fancyCompletionItem.Content : item.Text; + } } public object Description { get { - return item.Description; + return (fancyCompletionItem != null) ? fancyCompletionItem.Description : item.Description; } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs new file mode 100644 index 0000000000..063457f4b4 --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippet.cs @@ -0,0 +1,192 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text.RegularExpressions; + +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Dom.Refactoring; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.CodeCompletion; + +namespace ICSharpCode.AvalonEdit.AddIn.Snippets +{ + /// + /// A code snippet. + /// + public class CodeSnippet : INotifyPropertyChanged + { + string name, description, text; + + public string Name { + get { return name; } + set { + if (name != value) { + name = value; + OnPropertyChanged("Name"); + } + } + } + + public string Text { + get { return text; } + set { + if (text != value) { + text = value; + OnPropertyChanged("Text"); + } + } + } + + public string Description { + get { return description; } + set { + if (description != value) { + description = value; + OnPropertyChanged("Description"); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + public Snippet CreateAvalonEditSnippet(ITextEditor context) + { + return CreateAvalonEditSnippet(context, this.Text); + } + + public ICompletionItem CreateCompletionItem(ITextEditor context) + { + return new SnippetCompletionItem(context, this); + } + + readonly static Regex pattern = new Regex(@"\$\{([^\}]*)\}", RegexOptions.CultureInvariant); + + public static Snippet CreateAvalonEditSnippet(ITextEditor context, string snippetText) + { + if (snippetText == null) + throw new ArgumentNullException("text"); + var replaceableElements = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (Match m in pattern.Matches(snippetText)) { + string val = m.Groups[1].Value; + int equalsSign = val.IndexOf('='); + if (equalsSign > 0) { + string name = val.Substring(0, equalsSign); + replaceableElements[name] = new SnippetReplaceableTextElement(); + } + } + Snippet snippet = new Snippet(); + int pos = 0; + foreach (Match m in pattern.Matches(snippetText)) { + if (pos < m.Index) { + snippet.Elements.Add(new SnippetTextElement { Text = snippetText.Substring(pos, m.Index - pos) }); + pos = m.Index; + } + snippet.Elements.Add(CreateElementForValue(context, replaceableElements, m.Groups[1].Value)); + pos = m.Index + m.Length; + } + if (pos < snippetText.Length) { + snippet.Elements.Add(new SnippetTextElement { Text = snippetText.Substring(pos) }); + } + return snippet; + } + + readonly static Regex functionPattern = new Regex(@"^([a-zA-Z]+)\(([^\)]*)\)$", RegexOptions.CultureInvariant); + + static SnippetElement CreateElementForValue(ITextEditor context, Dictionary replaceableElements, string val) + { + SnippetReplaceableTextElement srte; + int equalsSign = val.IndexOf('='); + if (equalsSign > 0) { + string name = val.Substring(0, equalsSign); + if (replaceableElements.TryGetValue(name, out srte)) { + if (srte.Text == null) + srte.Text = val.Substring(equalsSign + 1); + return srte; + } + } + if ("Selection".Equals(val, StringComparison.OrdinalIgnoreCase)) + return new SnippetCaretElement(); + if (replaceableElements.TryGetValue(val, out srte)) + return new SnippetBoundElement { TargetElement = srte }; + Match m = functionPattern.Match(val); + if (m.Success) { + Func f = GetFunction(context, m.Groups[1].Value); + if (f != null) { + string innerVal = m.Groups[2].Value; + if (replaceableElements.TryGetValue(innerVal, out srte)) + return new FunctionBoundElement { TargetElement = srte, function = f }; + string result2 = GetValue(context, innerVal); + if (result2 != null) + return new SnippetTextElement { Text = f(result2) }; + else + return new SnippetTextElement { Text = f(innerVal) }; + } + } + string result = GetValue(context, val); + if (result != null) + return new SnippetTextElement { Text = result }; + else + return new SnippetReplaceableTextElement { Text = val }; // ${unknown} -> replaceable element + } + + static string GetValue(ITextEditor editor, string propertyName) + { + if ("ClassName".Equals(propertyName, StringComparison.OrdinalIgnoreCase)) { + IClass c = GetCurrentClass(editor); + if (c != null) + return c.Name; + } + return Core.StringParser.GetValue(propertyName); + } + + static IClass GetCurrentClass(ITextEditor editor) + { + var parseInfo = ParserService.GetExistingParseInformation(editor.FileName); + if (parseInfo != null) { + return parseInfo.CompilationUnit.GetInnermostClass(editor.Caret.Line, editor.Caret.Column); + } + return null; + } + + static Func GetFunction(ITextEditor context, string name) + { + if ("toLower".Equals(name, StringComparison.OrdinalIgnoreCase)) + return s => s.ToLower(); + if ("toUpper".Equals(name, StringComparison.OrdinalIgnoreCase)) + return s => s.ToUpper(); + if ("toFieldName".Equals(name, StringComparison.OrdinalIgnoreCase)) + return s => context.Language.Properties.CodeGenerator.GetFieldName(s); + if ("toPropertyName".Equals(name, StringComparison.OrdinalIgnoreCase)) + return s => context.Language.Properties.CodeGenerator.GetPropertyName(s); + if ("toParameterName".Equals(name, StringComparison.OrdinalIgnoreCase)) + return s => context.Language.Properties.CodeGenerator.GetParameterName(s); + return null; + } + + sealed class FunctionBoundElement : SnippetBoundElement + { + internal Func function; + + public override string ConvertText(string input) + { + return function(input); + } + } + } +} diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs new file mode 100644 index 0000000000..5aad4a2784 --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/CodeSnippetGroup.cs @@ -0,0 +1,47 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace ICSharpCode.AvalonEdit.AddIn.Snippets +{ + /// + /// A group of snippets (for a specific file extension). + /// + public class CodeSnippetGroup : INotifyPropertyChanged + { + string extensions = ""; + ObservableCollection snippets = new ObservableCollection(); + + public ObservableCollection Snippets { + get { return snippets; } + } + + public string Extensions { + get { return extensions; } + set { + if (value == null) + throw new ArgumentNullException(); + if (extensions != value) { + extensions = value; + OnPropertyChanged("Extensions"); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetCompletionItem.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetCompletionItem.cs new file mode 100644 index 0000000000..41c9a76fe4 --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetCompletionItem.cs @@ -0,0 +1,94 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Windows.Controls; +using System.Windows.Documents; + +using ICSharpCode.AvalonEdit.Editing; +using ICSharpCode.AvalonEdit.Snippets; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Editor.CodeCompletion; + +namespace ICSharpCode.AvalonEdit.AddIn.Snippets +{ + /// + /// Code completion item for snippets. + /// + public class SnippetCompletionItem : IFancyCompletionItem + { + readonly CodeSnippet codeSnippet; + readonly ITextEditor textEditor; + readonly TextArea textArea; + + public SnippetCompletionItem(ITextEditor textEditor, CodeSnippet codeSnippet) + { + if (textEditor == null) + throw new ArgumentNullException("textEditor"); + if (codeSnippet == null) + throw new ArgumentNullException("codeSnippet"); + this.textEditor = textEditor; + this.textArea = textEditor.GetService(typeof(TextArea)) as TextArea; + if (textArea == null) + throw new ArgumentException("textEditor must be an AvalonEdit text editor"); + this.codeSnippet = codeSnippet; + } + + public string Text { + get { return codeSnippet.Name; } + } + + public string Description { + get { + return codeSnippet.Description + Environment.NewLine + + ResourceService.GetString("Dialog.Options.CodeTemplate.PressTabToInsertTemplate"); + } + } + + public ICSharpCode.SharpDevelop.IImage Image { + get { + return ClassBrowserIconService.CodeTemplate; + } + } + + public void Complete(CompletionContext context) + { + if (context.Editor != this.textEditor) + throw new ArgumentException("wrong editor"); + context.Editor.Document.Remove(context.StartOffset, context.Length); + CreateSnippet().Insert(textArea); + } + + Snippet CreateSnippet() + { + return codeSnippet.CreateAvalonEditSnippet(textEditor); + } + + object IFancyCompletionItem.Content { + get { + return Text; + } + } + + TextBlock fancyDescription; + + object IFancyCompletionItem.Description { + get { + if (fancyDescription == null) { + fancyDescription = new TextBlock(); + fancyDescription.Inlines.Add(new Run(this.Description + Environment.NewLine + Environment.NewLine)); + Inline r = CreateSnippet().ToTextRun(); + r.FontFamily = textArea.FontFamily; + fancyDescription.Inlines.Add(r); + } + return fancyDescription; + } + } + } +} diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs new file mode 100644 index 0000000000..aedd0a11cd --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetManager.cs @@ -0,0 +1,153 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +using ICSharpCode.Core; + +namespace ICSharpCode.AvalonEdit.AddIn.Snippets +{ + /// + /// SnippetManager singleton. + /// + public sealed class SnippetManager + { + public static readonly SnippetManager Instance = new SnippetManager(); + readonly object lockObj = new object(); + static readonly List defaultSnippets = new List { + new CodeSnippetGroup { + Extensions = ".cs", + Snippets = { + new CodeSnippet { + Name = "for", + Description = "for loop", + Text = "for (int ${counter=i} = 0; ${counter} < ${end}; ${counter}++) {\n\t${Selection}\n}" + }, + new CodeSnippet { + Name = "foreach", + Description = "foreach loop", + Text = "foreach (${var} ${element} in ${collection}) {\n\t${Selection}\n}" + }, + new CodeSnippet { + Name = "if", + Description = "if statement", + Text = "if (${condition}) {\n\t${Selection}\n}" + }, + new CodeSnippet { + Name = "ifelse", + Description = "if-else statement", + Text = "if (${condition}) {\n\t${Selection}\n} else {\n\t\n}" + }, + new CodeSnippet { + Name = "while", + Description = "while loop", + Text = "while (${condition}) {\n\t${Selection}\n}" + }, + new CodeSnippet { + Name = "prop", + Description = "Property", + Text = "${type} ${toFieldName(name)};\n\npublic ${type=int} ${name=Property} {\n\tget { return ${toFieldName(name)}; }\n\tset { ${toFieldName(name)} = value; }\n}" + }, + new CodeSnippet { + Name = "propdp", + Description = "Dependency Property", + Text = "public static readonly DependencyProperty ${name}Property =" + Environment.NewLine + + "\tDependencyProperty.Register(\"${name}\", typeof(${type}), typeof(${ClassName})," + Environment.NewLine + + "\t new FrameworkPropertyMetadata());" + Environment.NewLine + + "" + Environment.NewLine + + "public ${type=int} ${name=Property} {" + Environment.NewLine + + "\tget { return (${type})GetValue(${name}Property); }" + Environment.NewLine + + "\tset { SetValue(${name}Property, value); }" + + Environment.NewLine + "}" + }, + new CodeSnippet { + Name = "ctor", + Description = "Constructor", + Text = "public ${ClassName}()\n{\t\n${Selection}\n}" + }, + new CodeSnippet { + Name = "switch", + Description = "Switch statement", + Text = "switch (${condition}) {\n\tcase ${firstcase=0}:\n\t\tbreak;\n\tdefault:\n\t\t${Selection}\n\t\tbreak;\n}" + }, + new CodeSnippet { + Name = "try", + Description = "Try-catch statement", + Text = "try {\n\t${Selection}\n} catch (Exception) {\n\t\n\tthrow;\n}" + }, + new CodeSnippet { + Name = "trycf", + Description = "Try-catch-finally statement", + Text = "try {\n\t${Selection}\n} catch (Exception) {\n\t\n\tthrow;\n} finally {\n\t\n}" + }, + new CodeSnippet { + Name = "tryf", + Description = "Try-finally statement", + Text = "try {\n\t${Selection}\n} finally {\n\t\n}" + }, + } + } + }; + + private SnippetManager() {} + + /// + /// Loads copies of all code snippet groups. + /// + public List LoadGroups() + { + return PropertyService.Get("CodeSnippets", defaultSnippets); + } + + /// + /// Saves the set of groups. + /// + public void SaveGroups(IEnumerable groups) + { + lock (lockObj) { + activeGroups = null; + PropertyService.Set("CodeSnippets", groups.ToList()); + } + } + + ReadOnlyCollection activeGroups; + + public ReadOnlyCollection ActiveGroups { + get { + lock (lockObj) { + if (activeGroups == null) + activeGroups = LoadGroups().AsReadOnly(); + return activeGroups; + } + } + } + + public CodeSnippetGroup FindGroup(string extension) + { + foreach (CodeSnippetGroup g in ActiveGroups) { + string[] extensions = g.Extensions.Split(';'); + foreach (string gext in extensions) { + if (gext.Equals(extension, StringComparison.OrdinalIgnoreCase)) + return g; + } + } + return null; + } + + public CodeSnippet FindSnippet(string extension, string name) + { + CodeSnippetGroup g = FindGroup(extension); + if (g != null) { + return g.Snippets.FirstOrDefault(s => s.Name == name); + } + return null; + } + } +} diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.cs new file mode 100644 index 0000000000..6381186922 --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.cs @@ -0,0 +1,110 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; + +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; + +namespace ICSharpCode.AvalonEdit.AddIn.Snippets +{ + /// + /// Interaction logic for Snippets.xaml + /// + public partial class SnippetOptionPanel : UserControl, IOptionPanel + { + ObservableCollection groups; + + public SnippetOptionPanel() + { + InitializeComponent(); + } + + public object Owner { get; set; } + + public object Control { + get { return this; } + } + + public void LoadOptions() + { + groups = new ObservableCollection(SnippetManager.Instance.LoadGroups().OrderBy(g => g.Extensions)); + extensionComboBox.ItemsSource = groups; + extensionComboBox.SelectedItem = groups.FirstOrDefault(); + } + + public bool SaveOptions() + { + SnippetManager.Instance.SaveGroups(groups); + return true; + } + + void AddGroupButton_Click(object sender, RoutedEventArgs e) + { + string result = MessageService.ShowInputBox( + "${res:Dialog.Options.CodeTemplate.AddGroupLabel}", + "${res:Dialog.Options.CodeTemplate.EditGroupDialog.Text}", + ""); + if (!string.IsNullOrEmpty(result)) { + CodeSnippetGroup g = new CodeSnippetGroup(); + g.Extensions = result; + groups.Add(g); + extensionComboBox.SelectedItem = g; + } + } + + void RemoveGroupButton_Click(object sender, RoutedEventArgs e) + { + if (extensionComboBox.SelectedIndex >= 0) + groups.RemoveAt(extensionComboBox.SelectedIndex); + } + + void EditGroupButton_Click(object sender, RoutedEventArgs e) + { + CodeSnippetGroup g = (CodeSnippetGroup)extensionComboBox.SelectedItem; + if (g != null) { + string result = MessageService.ShowInputBox( + "${res:Dialog.Options.CodeTemplate.EditGroupLabel}", + "${res:Dialog.Options.CodeTemplate.EditGroupDialog.Text}", + g.Extensions); + if (!string.IsNullOrEmpty(result)) + g.Extensions = result; + } + } + + void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + CodeSnippet snippet = dataGrid.SelectedItem as CodeSnippet; + if (snippet != null) { + snippetTextBox.Text = snippet.Text; + snippetTextBox.IsReadOnly = false; + snippetTextBox.Background = SystemColors.WindowBrush; + } else { + snippetTextBox.Text = null; + snippetTextBox.IsReadOnly = true; + snippetTextBox.Background = SystemColors.ControlBrush; + } + } + + void SnippetTextBox_TextChanged(object sender, EventArgs e) + { + CodeSnippet snippet = dataGrid.SelectedItem as CodeSnippet; + if (snippet != null) + snippet.Text = snippetTextBox.Text; + } + } +} \ No newline at end of file diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.xaml b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.xaml new file mode 100644 index 0000000000..698428dafb --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Snippets/SnippetOptionPanel.xaml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + +