diff options
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor')
49 files changed, 9830 insertions, 0 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/AutoSave.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/AutoSave.cs new file mode 100644 index 0000000000..a0eacc97ef --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/AutoSave.cs @@ -0,0 +1,225 @@ +// +// AutoSave.cs +// +// Author: +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// 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.IO; +using System.Threading; +using MonoDevelop.Core; +using Gtk; +using MonoDevelop.Core.Text; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// This class handles the auto save mechanism for open files. + /// It should only be used by editor implementations. + /// </summary> + static class AutoSave + { + //FIXME: is this path a good one? wouldn't it be better to put autosaves beside the files anyway? + static string autoSavePath = UserProfile.Current.CacheDir.Combine ("AutoSave"); + static bool autoSaveEnabled; + + static AutoSave () + { + try { + if (!Directory.Exists (autoSavePath)) + Directory.CreateDirectory (autoSavePath); + } catch (Exception e) { + LoggingService.LogError ("Can't create auto save path:" + autoSavePath +". Auto save is disabled.", e); + autoSaveEnabled = false; + return; + } + autoSaveEnabled = true; + StartAutoSaveThread (); + } + + static string GetAutoSaveFileName (string fileName) + { + if (fileName == null) + return null; + string newFileName = Path.Combine (Path.GetDirectoryName (fileName), Path.GetFileNameWithoutExtension (fileName) + Path.GetExtension (fileName) + "~"); + newFileName = Path.Combine (autoSavePath, newFileName.Replace(',','_').Replace(" ","").Replace (":","").Replace (Path.DirectorySeparatorChar, '_').Replace (Path.AltDirectorySeparatorChar, '_')); + return newFileName; + } + + /// <summary> + /// Returns true if an auto save exists for the given file name. + /// </summary> + public static bool AutoSaveExists (string fileName) + { + if (!autoSaveEnabled) + return false; + try { + var autoSaveFilename = GetAutoSaveFileName (fileName); + bool autoSaveExists = File.Exists (autoSaveFilename); + if (autoSaveExists) { + if (File.GetLastWriteTimeUtc (autoSaveFilename) < File.GetLastWriteTimeUtc (fileName)) { + File.Delete (autoSaveFilename); + return false; + } + } + return autoSaveExists; + } catch (Exception e) { + LoggingService.LogError ("Error in auto save - disableing.", e); + DisableAutoSave (); + return false; + } + } + + static void CreateAutoSave (string fileName, ITextSource content) + { + if (!autoSaveEnabled) + return; + try { + // Directory may have removed/unmounted. Therefore this operation is not guaranteed to work. + var autosaveFileName = GetAutoSaveFileName (fileName); + if (File.Exists (autosaveFileName)) + File.Delete (autosaveFileName); + content.WriteTextTo (autosaveFileName); + Counters.AutoSavedFiles++; + } catch (Exception e) { + LoggingService.LogError ("Error in auto save while creating: " + fileName +". Disableing auto save.", e); + DisableAutoSave (); + } + } + + #region AutoSave + class FileContent + { + public string FileName; + public ITextSource Content; + + public FileContent (string fileName, ITextSource content) + { + this.FileName = fileName; + this.Content = content; + } + } + static readonly AutoResetEvent resetEvent = new AutoResetEvent (false); + + public static bool Running { + get { + return autoSaveThreadRunning; + } + } + static bool autoSaveThreadRunning = false; + static Thread autoSaveThread; + static Queue<FileContent> queue = new Queue<FileContent> (); + static object contentLock = new object (); + + static void StartAutoSaveThread () + { + autoSaveThreadRunning = true; + if (autoSaveThread == null) { + autoSaveThread = new Thread (AutoSaveThread); + autoSaveThread.Name = "Autosave"; + autoSaveThread.IsBackground = true; + autoSaveThread.Start (); + } + } + + static void AutoSaveThread () + { + while (autoSaveThreadRunning) { + resetEvent.WaitOne (); + while (queue.Count > 0) { + var content = queue.Dequeue (); + // Don't create an auto save for unsaved files. + if (string.IsNullOrEmpty (content.FileName)) + continue; + CreateAutoSave (content.FileName, content.Content); + } + } + } + + /// <summary> + /// Loads the content from an auto save file and removes the auto save file. + /// </summary> + public static ITextSource LoadAndRemoveAutoSave (string fileName) + { + string autoSaveFileName = GetAutoSaveFileName (fileName); + var result = StringTextSource.ReadFrom (autoSaveFileName); + AutoSave.RemoveAutoSaveFile (fileName); + return result; + } + + /// <summary> + /// Loads the content from an auto save file. + /// </summary> + public static ITextSource LoadAutoSave (string fileName) + { + string autoSaveFileName = GetAutoSaveFileName (fileName); + return StringTextSource.ReadFrom (autoSaveFileName); + } + + /// <summary> + /// Removes the auto save file. + /// </summary> + /// <param name="fileName">The file name for which the auto save file should be removed.</param> + public static void RemoveAutoSaveFile (string fileName) + { + if (!autoSaveEnabled) + return; + if (AutoSaveExists (fileName)) { + string autoSaveFileName = GetAutoSaveFileName (fileName); + try { + lock (contentLock) { + File.Delete (autoSaveFileName); + } + } catch (Exception e) { + LoggingService.LogError ("Can't delete auto save file: " + autoSaveFileName +". Disableing auto save.", e); + DisableAutoSave (); + } + } + } + + internal static void InformAutoSaveThread (ITextSource content, string fileName, bool isDirty) + { + if (content == null || !autoSaveEnabled) + return; + if (isDirty) { + queue.Enqueue (new FileContent (fileName, content)); + resetEvent.Set (); + } else { + RemoveAutoSaveFile (fileName); + } + } + + static void DisableAutoSave () + { + autoSaveThreadRunning = false; + if (autoSaveThread != null) { + resetEvent.Set (); + autoSaveThread.Join (); + autoSaveThread = null; + } + autoSaveEnabled = false; + } +#endregion + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Commands/DynamicAbbrevHandler.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Commands/DynamicAbbrevHandler.cs new file mode 100644 index 0000000000..08306da514 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Commands/DynamicAbbrevHandler.cs @@ -0,0 +1,193 @@ +// +// DynamicAbbrevHandler.cs +// +// Author: +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// 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.Linq; +using MonoDevelop.Components.Commands; +using MonoDevelop.Ide.Gui; +using System.Collections.Generic; +using MonoDevelop.Ide; + +namespace MonoDevelop.Ide.Editor +{ + class DynamicAbbrevHandler : CommandHandler + { + enum AbbrevState { + SearchBackward, + SearchForward, + SearchOtherBuffers, + CycleThroughFoundWords + } + + static TextEditor lastView = null; + static string lastAbbrev = null; + static int lastTriggerOffset = 0; + static int lastInsertPos = 0; + static List<string> foundWords = new List<string> (); + static int lastStartOffset = 0; + static AbbrevState curState; + + protected override void Run (object data) + { + var doc = IdeApp.Workbench.ActiveDocument; + if (doc == null) + return; + var editor = doc.Editor; + if (editor == null) + return; + + string abbrevWord; + int offset; + int startOffset; + + if (lastView == editor && editor.CaretOffset == lastTriggerOffset) { + abbrevWord = lastAbbrev; + offset = lastStartOffset; + } else { + abbrevWord = GetWordBeforeCaret (editor); + lastAbbrev = abbrevWord; + offset = editor.CaretOffset - abbrevWord.Length - 1; + lastInsertPos = lastTriggerOffset = offset + 1; + foundWords.Clear (); + foundWords.Add (abbrevWord); + curState = AbbrevState.SearchBackward; + } + + lastView = editor; + switch (curState) { + case AbbrevState.SearchBackward: + while (offset > 0) { + if (IsMatchAt (editor, offset, abbrevWord)) { + int endOffset = SearchEndPos (offset, editor); + string curWord = editor.GetTextBetween (offset, endOffset); + if (foundWords.Contains (curWord)) { + offset--; + continue; + } + foundWords.Add (curWord); + ReplaceWord (editor, curWord); + lastStartOffset = offset - 1; + return; + } + offset--; + } + offset = editor.CaretOffset; + curState = AbbrevState.SearchForward; + goto case AbbrevState.SearchForward; + case AbbrevState.SearchForward: + while (offset < editor.Length) { + if (IsMatchAt (editor, offset, abbrevWord)) { + int endOffset = SearchEndPos (offset, editor); + string curWord = editor.GetTextBetween (offset, endOffset); + if (foundWords.Contains (curWord)) { + offset++; + continue; + } + foundWords.Add (curWord); + ReplaceWord (editor, curWord); + lastStartOffset = offset + 1; + return; + } + offset++; + } + curState = AbbrevState.SearchOtherBuffers; + goto case AbbrevState.SearchOtherBuffers; + case AbbrevState.SearchOtherBuffers: + foreach (Document curDoc in IdeApp.Workbench.Documents) { + var otherView = curDoc.GetContent<TextEditor> (); + if (curDoc == doc || otherView == null) + continue; + for (int i = 0; i < otherView.Length; i++) { + if (IsMatchAt (otherView, i, abbrevWord)) { + int endOffset = SearchEndPos (i, otherView); + string curWord = otherView.GetTextBetween (i, endOffset); + if (foundWords.Contains (curWord)) + continue; + foundWords.Add (curWord); + } + } + } + curState = AbbrevState.CycleThroughFoundWords; + goto case AbbrevState.CycleThroughFoundWords; + case AbbrevState.CycleThroughFoundWords: + int index = foundWords.IndexOf (editor.GetTextAt (lastInsertPos, editor.CaretOffset - lastInsertPos)); + if (index < 0) + break; + startOffset = offset; + offset = startOffset + foundWords[index].Length; + index = (index + foundWords.Count + 1) % foundWords.Count; + ReplaceWord (editor, foundWords[index]); + break; + } + } + + public static bool IsIdentifierPart (char ch) + { + return char.IsLetterOrDigit (ch) || ch == '_'; + } + + static string GetWordBeforeCaret (TextEditor editor) + { + int startOffset = editor.CaretOffset; + int offset = startOffset - 1; + while (offset > 0) { + char ch = editor.GetCharAt (offset); + if (!IsIdentifierPart (ch)) { + offset++; + break; + } + offset--; + } + if (offset >= startOffset) + return ""; + return editor.GetTextBetween (offset, startOffset); + } + + static void ReplaceWord (TextEditor editor, string curWord) + { + editor.ReplaceText (lastInsertPos, editor.CaretOffset - lastInsertPos, curWord); + lastTriggerOffset = editor.CaretOffset; + } + + static int SearchEndPos (int offset, TextEditor editor) + { + while (offset < editor.Length && IsIdentifierPart (editor.GetCharAt (offset))) { + offset++; + } + return offset; + } + + static bool IsMatchAt (TextEditor editor, int offset, string abbrevWord) + { + if (offset + abbrevWord.Length >= editor.Length) + return false; + if (offset > 0 && IsIdentifierPart (editor.GetCharAt (offset - 1))) + return false; + if (offset + abbrevWord.Length < editor.Length && !IsIdentifierPart (editor.GetCharAt (offset + abbrevWord.Length))) + return false; + return editor.GetTextAt (offset, abbrevWord.Length) == abbrevWord; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/CustomEditorOptions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/CustomEditorOptions.cs new file mode 100644 index 0000000000..a3932167e2 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/CustomEditorOptions.cs @@ -0,0 +1,186 @@ +// +// ChangeableEditorOptions.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + public sealed class CustomEditorOptions : ITextEditorOptions + { + #region ITextEditorOptions implementation + public WordFindStrategy WordFindStrategy { + get; + set; + } + + public bool TabsToSpaces { + get; + set; + } + + public int IndentationSize { + get; + set; + } + + public int TabSize { + get; + set; + } + + public bool ShowIconMargin { + get; + set; + } + + public bool ShowLineNumberMargin { + get; + set; + } + + public bool ShowFoldMargin { + get; + set; + } + + public bool HighlightCaretLine { + get; + set; + } + + public int RulerColumn { + get; + set; + } + + public bool ShowRuler { + get; + set; + } + + public IndentStyle IndentStyle { + get; + set; + } + + public bool OverrideDocumentEolMarker { + get; + set; + } + + public bool EnableSyntaxHighlighting { + get; + set; + } + + public bool RemoveTrailingWhitespaces { + get; + set; + } + + public bool WrapLines { + get; + set; + } + + public string FontName { + get; + set; + } + + public string GutterFontName { + get; + set; + } + + public string ColorScheme { + get; + set; + } + + public string DefaultEolMarker { + get; + set; + } + + public bool GenerateFormattingUndoStep { + get; + set; + } + + public ShowWhitespaces ShowWhitespaces { + get; + set; + } + + public IncludeWhitespaces IncludeWhitespaces { + get; + set; + } + #endregion + + public CustomEditorOptions () + { + this.ColorScheme = "Default"; + this.TabSize = this.IndentationSize = 4; + this.DefaultEolMarker = "\n"; + } + + public CustomEditorOptions (ITextEditorOptions initializeFrom) + { + if (initializeFrom == null) + throw new ArgumentNullException (nameof (initializeFrom)); + WordFindStrategy = initializeFrom.WordFindStrategy; + TabsToSpaces = initializeFrom.TabsToSpaces; + IndentationSize = initializeFrom.IndentationSize; + TabSize = initializeFrom.TabSize; + ShowIconMargin = initializeFrom.ShowIconMargin; + ShowLineNumberMargin = initializeFrom.ShowLineNumberMargin; + ShowFoldMargin = initializeFrom.ShowFoldMargin; + HighlightCaretLine = initializeFrom.HighlightCaretLine; + RulerColumn = initializeFrom.RulerColumn; + ShowRuler = initializeFrom.ShowRuler; + IndentStyle = initializeFrom.IndentStyle; + OverrideDocumentEolMarker = initializeFrom.OverrideDocumentEolMarker; + EnableSyntaxHighlighting = initializeFrom.EnableSyntaxHighlighting; + RemoveTrailingWhitespaces = initializeFrom.RemoveTrailingWhitespaces; + WrapLines = initializeFrom.WrapLines; + FontName = initializeFrom.FontName; + GutterFontName = initializeFrom.GutterFontName; + ColorScheme = initializeFrom.ColorScheme; + DefaultEolMarker = initializeFrom.DefaultEolMarker; + GenerateFormattingUndoStep = initializeFrom.GenerateFormattingUndoStep; + ShowWhitespaces = initializeFrom.ShowWhitespaces; + IncludeWhitespaces = initializeFrom.IncludeWhitespaces; + } + + #region IDisposable implementation + public void Dispose () + { + } + #endregion + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DefaultSourceEditorOptions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DefaultSourceEditorOptions.cs new file mode 100644 index 0000000000..1e83737769 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DefaultSourceEditorOptions.cs @@ -0,0 +1,785 @@ +// +// DefaultSourceEditorOptions.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core; +using MonoDevelop.Ide.Gui.Content; +using MonoDevelop.Ide.Fonts; +using MonoDevelop.Ide.Editor.Extension; + +namespace MonoDevelop.Ide.Editor +{ + [Obsolete ("Use WordNavigationStyle")] + public enum ControlLeftRightMode + { + MonoDevelop, + Emacs, + SharpDevelop + } + + public enum WordNavigationStyle + { + Unix, + Windows + } + + public enum LineEndingConversion { + Ask, + LeaveAsIs, + ConvertAlways + } + + /// <summary> + /// This class contains all text editor options from ITextEditorOptions and additional options + /// the text editor frontend may use. + /// </summary> + public sealed class DefaultSourceEditorOptions : ITextEditorOptions + { + static DefaultSourceEditorOptions instance; + //static TextStylePolicy defaultPolicy; + static bool inited; + + public static DefaultSourceEditorOptions Instance { + get { return instance; } + } + + public static ITextEditorOptions PlainEditor { + get; + private set; + } + + static DefaultSourceEditorOptions () + { + Init (); + } + + public static void Init () + { + if (inited) + return; + inited = true; + + var policy = MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<TextStylePolicy> ("text/plain"); + instance = new DefaultSourceEditorOptions (policy); + MonoDevelop.Projects.Policies.PolicyService.DefaultPolicies.PolicyChanged += instance.HandlePolicyChanged; + + PlainEditor = new PlainEditorOptions (); + } + + class PlainEditorOptions : ITextEditorOptions + { + #region IDisposable implementation + + void IDisposable.Dispose () + { + // nothing + } + + #endregion + + #region ITextEditorOptions implementation + + WordFindStrategy ITextEditorOptions.WordFindStrategy { + get { + return DefaultSourceEditorOptions.Instance.WordFindStrategy; + } + } + + bool ITextEditorOptions.TabsToSpaces { + get { + return DefaultSourceEditorOptions.Instance.TabsToSpaces; + } + } + + int ITextEditorOptions.IndentationSize { + get { + return DefaultSourceEditorOptions.Instance.IndentationSize; + } + } + + int ITextEditorOptions.TabSize { + get { + return DefaultSourceEditorOptions.Instance.TabSize; + } + } + + bool ITextEditorOptions.ShowIconMargin { + get { + return false; + } + } + + bool ITextEditorOptions.ShowLineNumberMargin { + get { + return false; + } + } + + bool ITextEditorOptions.ShowFoldMargin { + get { + return false; + } + } + + bool ITextEditorOptions.HighlightCaretLine { + get { + return DefaultSourceEditorOptions.Instance.HighlightCaretLine; + } + } + + int ITextEditorOptions.RulerColumn { + get { + return DefaultSourceEditorOptions.Instance.RulerColumn; + } + } + + bool ITextEditorOptions.ShowRuler { + get { + return false; + } + } + + IndentStyle ITextEditorOptions.IndentStyle { + get { + return DefaultSourceEditorOptions.Instance.IndentStyle; + } + } + + bool ITextEditorOptions.OverrideDocumentEolMarker { + get { + return false; + } + } + + bool ITextEditorOptions.EnableSyntaxHighlighting { + get { + return DefaultSourceEditorOptions.Instance.EnableSyntaxHighlighting; + } + } + + bool ITextEditorOptions.RemoveTrailingWhitespaces { + get { + return DefaultSourceEditorOptions.Instance.RemoveTrailingWhitespaces; + } + } + + bool ITextEditorOptions.WrapLines { + get { + return DefaultSourceEditorOptions.Instance.WrapLines; + } + } + + string ITextEditorOptions.FontName { + get { + return DefaultSourceEditorOptions.Instance.FontName; + } + } + + string ITextEditorOptions.GutterFontName { + get { + return DefaultSourceEditorOptions.Instance.GutterFontName; + } + } + + string ITextEditorOptions.ColorScheme { + get { + return DefaultSourceEditorOptions.Instance.ColorScheme; + } + } + + string ITextEditorOptions.DefaultEolMarker { + get { + return DefaultSourceEditorOptions.Instance.DefaultEolMarker; + } + } + + bool ITextEditorOptions.GenerateFormattingUndoStep { + get { + return DefaultSourceEditorOptions.Instance.GenerateFormattingUndoStep; + } + } + + ShowWhitespaces ITextEditorOptions.ShowWhitespaces { + get { + return ShowWhitespaces.Never; + } + } + + IncludeWhitespaces ITextEditorOptions.IncludeWhitespaces { + get { + return DefaultSourceEditorOptions.Instance.IncludeWhitespaces; + } + } + + #endregion + + + } + + void HandlePolicyChanged (object sender, MonoDevelop.Projects.Policies.PolicyChangedEventArgs args) + { + TextStylePolicy pol = MonoDevelop.Projects.Policies.PolicyService.GetDefaultPolicy<TextStylePolicy> ("text/plain"); + UpdateStylePolicy (pol); + } + + DefaultSourceEditorOptions (TextStylePolicy currentPolicy) + { + var defaultControlMode = (ControlLeftRightMode)Enum.Parse (typeof(ControlLeftRightMode), DesktopService.DefaultControlLeftRightBehavior); + controlLeftRightMode = new PropertyWrapper<ControlLeftRightMode> ("ControlLeftRightMode", defaultControlMode); + + WordNavigationStyle defaultWordNavigation = WordNavigationStyle.Unix; + if (Platform.IsWindows || controlLeftRightMode.Value == ControlLeftRightMode.SharpDevelop) { + defaultWordNavigation = WordNavigationStyle.Windows; + } + wordNavigationStyle = new PropertyWrapper<WordNavigationStyle> ("WordNavigationStyle", defaultWordNavigation); + + UpdateStylePolicy (currentPolicy); + FontService.RegisterFontChangedCallback ("Editor", UpdateFont); + FontService.RegisterFontChangedCallback ("MessageBubbles", UpdateFont); + } + + void UpdateFont () + { + this.OnChanged (EventArgs.Empty); + } + + void UpdateStylePolicy (MonoDevelop.Ide.Gui.Content.TextStylePolicy currentPolicy) + { + defaultEolMarker = TextStylePolicy.GetEolMarker (currentPolicy.EolMarker); + tabsToSpaces = currentPolicy.TabsToSpaces; // PropertyService.Get ("TabsToSpaces", false); + indentationSize = currentPolicy.TabWidth; //PropertyService.Get ("TabIndent", 4); + rulerColumn = currentPolicy.FileWidth; //PropertyService.Get ("RulerColumn", 80); + allowTabsAfterNonTabs = !currentPolicy.NoTabsAfterNonTabs; //PropertyService.Get ("AllowTabsAfterNonTabs", true); + removeTrailingWhitespaces = currentPolicy.RemoveTrailingWhitespace; //PropertyService.Get ("RemoveTrailingWhitespaces", true); + } + + public ITextEditorOptions WithTextStyle (MonoDevelop.Ide.Gui.Content.TextStylePolicy policy) + { + if (policy == null) + throw new ArgumentNullException ("policy"); + var result = (DefaultSourceEditorOptions)MemberwiseClone (); + result.UpdateStylePolicy (policy); + result.Changed = null; + return result; + } + + #region new options + + public bool EnableAutoCodeCompletion { + get { return CompletionTextEditorExtension.EnableAutoCodeCompletion; } + set { CompletionTextEditorExtension.EnableAutoCodeCompletion.Set (value); } + } + + PropertyWrapper<bool> defaultRegionsFolding = new PropertyWrapper<bool> ("DefaultRegionsFolding", false); + public bool DefaultRegionsFolding { + get { + return defaultRegionsFolding; + } + set { + if (defaultRegionsFolding.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> defaultCommentFolding = new PropertyWrapper<bool> ("DefaultCommentFolding", true); + public bool DefaultCommentFolding { + get { + return defaultCommentFolding; + } + set { + if (defaultCommentFolding.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> enableSemanticHighlighting = new PropertyWrapper<bool> ("EnableSemanticHighlighting", true); + public bool EnableSemanticHighlighting { + get { + return enableSemanticHighlighting; + } + set { + if (enableSemanticHighlighting.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> tabIsReindent = new PropertyWrapper<bool> ("TabIsReindent", false); + public bool TabIsReindent { + get { + return tabIsReindent; + } + set { + if (tabIsReindent.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> autoInsertMatchingBracket = new PropertyWrapper<bool> ("AutoInsertMatchingBracket", false); + public bool AutoInsertMatchingBracket { + get { + return autoInsertMatchingBracket; + } + set { + if (autoInsertMatchingBracket.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> smartSemicolonPlacement = new PropertyWrapper<bool> ("SmartSemicolonPlacement", false); + public bool SmartSemicolonPlacement { + get { + return smartSemicolonPlacement; + } + set { + if (smartSemicolonPlacement.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> underlineErrors = new PropertyWrapper<bool> ("UnderlineErrors", true); + public bool UnderlineErrors { + get { + return underlineErrors; + } + set { + if (underlineErrors.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<IndentStyle> indentStyle = new PropertyWrapper<IndentStyle> ("IndentStyle", IndentStyle.Smart); + public IndentStyle IndentStyle { + get { + return indentStyle; + } + set { + if (indentStyle.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> enableHighlightUsages = new PropertyWrapper<bool> ("EnableHighlightUsages", false); + public bool EnableHighlightUsages { + get { + return enableHighlightUsages; + } + set { + if (enableHighlightUsages.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<LineEndingConversion> lineEndingConversion = new PropertyWrapper<LineEndingConversion> ("LineEndingConversion", LineEndingConversion.Ask); + public LineEndingConversion LineEndingConversion { + get { + return lineEndingConversion; + } + set { + if (lineEndingConversion.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + #endregion + + PropertyWrapper<bool> useViModes = new PropertyWrapper<bool> ("UseViModes", true); + public bool UseViModes { + get { + return useViModes; + } + set { + if (useViModes.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> onTheFlyFormatting = new PropertyWrapper<bool> ("OnTheFlyFormatting", true); + public bool OnTheFlyFormatting { + get { + return onTheFlyFormatting; + } + set { + if (onTheFlyFormatting.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + #region ITextEditorOptions + string defaultEolMarker = Environment.NewLine; + public string DefaultEolMarker { + get { + return defaultEolMarker; + } + set { + if (defaultEolMarker != value) { + defaultEolMarker = value; + OnChanged (EventArgs.Empty); + } + } + } + + PropertyWrapper<ControlLeftRightMode> controlLeftRightMode; + [Obsolete("Use WordNavigationStyle")] + public ControlLeftRightMode ControlLeftRightMode { + get { + return controlLeftRightMode; + } + set { + if (controlLeftRightMode.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<WordNavigationStyle> wordNavigationStyle; + public WordNavigationStyle WordNavigationStyle { + get { + return wordNavigationStyle; + } + set { + if (wordNavigationStyle.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + public WordFindStrategy WordFindStrategy { + get { + if (useViModes) { + return WordFindStrategy.Vim; + } + switch (WordNavigationStyle) { + case WordNavigationStyle.Windows: + return WordFindStrategy.SharpDevelop; + default: + return WordFindStrategy.Emacs; + } + } + set { + throw new System.NotImplementedException (); + } + } + + bool allowTabsAfterNonTabs = true; + public bool AllowTabsAfterNonTabs { + get { + return allowTabsAfterNonTabs; + } + set { + if (allowTabsAfterNonTabs != value) { + PropertyService.Set ("AllowTabsAfterNonTabs", value); + allowTabsAfterNonTabs = value; + OnChanged (EventArgs.Empty); + } + } + } + + bool tabsToSpaces = false; + public bool TabsToSpaces { + get { + return tabsToSpaces; + } + set { + if (tabsToSpaces != value) { + PropertyService.Set ("TabsToSpaces", value); + tabsToSpaces = value; + OnChanged (EventArgs.Empty); + } + } + } + + int indentationSize = 4; + public int IndentationSize { + get { + return indentationSize; + } + set { + if (indentationSize != value) { + PropertyService.Set ("TabIndent", value); + indentationSize = value; + OnChanged (EventArgs.Empty); + } + } + } + + + public string IndentationString { + get { + return TabsToSpaces ? new string (' ', this.TabSize) : "\t"; + } + } + + public int TabSize { + get { + return IndentationSize; + } + set { + IndentationSize = value; + } + } + + + bool removeTrailingWhitespaces = true; + public bool RemoveTrailingWhitespaces { + get { + return removeTrailingWhitespaces; + } + set { + if (removeTrailingWhitespaces != value) { + PropertyService.Set ("RemoveTrailingWhitespaces", value); + OnChanged (EventArgs.Empty); + removeTrailingWhitespaces = value; + } + } + } + + PropertyWrapper<bool> showLineNumberMargin = new PropertyWrapper<bool> ("ShowLineNumberMargin", true); + public bool ShowLineNumberMargin { + get { + return showLineNumberMargin; + } + set { + if (showLineNumberMargin.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> showFoldMargin = new PropertyWrapper<bool> ("ShowFoldMargin", false); + public bool ShowFoldMargin { + get { + return showFoldMargin; + } + set { + if (showFoldMargin.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + bool showIconMargin = true; + public bool ShowIconMargin { + get { + return showIconMargin; + } + set { + if (showIconMargin != value) { + PropertyService.Set ("ShowIconMargin", value); + showIconMargin = value; + OnChanged (EventArgs.Empty); + } + } + } + + PropertyWrapper<bool> highlightCaretLine = new PropertyWrapper<bool> ("HighlightCaretLine", false); + public bool HighlightCaretLine { + get { + return highlightCaretLine; + } + set { + if (highlightCaretLine.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> enableSyntaxHighlighting = new PropertyWrapper<bool> ("EnableSyntaxHighlighting", true); + public bool EnableSyntaxHighlighting { + get { + return enableSyntaxHighlighting; + } + set { + if (enableSyntaxHighlighting.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> highlightMatchingBracket = new PropertyWrapper<bool> ("HighlightMatchingBracket", true); + public bool HighlightMatchingBracket { + get { + return highlightMatchingBracket; + } + set { + if (highlightMatchingBracket.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + int rulerColumn = 80; + + public int RulerColumn { + get { + return rulerColumn; + } + set { + if (rulerColumn != value) { + PropertyService.Set ("RulerColumn", value); + rulerColumn = value; + OnChanged (EventArgs.Empty); + } + } + } + + PropertyWrapper<bool> showRuler = new PropertyWrapper<bool> ("ShowRuler", true); + public bool ShowRuler { + get { + return showRuler; + } + set { + if (showRuler.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> enableAnimations = new PropertyWrapper<bool> ("EnableAnimations", true); + public bool EnableAnimations { + get { + return enableAnimations; + } + set { + if (enableAnimations.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> drawIndentationMarkers = new PropertyWrapper<bool> ("DrawIndentationMarkers", false); + public bool DrawIndentationMarkers { + get { + return drawIndentationMarkers; + } + set { + if (drawIndentationMarkers.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> wrapLines = new PropertyWrapper<bool> ("WrapLines", false); + public bool WrapLines { + get { + return wrapLines; + } + set { + if (wrapLines.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> enableQuickDiff = new PropertyWrapper<bool> ("EnableQuickDiff", false); + public bool EnableQuickDiff { + get { + return enableQuickDiff; + } + set { + if (enableQuickDiff.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + public string FontName { + get { + return FontService.FilterFontName (FontService.GetUnderlyingFontName ("Editor")); + } + set { + throw new InvalidOperationException ("Set font through font service"); + } + } + + public string GutterFontName { + get { + return FontService.FilterFontName (FontService.GetUnderlyingFontName ("Editor")); + } + set { + throw new InvalidOperationException ("Set font through font service"); + } + } + + PropertyWrapper<string> colorScheme = new PropertyWrapper<string> ("ColorScheme", "Default"); + public string ColorScheme { + get { + return colorScheme; + } + set { + if (colorScheme.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<bool> generateFormattingUndoStep = new PropertyWrapper<bool> ("GenerateFormattingUndoStep", false); + public bool GenerateFormattingUndoStep { + get { + return generateFormattingUndoStep; + } + set { + if (generateFormattingUndoStep.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + bool overrideDocumentEolMarker = false; + public bool OverrideDocumentEolMarker { + get { + return overrideDocumentEolMarker; + } + set { + if (overrideDocumentEolMarker != value) { + overrideDocumentEolMarker = value; + OnChanged (EventArgs.Empty); + } + } + } + + PropertyWrapper<ShowWhitespaces> showWhitespaces = new PropertyWrapper<ShowWhitespaces> ("ShowWhitespaces", ShowWhitespaces.Never); + public ShowWhitespaces ShowWhitespaces { + get { + return showWhitespaces; + } + set { + if (showWhitespaces.Set (value)) + OnChanged (EventArgs.Empty); + } + } + + PropertyWrapper<IncludeWhitespaces> includeWhitespaces = new PropertyWrapper<IncludeWhitespaces> ("IncludeWhitespaces", IncludeWhitespaces.All); + public IncludeWhitespaces IncludeWhitespaces { + get { + return includeWhitespaces; + } + set { + if (includeWhitespaces.Set (value)) + OnChanged (EventArgs.Empty); + } + } + #endregion + + public void Dispose () + { + FontService.RemoveCallback (UpdateFont); + } + + protected void OnChanged (EventArgs args) + { + if (Changed != null) + Changed (null, args); + } + + public event EventHandler Changed; + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentContext.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentContext.cs new file mode 100644 index 0000000000..58cac26e26 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentContext.cs @@ -0,0 +1,158 @@ +// +// DocumentContext.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Projects; +using ICSharpCode.NRefactory.TypeSystem; +using MonoDevelop.Ide.TypeSystem; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.CodeAnalysis.Options; +using System.Collections.Generic; +using MonoDevelop.Ide.Editor.Projection; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// A document context puts a textual document in a semantic context inside a project and gives access + /// to the parse information of the textual document. + /// </summary> + public abstract class DocumentContext + { + /// <summary> + /// The name of the document. It's the file name for files on disc. + /// For unsaved files that name is different. + /// </summary> + public abstract string Name + { + get; + } + + /// <summary> + /// Project != null + /// </summary> + public virtual bool HasProject + { + get { return Project != null; } + } + + /// <summary> + /// Gets the project this context is in. + /// </summary> + public abstract Project Project + { + get; + } + + public Microsoft.CodeAnalysis.Workspace RoslynWorkspace + { + get; + protected set; + } + + /// <summary> + /// Returns the roslyn document for this document. This may return <c>null</c> if it's no compileable document. + /// Even if it's a C# file. + /// </summary> + public abstract Microsoft.CodeAnalysis.Document AnalysisDocument + { + get; + } + + /// <summary> + /// The parsed document. Contains all syntax information about the text. + /// </summary> + public abstract ParsedDocument ParsedDocument + { + get; + } + + /// <summary> + /// If true, the document is part of the ProjectContent. + /// </summary> + public virtual bool IsCompileableInProject + { + get + { + return true; + } + } + + public virtual T GetContent<T>() where T : class + { + var t = this as T; + if (t != null) + return t; + return null; + } + + public virtual IEnumerable<T> GetContents<T>() where T : class + { + var t = this as T; + if (t != null) + yield return t; + } + + /// <summary> + /// This is called after the ParsedDocument updated. + /// </summary> + public event EventHandler DocumentParsed; + + protected void OnDocumentParsed (EventArgs e) + { + var handler = DocumentParsed; + if (handler != null) + handler (this, e); + } + + public abstract void AttachToProject (Project project); + + /// <summary> + /// Forces a reparse of the document. This call doesn't block the ui thread. + /// The next call to ParsedDocument will give always the current parsed document but may block the UI thread. + /// </summary> + public abstract void ReparseDocument (); + + public abstract OptionSet GetOptionSet (); + + public abstract ParsedDocument UpdateParseDocument (); + + // TODO: IMO that needs to be handled differently (this is atm only used in the ASP.NET binding) + // Maybe using the file service. Files can be changed/saved w/o beeing opened. + public event EventHandler Saved; + + protected virtual void OnSaved (EventArgs e) + { + var handler = Saved; + if (handler != null) + handler (this, e); + } + + internal virtual Task<IReadOnlyList<Editor.Projection.Projection>> GetPartialProjectionsAsync (CancellationToken cancellationToken = default(CancellationToken)) + { + return null; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentLocation.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentLocation.cs new file mode 100644 index 0000000000..7e95e9df8e --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentLocation.cs @@ -0,0 +1,241 @@ +// +// DocumentLocation.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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.ComponentModel; +using System.Globalization; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// A line/column position. + /// Text editor lines/columns are counted started from one. + /// </summary> + /// <remarks> + /// The document provides the methods <see cref="Editor.IDocument.GetLocation"/> and + /// <see cref="Editor.IDocument.GetOffset(TextLocation)"/> to convert between offsets and TextLocations. + /// </remarks> + [Serializable] + [TypeConverter(typeof(DocumentLocationConverter))] + public struct DocumentLocation : IComparable<DocumentLocation>, IEquatable<DocumentLocation> + { + /// <summary> + /// Represents no text location (0, 0). + /// </summary> + public static readonly DocumentLocation Empty = new DocumentLocation(0, 0); + + /// <summary> + /// Constant of the minimum line. + /// </summary> + public const int MinLine = 1; + + /// <summary> + /// Constant of the minimum column. + /// </summary> + public const int MinColumn = 1; + + /// <summary> + /// Creates a TextLocation instance. + /// </summary> + public DocumentLocation(int line, int column) + { + this.line = line; + this.column = column; + } + + int column, line; + + /// <summary> + /// Gets the line number. + /// </summary> + public int Line { + get { return line; } + } + + /// <summary> + /// Gets the column number. + /// </summary> + public int Column { + get { return column; } + } + + /// <summary> + /// Gets whether the TextLocation instance is empty. + /// </summary> + public bool IsEmpty { + get { + return column < MinLine && line < MinColumn; + } + } + + /// <summary> + /// Gets a string representation for debugging purposes. + /// </summary> + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "(Line {1}, Col {0})", this.column, this.line); + } + + /// <summary> + /// Gets a hash code. + /// </summary> + public override int GetHashCode() + { + return unchecked (column << 20 ^ line); + } + + /// <summary> + /// Equality test. + /// </summary> + public override bool Equals(object obj) + { + if (!(obj is DocumentLocation)) return false; + return (DocumentLocation)obj == this; + } + + /// <summary> + /// Equality test. + /// </summary> + public bool Equals(DocumentLocation other) + { + return this == other; + } + + /// <summary> + /// Equality test. + /// </summary> + public static bool operator ==(DocumentLocation left, DocumentLocation right) + { + return left.column == right.column && left.line == right.line; + } + + /// <summary> + /// Inequality test. + /// </summary> + public static bool operator !=(DocumentLocation left, DocumentLocation right) + { + return left.column != right.column || left.line != right.line; + } + + /// <summary> + /// Compares two text locations. + /// </summary> + public static bool operator <(DocumentLocation left, DocumentLocation right) + { + if (left.line < right.line) + return true; + else if (left.line == right.line) + return left.column < right.column; + else + return false; + } + + /// <summary> + /// Compares two text locations. + /// </summary> + public static bool operator >(DocumentLocation left, DocumentLocation right) + { + if (left.line > right.line) + return true; + else if (left.line == right.line) + return left.column > right.column; + else + return false; + } + + /// <summary> + /// Compares two text locations. + /// </summary> + public static bool operator <=(DocumentLocation left, DocumentLocation right) + { + return !(left > right); + } + + /// <summary> + /// Compares two text locations. + /// </summary> + public static bool operator >=(DocumentLocation left, DocumentLocation right) + { + return !(left < right); + } + + public static implicit operator Microsoft.CodeAnalysis.Text.LinePosition (DocumentLocation location) + { + return new Microsoft.CodeAnalysis.Text.LinePosition (location.Line - 1, location.Column - 1); + } + + public static implicit operator DocumentLocation(Microsoft.CodeAnalysis.Text.LinePosition location) + { + return new DocumentLocation (location.Line + 1, location.Character + 1); + } + + /// <summary> + /// Compares two text locations. + /// </summary> + public int CompareTo(DocumentLocation other) + { + if (this == other) + return 0; + if (this < other) + return -1; + else + return 1; + } + } + + public class DocumentLocationConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(DocumentLocation) || base.CanConvertTo(context, destinationType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string) { + string[] parts = ((string)value).Split(';', ','); + if (parts.Length == 2) { + return new DocumentLocation(int.Parse(parts[0]), int.Parse(parts[1])); + } + } + return base.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value is DocumentLocation) { + var loc = (DocumentLocation)value; + return loc.Line + ";" + loc.Column; + } + return base.ConvertTo(context, culture, value, destinationType); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentRegion.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentRegion.cs new file mode 100644 index 0000000000..ff5863936b --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/DocumentRegion.cs @@ -0,0 +1,181 @@ +// +// DocumentRegion.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using Microsoft.CodeAnalysis; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// An (Begin, End) pair representing a document span. It's a TextSegment working with lines & columns instead of offsets. + /// </summary> + public struct DocumentRegion : IEquatable<DocumentRegion> + { + public static readonly DocumentRegion Empty = new DocumentRegion (0, 0, 0, 0); + + /// <summary> + /// Gets a value indicating whether this DocumentRegion is empty. + /// </summary> + public bool IsEmpty { + get { + return beginLine < 1; + } + } + + readonly int beginLine; + public int BeginLine { + get { + return beginLine; + } + } + + readonly int beginColumn; + public int BeginColumn { + get { + return beginColumn; + } + } + + readonly int endLine; + public int EndLine { + get { + return endLine; + } + } + + readonly int endColumn; + public int EndColumn { + get { + return endColumn; + } + } + + public DocumentLocation Begin { + get { + return new DocumentLocation (BeginLine, BeginColumn); + } + } + + public DocumentLocation End { + get { + return new DocumentLocation (EndLine, EndColumn); + } + } + + public DocumentRegion (int beginLine, int beginColumn, int endLine, int endColumn) + { + this.beginLine = beginLine; + this.beginColumn = beginColumn; + this.endLine = endLine; + this.endColumn = endColumn; + } + + public DocumentRegion (DocumentLocation begin, DocumentLocation end) + { + beginLine = begin.Line; + beginColumn = begin.Column; + endLine = end.Line; + endColumn = end.Column; + } + + public bool Contains (DocumentLocation location) + { + return Begin <= location && location < End; + } + + public bool Contains (int line, int column) + { + return Contains (new DocumentLocation (line, column)); + } + + public bool IsInside (DocumentLocation location) + { + return Begin <= location && location <= End; + } + + public bool IsInside (int line, int column) + { + return IsInside (new DocumentLocation (line, column)); + } + + public override bool Equals (object obj) + { + return obj is DocumentRegion && Equals ((DocumentRegion)obj); + } + + public override int GetHashCode () + { + return unchecked (Begin.GetHashCode () ^ End.GetHashCode ()); + } + + public bool Equals (DocumentRegion other) + { + return Begin == other.Begin && End == other.End; + } + + public static bool operator == (DocumentRegion left, DocumentRegion right) + { + return left.Equals(right); + } + + public static bool operator != (DocumentRegion left, DocumentRegion right) + { + return !left.Equals(right); + } + + public TextSegment GetSegment (TextEditor document) + { + if (document == null) + throw new ArgumentNullException ("document"); + var begin = document.LocationToOffset (Begin); + var end = document.LocationToOffset (End); + return new TextSegment (begin, end - begin); + } + + public static implicit operator Microsoft.CodeAnalysis.Text.LinePositionSpan (DocumentRegion location) + { + return new Microsoft.CodeAnalysis.Text.LinePositionSpan (location.Begin, location.End); + } + + public static implicit operator DocumentRegion(Microsoft.CodeAnalysis.Text.LinePositionSpan location) + { + return new DocumentRegion (location.Start, location.End); + } + + + public static implicit operator DocumentRegion(FileLinePositionSpan location) + { + return new DocumentRegion (location.StartLinePosition, location.EndLinePosition); + } + + + public override string ToString () + { + return string.Format ("[DocumentRegion: Begin={0}, End={1}]", Begin, End); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/EditActions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/EditActions.cs new file mode 100644 index 0000000000..a72688c0e2 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/EditActions.cs @@ -0,0 +1,516 @@ +// +// EditActions.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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.Text; +using MonoDevelop.Core.Text; +using MonoDevelop.Ide.Editor.Util; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// This class contains some common actions for the text editor. + /// </summary> + public static class EditActions + { + public static void MoveCaretDown (TextEditor editor) + { + editor.EditorActionHost.MoveCaretDown (); + } + + public static void MoveCaretUp (TextEditor editor) + { + editor.EditorActionHost.MoveCaretUp (); + } + + public static void MoveCaretRight (TextEditor editor) + { + editor.EditorActionHost.MoveCaretRight (); + } + + public static void MoveCaretLeft (TextEditor editor) + { + editor.EditorActionHost.MoveCaretLeft (); + } + + public static void MoveCaretToLineEnd (TextEditor editor) + { + editor.EditorActionHost.MoveCaretToLineEnd (); + } + + public static void MoveCaretToLineStart (TextEditor editor) + { + editor.EditorActionHost.MoveCaretToLineStart (); + } + + public static void MoveCaretToDocumentStart (TextEditor editor) + { + editor.EditorActionHost.MoveCaretToDocumentStart (); + } + + public static void MoveCaretToDocumentEnd (TextEditor editor) + { + editor.EditorActionHost.MoveCaretToDocumentEnd (); + } + + public static void Backspace (TextEditor editor) + { + editor.EditorActionHost.Backspace (); + } + + public static void Delete (TextEditor editor) + { + editor.EditorActionHost.Delete (); + } + + public static void ClipboardCopy (TextEditor editor) + { + editor.EditorActionHost.ClipboardCopy (); + } + + public static void ClipboardCut (TextEditor editor) + { + editor.EditorActionHost.ClipboardCut (); + } + + public static void ClipboardPaste (TextEditor editor) + { + editor.EditorActionHost.ClipboardPaste (); + } + + + + public static void SelectAll (TextEditor editor) + { + editor.EditorActionHost.SelectAll (); + } + + public static void NewLine (TextEditor editor) + { + editor.EditorActionHost.NewLine (); + } + + public static void PageUp (TextEditor textEditor) + { + textEditor.EditorActionHost.PageUp (); + } + + public static void PageDown (TextEditor textEditor) + { + textEditor.EditorActionHost.PageDown (); + } + + public static void Undo (TextEditor editor) + { + editor.EditorActionHost.Undo (); + } + + public static void Redo (TextEditor editor) + { + editor.EditorActionHost.Redo (); + } + + public static void DeleteCurrentLine (TextEditor textEditor) + { + textEditor.EditorActionHost.DeleteCurrentLine (); + } + + public static void DeleteCurrentLineToEnd (TextEditor textEditor) + { + textEditor.EditorActionHost.DeleteCurrentLineToEnd (); + } + + public static void ScrollLineUp (TextEditor textEditor) + { + textEditor.EditorActionHost.ScrollLineUp (); + } + + public static void ScrollLineDown (TextEditor textEditor) + { + textEditor.EditorActionHost.ScrollLineDown (); + } + + public static void ScrollPageUp (TextEditor textEditor) + { + textEditor.EditorActionHost.ScrollPageUp (); + } + + public static void ScrollPageDown (TextEditor textEditor) + { + textEditor.EditorActionHost.ScrollPageDown (); + } + + public static void GotoMatchingBrace (TextEditor textEditor) + { + var offset = SimpleBracketMatcher.GetMatchingBracketOffset (textEditor, textEditor.CaretOffset); + if (offset > 0) + textEditor.CaretOffset = offset; + } + + public static void MovePrevWord (TextEditor textEditor) + { + textEditor.EditorActionHost.MovePrevWord (); + } + + public static void MoveNextWord (TextEditor textEditor) + { + textEditor.EditorActionHost.MoveNextWord (); + } + + public static void MovePrevSubWord (TextEditor textEditor) + { + textEditor.EditorActionHost.MovePrevSubWord (); + } + + public static void MoveNextSubWord (TextEditor textEditor) + { + textEditor.EditorActionHost.MoveNextSubWord (); + } + + + public static void TransposeCharacters (TextEditor textEditor) + { + // Code from Mono.TextEditor.MiscActions.TransposeCharacters + if (textEditor.CaretOffset == 0) + return; + var line = textEditor.GetLine (textEditor.CaretLine); + if (line == null) + return; + using (var undoGroup = textEditor.OpenUndoGroup ()) { + int transposeOffset = textEditor.CaretOffset - 1; + char ch; + if (textEditor.CaretColumn == 0) { + var lineAbove = textEditor.GetLine (textEditor.CaretLine - 1); + if (lineAbove.Length == 0 && line.Length == 0) + return; + + if (line.Length != 0) { + ch = textEditor.GetCharAt (textEditor.CaretOffset); + textEditor.RemoveText (textEditor.CaretOffset, 1); + textEditor.InsertText (lineAbove.Offset + lineAbove.Length, ch.ToString ()); + return; + } + + int lastCharOffset = lineAbove.Offset + lineAbove.Length - 1; + ch = textEditor.GetCharAt (lastCharOffset); + textEditor.RemoveText (lastCharOffset, 1); + textEditor.InsertAtCaret (ch.ToString ()); + return; + } + + int offset = textEditor.CaretOffset; + if (textEditor.CaretColumn >= line.Length + 1) { + offset = line.Offset + line.Length - 1; + transposeOffset = offset - 1; + // case one char in line: + if (transposeOffset < line.Offset) { + var lineAbove = textEditor.GetLine (textEditor.CaretLine - 1); + transposeOffset = lineAbove.Offset + lineAbove.Length; + ch = textEditor.GetCharAt (offset); + textEditor.RemoveText (offset, 1); + textEditor.InsertText (transposeOffset, ch.ToString ()); + textEditor.CaretOffset = line.Offset; + return; + } + } + + ch = textEditor.GetCharAt (offset); + textEditor.ReplaceText (offset, 1, textEditor.GetCharAt (transposeOffset).ToString ()); + textEditor.ReplaceText (transposeOffset, 1, ch.ToString ()); + if (textEditor.CaretColumn < line.Length + 1) + textEditor.CaretOffset = offset + 1; + } + } + + public static void DuplicateCurrentLine (TextEditor textEditor) + { + // Code from Mono.TextEditor.MiscActions.DuplicateLine + using (var undoGroup = textEditor.OpenUndoGroup ()) { + if (textEditor.IsSomethingSelected) { + var selectedText = textEditor.SelectedText; + textEditor.ClearSelection (); + textEditor.InsertAtCaret (selectedText); + } else { + var line = textEditor.GetLine (textEditor.CaretLine); + if (line == null) + return; + textEditor.InsertText (line.Offset, textEditor.GetTextAt (line.SegmentIncludingDelimiter)); + } + } + } + + public static void JoinLines (TextEditor textEditor) + { + textEditor.EditorActionHost.JoinLines (); + } + + public static void RecenterEditor (TextEditor textEditor) + { + textEditor.EditorActionHost.RecenterEditor (); + } + + public static void StartCaretPulseAnimation (TextEditor textEditor) + { + textEditor.EditorActionHost.StartCaretPulseAnimation (); + } + + public static void DeleteNextSubword (TextEditor textEditor) + { + textEditor.EditorActionHost.DeleteNextSubword (); + } + + public static void DeletePreviousSubword (TextEditor textEditor) + { + textEditor.EditorActionHost.DeletePreviousSubword (); + } + + public static void DeleteNextWord (TextEditor textEditor) + { + textEditor.EditorActionHost.DeleteNextWord (); + } + + public static void DeletePreviousWord (TextEditor textEditor) + { + textEditor.EditorActionHost.DeletePreviousWord (); + } + + public static void InsertNewLinePreserveCaretPosition (TextEditor textEditor) + { + if (textEditor.IsReadOnly) + return; + using (var undoGroup = textEditor.OpenUndoGroup ()) { + var loc = textEditor.CaretLocation; + InsertNewLine (textEditor); + textEditor.CaretLocation = loc; + } + } + + public static void InsertNewLineAtEnd (TextEditor textEditor) + { + if (textEditor.IsReadOnly) + return; + using (var undoGroup = textEditor.OpenUndoGroup ()) { + MoveCaretToLineEnd (textEditor); + InsertNewLine (textEditor); + } + } + + public static void InsertNewLine (TextEditor textEditor) + { + textEditor.EditorActionHost.InsertNewLine (); + } + + public static void RemoveTab (TextEditor textEditor) + { + textEditor.EditorActionHost.RemoveTab (); + } + + public static void InsertTab (TextEditor textEditor) + { + textEditor.EditorActionHost.InsertTab (); + } + + public static void SwitchCaretMode (TextEditor textEditor) + { + textEditor.EditorActionHost.SwitchCaretMode (); + } + + public static void MoveBlockUp (TextEditor textEditor) + { + textEditor.EditorActionHost.MoveBlockUp (); + } + + public static void MoveBlockDown (TextEditor textEditor) + { + textEditor.EditorActionHost.MoveBlockDown (); + } + + public static void ToggleBlockSelectionMode (TextEditor textEditor) + { + textEditor.EditorActionHost.ToggleBlockSelectionMode (); + } + + public static void IndentSelection (TextEditor editor) + { + editor.EditorActionHost.IndentSelection (); + } + + public static void UnIndentSelection (TextEditor editor) + { + editor.EditorActionHost.UnIndentSelection (); + } + + #region SelectionActions + + static void RunSelectionAction (TextEditor textEditor, Action<TextEditor> action) + { + using (var undo = textEditor.OpenUndoGroup ()) { + var anchor = textEditor.IsSomethingSelected ? textEditor.SelectionAnchorOffset : textEditor.CaretOffset; + action (textEditor); + textEditor.SetSelection (anchor, textEditor.CaretOffset); + } + } + + public static void SelectionMoveToDocumentEnd (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretToDocumentEnd); + } + + public static void ExpandSelectionToLine (TextEditor textEditor) + { + // from Mono.TextEditor.SelectionActions.ExpandSelectionToLine + using (var undoGroup = textEditor.OpenUndoGroup ()) { + var curLineSegment = textEditor.GetLine (textEditor.CaretLine).SegmentIncludingDelimiter; + var range = textEditor.SelectionRange; + var selection = TextSegment.FromBounds ( + System.Math.Min (range.Offset, curLineSegment.Offset), + System.Math.Max (range.EndOffset, curLineSegment.EndOffset)); + textEditor.CaretOffset = selection.EndOffset; + textEditor.SelectionRange = selection; + } + } + + public static void SelectionMoveToDocumentStart (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretToDocumentStart); + } + + public static void SelectionMoveLineEnd (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretToLineEnd); + } + + public static void SelectionMoveLineStart (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretToLineStart); + } + + public static void SelectionMoveDown (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretDown); + } + + public static void SelectionMoveUp (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretUp); + } + + public static void SelectionPageUp (TextEditor textEditor) + { + RunSelectionAction (textEditor, PageUp); + } + + public static void SelectionPageDown (TextEditor textEditor) + { + RunSelectionAction (textEditor, PageDown); + } + + public static void SelectionMovePrevSubWord (TextEditor textEditor) + { + RunSelectionAction (textEditor, MovePrevSubWord); + } + + public static void SelectionMoveNextSubWord (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveNextSubWord); + } + + public static void SelectionMoveLeft (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretLeft); + } + + public static void SelectionMoveRight (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveCaretRight); + } + + public static void SelectionMovePrevWord (TextEditor textEditor) + { + RunSelectionAction (textEditor, MovePrevWord); + } + + public static void SelectionMoveNextWord (TextEditor textEditor) + { + RunSelectionAction (textEditor, MoveNextWord); + } + + public static void SortSelectedLines (TextEditor textEditor) + { + var selectionRegion = textEditor.SelectionRegion; + var start = selectionRegion.Begin; + var end = selectionRegion.End; + var caret = textEditor.CaretLocation; + + int startLine = start.Line; + int endLine = end.Line; + if (startLine == endLine) + return; + + int length = 0; + var lines = new string[endLine - startLine + 1]; + for (int i = startLine; i <= endLine; i++) { + //get lines *with* line endings + var lineText = textEditor.GetLineText (i, true); + lines [i - startLine] = lineText; + length += lineText.Length; + } + + var linesUnsorted = new string[lines.Length]; + + Array.Sort (lines, StringComparer.Ordinal); + + bool changed = false; + for (int i = 0; i <= lines.Length; i++) { + //can't simply use reference comparison as Array.Sort is not stable + if (string.Equals (lines [i], linesUnsorted [i], StringComparison.Ordinal)) { + continue; + } + changed = true; + break; + } + if (!changed) + return; + + + var sb = new StringBuilder (); + for (int i = 0; i < lines.Length; i++) { + sb.Append (lines [i]); + } + + var startOffset = textEditor.LocationToOffset (startLine, 1); + textEditor.ReplaceText (startOffset, length, sb.ToString ()); + + textEditor.CaretLocation = LimitColumn (textEditor, caret); + textEditor.SetSelection (LimitColumn (textEditor, start), LimitColumn (textEditor, end)); + } + + static DocumentLocation LimitColumn (TextEditor data, DocumentLocation loc) + { + return new DocumentLocation (loc.Line, System.Math.Min (loc.Column, data.GetLine (loc.Line).Length + 1)); + } + #endregion + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/FileSettingsStore.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/FileSettingsStore.cs new file mode 100644 index 0000000000..bec7ddd1b6 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/FileSettingsStore.cs @@ -0,0 +1,73 @@ +// +// FileSettingsStore.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + public static class FileSettingsStore + { + public class Settings + { + public int CaretOffset { get; set; } + + public double vAdjustment { get; set; } + + public double hAdjustment { get; set; } + + public Dictionary<int, bool> FoldingStates = new Dictionary<int, bool> (); + + public override string ToString () + { + return string.Format ("[Settings: CaretOffset={0}, vAdjustment={1}, hAdjustment={2}]", CaretOffset, vAdjustment, hAdjustment); + } + } + + static Dictionary<string, Settings> settingStore = new Dictionary<string, Settings> (); + + public static bool TryGetValue (string contentName, out Settings settings) + { + if (contentName == null) + throw new ArgumentNullException ("contentName"); + return settingStore.TryGetValue (contentName, out settings); + } + + public static void Store (string contentName, Settings settings) + { + if (contentName == null) + throw new ArgumentNullException ("contentName"); + if (settings == null) + throw new ArgumentNullException ("settings"); + settingStore [contentName] = settings; + } + + public static void Remove (string fileName) + { + settingStore.Remove (fileName); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/FoldSegmentFactory.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/FoldSegmentFactory.cs new file mode 100644 index 0000000000..468defb00f --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/FoldSegmentFactory.cs @@ -0,0 +1,86 @@ +// +// FoldSegmentFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; + +namespace MonoDevelop.Ide.Editor +{ + public static class FoldSegmentFactory + { + public static IFoldSegment CreateFoldSegment (TextEditor editor, int offset, int length, bool isCollapsed = false, string collapsedText = "...", FoldingType foldingType = FoldingType.Unknown) + { + if (editor == null) + throw new ArgumentNullException ("editor"); + var result = editor.CreateFoldSegment (offset, length, isCollapsed); + result.CollapsedText = collapsedText; + result.FoldingType = foldingType; + return result; + } + + public static IFoldSegment CreateFoldSegment (TextEditor editor, ISegment segment, bool isCollapsed = false, string collapsedText = "...", FoldingType foldingType = FoldingType.Unknown) + { + if (editor == null) + throw new ArgumentNullException ("editor"); + if (segment == null) + throw new ArgumentNullException ("segment"); + return CreateFoldSegment (editor, segment.Offset, segment.Length, isCollapsed, collapsedText, foldingType); + } + + public static IFoldSegment CreateFoldSegment (TextEditor editor, int offset, int length, string collapsedText = "...", FoldingType foldingType = FoldingType.Unknown) + { + if (editor == null) + throw new ArgumentNullException ("editor"); + return CreateFoldSegment (editor, offset, length, false, collapsedText, foldingType); + } + + public static IFoldSegment CreateFoldSegment (TextEditor editor, ISegment segment, string collapsedText = "...", FoldingType foldingType = FoldingType.Unknown) + { + if (editor == null) + throw new ArgumentNullException ("editor"); + if (segment == null) + throw new ArgumentNullException ("segment"); + return CreateFoldSegment (editor, segment.Offset, segment.Length, false, collapsedText, foldingType); + } + + public static IFoldSegment CreateFoldSegment (TextEditor editor, int offset, int length, FoldingType foldingType = FoldingType.Unknown) + { + if (editor == null) + throw new ArgumentNullException ("editor"); + return CreateFoldSegment (editor, offset, length, false, "...", foldingType); + } + + public static IFoldSegment CreateFoldSegment (TextEditor editor, ISegment segment, FoldingType foldingType = FoldingType.Unknown) + { + if (editor == null) + throw new ArgumentNullException ("editor"); + if (segment == null) + throw new ArgumentNullException ("segment"); + return CreateFoldSegment (editor, segment.Offset, segment.Length, false, "...", foldingType); + } + + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IDocumentLine.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IDocumentLine.cs new file mode 100644 index 0000000000..7135d74dad --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IDocumentLine.cs @@ -0,0 +1,118 @@ +// +// IDocumentLine.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using System.Text; +using MonoDevelop.Ide.Editor.Highlighting; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// A line inside a <see cref="ITextDocument"/>. + /// </summary> + public interface IDocumentLine : ISegment + { + /// <summary> + /// Gets the length of the line including the line delimiter. + /// </summary> + int LengthIncludingDelimiter { + get; + } + + int EndOffsetIncludingDelimiter { + get; + } + + /// <summary> + /// Gets the text segment of the line including the line delimiter. + /// </summary> + ISegment SegmentIncludingDelimiter { + get; + } + + /// <summary> + /// Gets the unicode newline for this line. Returns UnicodeNewline.Unknown for no new line (in the last line of the document) + /// </summary>EndOffsetIncludingDelimiterEndOffsetIncludingDelimiter + UnicodeNewline UnicodeNewline { + get; + } + + /// <summary> + /// Gets the length of the line terminator. + /// Returns 1 or 2; or 0 at the end of the document. + /// </summary> + int DelimiterLength { get; } + + /// <summary> + /// Gets the number of this line. + /// The first line has the number 1. + /// </summary> + int LineNumber { get; } + + /// <summary> + /// Gets the previous line. Returns null if this is the first line in the document. + /// </summary> + IDocumentLine PreviousLine { get; } + + /// <summary> + /// Gets the next line. Returns null if this is the last line in the document. + /// </summary> + IDocumentLine NextLine { get; } + + /// <summary> + /// Gets whether the line was deleted. + /// </summary> + bool IsDeleted { get; } + } + + public static class DocumentLineExt + { + /// <summary> + /// This method gets the line indentation. + /// </summary> + /// <param name = "line"></param> + /// <param name="doc"> + /// The <see cref="IReadonlyTextDocument"/> the line belongs to. + /// </param> + /// <returns> + /// The indentation of the line (all whitespace chars up to the first non ws char). + /// </returns> + public static string GetIndentation (this IDocumentLine line, IReadonlyTextDocument doc) + { + var result = new StringBuilder (); + int offset = line.Offset; + int max = Math.Min (offset + line.LengthIncludingDelimiter, doc.Length); + for (int i = offset; i < max; i++) { + char ch = doc.GetCharAt (i); + if (ch != ' ' && ch != '\t') + break; + result.Append (ch); + } + return result.ToString (); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IFoldSegment.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IFoldSegment.cs new file mode 100644 index 0000000000..d1708faf36 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IFoldSegment.cs @@ -0,0 +1,74 @@ +// +// IFoldSegment.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using System.Web.UI.WebControls; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// Represents the origin for a fold segment + /// </summary> + public enum FoldingType { + Unknown, + Region, + TypeDefinition, + TypeMember, + Comment + } + + /// <summary> + /// A fold segment represents a collapsible region inside the text editor. + /// </summary> + public interface IFoldSegment : ISegment + { + /// <summary> + /// Gets or sets a value indicating whether this fold segment is collapsed. + /// </summary> + bool IsCollapsed { + get; + set; + } + + /// <summary> + /// Gets or sets the collapsed text. This is displayed when the folding is collapsed instead of the collapsed region. + /// </summary> + string CollapsedText { + get; + set; + } + + /// <summary> + /// Gets or sets the type of the folding. This type gives some info about where this folding segment + /// originates from. + /// </summary> + FoldingType FoldingType { + get; + set; + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IReadonlyTextDocument.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IReadonlyTextDocument.cs new file mode 100644 index 0000000000..616e2fd1d1 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IReadonlyTextDocument.cs @@ -0,0 +1,246 @@ +// +// IReadonlyTextDocument.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using System.Collections.Generic; +using System.Text; +using MonoDevelop.Core; +using MonoDevelop.Ide.Editor.Util; + +namespace MonoDevelop.Ide.Editor +{ + public interface IReadonlyTextDocument : ITextSource + { + bool IsReadOnly { get; } + + FilePath FileName { get; } + + string MimeType { get; } + + /// <summary> + /// Gets the number of lines in the document. + /// </summary> + int LineCount { get; } + + int LocationToOffset (int line, int column); + + DocumentLocation OffsetToLocation (int offset); + + IDocumentLine GetLine (int lineNumber); + + IDocumentLine GetLineByOffset (int offset); + } + + public static class ReadonlyTextDocumentExtensions + { + /// <summary> + /// Retrieves the text for a portion of the document. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> + public static string GetTextAt(this IReadonlyTextDocument source, ISegment segment) + { + if (source == null) + throw new ArgumentNullException ("source"); + return source.GetTextAt (segment.Offset, segment.Length); + } + + public static IEnumerable<IDocumentLine> GetLines (this IReadonlyTextDocument document) + { + if (document == null) + throw new ArgumentNullException ("document"); + return document.GetLinesStartingAt (1); + } + + public static IEnumerable<IDocumentLine> GetLinesBetween (this IReadonlyTextDocument document, int startLine, int endLine) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (startLine < 1 || startLine > document.LineCount) + throw new ArgumentOutOfRangeException ("startLine", startLine, string.Format ("value should be between 1 and {0}", document.LineCount)); + if (endLine < 1 || endLine > document.LineCount) + throw new ArgumentOutOfRangeException ("endLine", endLine, string.Format ("value should be between 1 and {0}", document.LineCount)); + + var curLine = document.GetLine (startLine); + int count = endLine - startLine; + while (curLine != null && count --> 0) { + yield return curLine; + curLine = curLine.NextLine; + } + } + + public static IEnumerable<IDocumentLine> GetLinesStartingAt (this IReadonlyTextDocument document, int startLine) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (startLine < 1 || startLine > document.LineCount) + throw new ArgumentOutOfRangeException ("startLine", startLine, string.Format ("value should be between 1 and {0}", document.LineCount)); + var curLine = document.GetLine (startLine); + while (curLine != null) { + yield return curLine; + curLine = curLine.NextLine; + } + } + + public static IEnumerable<IDocumentLine> GetLinesReverseStartingAt (this IReadonlyTextDocument document, int startLine) + { + if (startLine < 1 || startLine > document.LineCount) + throw new ArgumentOutOfRangeException ("startLine", startLine, string.Format ("value should be between 1 and {0}", document.LineCount)); + var curLine = document.GetLine (startLine); + while (curLine != null) { + yield return curLine; + curLine = curLine.PreviousLine; + } + } + + public static string GetTextBetween (this IReadonlyTextDocument document, int startLine, int startColumn, int endLine, int endColumn) + { + if (document == null) + throw new ArgumentNullException ("document"); + return document.GetTextBetween (new DocumentLocation (startLine, startColumn), new DocumentLocation (endLine, endColumn)); + } + + public static string GetLineIndent (this IReadonlyTextDocument document, int lineNumber) + { + if (document == null) + throw new ArgumentNullException ("document"); + return document.GetLineIndent (document.GetLine (lineNumber)); + } + + public static string GetLineIndent (this IReadonlyTextDocument document, IDocumentLine segment) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (segment == null) + throw new ArgumentNullException ("segment"); + return segment.GetIndentation (document); + } + + public static string GetLineText (this IReadonlyTextDocument document, IDocumentLine line, bool includeDelimiter = false) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (line == null) + throw new ArgumentNullException ("line"); + return document.GetTextAt (includeDelimiter ? line.SegmentIncludingDelimiter : line); + } + + public static string GetLineText (this IReadonlyTextDocument document, int lineNumber, bool includeDelimiter = false) + { + if (document == null) + throw new ArgumentNullException ("document"); + var line = document.GetLine (lineNumber); + return document.GetTextAt (includeDelimiter ? line.SegmentIncludingDelimiter : line); + } + + static int[] GetDiffCodes (IReadonlyTextDocument document, ref int codeCounter, Dictionary<string, int> codeDictionary, bool includeEol) + { + int i = 0; + var result = new int[document.LineCount]; + foreach (var line in document.GetLinesStartingAt (1)) { + string lineText = document.GetTextAt (line.Offset, includeEol ? line.LengthIncludingDelimiter : line.Length); + int curCode; + if (!codeDictionary.TryGetValue (lineText, out curCode)) { + codeDictionary[lineText] = curCode = ++codeCounter; + } + result[i] = curCode; + i++; + } + return result; + } + + public static IEnumerable<DiffHunk> GetDiff (this IReadonlyTextDocument document, IReadonlyTextDocument changedDocument, bool includeEol = true) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (changedDocument == null) + throw new ArgumentNullException ("changedDocument"); + var codeDictionary = new Dictionary<string, int> (); + int codeCounter = 0; + return Diff.GetDiff<int> (GetDiffCodes (document, ref codeCounter, codeDictionary, includeEol), + GetDiffCodes (changedDocument, ref codeCounter, codeDictionary, includeEol)); + } + + public static string GetDiffAsString (this IReadonlyTextDocument document, IReadonlyTextDocument changedDocument, bool includeEol = true) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (changedDocument == null) + throw new ArgumentNullException ("changedDocument"); + return Diff.GetDiffString (GetDiff (document, changedDocument, includeEol), document, changedDocument, document.FileName, changedDocument.FileName); + } + + public static int OffsetToLineNumber (this IReadonlyTextDocument document, int offset) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (offset < 0 || offset > document.Length) + throw new ArgumentOutOfRangeException ("offset", string.Format ("offset should be between 0 and <={0} but was {1}.", document.Length, offset)); + return document.OffsetToLocation (offset).Line; + } + + public static int LocationToOffset (this IReadonlyTextDocument document, DocumentLocation location) + { + if (document == null) + throw new ArgumentNullException ("document"); + return document.LocationToOffset (location.Line, location.Column); + } + + public static string GetTextBetween (this IReadonlyTextDocument document, int startOffset, int endOffset) + { + if (document == null) + throw new ArgumentNullException ("document"); + if (startOffset < 0 || startOffset > document.Length) + throw new ArgumentNullException ("startOffset"); + if (endOffset < 0 || endOffset > document.Length) + throw new ArgumentNullException ("endOffset"); + if (startOffset > endOffset) + throw new InvalidOperationException (); + return document.GetTextAt (startOffset, endOffset - startOffset); + } + + public static string GetTextBetween (this IReadonlyTextDocument document, DocumentLocation start, DocumentLocation end) + { + if (document == null) + throw new ArgumentNullException ("document"); + return document.GetTextBetween (document.LocationToOffset (start), document.LocationToOffset (end)); + } + + public static string GetEolMarker (this IReadonlyTextDocument document) + { + if (document == null) + throw new ArgumentNullException ("document"); + string eol = null; + if (document.LineCount > 0) { + var line = document.GetLine (1); + if (line.DelimiterLength > 0) + eol = document.GetTextAt (line.Length, line.DelimiterLength); + } + + return !string.IsNullOrEmpty (eol) ? eol : DefaultSourceEditorOptions.Instance.DefaultEolMarker; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextDocument.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextDocument.cs new file mode 100644 index 0000000000..b2ed856224 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextDocument.cs @@ -0,0 +1,140 @@ +// +// ITextDocument.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using System.Text; +using MonoDevelop.Core; + +namespace MonoDevelop.Ide.Editor +{ + public interface ITextDocument : IReadonlyTextDocument + { + /// <summary> + /// Gets/Sets the text of the whole document.. + /// </summary> + new string Text { get; set; } // hides ITextSource.Text to add the setter + + /// <summary> + /// Gets or Sets a character at the specified position in the document. + /// </summary> + /// <paramref name="offset">The index of the character to get.</paramref> + /// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to TextLength-1).</exception> + /// <returns>The character at the specified position.</returns> + /// <remarks>This is the same as Text[offset], but is more efficient because + /// it doesn't require creating a String object.</remarks> + new char this [int offset] { get; set; } + + new bool IsReadOnly { get; set; } + + new FilePath FileName { get; set; } + + new string MimeType { get; set; } + + new bool UseBOM { get; set; } + + new Encoding Encoding { get; set; } + + void InsertText (int offset, string text); + + void InsertText (int offset, ITextSource text); + + void RemoveText (int offset, int length); + + void ReplaceText (int offset, int length, string value); + + void ReplaceText (int offset, int length, ITextSource value); + + bool IsInAtomicUndo { + get; + } + + IDisposable OpenUndoGroup(); + + /// <summary> + /// This event is called directly before a change is applied to the document. + /// </summary> + /// <remarks> + /// It is invalid to modify the document within this event handler. + /// Aborting the change (by throwing an exception) is likely to cause corruption of data structures + /// that listen to the Changing and Changed events. + /// </remarks> + event EventHandler<TextChangeEventArgs> TextChanging; + + /// <summary> + /// This event is called directly after a change is applied to the document. + /// </summary> + /// <remarks> + /// It is invalid to modify the document within this event handler. + /// Aborting the event handler (by throwing an exception) is likely to cause corruption of data structures + /// that listen to the Changing and Changed events. + /// </remarks> + event EventHandler<TextChangeEventArgs> TextChanged; + + event EventHandler FileNameChanged; + event EventHandler MimeTypeChanged; + + /// <summary> + /// Creates an immutable snapshot of this document. + /// </summary> + IReadonlyTextDocument CreateDocumentSnapshot(); + +// event EventHandler<LineEventArgs> LineChanged; +// event EventHandler<LineEventArgs> LineInserted; +// event EventHandler<LineEventArgs> LineRemoved; + } + + public static class DocumentExtensions + { + public static void RemoveText (this ITextDocument document, ISegment segment) + { + if (document == null) + throw new ArgumentNullException ("document"); + document.RemoveText (segment.Offset, segment.Length); + } + + public static void ReplaceText (this ITextDocument document, ISegment segment, string value) + { + if (document == null) + throw new ArgumentNullException ("document"); + document.ReplaceText (segment.Offset, segment.Length, value); + } + + public static void ReplaceText (this ITextDocument document, ISegment segment, ITextSource textSource) + { + if (document == null) + throw new ArgumentNullException ("document"); + document.ReplaceText (segment.Offset, segment.Length, textSource); + } + + public static void Save (this ITextDocument document) + { + if (document == null) + throw new ArgumentNullException ("document"); + document.WriteTextTo (document.FileName); + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextEditorOptions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextEditorOptions.cs new file mode 100644 index 0000000000..223f2cf2ec --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextEditorOptions.cs @@ -0,0 +1,124 @@ +// +// ITextEditorOptions.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Ide.Editor.Highlighting; + +namespace MonoDevelop.Ide.Editor +{ + public enum IndentStyle + { + /// <summary> + /// No indentation occurs + /// </summary> + None, + + /// <summary> + /// The indentation from the line above will be + /// taken to indent the current line + /// </summary> + Auto, + + /// <summary> + /// Intelligent, context sensitive indentation will occur + /// </summary> + Smart, + + /// <summary> + /// Intelligent, context sensitive indentation that minimizes whitespaces will occur + /// </summary> + Virtual + } + + public enum ShowWhitespaces { + Never, + Selection, + Always + } + + [Flags] + public enum IncludeWhitespaces { + None = 0, + Space = 1, + Tab = 2, + LineEndings = 4, + All = Space | Tab | LineEndings + } + + public interface ITextEditorOptions : IDisposable + { + WordFindStrategy WordFindStrategy { get; } + + bool TabsToSpaces { get; } + int IndentationSize { get; } + int TabSize { get; } + bool ShowIconMargin { get; } + bool ShowLineNumberMargin { get; } + bool ShowFoldMargin { get; } + bool HighlightCaretLine { get; } + int RulerColumn { get; } + bool ShowRuler { get; } + IndentStyle IndentStyle { get; } + bool OverrideDocumentEolMarker { get; } + bool EnableSyntaxHighlighting { get; } + bool RemoveTrailingWhitespaces { get; } + + bool WrapLines { get; } + + string FontName { get; } + + string GutterFontName { get; } + + string ColorScheme { get; } + + string DefaultEolMarker { get; } + + bool GenerateFormattingUndoStep { get; } + + ShowWhitespaces ShowWhitespaces { get; } + + IncludeWhitespaces IncludeWhitespaces { get; } + } + + public static class TextEditorOptionsExtension + { + public static ColorScheme GetColorStyle (this ITextEditorOptions options) + { + if (options == null) + throw new ArgumentNullException ("options"); + return SyntaxModeService.GetColorStyle (options.ColorScheme); + } + + /// <summary> + /// Gets the indentation string for a single indent. + /// </summary> + public static string GetIndentationString (this ITextEditorOptions options) + { + if (options == null) + throw new ArgumentNullException ("options"); + return options.TabsToSpaces ? new string (' ', options.IndentationSize) : "\t"; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextLineMarker.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextLineMarker.cs new file mode 100644 index 0000000000..982033e951 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextLineMarker.cs @@ -0,0 +1,80 @@ +// +// ITextLineMarker.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + public interface ITextLineMarker + { + IDocumentLine Line { + get; + } + + bool IsVisible { + get; + set; + } + + object Tag { + get; + set; + } + } + + public enum UrlType { + Unknown, + Url, + Email + } + + public interface IUrlTextLineMarker : ITextLineMarker + { + UrlType UrlType { + get; + } + + string Url { + get; + } + } + + public interface ICurrentDebugLineTextMarker : ITextLineMarker + { + + } + + public interface IMessageBubbleLineMarker : ITextLineMarker + { + int TaskCount { get; } + + MonoDevelop.Ide.Tasks.TaskListEntry PrimaryTask { get; set; } + + IEnumerable<MonoDevelop.Ide.Tasks.TaskListEntry> Tasks { get; } + + void AddTask (MonoDevelop.Ide.Tasks.TaskListEntry task); + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextSegmentMarker.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextSegmentMarker.cs new file mode 100644 index 0000000000..625f960988 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/ITextSegmentMarker.cs @@ -0,0 +1,86 @@ +// +// ITextSegmentMarker.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using MonoDevelop.Ide.Editor; +using MonoDevelop.Components; +using MonoDevelop.Ide.TypeSystem; + +namespace MonoDevelop.Ide.Editor +{ + public interface ITextSegmentMarker : ISegment + { + bool IsVisible { + get; + set; + } + + object Tag { + get; + set; + } + + event EventHandler<TextMarkerMouseEventArgs> MousePressed; + event EventHandler<TextMarkerMouseEventArgs> MouseHover; + } + + public enum TextSegmentMarkerEffect { + /// <summary> + /// The region is marked as waved underline. + /// </summary> + WavedLine, + + /// <summary> + /// The region is marked as dotted line. + /// </summary> + DottedLine, + + /// <summary> + /// The text is grayed out. + /// </summary> + GrayOut + } + + public interface IGenericTextSegmentMarker : ITextSegmentMarker + { + TextSegmentMarkerEffect Effect { get; } + + HslColor Color { get; set; } + } + + public interface IErrorMarker : ITextSegmentMarker + { + Error Error { get; } + } + + public interface ISmartTagMarker : ITextSegmentMarker + { + bool IsInsideSmartTag (double x, double y); + + bool IsInsideWindow (Gtk.MotionNotifyEventArgs args); + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IUnitTestMarker.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IUnitTestMarker.cs new file mode 100644 index 0000000000..0350ff9839 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/IUnitTestMarker.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace MonoDevelop.Ide.Editor +{ + public interface IUnitTestMarker : ITextLineMarker + { + UnitTestLocation UnitTest { get; } + + void UpdateState (); + } + + public abstract class UnitTestMarkerHost + { + public abstract Xwt.Drawing.Image GetStatusIcon (string unitTestIdentifier, string caseId = null); + public abstract bool IsFailure (string unitTestIdentifier, string caseId = null); + public abstract string GetMessage (string unitTestIdentifier, string caseId = null); + public abstract bool HasResult (string unitTestIdentifier, string caseId = null); + + public abstract void PopupContextMenu (UnitTestLocation unitTest, int x, int y); + } + + public class UnitTestLocation + { + public int Offset { get; set; } + public bool IsFixture { get; set; } + public string UnitTestIdentifier { get; set; } + public bool IsIgnored { get; set; } + + public List<string> TestCases = new List<string> (); + + public UnitTestLocation (int offset) + { + Offset = offset; + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionCursorEventArgs.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionCursorEventArgs.cs new file mode 100644 index 0000000000..2f5996cc6a --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionCursorEventArgs.cs @@ -0,0 +1,51 @@ +// +// InsertionCursorEventArgs.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + [Serializable] + public sealed class InsertionCursorEventArgs : EventArgs + { + public bool Success { + get; + private set; + } + + public InsertionPoint InsertionPoint { + get; + private set; + } + + public InsertionCursorEventArgs (bool success, InsertionPoint insertionPoint) + { + Success = success; + InsertionPoint = insertionPoint; + } + } + +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionModeOptions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionModeOptions.cs new file mode 100644 index 0000000000..c65e4cea08 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionModeOptions.cs @@ -0,0 +1,88 @@ +// +// InsertionModeOptions.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// This class contains information the editor needs to initiate the insertion mode. + /// </summary> + public sealed class InsertionModeOptions + { + /// <summary> + /// A user visible string describing this operation. + /// </summary> + public string Operation { + get; + private set; + } + + /// <summary> + /// The list of insertion points that are used for the insertion mode. The caret is only able to move between + /// the insertion points. + /// </summary> + public IList<InsertionPoint> InsertionPoints { + get; + private set; + } + + /// <summary> + /// That's the action that is started after the insertion mode ended. + /// </summary> + public Action<InsertionCursorEventArgs> ModeExitedAction { + get; + set; + } + + /// <summary> + /// Gets or sets the first selected insertion point. The default value is 0. + /// </summary> + public int FirstSelectedInsertionPoint { + get; + set; + } + + /// <summary> + /// Initializes a new instance of the <see cref="MonoDevelop.Ide.Editor.InsertionModeOptions"/> class. + /// </summary> + /// <param name="operation">A user visible string describing this operation.</param> + /// <param name="insertionPoints">The list of insertion points that are used for the insertion mode.</param> + /// <param name="modeExitedAction">The action that is started after the exit mode ended.</param> + public InsertionModeOptions (string operation, IList<InsertionPoint> insertionPoints, Action<InsertionCursorEventArgs> modeExitedAction) + { + if (operation == null) + throw new ArgumentNullException ("operation"); + if (insertionPoints == null) + throw new ArgumentNullException ("insertionPoints"); + if (modeExitedAction == null) + throw new ArgumentNullException ("modeExitedAction"); + Operation = operation; + InsertionPoints = insertionPoints; + ModeExitedAction = modeExitedAction; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionPoint.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionPoint.cs new file mode 100644 index 0000000000..7bf2808146 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InsertionPoint.cs @@ -0,0 +1,119 @@ +// +// InsertionPoint.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using MonoDevelop.Ide.CodeFormatting; + +namespace MonoDevelop.Ide.Editor +{ + public enum NewLineInsertion + { + None, + Eol, + BlankLine + } + + public sealed class InsertionPoint + { + public DocumentLocation Location { + get; + set; + } + + public NewLineInsertion LineBefore { get; set; } + public NewLineInsertion LineAfter { get; set; } + + public InsertionPoint (DocumentLocation location, NewLineInsertion lineBefore, NewLineInsertion lineAfter) + { + this.Location = location; + this.LineBefore = lineBefore; + this.LineAfter = lineAfter; + } + + public override string ToString () + { + return string.Format ("[InsertionPoint: Location={0}, LineBefore={1}, LineAfter={2}]", Location, LineBefore, LineAfter); + } + + public void InsertNewLine (ITextDocument editor, NewLineInsertion insertion, ref int offset) + { + string str = null; + switch (insertion) { + case NewLineInsertion.Eol: + str = editor.GetEolMarker (); + break; + case NewLineInsertion.BlankLine: + str = editor.GetEolMarker () + editor.GetEolMarker (); + break; + default: + return; + } + + editor.InsertText (offset, str); + offset += str.Length; + } + + public int Insert (TextEditor editor, DocumentContext ctx, string text) + { + int offset = editor.LocationToOffset (Location); + using (var undo = editor.OpenUndoGroup ()) { + + var line = editor.GetLineByOffset (offset); + int insertionOffset = line.Offset + Location.Column - 1; + offset = insertionOffset; + InsertNewLine (editor, LineBefore, ref offset); + int result = offset - insertionOffset; + + editor.InsertText (offset, text); + offset += text.Length; + InsertNewLine (editor, LineAfter, ref offset); + CodeFormatterService.Format (editor, ctx, TextSegment.FromBounds (insertionOffset - 1, offset)); + return result; + } + } + + public int Insert (ITextDocument editor, string text) + { + int offset = editor.LocationToOffset (Location); + using (var undo = editor.OpenUndoGroup ()) { + + // TODO: Run formatter !!! + // text = editor.FormatString (Location, text); + + var line = editor.GetLineByOffset (offset); + int insertionOffset = line.Offset + Location.Column - 1; + offset = insertionOffset; + InsertNewLine (editor, LineBefore, ref offset); + int result = offset - insertionOffset; + + editor.InsertText (offset, text); + offset += text.Length; + InsertNewLine (editor, LineAfter, ref offset); + return result; + } + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IEditorActionHost.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IEditorActionHost.cs new file mode 100644 index 0000000000..f95b0607fe --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IEditorActionHost.cs @@ -0,0 +1,123 @@ +// +// ITextEditor.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; + +namespace MonoDevelop.Ide.Editor +{ + interface IEditorActionHost + { + void SwitchCaretMode (); + + void InsertTab (); + + void RemoveTab (); + + void InsertNewLine (); + + void DeletePreviousWord (); + + void DeleteNextWord (); + + void DeletePreviousSubword (); + + void DeleteNextSubword (); + + void StartCaretPulseAnimation (); + + void RecenterEditor (); + + void JoinLines (); + + void MoveNextSubWord (); + + void MovePrevSubWord (); + + void MoveNextWord (); + + void MovePrevWord (); + + void PageUp (); + + void PageDown (); + + void MoveCaretDown (); + + void MoveCaretUp (); + + void MoveCaretRight (); + + void MoveCaretLeft (); + + void MoveCaretToLineEnd (); + + void MoveCaretToLineStart (); + + void MoveCaretToDocumentStart (); + + void MoveCaretToDocumentEnd (); + + void Backspace (); + + void Delete (); + + void ClipboardCopy (); + + void ClipboardCut (); + + void ClipboardPaste (); + + void SelectAll (); + + void NewLine (); + + void Undo (); + + void Redo (); + + void DeleteCurrentLine (); + + void DeleteCurrentLineToEnd (); + + void ScrollLineUp (); + + void ScrollLineDown (); + + void ScrollPageUp (); + + void ScrollPageDown (); + + void MoveBlockUp (); + + void MoveBlockDown (); + + void ToggleBlockSelectionMode (); + + void IndentSelection (); + + void UnIndentSelection (); + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorFactory.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorFactory.cs new file mode 100644 index 0000000000..4b88445dab --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorFactory.cs @@ -0,0 +1,44 @@ +// +// ITextEditorFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; + +namespace MonoDevelop.Ide.Editor +{ + interface ITextEditorFactory + { + ITextDocument CreateNewDocument (); + ITextDocument CreateNewDocument (ITextSource textSource, string fileName, string mimeType); + + IReadonlyTextDocument CreateNewReadonlyDocument (ITextSource textSource, string fileName, string mimeType); + + ITextEditorImpl CreateNewEditor (); + ITextEditorImpl CreateNewEditor (IReadonlyTextDocument document); + + string[] GetSyntaxProperties (string mimeType, string name); + } + +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs new file mode 100644 index 0000000000..4b299a40ef --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs @@ -0,0 +1,244 @@ +// +// ITextEditorImpl.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using System.Collections.Generic; +using MonoDevelop.Ide.Gui; +using MonoDevelop.Ide.Editor.Extension; +using MonoDevelop.Ide.Editor.Highlighting; +using MonoDevelop.Components; + +namespace MonoDevelop.Ide.Editor +{ + public enum EditMode + { + Edit, + TextLink, + CursorInsertion + } + + /// <summary> + /// A skip char is a character in the editor at a defined position that is skipped when this + /// exact character is pressed at the character position. That's useful for inserting automatically generated brackets without + /// interfering with the typing flow. + /// </summary> + public struct SkipChar + { + /// <summary> + /// Gets the offset. + /// </summary> + public readonly int Offset; + + /// <summary> + /// Gets the char. + /// </summary> + public readonly char Char; + + /// <summary> + /// Initializes a new instance of the <see cref="MonoDevelop.Ide.Editor.SkipChar"/> struct. + /// </summary> + /// <param name="offset">The offset of the char.</param> + /// <param name="ch">The character</param> + public SkipChar (int offset, char ch) + { + Offset = offset; + Char = ch; + } + + /// <summary> + /// Returns a <see cref="System.String"/> that represents the current <see cref="MonoDevelop.Ide.Editor.SkipChar"/>. + /// </summary> + /// <returns>A <see cref="System.String"/> that represents the current <see cref="MonoDevelop.Ide.Editor.SkipChar"/>.</returns> + public override string ToString () + { + return string.Format ("[SkipChar: Offset={0}, Char={1}]", Offset, Char); + } + } + + interface ITextEditorImpl : IViewContent, IDisposable + { + EditMode EditMode { get; } + + ITextEditorOptions Options { get; set; } + + IReadonlyTextDocument Document { get; set; } + + DocumentLocation CaretLocation { get; set; } + + SemanticHighlighting SemanticHighlighting { get; set; } + + int CaretOffset { get; set; } + + bool IsSomethingSelected { get; } + + SelectionMode SelectionMode { get; } + + ISegment SelectionRange { get; set; } + int SelectionAnchorOffset { get; set; } + int SelectionLeadOffset { get; set; } + + DocumentRegion SelectionRegion { get; set; } + + void SetSelection (int anchorOffset, int leadOffset); + + event EventHandler SelectionChanged; + + event EventHandler CaretPositionChanged; + + event EventHandler BeginMouseHover; + + event EventHandler VAdjustmentChanged; + + event EventHandler HAdjustmentChanged; + + void ClearSelection (); + + void CenterToCaret (); + + void StartCaretPulseAnimation (); + + int EnsureCaretIsNotVirtual (); + + void FixVirtualIndentation (); + + IEditorActionHost Actions { get; } + + ITextMarkerFactory TextMarkerFactory { get; } + + event EventHandler BeginAtomicUndoOperation; + + event EventHandler EndAtomicUndoOperation; + + object CreateNativeControl (); + + void RunWhenLoaded (Action action); + + string FormatString (int offset, string code); + + void StartInsertionMode (InsertionModeOptions insertionModeOptions); + + void StartTextLinkMode (TextLinkModeOptions textLinkModeOptions); + + double LineHeight { get; } + + DocumentLocation PointToLocation (double xp, double yp, bool endAtEol = false); + + Xwt.Point LocationToPoint (int line, int column); + + void AddMarker (IDocumentLine line, ITextLineMarker lineMarker); + + void RemoveMarker (ITextLineMarker lineMarker); + + void ScrollTo (int offset); + + void CenterTo (int offset); + + IList<SkipChar> SkipChars + { + get; + } + + void AddSkipChar (int offset, char ch); + + string GetVirtualIndentationString (int lineNumber); + + IEnumerable<ITextLineMarker> GetLineMarkers (IDocumentLine line); + + #region Text segment markers + + IEnumerable<ITextSegmentMarker> GetTextSegmentMarkersAt (ISegment segment); + + IEnumerable<ITextSegmentMarker> GetTextSegmentMarkersAt (int offset); + + /// <summary> + /// Adds a marker to the document. + /// </summary> + void AddMarker (ITextSegmentMarker marker); + + /// <summary> + /// Removes a marker from the document. + /// </summary> + /// <returns><c>true</c>, if marker was removed, <c>false</c> otherwise.</returns> + /// <param name="marker">Marker.</param> + bool RemoveMarker (ITextSegmentMarker marker); + + #endregion + + IFoldSegment CreateFoldSegment (int offset, int length, bool isFolded = false); + + void SetFoldings (IEnumerable<IFoldSegment> foldings); + + IEnumerable<IFoldSegment> GetFoldingsContaining (int offset); + + IEnumerable<IFoldSegment> GetFoldingsIn (int offset, int length); + + string GetPangoMarkup (int offset, int length); + + void SetIndentationTracker (IndentationTracker indentationTracker); + void SetSelectionSurroundingProvider (SelectionSurroundingProvider surroundingProvider); + void SetTextPasteHandler (TextPasteHandler textPasteHandler); + + event EventHandler<LineEventArgs> LineChanged; + event EventHandler<LineEventArgs> LineInserted; + event EventHandler<LineEventArgs> LineRemoved; + + #region Internal use only API (do not mirror in TextEditor) + + TextEditorExtension EditorExtension + { + get; + set; + } + + IEnumerable<TooltipProvider> TooltipProvider + { + get; + } + + void ClearTooltipProviders (); + + void AddTooltipProvider (TooltipProvider provider); + + void RemoveTooltipProvider (TooltipProvider provider); + + Xwt.Point GetEditorWindowOrigin (); + + Xwt.Rectangle GetEditorAllocation (); + + void InformLoadComplete (); + + void SetUsageTaskProviders (IEnumerable<UsageProviderEditorExtension> providers); + + void SetQuickTaskProviders (IEnumerable<IQuickTaskProvider> providers); + #endregion + + double ZoomLevel { get; set; } + event EventHandler ZoomLevelChanged; + + void AddOverlay (Control messageOverlayContent, Func<int> sizeFunc); + void RemoveOverlay (Control messageOverlayContent); + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextMarkerFactory.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextMarkerFactory.cs new file mode 100644 index 0000000000..9c870c9874 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextMarkerFactory.cs @@ -0,0 +1,58 @@ +// +// ITextMarkerFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using MonoDevelop.Ide.Editor.Extension; +using MonoDevelop.Ide.TypeSystem; + +namespace MonoDevelop.Ide.Editor +{ + public enum LinkRequest + { + SameView, + RequestNewView + } + + interface ITextMarkerFactory + { + #region Line marker + IUrlTextLineMarker CreateUrlTextMarker (TextEditor editor, IDocumentLine line, string value, UrlType url, string syntax, int startCol, int endCol); + ICurrentDebugLineTextMarker CreateCurrentDebugLineTextMarker (TextEditor editor); + ITextLineMarker CreateAsmLineMarker (TextEditor editor); + IUnitTestMarker CreateUnitTestMarker (TextEditor editor, UnitTestMarkerHost host, UnitTestLocation unitTestLocation); + IMessageBubbleLineMarker CreateMessageBubbleLineMarker (TextEditor editor); + #endregion + + #region Segment marker + ITextSegmentMarker CreateUsageMarker (TextEditor editor, Usage usage); + ITextSegmentMarker CreateLinkMarker (TextEditor editor, int offset, int length, Action<LinkRequest> activateLink); + + IGenericTextSegmentMarker CreateGenericTextSegmentMarker (TextEditor editor, TextSegmentMarkerEffect effect, int offset, int length); + ISmartTagMarker CreateSmartTagMarker (TextEditor editor, int offset, DocumentLocation realLocation); + IErrorMarker CreateErrorMarker (TextEditor editor, Error info, int offset, int length); + #endregion + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/LineEventArgs.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/LineEventArgs.cs new file mode 100644 index 0000000000..3dd6e0cc56 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/LineEventArgs.cs @@ -0,0 +1,48 @@ +// +// LineEventArgs.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + public class LineEventArgs : System.EventArgs + { + readonly IDocumentLine line; + + public IDocumentLine Line { + get { + return line; + } + } + + public LineEventArgs (IDocumentLine line) + { + if (line == null) + throw new ArgumentNullException ("line"); + this.line = line; + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/MessageBubbles/MessageBubbleCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/MessageBubbles/MessageBubbleCommands.cs new file mode 100644 index 0000000000..2e81dbf847 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/MessageBubbles/MessageBubbleCommands.cs @@ -0,0 +1,77 @@ +// +// MessageBubbleCommands.cs +// +// Author: +// Mike Krüger <mkrueger@novell.com> +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// 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 MonoDevelop.Ide; +using MonoDevelop.Components.Commands; +using MonoDevelop.Core; + +namespace MonoDevelop.Ide.Editor +{ + enum MessageBubbleCommands + { + Toggle, + HideIssues, + ToggleIssues + } + + class HideIssuesHandler : CommandHandler + { + protected override void Update (CommandInfo info) + { + base.Update (info); + info.Text = IdeApp.Preferences.DefaultHideMessageBubbles ? GettextCatalog.GetString ("_Show Message Bubbles") : GettextCatalog.GetString ("_Hide Message Bubbles"); + } + + protected override void Run (object data) + { + IdeApp.Preferences.DefaultHideMessageBubbles = !IdeApp.Preferences.DefaultHideMessageBubbles; + } + } + + class ToggleIssuesHandler : CommandHandler + { + protected override void Run (object data) + { + Action action = data as Action; + if (action != null) + action (); + } + + protected override void Update (CommandArrayInfo ainfo) + { + CommandInfo info = ainfo.Add (GettextCatalog.GetString ("_Errors & Warnings"), new Action (delegate { + MonoDevelop.Ide.IdeApp.Preferences.ShowMessageBubbles = MonoDevelop.Ide.ShowMessageBubbles.ForErrorsAndWarnings; + })); + info.Checked = MonoDevelop.Ide.IdeApp.Preferences.ShowMessageBubbles == MonoDevelop.Ide.ShowMessageBubbles.ForErrorsAndWarnings; + + info = ainfo.Add (GettextCatalog.GetString ("E_rrors only"), new Action (delegate { + MonoDevelop.Ide.IdeApp.Preferences.ShowMessageBubbles = MonoDevelop.Ide.ShowMessageBubbles.ForErrors; + })); + info.Checked = MonoDevelop.Ide.IdeApp.Preferences.ShowMessageBubbles == MonoDevelop.Ide.ShowMessageBubbles.ForErrors; + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/IProjectionExtension.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/IProjectionExtension.cs new file mode 100644 index 0000000000..b995eaabe1 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/IProjectionExtension.cs @@ -0,0 +1,39 @@ +// +// IProjectionExtension.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor.Projection +{ + interface IProjectionExtension + { + IReadOnlyList<Projection> Projections { + get; + set; + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedCompletionExtension.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedCompletionExtension.cs new file mode 100644 index 0000000000..91f4b820a9 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedCompletionExtension.cs @@ -0,0 +1,433 @@ +// +// ProjectedCompletionExtension.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Ide.Editor.Extension; +using System.Collections.Generic; +using MonoDevelop.Ide.CodeCompletion; + + +namespace MonoDevelop.Ide.Editor.Projection +{ + sealed class ProjectedCompletionExtension : CompletionTextEditorExtension, IProjectionExtension + { + DocumentContext ctx; + IReadOnlyList<Projection> projections; + + public IReadOnlyList<Projection> Projections { + get { + return projections; + } + set { + projections = value; + } + } + + public ProjectedCompletionExtension (DocumentContext ctx, IReadOnlyList<Projection> projections) + { + if (projections == null) + throw new ArgumentNullException ("projections"); + this.ctx = ctx; + this.projections = projections; + } + + public override bool IsValidInContext (DocumentContext context) + { + var pctx = context as ProjectedDocumentContext; + if (pctx == null) + return false; + return pctx.ProjectedEditor.GetContent<CompletionTextEditorExtension> () != null; + } + + Projection GetProjectionAt (int offset) + { + foreach (var projection in projections) { + foreach (var seg in projection.ProjectedSegments) { + if (seg.ContainsOriginal (offset)) { + projection.ProjectedEditor.CaretOffset = seg.FromOriginalToProjected (offset); + return projection; + } + } + } + return null; + } + + CompletionTextEditorExtension GetExtensionAt (int offset) + { + var projection = GetProjectionAt (offset); + if (projection != null) { + var result = projection.ProjectedEditor.GetContent<CompletionTextEditorExtension> (); + if (result != null) { + result.CompletionWidget = new ProjectedCompletionWidget (CompletionWidget, projection); + } + return result; + } + return null; + } + + class ProjectedCompletionWidget : ICompletionWidget + { + readonly ICompletionWidget completionWidget; + readonly Projection projection; + + public ProjectedCompletionWidget (ICompletionWidget completionWidget, Projection projection) + { + if (completionWidget == null) + throw new ArgumentNullException ("completionWidget"); + if (projection == null) + throw new ArgumentNullException ("projection"); + this.projection = projection; + this.completionWidget = completionWidget; + } + + #region ICompletionWidget implementation + public double ZoomLevel { + get { + return completionWidget.ZoomLevel; + } + } + + event EventHandler ICompletionWidget.CompletionContextChanged { + add { + completionWidget.CompletionContextChanged += value; + } + remove { + completionWidget.CompletionContextChanged -= value; + } + } + + string ICompletionWidget.GetText (int startOffset, int endOffset) + { + return projection.ProjectedEditor.GetTextBetween (startOffset, endOffset); + } + + char ICompletionWidget.GetChar (int offset) + { + return projection.ProjectedEditor.GetCharAt (offset); + } + + void ICompletionWidget.Replace (int offset, int count, string text) + { + foreach (var seg in projection.ProjectedSegments) { + if (seg.ContainsProjected (offset)) { + offset = seg.FromProjectedToOriginal (offset); + break; + } + } + + completionWidget.Replace (offset, count, text); + } + + int ConvertOffset (int triggerOffset) + { + int result = triggerOffset; + foreach (var seg in projection.ProjectedSegments) { + if (seg.ContainsProjected (result)) { + result = seg.FromProjectedToOriginal (result); + break; + } + } + return result; + } + + int ProjectOffset (int offset) + { + int result = offset; + foreach (var seg in projection.ProjectedSegments) { + if (seg.ContainsOriginal (result)) { + result = seg.FromOriginalToProjected (result); + break; + } + } + return result; + + } + + CodeCompletionContext ICompletionWidget.CreateCodeCompletionContext (int triggerOffset) + { + var originalTriggerOffset = ConvertOffset (triggerOffset); + var completionContext = completionWidget.CreateCodeCompletionContext (originalTriggerOffset); + return ConvertContext (completionContext, projection); + } + + string ICompletionWidget.GetCompletionText (CodeCompletionContext ctx) + { + return completionWidget.GetCompletionText (ImportContext (ctx, projection)); + } + + void ICompletionWidget.SetCompletionText (CodeCompletionContext ctx, string partial_word, string complete_word) + { + completionWidget.SetCompletionText (ImportContext (ctx, projection), partial_word, complete_word); + } + + void ICompletionWidget.SetCompletionText (CodeCompletionContext ctx, string partial_word, string complete_word, int completeWordOffset) + { + completionWidget.SetCompletionText (ImportContext (ctx, projection), partial_word, complete_word, completeWordOffset); + } + + void ICompletionWidget.AddSkipChar (int cursorPosition, char c) + { + completionWidget.AddSkipChar (ProjectOffset (cursorPosition), c); + } + + CodeCompletionContext ICompletionWidget.CurrentCodeCompletionContext { + get { + return ConvertContext (completionWidget.CurrentCodeCompletionContext, projection); + } + } + + int ICompletionWidget.CaretOffset { + get { + return ConvertOffset (completionWidget.CaretOffset); + } + set { + completionWidget.CaretOffset = ProjectOffset (value); + } + } + + int ICompletionWidget.TextLength { + get { + return projection.ProjectedEditor.Length; + } + } + + int ICompletionWidget.SelectedLength { + get { + return completionWidget.SelectedLength; + } + } + + Gtk.Style ICompletionWidget.GtkStyle { + get { + return completionWidget.GtkStyle; + } + } + #endregion + } + + CompletionTextEditorExtension GetCurrentExtension () + { + return GetExtensionAt (Editor.CaretOffset); + } + + public override bool CanRunCompletionCommand () + { + var projectedExtension = GetCurrentExtension (); + if (projectedExtension == null) + return false; + return projectedExtension.CanRunCompletionCommand (); + } + + public override MonoDevelop.Ide.CodeCompletion.ICompletionDataList CodeCompletionCommand (MonoDevelop.Ide.CodeCompletion.CodeCompletionContext completionContext) + { + var projectedExtension = GetExtensionAt (completionContext.TriggerOffset); + if (projectedExtension == null) + return null; + return projectedExtension.CodeCompletionCommand (ConvertContext (completionContext)); + } + + public override bool CanRunParameterCompletionCommand () + { + var projectedExtension = GetCurrentExtension (); + if (projectedExtension == null) + return false; + return projectedExtension.CanRunParameterCompletionCommand (); + } + + public override string CompletionLanguage { + get { + var projectedExtension = GetCurrentExtension (); + if (projectedExtension == null) + return base.CompletionLanguage; + return projectedExtension.CompletionLanguage; + } + } + + public override bool GetCompletionCommandOffset (out int cpos, out int wlen) + { + var projectedExtension = GetCurrentExtension (); + if (projectedExtension == null) { + cpos = 0; + wlen = 0; + return false; + } + return projectedExtension.GetCompletionCommandOffset (out cpos, out wlen); + } + + public override int GetCurrentParameterIndex (int startOffset) + { + var projectedExtension = GetExtensionAt (startOffset); + if (projectedExtension == null) + return -1; + return projectedExtension.GetCurrentParameterIndex (startOffset); + } + + public override int GuessBestMethodOverload (ParameterHintingResult provider, int currentOverload) + { + var projectedExtension = GetCurrentExtension (); + if (projectedExtension == null) + return -1; + return projectedExtension.GuessBestMethodOverload (provider, currentOverload); + } + + public override System.Threading.Tasks.Task<MonoDevelop.Ide.CodeCompletion.ICompletionDataList> HandleCodeCompletionAsync (MonoDevelop.Ide.CodeCompletion.CodeCompletionContext completionContext, char completionChar, System.Threading.CancellationToken token) + { + var projectedExtension = GetExtensionAt (completionContext.TriggerOffset); + if (projectedExtension == null) + return null; + + return projectedExtension.HandleCodeCompletionAsync (ConvertContext (completionContext), completionChar, token); + } + + public override System.Threading.Tasks.Task<ParameterHintingResult> HandleParameterCompletionAsync (MonoDevelop.Ide.CodeCompletion.CodeCompletionContext completionContext, char completionChar, System.Threading.CancellationToken token) + { + var projectedExtension = GetExtensionAt (completionContext.TriggerOffset); + if (projectedExtension == null) + return null; + return projectedExtension.HandleParameterCompletionAsync (ConvertContext (completionContext), completionChar, token); + } + + public override bool KeyPress (KeyDescriptor descriptor) + { + projections = ctx.GetPartialProjectionsAsync ().Result; + var projectedExtension = GetCurrentExtension(); + if (projectedExtension != null) + return projectedExtension.KeyPress (descriptor); + return base.KeyPress (descriptor); + } + + public override ParameterHintingResult ParameterCompletionCommand (MonoDevelop.Ide.CodeCompletion.CodeCompletionContext completionContext) + { + var projectedExtension = GetExtensionAt (completionContext.TriggerOffset); + if (projectedExtension == null) + return null; + return projectedExtension.ParameterCompletionCommand (ConvertContext (completionContext)); + } + + public override void RunCompletionCommand () + { + var projectedExtension = GetCurrentExtension(); + if (projectedExtension == null) + return; + projectedExtension.RunCompletionCommand (); + } + + public override void RunParameterCompletionCommand () + { + var projectedExtension = GetCurrentExtension(); + if (projectedExtension == null) + return; + + projectedExtension.RunParameterCompletionCommand (); + } + + public override void RunShowCodeTemplatesWindow () + { + var projectedExtension = GetCurrentExtension(); + if (projectedExtension == null) + return; + projectedExtension.RunShowCodeTemplatesWindow (); + } + + public override MonoDevelop.Ide.CodeCompletion.ICompletionDataList ShowCodeSurroundingsCommand (MonoDevelop.Ide.CodeCompletion.CodeCompletionContext completionContext) + { + var projectedExtension = GetExtensionAt (completionContext.TriggerOffset); + if (projectedExtension == null) + return null; + return projectedExtension.ShowCodeSurroundingsCommand (ConvertContext (completionContext)); + } + + public override MonoDevelop.Ide.CodeCompletion.ICompletionDataList ShowCodeTemplatesCommand (MonoDevelop.Ide.CodeCompletion.CodeCompletionContext completionContext) + { + var projectedExtension = GetExtensionAt (completionContext.TriggerOffset); + if (projectedExtension == null) + return null; + return projectedExtension.ShowCodeTemplatesCommand (ConvertContext (completionContext)); + } + + CodeCompletionContext ConvertContext (CodeCompletionContext completionContext) + { + var projection = GetProjectionAt (completionContext.TriggerOffset); + return ConvertContext (completionContext, projection); + } + + static CodeCompletionContext ConvertContext (CodeCompletionContext completionContext, Projection projection) + { + int offset = completionContext.TriggerOffset; + int line = completionContext.TriggerLine; + int lineOffset = completionContext.TriggerLineOffset; + + if (projection != null) { + foreach (var seg in projection.ProjectedSegments) { + if (seg.ContainsOriginal (offset)) { + offset = seg.FromOriginalToProjected (offset); + var loc = projection.ProjectedEditor.OffsetToLocation (offset); + line = loc.Line; + lineOffset = loc.Column - 1; + } + } + } + + return new MonoDevelop.Ide.CodeCompletion.CodeCompletionContext { + TriggerOffset = offset, + TriggerLine = line, + TriggerLineOffset = lineOffset, + TriggerXCoord = completionContext.TriggerXCoord, + TriggerYCoord = completionContext.TriggerYCoord, + TriggerTextHeight = completionContext.TriggerTextHeight, + TriggerWordLength = completionContext.TriggerWordLength + }; + } + + static CodeCompletionContext ImportContext (CodeCompletionContext completionContext, Projection projection) + { + int offset = completionContext.TriggerOffset; + int line = completionContext.TriggerLine; + int lineOffset = completionContext.TriggerLineOffset; + + if (projection != null) { + foreach (var seg in projection.ProjectedSegments) { + if (seg.ContainsProjected (offset)) { + offset = seg.FromProjectedToOriginal (offset); + var loc = projection.ProjectedEditor.OffsetToLocation (offset); + line = loc.Line; + lineOffset = loc.Column - 1; + } + } + } + + return new MonoDevelop.Ide.CodeCompletion.CodeCompletionContext { + TriggerOffset = offset, + TriggerLine = line, + TriggerLineOffset = lineOffset, + TriggerXCoord = completionContext.TriggerXCoord, + TriggerYCoord = completionContext.TriggerYCoord, + TriggerTextHeight = completionContext.TriggerTextHeight, + TriggerWordLength = completionContext.TriggerWordLength + }; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedDocumentContext.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedDocumentContext.cs new file mode 100644 index 0000000000..4571fefeb8 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedDocumentContext.cs @@ -0,0 +1,143 @@ +// +// ProjectedDocumentContext.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Ide.Editor; +using System.Collections.Immutable; +using MonoDevelop.Core.Text; +using MonoDevelop.Ide.Gui; +using MonoDevelop.Ide.TypeSystem; + +namespace MonoDevelop.Ide.Editor.Projection +{ + + class ProjectedDocumentContext : DocumentContext + { + DocumentContext originalContext; + TextEditor projectedEditor; + ParsedDocument parsedDocument; + + public TextEditor ProjectedEditor { + get { + return projectedEditor; + } + } + + public DocumentContext OriginalContext { + get { + return originalContext; + } + } + + Microsoft.CodeAnalysis.Document projectedDocument; + + public ProjectedDocumentContext (TextEditor projectedEditor, DocumentContext originalContext) + { + if (projectedEditor == null) + throw new ArgumentNullException ("projectedEditor"); + if (originalContext == null) + throw new ArgumentNullException ("originalContext"); + this.projectedEditor = projectedEditor; + this.originalContext = originalContext; + + if (originalContext.Project != null) { + var originalProjectId = TypeSystemService.GetProjectId (originalContext.Project); + if (originalProjectId != null) { + var originalProject = TypeSystemService.Workspace.CurrentSolution.GetProject (originalProjectId); + if (originalProject != null) { + projectedDocument = originalProject.AddDocument ( + projectedEditor.FileName, + projectedEditor + ); + } + } + } + + projectedEditor.TextChanged += delegate(object sender, TextChangeEventArgs e) { + if (projectedDocument != null) + projectedDocument = projectedDocument.WithText (projectedEditor); + ReparseDocument (); + }; + + ReparseDocument (); + } + + #region implemented abstract members of DocumentContext + public override void AttachToProject (MonoDevelop.Projects.Project project) + { + } + + public override void ReparseDocument () + { + var options = new ParseOptions { + FileName = projectedEditor.FileName, + Content = projectedEditor, + Project = Project, + RoslynDocument = projectedDocument + }; + parsedDocument = TypeSystemService.ParseFile (options, projectedEditor.MimeType).Result; + + base.OnDocumentParsed (EventArgs.Empty); + } + + public override Microsoft.CodeAnalysis.Options.OptionSet GetOptionSet () + { + return originalContext.GetOptionSet (); + } + + public override MonoDevelop.Ide.TypeSystem.ParsedDocument UpdateParseDocument () + { + ReparseDocument (); + return parsedDocument; + } + + public override string Name { + get { + return projectedEditor.FileName; + } + } + + public override MonoDevelop.Projects.Project Project { + get { + return originalContext.Project; + } + } + + public override Microsoft.CodeAnalysis.Document AnalysisDocument { + get { + + return projectedDocument; + } + } + + public override MonoDevelop.Ide.TypeSystem.ParsedDocument ParsedDocument { + get { + return parsedDocument; + } + } + #endregion + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedFilterCompletionTextEditorExtension.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedFilterCompletionTextEditorExtension.cs new file mode 100644 index 0000000000..15b3ecfc61 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedFilterCompletionTextEditorExtension.cs @@ -0,0 +1,186 @@ +// +// ProjectedFilterCompletionTextEditorExtension.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Ide.Editor.Extension; + +namespace MonoDevelop.Ide.Editor.Projection +{ + sealed class ProjectedFilterCompletionTextEditorExtension : CompletionTextEditorExtension, IProjectionExtension + { + CompletionTextEditorExtension completionTextEditorExtension; + IReadOnlyList<Projection> projections; + + IReadOnlyList<Projection> IProjectionExtension.Projections { + get { + return projections; + } + set { + projections = value; + } + } + + public ProjectedFilterCompletionTextEditorExtension (CompletionTextEditorExtension completionTextEditorExtension, IReadOnlyList<Projection> projections) + { + this.completionTextEditorExtension = completionTextEditorExtension; + this.projections = projections; + } + + bool IsInProjection () + { + int offset = Editor.CaretOffset; + foreach (var p in projections) { + foreach (var seg in p.ProjectedSegments) { + if (seg.ContainsOriginal (offset)) + return true; + } + } + return false; + } + + public override bool KeyPress (KeyDescriptor descriptor) + { + if (IsInProjection()) + return Next == null || Next.KeyPress (descriptor); + return completionTextEditorExtension.KeyPress (descriptor); + } + + public override bool IsValidInContext (DocumentContext context) + { + if (IsInProjection()) + return false; + return completionTextEditorExtension.IsValidInContext (context); + } + + public override int GetCurrentParameterIndex (int startOffset) + { + if (IsInProjection()) + return -1; + return completionTextEditorExtension.GetCurrentParameterIndex (startOffset); + } + + public override string CompletionLanguage { + get { + return completionTextEditorExtension.CompletionLanguage; + } + } + + public override void RunCompletionCommand () + { + if (IsInProjection ()) + return; + completionTextEditorExtension.RunCompletionCommand (); + } + + public override void RunShowCodeTemplatesWindow () + { + if (IsInProjection ()) + return; + completionTextEditorExtension.RunShowCodeTemplatesWindow (); + } + + public override void RunParameterCompletionCommand () + { + if (IsInProjection ()) + return; + completionTextEditorExtension.RunParameterCompletionCommand (); + } + + public override bool CanRunCompletionCommand () + { + if (IsInProjection ()) + return false; + return completionTextEditorExtension.CanRunCompletionCommand (); + } + + public override bool CanRunParameterCompletionCommand () + { + if (IsInProjection ()) + return false; + return completionTextEditorExtension.CanRunParameterCompletionCommand (); + } + + public override System.Threading.Tasks.Task<CodeCompletion.ICompletionDataList> HandleCodeCompletionAsync (CodeCompletion.CodeCompletionContext completionContext, char completionChar, System.Threading.CancellationToken token) + { + return completionTextEditorExtension.HandleCodeCompletionAsync (completionContext, completionChar, token); + } + + public override System.Threading.Tasks.Task<CodeCompletion.ParameterHintingResult> HandleParameterCompletionAsync (CodeCompletion.CodeCompletionContext completionContext, char completionChar, System.Threading.CancellationToken token) + { + return completionTextEditorExtension.HandleParameterCompletionAsync (completionContext, completionChar, token); + } + + public override bool GetCompletionCommandOffset (out int cpos, out int wlen) + { + if (IsInProjection ()) { + cpos = 0; wlen = 0; + return false; + } + return completionTextEditorExtension.GetCompletionCommandOffset (out cpos, out wlen); + } + + public override CodeCompletion.ICompletionDataList ShowCodeSurroundingsCommand (CodeCompletion.CodeCompletionContext completionContext) + { + if (IsInProjection ()) return null; + return completionTextEditorExtension.ShowCodeSurroundingsCommand (completionContext); + } + + public override CodeCompletion.ICompletionDataList ShowCodeTemplatesCommand (CodeCompletion.CodeCompletionContext completionContext) + { + if (IsInProjection ()) return null; + return completionTextEditorExtension.ShowCodeTemplatesCommand (completionContext); + } + + public override CodeCompletion.ICompletionDataList CodeCompletionCommand (CodeCompletion.CodeCompletionContext completionContext) + { + if (IsInProjection ()) return null; + return completionTextEditorExtension.CodeCompletionCommand (completionContext); + } + + public override CodeCompletion.ParameterHintingResult ParameterCompletionCommand (CodeCompletion.CodeCompletionContext completionContext) + { + if (IsInProjection ()) return null; + return completionTextEditorExtension.ParameterCompletionCommand (completionContext); + } + + public override int GuessBestMethodOverload (CodeCompletion.ParameterHintingResult provider, int currentOverload) + { + if (IsInProjection ()) return -1; + return completionTextEditorExtension.GuessBestMethodOverload (provider, currentOverload); + } + + protected override void Initialize () + { + completionTextEditorExtension.InternalInitialize (); + } + + public override void Dispose () + { + completionTextEditorExtension.Dispose (); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedSegment.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedSegment.cs new file mode 100644 index 0000000000..8a49d40ca3 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedSegment.cs @@ -0,0 +1,89 @@ +// +// ProjectedSegment.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; + +namespace MonoDevelop.Ide.Editor.Projection +{ + public struct ProjectedSegment + { + public int Offset { + get; + private set; + } + + public int ProjectedOffset { + get; + private set; + } + + public int Length { + get; + private set; + } + + public ProjectedSegment (int offset, int projectedOffset, int length) + : this () + { + this.Offset = offset; + this.ProjectedOffset = projectedOffset; + this.Length = length; + } + + public bool ContainsOriginal (int offset) + { + return Offset <= offset && offset < Offset + Length; + } + + public bool ContainsProjected (int offset) + { + return ProjectedOffset <= offset && offset < ProjectedOffset + Length; + } + + public bool IsInOriginal (ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + + return segment.Contains(Offset) && segment.Contains (Offset + Length); + } + + public ISegment FromOriginalToProjected (ISegment segment) + { + return new TextSegment (segment.Offset - Offset + ProjectedOffset, segment.Length); + } + + public int FromOriginalToProjected (int offset) + { + return offset - Offset + ProjectedOffset; + } + + public int FromProjectedToOriginal (int offset) + { + return offset + Offset - ProjectedOffset; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedSemanticHighlighting.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedSemanticHighlighting.cs new file mode 100644 index 0000000000..82ceb984a2 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedSemanticHighlighting.cs @@ -0,0 +1,98 @@ +// +// ProjectedSemanticHighlighting.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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.Linq; +using MonoDevelop.Ide.Editor.Highlighting; +using System.Collections.Generic; + +namespace MonoDevelop.Ide.Editor.Projection +{ + sealed class ProjectedSemanticHighlighting : SemanticHighlighting + { + List<Projection> projections; + + public ProjectedSemanticHighlighting (TextEditor editor, DocumentContext documentContext, IEnumerable<Projection> projections) : base (editor, documentContext) + { + this.projections = new List<Projection> (projections); + foreach (var p in this.projections) { + if (p.ProjectedEditor.SemanticHighlighting == null) + continue; + p.ProjectedEditor.SemanticHighlighting.SemanticHighlightingUpdated += HandleSemanticHighlightingUpdated; + } + } + + public void UpdateProjection (IEnumerable<Projection> projections) + { + foreach (var p in this.projections) { + if (p.ProjectedEditor.SemanticHighlighting == null) + continue; + p.ProjectedEditor.SemanticHighlighting.SemanticHighlightingUpdated -= HandleSemanticHighlightingUpdated; + } + this.projections = new List<Projection> (projections); + foreach (var p in this.projections) { + if (p.ProjectedEditor.SemanticHighlighting == null) + continue; + p.ProjectedEditor.SemanticHighlighting.SemanticHighlightingUpdated += HandleSemanticHighlightingUpdated; + } + } + + void HandleSemanticHighlightingUpdated (object sender, EventArgs e) + { + NotifySemanticHighlightingUpdate (); + } + + protected override void DocumentParsed () + { + NotifySemanticHighlightingUpdate (); + } + + public override IEnumerable<ColoredSegment> GetColoredSegments (MonoDevelop.Core.Text.ISegment segment) + { + foreach (Projection p in projections) { + foreach (var seg in p.ProjectedSegments) { + if (seg.IsInOriginal (segment)) { + if (p.ProjectedEditor.SemanticHighlighting == null) + continue; + + int delta = segment.EndOffset - (seg.Offset + seg.Length); + int len = seg.Length; + if (delta < 0) { + len += delta; + } + foreach (var cs in p.ProjectedEditor.SemanticHighlighting.GetColoredSegments (new MonoDevelop.Core.Text.TextSegment (seg.ProjectedOffset, len))) { + yield return new ColoredSegment (cs.Offset - seg.ProjectedOffset + seg.Offset, cs.Length, cs.ColorStyleKey); + + } + foreach (var cs in GetColoredSegments (MonoDevelop.Core.Text.TextSegment.FromBounds (seg.Offset + len, segment.EndOffset))) + yield return cs; + yield break; + } + } + } + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedTooltipProvider.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedTooltipProvider.cs new file mode 100644 index 0000000000..bb50723ae7 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/ProjectedTooltipProvider.cs @@ -0,0 +1,95 @@ +// +// ProjectedTooltipProvider.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Components; + +namespace MonoDevelop.Ide.Editor.Projection +{ + sealed class ProjectedTooltipProvider : TooltipProvider + { + readonly TextEditor editor; + readonly DocumentContext ctx; + readonly Projection projection; + readonly TooltipProvider projectedTooltipProvider; + + public ProjectedTooltipProvider (TextEditor editor, DocumentContext ctx, Projection projection, TooltipProvider projectedTooltipProvider) + { + if (editor == null) + throw new ArgumentNullException ("editor"); + if (ctx == null) + throw new ArgumentNullException ("ctx"); + if (projection == null) + throw new ArgumentNullException ("projection"); + if (projectedTooltipProvider == null) + throw new ArgumentNullException ("projectedTooltipProvider"); + this.projectedTooltipProvider = projectedTooltipProvider; + this.projection = projection; + this.editor = editor; + this.ctx = ctx; + } + + public override TooltipItem GetItem (TextEditor editor, DocumentContext ctx, int offset) + { + foreach (var pseg in projection.ProjectedSegments) { + if (pseg.ContainsOriginal (offset)) { + var result = projectedTooltipProvider.GetItem (projection.ProjectedEditor, projection.ProjectedContext, pseg.FromOriginalToProjected (offset)); + if (result == null) + return null; + result.Offset = pseg.FromProjectedToOriginal (result.Offset); + return result; + } + } + return null; + } + + public override bool IsInteractive (TextEditor editor, Control tipWindow) + { + return projectedTooltipProvider.IsInteractive (editor, tipWindow); + } + + public override void ShowTooltipWindow (TextEditor editor, Control tipWindow, TooltipItem item, Gdk.ModifierType modifierState, int mouseX, int mouseY) + { + projectedTooltipProvider.ShowTooltipWindow (editor, tipWindow, item, modifierState, mouseX, mouseY); + } + + public override void GetRequiredPosition (TextEditor editor, Control tipWindow, out int requiredWidth, out double xalign) + { + projectedTooltipProvider.GetRequiredPosition (editor, tipWindow, out requiredWidth, out xalign); + } + + public override Control CreateTooltipWindow (TextEditor editor, DocumentContext ctx, TooltipItem item, int offset, Gdk.ModifierType modifierState) + { + foreach (var pseg in projection.ProjectedSegments) { + if (pseg.ContainsOriginal (offset)) { + return projectedTooltipProvider.CreateTooltipWindow (projection.ProjectedEditor, projection.ProjectedContext, item, pseg.FromOriginalToProjected (offset), modifierState); + } + } + return null; + } + + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/Projection.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/Projection.cs new file mode 100644 index 0000000000..8698e57c4c --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Projection/Projection.cs @@ -0,0 +1,130 @@ +// +// Projection.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Ide.Editor; +using System.Collections.Immutable; +using MonoDevelop.Core.Text; +using MonoDevelop.Ide.Gui; +using MonoDevelop.Ide.TypeSystem; +using System.Collections.Generic; + +namespace MonoDevelop.Ide.Editor.Projection +{ + public sealed class Projection + { + public ITextDocument Document { get; private set; } + + SegmentTree<ProjectedTreeSegment> originalProjections = new SegmentTree<ProjectedTreeSegment> (); + SegmentTree<ProjectedTreeSegment> projectedProjections = new SegmentTree<ProjectedTreeSegment> (); + + class ProjectedTreeSegment : TreeSegment + { + public ProjectedTreeSegment LinkedTo { get; set; } + + public ProjectedTreeSegment (int offset, int length) : base (offset, length) + { + } + } + + public IEnumerable<ProjectedSegment> ProjectedSegments { + get { + foreach (var treeSeg in originalProjections) { + yield return new ProjectedSegment (treeSeg.Offset, treeSeg.LinkedTo.Offset, treeSeg.Length); + } + } + } + + TextEditor projectedEditor; + + internal TextEditor ProjectedEditor + { + get + { + return projectedEditor; + } + } + + ProjectedDocumentContext projectedDocumentContext; + TextEditor attachedEditor; + + internal DocumentContext ProjectedContext { + get { + return projectedDocumentContext; + } + } + + public TextEditor CreateProjectedEditor (DocumentContext originalContext) + { + if (projectedEditor == null) { + projectedEditor = TextEditorFactory.CreateNewEditor (Document); + projectedDocumentContext = new ProjectedDocumentContext (projectedEditor, originalContext); + projectedEditor.InitializeExtensionChain (projectedDocumentContext); + projectedProjections.InstallListener (projectedEditor); + } + return projectedEditor; + } + + public Projection (ITextDocument document, IReadOnlyList<ProjectedSegment> projectedSegments) + { + if (document == null) + throw new ArgumentNullException (nameof (document)); + this.Document = document; + + for (int i = 0; i < projectedSegments.Count; i++) { + var p = projectedSegments [i]; + var original = new ProjectedTreeSegment (p.Offset, p.Length); + var projected = new ProjectedTreeSegment (p.ProjectedOffset, p.Length); + original.LinkedTo = projected; + projected.LinkedTo = original; + originalProjections.Add (original); + projectedProjections.Add (projected); + } + } + + internal void Dettach () + { + attachedEditor.TextChanged -= HandleTextChanged; + } + + internal void Attach (TextEditor textEditor) + { + attachedEditor = textEditor; + attachedEditor.TextChanged += HandleTextChanged; + } + + void HandleTextChanged (object sender, TextChangeEventArgs e) + { + foreach (var segment in originalProjections) { + if (segment.Contains (e.Offset)) { + var projectedOffset = e.Offset - segment.Offset + segment.LinkedTo.Offset; + projectedEditor.ReplaceText (projectedOffset, e.RemovalLength, e.InsertedText); + } + } + + originalProjections.UpdateOnTextReplace (sender, e); + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/SegmentTree.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/SegmentTree.cs new file mode 100644 index 0000000000..e22632c0b9 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/SegmentTree.cs @@ -0,0 +1,801 @@ +// +// SegmentTree.cs +// +// Author: +// mkrueger <mkrueger@novell.com> +// +// Copyright (c) 2011 Novell, Inc (http://www.novell.com) +// +// 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.Linq; +using MonoDevelop.Core.Text; +using System.Text; +using System.Web.UI.WebControls; +using System.Diagnostics; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// A segment tree contains overlapping segments and get all segments overlapping a segment. It's implemented as a augmented interval tree + /// described in Cormen et al. (2001, Section 14.3: Interval trees, pp. 311–317). + /// </summary> + public class SegmentTree<T> : TextSegmentTree, ICollection<T> where T : TreeSegment + { + readonly RedBlackTree tree = new RedBlackTree (); + + ITextDocument ownerDocument; + + public int Count { + get { + return tree.Count; + } + } + + public IEnumerator<T> GetEnumerator () + { + var root = tree.Root; + if (root == null) + yield break; + var node = root.OuterLeft; + while (node != null) { + yield return (T)node; + node = node.NextNode; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return GetEnumerator (); + } + + public bool Contains (T item) + { + return this.Any (item.Equals); + } + + public void CopyTo (T[] array, int arrayIndex) + { + Debug.Assert (array != null); + Debug.Assert (0 <= arrayIndex && arrayIndex < array.Length); + int i = arrayIndex; + foreach (T value in this) + array[i++] = value; + } + + bool ICollection<T>.IsReadOnly { + get { + return false; + } + } + + public void Add (T item) + { + InternalAdd (item); + } + + public bool Remove (T item) + { + return InternalRemove (item); + } + + public void Clear () + { + tree.Clear (); + } + + public IEnumerable<T> GetSegmentsAt (int offset) + { + return GetSegmentsOverlapping (offset, 0); + } + + public IEnumerable<T> GetSegmentsOverlapping (ISegment segment) + { + if (segment.Offset < 0) + return Enumerable.Empty<T> (); + return GetSegmentsOverlapping (segment.Offset, segment.Length); + } + + public IEnumerable<T> GetSegmentsOverlapping (int offset, int length) + { + if (tree.Root == null) + yield break; + var intervalStack = new Stack<Interval> (); + intervalStack.Push (new Interval (tree.Root, offset, offset + length)); + while (intervalStack.Count > 0) { + var interval = intervalStack.Pop (); + if (interval.end < 0) + continue; + + var node = interval.node; + int nodeStart = interval.start - node.DistanceToPrevNode; + int nodeEnd = interval.end - node.DistanceToPrevNode; + var leftNode = node.Left; + if (leftNode != null) { + nodeStart -= leftNode.TotalLength; + nodeEnd -= leftNode.TotalLength; + } + + if (node.DistanceToMaxEnd < nodeStart) + continue; + + if (leftNode != null) + intervalStack.Push (new Interval (leftNode, interval.start, interval.end)); + + if (nodeEnd < 0) + continue; + + if (nodeStart <= node.Length) + yield return (T)node; + + var rightNode = node.Right; + if (rightNode != null) + intervalStack.Push (new Interval (rightNode, nodeStart, nodeEnd)); + } + } + + public void InstallListener (ITextDocument doc) + { + if (ownerDocument != null) + throw new InvalidOperationException ("Segment tree already installed"); + ownerDocument = doc; + doc.TextChanged += UpdateOnTextReplace; + } + + public void RemoveListener () + { + if (ownerDocument == null) + throw new InvalidOperationException ("Segment tree is not installed"); + ownerDocument.TextChanged -= UpdateOnTextReplace; + ownerDocument = null; + } + + internal void UpdateOnTextReplace (object sender, TextChangeEventArgs e) + { + if (e.RemovalLength == 0) { + var length = e.InsertionLength; + foreach (var segment in GetSegmentsAt (e.Offset).Where (s => s.Offset < e.Offset && e.Offset < s.EndOffset)) { + segment.Length += length; + segment.UpdateAugmentedData (); + } + var node = SearchFirstSegmentWithStartAfter (e.Offset); + if (node != null) { + node.DistanceToPrevNode += length; + node.UpdateAugmentedData (); + } + return; + } + int delta = e.ChangeDelta; + foreach (var segment in new List<T> (GetSegmentsOverlapping (e.Offset, e.RemovalLength))) { + if (segment.Offset < e.Offset) { + if (segment.EndOffset >= e.Offset + e.RemovalLength) { + segment.Length += delta; + } else { + segment.Length = e.Offset - segment.Offset; + } + segment.UpdateAugmentedData (); + continue; + } + int remainingLength = segment.EndOffset - (e.Offset + e.RemovalLength); + InternalRemove (segment); + if (remainingLength > 0) { + segment.Offset = e.Offset + e.RemovalLength; + segment.Length = remainingLength; + InternalAdd (segment); + } + } + var next = SearchFirstSegmentWithStartAfter (e.Offset + 1); + + if (next != null) { + next.DistanceToPrevNode += delta; + next.UpdateAugmentedData (); + } + } + + void InternalAdd (TreeSegment node) + { + if (node == null) + throw new ArgumentNullException ("node"); + if (node.segmentTree != null) + throw new InvalidOperationException ("Node already attached."); + + node.segmentTree = this; + + + int insertionOffset = node.Offset; + node.DistanceToMaxEnd = node.Length; + + if (tree.Root == null) { + tree.Count = 1; + tree.Root = (T)node; + node.TotalLength = node.DistanceToPrevNode; + return; + } + + if (insertionOffset < tree.Root.TotalLength) { + var n = SearchNode (ref insertionOffset); + node.TotalLength = node.DistanceToPrevNode = insertionOffset; + n.DistanceToPrevNode -= insertionOffset; + tree.InsertBefore (n, node); + return; + } + + node.DistanceToPrevNode = node.TotalLength = insertionOffset - tree.Root.TotalLength; + tree.InsertRight (tree.Root.OuterRight, node); + } + + bool InternalRemove (TreeSegment node) + { + if (node.segmentTree == null) + return false; + if (node.segmentTree != this) + throw new InvalidOperationException ("Tried to remove tree segment from wrong tree."); + var calculatedOffset = node.Offset; + var next = node.NextNode; + if (next != null) + next.DistanceToPrevNode += node.DistanceToPrevNode; + tree.Remove (node); + if (next != null) + next.UpdateAugmentedData (); + node.segmentTree = null; + node.Parent = node.Left = node.Right = null; + node.DistanceToPrevNode = calculatedOffset; + return true; + } + + TreeSegment SearchFirstSegmentWithStartAfter (int startOffset) + { + if (tree.Root == null) + return null; + if (startOffset <= 0) + return tree.Root.OuterLeft; + var result = SearchNode (ref startOffset); + while (startOffset == 0) { + var pre = result == null ? tree.Root.OuterRight : result.PrevNode; + if (pre == null) + return null; + startOffset += pre.DistanceToPrevNode; + result = pre; + } + return result; + } + + TreeSegment SearchNode (ref int offset) + { + TreeSegment n = tree.Root; + while (true) { + if (n.Left != null) { + if (offset < n.Left.TotalLength) { + n = n.Left; + continue; + } + offset -= n.Left.TotalLength; + } + if (offset < n.DistanceToPrevNode) + return n; + offset -= n.DistanceToPrevNode; + if (n.Right == null) + return null; + n = n.Right; + } + } + + #region TextSegmentTree implementation + + void TextSegmentTree.Add (TreeSegment segment) + { + InternalAdd (segment); + } + + bool TextSegmentTree.Remove (TreeSegment segment) + { + return InternalRemove (segment); + } + + #endregion + + const bool Black = false; + const bool Red = true; + + struct Interval + { + internal TreeSegment node; + internal int start, end; + + public Interval (TreeSegment node,int start,int end) + { + this.node = node; + this.start = start; + this.end = end; + } + + public override string ToString () + { + return string.Format ("[Interval: start={0},end={1}]", start, end); + } + } + + sealed class RedBlackTree + { + public T Root { get; set; } + + public void InsertBefore (TreeSegment node, TreeSegment newNode) + { + if (node.Left == null) { + InsertLeft (node, newNode); + } else { + InsertRight (node.Left.OuterRight, newNode); + } + } + + public void InsertLeft (TreeSegment parentNode, TreeSegment newNode) + { + parentNode.Left = newNode; + newNode.Parent = parentNode; + newNode.Color = Red; + parentNode.UpdateAugmentedData (); + FixTreeOnInsert (newNode); + Count++; + } + + public void InsertRight (TreeSegment parentNode, TreeSegment newNode) + { + parentNode.Right = newNode; + newNode.Parent = parentNode; + newNode.Color = Red; + parentNode.UpdateAugmentedData (); + FixTreeOnInsert (newNode); + Count++; + } + + void FixTreeOnInsert (TreeSegment node) + { + var parent = node.Parent; + if (parent == null) { + node.Color = Black; + return; + } + + if (parent.Color == Black) + return; + var uncle = node.Uncle; + TreeSegment grandParent = parent.Parent; + + if (uncle != null && uncle.Color == Red) { + parent.Color = Black; + uncle.Color = Black; + grandParent.Color = Red; + FixTreeOnInsert (grandParent); + return; + } + + if (node == parent.Right && parent == grandParent.Left) { + RotateLeft (parent); + node = node.Left; + } else if (node == parent.Left && parent == grandParent.Right) { + RotateRight (parent); + node = node.Right; + } + + parent = node.Parent; + grandParent = parent.Parent; + + parent.Color = Black; + grandParent.Color = Red; + if (node == parent.Left && parent == grandParent.Left) { + RotateRight (grandParent); + } else { + RotateLeft (grandParent); + } + } + + void RotateLeft (TreeSegment node) + { + TreeSegment right = node.Right; + Replace (node, right); + node.Right = right.Left; + if (node.Right != null) + node.Right.Parent = node; + right.Left = node; + node.Parent = right; + node.UpdateAugmentedData (); + node.Parent.UpdateAugmentedData (); + } + + void RotateRight (TreeSegment node) + { + TreeSegment left = node.Left; + Replace (node, left); + node.Left = left.Right; + if (node.Left != null) + node.Left.Parent = node; + left.Right = node; + node.Parent = left; + node.UpdateAugmentedData (); + node.Parent.UpdateAugmentedData (); + } + + void Replace (TreeSegment oldNode, TreeSegment newNode) + { + if (newNode != null) + newNode.Parent = oldNode.Parent; + if (oldNode.Parent == null) { + Root = (T)newNode; + } else { + if (oldNode.Parent.Left == oldNode) + oldNode.Parent.Left = newNode; + else + oldNode.Parent.Right = newNode; + oldNode.Parent.UpdateAugmentedData (); + } + } + + public void Remove (TreeSegment node) + { + if (node.Left != null && node.Right != null) { + var outerLeft = node.Right.OuterLeft; + InternalRemove (outerLeft); + Replace (node, outerLeft); + + outerLeft.Color = node.Color; + outerLeft.Left = node.Left; + if (outerLeft.Left != null) + outerLeft.Left.Parent = outerLeft; + + outerLeft.Right = node.Right; + if (outerLeft.Right != null) + outerLeft.Right.Parent = outerLeft; + outerLeft.UpdateAugmentedData (); + return; + } + InternalRemove (node); + } + + void InternalRemove (TreeSegment node) + { + if (node.Left != null && node.Right != null) { + var outerLeft = node.Right.OuterLeft; + InternalRemove (outerLeft); + Replace (node, outerLeft); + + outerLeft.Color = node.Color; + outerLeft.Left = node.Left; + if (outerLeft.Left != null) + outerLeft.Left.Parent = outerLeft; + + outerLeft.Right = node.Right; + if (outerLeft.Right != null) + outerLeft.Right.Parent = outerLeft; + outerLeft.UpdateAugmentedData (); + return; + } + Count--; + // node has only one child + TreeSegment child = node.Left ?? node.Right; + + Replace (node, child); + + if (node.Color == Black && child != null) { + if (child.Color == Red) { + child.Color = Black; + } else { + DeleteOneChild (child); + } + } + } + + static bool GetColorSafe (TreeSegment node) + { + return node != null ? node.Color : Black; + } + + void DeleteOneChild (TreeSegment node) + { + // case 1 + if (node == null || node.Parent == null) + return; + + var parent = node.Parent; + var sibling = node.Sibling; + if (sibling == null) + return; + + // case 2 + if (sibling.Color == Red) { + parent.Color = Red; + sibling.Color = Black; + if (node == parent.Left) { + RotateLeft (parent); + } else { + RotateRight (parent); + } + sibling = node.Sibling; + if (sibling == null) + return; + } + + // case 3 + if (parent.Color == Black && sibling.Color == Black && GetColorSafe (sibling.Left) == Black && GetColorSafe (sibling.Right) == Black) { + sibling.Color = Red; + DeleteOneChild (parent); + return; + } + + // case 4 + if (parent.Color == Red && sibling.Color == Black && GetColorSafe (sibling.Left) == Black && GetColorSafe (sibling.Right) == Black) { + sibling.Color = Red; + parent.Color = Black; + return; + } + + // case 5 + if (node == parent.Left && sibling.Color == Black && GetColorSafe (sibling.Left) == Red && GetColorSafe (sibling.Right) == Black) { + sibling.Color = Red; + if (sibling.Left != null) + sibling.Left.Color = Black; + RotateRight (sibling); + } else if (node == parent.Right && sibling.Color == Black && GetColorSafe (sibling.Right) == Red && GetColorSafe (sibling.Left) == Black) { + sibling.Color = Red; + if (sibling.Right != null) + sibling.Right.Color = Black; + RotateLeft (sibling); + } + + // case 6 + sibling = node.Sibling; + if (sibling == null) + return; + sibling.Color = parent.Color; + parent.Color = Black; + if (node == parent.Left) { + if (sibling.Right != null) + sibling.Right.Color = Black; + RotateLeft (parent); + } else { + if (sibling.Left != null) + sibling.Left.Color = Black; + RotateRight (parent); + } + } + + public int Count { get; set; } + + public void Clear () + { + Root = null; + Count = 0; + } + + static string GetIndent (int level) + { + return new String ('\t', level); + } + + static void AppendNode (StringBuilder builder, TreeSegment node, int indent) + { + builder.Append (GetIndent (indent) + "Node (" + (node.Color == Red ? "r" : "b") + "):" + node + Environment.NewLine); + builder.Append (GetIndent (indent) + "Left: "); + if (node.Left != null) { + builder.Append (Environment.NewLine); + AppendNode (builder, node.Left, indent + 1); + } else { + builder.Append ("null"); + } + + builder.Append (Environment.NewLine); + builder.Append (GetIndent (indent) + "Right: "); + if (node.Right != null) { + builder.Append (Environment.NewLine); + AppendNode (builder, node.Right, indent + 1); + } else { + builder.Append ("null"); + } + } + + public override string ToString () + { + if (Root == null) + return "<null>"; + var result = new StringBuilder (); + AppendNode (result, Root, 0); + return result.ToString (); + } + } + } + + interface TextSegmentTree + { + void Add (TreeSegment segment); + bool Remove (TreeSegment segment); + } + + public class TreeSegment : ISegment + { + public int Offset { + get { + if (segmentTree == null) + return DistanceToPrevNode; + + var curNode = this; + int offset = curNode.DistanceToPrevNode; + if (curNode.Left != null) + offset += curNode.Left.TotalLength; + while (curNode.Parent != null) { + if (curNode == curNode.Parent.Right) { + if (curNode.Parent.Left != null) + offset += curNode.Parent.Left.TotalLength; + offset += curNode.Parent.DistanceToPrevNode; + } + curNode = curNode.Parent; + } + return offset; + } + set { + if (segmentTree != null) + segmentTree.Remove (this); + DistanceToPrevNode = value; + if (segmentTree != null) + segmentTree.Add (this); + } + } + + public int Length { + get; + set; + } + + public int EndOffset { + get { + return Offset + Length; + } + } + + protected TreeSegment () + { + } + + public TreeSegment (int offset, int length) + { + Offset = offset; + Length = length; + } + + public TreeSegment (ISegment segment) : this (segment.Offset, segment.Length) + { + } + + #region Internal API + internal TextSegmentTree segmentTree; + internal TreeSegment Parent, Left, Right; + internal bool Color; + + // TotalLength = DistanceToPrevNode + Left.DistanceToPrevNode + Right.DistanceToPrevNode + internal int TotalLength; + + internal int DistanceToPrevNode; + + // DistanceToMaxEnd = Max (Length, left.DistanceToMaxEnd + Max (left.Offset, right.Offset) - Offset) + internal int DistanceToMaxEnd; + + internal void UpdateAugmentedData () + { + int totalLength = DistanceToPrevNode; + int distanceToMaxEnd = Length; + + if (Left != null) { + totalLength += Left.TotalLength; + int leftdistance = Left.DistanceToMaxEnd - DistanceToPrevNode; + if (Left.Right != null) + leftdistance -= Left.Right.TotalLength; + if (leftdistance > distanceToMaxEnd) + distanceToMaxEnd = leftdistance; + } + + if (Right != null) { + totalLength += Right.TotalLength; + int rightdistance = Right.DistanceToMaxEnd + Right.DistanceToPrevNode; + if (Right.Left != null) + rightdistance += Right.Left.TotalLength; + if (rightdistance > distanceToMaxEnd) + distanceToMaxEnd = rightdistance; + } + + if (TotalLength != totalLength || DistanceToMaxEnd != distanceToMaxEnd) { + TotalLength = totalLength; + DistanceToMaxEnd = distanceToMaxEnd; + if (Parent != null) + Parent.UpdateAugmentedData (); + } + } + + internal TreeSegment Sibling { + get { + if (Parent == null) + return null; + return this == Parent.Left ? Parent.Right : Parent.Left; + } + } + + internal TreeSegment OuterLeft { + get { + TreeSegment result = this; + while (result.Left != null) + result = result.Left; + return result; + } + } + + internal TreeSegment OuterRight { + get { + TreeSegment result = this; + while (result.Right != null) { + result = result.Right; + } + return result; + } + } + + internal TreeSegment Grandparent { + get { + return Parent != null ? Parent.Parent : null; + } + } + + internal TreeSegment Uncle { + get { + TreeSegment grandparent = Grandparent; + if (grandparent == null) + return null; + return Parent == grandparent.Left ? grandparent.Right : grandparent.Left; + } + } + + internal TreeSegment NextNode { + get { + if (Right == null) { + TreeSegment curNode = this; + TreeSegment oldNode; + do { + oldNode = curNode; + curNode = curNode.Parent; + } while (curNode != null && curNode.Right == oldNode); + return curNode; + } + return Right.OuterLeft; + } + } + + internal TreeSegment PrevNode { + get { + if (Left == null) { + TreeSegment curNode = this; + TreeSegment oldNode; + do { + oldNode = curNode; + curNode = curNode.Parent; + } while (curNode != null && curNode.Left == oldNode); + return curNode; + } + return Left.OuterRight; + } + } + #endregion + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/SelectionMode.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/SelectionMode.cs new file mode 100644 index 0000000000..71b981f189 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/SelectionMode.cs @@ -0,0 +1,33 @@ +// +// SelectionMode.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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. + +namespace MonoDevelop.Ide.Editor +{ + public enum SelectionMode { + Normal, + Block + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs new file mode 100644 index 0000000000..444f64fd04 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs @@ -0,0 +1,1260 @@ +// +// ITextEditor.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using System.Collections.Generic; +using System.Text; +using MonoDevelop.Ide.Gui; +using MonoDevelop.Ide.Editor.Extension; +using System.IO; +using MonoDevelop.Ide.Editor.Highlighting; +using Mono.Addins; +using MonoDevelop.Core; +using MonoDevelop.Ide.Extensions; +using System.Linq; +using MonoDevelop.Components; +using System.ComponentModel; +using MonoDevelop.Ide.TypeSystem; +using System.Threading; +using MonoDevelop.Ide.Editor.Projection; + +namespace MonoDevelop.Ide.Editor +{ + public sealed class TextEditor : Control, ITextDocument, IDisposable + { + readonly ITextEditorImpl textEditorImpl; + IReadonlyTextDocument ReadOnlyTextDocument { get { return textEditorImpl.Document; } } + ITextDocument ReadWriteTextDocument { get { return (ITextDocument)textEditorImpl.Document; } } + + public ITextSourceVersion Version { + get { + return ReadOnlyTextDocument.Version; + } + } + + FileTypeCondition fileTypeCondition = new FileTypeCondition (); + + List<TooltipExtensionNode> allProviders = new List<TooltipExtensionNode> (); + + void OnTooltipProviderChanged (object s, ExtensionNodeEventArgs a) + { + TooltipProvider provider; + try { + var extensionNode = a.ExtensionNode as TooltipExtensionNode; + allProviders.Add (extensionNode); + if (extensionNode.IsValidFor (MimeType)) + return; + provider = (TooltipProvider) extensionNode.CreateInstance (); + } catch (Exception e) { + LoggingService.LogError ("Can't create tooltip provider:"+ a.ExtensionNode, e); + return; + } + if (a.Change == ExtensionChange.Add) { + textEditorImpl.AddTooltipProvider (provider); + } else { + textEditorImpl.RemoveTooltipProvider (provider); + } + } + + public event EventHandler SelectionChanged { + add { textEditorImpl.SelectionChanged += value; } + remove { textEditorImpl.SelectionChanged -= value; } + } + + public event EventHandler CaretPositionChanged { + add { textEditorImpl.CaretPositionChanged += value; } + remove { textEditorImpl.CaretPositionChanged -= value; } + } + + public event EventHandler BeginAtomicUndoOperation { + add { textEditorImpl.BeginAtomicUndoOperation += value; } + remove { textEditorImpl.BeginAtomicUndoOperation -= value; } + } + + public event EventHandler EndAtomicUndoOperation { + add { textEditorImpl.EndAtomicUndoOperation += value; } + remove { textEditorImpl.EndAtomicUndoOperation -= value; } + } + + public event EventHandler<TextChangeEventArgs> TextChanging { + add { ReadWriteTextDocument.TextChanging += value; } + remove { ReadWriteTextDocument.TextChanging -= value; } + } + + public event EventHandler<TextChangeEventArgs> TextChanged { + add { ReadWriteTextDocument.TextChanged += value; } + remove { ReadWriteTextDocument.TextChanged -= value; } + } + + public event EventHandler BeginMouseHover { + add { textEditorImpl.BeginMouseHover += value; } + remove { textEditorImpl.BeginMouseHover -= value; } + } + + public event EventHandler VAdjustmentChanged { + add { textEditorImpl.VAdjustmentChanged += value; } + remove { textEditorImpl.VAdjustmentChanged -= value; } + } + + public event EventHandler HAdjustmentChanged { + add { textEditorImpl.HAdjustmentChanged += value; } + remove { textEditorImpl.HAdjustmentChanged -= value; } + } + public char this[int offset] { + get { + return ReadOnlyTextDocument [offset]; + } + set { + ReadWriteTextDocument [offset] = value; + } + } + +// public event EventHandler<LineEventArgs> LineChanged { +// add { textEditorImpl.LineChanged += value; } +// remove { textEditorImpl.LineChanged -= value; } +// } +// +// public event EventHandler<LineEventArgs> LineInserted { +// add { textEditorImpl.LineInserted += value; } +// remove { textEditorImpl.LineInserted -= value; } +// } +// +// public event EventHandler<LineEventArgs> LineRemoved { +// add { textEditorImpl.LineRemoved += value; } +// remove { textEditorImpl.LineRemoved -= value; } +// } + + public ITextEditorOptions Options { + get { + return textEditorImpl.Options; + } + set { + textEditorImpl.Options = value; + OnOptionsChanged (EventArgs.Empty); + } + } + + public event EventHandler OptionsChanged; + + void OnOptionsChanged (EventArgs e) + { + var handler = OptionsChanged; + if (handler != null) + handler (this, e); + } + + public EditMode EditMode { + get { + return textEditorImpl.EditMode; + } + } + + public DocumentLocation CaretLocation { + get { + return textEditorImpl.CaretLocation; + } + set { + textEditorImpl.CaretLocation = value; + } + } + + public SemanticHighlighting SemanticHighlighting { + get { + return textEditorImpl.SemanticHighlighting; + } + set { + textEditorImpl.SemanticHighlighting = value; + } + } + + public int CaretLine { + get { + return CaretLocation.Line; + } + set { + CaretLocation = new DocumentLocation (value, CaretColumn); + } + } + + public int CaretColumn { + get { + return CaretLocation.Column; + } + set { + CaretLocation = new DocumentLocation (CaretLine, value); + } + } + + public int CaretOffset { + get { + return textEditorImpl.CaretOffset; + } + set { + textEditorImpl.CaretOffset = value; + } + } + + public bool IsReadOnly { + get { + return ReadOnlyTextDocument.IsReadOnly; + } + set { + ReadWriteTextDocument.IsReadOnly = value; + } + } + + public bool IsSomethingSelected { + get { + return textEditorImpl.IsSomethingSelected; + } + } + + public SelectionMode SelectionMode { + get { + return textEditorImpl.SelectionMode; + } + } + + public ISegment SelectionRange { + get { + return textEditorImpl.SelectionRange; + } + set { + textEditorImpl.SelectionRange = value; + } + } + + public DocumentRegion SelectionRegion { + get { + return textEditorImpl.SelectionRegion; + } + set { + textEditorImpl.SelectionRegion = value; + } + } + + public int SelectionAnchorOffset { + get { + return textEditorImpl.SelectionAnchorOffset; + } + set { + textEditorImpl.SelectionAnchorOffset = value; + } + } + + public int SelectionLeadOffset { + get { + return textEditorImpl.SelectionLeadOffset; + } + set { + textEditorImpl.SelectionLeadOffset = value; + } + } + + public string SelectedText { + get { + return IsSomethingSelected ? ReadOnlyTextDocument.GetTextAt (SelectionRange) : null; + } + set { + var selection = SelectionRange; + ReplaceText (selection, value); + SelectionRange = new TextSegment (selection.Offset, value.Length); + } + } + + public bool IsInAtomicUndo { + get { + return ReadWriteTextDocument.IsInAtomicUndo; + } + } + + public double LineHeight { + get { + return textEditorImpl.LineHeight; + } + } + + /// <summary> + /// Gets or sets the type of the MIME. + /// </summary> + /// <value>The type of the MIME.</value> + public string MimeType { + get { + return ReadOnlyTextDocument.MimeType; + } + set { + ReadWriteTextDocument.MimeType = value; + } + } + + public event EventHandler MimeTypeChanged { + add { ReadWriteTextDocument.MimeTypeChanged += value; } + remove { ReadWriteTextDocument.MimeTypeChanged -= value; } + } + + public string Text { + get { + return ReadOnlyTextDocument.Text; + } + set { + ReadWriteTextDocument.Text = value; + } + } + + /// <summary> + /// Gets the eol marker. On a text editor always use that and not GetEolMarker. + /// The EOL marker of the document may get overwritten my the one from the options. + /// </summary> + public string EolMarker { + get { + if (Options.OverrideDocumentEolMarker) + return Options.DefaultEolMarker; + return ReadOnlyTextDocument.GetEolMarker (); + } + } + + public bool UseBOM { + get { + return ReadOnlyTextDocument.UseBOM; + } + set { + ReadWriteTextDocument.UseBOM = value; + } + } + + public Encoding Encoding { + get { + return ReadOnlyTextDocument.Encoding; + } + set { + ReadWriteTextDocument.Encoding = value; + } + } + + public int LineCount { + get { + return ReadOnlyTextDocument.LineCount; + } + } + + /// <summary> + /// Gets the name of the file the document is stored in. + /// Could also be a non-existent dummy file name or null if no name has been set. + /// </summary> + public FilePath FileName { + get { + return ReadOnlyTextDocument.FileName; + } + set { + ReadWriteTextDocument.FileName = value; + } + } + + public event EventHandler FileNameChanged { + add { + ReadWriteTextDocument.FileNameChanged += value; + } + remove { + ReadWriteTextDocument.FileNameChanged -= value; + } + } + + public int Length { + get { + return ReadOnlyTextDocument.Length; + } + } + + public double ZoomLevel { + get { + return textEditorImpl.ZoomLevel; + } + set { + textEditorImpl.ZoomLevel = value; + } + } + + public event EventHandler ZoomLevelChanged { + add { + textEditorImpl.ZoomLevelChanged += value; + } + remove { + textEditorImpl.ZoomLevelChanged -= value; + } + } + + public IDisposable OpenUndoGroup () + { + return ReadWriteTextDocument.OpenUndoGroup (); + } + + public void SetSelection (int anchorOffset, int leadOffset) + { + textEditorImpl.SetSelection (anchorOffset, leadOffset); + } + + public void SetSelection (DocumentLocation anchor, DocumentLocation lead) + { + SetSelection (LocationToOffset (anchor), LocationToOffset (lead)); + } + + public void SetCaretLocation (DocumentLocation location, bool usePulseAnimation = false) + { + CaretLocation = location; + ScrollTo (CaretLocation); + if (usePulseAnimation) + StartCaretPulseAnimation (); + } + + public void SetCaretLocation (int line, int col, bool usePulseAnimation = false) + { + CaretLocation = new DocumentLocation (line, col); + CenterTo (CaretLocation); + if (usePulseAnimation) + StartCaretPulseAnimation (); + } + + public void ClearSelection () + { + textEditorImpl.ClearSelection (); + } + + public void CenterToCaret () + { + textEditorImpl.CenterToCaret (); + } + + public void StartCaretPulseAnimation () + { + textEditorImpl.StartCaretPulseAnimation (); + } + + public int EnsureCaretIsNotVirtual () + { + return textEditorImpl.EnsureCaretIsNotVirtual (); + } + + public void FixVirtualIndentation () + { + textEditorImpl.FixVirtualIndentation (); + } + + public void RunWhenLoaded (Action action) + { + if (action == null) + throw new ArgumentNullException ("action"); + textEditorImpl.RunWhenLoaded (action); + } + + public string FormatString (DocumentLocation insertPosition, string code) + { + return textEditorImpl.FormatString (LocationToOffset (insertPosition), code); + } + + public string FormatString (int offset, string code) + { + return textEditorImpl.FormatString (offset, code); + } + + public void StartInsertionMode (InsertionModeOptions insertionModeOptions) + { + if (insertionModeOptions == null) + throw new ArgumentNullException ("insertionModeOptions"); + textEditorImpl.StartInsertionMode (insertionModeOptions); + } + + public void StartTextLinkMode (TextLinkModeOptions textLinkModeOptions) + { + if (textLinkModeOptions == null) + throw new ArgumentNullException ("textLinkModeOptions"); + textEditorImpl.StartTextLinkMode (textLinkModeOptions); + } + + public void InsertAtCaret (string text) + { + InsertText (CaretOffset, text); + } + + public DocumentLocation PointToLocation (double xp, double yp, bool endAtEol = false) + { + return textEditorImpl.PointToLocation (xp, yp, endAtEol); + } + + public Xwt.Point LocationToPoint (DocumentLocation location) + { + return textEditorImpl.LocationToPoint (location.Line, location.Column); + } + + public Xwt.Point LocationToPoint (int line, int column) + { + return textEditorImpl.LocationToPoint (line, column); + } + + public string GetLineText (int line, bool includeDelimiter = false) + { + var segment = GetLine (line); + return GetTextAt (includeDelimiter ? segment.SegmentIncludingDelimiter : segment); + } + + public int LocationToOffset (int line, int column) + { + return ReadOnlyTextDocument.LocationToOffset (new DocumentLocation (line, column)); + } + + public int LocationToOffset (DocumentLocation location) + { + return ReadOnlyTextDocument.LocationToOffset (location); + } + + public DocumentLocation OffsetToLocation (int offset) + { + return ReadOnlyTextDocument.OffsetToLocation (offset); + } + + public void InsertText (int offset, string text) + { + ReadWriteTextDocument.InsertText (offset, text); + } + + public void InsertText (int offset, ITextSource text) + { + ReadWriteTextDocument.InsertText (offset, text); + } + + public void RemoveText (int offset, int count) + { + RemoveText (new TextSegment (offset, count)); + } + + public void RemoveText (ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + ReadWriteTextDocument.RemoveText (segment); + } + + public void ReplaceText (int offset, int count, string value) + { + ReadWriteTextDocument.ReplaceText (offset, count, value); + } + + public void ReplaceText (int offset, int count, ITextSource value) + { + ReadWriteTextDocument.ReplaceText (offset, count, value); + } + + public void ReplaceText (ISegment segment, string value) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + ReadWriteTextDocument.ReplaceText (segment.Offset, segment.Length, value); + } + + public void ReplaceText (ISegment segment, ITextSource value) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + ReadWriteTextDocument.ReplaceText (segment.Offset, segment.Length, value); + } + + public IDocumentLine GetLine (int lineNumber) + { + return ReadOnlyTextDocument.GetLine (lineNumber); + } + + public IDocumentLine GetLineByOffset (int offset) + { + return ReadOnlyTextDocument.GetLineByOffset (offset); + } + + public int OffsetToLineNumber (int offset) + { + return ReadOnlyTextDocument.OffsetToLineNumber (offset); + } + + public void AddMarker (IDocumentLine line, ITextLineMarker lineMarker) + { + if (line == null) + throw new ArgumentNullException ("line"); + if (lineMarker == null) + throw new ArgumentNullException ("lineMarker"); + textEditorImpl.AddMarker (line, lineMarker); + } + + public void AddMarker (int lineNumber, ITextLineMarker lineMarker) + { + if (lineMarker == null) + throw new ArgumentNullException ("lineMarker"); + AddMarker (GetLine (lineNumber), lineMarker); + } + + public void RemoveMarker (ITextLineMarker lineMarker) + { + if (lineMarker == null) + throw new ArgumentNullException ("lineMarker"); + textEditorImpl.RemoveMarker (lineMarker); + } + + public IEnumerable<ITextLineMarker> GetLineMarkers (IDocumentLine line) + { + if (line == null) + throw new ArgumentNullException ("line"); + return textEditorImpl.GetLineMarkers (line); + } + + public IEnumerable<ITextSegmentMarker> GetTextSegmentMarkersAt (ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + return textEditorImpl.GetTextSegmentMarkersAt (segment); + } + + public IEnumerable<ITextSegmentMarker> GetTextSegmentMarkersAt (int offset) + { + return textEditorImpl.GetTextSegmentMarkersAt (offset); + } + + public void AddMarker (ITextSegmentMarker marker) + { + if (marker == null) + throw new ArgumentNullException ("marker"); + textEditorImpl.AddMarker (marker); + } + + public bool RemoveMarker (ITextSegmentMarker marker) + { + if (marker == null) + throw new ArgumentNullException ("marker"); + return textEditorImpl.RemoveMarker (marker); + } + + public void SetFoldings (IEnumerable<IFoldSegment> foldings) + { + if (foldings == null) + throw new ArgumentNullException ("foldings"); + textEditorImpl.SetFoldings (foldings); + } + + + public IEnumerable<IFoldSegment> GetFoldingsContaining (int offset) + { + return textEditorImpl.GetFoldingsContaining (offset); + } + + public IEnumerable<IFoldSegment> GetFoldingsIn (ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + return textEditorImpl.GetFoldingsIn (segment.Offset, segment.Length); + } + + /// <summary> + /// Gets a character at the specified position in the document. + /// </summary> + /// <paramref name="offset">The index of the character to get.</paramref> + /// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to TextLength-1).</exception> + /// <returns>The character at the specified position.</returns> + /// <remarks>This is the same as Text[offset], but is more efficient because + /// it doesn't require creating a String object.</remarks> + public char GetCharAt (int offset) + { + return ReadOnlyTextDocument.GetCharAt (offset); + } + + public string GetTextAt (int offset, int length) + { + return ReadOnlyTextDocument.GetTextAt (offset, length); + } + + public string GetTextAt (ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + return ReadOnlyTextDocument.GetTextAt (segment); + } + + public IReadonlyTextDocument CreateDocumentSnapshot () + { + return ReadWriteTextDocument.CreateDocumentSnapshot (); + } + + public string GetVirtualIndentationString (int lineNumber) + { + if (lineNumber < 1 || lineNumber > LineCount) + throw new ArgumentOutOfRangeException ("lineNumber"); + return textEditorImpl.GetVirtualIndentationString (lineNumber); + } + + public string GetVirtualIndentationString (IDocumentLine line) + { + if (line == null) + throw new ArgumentNullException ("line"); + return textEditorImpl.GetVirtualIndentationString (line.LineNumber); + } + + public int GetVirtualIndentationColumn (int lineNumber) + { + if (lineNumber < 1 || lineNumber > LineCount) + throw new ArgumentOutOfRangeException ("lineNumber"); + return 1 + textEditorImpl.GetVirtualIndentationString (lineNumber).Length; + } + + public int GetVirtualIndentationColumn (IDocumentLine line) + { + if (line == null) + throw new ArgumentNullException ("line"); + return 1 + textEditorImpl.GetVirtualIndentationString (line.LineNumber).Length; + } + + public TextReader CreateReader () + { + return ReadOnlyTextDocument.CreateReader (); + } + + public TextReader CreateReader (int offset, int length) + { + return ReadOnlyTextDocument.CreateReader (offset, length); + } + + public ITextSource CreateSnapshot () + { + return ReadOnlyTextDocument.CreateSnapshot (); + } + + public ITextSource CreateSnapshot (int offset, int length) + { + return ReadOnlyTextDocument.CreateSnapshot (offset, length); + } + + public ITextSource CreateSnapshot (ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + return ReadOnlyTextDocument.CreateSnapshot (segment.Offset, segment.Length); + } + + public void WriteTextTo (TextWriter writer) + { + if (writer == null) + throw new ArgumentNullException ("writer"); + ReadOnlyTextDocument.WriteTextTo (writer); + } + + public void WriteTextTo (TextWriter writer, int offset, int length) + { + if (writer == null) + throw new ArgumentNullException ("writer"); + ReadOnlyTextDocument.WriteTextTo (writer, offset, length); + } + + public void ScrollTo (int offset) + { + textEditorImpl.ScrollTo (offset); + } + + public void ScrollTo (DocumentLocation loc) + { + ScrollTo (LocationToOffset (loc)); + } + + public void CenterTo (int offset) + { + textEditorImpl.CenterTo (offset); + } + + public void CenterTo (DocumentLocation loc) + { + CenterTo (LocationToOffset (loc)); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public void SetIndentationTracker (IndentationTracker indentationTracker) + { + textEditorImpl.SetIndentationTracker (indentationTracker); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public void SetSelectionSurroundingProvider (SelectionSurroundingProvider surroundingProvider) + { + textEditorImpl.SetSelectionSurroundingProvider (surroundingProvider); + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public void SetTextPasteHandler (TextPasteHandler textPasteHandler) + { + textEditorImpl.SetTextPasteHandler (textPasteHandler); + } + + public IList<SkipChar> SkipChars { + get { + return textEditorImpl.SkipChars; + } + } + + /// <summary> + /// Skip chars are + /// </summary> + public void AddSkipChar (int offset, char ch) + { + textEditorImpl.AddSkipChar (offset, ch); + } + + protected override void Dispose (bool disposing) + { + if (disposing) { + DetachExtensionChain (); + textEditorImpl.Dispose (); + } + base.Dispose (disposing); + } + + protected override object CreateNativeWidget () + { + return textEditorImpl.CreateNativeControl (); + } + + #region Internal API + ExtensionContext extensionContext; + + internal ExtensionContext ExtensionContext { + get { + return extensionContext; + } + set { + if (extensionContext != null) { + extensionContext.RemoveExtensionNodeHandler ("MonoDevelop/SourceEditor2/TooltipProviders", OnTooltipProviderChanged); +// textEditorImpl.ClearTooltipProviders (); + } + extensionContext = value; + if (extensionContext != null) + extensionContext.AddExtensionNodeHandler ("MonoDevelop/SourceEditor2/TooltipProviders", OnTooltipProviderChanged); + } + } + + internal IEditorActionHost EditorActionHost { + get { + return textEditorImpl.Actions; + } + } + + internal ITextMarkerFactory TextMarkerFactory { + get { + return textEditorImpl.TextMarkerFactory; + } + } + + internal TextEditor (ITextEditorImpl textEditorImpl) + { + if (textEditorImpl == null) + throw new ArgumentNullException ("textEditorImpl"); + this.textEditorImpl = textEditorImpl; + commandRouter = new InternalCommandRouter (this); + fileTypeCondition.SetFileName (FileName); + ExtensionContext = AddinManager.CreateExtensionContext (); + ExtensionContext.RegisterCondition ("FileType", fileTypeCondition); + + FileNameChanged += delegate { + fileTypeCondition.SetFileName (FileName); + }; + + MimeTypeChanged += delegate { + textEditorImpl.ClearTooltipProviders (); + foreach (var extensionNode in allProviders) { + if (extensionNode.IsValidFor (MimeType)) + textEditorImpl.AddTooltipProvider ((TooltipProvider) extensionNode.CreateInstance ()); + } + }; + } + + TextEditorViewContent viewContent; + internal IViewContent GetViewContent () + { + if (viewContent == null) { + viewContent = new TextEditorViewContent (this, textEditorImpl); + } + + return viewContent; + } + + internal IFoldSegment CreateFoldSegment (int offset, int length, bool isFolded = false) + { + return textEditorImpl.CreateFoldSegment (offset, length, isFolded); + } + #endregion + + #region Editor extensions + InternalCommandRouter commandRouter; + class InternalCommandRouter : MonoDevelop.Components.Commands.IMultiCastCommandRouter + { + readonly TextEditor editor; + + public InternalCommandRouter (TextEditor editor) + { + this.editor = editor; + } + + #region IMultiCastCommandRouter implementation + + System.Collections.IEnumerable MonoDevelop.Components.Commands.IMultiCastCommandRouter.GetCommandTargets () + { + yield return editor.textEditorImpl; + yield return editor.textEditorImpl.EditorExtension; + } + #endregion + } + + internal object CommandRouter { + get { + return commandRouter; + } + } + + DocumentContext documentContext; + internal DocumentContext DocumentContext { + get { + return documentContext; + } + set { + documentContext = value; + OnDocumentContextChanged (EventArgs.Empty); + } + } + + public event EventHandler DocumentContextChanged; + + void OnDocumentContextChanged (EventArgs e) + { + if (DocumentContext != null) { + textEditorImpl.SetQuickTaskProviders (DocumentContext.GetContents<IQuickTaskProvider> ()); + textEditorImpl.SetUsageTaskProviders (DocumentContext.GetContents<UsageProviderEditorExtension> ()); + } else { + textEditorImpl.SetQuickTaskProviders (Enumerable.Empty<IQuickTaskProvider> ()); + textEditorImpl.SetUsageTaskProviders (Enumerable.Empty<UsageProviderEditorExtension> ()); + } + var handler = DocumentContextChanged; + if (handler != null) + handler (this, e); + } + + internal void InitializeExtensionChain (DocumentContext documentContext) + { + if (documentContext == null) + throw new ArgumentNullException ("documentContext"); + DetachExtensionChain (); + var extensions = ExtensionContext.GetExtensionNodes ("/MonoDevelop/Ide/TextEditorExtensions", typeof(TextEditorExtensionNode)); + TextEditorExtension last = null; + var mimetypeChain = DesktopService.GetMimeTypeInheritanceChainForFile (FileName).ToArray (); + foreach (TextEditorExtensionNode extNode in extensions) { + if (!extNode.Supports (FileName, mimetypeChain)) + continue; + TextEditorExtension ext; + try { + var instance = extNode.CreateInstance (); + ext = instance as TextEditorExtension; + if (ext == null) + continue; + } catch (Exception e) { + LoggingService.LogError ("Error while creating text editor extension :" + extNode.Id + "(" + extNode.Type +")", e); + continue; + } + if (ext.IsValidInContext (documentContext)) { + if (last != null) { + last.Next = ext; + last = ext; + } else { + textEditorImpl.EditorExtension = last = ext; + } + ext.Initialize (this, documentContext); + } + } + this.DocumentContext = documentContext; + } + + void DetachExtensionChain () + { + var editorExtension = textEditorImpl.EditorExtension; + while (editorExtension != null) { + try { + editorExtension.Dispose (); + } catch (Exception ex) { + LoggingService.LogError ("Exception while disposing extension:" + editorExtension, ex); + } + editorExtension = editorExtension.Next; + } + textEditorImpl.EditorExtension = null; + } + + public T GetContent<T> () where T : class + { + T result = textEditorImpl as T; + if (result != null) + return result; + var ext = textEditorImpl.EditorExtension; + while (ext != null) { + result = ext as T; + if (result != null) + return result; + ext = ext.Next; + } + return null; + } + + public IEnumerable<T> GetContents<T> () where T : class + { + T result = textEditorImpl as T; + if (result != null) + yield return result; + var ext = textEditorImpl.EditorExtension; + while (ext != null) { + result = ext as T; + if (result != null) + yield return result; + ext = ext.Next; + } + } + #endregion + + public string GetPangoMarkup (int offset, int length) + { + return textEditorImpl.GetPangoMarkup (offset, length); + } + + public string GetPangoMarkup (ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + return textEditorImpl.GetPangoMarkup (segment.Offset, segment.Length); + } + + public static implicit operator Microsoft.CodeAnalysis.Text.SourceText (TextEditor editor) + { + return new MonoDevelopSourceText (editor); + } + + + #region Annotations + // Annotations: points either null (no annotations), to the single annotation, + // or to an AnnotationList. + // Once it is pointed at an AnnotationList, it will never change (this allows thread-safety support by locking the list) + + object annotations; + sealed class AnnotationList : List<object>, ICloneable + { + // There are two uses for this custom list type: + // 1) it's private, and thus (unlike List<object>) cannot be confused with real annotations + // 2) It allows us to simplify the cloning logic by making the list behave the same as a clonable annotation. + public AnnotationList (int initialCapacity) : base(initialCapacity) + { + } + + public object Clone () + { + lock (this) { + AnnotationList copy = new AnnotationList (this.Count); + for (int i = 0; i < this.Count; i++) { + object obj = this [i]; + ICloneable c = obj as ICloneable; + copy.Add (c != null ? c.Clone () : obj); + } + return copy; + } + } + } + + public void AddAnnotation (object annotation) + { + if (annotation == null) + throw new ArgumentNullException ("annotation"); + retry: // Retry until successful + object oldAnnotation = Interlocked.CompareExchange (ref this.annotations, annotation, null); + if (oldAnnotation == null) { + return; // we successfully added a single annotation + } + AnnotationList list = oldAnnotation as AnnotationList; + if (list == null) { + // we need to transform the old annotation into a list + list = new AnnotationList (4); + list.Add (oldAnnotation); + list.Add (annotation); + if (Interlocked.CompareExchange (ref this.annotations, list, oldAnnotation) != oldAnnotation) { + // the transformation failed (some other thread wrote to this.annotations first) + goto retry; + } + } else { + // once there's a list, use simple locking + lock (list) { + list.Add (annotation); + } + } + } + + public void RemoveAnnotations<T> () where T : class + { + retry: // Retry until successful + object oldAnnotations = this.annotations; + var list = oldAnnotations as AnnotationList; + if (list != null) { + lock (list) + list.RemoveAll (obj => obj is T); + } else if (oldAnnotations is T) { + if (Interlocked.CompareExchange (ref this.annotations, null, oldAnnotations) != oldAnnotations) { + // Operation failed (some other thread wrote to this.annotations first) + goto retry; + } + } + } + + public T Annotation<T> () where T: class + { + object annotations = this.annotations; + var list = annotations as AnnotationList; + if (list != null) { + lock (list) { + foreach (object obj in list) { + T t = obj as T; + if (t != null) + return t; + } + return null; + } + } + return annotations as T; + } + + /// <summary> + /// Gets all annotations stored on this AstNode. + /// </summary> + public IEnumerable<object> Annotations { + get { + object annotations = this.annotations; + AnnotationList list = annotations as AnnotationList; + if (list != null) { + lock (list) { + return list.ToArray (); + } + } + if (annotations != null) + return new [] { annotations }; + return Enumerable.Empty<object> (); + } + } + #endregion + + List<ProjectedTooltipProvider> projectedProviders = new List<ProjectedTooltipProvider> (); + IReadOnlyList<MonoDevelop.Ide.Editor.Projection.Projection> projections = null; + + public void SetOrUpdateProjections (DocumentContext ctx, IReadOnlyList<MonoDevelop.Ide.Editor.Projection.Projection> projections, DisabledProjectionFeatures disabledFeatures = DisabledProjectionFeatures.None) + { + if (ctx == null) + throw new ArgumentNullException ("ctx"); + if (this.projections != null) { + foreach (var projection in this.projections) { + projection.Dettach (); + } + } + this.projections = projections; + if (projections != null) { + foreach (var projection in projections) { + projection.Attach (this); + } + } + + if ((disabledFeatures & DisabledProjectionFeatures.SemanticHighlighting) != DisabledProjectionFeatures.SemanticHighlighting) { + if (SemanticHighlighting is ProjectedSemanticHighlighting) { + ((ProjectedSemanticHighlighting)SemanticHighlighting).UpdateProjection (projections); + } else { + SemanticHighlighting = new ProjectedSemanticHighlighting (this, ctx, projections); + } + } + + if ((disabledFeatures & DisabledProjectionFeatures.Tooltips) != DisabledProjectionFeatures.Tooltips) { + projectedProviders.ForEach (textEditorImpl.RemoveTooltipProvider); + projectedProviders = new List<ProjectedTooltipProvider> (); + foreach (var projection in projections) { + foreach (var tp in projection.ProjectedEditor.textEditorImpl.TooltipProvider) { + var newProvider = new ProjectedTooltipProvider (this, ctx, projection, tp); + projectedProviders.Add (newProvider); + textEditorImpl.AddTooltipProvider (newProvider); + } + } + } + InitializeProjectionExtensions (ctx, disabledFeatures); + } + + bool projectionsAdded = false; + void InitializeProjectionExtensions (DocumentContext ctx, DisabledProjectionFeatures disabledFeatures) + { + if (projectionsAdded) { + TextEditorExtension ext = textEditorImpl.EditorExtension; + while (ext != null && ext.Next != null) { + var pext = ext as IProjectionExtension; + if (pext != null) { + pext.Projections = projections; + } + ext = ext.Next; + } + return; + } + + if (projections.Count == 0) + return; + + TextEditorExtension lastExtension = textEditorImpl.EditorExtension; + while (lastExtension != null && lastExtension.Next != null) { + var completionTextEditorExtension = lastExtension.Next as CompletionTextEditorExtension; + if (completionTextEditorExtension != null) { + var projectedFilterExtension = new ProjectedFilterCompletionTextEditorExtension (completionTextEditorExtension, projections) { Next = completionTextEditorExtension.Next }; + lastExtension.Next = projectedFilterExtension; + projectedFilterExtension.Initialize (this, DocumentContext); + } + lastExtension = lastExtension.Next; + } + + // no extensions -> no projections needed + if (textEditorImpl.EditorExtension == null) + return; + + if ((disabledFeatures & DisabledProjectionFeatures.Completion) != DisabledProjectionFeatures.Completion) { + var projectedCompletionExtension = new ProjectedCompletionExtension (ctx, projections); + projectedCompletionExtension.Next = textEditorImpl.EditorExtension; + + textEditorImpl.EditorExtension = projectedCompletionExtension; + projectedCompletionExtension.Initialize (this, DocumentContext); + } + projectionsAdded = true; + } + + public void AddOverlay (Control messageOverlayContent, Func<int> sizeFunc) + { + textEditorImpl.AddOverlay (messageOverlayContent, sizeFunc); + } + + public void RemoveOverlay (Control messageOverlayContent) + { + textEditorImpl.RemoveOverlay (messageOverlayContent); + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorDisplayBinding.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorDisplayBinding.cs new file mode 100644 index 0000000000..1fe76c43a0 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorDisplayBinding.cs @@ -0,0 +1,114 @@ +// +// TextEditorDisplayBinding.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core; +using MonoDevelop.Ide.Gui; +using System.IO; +using MonoDevelop.Projects; +using System.ComponentModel; +using MonoDevelop.Ide.Editor.Highlighting; + +namespace MonoDevelop.Ide.Editor +{ + public class TextEditorDisplayBinding : IViewDisplayBinding + { + static bool IsInitialized = false; + + public static FilePath SyntaxModePath { + get { + return UserProfile.Current.UserDataRoot.Combine ("HighlightingSchemes"); + } + } + + static TextEditorDisplayBinding () + { + InitSourceEditor (); + } + + public static void InitSourceEditor () + { + if (IsInitialized) + return; + IsInitialized = true; + + // MonoDevelop.SourceEditor.Extension.TemplateExtensionNodeLoader.Init (); + DefaultSourceEditorOptions.Init (); + // SyntaxModeService.EnsureLoad (); + LoadCustomStylesAndModes (); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void LoadCustomStylesAndModes () + { + bool success = true; + if (!Directory.Exists (SyntaxModePath)) { + try { + Directory.CreateDirectory (SyntaxModePath); + } catch (Exception e) { + success = false; + LoggingService.LogError ("Can't create syntax mode directory", e); + } + } + if (success) + SyntaxModeService.LoadStylesAndModes (SyntaxModePath); + } + + public string Name { + get { + return GettextCatalog.GetString ("Source Code Editor"); + } + } + + public bool CanHandle (FilePath fileName, string mimeType, Project ownerProject) + { + if (fileName != null) + return DesktopService.GetFileIsText (fileName, mimeType); + + if (!string.IsNullOrEmpty (mimeType)) + return DesktopService.GetMimeTypeIsText (mimeType); + + return false; + } + + public IViewContent CreateContent (FilePath fileName, string mimeType, Project ownerProject) + { + var editor = TextEditorFactory.CreateNewEditor (); + editor.MimeType = mimeType; + editor.GetViewContent ().Project = ownerProject; + editor.GetViewContent ().ContentName = fileName; + return editor.GetViewContent (); + } + + public bool CanHandleFile (string fileName) + { + return DesktopService.GetFileIsText (fileName); + } + + public bool CanUseAsDefault { + get { return true; } + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorFactory.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorFactory.cs new file mode 100644 index 0000000000..6d54b8060b --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorFactory.cs @@ -0,0 +1,95 @@ +// +// DocumentFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using Mono.Addins; + +namespace MonoDevelop.Ide.Editor +{ + public static class TextEditorFactory + { + static ITextEditorFactory currentFactory; + + static TextEditorFactory () + { + AddinManager.AddExtensionNodeHandler ("/MonoDevelop/SourceEditor2/EditorFactory", delegate(object sender, ExtensionNodeEventArgs args) { + switch (args.Change) { + case ExtensionChange.Add: + if (currentFactory == null) + currentFactory = (ITextEditorFactory)args.ExtensionObject; + break; + } + }); + } + + public static ITextDocument CreateNewDocument () + { + return currentFactory.CreateNewDocument (); + } + + public static ITextDocument CreateNewDocument (ITextSource textSource, string fileName, string mimeType = null) + { + if (textSource == null) + throw new System.ArgumentNullException ("textSource"); + return currentFactory.CreateNewDocument (textSource, fileName, mimeType); + } + + public static ITextDocument LoadDocument (string fileName, string mimeType = null) + { + if (fileName == null) + throw new System.ArgumentNullException ("fileName"); + return currentFactory.CreateNewDocument (StringTextSource.ReadFrom (fileName), fileName, mimeType); + } + + public static IReadonlyTextDocument CreateNewReadonlyDocument (ITextSource textSource, string fileName, string mimeType = null) + { + if (textSource == null) + throw new System.ArgumentNullException ("textSource"); + return currentFactory.CreateNewDocument (textSource, fileName, mimeType); + } + + public static TextEditor CreateNewEditor () + { + return new TextEditor (currentFactory.CreateNewEditor ()); + } + + public static TextEditor CreateNewEditor (IReadonlyTextDocument document) + { + if (document == null) + throw new System.ArgumentNullException ("document"); + return new TextEditor (currentFactory.CreateNewEditor (document)); + } + + public static string[] GetSyntaxProperties (string mimeType, string name) + { + if (mimeType == null) + throw new System.ArgumentNullException ("mimeType"); + if (name == null) + throw new System.ArgumentNullException ("name"); + return currentFactory.GetSyntaxProperties (mimeType, name); + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorViewContent.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorViewContent.cs new file mode 100644 index 0000000000..2305687ba2 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorViewContent.cs @@ -0,0 +1,1233 @@ +// +// TextEditorViewContent.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Ide.Gui; +using MonoDevelop.Projects; +using MonoDevelop.Ide.Gui.Content; +using MonoDevelop.Components.Commands; +using MonoDevelop.Ide.Commands; +using System.Collections; +using System.Collections.Generic; +using MonoDevelop.Ide.TypeSystem; +using System.IO; +using MonoDevelop.Core.Text; +using System.Text; +using Gtk; +using ICSharpCode.NRefactory.TypeSystem; +using System.Linq; +using MonoDevelop.Ide.Editor.Extension; +using ICSharpCode.NRefactory.Refactoring; +using MonoDevelop.Components; +using MonoDevelop.Core; +using System.Threading.Tasks; +using System.Threading; +using System.Threading; +using Microsoft.CodeAnalysis; +using Gdk; +using MonoDevelop.Ide.CodeFormatting; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// The TextEditor object needs to be available through IBaseViewContent.GetContent therefore we need to insert a + /// decorator in between. + /// </summary> + class TextEditorViewContent : IViewContent, ICommandRouter, IQuickTaskProvider + { + readonly TextEditor textEditor; + readonly ITextEditorImpl textEditorImpl; + + DocumentContext currentContext; + MonoDevelop.Projects.Policies.PolicyContainer policyContainer; + + public TextEditorViewContent (TextEditor textEditor, ITextEditorImpl textEditorImpl) + { + if (textEditor == null) + throw new ArgumentNullException ("textEditor"); + if (textEditorImpl == null) + throw new ArgumentNullException ("textEditorImpl"); + this.textEditor = textEditor; + this.textEditorImpl = textEditorImpl; + this.textEditor.MimeTypeChanged += UpdateTextEditorOptions; + DefaultSourceEditorOptions.Instance.Changed += UpdateTextEditorOptions; + this.textEditor.DocumentContextChanged += delegate { + if (currentContext != null) + currentContext.DocumentParsed -= HandleDocumentParsed; + currentContext = textEditor.DocumentContext; + currentContext.DocumentParsed += HandleDocumentParsed; + }; + } + + void HandleDirtyChanged (object sender, EventArgs e) + { + InformAutoSave (); + } + + void HandleTextChanged (object sender, MonoDevelop.Core.Text.TextChangeEventArgs e) + { + InformAutoSave (); + } + + void UpdateTextEditorOptions (object sender, EventArgs e) + { + UpdateStyleParent (Project, textEditor.MimeType); + } + + uint autoSaveTimer = 0; + + void InformAutoSave () + { + RemoveAutoSaveTimer (); + autoSaveTimer = GLib.Timeout.Add (500, delegate { + AutoSave.InformAutoSaveThread (textEditor.CreateSnapshot (), textEditor.FileName, textEditorImpl.IsDirty); + autoSaveTimer = 0; + return false; + }); + } + + + void RemoveAutoSaveTimer () + { + if (autoSaveTimer == 0) + return; + GLib.Source.Remove (autoSaveTimer); + autoSaveTimer = 0; + } + + void RemovePolicyChangeHandler () + { + if (policyContainer != null) + policyContainer.PolicyChanged -= HandlePolicyChanged; + } + + void UpdateStyleParent (MonoDevelop.Projects.Project styleParent, string mimeType) + { + RemovePolicyChangeHandler (); + + if (string.IsNullOrEmpty (mimeType)) + mimeType = "text/plain"; + + var mimeTypes = DesktopService.GetMimeTypeInheritanceChain (mimeType); + + if (styleParent != null) + policyContainer = styleParent.Policies; + else + policyContainer = MonoDevelop.Projects.Policies.PolicyService.DefaultPolicies; + var currentPolicy = policyContainer.Get<TextStylePolicy> (mimeTypes); + + policyContainer.PolicyChanged += HandlePolicyChanged; + textEditor.Options = DefaultSourceEditorOptions.Instance.WithTextStyle (currentPolicy); + } + + void HandlePolicyChanged (object sender, MonoDevelop.Projects.Policies.PolicyChangedEventArgs args) + { + var mimeTypes = DesktopService.GetMimeTypeInheritanceChain (textEditor.MimeType); + var currentPolicy = policyContainer.Get<TextStylePolicy> (mimeTypes); + textEditor.Options = DefaultSourceEditorOptions.Instance.WithTextStyle (currentPolicy); + } + + void HandleDocumentParsed (object sender, EventArgs e) + { + var ctx = (DocumentContext)sender; + src.Cancel (); + src = new CancellationTokenSource (); + var token = src.Token; + Task.Run (() => { + try { + UpdateErrorUndelines (ctx.ParsedDocument, token); + UpdateQuickTasks (ctx.ParsedDocument, token); + UpdateFoldings (ctx.ParsedDocument, false, token); + } catch (OperationCanceledException) { + // ignore + } + }, token); + } + + #region Error handling + List<IErrorMarker> errors = new List<IErrorMarker> (); + uint resetTimerId; + + void RemoveErrorUndelinesResetTimerId () + { + if (resetTimerId > 0) { + GLib.Source.Remove (resetTimerId); + resetTimerId = 0; + } + } + + void RemoveErrorUnderlines () + { + errors.ForEach (err => textEditor.RemoveMarker (err)); + errors.Clear (); + } + + void UnderLineError (MonoDevelop.Ide.TypeSystem.Error info) + { + var error = TextMarkerFactory.CreateErrorMarker (textEditor, info); + textEditor.AddMarker (error); + errors.Add (error); + } + + async void UpdateErrorUndelines (ParsedDocument parsedDocument, CancellationToken token) + { + if (!DefaultSourceEditorOptions.Instance.UnderlineErrors || parsedDocument == null) + return; + try { + var errors = await parsedDocument.GetErrorsAsync(token).ConfigureAwait (false); + Application.Invoke (delegate { + if (token.IsCancellationRequested) + return; + RemoveErrorUndelinesResetTimerId (); + const uint timeout = 500; + resetTimerId = GLib.Timeout.Add (timeout, delegate { + if (token.IsCancellationRequested) { + resetTimerId = 0; + return false; + } + RemoveErrorUnderlines (); + // Else we underline the error + if (errors != null) { + foreach (var error in errors) { + UnderLineError (error); + } + } + resetTimerId = 0; + return false; + }); + }); + } catch (OperationCanceledException) { + // ignore + } + } + #endregion + CancellationTokenSource src = new CancellationTokenSource (); + void UpdateFoldings (ParsedDocument parsedDocument, bool firstTime = false, CancellationToken token = default (CancellationToken)) + { + if (parsedDocument == null || !textEditor.Options.ShowFoldMargin) + return; + // don't update parsed documents that contain errors - the foldings from there may be invalid. + if (parsedDocument.HasErrors) + return; + var caretLocation = textEditor.CaretLocation; + try { + var foldSegments = new List<IFoldSegment> (); + + foreach (FoldingRegion region in parsedDocument.GetFoldingsAsync(token).Result) { + if (token.IsCancellationRequested) + return; + var type = FoldingType.Unknown; + bool setFolded = false; + bool folded = false; + //decide whether the regions should be folded by default + switch (region.Type) { + case FoldType.Member: + type = FoldingType.TypeMember; + break; + case FoldType.Type: + type = FoldingType.TypeDefinition; + break; + case FoldType.UserRegion: + type = FoldingType.Region; + setFolded = DefaultSourceEditorOptions.Instance.DefaultRegionsFolding; + folded = true; + break; + case FoldType.Comment: + type = FoldingType.Comment; + setFolded = DefaultSourceEditorOptions.Instance.DefaultCommentFolding; + folded = true; + break; + case FoldType.CommentInsideMember: + type = FoldingType.Comment; + setFolded = DefaultSourceEditorOptions.Instance.DefaultCommentFolding; + folded = false; + break; + case FoldType.Undefined: + setFolded = true; + folded = region.IsFoldedByDefault; + break; + } + var start = textEditor.LocationToOffset (region.Region.Begin); + var end = textEditor.LocationToOffset (region.Region.End); + var marker = textEditor.CreateFoldSegment (start, end - start); + foldSegments.Add (marker); + marker.CollapsedText = region.Name; + marker.FoldingType = type; + //and, if necessary, set its fold state + if (marker != null && setFolded && firstTime) { + // only fold on document open, later added folds are NOT folded by default. + marker.IsCollapsed = folded; + continue; + } + if (marker != null && region.Region.Contains (caretLocation.Line, caretLocation.Column)) + marker.IsCollapsed = false; + } + if (firstTime) { + textEditor.SetFoldings (foldSegments); + } else { + Application.Invoke (delegate { + if (!token.IsCancellationRequested) + textEditor.SetFoldings (foldSegments); + }); + } + } catch (Exception ex) { + LoggingService.LogError ("Unhandled exception in ParseInformationUpdaterWorkerThread", ex); + } + } + + void RunFirstTimeFoldUpdate (string text) + { + if (string.IsNullOrEmpty (text)) + return; + ParsedDocument parsedDocument = null; + + var foldingParser = TypeSystemService.GetFoldingParser (textEditor.MimeType); + if (foldingParser != null) { + parsedDocument = foldingParser.Parse (textEditor.FileName, text); + } else { + var normalParser = TypeSystemService.GetParser (textEditor.MimeType); + if (normalParser != null) { + parsedDocument = normalParser.Parse (new MonoDevelop.Ide.TypeSystem.ParseOptions { FileName = textEditor.FileName, Content = new StringTextSource (text) }).Result; + } + } + if (parsedDocument != null) { + UpdateFoldings (parsedDocument, true); + } + } + + + #region IViewFContent implementation + + event EventHandler IViewContent.ContentNameChanged { + add { + textEditorImpl.ContentNameChanged += value; + } + remove { + textEditorImpl.ContentNameChanged -= value; + } + } + + event EventHandler IViewContent.ContentChanged { + add { + textEditorImpl.ContentChanged += value; + } + remove { + textEditorImpl.ContentChanged -= value; + } + } + + event EventHandler IViewContent.DirtyChanged { + add { + textEditorImpl.DirtyChanged += value; + } + remove { + textEditorImpl.DirtyChanged -= value; + } + } + + event EventHandler IViewContent.BeforeSave { + add { + textEditorImpl.BeforeSave += value; + } + remove { + textEditorImpl.BeforeSave -= value; + } + } + + void IViewContent.Load (FileOpenInformation fileOpenInformation) + { + this.textEditorImpl.DirtyChanged -= HandleDirtyChanged; + this.textEditor.TextChanged -= HandleTextChanged; + textEditorImpl.Load (fileOpenInformation); + RunFirstTimeFoldUpdate (textEditor.Text); + this.textEditor.TextChanged += HandleTextChanged; + this.textEditorImpl.DirtyChanged += HandleDirtyChanged; + } + + void IViewContent.Load (string fileName) + { + this.textEditorImpl.DirtyChanged -= HandleDirtyChanged; + this.textEditor.TextChanged -= HandleTextChanged; + textEditorImpl.Load (new FileOpenInformation (fileName)); + RunFirstTimeFoldUpdate (textEditor.Text); + this.textEditor.TextChanged += HandleTextChanged; + this.textEditorImpl.DirtyChanged += HandleDirtyChanged; + } + + void IViewContent.LoadNew (System.IO.Stream content, string mimeType) + { + textEditor.MimeType = mimeType; + string text = null; + if (content != null) { + Encoding encoding; + bool hadBom; + text = TextFileUtility.GetText (content, out encoding, out hadBom); + textEditor.Text = text; + textEditor.Encoding = encoding; + textEditor.UseBOM = hadBom; + } + RunFirstTimeFoldUpdate (text); + textEditorImpl.InformLoadComplete (); + } + + void IViewContent.Save (FileSaveInformation fileSaveInformation) + { + if (!string.IsNullOrEmpty (fileSaveInformation.FileName)) + AutoSave.RemoveAutoSaveFile (fileSaveInformation.FileName); + textEditorImpl.Save (fileSaveInformation); + } + + void IViewContent.Save (string fileName) + { + if (!string.IsNullOrEmpty (fileName)) + AutoSave.RemoveAutoSaveFile (fileName); + textEditorImpl.Save (new FileSaveInformation (fileName)); + } + + void IViewContent.Save () + { + if (!string.IsNullOrEmpty (textEditorImpl.ContentName)) + AutoSave.RemoveAutoSaveFile (textEditorImpl.ContentName); + textEditorImpl.Save (); + } + + void IViewContent.DiscardChanges () + { + if (!string.IsNullOrEmpty (textEditorImpl.ContentName)) + AutoSave.RemoveAutoSaveFile (textEditorImpl.ContentName); + textEditorImpl.DiscardChanges (); + } + + public MonoDevelop.Projects.Project Project { + get { + return textEditorImpl.Project; + } + set { + textEditorImpl.Project = value; + UpdateTextEditorOptions (null, null); + } + } + + string IViewContent.PathRelativeToProject { + get { + return textEditorImpl.PathRelativeToProject; + } + } + + string IViewContent.ContentName { + get { + return textEditorImpl.ContentName; + } + set { + textEditorImpl.ContentName = value; + } + } + + string IViewContent.UntitledName { + get { + return textEditorImpl.UntitledName; + } + set { + textEditorImpl.UntitledName = value; + } + } + + string IViewContent.StockIconId { + get { + return textEditorImpl.StockIconId; + } + } + + bool IViewContent.IsUntitled { + get { + return textEditorImpl.IsUntitled; + } + } + + bool IViewContent.IsViewOnly { + get { + return textEditorImpl.IsViewOnly; + } + } + + bool IViewContent.IsFile { + get { + return textEditorImpl.IsFile; + } + } + + bool IViewContent.IsDirty { + get { + return textEditorImpl.IsDirty; + } + set { + textEditorImpl.IsDirty = value; + } + } + + bool IViewContent.IsReadOnly { + get { + return textEditorImpl.IsReadOnly; + } + } + + #endregion + + #region IBaseViewContent implementation + object IBaseViewContent.GetContent (Type type) + { + if (type.IsAssignableFrom (typeof(TextEditor))) + return textEditor; + var ext = textEditorImpl.EditorExtension; + while (ext != null) { + if (type.IsInstanceOfType (ext)) + return ext; + ext = ext.Next; + } + return textEditorImpl.GetContent (type); + } + + public virtual IEnumerable<T> GetContents<T> () where T : class + { + if (typeof(T) == typeof(TextEditor)) { + yield return (T)(object)textEditor; + yield break; + } + var result = this as T; + if (result != null) { + yield return result; + } + var ext = textEditorImpl.EditorExtension; + while (ext != null) { + result = ext as T; + if (result != null) { + yield return result; + } + ext = ext.Next; + } + foreach (var cnt in textEditorImpl.GetContents<T> ()) { + yield return cnt; + } + } + + bool IBaseViewContent.CanReuseView (string fileName) + { + return textEditorImpl.CanReuseView (fileName); + } + + void IBaseViewContent.RedrawContent () + { + textEditorImpl.RedrawContent (); + } + + IWorkbenchWindow IBaseViewContent.WorkbenchWindow { + get { + return textEditorImpl.WorkbenchWindow; + } + set { + textEditorImpl.WorkbenchWindow = value; + } + } + + Gtk.Widget IBaseViewContent.Control { + get { + return textEditor; + } + } + + string IBaseViewContent.TabPageLabel { + get { + return textEditorImpl.TabPageLabel; + } + } + + #endregion + + #region IDisposable implementation + + void IDisposable.Dispose () + { + DefaultSourceEditorOptions.Instance.Changed -= UpdateTextEditorOptions; + RemovePolicyChangeHandler (); + RemoveAutoSaveTimer (); + RemoveErrorUndelinesResetTimerId (); + textEditorImpl.Dispose (); + } + + #endregion + + #region ICommandRouter implementation + + object ICommandRouter.GetNextCommandTarget () + { + return textEditorImpl; + } + + #endregion + + #region Commands + void ToggleCodeCommentWithBlockComments () + { + var blockStarts = TextEditorFactory.GetSyntaxProperties (textEditor.MimeType, "BlockCommentStart"); + var blockEnds = TextEditorFactory.GetSyntaxProperties (textEditor.MimeType, "BlockCommentEnd"); + if (blockStarts == null || blockEnds == null || blockStarts.Length == 0 || blockEnds.Length == 0) + return; + + string blockStart = blockStarts[0]; + string blockEnd = blockEnds[0]; + + using (var undo = textEditor.OpenUndoGroup ()) { + IDocumentLine startLine; + IDocumentLine endLine; + + if (textEditor.IsSomethingSelected) { + startLine = textEditor.GetLineByOffset (textEditor.SelectionRange.Offset); + endLine = textEditor.GetLineByOffset (textEditor.SelectionRange.EndOffset); + } else { + startLine = endLine = textEditor.GetLine (textEditor.CaretLine); + } + string startLineText = textEditor.GetTextAt (startLine.Offset, startLine.Length); + string endLineText = textEditor.GetTextAt (endLine.Offset, endLine.Length); + if (startLineText.StartsWith (blockStart, StringComparison.Ordinal) && endLineText.EndsWith (blockEnd, StringComparison.Ordinal)) { + textEditor.RemoveText (endLine.Offset + endLine.Length - blockEnd.Length, blockEnd.Length); + textEditor.RemoveText (startLine.Offset, blockStart.Length); + if (textEditor.IsSomethingSelected) { + textEditor.SelectionAnchorOffset -= blockEnd.Length; + } + } else { + textEditor.InsertText (endLine.Offset + endLine.Length, blockEnd); + textEditor.InsertText (startLine.Offset, blockStart); + if (textEditor.IsSomethingSelected) { + textEditor.SelectionAnchorOffset += blockEnd.Length; + } + } + } + } + + bool TryGetLineCommentTag (out string commentTag) + { + var lineComments = TextEditorFactory.GetSyntaxProperties (textEditor.MimeType, "LineComment"); + if (lineComments == null || lineComments.Length == 0) { + commentTag = null; + return false; + } + commentTag = lineComments [0]; + return true; + } + + [CommandUpdateHandler (EditCommands.AddCodeComment)] + [CommandUpdateHandler (EditCommands.RemoveCodeComment)] + [CommandUpdateHandler (EditCommands.ToggleCodeComment)] + void OnUpdateToggleComment (CommandInfo info) + { + var lineComments = TextEditorFactory.GetSyntaxProperties (textEditor.MimeType, "LineComment"); + if (lineComments != null && lineComments.Length > 0) { + info.Visible = true; + return; + } + var blockStarts = TextEditorFactory.GetSyntaxProperties (textEditor.MimeType, "BlockCommentStart"); + var blockEnds = TextEditorFactory.GetSyntaxProperties (textEditor.MimeType, "BlockCommentEnd"); + info.Visible = blockStarts != null && blockStarts.Length > 0 && blockEnds != null && blockEnds.Length > 0; + } + + [CommandHandler (EditCommands.ToggleCodeComment)] + internal void ToggleCodeComment () + { + string commentTag; + if (!TryGetLineCommentTag (out commentTag)) + return; + bool comment = false; + foreach (var line in GetSelectedLines (textEditor)) { + int startOffset; + int offset = line.Offset; + if (!StartsWith (textEditor, offset, line.Length, commentTag, out startOffset)) { + if (startOffset - offset == line.Length) // case: line consists only of white spaces + continue; + comment = true; + break; + } + } + + if (comment) { + AddCodeComment (); + } else { + RemoveCodeComment (); + } + } + + static bool StartsWith (ITextSource text, int offset, int length, string commentTag, out int startOffset) + { + int max = Math.Min (offset + length, text.Length); + int i = offset; + for (; i < max; i++) { + char ch = text.GetCharAt (i); + if (ch != ' ' && ch != '\t') + break; + } + startOffset = i; + for (int j = 0; j < commentTag.Length; j++) { + if (text.GetCharAt (i) != commentTag [j]) + return false; + i++; + } + + return true; + } + + static IEnumerable<IDocumentLine> GetSelectedLines (TextEditor textEditor) + { + var selection = textEditor.SelectionRange; + var line = textEditor.GetLineByOffset (selection.EndOffset); + do { + yield return line; + line = line.PreviousLine; + } while (line != null && line.EndOffset > selection.Offset); + } + + [CommandHandler (EditCommands.AddCodeComment)] + internal void AddCodeComment () + { + string commentTag; + if (!TryGetLineCommentTag (out commentTag)) + return; + + using (var undo = textEditor.OpenUndoGroup ()) { + var wasSelected = textEditor.IsSomethingSelected; + var lead = textEditor.SelectionLeadOffset; + var anchor = textEditor.SelectionAnchorOffset; + var lineAndIndents = new List<Tuple<IDocumentLine, string>>(); + string indent = null; + var oldVersion = textEditor.Version; + foreach (var line in GetSelectedLines (textEditor)) { + var curIndent = line.GetIndentation (textEditor); + if (line.Length == curIndent.Length) { + lineAndIndents.Add (Tuple.Create ((IDocumentLine)null, "")); + continue; + } + if (indent == null || curIndent.Length < indent.Length) + indent = curIndent; + lineAndIndents.Add (Tuple.Create (line, curIndent)); + } + + foreach (var line in lineAndIndents) { + if (line.Item1 == null) + continue; + textEditor.InsertText (line.Item1.Offset + indent.Length, commentTag); + } + if (wasSelected) { + textEditor.SelectionAnchorOffset = oldVersion.MoveOffsetTo (textEditor.Version, anchor); + textEditor.SelectionLeadOffset = oldVersion.MoveOffsetTo (textEditor.Version, lead); + } + } + } + + [CommandHandler (EditCommands.RemoveCodeComment)] + internal void RemoveCodeComment () + { + string commentTag; + if (!TryGetLineCommentTag (out commentTag)) + return; + + using (var undo = textEditor.OpenUndoGroup ()) { + var wasSelected = textEditor.IsSomethingSelected; + var lead = textEditor.SelectionLeadOffset; + var anchor = textEditor.SelectionAnchorOffset; + int lines = 0; + + IDocumentLine first = null; + IDocumentLine last = null; + var oldVersion = textEditor.Version; + foreach (var line in GetSelectedLines (textEditor)) { + int startOffset; + if (StartsWith (textEditor, line.Offset, line.Length, commentTag, out startOffset)) { + textEditor.RemoveText (startOffset, commentTag.Length); + lines++; + } + + first = line; + if (last == null) + last = line; + } + + if (wasSelected) { +// if (IdeApp.Workbench != null) +// CodeFormatterService.Format (textEditor, IdeApp.Workbench.ActiveDocument, TextSegment.FromBounds (first.Offset, last.EndOffset)); + + textEditor.SelectionAnchorOffset = oldVersion.MoveOffsetTo (textEditor.Version, anchor); + textEditor.SelectionLeadOffset = oldVersion.MoveOffsetTo (textEditor.Version, lead); + } + } + } + + [CommandHandler (EditCommands.InsertGuid)] + void InsertGuid () + { + textEditor.InsertAtCaret (Guid.NewGuid ().ToString ()); + } + + [CommandUpdateHandler (MessageBubbleCommands.Toggle)] + public void OnUpdateToggleErrorTextMarker (CommandInfo info) + { + var line = textEditor.GetLine (textEditor.CaretLine); + if (line == null) { + info.Visible = false; + return; + } + + var marker = (IMessageBubbleLineMarker)textEditor.GetLineMarkers (line).FirstOrDefault (m => m is IMessageBubbleLineMarker); + info.Visible = marker != null; + } + + [CommandHandler (MessageBubbleCommands.Toggle)] + public void OnToggleErrorTextMarker () + { + var line = textEditor.GetLine (textEditor.CaretLine); + if (line == null) + return; + var marker = (IMessageBubbleLineMarker)textEditor.GetLineMarkers (line).FirstOrDefault (m => m is IMessageBubbleLineMarker); + if (marker != null) { + marker.IsVisible = !marker.IsVisible; + } + } + #endregion + + #region IQuickTaskProvider implementation + List<QuickTask> tasks = new List<QuickTask> (); + + public event EventHandler TasksUpdated; + + protected virtual void OnTasksUpdated (EventArgs e) + { + EventHandler handler = this.TasksUpdated; + if (handler != null) + handler (this, e); + } + + public IEnumerable<QuickTask> QuickTasks { + get { + return tasks; + } + } + + async void UpdateQuickTasks (ParsedDocument doc, CancellationToken token) + { + var newTasks = new List<QuickTask> (); + if (doc != null) { + foreach (var cmt in await doc.GetTagCommentsAsync(token).ConfigureAwait (false)) { + var newTask = new QuickTask (cmt.Text, textEditor.LocationToOffset (cmt.Region.Begin.Line, cmt.Region.Begin.Column), DiagnosticSeverity.Info); + newTasks.Add (newTask); + } + + foreach (var error in await doc.GetErrorsAsync(token).ConfigureAwait (false)) { + var newTask = new QuickTask (error.Message, textEditor.LocationToOffset (error.Region.Begin.Line, error.Region.Begin.Column), error.ErrorType == MonoDevelop.Ide.TypeSystem.ErrorType.Error ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning); + newTasks.Add (newTask); + } + } + Application.Invoke (delegate { + if (token.IsCancellationRequested) + return; + tasks = newTasks; + OnTasksUpdated (EventArgs.Empty); + }); + } + #endregion + + #region Key bindings + + [CommandHandler (TextEditorCommands.LineEnd)] + void OnLineEnd () + { + EditActions.MoveCaretToLineEnd (textEditor); + } + + [CommandHandler (TextEditorCommands.LineStart)] + void OnLineStart () + { + EditActions.MoveCaretToLineStart (textEditor); + } + + [CommandHandler (TextEditorCommands.DeleteLeftChar)] + void OnDeleteLeftChar () + { + EditActions.Backspace (textEditor); + } + + [CommandHandler (TextEditorCommands.DeleteRightChar)] + void OnDeleteRightChar () + { + EditActions.Delete (textEditor); + } + + [CommandHandler (TextEditorCommands.CharLeft)] + void OnCharLeft () + { + EditActions.MoveCaretLeft (textEditor); + } + + [CommandHandler (TextEditorCommands.CharRight)] + void OnCharRight () + { + EditActions.MoveCaretRight (textEditor); + } + + [CommandHandler (TextEditorCommands.LineUp)] + void OnLineUp () + { + EditActions.MoveCaretUp (textEditor); + } + + [CommandHandler (TextEditorCommands.LineDown)] + void OnLineDown () + { + EditActions.MoveCaretDown (textEditor); + } + + [CommandHandler (TextEditorCommands.DocumentStart)] + void OnDocumentStart () + { + EditActions.MoveCaretToDocumentStart (textEditor); + } + + [CommandHandler (TextEditorCommands.DocumentEnd)] + void OnDocumentEnd () + { + EditActions.MoveCaretToDocumentEnd (textEditor); + } + + [CommandHandler (TextEditorCommands.PageUp)] + void OnPageUp () + { + EditActions.PageUp (textEditor); + } + + [CommandHandler (TextEditorCommands.PageDown)] + void OnPageDown () + { + EditActions.PageDown (textEditor); + } + + [CommandHandler (TextEditorCommands.DeleteLine)] + void OnDeleteLine () + { + EditActions.DeleteCurrentLine (textEditor); + } + + [CommandHandler (TextEditorCommands.DeleteToLineEnd)] + void OnDeleteToLineEnd () + { + EditActions.DeleteCurrentLineToEnd (textEditor); + } + + [CommandHandler (TextEditorCommands.ScrollLineUp)] + void OnScrollLineUp () + { + EditActions.ScrollLineUp (textEditor); + } + + [CommandHandler (TextEditorCommands.ScrollLineDown)] + void OnScrollLineDown () + { + EditActions.ScrollLineDown (textEditor); + } + + [CommandHandler (TextEditorCommands.ScrollPageUp)] + void OnScrollPageUp () + { + EditActions.ScrollPageUp (textEditor); + } + + [CommandHandler (TextEditorCommands.ScrollPageDown)] + void OnScrollPageDown () + { + EditActions.ScrollPageDown (textEditor); + } + + [CommandHandler (TextEditorCommands.GotoMatchingBrace)] + void OnGotoMatchingBrace () + { + EditActions.GotoMatchingBrace (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveLeft)] + void OnSelectionMoveLeft () + { + EditActions.SelectionMoveLeft (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveRight)] + void OnSelectionMoveRight () + { + EditActions.SelectionMoveRight (textEditor); + } + + [CommandHandler (TextEditorCommands.MovePrevWord)] + void OnMovePrevWord () + { + EditActions.MovePrevWord (textEditor); + } + + [CommandHandler (TextEditorCommands.MoveNextWord)] + void OnMoveNextWord () + { + EditActions.MoveNextWord (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMovePrevWord)] + void OnSelectionMovePrevWord () + { + EditActions.SelectionMovePrevWord (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveNextWord)] + void OnSelectionMoveNextWord () + { + EditActions.SelectionMoveNextWord (textEditor); + } + + [CommandHandler (TextEditorCommands.MovePrevSubword)] + void OnMovePrevSubword () + { + EditActions.MovePrevSubWord (textEditor); + } + + [CommandHandler (TextEditorCommands.MoveNextSubword)] + void OnMoveNextSubword () + { + EditActions.MoveNextSubWord (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMovePrevSubword)] + void OnSelectionMovePrevSubword () + { + EditActions.SelectionMovePrevSubWord (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveNextSubword)] + void OnSelectionMoveNextSubword () + { + EditActions.SelectionMoveNextSubWord (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveUp)] + void OnSelectionMoveUp () + { + EditActions.SelectionMoveUp (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveDown)] + void OnSelectionMoveDown () + { + EditActions.SelectionMoveDown (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveHome)] + void OnSelectionMoveHome () + { + EditActions.SelectionMoveLineStart (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveEnd)] + void OnSelectionMoveEnd () + { + EditActions.SelectionMoveLineEnd (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveToDocumentStart)] + void OnSelectionMoveToDocumentStart () + { + EditActions.SelectionMoveToDocumentStart (textEditor); + } + + [CommandHandler (TextEditorCommands.ExpandSelectionToLine)] + void OnExpandSelectionToLine () + { + EditActions.ExpandSelectionToLine (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionMoveToDocumentEnd)] + void OnSelectionMoveToDocumentEnd () + { + EditActions.SelectionMoveToDocumentEnd (textEditor); + } + + [CommandHandler (TextEditorCommands.SwitchCaretMode)] + void OnSwitchCaretMode () + { + EditActions.SwitchCaretMode (textEditor); + } + + [CommandHandler (TextEditorCommands.InsertTab)] + void OnInsertTab () + { + EditActions.InsertTab (textEditor); + } + + [CommandHandler (TextEditorCommands.RemoveTab)] + void OnRemoveTab () + { + EditActions.RemoveTab (textEditor); + } + + [CommandHandler (TextEditorCommands.InsertNewLine)] + void OnInsertNewLine () + { + EditActions.InsertNewLine (textEditor); + } + + [CommandHandler (TextEditorCommands.InsertNewLineAtEnd)] + void OnInsertNewLineAtEnd () + { + EditActions.InsertNewLineAtEnd (textEditor); + } + + [CommandHandler (TextEditorCommands.InsertNewLinePreserveCaretPosition)] + void OnInsertNewLinePreserveCaretPosition () + { + EditActions.InsertNewLinePreserveCaretPosition (textEditor); + } + + [CommandHandler (TextEditorCommands.CompleteStatement)] + void OnCompleteStatement () + { + var doc = IdeApp.Workbench.ActiveDocument; + var generator = CodeGenerator.CreateGenerator (doc); + if (generator != null) { + generator.CompleteStatement (doc); + } + } + + [CommandHandler (TextEditorCommands.DeletePrevWord)] + void OnDeletePrevWord () + { + EditActions.DeletePreviousWord (textEditor); + } + + [CommandHandler (TextEditorCommands.DeleteNextWord)] + void OnDeleteNextWord () + { + EditActions.DeleteNextWord (textEditor); + } + + [CommandHandler (TextEditorCommands.DeletePrevSubword)] + void OnDeletePrevSubword () + { + EditActions.DeletePreviousSubword (textEditor); + } + + [CommandHandler (TextEditorCommands.DeleteNextSubword)] + void OnDeleteNextSubword () + { + EditActions.DeleteNextSubword (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionPageDownAction)] + void OnSelectionPageDownAction () + { + EditActions.SelectionPageDown (textEditor); + } + + [CommandHandler (TextEditorCommands.SelectionPageUpAction)] + void OnSelectionPageUpAction () + { + EditActions.SelectionPageUp (textEditor); + } + + [CommandHandler (TextEditorCommands.PulseCaret)] + void OnPulseCaretCommand () + { + EditActions.StartCaretPulseAnimation (textEditor); + } + + [CommandHandler (TextEditorCommands.TransposeCharacters)] + void TransposeCharacters () + { + EditActions.TransposeCharacters (textEditor); + } + + [CommandHandler (TextEditorCommands.DuplicateLine)] + void DuplicateLine () + { + EditActions.DuplicateCurrentLine (textEditor); + } + + [CommandHandler (TextEditorCommands.RecenterEditor)] + void RecenterEditor () + { + EditActions.RecenterEditor (textEditor); + } + + [CommandHandler (EditCommands.JoinWithNextLine)] + void JoinLines () + { + EditActions.JoinLines (textEditor); + } + + [CommandHandler (TextEditorCommands.MoveBlockUp)] + void OnMoveBlockUp () + { + EditActions.MoveBlockUp (textEditor); + } + + [CommandHandler (TextEditorCommands.MoveBlockDown)] + void OnMoveBlockDown () + { + EditActions.MoveBlockDown (textEditor); + } + + [CommandHandler (TextEditorCommands.ToggleBlockSelectionMode)] + void OnToggleBlockSelectionMode () + { + EditActions.ToggleBlockSelectionMode (textEditor); + } + + [CommandHandler (EditCommands.IndentSelection)] + void IndentSelection () + { + EditActions.IndentSelection (textEditor); + } + + [CommandHandler (EditCommands.UnIndentSelection)] + void UnIndentSelection () + { + EditActions.UnIndentSelection (textEditor); + } + + + [CommandHandler (EditCommands.SortSelectedLines)] + void SortSelectedLines () + { + EditActions.SortSelectedLines (textEditor); + } + + [CommandUpdateHandler (EditCommands.SortSelectedLines)] + void UpdateSortSelectedLines (CommandInfo ci) + { + var region = textEditor.SelectionRegion; + ci.Enabled = region.BeginLine != region.EndLine; + } + #endregion + + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLink.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLink.cs new file mode 100644 index 0000000000..f5f25604bb --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLink.cs @@ -0,0 +1,136 @@ +// +// TextLink.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Ide.CodeTemplates; +using MonoDevelop.Core.Text; +using System.Collections.Generic; + +namespace MonoDevelop.Ide.Editor +{ + public sealed class TextLink : IListDataProvider<string> + { + public ISegment PrimaryLink { + get { + if (links.Count == 0) + return TextSegment.Invalid; + return links [0]; + } + } + + List<ISegment> links = new List<ISegment> (); + + public List<ISegment> Links { + get { + return links; + } + set { + links = value; + } + } + + public bool IsIdentifier { + get; + set; + } + + public bool IsEditable { + get; + set; + } + + public string Name { + get; + set; + } + + public string CurrentText { + get; + set; + } + + public string Tooltip { + get; + set; + } + + public IListDataProvider<string> Values { + get; + set; + } + + public Func<Func<string, string>, IListDataProvider<string>> GetStringFunc { + get; + set; + } + + public TextLink (string name) + { + IsEditable = true; + this.Name = name; + this.IsIdentifier = false; + } + + public override string ToString () + { + return string.Format ("[TextLink: Name={0}, Links={1}, IsEditable={2}, Tooltip={3}, CurrentText={4}, Values=({5})]", + Name, + Links.Count, + IsEditable, + Tooltip, + CurrentText, + Values.Count); + } + + public void AddLink (ISegment segment) + { + links.Add (segment); + } + #region IListDataProvider implementation + public string GetText (int n) + { + return Values != null ? Values.GetText (n) : ""; + } + + public string this [int n] { + get { + return Values != null ? Values [n] : ""; + } + } + + public Xwt.Drawing.Image GetIcon (int n) + { + return Values != null ? Values.GetIcon (n) : null; + } + + public int Count { + get { + return Values != null ? Values.Count : 0; + } + } + #endregion + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLinkModeEventArgs.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLinkModeEventArgs.cs new file mode 100644 index 0000000000..f32d8596c9 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLinkModeEventArgs.cs @@ -0,0 +1,43 @@ +// +// TextLinkModeEventArgs.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + [Serializable] + public sealed class TextLinkModeEventArgs : EventArgs + { + public bool Success { + get; + private set; + } + + public TextLinkModeEventArgs (bool success) + { + this.Success = success; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLinkModeOptions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLinkModeOptions.cs new file mode 100644 index 0000000000..ef20516cf9 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextLinkModeOptions.cs @@ -0,0 +1,66 @@ +// +// ITextEditorImpl.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// This class contains information the editor needs to initiate the text link mode. + /// </summary> + public sealed class TextLinkModeOptions + { + /// <summary> + /// The text links. Note that this property will change to IReadOnlyList in future versions. + /// </summary> + public IList<TextLink> Links { + get; + private set; + } + + /// <summary> + /// That's the action that is started after the text link mode ended. + /// This may be null (in that case no action is started). + /// </summary> + public Action<TextLinkModeEventArgs> ModeExitedAction { + get; + private set; + } + + /// <summary> + /// Initializes a new instance of the <see cref="MonoDevelop.Ide.Editor.TextLinkModeOptions"/> class. + /// </summary> + /// <param name="links">The text links. </param> + /// <param name="modeExitedAction">That's the action that is started after the text link mode ended.</param> + public TextLinkModeOptions (IList<TextLink> links, Action<TextLinkModeEventArgs> modeExitedAction = null) + { + if (links == null) + throw new ArgumentNullException ("links"); + Links = links; + ModeExitedAction = modeExitedAction; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextMarkerFactory.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextMarkerFactory.cs new file mode 100644 index 0000000000..a185dd93ba --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextMarkerFactory.cs @@ -0,0 +1,121 @@ +// +// TextMarkerFactory.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using MonoDevelop.Ide.Editor.Extension; +using MonoDevelop.Ide.TypeSystem; + +namespace MonoDevelop.Ide.Editor +{ + /// <summary> + /// The text marker factory creates line and segment markers for the text editor. + /// Note that this is the only valid way of creating markers for the editor. + /// </summary> + public static class TextMarkerFactory + { + #region Line marker + public static IUrlTextLineMarker CreateUrlTextMarker (TextEditor editor, IDocumentLine line, string value, UrlType url, string syntax, int startCol, int endCol) + { + return editor.TextMarkerFactory.CreateUrlTextMarker (editor, line, value, url, syntax, startCol, endCol); + } + + public static ICurrentDebugLineTextMarker CreateCurrentDebugLineTextMarker (TextEditor editor) + { + return editor.TextMarkerFactory.CreateCurrentDebugLineTextMarker (editor); + } + + public static ITextLineMarker CreateAsmLineMarker (TextEditor editor) + { + return editor.TextMarkerFactory.CreateAsmLineMarker (editor); + } + + public static IUnitTestMarker CreateUnitTestMarker (TextEditor editor, UnitTestMarkerHost host, UnitTestLocation unitTestLocation) + { + return editor.TextMarkerFactory.CreateUnitTestMarker (editor, host, unitTestLocation); + } + + public static IMessageBubbleLineMarker CreateMessageBubbleLineMarker (TextEditor editor) + { + return editor.TextMarkerFactory.CreateMessageBubbleLineMarker (editor); + } + + + #endregion + + #region Segment marker + public static ITextSegmentMarker CreateUsageMarker (TextEditor editor, Usage usage) + { + return editor.TextMarkerFactory.CreateUsageMarker (editor, usage); + } + + public static ITextSegmentMarker CreateLinkMarker (TextEditor editor, int offset, int length, Action<LinkRequest> activateLink) + { + return editor.TextMarkerFactory.CreateLinkMarker (editor, offset, length, activateLink); + } + + public static ITextSegmentMarker CreateLinkMarker (TextEditor editor, ISegment segment, Action<LinkRequest> activateLink) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + return editor.TextMarkerFactory.CreateLinkMarker (editor, segment.Offset, segment.Length, activateLink); + } + + public static IGenericTextSegmentMarker CreateGenericTextSegmentMarker (TextEditor editor, TextSegmentMarkerEffect effect, int offset, int length) + { + return editor.TextMarkerFactory.CreateGenericTextSegmentMarker (editor, effect, offset, length); + } + + public static IGenericTextSegmentMarker CreateGenericTextSegmentMarker (TextEditor editor, TextSegmentMarkerEffect effect, ISegment segment) + { + if (segment == null) + throw new ArgumentNullException ("segment"); + return editor.TextMarkerFactory.CreateGenericTextSegmentMarker (editor, effect, segment.Offset, segment.Length); + } + + public static ISmartTagMarker CreateSmartTagMarker (TextEditor editor, int offset, DocumentLocation realLocation) + { + return editor.TextMarkerFactory.CreateSmartTagMarker (editor, offset, realLocation); + } + + static bool IsIdentifierPart (char ch) + { + return char.IsLetterOrDigit (ch) || ch == '_'; + } + + public static IErrorMarker CreateErrorMarker (TextEditor editor, Error info) + { + int offset = editor.LocationToOffset (info.Region.BeginLine, info.Region.BeginColumn); + int endOffset = editor.LocationToOffset (info.Region.EndLine, info.Region.EndColumn); + if (endOffset < offset) { + endOffset = offset + 1; + while (endOffset < editor.Length && IsIdentifierPart (editor.GetCharAt (endOffset))) + endOffset++; + } + return editor.TextMarkerFactory.CreateErrorMarker (editor, info, offset, endOffset - offset); + } + #endregion + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextMarkerMouseEventArgs.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextMarkerMouseEventArgs.cs new file mode 100644 index 0000000000..7a66ca5924 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextMarkerMouseEventArgs.cs @@ -0,0 +1,49 @@ +// +// ITextSegmentMarker.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using MonoDevelop.Ide.Editor; + +namespace MonoDevelop.Ide.Editor +{ + public abstract class TextMarkerMouseEventArgs : EventArgs + { + public abstract double X { + get; + } + + public abstract double Y { + get; + } + + public abstract object OverwriteCursor { get; set; } + + public abstract string TooltipMarkup { get; set; } + + public abstract bool TriggersContextMenu (); + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TooltipExtensionNode.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TooltipExtensionNode.cs new file mode 100644 index 0000000000..d47513e925 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TooltipExtensionNode.cs @@ -0,0 +1,49 @@ +// +// TooltipExtensionNode.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// 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 MonoDevelop.Core.Text; +using MonoDevelop.Components; +using MonoDevelop.Ide.CodeCompletion; +using Mono.Addins; + +namespace MonoDevelop.Ide.Editor +{ + sealed class TooltipExtensionNode : TypeExtensionNode + { + [NodeAttribute("mimeType", false, "The mimetype that this tooltip provider can handle.")] + string mimeType; + + public string MimeType { + get { return mimeType; } + } + + internal bool IsValidFor (string mimeType) + { + return string.IsNullOrEmpty (this.mimeType) || this.mimeType == mimeType; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TooltipProvider.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TooltipProvider.cs new file mode 100644 index 0000000000..e397e54664 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TooltipProvider.cs @@ -0,0 +1,177 @@ +// +// TooltipProvider.cs +// +// Author: +// Lluis Sanchez <lluis@xamarin.com> +// +// Copyright (c) 2012 Xamarin Inc +// +// 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 MonoDevelop.Core.Text; +using MonoDevelop.Components; +using MonoDevelop.Ide.CodeCompletion; + +namespace MonoDevelop.Ide.Editor +{ + public sealed class TooltipItem : ISegment + { + int offset; + int length; + + #region ISegment implementation + + public int Offset { + get { + return offset; + } + internal set { + offset = value; + } + } + + public int Length { + get { + return length; + } + internal set { + length = value; + } + } + + public int EndOffset { + get { + return offset + length; + } + } + + #endregion + + public object Item { get; set; } + + public TooltipItem (object item, ISegment itemSegment) + { + if (itemSegment == null) + throw new ArgumentNullException ("itemSegment"); + Item = item; + this.offset = itemSegment.Offset; + this.length = itemSegment.Length; + } + + public TooltipItem (object item, int offset, int length) + { + Item = item; + this.offset = offset; + this.length = length; + } + } + + // TODO: Improve tooltip API - that really looks messy + public abstract class TooltipProvider + { + public abstract TooltipItem GetItem (TextEditor editor, DocumentContext ctx, int offset); + + public virtual bool IsInteractive (TextEditor editor, Control tipWindow) + { + return false; + } + + public virtual void GetRequiredPosition (TextEditor editor, Control tipWindow, out int requiredWidth, out double xalign) + { + requiredWidth = ((Gtk.Widget)tipWindow).SizeRequest ().Width; + xalign = 0.5; + } + + public virtual Control CreateTooltipWindow (TextEditor editor, DocumentContext ctx, TooltipItem item, int offset, Gdk.ModifierType modifierState) + { + return null; + } + + protected Xwt.Rectangle GetAllocation (TextEditor editor) + { + return editor.GetContent<ITextEditorImpl> ().GetEditorAllocation (); + } + + void ShowTipInfoWindow (TextEditor editor, TooltipInformationWindow tipWindow, TooltipItem item, Gdk.ModifierType modifierState, int mouseX, int mouseY) + { + Gtk.Widget editorWidget = editor; + + var startLoc = editor.OffsetToLocation (item.Offset); + var endLoc = editor.OffsetToLocation (item.EndOffset); + var p1 = editor.LocationToPoint (startLoc); + var p2 = editor.LocationToPoint (endLoc); + + int w = (int)(p2.X - p1.X); + + var caret = new Gdk.Rectangle ( + (int)p1.X, + (int)p1.Y, + (int)w, + (int)editor.LineHeight + ); + + tipWindow.ShowPopup (editorWidget, caret, PopupPosition.Top); + } + + public virtual void ShowTooltipWindow (TextEditor editor, Control tipWindow, TooltipItem item, Gdk.ModifierType modifierState, int mouseX, int mouseY) + { + if (tipWindow == null) + return; + + var tipInfoWindow = ((Gtk.Widget)tipWindow) as TooltipInformationWindow; + if (tipInfoWindow != null) { + ShowTipInfoWindow (editor, tipInfoWindow, item, modifierState, mouseX, mouseY); + return; + } + + var origin = editor.GetContent<ITextEditorImpl> ().GetEditorWindowOrigin (); + + int w; + double xalign; + GetRequiredPosition (editor, tipWindow, out w, out xalign); + w += 10; + + var allocation = GetAllocation (editor); + int x = (int)(mouseX + origin.X + allocation.X); + int y = (int)(mouseY + origin.Y + allocation.Y); + Gtk.Widget widget = editor; + var geometry = widget.Screen.GetUsableMonitorGeometry (widget.Screen.GetMonitorAtPoint (x, y)); + + x -= (int) ((double) w * xalign); + y += 10; + + if (x + w >= geometry.X + geometry.Width) + x = geometry.X + geometry.Width - w; + if (x < geometry.Left) + x = geometry.Left; + + var gtkWindow = (Gtk.Window)tipWindow; + int h = gtkWindow.SizeRequest ().Height; + if (y + h >= geometry.Y + geometry.Height) + y = geometry.Y + geometry.Height - h; + if (y < geometry.Top) + y = geometry.Top; + + gtkWindow.Move (x, y); + + gtkWindow.ShowAll (); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/WordFindStrategy.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/WordFindStrategy.cs new file mode 100644 index 0000000000..d850856538 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/WordFindStrategy.cs @@ -0,0 +1,38 @@ +// +// WordFindStrategy.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) +// +// 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; + +namespace MonoDevelop.Ide.Editor +{ + public enum WordFindStrategy + { + MonoDevelop, + Emacs, + SharpDevelop, + Vim + } +} + |