diff options
Diffstat (limited to 'main/src/core/Mono.Texteditor')
55 files changed, 2809 insertions, 1287 deletions
diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/ColorScheme.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/ColorScheme.cs index 79dc77fd34..b2a0451ba3 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/ColorScheme.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/ColorScheme.cs @@ -36,7 +36,7 @@ using Xwt.Drawing; namespace Mono.TextEditor.Highlighting { - public class ColorScheme + public sealed class ColorScheme { public string Name { get; set; } public string Description { get; set; } @@ -202,335 +202,437 @@ namespace Mono.TextEditor.Highlighting #endregion #region Text Colors - - [ColorDescription("Plain Text", VSSetting = "Plain Text")] + + public const string PlainTextKey = "Plain Text"; + + [ColorDescription(PlainTextKey, VSSetting = "Plain Text")] public ChunkStyle PlainText { get; private set; } - [ColorDescription("Selected Text", VSSetting = "Selected Text")] + public const string SelectedTextKey = "Selected Text"; + [ColorDescription(SelectedTextKey, VSSetting = "Selected Text")] public ChunkStyle SelectedText { get; private set; } - [ColorDescription("Selected Text(Inactive)", VSSetting = "Inactive Selected Text")] + public const string SelectedInactiveTextKey = "Selected Text(Inactive)"; + [ColorDescription(SelectedInactiveTextKey, VSSetting = "Inactive Selected Text")] public ChunkStyle SelectedInactiveText { get; private set; } - [ColorDescription("Collapsed Text", VSSetting = "Collapsible Text")] + public const string CollapsedTextKey = "Collapsed Text"; + [ColorDescription(CollapsedTextKey, VSSetting = "Collapsible Text")] public ChunkStyle CollapsedText { get; private set; } - [ColorDescription("Line Numbers", VSSetting = "Line Numbers")] + public const string LineNumbersKey = "Line Numbers"; + [ColorDescription(LineNumbersKey, VSSetting = "Line Numbers")] public ChunkStyle LineNumbers { get; private set; } - [ColorDescription("Punctuation", VSSetting = "Operator")] + public const string PunctuationKey = "Punctuation"; + [ColorDescription(PunctuationKey, VSSetting = "Operator")] public ChunkStyle Punctuation { get; private set; } - [ColorDescription("Punctuation(Brackets)", VSSetting = "Plain Text")] + public const string PunctuationForBracketsKey = "Punctuation(Brackets)"; + [ColorDescription(PunctuationForBracketsKey, VSSetting = "Plain Text")] public ChunkStyle PunctuationForBrackets { get; private set; } - [ColorDescription("Comment(Line)", VSSetting = "Comment")] + public const string CommentsSingleLineKey = "Comment(Line)"; + [ColorDescription(CommentsSingleLineKey, VSSetting = "Comment")] public ChunkStyle CommentsSingleLine { get; private set; } - [ColorDescription("Comment(Block)", VSSetting = "Comment")] - public ChunkStyle CommentsMultiLine { get; private set; } + public const string CommentsBlockKey = "Comment(Block)"; + [ColorDescription(CommentsBlockKey, VSSetting = "Comment")] + public ChunkStyle CommentsBlock { get; private set; } - [ColorDescription("Comment(Doc)", VSSetting = "XML Doc Comment")] + public const string CommentsForDocumentationKey = "Comment(Doc)"; + [ColorDescription(CommentsForDocumentationKey, VSSetting = "XML Doc Comment")] public ChunkStyle CommentsForDocumentation { get; private set; } - [ColorDescription("Comment(DocTag)", VSSetting = "XML Doc Tag")] + public const string CommentsForDocumentationTagsKey = "Comment(DocTag)"; + [ColorDescription(CommentsForDocumentationTagsKey, VSSetting = "XML Doc Tag")] public ChunkStyle CommentsForDocumentationTags { get; private set; } - [ColorDescription("Comment Tag", VSSetting = "Comment")] + public const string CommentTagsKey = "Comment Tag"; + [ColorDescription(CommentTagsKey, VSSetting = "Comment")] public ChunkStyle CommentTags { get; private set; } - [ColorDescription("Excluded Code", VSSetting = "Excluded Code")] + public const string ExcludedCodeKey = "Excluded Code"; + [ColorDescription(ExcludedCodeKey, VSSetting = "Excluded Code")] public ChunkStyle ExcludedCode { get; private set; } - [ColorDescription("String", VSSetting = "String")] + public const string StringKey = "String"; + [ColorDescription(StringKey, VSSetting = "String")] public ChunkStyle String { get; private set; } - [ColorDescription("String(Escape)", VSSetting = "String")] + public const string StringEscapeSequenceKey = "String(Escape)"; + [ColorDescription(StringEscapeSequenceKey, VSSetting = "String")] public ChunkStyle StringEscapeSequence { get; private set; } - [ColorDescription("String(C# @ Verbatim)", VSSetting = "String(C# @ Verbatim)")] + public const string StringVerbatimKey = "String(C# @ Verbatim)"; + [ColorDescription(StringVerbatimKey, VSSetting = "String(C# @ Verbatim)")] public ChunkStyle StringVerbatim { get; private set; } - [ColorDescription("Number", VSSetting = "Number")] + public const string NumberKey = "Number"; + [ColorDescription(NumberKey, VSSetting = "Number")] public ChunkStyle Number { get; private set; } - [ColorDescription("Preprocessor", VSSetting = "Preprocessor Keyword")] + public const string PreprocessorKey = "Preprocessor"; + [ColorDescription(PreprocessorKey, VSSetting = "Preprocessor Keyword")] public ChunkStyle Preprocessor { get; private set; } - [ColorDescription("Preprocessor(Region Name)", VSSetting = "Plain Text")] + public const string PreprocessorRegionNameKey = "Preprocessor(Region Name)"; + [ColorDescription(PreprocessorRegionNameKey, VSSetting = "Plain Text")] public ChunkStyle PreprocessorRegionName { get; private set; } - [ColorDescription("Xml Text", VSSetting = "XML Text")] + public const string XmlTextKey = "Xml Text"; + [ColorDescription(XmlTextKey, VSSetting = "XML Text")] public ChunkStyle XmlText { get; private set; } - [ColorDescription("Xml Delimiter", VSSetting = "XML Delimiter")] + public const string XmlDelimiterKey = "Xml Delimiter"; + [ColorDescription(XmlDelimiterKey, VSSetting = "XML Delimiter")] public ChunkStyle XmlDelimiter { get; private set; } - [ColorDescription("Xml Name", VSSetting ="XML Name")] + public const string XmlNameKey = "Xml Name"; + [ColorDescription(XmlNameKey, VSSetting ="XML Name")] public ChunkStyle XmlName { get; private set; } - [ColorDescription("Xml Attribute", VSSetting = "XML Attribute")] + public const string XmlAttributeKey = "Xml Attribute"; + [ColorDescription(XmlAttributeKey, VSSetting = "XML Attribute")] public ChunkStyle XmlAttribute { get; private set; } - [ColorDescription("Xml Attribute Quotes", VSSetting = "XML Attribute Quotes")] + public const string XmlAttributeQuotesKey = "Xml Attribute Quotes"; + [ColorDescription(XmlAttributeQuotesKey, VSSetting = "XML Attribute Quotes")] public ChunkStyle XmlAttributeQuotes { get; private set; } - [ColorDescription("Xml Attribute Value", VSSetting = "XML Attribute Value")] + public const string XmlAttributeValueKey = "Xml Attribute Value"; + [ColorDescription(XmlAttributeValueKey, VSSetting = "XML Attribute Value")] public ChunkStyle XmlAttributeValue { get; private set; } - [ColorDescription("Xml Comment", VSSetting = "XML Comment")] + public const string XmlCommentKey = "Xml Comment"; + [ColorDescription(XmlCommentKey, VSSetting = "XML Comment")] public ChunkStyle XmlComment { get; private set; } - [ColorDescription("Xml CData Section", VSSetting = "XML CData Section")] + public const string XmlCDataSectionKey = "Xml CData Section"; + [ColorDescription(XmlCDataSectionKey, VSSetting = "XML CData Section")] public ChunkStyle XmlCDataSection { get; private set; } - [ColorDescription("Tooltip Text")] // not defined in vs.net + public const string TooltipTextKey = "Tooltip Text"; + [ColorDescription(TooltipTextKey)] // not defined in vs.net public ChunkStyle TooltipText { get; private set; } - [ColorDescription("Notification Text")] // not defined in vs.net + public const string NotificationTextKey = "Notification Text"; + [ColorDescription(NotificationTextKey)] // not defined in vs.net public ChunkStyle NotificationText { get; private set; } - [ColorDescription("Completion Text")] //not defined in vs.net + public const string CompletionTextKey = "Completion Text"; + [ColorDescription(CompletionTextKey)] //not defined in vs.net public ChunkStyle CompletionText { get; private set; } - [ColorDescription("Completion Selected Text")] //not defined in vs.net + public const string CompletionSelectedTextKey = "Completion Selected Text"; + [ColorDescription(CompletionSelectedTextKey)] //not defined in vs.net public ChunkStyle CompletionSelectedText { get; private set; } - [ColorDescription("Completion Selected Text(Inactive)")] //not defined in vs.net + public const string CompletionSelectedInactiveTextKey = "Completion Selected Text(Inactive)"; + [ColorDescription(CompletionSelectedInactiveTextKey)] //not defined in vs.net public ChunkStyle CompletionSelectedInactiveText { get; private set; } - [ColorDescription("Keyword(Access)", VSSetting = "Keyword")] + public const string KeywordAccessorsKey = "Keyword(Access)"; + [ColorDescription(KeywordAccessorsKey, VSSetting = "Keyword")] public ChunkStyle KeywordAccessors { get; private set; } - [ColorDescription("Keyword(Type)", VSSetting = "Keyword")] + public const string KeywordTypesKey = "Keyword(Type)"; + [ColorDescription(KeywordTypesKey, VSSetting = "Keyword")] public ChunkStyle KeywordTypes { get; private set; } - [ColorDescription("Keyword(Operator)", VSSetting = "Keyword")] + public const string KeywordOperatorsKey = "Keyword(Operator)"; + [ColorDescription(KeywordOperatorsKey, VSSetting = "Keyword")] public ChunkStyle KeywordOperators { get; private set; } - [ColorDescription("Keyword(Selection)", VSSetting = "Keyword")] + public const string KeywordSelectionKey = "Keyword(Selection)"; + [ColorDescription(KeywordSelectionKey, VSSetting = "Keyword")] public ChunkStyle KeywordSelection { get; private set; } - [ColorDescription("Keyword(Iteration)", VSSetting = "Keyword")] + public const string KeywordIterationKey = "Keyword(Iteration)"; + [ColorDescription(KeywordIterationKey, VSSetting = "Keyword")] public ChunkStyle KeywordIteration { get; private set; } - [ColorDescription("Keyword(Jump)", VSSetting = "Keyword")] + public const string KeywordJumpKey = "Keyword(Jump)"; + [ColorDescription(KeywordJumpKey, VSSetting = "Keyword")] public ChunkStyle KeywordJump { get; private set; } - [ColorDescription("Keyword(Context)", VSSetting = "Keyword")] + public const string KeywordContextKey = "Keyword(Context)"; + [ColorDescription(KeywordContextKey, VSSetting = "Keyword")] public ChunkStyle KeywordContext { get; private set; } - [ColorDescription("Keyword(Exception)", VSSetting = "Keyword")] + public const string KeywordExceptionKey = "Keyword(Exception)"; + [ColorDescription(KeywordExceptionKey, VSSetting = "Keyword")] public ChunkStyle KeywordException { get; private set; } - [ColorDescription("Keyword(Modifiers)", VSSetting = "Keyword")] + public const string KeywordModifiersKey = "Keyword(Modifiers)"; + [ColorDescription(KeywordModifiersKey, VSSetting = "Keyword")] public ChunkStyle KeywordModifiers { get; private set; } - [ColorDescription("Keyword(Constants)", VSSetting = "Keyword")] + public const string KeywordConstantsKey = "Keyword(Constants)"; + [ColorDescription(KeywordConstantsKey, VSSetting = "Keyword")] public ChunkStyle KeywordConstants { get; private set; } - [ColorDescription("Keyword(Void)", VSSetting = "Keyword")] + public const string KeywordVoidKey = "Keyword(Void)"; + [ColorDescription(KeywordVoidKey, VSSetting = "Keyword")] public ChunkStyle KeywordVoid { get; private set; } - [ColorDescription("Keyword(Namespace)", VSSetting = "Keyword")] + public const string KeywordNamespaceKey = "Keyword(Namespace)"; + [ColorDescription(KeywordNamespaceKey, VSSetting = "Keyword")] public ChunkStyle KeywordNamespace { get; private set; } - [ColorDescription("Keyword(Property)", VSSetting = "Keyword")] + public const string KeywordPropertyKey = "Keyword(Property)"; + [ColorDescription(KeywordPropertyKey, VSSetting = "Keyword")] public ChunkStyle KeywordProperty { get; private set; } - [ColorDescription("Keyword(Declaration)", VSSetting = "Keyword")] + public const string KeywordDeclarationKey = "Keyword(Declaration)"; + [ColorDescription(KeywordDeclarationKey, VSSetting = "Keyword")] public ChunkStyle KeywordDeclaration { get; private set; } - [ColorDescription("Keyword(Parameter)", VSSetting = "Keyword")] + public const string KeywordParameterKey = "Keyword(Parameter)"; + [ColorDescription(KeywordParameterKey, VSSetting = "Keyword")] public ChunkStyle KeywordParameter { get; private set; } - [ColorDescription("Keyword(Operator Declaration)", VSSetting = "Keyword")] + public const string KeywordOperatorDeclarationKey = "Keyword(Operator Declaration)"; + [ColorDescription(KeywordOperatorDeclarationKey, VSSetting = "Keyword")] public ChunkStyle KeywordOperatorDeclaration { get; private set; } - [ColorDescription("Keyword(Other)", VSSetting = "Keyword")] + public const string KeywordOtherKey = "Keyword(Other)"; + [ColorDescription(KeywordOtherKey, VSSetting = "Keyword")] public ChunkStyle KeywordOther { get; private set; } - [ColorDescription("User Types", VSSetting = "User Types")] + public const string UserTypesKey = "User Types"; + [ColorDescription(UserTypesKey, VSSetting = "User Types")] public ChunkStyle UserTypes { get; private set; } - [ColorDescription("User Types(Enums)", VSSetting = "User Types(Enums)")] + public const string UserTypesEnumsKey = "User Types(Enums)"; + [ColorDescription(UserTypesEnumsKey, VSSetting = "User Types(Enums)")] public ChunkStyle UserTypesEnums { get; private set; } - [ColorDescription("User Types(Interfaces)", VSSetting = "User Types(Interfaces)")] + public const string UserTypesInterfacesKey = "User Types(Interfaces)"; + [ColorDescription(UserTypesInterfacesKey, VSSetting = "User Types(Interfaces)")] public ChunkStyle UserTypesInterfaces { get; private set; } - [ColorDescription("User Types(Delegates)", VSSetting = "User Types(Delegates)")] - public ChunkStyle UserTypesDelegatess { get; private set; } + public const string UserTypesDelegatesKey = "User Types(Delegates)"; + [ColorDescription(UserTypesDelegatesKey, VSSetting = "User Types(Delegates)")] + public ChunkStyle UserTypesDelegates { get; private set; } - [ColorDescription("User Types(Value types)", VSSetting = "User Types(Value types)")] + public const string UserTypesValueTypesKey = "User Types(Value types)"; + [ColorDescription(UserTypesValueTypesKey, VSSetting = "User Types(Value types)")] public ChunkStyle UserTypesValueTypes { get; private set; } - [ColorDescription("User Types(Type parameters)", VSSetting = "User Types(Type parameters)")] + public const string UserTypesTypeParametersKey = "User Types(Type parameters)"; + [ColorDescription(UserTypesTypeParametersKey, VSSetting = "User Types(Type parameters)")] public ChunkStyle UserTypesTypeParameters { get; private set; } - [ColorDescription("User Field Usage", VSSetting = "Identifier")] + public const string UserFieldUsageKey = "User Field Usage"; + [ColorDescription(UserFieldUsageKey, VSSetting = "Identifier")] public ChunkStyle UserFieldUsage { get; private set; } - [ColorDescription("User Field Declaration", VSSetting = "Identifier")] + public const string UserFieldDeclarationKey = "User Field Declaration"; + [ColorDescription(UserFieldDeclarationKey, VSSetting = "Identifier")] public ChunkStyle UserFieldDeclaration { get; private set; } - [ColorDescription("User Property Usage", VSSetting = "Identifier")] + public const string UserPropertyUsageKey = "User Property Usage"; + [ColorDescription(UserPropertyUsageKey, VSSetting = "Identifier")] public ChunkStyle UserPropertyUsage { get; private set; } - [ColorDescription("User Property Declaration", VSSetting = "Identifier")] + public const string UserPropertyDeclarationKey = "User Property Declaration"; + [ColorDescription(UserPropertyDeclarationKey, VSSetting = "Identifier")] public ChunkStyle UserPropertyDeclaration { get; private set; } - [ColorDescription("User Event Usage", VSSetting = "Identifier")] + public const string UserEventUsageKey = "User Event Usage"; + [ColorDescription(UserEventUsageKey, VSSetting = "Identifier")] public ChunkStyle UserEventUsage { get; private set; } - [ColorDescription("User Event Declaration", VSSetting = "Identifier")] + public const string UserEventDeclarationKey = "User Event Declaration"; + [ColorDescription(UserEventDeclarationKey, VSSetting = "Identifier")] public ChunkStyle UserEventDeclaration { get; private set; } - [ColorDescription("User Method Usage", VSSetting = "Identifier")] + public const string UserMethodUsageKey = "User Method Usage"; + [ColorDescription(UserMethodUsageKey, VSSetting = "Identifier")] public ChunkStyle UserMethodUsage { get; private set; } - [ColorDescription("User Method Declaration", VSSetting = "Identifier")] + public const string UserMethodDeclarationKey = "User Method Declaration"; + [ColorDescription(UserMethodDeclarationKey, VSSetting = "Identifier")] public ChunkStyle UserMethodDeclaration { get; private set; } - [ColorDescription("User Parameter Usage", VSSetting = "Identifier")] + public const string UserParameterUsageKey = "User Parameter Usage"; + [ColorDescription(UserParameterUsageKey, VSSetting = "Identifier")] public ChunkStyle UserParameterUsage { get; private set; } - [ColorDescription("User Parameter Declaration", VSSetting = "Identifier")] + public const string UserParameterDeclarationKey = "User Parameter Declaration"; + [ColorDescription(UserParameterDeclarationKey, VSSetting = "Identifier")] public ChunkStyle UserParameterDeclaration { get; private set; } - [ColorDescription("User Variable Usage", VSSetting = "Identifier")] + public const string UserVariableUsageKey = "User Variable Usage"; + [ColorDescription(UserVariableUsageKey, VSSetting = "Identifier")] public ChunkStyle UserVariableUsage { get; private set; } - [ColorDescription("User Variable Declaration", VSSetting = "Identifier")] + public const string UserVariableDeclarationKey = "User Variable Declaration"; + [ColorDescription(UserVariableDeclarationKey, VSSetting = "Identifier")] public ChunkStyle UserVariableDeclaration { get; private set; } - [ColorDescription("Syntax Error", VSSetting = "Syntax Error")] + public const string SyntaxErrorKey = "Syntax Error"; + [ColorDescription(SyntaxErrorKey, VSSetting = "Syntax Error")] public ChunkStyle SyntaxError { get; private set; } - [ColorDescription("String Format Items", VSSetting = "String")] + public const string StringFormatItemsKey = "String Format Items"; + [ColorDescription(StringFormatItemsKey, VSSetting = "String")] public ChunkStyle StringFormatItems { get; private set; } - [ColorDescription("Breakpoint Text", VSSetting = "Breakpoint (Enabled)")] + public const string BreakpointTextKey = "Breakpoint Text"; + [ColorDescription(BreakpointTextKey, VSSetting = "Breakpoint (Enabled)")] public ChunkStyle BreakpointText { get; private set; } - [ColorDescription("Debugger Current Statement", VSSetting = "Current Statement")] + public const string DebuggerCurrentLineKey = "Debugger Current Statement"; + [ColorDescription(DebuggerCurrentLineKey, VSSetting = "Current Statement")] public ChunkStyle DebuggerCurrentLine { get; private set; } - [ColorDescription("Debugger Stack Line")] // not defined + public const string DebuggerStackLineKey = "Debugger Stack Line"; + [ColorDescription(DebuggerStackLineKey)] // not defined public ChunkStyle DebuggerStackLine { get; private set; } - [ColorDescription("Diff Line(Added)")] //not defined + public const string DiffLineAddedKey = "Diff Line(Added)"; + [ColorDescription(DiffLineAddedKey)] //not defined public ChunkStyle DiffLineAdded { get; private set; } - [ColorDescription("Diff Line(Removed)")] //not defined + public const string DiffLineRemovedKey = "Diff Line(Removed)"; + [ColorDescription(DiffLineRemovedKey)] //not defined public ChunkStyle DiffLineRemoved { get; private set; } - [ColorDescription("Diff Line(Changed)")] //not defined + public const string DiffLineChangedKey = "Diff Line(Changed)"; + [ColorDescription(DiffLineChangedKey)] //not defined public ChunkStyle DiffLineChanged { get; private set; } - [ColorDescription("Diff Header")] //not defined + public const string DiffHeaderKey = "Diff Header"; + [ColorDescription(DiffHeaderKey)] //not defined public ChunkStyle DiffHeader { get; private set; } - [ColorDescription("Diff Header(Separator)")] //not defined + public const string DiffHeaderSeparatorKey = "Diff Header(Separator)"; + [ColorDescription(DiffHeaderSeparatorKey)] //not defined public ChunkStyle DiffHeaderSeparator { get; private set; } - [ColorDescription("Diff Header(Old)")] //not defined + public const string DiffHeaderOldKey = "Diff Header(Old)"; + [ColorDescription(DiffHeaderOldKey)] //not defined public ChunkStyle DiffHeaderOld { get; private set; } - [ColorDescription("Diff Header(New)")] //not defined + public const string DiffHeaderNewKey = "Diff Header(New)"; + [ColorDescription(DiffHeaderNewKey)] //not defined public ChunkStyle DiffHeaderNew { get; private set; } - [ColorDescription("Diff Location")] //not defined + public const string DiffLocationKey = "Diff Location"; + [ColorDescription(DiffLocationKey)] //not defined public ChunkStyle DiffLocation { get; private set; } - [ColorDescription("Html Attribute Name", VSSetting="HTML Attribute")] + public const string HtmlAttributeNameKey = "Html Attribute Name"; + [ColorDescription(HtmlAttributeNameKey, VSSetting="HTML Attribute")] public ChunkStyle HtmlAttributeName { get; private set; } - - [ColorDescription("Html Attribute Value", VSSetting="HTML Attribute Value")] + + public const string HtmlAttributeValueKey = "Html Attribute Value"; + [ColorDescription(HtmlAttributeValueKey, VSSetting="HTML Attribute Value")] public ChunkStyle HtmlAttributeValue { get; private set; } - [ColorDescription("Html Comment", VSSetting="HTML Comment")] + public const string HtmlCommentKey = "Html Comment"; + [ColorDescription(HtmlCommentKey, VSSetting="HTML Comment")] public ChunkStyle HtmlComment { get; private set; } - [ColorDescription("Html Element Name", VSSetting="HTML Element Name")] + public const string HtmlElementNameKey = "Html Element Name"; + [ColorDescription(HtmlElementNameKey, VSSetting="HTML Element Name")] public ChunkStyle HtmlElementName { get; private set; } - [ColorDescription("Html Entity", VSSetting="HTML Entity")] + public const string HtmlEntityKey = "Html Entity"; + [ColorDescription(HtmlEntityKey, VSSetting="HTML Entity")] public ChunkStyle HtmlEntity { get; private set; } - [ColorDescription("Html Operator", VSSetting="HTML Operator")] + public const string HtmlOperatorKey = "Html Operator"; + [ColorDescription(HtmlOperatorKey, VSSetting="HTML Operator")] public ChunkStyle HtmlOperator { get; private set; } - [ColorDescription("Html Server-Side Script", VSSetting="HTML Server-Side Script")] + public const string HtmlServerSideScriptKey = "Html Server-Side Script"; + [ColorDescription(HtmlServerSideScriptKey, VSSetting="HTML Server-Side Script")] public ChunkStyle HtmlServerSideScript { get; private set; } - [ColorDescription("Html Tag Delimiter", VSSetting="HTML Tag Delimiter")] + public const string HtmlTagDelimiterKey = "Html Tag Delimiter"; + [ColorDescription(HtmlTagDelimiterKey, VSSetting="HTML Tag Delimiter")] public ChunkStyle HtmlTagDelimiter { get; private set; } - [ColorDescription("Razor Code", VSSetting="Razor Code")] + public const string RazorCodeKey = "Razor Code"; + [ColorDescription(RazorCodeKey, VSSetting="Razor Code")] public ChunkStyle RazorCode { get; private set; } - [ColorDescription("Css Comment", VSSetting="CSS Comment")] + public const string CssCommentKey = "Css Comment"; + [ColorDescription(CssCommentKey, VSSetting="CSS Comment")] public ChunkStyle CssComment { get; private set; } - [ColorDescription("Css Property Name", VSSetting="CSS Property Name")] + public const string CssPropertyNameKey = "Css Property Name"; + [ColorDescription(CssPropertyNameKey, VSSetting="CSS Property Name")] public ChunkStyle CssPropertyName { get; private set; } - [ColorDescription("Css Property Value", VSSetting="CSS Property Value")] + public const string CssPropertyValueKey = "Css Property Value"; + [ColorDescription(CssPropertyValueKey, VSSetting="CSS Property Value")] public ChunkStyle CssPropertyValue { get; private set; } - [ColorDescription("Css Selector", VSSetting="CSS Selector")] + public const string CssSelectorKey = "Css Selector"; + [ColorDescription(CssSelectorKey, VSSetting="CSS Selector")] public ChunkStyle CssSelector { get; private set; } - [ColorDescription("Css String Value", VSSetting="CSS String Value")] + public const string CssStringValueKey = "Css String Value"; + [ColorDescription(CssStringValueKey, VSSetting="CSS String Value")] public ChunkStyle CssStringValue { get; private set; } - [ColorDescription("Css Keyword", VSSetting="CSS Keyword")] + public const string CssKeywordKey = "Css Keyword"; + [ColorDescription(CssKeywordKey, VSSetting="CSS Keyword")] public ChunkStyle CssKeyword { get; private set; } - [ColorDescription("Script Comment", VSSetting="Script Comment")] + public const string ScriptCommentKey = "Script Comment"; + [ColorDescription(ScriptCommentKey, VSSetting="Script Comment")] public ChunkStyle ScriptComment { get; private set; } - [ColorDescription("Script Identifier", VSSetting="Script Identifier")] + public const string ScriptIdentifierKey = "Script Identifier"; + [ColorDescription(ScriptIdentifierKey, VSSetting="Script Identifier")] public ChunkStyle ScriptIdentifier { get; private set; } - [ColorDescription("Script Keyword", VSSetting="Script Keyword")] + public const string ScriptKeywordKey = "Script Keyword"; + [ColorDescription(ScriptKeywordKey, VSSetting="Script Keyword")] public ChunkStyle ScriptKeyword { get; private set; } - [ColorDescription("Script Number", VSSetting="Script Number")] + public const string ScriptNumberKey = "Script Number"; + [ColorDescription(ScriptNumberKey, VSSetting="Script Number")] public ChunkStyle ScriptNumber { get; private set; } - [ColorDescription("Script Operator", VSSetting="Script Operator")] + public const string ScriptOperatorKey = "Script Operator"; + [ColorDescription(ScriptOperatorKey, VSSetting="Script Operator")] public ChunkStyle ScriptOperator { get; private set; } - [ColorDescription("Script String", VSSetting="Script String")] + public const string ScriptStringKey = "Script String"; + [ColorDescription(ScriptStringKey, VSSetting="Script String")] public ChunkStyle ScriptString { get; private set; } #endregion - public class PropertyDecsription + public sealed class PropertyDescription { public readonly PropertyInfo Info; public readonly ColorDescriptionAttribute Attribute; - public PropertyDecsription (PropertyInfo info, ColorDescriptionAttribute attribute) + public PropertyDescription (PropertyInfo info, ColorDescriptionAttribute attribute) { this.Info = info; this.Attribute = attribute; } } - static Dictionary<string, PropertyDecsription> textColors = new Dictionary<string, PropertyDecsription> (); + static Dictionary<string, PropertyDescription> textColors = new Dictionary<string, PropertyDescription> (); - public static IEnumerable<PropertyDecsription> TextColors { + public static IEnumerable<PropertyDescription> TextColors { get { return textColors.Values; } } - static Dictionary<string, PropertyDecsription> ambientColors = new Dictionary<string, PropertyDecsription> (); + static Dictionary<string, PropertyDescription> ambientColors = new Dictionary<string, PropertyDescription> (); - public static IEnumerable<PropertyDecsription> AmbientColors { + public static IEnumerable<PropertyDescription> AmbientColors { get { return ambientColors.Values; } @@ -543,9 +645,9 @@ namespace Mono.TextEditor.Highlighting if (description == null) continue; if (property.PropertyType == typeof (ChunkStyle)) { - textColors.Add (description.Name, new PropertyDecsription (property, description)); + textColors.Add (description.Name, new PropertyDescription (property, description)); } else { - ambientColors.Add (description.Name, new PropertyDecsription (property, description)); + ambientColors.Add (description.Name, new PropertyDescription (property, description)); } } } @@ -591,7 +693,7 @@ namespace Mono.TextEditor.Highlighting { if (color == null) return GetChunkStyle ("Plain Text"); - PropertyDecsription val; + PropertyDescription val; if (!textColors.TryGetValue (color, out val)) { Console.WriteLine ("Chunk style : " + color + " is undefined."); return GetChunkStyle ("Plain Text"); @@ -652,7 +754,7 @@ namespace Mono.TextEditor.Highlighting foreach (var colorElement in root.XPathSelectElements("//colors/*")) { var color = AmbientColor.Create (colorElement, palette); - PropertyDecsription info; + PropertyDescription info; if (!ambientColors.TryGetValue (color.Name, out info)) { Console.WriteLine ("Ambient color:" + color.Name + " not found."); continue; @@ -662,7 +764,7 @@ namespace Mono.TextEditor.Highlighting foreach (var textColorElement in root.XPathSelectElements("//text/*")) { var color = ChunkStyle.Create (textColorElement, palette); - PropertyDecsription info; + PropertyDescription info; if (!textColors.TryGetValue (color.Name, out info)) { Console.WriteLine ("Text color:" + color.Name + " not found."); continue; @@ -805,7 +907,7 @@ namespace Mono.TextEditor.Highlighting return HslColor.Parse (color); } - public class VSSettingColor + public sealed class VSSettingColor { public string Name { get; private set; } public string Foreground { get; private set; } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/SyntaxMode.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/SyntaxMode.cs index 0d9ecc76fb..ef17e3d304 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/SyntaxMode.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Highlighting/SyntaxMode.cs @@ -38,7 +38,7 @@ namespace Mono.TextEditor.Highlighting { protected TextDocument doc; - public TextDocument Document { + public virtual TextDocument Document { get { return doc; } @@ -743,6 +743,9 @@ namespace Mono.TextEditor.Highlighting string extends = reader.GetAttribute ("extends"); if (!String.IsNullOrEmpty (extends)) { result = (SyntaxMode)SyntaxModeService.GetSyntaxMode (null, extends).MemberwiseClone (); + spanList.AddRange (result.spans); + prevMarkerList.AddRange (result.prevMarker); + matches.AddRange (result.matches); } result.Name = reader.GetAttribute ("name"); result.MimeType = reader.GetAttribute (MimeTypesAttribute); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Theatrics/BounceFadePopupWindow.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Theatrics/BounceFadePopupWindow.cs index 6fc42b0782..8f74963df2 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Theatrics/BounceFadePopupWindow.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Theatrics/BounceFadePopupWindow.cs @@ -37,12 +37,12 @@ namespace Mono.TextEditor.Theatrics { Stage<BounceFadePopupWindow> stage = new Stage<BounceFadePopupWindow> (); Gdk.Pixbuf textImage = null; - TextEditor editor; + MonoTextEditor editor; protected double scale = 0.0; protected double opacity = 1.0; - public BounceFadePopupWindow (TextEditor editor) : base (Gtk.WindowType.Popup) + public BounceFadePopupWindow (MonoTextEditor editor) : base (Gtk.WindowType.Popup) { if (!IsComposited) throw new InvalidOperationException ("Only works with composited screen. Check Widget.IsComposited."); @@ -69,7 +69,7 @@ namespace Mono.TextEditor.Theatrics stage.UpdateFrequency = 10; } - protected TextEditor Editor { get { return editor; } } + protected MonoTextEditor Editor { get { return editor; } } /// <summary>Duration of the animation, in milliseconds.</summary> public uint Duration { get; set; } @@ -227,12 +227,12 @@ namespace Mono.TextEditor.Theatrics { Stage<BounceFadePopupWidget> stage = new Stage<BounceFadePopupWidget> (); Gdk.Pixbuf textImage = null; - TextEditor editor; + MonoTextEditor editor; protected double scale = 0.0; protected double opacity = 1.0; - public BounceFadePopupWidget (TextEditor editor) + public BounceFadePopupWidget (MonoTextEditor editor) { if (!IsComposited) throw new InvalidOperationException ("Only works with composited screen. Check Widget.IsComposited."); @@ -256,7 +256,7 @@ namespace Mono.TextEditor.Theatrics stage.UpdateFrequency = 10; } - protected TextEditor Editor { get { return editor; } } + protected MonoTextEditor Editor { get { return editor; } } /// <summary>Duration of the animation, in milliseconds.</summary> public uint Duration { get; set; } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/CharRope.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/CharRope.cs new file mode 100644 index 0000000000..df70acc8c4 --- /dev/null +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/CharRope.cs @@ -0,0 +1,226 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Globalization; +using System.IO; +using System.Text; + +namespace Mono.TextEditor.Utils +{ + /// <summary> + /// Poor man's template specialization: extension methods for Rope<char>. + /// </summary> + public static class CharRope + { + /// <summary> + /// Creates a new rope from the specified text. + /// </summary> + public static Rope<char> Create(string text) + { + if (text == null) + throw new ArgumentNullException("text"); + return new Rope<char>(InitFromString(text)); + } + + /// <summary> + /// Retrieves the text for a portion of the rope. + /// Runs in O(lg N + M), where M=<paramref name="length"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public static string ToString(this Rope<char> rope, int startIndex, int length) + { + if (rope == null) + throw new ArgumentNullException("rope"); + #if DEBUG + if (length < 0) + throw new ArgumentOutOfRangeException("length", length, "Value must be >= 0"); + #endif + if (length == 0) + return string.Empty; + char[] buffer = new char[length]; + rope.CopyTo(startIndex, buffer, 0, length); + return new string(buffer); + } + + /// <summary> + /// Retrieves the text for a portion of the rope and writes it to the specified text writer. + /// Runs in O(lg N + M), where M=<paramref name="length"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public static void WriteTo(this Rope<char> rope, TextWriter output, int startIndex, int length) + { + if (rope == null) + throw new ArgumentNullException("rope"); + if (output == null) + throw new ArgumentNullException("output"); + rope.VerifyRange(startIndex, length); + rope.root.WriteTo(startIndex, output, length); + } + + /// <summary> + /// Appends text to this rope. + /// Runs in O(lg N + M). + /// </summary> + /// <exception cref="ArgumentNullException">newElements is null.</exception> + public static void AddText(this Rope<char> rope, string text) + { + InsertText(rope, rope.Length, text); + } + + /// <summary> + /// Inserts text into this rope. + /// Runs in O(lg N + M). + /// </summary> + /// <exception cref="ArgumentNullException">newElements is null.</exception> + /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception> + public static void InsertText(this Rope<char> rope, int index, string text) + { + if (rope == null) + throw new ArgumentNullException("rope"); + rope.InsertRange(index, text.ToCharArray(), 0, text.Length); + /*if (index < 0 || index > rope.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + rope.Length.ToString(CultureInfo.InvariantCulture)); + } + if (text == null) + throw new ArgumentNullException("text"); + if (text.Length == 0) + return; + rope.root = rope.root.Insert(index, text); + rope.OnChanged();*/ + } + + internal static RopeNode<char> InitFromString(string text) + { + if (text.Length == 0) { + return RopeNode<char>.emptyRopeNode; + } + RopeNode<char> node = RopeNode<char>.CreateNodes(text.Length); + FillNode(node, text, 0); + return node; + } + + static void FillNode(RopeNode<char> node, string text, int start) + { + if (node.contents != null) { + text.CopyTo(start, node.contents, 0, node.length); + } else { + FillNode(node.left, text, start); + FillNode(node.right, text, start + node.left.length); + } + } + + internal static void WriteTo(this RopeNode<char> node, int index, TextWriter output, int count) + { + if (node.height == 0) { + if (node.contents == null) { + // function node + node.GetContentNode().WriteTo(index, output, count); + } else { + // leaf node: append data + output.Write(node.contents, index, count); + } + } else { + // concat node: do recursive calls + if (index + count <= node.left.length) { + node.left.WriteTo(index, output, count); + } else if (index >= node.left.length) { + node.right.WriteTo(index - node.left.length, output, count); + } else { + int amountInLeft = node.left.length - index; + node.left.WriteTo(index, output, amountInLeft); + node.right.WriteTo(0, output, count - amountInLeft); + } + } + } + + /// <summary> + /// Gets the index of the first occurrence of any element in the specified array. + /// </summary> + /// <param name="rope">The target rope.</param> + /// <param name="anyOf">Array of characters being searched.</param> + /// <param name="startIndex">Start index of the search.</param> + /// <param name="length">Length of the area to search.</param> + /// <returns>The first index where any character was found; or -1 if no occurrence was found.</returns> + public static int IndexOfAny(this Rope<char> rope, char[] anyOf, int startIndex, int length) + { + if (rope == null) + throw new ArgumentNullException("rope"); + if (anyOf == null) + throw new ArgumentNullException("anyOf"); + rope.VerifyRange(startIndex, length); + + while (length > 0) { + var entry = rope.FindNodeUsingCache(startIndex).PeekOrDefault(); + char[] contents = entry.node.contents; + int startWithinNode = startIndex - entry.nodeStartIndex; + int nodeLength = System.Math.Min(entry.node.length, startWithinNode + length); + for (int i = startIndex - entry.nodeStartIndex; i < nodeLength; i++) { + char element = contents[i]; + foreach (char needle in anyOf) { + if (element == needle) + return entry.nodeStartIndex + i; + } + } + length -= nodeLength - startWithinNode; + startIndex = entry.nodeStartIndex + nodeLength; + } + return -1; + } + + /// <summary> + /// Gets the index of the first occurrence of the search text. + /// </summary> + public static int IndexOf(this Rope<char> rope, string searchText, int startIndex, int length, StringComparison comparisonType) + { + if (rope == null) + throw new ArgumentNullException("rope"); + if (searchText == null) + throw new ArgumentNullException("searchText"); + rope.VerifyRange(startIndex, length); + int pos = rope.ToString(startIndex, length).IndexOf(searchText, comparisonType); + if (pos < 0) + return -1; + else + return pos + startIndex; + } + + /// <summary> + /// Gets the index of the last occurrence of the search text. + /// </summary> + public static int LastIndexOf(this Rope<char> rope, string searchText, int startIndex, int length, StringComparison comparisonType) + { + if (rope == null) + throw new ArgumentNullException("rope"); + if (searchText == null) + throw new ArgumentNullException("searchText"); + rope.VerifyRange(startIndex, length); + int pos = rope.ToString(startIndex, length).LastIndexOf(searchText, comparisonType); + if (pos < 0) + return -1; + else + return pos + startIndex; + } + } +} diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/ImmutableStack.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/ImmutableStack.cs new file mode 100644 index 0000000000..4611571c0d --- /dev/null +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/ImmutableStack.cs @@ -0,0 +1,132 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Mono.TextEditor.Utils +{ + /// <summary> + /// An immutable stack. + /// + /// Using 'foreach' on the stack will return the items from top to bottom (in the order they would be popped). + /// </summary> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] + [Serializable] + public sealed class ImmutableStack<T> : IEnumerable<T> + { + /// <summary> + /// Gets the empty stack instance. + /// </summary> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "ImmutableStack is immutable")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static readonly ImmutableStack<T> Empty = new ImmutableStack<T>(); + + readonly T value; + readonly ImmutableStack<T> next; + + private ImmutableStack() + { + } + + private ImmutableStack(T value, ImmutableStack<T> next) + { + this.value = value; + this.next = next; + } + + /// <summary> + /// Pushes an item on the stack. This does not modify the stack itself, but returns a new + /// one with the value pushed. + /// </summary> + public ImmutableStack<T> Push(T item) + { + return new ImmutableStack<T>(item, this); + } + + /// <summary> + /// Gets the item on the top of the stack. + /// </summary> + /// <exception cref="InvalidOperationException">The stack is empty.</exception> + public T Peek() + { + if (IsEmpty) + throw new InvalidOperationException("Operation not valid on empty stack."); + return value; + } + + /// <summary> + /// Gets the item on the top of the stack. + /// Returns <c>default(T)</c> if the stack is empty. + /// </summary> + public T PeekOrDefault() + { + return value; + } + + /// <summary> + /// Gets the stack with the top item removed. + /// </summary> + /// <exception cref="InvalidOperationException">The stack is empty.</exception> + public ImmutableStack<T> Pop() + { + if (IsEmpty) + throw new InvalidOperationException("Operation not valid on empty stack."); + return next; + } + + /// <summary> + /// Gets if this stack is empty. + /// </summary> + public bool IsEmpty { + get { return next == null; } + } + + /// <summary> + /// Gets an enumerator that iterates through the stack top-to-bottom. + /// </summary> + public IEnumerator<T> GetEnumerator() + { + ImmutableStack<T> t = this; + while (!t.IsEmpty) { + yield return t.value; + t = t.next; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// <inheritdoc/> + public override string ToString() + { + StringBuilder b = new StringBuilder("[Stack"); + foreach (T val in this) { + b.Append(' '); + b.Append(val); + } + b.Append(']'); + return b.ToString(); + } + } +}
\ No newline at end of file diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/Rope.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/Rope.cs new file mode 100644 index 0000000000..0ce950d5bc --- /dev/null +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/Rope.cs @@ -0,0 +1,854 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.Serialization; +using System.Text; + +namespace Mono.TextEditor.Utils +{ + /// <summary> + /// A kind of List<T>, but more efficient for random insertions/removal. + /// Also has cheap Clone() and SubRope() implementations. + /// </summary> + /// <remarks> + /// This class is not thread-safe: multiple concurrent write operations or writes concurrent to reads have undefined behaviour. + /// Concurrent reads, however, are safe. + /// However, clones of a rope are safe to use on other threads even though they share data with the original rope. + /// </remarks> + [Serializable] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] + public sealed class Rope<T> : IList<T>, ICloneable + { + internal RopeNode<T> root; + + internal Rope(RopeNode<T> root) + { + this.root = root; + root.CheckInvariants(); + } + + /// <summary> + /// Creates a new rope representing the empty string. + /// </summary> + public Rope() + { + // we'll construct the empty rope as a clone of an imaginary static empty rope + this.root = RopeNode<T>.emptyRopeNode; + root.CheckInvariants(); + } + + /// <summary> + /// Creates a rope from the specified input. + /// This operation runs in O(N). + /// </summary> + /// <exception cref="ArgumentNullException">input is null.</exception> + public Rope(IEnumerable<T> input) + { + if (input == null) + throw new ArgumentNullException("input"); + Rope<T> inputRope = input as Rope<T>; + if (inputRope != null) { + // clone ropes instead of copying them + inputRope.root.Publish(); + this.root = inputRope.root; + } else { + string text = input as string; + if (text != null) { + // if a string is IEnumerable<T>, then T must be char + ((Rope<char>)(object)this).root = CharRope.InitFromString(text); + } else { + T[] arr = ToArray(input); + this.root = RopeNode<T>.CreateFromArray(arr, 0, arr.Length); + } + } + this.root.CheckInvariants(); + } + + /// <summary> + /// Creates a rope from a part of the array. + /// This operation runs in O(N). + /// </summary> + /// <exception cref="ArgumentNullException">input is null.</exception> + public Rope(T[] array, int arrayIndex, int count) + { + VerifyArrayWithRange(array, arrayIndex, count); + this.root = RopeNode<T>.CreateFromArray(array, arrayIndex, count); + this.root.CheckInvariants(); + } + + /// <summary> + /// Creates a new rope that lazily initalizes its content. + /// </summary> + /// <param name="length">The length of the rope that will be lazily loaded.</param> + /// <param name="initializer"> + /// The callback that provides the content for this rope. + /// <paramref name="initializer"/> will be called exactly once when the content of this rope is first requested. + /// It must return a rope with the specified length. + /// Because the initializer function is not called when a rope is cloned, and such clones may be used on another threads, + /// it is possible for the initializer callback to occur on any thread. + /// </param> + /// <remarks> + /// Any modifications inside the rope will also cause the content to be initialized. + /// However, insertions at the beginning and the end, as well as inserting this rope into another or + /// using the <see cref="Concat(Rope{T},Rope{T})"/> method, allows constructions of larger ropes where parts are + /// lazily loaded. + /// However, even methods like Concat may sometimes cause the initializer function to be called, e.g. when + /// two short ropes are concatenated. + /// </remarks> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Rope(int length, Func<Rope<T>> initializer) + { + if (initializer == null) + throw new ArgumentNullException("initializer"); + if (length < 0) + throw new ArgumentOutOfRangeException("length", length, "Length must not be negative"); + if (length == 0) { + this.root = RopeNode<T>.emptyRopeNode; + } else { + this.root = new FunctionNode<T>(length, initializer); + } + this.root.CheckInvariants(); + } + + static T[] ToArray(IEnumerable<T> input) + { + T[] arr = input as T[]; + return arr ?? input.ToArray(); + } + + /// <summary> + /// Clones the rope. + /// This operation runs in linear time to the number of rope nodes touched since the last clone was created. + /// If you count the per-node cost to the operation modifying the rope (doing this doesn't increase the complexity of the modification operations); + /// the remainder of Clone() runs in O(1). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public Rope<T> Clone() + { + // The Publish() call actually modifies this rope instance; but this modification is thread-safe + // as long as the tree structure doesn't change during the operation. + root.Publish(); + return new Rope<T>(root); + } + + object ICloneable.Clone() + { + return this.Clone(); + } + + /// <summary> + /// Resets the rope to an empty list. + /// Runs in O(1). + /// </summary> + public void Clear() + { + root = RopeNode<T>.emptyRopeNode; + OnChanged(); + } + + /// <summary> + /// Gets the length of the rope. + /// Runs in O(1). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public int Length { + get { return root.length; } + } + + /// <summary> + /// Gets the length of the rope. + /// Runs in O(1). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public int Count { + get { return root.length; } + } + + /// <summary> + /// Inserts another rope into this rope. + /// Runs in O(lg N + lg M), plus a per-node cost as if <c>newElements.Clone()</c> was called. + /// </summary> + /// <exception cref="ArgumentNullException">newElements is null.</exception> + /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception> + public void InsertRange(int index, Rope<T> newElements) + { + if (index < 0 || index > this.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + if (newElements == null) + throw new ArgumentNullException("newElements"); + newElements.root.Publish(); + root = root.Insert(index, newElements.root); + OnChanged(); + } + + /// <summary> + /// Inserts new elemetns into this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// </summary> + /// <exception cref="ArgumentNullException">newElements is null.</exception> + /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception> + public void InsertRange(int index, IEnumerable<T> newElements) + { + if (newElements == null) + throw new ArgumentNullException("newElements"); + Rope<T> newElementsRope = newElements as Rope<T>; + if (newElementsRope != null) { + InsertRange(index, newElementsRope); + } else { + T[] arr = ToArray(newElements); + InsertRange(index, arr, 0, arr.Length); + } + } + + /// <summary> + /// Inserts new elements into this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// </summary> + /// <exception cref="ArgumentNullException">newElements is null.</exception> + /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception> + public void InsertRange(int index, T[] array, int arrayIndex, int count) + { + if (index < 0 || index > this.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + VerifyArrayWithRange(array, arrayIndex, count); + if (count > 0) { + root = root.Insert(index, array, arrayIndex, count); + OnChanged(); + } + } + + /// <summary> + /// Appends multiple elements to the end of this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// </summary> + /// <exception cref="ArgumentNullException">newElements is null.</exception> + public void AddRange(IEnumerable<T> newElements) + { + InsertRange(this.Length, newElements); + } + + /// <summary> + /// Appends another rope to the end of this rope. + /// Runs in O(lg N + lg M), plus a per-node cost as if <c>newElements.Clone()</c> was called. + /// </summary> + /// <exception cref="ArgumentNullException">newElements is null.</exception> + public void AddRange(Rope<T> newElements) + { + InsertRange(this.Length, newElements); + } + + /// <summary> + /// Appends new elements to the end of this rope. + /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. + /// </summary> + /// <exception cref="ArgumentNullException">array is null.</exception> + public void AddRange(T[] array, int arrayIndex, int count) + { + InsertRange(this.Length, array, arrayIndex, count); + } + + /// <summary> + /// Removes a range of elements from the rope. + /// Runs in O(lg N). + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> + public void RemoveRange(int index, int count) + { + VerifyRange(index, count); + if (count > 0) { + root = root.RemoveRange(index, count); + OnChanged(); + } + } + + /// <summary> + /// Copies a range of the specified array into the rope, overwriting existing elements. + /// Runs in O(lg N + M). + /// </summary> + public void SetRange(int index, T[] array, int arrayIndex, int count) + { + VerifyRange(index, count); + VerifyArrayWithRange(array, arrayIndex, count); + if (count > 0) { + root = root.StoreElements(index, array, arrayIndex, count); + OnChanged(); + } + } + + /// <summary> + /// Creates a new rope and initializes it with a part of this rope. + /// Runs in O(lg N) plus a per-node cost as if <c>this.Clone()</c> was called. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public Rope<T> GetRange(int index, int count) + { + VerifyRange(index, count); + Rope<T> newRope = Clone(); + int endIndex = index + count; + newRope.RemoveRange(endIndex, newRope.Length - endIndex); + newRope.RemoveRange(0, index); + return newRope; + } + + /* + #region Equality + /// <summary> + /// Gets whether the two ropes have the same content. + /// Runs in O(N + M). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public bool Equals(Rope other) + { + if (other == null) + return false; + // quick detection for ropes that are clones of each other: + if (other.root == this.root) + return true; + if (other.Length != this.Length) + return false; + using (RopeTextReader a = new RopeTextReader(this, false)) { + using (RopeTextReader b = new RopeTextReader(other, false)) { + int charA, charB; + do { + charA = a.Read(); + charB = b.Read(); + if (charA != charB) + return false; + } while (charA != -1); + return true; + } + } + } + + /// <summary> + /// Gets whether two ropes have the same content. + /// Runs in O(N + M). + /// </summary> + public override bool Equals(object obj) + { + return Equals(obj as Rope); + } + + /// <summary> + /// Calculates the hash code of the rope's content. + /// Runs in O(N). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public override int GetHashCode() + { + int hashcode = 0; + using (RopeTextReader reader = new RopeTextReader(this, false)) { + unchecked { + int val; + while ((val = reader.Read()) != -1) { + hashcode = hashcode * 31 + val; + } + } + } + return hashcode; + } + #endregion + */ + + /// <summary> + /// Concatenates two ropes. The input ropes are not modified. + /// Runs in O(lg N + lg M). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static Rope<T> Concat(Rope<T> left, Rope<T> right) + { + if (left == null) + throw new ArgumentNullException("left"); + if (right == null) + throw new ArgumentNullException("right"); + left.root.Publish(); + right.root.Publish(); + return new Rope<T>(RopeNode<T>.Concat(left.root, right.root)); + } + + /// <summary> + /// Concatenates multiple ropes. The input ropes are not modified. + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] + public static Rope<T> Concat(params Rope<T>[] ropes) + { + if (ropes == null) + throw new ArgumentNullException("ropes"); + Rope<T> result = new Rope<T>(); + foreach (Rope<T> r in ropes) + result.AddRange(r); + return result; + } + + #region Caches / Changed event + internal struct RopeCacheEntry + { + internal readonly RopeNode<T> node; + internal readonly int nodeStartIndex; + + internal RopeCacheEntry(RopeNode<T> node, int nodeStartOffset) + { + this.node = node; + this.nodeStartIndex = nodeStartOffset; + } + + internal bool IsInside(int offset) + { + return node != null && offset >= nodeStartIndex && offset < nodeStartIndex + node.length; + } + } + + // cached pointer to 'last used node', used to speed up accesses by index that are close together + [NonSerialized] + volatile ImmutableStack<RopeCacheEntry> lastUsedNodeStack; + + internal void OnChanged() + { + lastUsedNodeStack = null; + + root.CheckInvariants(); + } + #endregion + + #region GetChar / SetChar + /// <summary> + /// Gets/Sets a single character. + /// Runs in O(lg N) for random access. Sequential read-only access benefits from a special optimization and runs in amortized O(1). + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to Length-1).</exception> + /// <remarks> + /// The getter counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public T this[int index] { + get { + // use unsigned integers - this way negative values for index overflow and can be tested for with the same check + if (unchecked((uint)index >= (uint)this.Length)) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + RopeCacheEntry entry = FindNodeUsingCache(index).PeekOrDefault(); + return entry.node.contents[index - entry.nodeStartIndex]; + } + set { + if (index < 0 || index >= this.Length) { + throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + root = root.SetElement(index, value); + OnChanged(); + /* Here's a try at implementing the setter using the cached node stack (UNTESTED code!). + * However I don't use the code because it's complicated and doesn't integrate correctly with change notifications. + * Instead, I'll use the much easier to understand recursive solution. + * Oh, and it also doesn't work correctly with function nodes. + ImmutableStack<RopeCacheEntry> nodeStack = FindNodeUsingCache(offset); + RopeCacheEntry entry = nodeStack.Peek(); + if (!entry.node.isShared) { + entry.node.contents[offset - entry.nodeStartOffset] = value; + // missing: clear the caches except for the node stack cache (e.g. ToString() cache?) + } else { + RopeNode oldNode = entry.node; + RopeNode newNode = oldNode.Clone(); + newNode.contents[offset - entry.nodeStartOffset] = value; + for (nodeStack = nodeStack.Pop(); !nodeStack.IsEmpty; nodeStack = nodeStack.Pop()) { + RopeNode parentNode = nodeStack.Peek().node; + RopeNode newParentNode = parentNode.CloneIfShared(); + if (newParentNode.left == oldNode) { + newParentNode.left = newNode; + } else { + Debug.Assert(newParentNode.right == oldNode); + newParentNode.right = newNode; + } + if (parentNode == newParentNode) { + // we were able to change the existing node (it was not shared); + // there's no reason to go further upwards + ClearCacheOnModification(); + return; + } else { + oldNode = parentNode; + newNode = newParentNode; + } + } + // we reached the root of the rope. + Debug.Assert(root == oldNode); + root = newNode; + ClearCacheOnModification(); + }*/ + } + } + + internal ImmutableStack<RopeCacheEntry> FindNodeUsingCache(int index) + { + Debug.Assert(index >= 0 && index < this.Length); + + // thread safety: fetch stack into local variable + ImmutableStack<RopeCacheEntry> stack = lastUsedNodeStack; + ImmutableStack<RopeCacheEntry> oldStack = stack; + + if (stack == null) { + stack = ImmutableStack<RopeCacheEntry>.Empty.Push(new RopeCacheEntry(root, 0)); + } + while (!stack.PeekOrDefault().IsInside(index)) + stack = stack.Pop(); + while (true) { + RopeCacheEntry entry = stack.PeekOrDefault(); + // check if we've reached a leaf or function node + if (entry.node.height == 0) { + if (entry.node.contents == null) { + // this is a function node - go down into its subtree + entry = new RopeCacheEntry(entry.node.GetContentNode(), entry.nodeStartIndex); + // entry is now guaranteed NOT to be another function node + } + if (entry.node.contents != null) { + // this is a node containing actual content, so we're done + break; + } + } + // go down towards leaves + if (index - entry.nodeStartIndex >= entry.node.left.length) + stack = stack.Push(new RopeCacheEntry(entry.node.right, entry.nodeStartIndex + entry.node.left.length)); + else + stack = stack.Push(new RopeCacheEntry(entry.node.left, entry.nodeStartIndex)); + } + + // write back stack to volatile cache variable + // (in multithreaded access, it doesn't matter which of the threads wins - it's just a cache) + if (oldStack != stack) { + // no need to write when we the cache variable didn't change + lastUsedNodeStack = stack; + } + + // this method guarantees that it finds a leaf node + Debug.Assert(stack.Peek().node.contents != null); + return stack; + } + #endregion + + #region ToString / WriteTo + internal void VerifyRange(int startIndex, int length) + { + if (startIndex < 0 || startIndex > this.Length) { + throw new ArgumentOutOfRangeException("startIndex", startIndex, "0 <= startIndex <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + if (length < 0 || startIndex + length > this.Length) { + throw new ArgumentOutOfRangeException("length", length, "0 <= length, startIndex(" + startIndex + ")+length <= " + this.Length.ToString(CultureInfo.InvariantCulture)); + } + } + + internal static void VerifyArrayWithRange(T[] array, int arrayIndex, int count) + { + if (array == null) + throw new ArgumentNullException("array"); + if (arrayIndex < 0 || arrayIndex > array.Length) { + throw new ArgumentOutOfRangeException("startIndex", arrayIndex, "0 <= arrayIndex <= " + array.Length.ToString(CultureInfo.InvariantCulture)); + } + if (count < 0 || arrayIndex + count > array.Length) { + throw new ArgumentOutOfRangeException("count", count, "0 <= length, arrayIndex(" + arrayIndex + ")+count <= " + array.Length.ToString(CultureInfo.InvariantCulture)); + } + } + + /// <summary> + /// Creates a string from the rope. Runs in O(N). + /// </summary> + /// <returns>A string consisting of all elements in the rope as comma-separated list in {}. + /// As a special case, Rope<char> will return its contents as string without any additional separators or braces, + /// so it can be used like StringBuilder.ToString().</returns> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public override string ToString() + { + Rope<char> charRope = this as Rope<char>; + if (charRope != null) { + return charRope.ToString(0, this.Length); + } else { + StringBuilder b = new StringBuilder(); + foreach (T element in this) { + if (b.Length == 0) + b.Append('{'); + else + b.Append(", "); + b.Append(element.ToString()); + } + b.Append('}'); + return b.ToString(); + } + } + + internal string GetTreeAsString() + { + #if DEBUG + return root.GetTreeAsString(); + #else + return "Not available in release build."; + #endif + } + #endregion + + bool ICollection<T>.IsReadOnly { + get { return false; } + } + + /// <summary> + /// Finds the first occurance of item. + /// Runs in O(N). + /// </summary> + /// <returns>The index of the first occurance of item, or -1 if it cannot be found.</returns> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public int IndexOf(T item) + { + return IndexOf(item, 0, this.Length); + } + + /// <summary> + /// Gets the index of the first occurrence the specified item. + /// </summary> + /// <param name="item">Item to search for.</param> + /// <param name="startIndex">Start index of the search.</param> + /// <param name="count">Length of the area to search.</param> + /// <returns>The first index where the item was found; or -1 if no occurrence was found.</returns> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public int IndexOf(T item, int startIndex, int count) + { + VerifyRange(startIndex, count); + + while (count > 0) { + var entry = FindNodeUsingCache(startIndex).PeekOrDefault(); + T[] contents = entry.node.contents; + int startWithinNode = startIndex - entry.nodeStartIndex; + int nodeLength = System.Math.Min(entry.node.length, startWithinNode + count); + int r = Array.IndexOf(contents, item, startWithinNode, nodeLength - startWithinNode); + if (r >= 0) + return entry.nodeStartIndex + r; + count -= nodeLength - startWithinNode; + startIndex = entry.nodeStartIndex + nodeLength; + } + return -1; + } + + /// <summary> + /// Gets the index of the last occurrence of the specified item in this rope. + /// </summary> + public int LastIndexOf(T item) + { + return LastIndexOf(item, 0, this.Length); + } + + /// <summary> + /// Gets the index of the last occurrence of the specified item in this rope. + /// </summary> + /// <param name="item">The search item</param> + /// <param name="startIndex">Start index of the area to search.</param> + /// <param name="count">Length of the area to search.</param> + /// <returns>The last index where the item was found; or -1 if no occurrence was found.</returns> + /// <remarks>The search proceeds backwards from (startIndex+count) to startIndex. + /// This is different than the meaning of the parameters on Array.LastIndexOf!</remarks> + public int LastIndexOf(T item, int startIndex, int count) + { + VerifyRange(startIndex, count); + + var comparer = EqualityComparer<T>.Default; + for (int i = startIndex + count - 1; i >= startIndex; i--) { + if (comparer.Equals(this[i], item)) + return i; + } + return -1; + } + + /// <summary> + /// Inserts the item at the specified index in the rope. + /// Runs in O(lg N). + /// </summary> + public void Insert(int index, T item) + { + InsertRange(index, new[] { item }, 0, 1); + } + + /// <summary> + /// Removes a single item from the rope. + /// Runs in O(lg N). + /// </summary> + public void RemoveAt(int index) + { + RemoveRange(index, 1); + } + + /// <summary> + /// Appends the item at the end of the rope. + /// Runs in O(lg N). + /// </summary> + public void Add(T item) + { + InsertRange(this.Length, new[] { item }, 0, 1); + } + + /// <summary> + /// Searches the item in the rope. + /// Runs in O(N). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public bool Contains(T item) + { + return IndexOf(item) >= 0; + } + + /// <summary> + /// Copies the whole content of the rope into the specified array. + /// Runs in O(N). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public void CopyTo(T[] array, int arrayIndex) + { + CopyTo(0, array, arrayIndex, this.Length); + } + + /// <summary> + /// Copies the a part of the rope into the specified array. + /// Runs in O(lg N + M). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public void CopyTo(int index, T[] array, int arrayIndex, int count) + { + VerifyRange(index, count); + VerifyArrayWithRange(array, arrayIndex, count); + this.root.CopyTo(index, array, arrayIndex, count); + } + + /// <summary> + /// Removes the first occurance of an item from the rope. + /// Runs in O(N). + /// </summary> + public bool Remove(T item) + { + int index = IndexOf(item); + if (index >= 0) { + RemoveAt(index); + return true; + } + return false; + } + + /// <summary> + /// Retrieves an enumerator to iterate through the rope. + /// The enumerator will reflect the state of the rope from the GetEnumerator() call, further modifications + /// to the rope will not be visible to the enumerator. + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public IEnumerator<T> GetEnumerator() + { + this.root.Publish(); + return Enumerate(root); + } + + /// <summary> + /// Creates an array and copies the contents of the rope into it. + /// Runs in O(N). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public T[] ToArray() + { + T[] arr = new T[this.Length]; + this.root.CopyTo(0, arr, 0, arr.Length); + return arr; + } + + /// <summary> + /// Creates an array and copies the contents of the rope into it. + /// Runs in O(N). + /// </summary> + /// <remarks> + /// This method counts as a read access and may be called concurrently to other read accesses. + /// </remarks> + public T[] ToArray(int startIndex, int count) + { + VerifyRange(startIndex, count); + T[] arr = new T[count]; + CopyTo(startIndex, arr, 0, count); + return arr; + } + + static IEnumerator<T> Enumerate(RopeNode<T> node) + { + Stack<RopeNode<T>> stack = new Stack<RopeNode<T>>(); + while (node != null) { + // go to leftmost node, pushing the right parts that we'll have to visit later + while (node.contents == null) { + if (node.height == 0) { + // go down into function nodes + node = node.GetContentNode(); + continue; + } + Debug.Assert(node.right != null); + stack.Push(node.right); + node = node.left; + } + // yield contents of leaf node + for (int i = 0; i < node.length; i++) { + yield return node.contents[i]; + } + // go up to the next node not visited yet + if (stack.Count > 0) + node = stack.Pop(); + else + node = null; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/RopeNode.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/RopeNode.cs new file mode 100644 index 0000000000..bc6b5bade7 --- /dev/null +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/RopeNode.cs @@ -0,0 +1,621 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +using System.Text; + +namespace Mono.TextEditor.Utils +{ + // Class used to represent a node in the tree. + // There are three types of nodes: + // Concat nodes: height>0, left!=null, right!=null, contents==null + // Leaf nodes: height==0, left==null, right==null, contents!=null + // Function nodes: height==0, left==null, right==null, contents==null, are of type FunctionNode<T> + + [Serializable] + class RopeNode<T> + { + internal const int NodeSize = 256; + + internal static readonly RopeNode<T> emptyRopeNode = new RopeNode<T> { isShared = true, contents = new T[RopeNode<T>.NodeSize] }; + + // Fields for pointers to sub-nodes. Only non-null for concat nodes (height>=1) + internal RopeNode<T> left, right; + internal volatile bool isShared; // specifies whether this node is shared between multiple ropes + // the total length of all text in this subtree + internal int length; + // the height of this subtree: 0 for leaf nodes; 1+max(left.height,right.height) for concat nodes + internal byte height; + + // The character data. Only non-null for leaf nodes (height=0) that aren't function nodes. + internal T[] contents; + + internal int Balance { + get { return right.height - left.height; } + } + + [Conditional("DATACONSISTENCYTEST")] + internal void CheckInvariants() + { + if (height == 0) { + Debug.Assert(left == null && right == null); + if (contents == null) { + Debug.Assert(this is FunctionNode<T>); + Debug.Assert(length > 0); + Debug.Assert(isShared); + } else { + Debug.Assert(contents != null && contents.Length == NodeSize); + Debug.Assert(length >= 0 && length <= NodeSize); + } + } else { + Debug.Assert(left != null && right != null); + Debug.Assert(contents == null); + Debug.Assert(length == left.length + right.length); + Debug.Assert(height == 1 + System.Math.Max(left.height, right.height)); + Debug.Assert(System.Math.Abs(this.Balance) <= 1); + + // this is an additional invariant that forces the tree to combine small leafs to prevent excessive memory usage: + Debug.Assert(length > NodeSize); + // note that this invariant ensures that all nodes except for the empty rope's single node have at least length 1 + + if (isShared) + Debug.Assert(left.isShared && right.isShared); + left.CheckInvariants(); + right.CheckInvariants(); + } + } + + internal RopeNode<T> Clone() + { + if (height == 0) { + // If a function node needs cloning, we'll evaluate it. + if (contents == null) + return GetContentNode().Clone(); + T[] newContents = new T[NodeSize]; + contents.CopyTo(newContents, 0); + return new RopeNode<T> { + length = this.length, + contents = newContents + }; + } else { + return new RopeNode<T> { + left = this.left, + right = this.right, + length = this.length, + height = this.height + }; + } + } + + internal RopeNode<T> CloneIfShared() + { + if (isShared) + return Clone(); + else + return this; + } + + internal void Publish() + { + if (!isShared) { + if (left != null) + left.Publish(); + if (right != null) + right.Publish(); + // it's important that isShared=true is set at the end: + // Publish() must not return until the whole subtree is marked as shared, even when + // Publish() is called concurrently. + isShared = true; + } + } + + internal static RopeNode<T> CreateFromArray(T[] arr, int index, int length) + { + if (length == 0) { + return emptyRopeNode; + } + RopeNode<T> node = CreateNodes(length); + return node.StoreElements(0, arr, index, length); + } + + internal static RopeNode<T> CreateNodes(int totalLength) + { + int leafCount = (totalLength + NodeSize - 1) / NodeSize; + return CreateNodes(leafCount, totalLength); + } + + static RopeNode<T> CreateNodes(int leafCount, int totalLength) + { + Debug.Assert(leafCount > 0); + Debug.Assert(totalLength > 0); + RopeNode<T> result = new RopeNode<T>(); + result.length = totalLength; + if (leafCount == 1) { + result.contents = new T[NodeSize]; + } else { + int rightSide = leafCount / 2; + int leftSide = leafCount - rightSide; + int leftLength = leftSide * NodeSize; + result.left = CreateNodes(leftSide, leftLength); + result.right = CreateNodes(rightSide, totalLength - leftLength); + result.height = (byte)(1 + System.Math.Max(result.left.height, result.right.height)); + } + return result; + } + + /// <summary> + /// Balances this node and recomputes the 'height' field. + /// This method assumes that the children of this node are already balanced and have an up-to-date 'height' value. + /// </summary> + internal void Rebalance() + { + // Rebalance() shouldn't be called on shared nodes - it's only called after modifications! + Debug.Assert(!isShared); + // leaf nodes are always balanced (we don't use 'height' to detect leaf nodes here + // because Balance is supposed to recompute the height). + if (left == null) + return; + + // ensure we didn't miss a MergeIfPossible step + Debug.Assert(this.length > NodeSize); + + // We need to loop until it's balanced. Rotations might cause two small leaves to combine to a larger one, + // which changes the height and might mean we need additional balancing steps. + while (System.Math.Abs(this.Balance) > 1) { + // AVL balancing + // note: because we don't care about the identity of concat nodes, this works a little different than usual + // tree rotations: in our implementation, the "this" node will stay at the top, only its children are rearranged + if (this.Balance > 1) { + if (right.Balance < 0) { + right = right.CloneIfShared(); + right.RotateRight(); + } + this.RotateLeft(); + // If 'this' was unbalanced by more than 2, we've shifted some of the inbalance to the left node; so rebalance that. + this.left.Rebalance(); + } else if (this.Balance < -1) { + if (left.Balance > 0) { + left = left.CloneIfShared(); + left.RotateLeft(); + } + this.RotateRight(); + // If 'this' was unbalanced by more than 2, we've shifted some of the inbalance to the right node; so rebalance that. + this.right.Rebalance(); + } + } + + Debug.Assert(System.Math.Abs(this.Balance) <= 1); + this.height = (byte)(1 + System.Math.Max(left.height, right.height)); + } + + void RotateLeft() + { + Debug.Assert(!isShared); + + /* Rotate tree to the left + * + * this this + * / \ / \ + * A right ===> left C + * / \ / \ + * B C A B + */ + RopeNode<T> a = left; + RopeNode<T> b = right.left; + RopeNode<T> c = right.right; + // reuse right concat node, if possible + this.left = right.isShared ? new RopeNode<T>() : right; + this.left.left = a; + this.left.right = b; + this.left.length = a.length + b.length; + this.left.height = (byte)(1 + System.Math.Max(a.height, b.height)); + this.right = c; + + this.left.MergeIfPossible(); + } + + void RotateRight() + { + Debug.Assert(!isShared); + + /* Rotate tree to the right + * + * this this + * / \ / \ + * left C ===> A right + * / \ / \ + * A B B C + */ + RopeNode<T> a = left.left; + RopeNode<T> b = left.right; + RopeNode<T> c = right; + // reuse left concat node, if possible + this.right = left.isShared ? new RopeNode<T>() : left; + this.right.left = b; + this.right.right = c; + this.right.length = b.length + c.length; + this.right.height = (byte)(1 + System.Math.Max(b.height, c.height)); + this.left = a; + + this.right.MergeIfPossible(); + } + static readonly T[] emptyArr = new T[0]; + + void MergeIfPossible() + { + Debug.Assert(!isShared); + + if (this.length <= NodeSize) { + // Convert this concat node to leaf node. + // We know left and right cannot be concat nodes (they would have merged already), + // but they could be function nodes. + this.height = 0; + int lengthOnLeftSide = this.left.length; + if (this.left.isShared) { + this.contents = new T[NodeSize]; + left.CopyTo(0, this.contents, 0, lengthOnLeftSide); + } else { + // must be a leaf node: function nodes are always marked shared + Debug.Assert(this.left.contents != null); + // steal buffer from left side + this.contents = this.left.contents; + #if DEBUG + // In debug builds, explicitly mark left node as 'damaged' - but no one else should be using it + // because it's not shared. + this.left.contents = emptyArr; + #endif + } + this.left = null; + right.CopyTo(0, this.contents, lengthOnLeftSide, this.right.length); + this.right = null; + } + } + + /// <summary> + /// Copies from the array to this node. + /// </summary> + internal RopeNode<T> StoreElements(int index, T[] array, int arrayIndex, int count) + { + RopeNode<T> result = this.CloneIfShared(); + // result cannot be function node after a call to Clone() + if (result.height == 0) { + // leaf node: + Array.Copy(array, arrayIndex, result.contents, index, count); + } else { + // concat node: + if (index + count <= result.left.length) { + result.left = result.left.StoreElements(index, array, arrayIndex, count); + } else if (index >= this.left.length) { + result.right = result.right.StoreElements(index - result.left.length, array, arrayIndex, count); + } else { + int amountInLeft = result.left.length - index; + result.left = result.left.StoreElements(index, array, arrayIndex, amountInLeft); + result.right = result.right.StoreElements(0, array, arrayIndex + amountInLeft, count - amountInLeft); + } + result.Rebalance(); // tree layout might have changed if function nodes were replaced with their content + } + return result; + } + + /// <summary> + /// Copies from this node to the array. + /// </summary> + internal void CopyTo(int index, T[] array, int arrayIndex, int count) + { + if (height == 0) { + if (this.contents == null) { + // function node + this.GetContentNode().CopyTo(index, array, arrayIndex, count); + } else { + // leaf node + Array.Copy(this.contents, index, array, arrayIndex, count); + } + } else { + // concat node + if (index + count <= this.left.length) { + this.left.CopyTo(index, array, arrayIndex, count); + } else if (index >= this.left.length) { + this.right.CopyTo(index - this.left.length, array, arrayIndex, count); + } else { + int amountInLeft = this.left.length - index; + this.left.CopyTo(index, array, arrayIndex, amountInLeft); + this.right.CopyTo(0, array, arrayIndex + amountInLeft, count - amountInLeft); + } + } + } + + internal RopeNode<T> SetElement(int offset, T value) + { + RopeNode<T> result = CloneIfShared(); + // result of CloneIfShared() is leaf or concat node + if (result.height == 0) { + result.contents[offset] = value; + } else { + if (offset < result.left.length) { + result.left = result.left.SetElement(offset, value); + } else { + result.right = result.right.SetElement(offset - result.left.length, value); + } + result.Rebalance(); // tree layout might have changed if function nodes were replaced with their content + } + return result; + } + + internal static RopeNode<T> Concat(RopeNode<T> left, RopeNode<T> right) + { + if (left.length == 0) + return right; + if (right.length == 0) + return left; + + if (left.length + right.length <= NodeSize) { + left = left.CloneIfShared(); + // left is guaranteed to be leaf node after cloning: + // - it cannot be function node (due to clone) + // - it cannot be concat node (too short) + right.CopyTo(0, left.contents, left.length, right.length); + left.length += right.length; + return left; + } else { + RopeNode<T> concatNode = new RopeNode<T>(); + concatNode.left = left; + concatNode.right = right; + concatNode.length = left.length + right.length; + concatNode.Rebalance(); + return concatNode; + } + } + + /// <summary> + /// Splits this leaf node at offset and returns a new node with the part of the text after offset. + /// </summary> + RopeNode<T> SplitAfter(int offset) + { + Debug.Assert(!isShared && height == 0 && contents != null); + RopeNode<T> newPart = new RopeNode<T>(); + newPart.contents = new T[NodeSize]; + newPart.length = this.length - offset; + Array.Copy(this.contents, offset, newPart.contents, 0, newPart.length); + this.length = offset; + return newPart; + } + + internal RopeNode<T> Insert(int offset, RopeNode<T> newElements) + { + if (offset == 0) { + return Concat(newElements, this); + } else if (offset == this.length) { + return Concat(this, newElements); + } + + // first clone this node (converts function nodes to leaf or concat nodes) + RopeNode<T> result = CloneIfShared(); + if (result.height == 0) { + // leaf node: we'll need to split this node + RopeNode<T> left = result; + RopeNode<T> right = left.SplitAfter(offset); + return Concat(Concat(left, newElements), right); + } else { + // concat node + if (offset < result.left.length) { + result.left = result.left.Insert(offset, newElements); + } else { + result.right = result.right.Insert(offset - result.left.length, newElements); + } + result.length += newElements.length; + result.Rebalance(); + return result; + } + } + + internal RopeNode<T> Insert(int offset, T[] array, int arrayIndex, int count) + { + Debug.Assert(count > 0); + + if (this.length + count < RopeNode<char>.NodeSize) { + RopeNode<T> result = CloneIfShared(); + // result must be leaf node (Clone never returns function nodes, too short for concat node) + int lengthAfterOffset = result.length - offset; + T[] resultContents = result.contents; + for (int i = lengthAfterOffset; i >= 0; i--) { + resultContents[i + offset + count] = resultContents[i + offset]; + } + Array.Copy(array, arrayIndex, resultContents, offset, count); + result.length += count; + return result; + } else if (height == 0) { + // TODO: implement this more efficiently? + return Insert(offset, CreateFromArray(array, arrayIndex, count)); + } else { + // this is a concat node (both leafs and function nodes are handled by the case above) + RopeNode<T> result = CloneIfShared(); + if (offset < result.left.length) { + result.left = result.left.Insert(offset, array, arrayIndex, count); + } else { + result.right = result.right.Insert(offset - result.left.length, array, arrayIndex, count); + } + result.length += count; + result.Rebalance(); + return result; + } + } + + internal RopeNode<T> RemoveRange(int index, int count) + { + Debug.Assert(count > 0); + + // produce empty node when one node is deleted completely + if (index == 0 && count == this.length) + return emptyRopeNode; + + int endIndex = index + count; + RopeNode<T> result = CloneIfShared(); // convert function node to concat/leaf + if (result.height == 0) { + int remainingAfterEnd = result.length - endIndex; + for (int i = 0; i < remainingAfterEnd; i++) { + result.contents[index + i] = result.contents[endIndex + i]; + } + result.length -= count; + } else { + if (endIndex <= result.left.length) { + // deletion is only within the left part + result.left = result.left.RemoveRange(index, count); + } else if (index >= result.left.length) { + // deletion is only within the right part + result.right = result.right.RemoveRange(index - result.left.length, count); + } else { + // deletion overlaps both parts + int deletionAmountOnLeftSide = result.left.length - index; + result.left = result.left.RemoveRange(index, deletionAmountOnLeftSide); + result.right = result.right.RemoveRange(0, count - deletionAmountOnLeftSide); + } + // The deletion might have introduced empty nodes. Those must be removed. + if (result.left.length == 0) + return result.right; + if (result.right.length == 0) + return result.left; + + result.length -= count; + result.MergeIfPossible(); + result.Rebalance(); + } + return result; + } + + #region Debug Output + #if DEBUG + internal virtual void AppendTreeToString(StringBuilder b, int indent) + { + b.AppendLine(ToString()); + indent += 2; + if (left != null) { + b.Append(' ', indent); + b.Append("L: "); + left.AppendTreeToString(b, indent); + } + if (right != null) { + b.Append(' ', indent); + b.Append("R: "); + right.AppendTreeToString(b, indent); + } + } + + public override string ToString() + { + if (contents != null) { + char[] charContents = contents as char[]; + if (charContents != null) + return "[Leaf length=" + length + ", isShared=" + isShared + ", text=\"" + new string(charContents, 0, length) + "\"]"; + else + return "[Leaf length=" + length + ", isShared=" + isShared + "\"]"; + } else { + return "[Concat length=" + length + ", isShared=" + isShared + ", height=" + height + ", Balance=" + this.Balance + "]"; + } + } + + internal string GetTreeAsString() + { + StringBuilder b = new StringBuilder(); + AppendTreeToString(b, 0); + return b.ToString(); + } + #endif + #endregion + + /// <summary> + /// Gets the root node of the subtree from a lazily evaluated function node. + /// Such nodes are always marked as shared. + /// GetContentNode() will return either a Concat or Leaf node, never another FunctionNode. + /// </summary> + internal virtual RopeNode<T> GetContentNode() + { + throw new InvalidOperationException("Called GetContentNode() on non-FunctionNode."); + } + } + + sealed class FunctionNode<T> : RopeNode<T> + { + Func<Rope<T>> initializer; + RopeNode<T> cachedResults; + + public FunctionNode(int length, Func<Rope<T>> initializer) + { + Debug.Assert(length > 0); + Debug.Assert(initializer != null); + + this.length = length; + this.initializer = initializer; + // Function nodes are immediately shared, but cannot be cloned. + // This ensures we evaluate every initializer only once. + this.isShared = true; + } + + internal override RopeNode<T> GetContentNode() + { + lock (this) { + if (this.cachedResults == null) { + if (this.initializer == null) + throw new InvalidOperationException("Trying to load this node recursively; or: a previous call to a rope initializer failed."); + Func<Rope<T>> initializerCopy = this.initializer; + this.initializer = null; + Rope<T> resultRope = initializerCopy(); + if (resultRope == null) + throw new InvalidOperationException("Rope initializer returned null."); + RopeNode<T> resultNode = resultRope.root; + resultNode.Publish(); // result is shared between returned rope and the rope containing this function node + if (resultNode.length != this.length) + throw new InvalidOperationException("Rope initializer returned rope with incorrect length."); + if (resultNode.height == 0 && resultNode.contents == null) { + // ResultNode is another function node. + // We want to guarantee that GetContentNode() never returns function nodes, so we have to + // go down further in the tree. + this.cachedResults = resultNode.GetContentNode(); + } else { + this.cachedResults = resultNode; + } + } + return this.cachedResults; + } + } + + #if DEBUG + internal override void AppendTreeToString(StringBuilder b, int indent) + { + RopeNode<T> resultNode; + lock (this) { + b.AppendLine(ToString()); + resultNode = cachedResults; + } + indent += 2; + if (resultNode != null) { + b.Append(' ', indent); + b.Append("C: "); + resultNode.AppendTreeToString(b, indent); + } + } + + public override string ToString() + { + return "[FunctionNode length=" + length + " initializerRan=" + (initializer == null) + "]"; + } + #endif + } +} diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/RopeTextReader.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/RopeTextReader.cs new file mode 100644 index 0000000000..b6827683db --- /dev/null +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/RopeTextReader.cs @@ -0,0 +1,120 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace Mono.TextEditor.Utils +{ + /// <summary> + /// TextReader implementation that reads text from a rope. + /// </summary> + public sealed class RopeTextReader : TextReader + { + Stack<RopeNode<char>> stack = new Stack<RopeNode<char>>(); + RopeNode<char> currentNode; + int indexInsideNode; + + /// <summary> + /// Creates a new RopeTextReader. + /// Internally, this method creates a Clone of the rope; so the text reader will always read through the old + /// version of the rope if it is modified. <seealso cref="Rope{T}.Clone()"/> + /// </summary> + public RopeTextReader(Rope<char> rope) + { + if (rope == null) + throw new ArgumentNullException("rope"); + + // We force the user to iterate through a clone of the rope to keep the API contract of RopeTextReader simple + // (what happens when a rope is modified while iterating through it?) + rope.root.Publish(); + + // special case for the empty rope: + // leave currentNode initialized to null (RopeTextReader doesn't support empty nodes) + if (rope.Length != 0) { + currentNode = rope.root; + GoToLeftMostLeaf(); + } + } + + void GoToLeftMostLeaf() + { + while (currentNode.contents == null) { + if (currentNode.height == 0) { + // this is a function node - move to its contained rope + currentNode = currentNode.GetContentNode(); + continue; + } + Debug.Assert(currentNode.right != null); + stack.Push(currentNode.right); + currentNode = currentNode.left; + } + Debug.Assert(currentNode.height == 0); + } + + /// <inheritdoc/> + public override int Peek() + { + if (currentNode == null) + return -1; + return currentNode.contents[indexInsideNode]; + } + + /// <inheritdoc/> + public override int Read() + { + if (currentNode == null) + return -1; + char result = currentNode.contents[indexInsideNode++]; + if (indexInsideNode >= currentNode.length) + GoToNextNode(); + return result; + } + + void GoToNextNode() + { + if (stack.Count == 0) { + currentNode = null; + } else { + indexInsideNode = 0; + currentNode = stack.Pop(); + GoToLeftMostLeaf(); + } + } + + /// <inheritdoc/> + public override int Read(char[] buffer, int index, int count) + { + if (currentNode == null) + return 0; + int amountInCurrentNode = currentNode.length - indexInsideNode; + if (count < amountInCurrentNode) { + Array.Copy(currentNode.contents, indexInsideNode, buffer, index, count); + indexInsideNode += count; + return count; + } else { + // read to end of current node + Array.Copy(currentNode.contents, indexInsideNode, buffer, index, amountInCurrentNode); + GoToNextNode(); + return amountInCurrentNode; + } + } + } +} diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/TextBreaker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/TextBreaker.cs index e96caf39af..4e87370e22 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/TextBreaker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Utils/TextBreaker.cs @@ -49,7 +49,7 @@ namespace Mono.TextEditor.Utils /// <param name='lineCount'> /// The number of lines to get words from /// </param> - public static List<TextSegment> BreakLinesIntoWords (TextEditor editor, int startLine, int lineCount, bool includeDelimiter = true) + public static List<TextSegment> BreakLinesIntoWords (MonoTextEditor editor, int startLine, int lineCount, bool includeDelimiter = true) { return BreakLinesIntoWords (editor.Document, startLine, lineCount, includeDelimiter); } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/NewViEditMode.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/NewViEditMode.cs index 2527e953bd..e83d383e32 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/NewViEditMode.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/NewViEditMode.cs @@ -35,7 +35,7 @@ namespace Mono.TextEditor.Vi public class NewViEditMode : EditMode { ViStatusArea statusArea; - TextEditor viTextEditor; + MonoTextEditor viTextEditor; protected ViEditor ViEditor { get ; private set ;} @@ -75,7 +75,7 @@ namespace Mono.TextEditor.Vi } } - public override void AllocateTextArea (TextEditor textEditor, TextArea textArea, Gdk.Rectangle allocation) + public override void AllocateTextArea (MonoTextEditor textEditor, TextArea textArea, Gdk.Rectangle allocation) { statusArea.AllocateArea (textArea, allocation); } @@ -85,7 +85,7 @@ namespace Mono.TextEditor.Vi ViEditor.ProcessKey (modifier, key, (char)unicodeKey); } - public new TextEditor Editor { get { return base.Editor; } } + public new MonoTextEditor Editor { get { return base.Editor; } } public new TextEditorData Data { get { return base.Data; } } public override bool WantsToPreemptIM { diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViEditor.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViEditor.cs index 83110bd41e..9cd39fdd76 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViEditor.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViEditor.cs @@ -55,7 +55,7 @@ namespace Mono.TextEditor.Vi Dictionary<char,string> registers = new Dictionary<char,string> (); Dictionary<char,ViMark> marks = new Dictionary<char, ViMark> (); - public TextEditor Editor { get { return editMode.Editor; } } + public MonoTextEditor Editor { get { return editMode.Editor; } } public TextEditorData Data { get { return editMode.Data; } } public TextDocument Document { get { return Data.Document; } } ViBuilderContext Context { get; set; } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViMode.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViMode.cs index 9eb1a3164c..83d8caee06 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViMode.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViMode.cs @@ -187,7 +187,7 @@ namespace Mono.TextEditor.Vi } ViStatusArea statusArea; - TextEditor viTextEditor; + MonoTextEditor viTextEditor; void CheckVisualMode () { @@ -243,7 +243,7 @@ namespace Mono.TextEditor.Vi } } - public override void AllocateTextArea (TextEditor textEditor, TextArea textArea, Gdk.Rectangle allocation) + public override void AllocateTextArea (MonoTextEditor textEditor, TextArea textArea, Gdk.Rectangle allocation) { statusArea.AllocateArea (textArea, allocation); } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViStatusArea.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViStatusArea.cs index 6c768d7935..3b26ada8e7 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViStatusArea.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.Vi/ViStatusArea.cs @@ -31,18 +31,18 @@ namespace Mono.TextEditor.Vi { class ViStatusArea : Gtk.DrawingArea { - TextEditor editor; + MonoTextEditor editor; bool showCaret; string statusText; - public ViStatusArea (TextEditor editor) + public ViStatusArea (MonoTextEditor editor) { this.editor = editor; editor.TextViewMargin.CaretBlink += HandleCaretBlink; editor.Caret.PositionChanged += HandlePositionChanged; editor.AddTopLevelWidget (this, 0, 0); - ((TextEditor.EditorContainerChild)editor[this]).FixedPosition = true; + ((MonoTextEditor.EditorContainerChild)editor[this]).FixedPosition = true; Show (); } @@ -83,7 +83,7 @@ namespace Mono.TextEditor.Vi if (textArea.Allocation != allocation) { textArea.SizeAllocate (allocation); SetSizeRequest (allocation.Width, (int)editor.LineHeight); - var pos = ((TextEditor.EditorContainerChild)editor [this]); + var pos = ((MonoTextEditor.EditorContainerChild)editor [this]); if (pos.X != 0 || pos.Y != allocation.Height) editor.MoveTopLevelWidget (this, 0, allocation.Height); } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor.csproj b/main/src/core/Mono.Texteditor/Mono.TextEditor.csproj index fde0ebbec9..0f798d61d4 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor.csproj +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor.csproj @@ -168,9 +168,7 @@ <Compile Include="Mono.TextEditor\Document\DocumentLocation.cs" /> <Compile Include="Mono.TextEditor\Document\DocumentRegion.cs" /> <Compile Include="Mono.TextEditor\Document\DocumentUpdateRequest.cs" /> - <Compile Include="Mono.TextEditor\Document\IBuffer.cs" /> <Compile Include="Mono.TextEditor\Gui\ITooltipProvider.cs" /> - <Compile Include="Mono.TextEditor\Document\GapBuffer.cs" /> <Compile Include="Mono.TextEditor\Document\FoldSegment.cs" /> <Compile Include="Mono.TextEditor\Document\LineEventArgs.cs" /> <Compile Include="Mono.TextEditor\Document\LineSplitter.cs" /> @@ -183,12 +181,10 @@ <Compile Include="Mono.TextEditor\Document\PrimitiveLineSplitter.cs" /> <Compile Include="Mono.TextEditor\Document\IWordFindStrategy.cs" /> <Compile Include="Mono.TextEditor\Actions\SelectionActions.cs" /> - <Compile Include="Mono.TextEditor\Document\StringBuffer.cs" /> <Compile Include="Mono.TextEditor\Document\TextSegment.cs" /> <Compile Include="Mono.TextEditor\Gui\TextViewMargin.cs" /> <Compile Include="Mono.TextEditor\Document\SharpDevelopWordFindStrategy.cs" /> <Compile Include="Mono.TextEditor\Document\EmacsWordFindStrategy.cs" /> - <Compile Include="Mono.TextEditor\Document\BufferedTextReader.cs" /> <Compile Include="Mono.TextEditor\Document\SyntaxModeChangeEventArgs.cs" /> <Compile Include="Mono.TextEditor\Document\DocumentLine.cs" /> <Compile Include="Mono.TextEditor\Standalone\TextSourceVersionProvider.cs" /> @@ -207,7 +203,6 @@ <Compile Include="Mono.TextEditor\TextSegmentMarker.cs" /> <Compile Include="Mono.TextEditor\Gui\FoldMarkerMargin.cs" /> <Compile Include="Mono.TextEditor\Gui\TextArea.cs" /> - <Compile Include="Mono.TextEditor\Gui\TextEditor.cs" /> <Compile Include="Mono.TextEditor\Standalone\StringTextSource.cs" /> <Compile Include="Mono.TextEditor.Utils\HtmlWriter.cs" /> <Compile Include="Mono.TextEditor.PopupWindow\WindowTransparencyDecorator.cs" /> @@ -236,6 +231,13 @@ <Compile Include="Mono.TextEditor\Gui\LayoutCache.cs" /> <Compile Include="Mono.TextEditor\Gui\GtkUtil.cs" /> <Compile Include="Mono.TextEditor\EditModeChangedEventArgs.cs" /> + <Compile Include="Mono.TextEditor.Utils\Rope.cs" /> + <Compile Include="Mono.TextEditor.Utils\RopeNode.cs" /> + <Compile Include="Mono.TextEditor.Utils\ImmutableStack.cs" /> + <Compile Include="Mono.TextEditor.Utils\CharRope.cs" /> + <Compile Include="Mono.TextEditor.Utils\RopeTextReader.cs" /> + <Compile Include="Mono.TextEditor\Document\ImmutableLineSplitter.cs" /> + <Compile Include="Mono.TextEditor\Gui\MonoTextEditor.cs" /> </ItemGroup> <ItemGroup> <None Include="Mono.TextEditor.dll.config"> diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/DeleteActions.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/DeleteActions.cs index ce3e4c3b98..1c1cb81ad4 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/DeleteActions.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/DeleteActions.cs @@ -220,7 +220,35 @@ namespace Mono.TextEditor // Virtual indentation needs to be fixed before to have the same behavior // if it's there or not (otherwise user has to press multiple backspaces in some cases) data.EnsureCaretIsNotVirtual (); - DocumentLine line = data.Document.GetLine (data.Caret.Line); + + var line = data.Document.GetLine (data.Caret.Line); + // smart backspace (delete indentation) + if (data.Options.IndentStyle == IndentStyle.Smart || data.Options.IndentStyle == IndentStyle.Virtual) { + if (data.Caret.Column == data.GetVirtualIndentationColumn (data.Caret.Location)) { + bool isAllIndent = line.GetIndentation (data.Document).Length == data.Caret.Column - 1; + + if (isAllIndent) { + var prevLine = line.PreviousLine; + var prevLineIsEmpty = prevLine != null && prevLine.Length == 0; + + var startOffset = prevLine != null ? prevLine.EndOffset : 0; + data.Remove (startOffset, data.Caret.Offset - startOffset); + if (prevLine != null) { + if (prevLineIsEmpty) { + if (line.Length - data.Caret.Column - 1 > 0 && data.HasIndentationTracker) { + data.InsertAtCaret (data.IndentationTracker.GetIndentationString (data.Caret.Offset)); + } else { + data.Caret.Column = data.GetVirtualIndentationColumn (prevLine.Offset); + } + } + data.FixVirtualIndentation (); + } + return; + } + } + } + + // normal backspace. if (data.Caret.Column > line.Length + 1) { data.Caret.Column = line.Length + 1; } else if (data.Caret.Offset == line.Offset) { @@ -287,7 +315,21 @@ namespace Mono.TextEditor DocumentLine line = data.Document.GetLine (data.Caret.Line); if (data.Caret.Column == line.Length + 1) { if (data.Caret.Line < data.Document.LineCount) { - data.Remove (line.EndOffsetIncludingDelimiter - line.DelimiterLength, line.DelimiterLength); + var deletionLength = line.DelimiterLength; + // smart backspace (delete indentation) + if (data.Options.IndentStyle == IndentStyle.Smart || data.Options.IndentStyle == IndentStyle.Virtual) { + var next = line.NextLine; + if (next != null) { + if (data.HasIndentationTracker) { + var lineIndentation = next.GetIndentation (data.Document); + if (lineIndentation.StartsWith (data.IndentationTracker.GetIndentationString (next.Offset))) { + deletionLength += lineIndentation.Length; + } + } + } + } + + data.Remove (line.EndOffsetIncludingDelimiter - line.DelimiterLength, deletionLength); if (line.EndOffsetIncludingDelimiter == data.Document.TextLength) line.UnicodeNewline = UnicodeNewline.Unknown; } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/MiscActions.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/MiscActions.cs index 8a42f8b0a0..37b5dd2c81 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/MiscActions.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Actions/MiscActions.cs @@ -272,9 +272,11 @@ namespace Mono.TextEditor break; case IndentStyle.Auto: data.EnsureCaretIsNotVirtual (); - var sb = new StringBuilder (data.EolMarker); - sb.Append (data.Document.GetLineIndent (data.Caret.Line)); - data.InsertAtCaret (sb.ToString ()); + var indent = data.Document.GetLineIndent (data.Caret.Line); + data.InsertAtCaret (data.EolMarker); + data.EnsureCaretIsNotVirtual (); + if (data.GetLine (data.Caret.Line).Length == 0) + data.InsertAtCaret (indent); break; case IndentStyle.Smart: if (!data.HasIndentationTracker) @@ -511,59 +513,5 @@ namespace Mono.TextEditor } } - public static void SortSelectedLines (TextEditorData data) - { - var start = data.MainSelection.Start; - var end = data.MainSelection.End; - var caret = data.Caret.Location; - - 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 = data.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 = data.Document.LocationToOffset (new TextLocation (startLine, 0)); - data.Replace (startOffset, length, sb.ToString ()); - - data.Caret.Location = LimitColumn (data, caret); - data.SetSelection (LimitColumn (data, start), LimitColumn (data, end)); - } - - static DocumentLocation LimitColumn (TextEditorData data, DocumentLocation loc) - { - return new DocumentLocation (loc.Line, System.Math.Min (loc.Column, data.GetLine (loc.Line).Length + 1)); - } } }
\ No newline at end of file diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/BookmarkMarker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/BookmarkMarker.cs index c40ea22edf..63e525d6d5 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/BookmarkMarker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/BookmarkMarker.cs @@ -49,14 +49,14 @@ namespace Mono.TextEditor return margin is IconMargin; } - public override void DrawForeground (TextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) + public override void DrawForeground (MonoTextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) { DrawBookmarkFunc (editor, cr, LineSegment, metrics.X, metrics.Y, metrics.Width, metrics.Height); } - public static Action<TextEditor, Cairo.Context, DocumentLine, double, double, double, double> DrawBookmarkFunc = DrawIcon; + public static Action<MonoTextEditor, Cairo.Context, DocumentLine, double, double, double, double> DrawBookmarkFunc = DrawIcon; - static void DrawIcon (TextEditor editor, Cairo.Context cr, DocumentLine lineSegment, double x, double y, double width, double height) + static void DrawIcon (MonoTextEditor editor, Cairo.Context cr, DocumentLine lineSegment, double x, double y, double width, double height) { if (lineSegment.IsBookmarked) { var color1 = editor.ColorStyle.Bookmarks.Color; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/BufferedTextReader.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/BufferedTextReader.cs deleted file mode 100644 index 77340951be..0000000000 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/BufferedTextReader.cs +++ /dev/null @@ -1,93 +0,0 @@ -// BufferedTextReader.cs -// -// Author: -// Mike Krüger <mkrueger@novell.com> -// -// Copyright (c) 2008 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.IO; - -namespace Mono.TextEditor -{ - /// <summary> - /// Wraps the IBuffer interface to a System.IO.TextReader model. - /// </summary> - class BufferedTextReader : System.IO.TextReader - { - int position = 0; - IBuffer buffer; - - public BufferedTextReader (IBuffer buffer) - { - this.buffer = buffer; - } - - protected override void Dispose (bool disposing) - { - if (disposing) - buffer = null; - } - - public override void Close () - { - Dispose (); - } - - public override int Peek () - { - if (position < 0 || position >= buffer.TextLength) - return -1; - return buffer.GetCharAt (position); - } - - public override int Read () - { - if (position < 0 || position >= buffer.TextLength) - return -1; - return buffer.GetCharAt (position++); - } - - public override int Read (char[] buffer, int index, int count) - { - if (buffer == null) - throw new ArgumentNullException (); - int lastOffset = System.Math.Min (this.buffer.TextLength, position + count); - int length = lastOffset - position; - if (length <= 0) - return 0; - string text = this.buffer.GetTextAt (position, length); - text.CopyTo (0, buffer, index, length); - position += length; - return length; - } - - public override string ReadToEnd () - { - if (position < 0 || position >= buffer.TextLength) - return ""; - string result = this.buffer.GetTextAt (position, buffer.TextLength - position); - position = buffer.TextLength; - return result; - } - } -} diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DiffTracker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DiffTracker.cs index 1b56c0130f..1c8a1e190a 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DiffTracker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DiffTracker.cs @@ -46,7 +46,7 @@ namespace Mono.TextEditor public Mono.TextEditor.TextDocument.LineState GetLineState (DocumentLine line) { - if (line != null) { + if (line != null && lineStates != null) { try { var info = lineStates [line.LineNumber]; if (info != null) { diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DocumentUpdateRequest.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DocumentUpdateRequest.cs index bf4ce8f02d..baa5551ec1 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DocumentUpdateRequest.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/DocumentUpdateRequest.cs @@ -29,7 +29,7 @@ namespace Mono.TextEditor { public abstract class DocumentUpdateRequest { - public abstract void Update (TextEditor editor); + public abstract void Update (MonoTextEditor editor); } public class SinglePositionUpdate : DocumentUpdateRequest @@ -42,7 +42,7 @@ namespace Mono.TextEditor this.column = column; } - public override void Update (TextEditor editor) + public override void Update (MonoTextEditor editor) { editor.RedrawPosition (line, column); } @@ -50,7 +50,7 @@ namespace Mono.TextEditor public class UpdateAll : DocumentUpdateRequest { - public override void Update (TextEditor editor) + public override void Update (MonoTextEditor editor) { editor.QueueDraw (); } @@ -65,7 +65,7 @@ namespace Mono.TextEditor this.line = line; } - public override void Update (TextEditor editor) + public override void Update (MonoTextEditor editor) { editor.RedrawLine (line); } @@ -81,7 +81,7 @@ namespace Mono.TextEditor this.end = end; } - public override void Update (TextEditor editor) + public override void Update (MonoTextEditor editor) { editor.TextViewMargin.PurgeLayoutCache (); editor.RedrawLines (start, end); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/GapBuffer.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/GapBuffer.cs deleted file mode 100644 index 7ba81771aa..0000000000 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/GapBuffer.cs +++ /dev/null @@ -1,599 +0,0 @@ -// GapBuffer.cs -// -// Author: -// Mike Krüger <mkrueger@novell.com> -// -// Copyright (c) 2007 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.Text; -using System.Collections.Generic; - -namespace Mono.TextEditor -{ - public sealed class GapBuffer : IBuffer - { - char[] buffer = new char[0]; - - int gapBegin = 0; - int gapEnd = 0; - int gapLength = 0; - - const int minGapLength = 4 * 1024; - const int maxGapLength = 32 * 1024; - - public int TextLength { - get { - return buffer.Length - gapLength; - } - } - - public void Insert (int offset, string text) - { - Replace (offset, 0, text); - } - - public void Remove (int offset, int count) - { - Replace (offset, count, null); - } - - public void Remove (TextSegment segment) - { - Remove (segment.Offset, segment.Length); - } - - public string GetTextAt (TextSegment segment) - { - return GetTextAt (segment.Offset, segment.Length); - } - - public string Text { - get { - return GetTextAt (0, TextLength); - } - set { - buffer = value != null ? value.ToCharArray () : new char[0]; - gapBegin = gapEnd = gapLength = 0; - } - } - - public char GetCharAt (int offset) - { - return buffer[offset < gapBegin ? offset : offset + gapLength]; - } - - public string GetTextAt (int offset, int count) - { - int end = offset + count; - if (end < gapBegin) - return new string (buffer, offset, count); - if (offset > gapBegin) - return new string (buffer, offset + gapLength, count); - - int leftBlockSize = gapBegin - offset; - int rightBlockSize = end - gapBegin; - char[] result = new char [leftBlockSize + rightBlockSize]; - Array.Copy (buffer, offset, result, 0, leftBlockSize); - Array.Copy (buffer, gapEnd, result, leftBlockSize, rightBlockSize); - return new string (result); - } - - public void Replace (int offset, int count, string text) - { - if (!string.IsNullOrEmpty (text)) { - PlaceGap (offset, text.Length - count); - text.CopyTo (0, buffer, offset, text.Length); - gapBegin += text.Length; - } else { - PlaceGap (offset, 0); - } - gapEnd += count; - gapLength = gapEnd - gapBegin; - if (gapLength > maxGapLength) - CreateBuffer (gapBegin, minGapLength); - } - - void PlaceGap (int newOffset, int minLength) - { - if (gapLength < minLength) { - if (minLength < maxGapLength) { - CreateBuffer (newOffset, minLength + (maxGapLength - minLength) / 2); - } else { - CreateBuffer (newOffset, minLength + minGapLength); - } - return; - } - - int delta = gapBegin - newOffset; - if (delta > 0) { - Array.Copy (buffer, newOffset, buffer, gapEnd - delta, delta); - } else { - Array.Copy (buffer, gapEnd, buffer, gapBegin, -delta); - } - gapBegin -= delta; - gapEnd -= delta; - } - - void CreateBuffer (int gapOffset, int gapLength) - { - gapLength = System.Math.Max (minGapLength, gapLength); - - char[] newBuffer = new char[TextLength + gapLength]; - if (gapOffset < gapBegin) { - Array.Copy (buffer, 0, newBuffer, 0, gapOffset); - Array.Copy (buffer, gapOffset, newBuffer, gapOffset + gapLength, gapBegin - gapOffset); - Array.Copy (buffer, gapEnd, newBuffer, newBuffer.Length - (buffer.Length - gapEnd), buffer.Length - gapEnd); - } else { - Array.Copy (buffer, 0, newBuffer, 0, gapBegin); - Array.Copy (buffer, gapEnd, newBuffer, gapBegin, gapOffset - gapBegin); - int lastPartLength = newBuffer.Length - (gapOffset + gapLength); - Array.Copy (buffer, buffer.Length - lastPartLength, newBuffer, gapOffset + gapLength, lastPartLength); - } - - gapBegin = gapOffset; - gapEnd = gapOffset + gapLength; - this.gapLength = gapLength; - buffer = newBuffer; - } - - - // TODO: Optimize! - int IBuffer.IndexOf (char c, int startIndex, int count) - { - return Text.IndexOf (c, startIndex, count); - } - - int IBuffer.IndexOfAny (char[] anyOf, int startIndex, int count) - { - return Text.IndexOfAny (anyOf, startIndex, count); - } - - public int IndexOf (string searchText, int startIndex, int count, StringComparison comparisonType) - { - return Text.IndexOf (searchText, startIndex, count, comparisonType); - } - - int IBuffer.LastIndexOf (char c, int startIndex, int count) - { - return Text.LastIndexOf (c, startIndex, count); - } - - public int LastIndexOf (string searchText, int startIndex, int count, StringComparison comparisonType) - { - return Text.LastIndexOf (searchText, startIndex, count, comparisonType); - } - -// #region Search -// unsafe int SearchForwardInternal (string pattern, int startIndex) -// { -// if (startIndex > gapBegin) -// startIndex += gapLength; -// -// int valueLen = pattern.Length; -// if (startIndex >= buffer.Length - valueLen + 1) -// return -1; -// -// fixed (char* bufferPtr = buffer, patternPtr = pattern) { -// char* ap = bufferPtr + startIndex; -// char* bufferPhysEnd = bufferPtr + buffer.Length; -// char* bufferEnd = bufferPhysEnd - valueLen + 1; -// char* gapBeginPtr = bufferPtr + gapBegin; -// char* gapEndPtr = bufferPtr + gapEnd; -// char* stopGap = gapBeginPtr - valueLen + 1; -// char* patternPos1Ptr = patternPtr + 1; -// char* patternEndPtr = patternPtr + valueLen; -// char p0 = *patternPtr; -// -// if (ap < gapBeginPtr) { -// if (stopGap > bufferPtr) { -// while (ap < stopGap) { -// if (*ap == p0) { -// char* p = ap + 1; -// char* v = patternPos1Ptr; -// while (v < patternEndPtr) { -// if (*p != *v) -// goto NextVal; -// v++; -// p++; -// } -// return (int)(ap - bufferPtr); -// } -// NextVal: -// ap++; -// } -// } -// -// while (ap != gapBeginPtr) { -// if (*ap == p0) { -// char* p = ap + 1; -// char* v = patternPos1Ptr; -// while (v < patternEndPtr) { -// if (*p != *v) -// goto NextVal; -// v++; -// p++; -// if (p == gapBeginPtr) -// p = gapEndPtr; -// } -// return (int)(ap - bufferPtr); -// } -// NextVal: -// ap++; -// } -// } -// -// if (ap < gapEndPtr) -// ap = gapEndPtr; -// if (ap < bufferEnd) { -// while (ap != bufferEnd) { -// if (*ap == p0) { -// char* p = ap + 1; -// char* v = patternPos1Ptr; -// while (v < patternEndPtr) { -// if (*p != *v) -// goto NextVal; -// v++; -// p++; -// } -// return (int)(ap - gapLength - bufferPtr); -// } -// NextVal: -// ap++; -// } -// } -// } -// return -1; -// } -// -// unsafe int SearchForwardInternalIgnoreCase (string pattern, int startIndex) -// { -// if (startIndex > gapBegin) -// startIndex += gapLength; -// -// int valueLen = pattern.Length; -// if (startIndex >= buffer.Length - valueLen + 1) -// return -1; -// -// fixed (char* bufferPtr = buffer, patternPtr = pattern) { -// char* ap = bufferPtr + startIndex; -// char* bufferPhysEnd = bufferPtr + buffer.Length; -// char* bufferEnd = bufferPhysEnd - valueLen + 1; -// char* gapBeginPtr = bufferPtr + gapBegin; -// char* gapEndPtr = bufferPtr + gapEnd; -// char* stopGap = gapBeginPtr - valueLen + 1; -// char* patternPos1Ptr = patternPtr + 1; -// char* patternEndPtr = patternPtr + valueLen; -// char p0 = *patternPtr; -// -// if (ap < gapBeginPtr) { -// if (stopGap > bufferPtr) { -// while (ap < stopGap) { -// if (char.ToUpper (*ap) == p0) { -// char* p = ap + 1; -// char* v = patternPos1Ptr; -// while (v < patternEndPtr) { -// if (char.ToUpper (*p) != *v) -// goto NextVal; -// v++; -// p++; -// } -// return (int)(ap - bufferPtr); -// } -// NextVal: -// ap++; -// } -// } -// -// while (ap != gapBeginPtr) { -// if (char.ToUpper (*ap) == p0) { -// char* p = ap + 1; -// char* v = patternPos1Ptr; -// while (v < patternEndPtr) { -// if (char.ToUpper (*p) != *v) -// goto NextVal; -// v++; -// p++; -// if (p == gapBeginPtr) -// p = gapEndPtr; -// } -// return (int)(ap - bufferPtr); -// } -// NextVal: -// ap++; -// } -// } -// -// if (ap < gapEndPtr) -// ap = gapEndPtr; -// if (ap < bufferEnd) { -// while (ap != bufferEnd) { -// if (char.ToUpper (*ap) == p0) { -// char* p = ap + 1; -// char* v = patternPos1Ptr; -// while (v < patternEndPtr) { -// if (char.ToUpper (*p) != *v) -// goto NextVal; -// v++; -// p++; -// } -// return (int)(ap - gapLength - bufferPtr); -// } -// NextVal: -// ap++; -// } -// } -// } -// return -1; -// } -// -// public IEnumerable<int> SearchForward (string pattern, int startIndex) -// { -// int idx = startIndex; -// while ((idx = SearchForwardInternal (pattern, idx)) != -1) { -// yield return idx; -// idx += pattern.Length; -// } -// } -// -// public IEnumerable<int> SearchForwardIgnoreCase (string pattern, int startIndex) -// { -// pattern = pattern.ToUpper (); -// int idx = startIndex; -// while ((idx = SearchForwardInternalIgnoreCase (pattern, idx)) != -1) { -// yield return idx; -// idx += pattern.Length; -// } -// } -// -// unsafe int SearchBackwardInternal (string pattern, int startIndex) -// { -// int valueLen = pattern.Length; -// if (startIndex < valueLen - 1) -// return -1; -// if (startIndex > gapBegin) -// startIndex += gapLength; -// fixed (char* bufferPtr = buffer, patternPtr = pattern) { -// char* ap = bufferPtr + startIndex; -// char* bufferEnd = bufferPtr + valueLen - 1; -// -// char* bufferPhysEnd = bufferPtr + buffer.Length; -// char* gapBeginPtr = bufferPtr + gapBegin; -// char* gapEndPtr = bufferPtr + gapEnd; -// char* stopGap = gapEndPtr + valueLen - 1; -// char* patternPos1Ptr = patternPtr + valueLen - 2; -// char* patternEndPtr = patternPtr - 1; -// char p0 = *(patternPtr + valueLen - 1); -// -// if (ap >= gapEndPtr) { -// if (stopGap < bufferPhysEnd) { -// while (ap >= stopGap) { -// if (*ap == p0) { -// char* p = ap - 1; -// char* v = patternPos1Ptr; -// while (v > patternEndPtr) { -// if (*p != *v) -// goto NextVal; -// v--; -// p--; -// } -// return (int)(p - gapLength - bufferPtr + 1); -// } -// NextVal: -// ap--; -// } -// } -// -// while (ap >= gapEndPtr) { -// if (*ap == p0) { -// char* p = ap - 1; -// char* v = patternPos1Ptr; -// while (v > patternEndPtr) { -// if (*p != *v) -// goto NextVal; -// v--; -// if (p == gapEndPtr) { -// p = gapBeginPtr - 1; -// } else { -// p--; -// } -// } -// if (p >= gapEndPtr) -// return (int)(p - gapLength - bufferPtr + 1); -// return (int)(p - bufferPtr + 1); -// } -// NextVal: -// ap--; -// } -// } -// -// while (ap >= bufferEnd) { -// if (*ap == p0) { -// char* p = ap - 1; -// char* v = patternPos1Ptr; -// while (v > patternEndPtr) { -// if (*p != *v) -// goto NextVal; -// v--; -// p--; -// } -// return (int)(p - bufferPtr + 1); -// } -// NextVal: -// ap--; -// } -// } -// return -1; -// } -// -// unsafe int SearchBackwardInternalIgnoreCase (string pattern, int startIndex) -// { -// int valueLen = pattern.Length; -// if (startIndex < valueLen - 1) -// return -1; -// if (startIndex > gapBegin) -// startIndex += gapLength; -// -// fixed (char* bufferPtr = buffer, patternPtr = pattern) { -// char* ap = bufferPtr + startIndex; -// char* bufferEnd = bufferPtr + valueLen - 1; -// -// char* bufferPhysEnd = bufferPtr + buffer.Length; -// char* gapBeginPtr = bufferPtr + gapBegin; -// char* gapEndPtr = bufferPtr + gapEnd; -// char* stopGap = gapEndPtr + valueLen - 1; -// char* patternPos1Ptr = patternPtr + valueLen - 2; -// char* patternEndPtr = patternPtr - 1; -// char p0 = *(patternPtr + valueLen - 1); -// -// if (ap >= gapEndPtr) { -// if (stopGap < bufferPhysEnd) { -// while (ap >= stopGap) { -// if (char.ToUpper (*ap) == p0) { -// char* p = ap - 1; -// char* v = patternPos1Ptr; -// while (v > patternEndPtr) { -// if (char.ToUpper (*p) != *v) -// goto NextVal; -// v--; -// p--; -// } -// return (int)(p - gapLength - bufferPtr + 1); -// } -// NextVal: -// ap--; -// } -// } -// -// while (ap >= gapEndPtr) { -// if (char.ToUpper (*ap) == p0) { -// char* p = ap - 1; -// char* v = patternPos1Ptr; -// while (v > patternEndPtr) { -// if (char.ToUpper (*p) != *v) -// goto NextVal; -// v--; -// if (p == gapEndPtr) { -// p = gapBeginPtr - 1; -// } else { -// p--; -// } -// } -// if (p >= gapEndPtr) -// return (int)(p - gapLength - bufferPtr + 1); -// return (int)(p - bufferPtr + 1); -// } -// NextVal: -// ap--; -// } -// } -// while (ap >= bufferEnd) { -// if (char.ToUpper (*ap) == p0) { -// char* p = ap - 1; -// char* v = patternPos1Ptr; -// while (v > patternEndPtr) { -// if (char.ToUpper (*p) != *v) -// goto NextVal; -// v--; -// p--; -// } -// return (int)(p - bufferPtr + 1); -// } -// NextVal: -// ap--; -// } -// } -// return -1; -// } -// -// public IEnumerable<int> SearchBackward (string pattern, int startIndex) -// { -// int idx = startIndex; -// while ((idx = SearchBackwardInternal (pattern, idx)) != -1) { -// yield return idx; -// idx -= pattern.Length; -// } -// } -// -// public IEnumerable<int> SearchBackwardIgnoreCase (string pattern, int startIndex) -// { -// pattern = pattern.ToUpper (); -// int idx = startIndex; -// while ((idx = SearchBackwardInternalIgnoreCase (pattern, idx)) != -1) { -// yield return idx; -// idx -= pattern.Length; -// } -// } -// -// /* Boyer-Moore-Horspool-Raita implementation: (but on Intel Core i brute force outpeforms it. -// -// static int[] ProcessString (string pattern) -// { -// var result = new int[char.MaxValue]; -// for (int i = 0; i < result.Length; i++) -// result[i] = pattern.Length - 1; -// for (int i = 0; i < pattern.Length - 1; ++i) { -// result[pattern[i]] = pattern.Length - i - 1; -// } -// return result; -// } -// unsafe static int bmhrSearchBytes (string text, string pattern, int textStart, int[] b) -// { -// int lastIndex = text.Length + pattern.Length - 1; -// if (textStart >= lastIndex) -// return -1; -// -// int m = pattern.Length - 1; -// int mMinusOne = pattern.Length - 2; -// -// var last = pattern[pattern.Length - 1]; -// var first = pattern[0]; -// -// fixed (char* textPtr = text, pattenrPtr = pattern) { -// char* i = textPtr + textStart + pattern.Length - 1; -// char* endText = textPtr + lastIndex; -// while (i < endText) { -// if (*i == last) { -// //if (*(i - m) == first) { -// char* k = i - 1; -// char* pp = pattenrPtr + mMinusOne; -// -// while (pp >= pattenrPtr && *k == *pp) { -// --k; -// --pp; -// } -// -// if (pp < pattenrPtr) -// return (int)(k - textPtr) + 1; -// // } -// } -// i += b[*i]; -// } -// } -// return -1; -// } -// * */ -// #endregion - - } -}
\ No newline at end of file diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/IBuffer.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/IBuffer.cs deleted file mode 100644 index 0cfffef74e..0000000000 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/IBuffer.cs +++ /dev/null @@ -1,131 +0,0 @@ -// IBuffer.cs -// -// Author: -// Mike Krüger <mkrueger@novell.com> -// -// Copyright (c) 2007 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.Text; - -namespace Mono.TextEditor -{ - public interface IBuffer - { - /// <summary> - /// Gets the total text length. - /// </summary> - /// <returns>The length of the text, in characters.</returns> - /// <remarks>This is the same as Text.Length, but is more efficient because - /// it doesn't require creating a String object.</remarks> - int TextLength { - get; - } - - /// <summary> - /// Gets the whole text as string. - /// </summary> - string Text { - get; - set; - } - - /// <summary> - /// Replaces text. - /// </summary> - /// <param name="offset">The starting offset of the text to be replaced.</param> - /// <param name="count">The length of the text to be replaced.</param> - /// <param name="value">The new text.</param> - void Replace (int offset, int count, string value); - - /// <summary> - /// Retrieves the text for a portion of the document. - /// </summary> - /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> - /// <remarks>This is the same as Text.Substring, but is more efficient because - /// it doesn't require creating a String object for the whole document.</remarks> - string GetTextAt (int offset, int count); - - /// <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> - char GetCharAt (int offset); - - /// <summary> - /// Gets the index of the first occurrence of the character in the specified array. - /// </summary> - /// <param name="c">Character to search for</param> - /// <param name="startIndex">Start index of the area to search.</param> - /// <param name="count">Length of the area to search.</param> - /// <returns>The first index where the character was found; or -1 if no occurrence was found.</returns> - int IndexOf (char c, int startIndex, int count); - - /// <summary> - /// Gets the index of the first occurrence of any character in the specified array. - /// </summary> - /// <param name="anyOf">Characters to search for</param> - /// <param name="startIndex">Start index of the area to search.</param> - /// <param name="count">Length of the area to search.</param> - /// <returns>The first index where any character was found; or -1 if no occurrence was found.</returns> - int IndexOfAny (char[] anyOf, int startIndex, int count); - - /// <summary> - /// Gets the index of the first occurrence of the specified search text in this text source. - /// </summary> - /// <param name="searchText">The search text</param> - /// <param name="startIndex">Start index of the area to search.</param> - /// <param name="count">Length of the area to search.</param> - /// <param name="comparisonType">String comparison to use.</param> - /// <returns>The first index where the search term was found; or -1 if no occurrence was found.</returns> - int IndexOf (string searchText, int startIndex, int count, StringComparison comparisonType); - - /// <summary> - /// Gets the index of the last occurrence of the specified character in this text source. - /// </summary> - /// <param name="c">The search character</param> - /// <param name="startIndex">Start index of the area to search.</param> - /// <param name="count">Length of the area to search.</param> - /// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns> - /// <remarks>The search proceeds backwards from (startIndex+count) to startIndex. - /// This is different than the meaning of the parameters on string.LastIndexOf!</remarks> - int LastIndexOf (char c, int startIndex, int count); - - /// <summary> - /// Gets the index of the last occurrence of the specified search text in this text source. - /// </summary> - /// <param name="searchText">The search text</param> - /// <param name="startIndex">Start index of the area to search.</param> - /// <param name="count">Length of the area to search.</param> - /// <param name="comparisonType">String comparison to use.</param> - /// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns> - /// <remarks>The search proceeds backwards from (startIndex+count) to startIndex. - /// This is different than the meaning of the parameters on string.LastIndexOf!</remarks> - int LastIndexOf (string searchText, int startIndex, int count, StringComparison comparisonType); - } -} diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/ImmutableLineSplitter.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/ImmutableLineSplitter.cs new file mode 100644 index 0000000000..a2d65da072 --- /dev/null +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/ImmutableLineSplitter.cs @@ -0,0 +1,179 @@ +// +// ImmutableLineSplitter.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.Linq; +using ICSharpCode.NRefactory; +using System.Collections.Generic; + +namespace Mono.TextEditor +{ + class ImmutableLineSplitter : ILineSplitter + { + readonly LineSegment[] lines; + + sealed class LineSegment : DocumentLine + { + readonly ImmutableLineSplitter splitter; + readonly int lineNumber; + + public override int Offset { get; set; } + + public override int LineNumber { + get { + return lineNumber + 1; + } + } + + public override DocumentLine NextLine { + get { + return splitter.Get (lineNumber + 1); + } + } + + public override DocumentLine PreviousLine { + get { + return splitter.Get (lineNumber - 1); + } + } + + public LineSegment (ImmutableLineSplitter splitter, int lineNumber, int offset, int length, UnicodeNewline newLine) : base(length, newLine) + { + this.splitter = splitter; + this.lineNumber = lineNumber; + Offset = offset; + } + } + + public ImmutableLineSplitter (ILineSplitter src) + { + if (src == null) + throw new ArgumentNullException ("src"); + lines = new LineSegment[src.Count]; + int cur = 0; + foreach (var line in src.Lines) { + lines [cur] = new LineSegment (this, cur, line.Offset, line.LengthIncludingDelimiter, line.UnicodeNewline); + cur++; + } + } + + #region ILineSplitter implementation + + public event EventHandler<LineEventArgs> LineChanged; + + public event EventHandler<LineEventArgs> LineInserted; + + public event EventHandler<LineEventArgs> LineRemoved; + + public void Clear () + { + } + + public void Initalize (string text, out DocumentLine longestLine) + { + longestLine = null; + } + + public DocumentLine Get (int number) + { + return lines [number - 1]; + } + + public DocumentLine GetLineByOffset (int offset) + { + return lines [OffsetToLineNumber (offset) - 1]; + } + + public int OffsetToLineNumber (int offset) + { + int min = 0; + int max = lines.Length - 1; + while (min <= max) { + int mid = (min + max) / 2; + var middleLine = lines [mid]; + if (offset < middleLine.Offset) { + max = mid - 1; + } else if (offset > middleLine.EndOffset) { + min = mid + 1; + } else { + return mid + 1; + } + } + return lines.Length; + } + + public void TextReplaced (object sender, DocumentChangeEventArgs args) + { + } + + public void TextRemove (int offset, int length) + { + } + + public void TextInsert (int offset, string text) + { + } + + public IEnumerable<DocumentLine> GetLinesBetween (int startLine, int endLine) + { + for (int i = startLine; i <= endLine; i++) + yield return Get (i); + } + + public IEnumerable<DocumentLine> GetLinesStartingAt (int startLine) + { + for (int i = startLine; i <= Count; i++) + yield return Get (i); + } + + public IEnumerable<DocumentLine> GetLinesReverseStartingAt (int startLine) + { + for (int i = startLine; i-- > DocumentLocation.MinLine;) + yield return Get (i); + } + + public int Count { + get { + return lines.Length; + } + } + + public bool LineEndingMismatch { + get; + set; + } + + public System.Collections.Generic.IEnumerable<DocumentLine> Lines { + get { + return lines; + } + } + + #endregion + + + } +} + diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/StringBuffer.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/StringBuffer.cs deleted file mode 100644 index ba504a179a..0000000000 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/StringBuffer.cs +++ /dev/null @@ -1,70 +0,0 @@ - -using System; -using System.Collections.Generic; -using System.Text; -namespace Mono.TextEditor -{ - /// <summary>
- /// Simple implementation of the buffer interface to support fast read-only documents.
- /// </summary> - public class StringBuffer : IBuffer
- {
- string buffer;
-
- public StringBuffer (string buffer)
- {
- this.buffer = buffer;
- }
-
- #region IBuffer Members
- int IBuffer.TextLength {
- get { return buffer.Length; }
- }
-
- string IBuffer.Text {
- get { return buffer; }
- set { buffer = value; }
- }
-
- void IBuffer.Replace (int offset, int count, string value)
- {
- throw new NotSupportedException ("Operation not supported on this buffer.");
- }
-
- string IBuffer.GetTextAt (int offset, int count)
- {
- return buffer.Substring (offset, count);
- }
-
- char IBuffer.GetCharAt (int offset)
- {
- return buffer[offset];
- }
- - int IBuffer.IndexOf (char c, int startIndex, int count) - { - return buffer.IndexOf (c, startIndex, count); - } - - int IBuffer.IndexOfAny (char[] anyOf, int startIndex, int count) - { - return buffer.IndexOfAny (anyOf, startIndex, count); - } - - public int IndexOf (string searchText, int startIndex, int count, StringComparison comparisonType) - { - return buffer.IndexOf (searchText, startIndex, count, comparisonType); - } - - int IBuffer.LastIndexOf (char c, int startIndex, int count) - { - return buffer.LastIndexOf (c, startIndex, count); - } - - public int LastIndexOf (string searchText, int startIndex, int count, StringComparison comparisonType) - { - return buffer.LastIndexOf (searchText, startIndex, count, comparisonType); - } - #endregion - } -} diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/TextDocument.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/TextDocument.cs index f69c94d427..1b958ef287 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/TextDocument.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Document/TextDocument.cs @@ -40,7 +40,7 @@ namespace Mono.TextEditor { public class TextDocument : ICSharpCode.NRefactory.AbstractAnnotatable, ICSharpCode.NRefactory.Editor.IDocument { - readonly IBuffer buffer; + readonly Rope<char> buffer; readonly ILineSplitter splitter; ISyntaxMode syntaxMode = null; @@ -61,11 +61,21 @@ namespace Mono.TextEditor lock (this) { mimeType = value; SyntaxMode = SyntaxModeService.GetSyntaxMode (this, value); + OnMimeTypeChanged (EventArgs.Empty); } } } } - + + public event EventHandler MimeTypeChanged; + + protected virtual void OnMimeTypeChanged (EventArgs e) + { + EventHandler handler = this.MimeTypeChanged; + if (handler != null) + handler (this, e); + } + string fileName; public string FileName { get { @@ -91,6 +101,16 @@ namespace Mono.TextEditor set; } + public bool UseBom { + get; + set; + } + + public System.Text.Encoding Encoding { + get; + set; + } + internal ILineSplitter Splitter { get { return splitter; @@ -135,11 +155,10 @@ namespace Mono.TextEditor } } - protected TextDocument (IBuffer buffer,ILineSplitter splitter) + protected TextDocument (Rope<char> buffer,ILineSplitter splitter) { this.buffer = buffer; this.splitter = splitter; - splitter.LineChanged += SplitterLineSegmentTreeLineChanged; splitter.LineRemoved += HandleSplitterLineSegmentTreeLineRemoved; foldSegmentTree.tree.NodeRemoved += HandleFoldSegmentTreetreeNodeRemoved; textSegmentMarkerTree.InstallListener (this); @@ -152,7 +171,7 @@ namespace Mono.TextEditor foldedSegments.Remove (e.Node); } - public TextDocument () : this(new GapBuffer (), new LineSplitter ()) + public TextDocument () : this(new Rope<char> (), new LineSplitter ()) { } @@ -163,41 +182,56 @@ namespace Mono.TextEditor public static TextDocument CreateImmutableDocument (string text, bool suppressHighlighting = true) { - return new TextDocument (new StringBuffer (text), new PrimitiveLineSplitter ()) { + return new TextDocument (CharRope.Create (text), new PrimitiveLineSplitter ()) { SuppressHighlightUpdate = suppressHighlighting, Text = text, ReadOnly = true }; } - void SplitterLineSegmentTreeLineChanged (object sender, LineEventArgs e) - { - if (LineChanged != null) - LineChanged (this, e); + public event EventHandler<LineEventArgs> LineChanged { + add { splitter.LineChanged += value; } + remove { splitter.LineChanged -= value; } } - - public event EventHandler<LineEventArgs> LineChanged; - // public event EventHandler<LineEventArgs> LineInserted; - + + public event EventHandler<LineEventArgs> LineInserted { + add { splitter.LineInserted += value; } + remove { splitter.LineInserted -= value; } + } + + public event EventHandler<LineEventArgs> LineRemoved { + add { splitter.LineRemoved += value; } + remove { splitter.LineRemoved -= value; } + } + #region Buffer implementation + public int TextLength { get { - return buffer.TextLength; + return buffer.Length; } } public bool SuppressHighlightUpdate { get; set; } internal DocumentLine longestLineAtTextSet; + WeakReference cachedText; public string Text { get { - return buffer.Text; + string completeText = cachedText != null ? (cachedText.Target as string) : null; + if (completeText == null) { + completeText = buffer.ToString(); + cachedText = new WeakReference(completeText); + } + return completeText; } set { var args = new DocumentChangeEventArgs (0, Text, value); textSegmentMarkerTree.Clear (); OnTextReplacing (args); - buffer.Text = value; + cachedText = null; + buffer.Clear (); + buffer.InsertText (0, value); extendingTextMarkers = new List<TextLineMarker> (); splitter.Initalize (value, out longestLineAtTextSet); ClearFoldSegments (); @@ -255,8 +289,10 @@ namespace Mono.TextEditor } redoStack.Clear (); } - - buffer.Replace (offset, count, value); + cachedText = null; + buffer.RemoveRange(offset, count); + if (!string.IsNullOrEmpty (value)) + buffer.InsertText (offset, value); foldSegmentTree.UpdateOnTextReplace (this, args); splitter.TextReplaced (this, args); versionProvider.AppendChange (args); @@ -274,7 +310,7 @@ namespace Mono.TextEditor if (endOffset > TextLength) throw new ArgumentException ("endOffset > Length"); - return buffer.GetTextAt (startOffset, endOffset - startOffset); + return buffer.ToString (startOffset, endOffset - startOffset); } public string GetTextBetween (DocumentLocation start, DocumentLocation end) @@ -297,7 +333,7 @@ namespace Mono.TextEditor throw new ArgumentException ("count < 0"); if (offset + count > TextLength) throw new ArgumentException ("offset + count is beyond EOF"); - return buffer.GetTextAt (offset, count); + return buffer.ToString (offset, count); } public string GetTextAt (DocumentRegion region) @@ -337,17 +373,17 @@ namespace Mono.TextEditor throw new ArgumentException ("offset < 0"); if (offset >= TextLength) throw new ArgumentException ("offset >= TextLength"); - return buffer.GetCharAt (offset); + return buffer [offset]; } public char GetCharAt (DocumentLocation location) { - return buffer.GetCharAt (LocationToOffset (location)); + return buffer [LocationToOffset (location)]; } public char GetCharAt (int line, int column) { - return buffer.GetCharAt (LocationToOffset (line, column)); + return buffer [LocationToOffset (line, column)]; } /// <summary> @@ -1176,6 +1212,11 @@ namespace Mono.TextEditor return foldSegmentTree.GetSegmentsOverlapping (line.Offset, line.Length).Cast<FoldSegment> (); } + public IEnumerable<FoldSegment> GetFoldingContaining (int offset, int length) + { + return foldSegmentTree.GetSegmentsOverlapping (offset, length).Cast<FoldSegment> (); + } + public IEnumerable<FoldSegment> GetStartFoldings (int lineNumber) { return GetStartFoldings (this.GetLine (lineNumber)); @@ -1188,6 +1229,11 @@ namespace Mono.TextEditor return GetFoldingContaining (line).Where (fold => fold.StartLine == line); } + public IEnumerable<FoldSegment> GetStartFoldings (int offset, int length) + { + return GetFoldingContaining (offset, length).Where (fold => offset <= fold.StartLine.Offset && fold.StartLine.Offset < offset + length); + } + public IEnumerable<FoldSegment> GetEndFoldings (int lineNumber) { return GetStartFoldings (this.GetLine (lineNumber)); @@ -1200,7 +1246,12 @@ namespace Mono.TextEditor yield return segment; } } - + + public IEnumerable<FoldSegment> GetEndFoldings (int offset, int length) + { + return GetFoldingContaining (offset, length).Where (fold => offset <= fold.EndLine.Offset && fold.EndLine.Offset < offset + length); + } + public int GetLineCount (FoldSegment segment) { return segment.EndLine.LineNumber - segment.StartLine.LineNumber; @@ -1656,12 +1707,14 @@ namespace Mono.TextEditor #region Diff + + int[] GetDiffCodes (ref int codeCounter, Dictionary<string, int> codeDictionary, bool includeEol) { int i = 0; var result = new int[LineCount]; foreach (DocumentLine line in Lines) { - string lineText = buffer.GetTextAt (line.Offset, includeEol ? line.LengthIncludingDelimiter : line.Length); + string lineText = buffer.ToString (line.Offset, includeEol ? line.LengthIncludingDelimiter : line.Length); int curCode; if (!codeDictionary.TryGetValue (lineText, out curCode)) { codeDictionary[lineText] = curCode = ++codeCounter; @@ -1854,12 +1907,12 @@ namespace Mono.TextEditor public System.IO.TextReader CreateReader () { - return new BufferedTextReader (buffer); + return new RopeTextReader (buffer); } public System.IO.TextReader CreateReader (int offset, int length) { - throw new NotImplementedException (); + return new RopeTextReader(buffer.GetRange(offset, length)); } string ICSharpCode.NRefactory.Editor.ITextSource.GetText (int offset, int length) @@ -1893,25 +1946,37 @@ namespace Mono.TextEditor } } - public SnapshotDocument (string text, ITextSourceVersion version) : base (new StringBuffer (text), new PrimitiveLineSplitter ()) + public SnapshotDocument (TextDocument doc) : base (doc.buffer.Clone(), new ImmutableLineSplitter (doc.splitter)) { - this.version = version; - Text = text; + this.version = doc.Version; + fileName = doc.fileName; + Encoding = doc.Encoding; + UseBom = doc.UseBom; + mimeType = doc.mimeType; + ReadOnly = true; } } public TextDocument CreateDocumentSnapshot () { - return new SnapshotDocument (Text, Version); + return new SnapshotDocument (this); } - ICSharpCode.NRefactory.Editor.IDocument ICSharpCode.NRefactory.Editor.IDocument.CreateDocumentSnapshot () + public Mono.TextEditor.Utils.Rope<char> CloneRope () { - return new SnapshotDocument (Text, Version); + return buffer.Clone (); } + public Mono.TextEditor.Utils.Rope<char> CloneRope (int offset, int count) + { + return buffer.GetRange (offset, count); + } + ICSharpCode.NRefactory.Editor.IDocument ICSharpCode.NRefactory.Editor.IDocument.CreateDocumentSnapshot () + { + return new SnapshotDocument (this); + } #endregion } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/EditMode.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/EditMode.cs index a6f2acded4..90fcd4a2e8 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/EditMode.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/EditMode.cs @@ -37,10 +37,10 @@ namespace Mono.TextEditor //NOTE: the behaviour of this class is actually stateless; these variables are used to make the API // friendlier for subclassers of this class protected TextEditorData textEditorData; - protected TextEditor editor; + protected MonoTextEditor editor; // string status; - public void InternalHandleKeypress (TextEditor editor, TextEditorData data, Gdk.Key key, + public void InternalHandleKeypress (MonoTextEditor editor, TextEditorData data, Gdk.Key key, uint unicodeChar, Gdk.ModifierType modifier) { this.editor = editor; @@ -53,7 +53,7 @@ namespace Mono.TextEditor this.editor = null; } - internal virtual void InternalSelectionChanged (TextEditor editor, TextEditorData data) + internal virtual void InternalSelectionChanged (MonoTextEditor editor, TextEditorData data) { // only trigger SelectionChanged when event is a result of external stimuli, i.e. when // not already running HandleKeypress @@ -66,7 +66,7 @@ namespace Mono.TextEditor } } - internal void InternalCaretPositionChanged (TextEditor editor, TextEditorData data) + internal void InternalCaretPositionChanged (MonoTextEditor editor, TextEditorData data) { // only trigger CaretPositionChanged when event is a result of external stimuli, i.e. when // not already running HandleKeypress @@ -95,7 +95,7 @@ namespace Mono.TextEditor protected Caret Caret { get { return textEditorData.Caret; } } protected TextDocument Document { get { return textEditorData.Document; } } - protected TextEditor Editor { get { return editor; } } + protected MonoTextEditor Editor { get { return editor; } } protected TextEditorData Data { get { return textEditorData; } } protected abstract void HandleKeypress (Gdk.Key key, uint unicodeKey, Gdk.ModifierType modifier); @@ -266,7 +266,7 @@ namespace Mono.TextEditor } #region TextAreaControl - public virtual void AllocateTextArea (TextEditor textEditor, TextArea textArea, Rectangle allocation) + public virtual void AllocateTextArea (MonoTextEditor textEditor, TextArea textArea, Rectangle allocation) { if (textArea.Allocation != allocation) textArea.SizeAllocate (allocation); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ActionMargin.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ActionMargin.cs index ba069dfc6e..ea054fb966 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ActionMargin.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ActionMargin.cs @@ -30,7 +30,7 @@ namespace Mono.TextEditor { public class ActionMargin : Margin { - readonly TextEditor editor; + readonly MonoTextEditor editor; double marginWidth; public override double Width { @@ -39,7 +39,7 @@ namespace Mono.TextEditor } } - public ActionMargin (TextEditor editor) + public ActionMargin (MonoTextEditor editor) { if (editor == null) throw new ArgumentNullException ("editor"); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentEditorWindow.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentEditorWindow.cs index b461bc6b7a..93115383ac 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentEditorWindow.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentEditorWindow.cs @@ -29,7 +29,7 @@ namespace Mono.TextEditor { public class CodeSegmentEditorWindow : Gtk.Window { - TextEditor codeSegmentEditor = new TextEditor (); + MonoTextEditor codeSegmentEditor = new MonoTextEditor (); public ISyntaxMode SyntaxMode { get { @@ -49,7 +49,7 @@ namespace Mono.TextEditor } } - public CodeSegmentEditorWindow (TextEditor editor) : base (Gtk.WindowType.Toplevel) + public CodeSegmentEditorWindow (MonoTextEditor editor) : base (Gtk.WindowType.Toplevel) { Gtk.ScrolledWindow scrolledWindow = new Gtk.ScrolledWindow (); scrolledWindow.Child = codeSegmentEditor; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentPreviewWindow.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentPreviewWindow.cs index 29a546cb20..424ba054cc 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentPreviewWindow.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/CodeSegmentPreviewWindow.cs @@ -38,7 +38,7 @@ namespace Mono.TextEditor { const int DefaultPreviewWindowWidth = 320; const int DefaultPreviewWindowHeight = 200; - TextEditor editor; + MonoTextEditor editor; Pango.FontDescription fontDescription; Pango.Layout layout; Pango.Layout informLayout; @@ -64,11 +64,11 @@ namespace Mono.TextEditor } } - public CodeSegmentPreviewWindow (TextEditor editor, bool hideCodeSegmentPreviewInformString, TextSegment segment, bool removeIndent = true) : this(editor, hideCodeSegmentPreviewInformString, segment, DefaultPreviewWindowWidth, DefaultPreviewWindowHeight, removeIndent) + public CodeSegmentPreviewWindow (MonoTextEditor editor, bool hideCodeSegmentPreviewInformString, TextSegment segment, bool removeIndent = true) : this(editor, hideCodeSegmentPreviewInformString, segment, DefaultPreviewWindowWidth, DefaultPreviewWindowHeight, removeIndent) { } - public CodeSegmentPreviewWindow (TextEditor editor, bool hideCodeSegmentPreviewInformString, TextSegment segment, int width, int height, bool removeIndent = true) : base (Gtk.WindowType.Popup) + public CodeSegmentPreviewWindow (MonoTextEditor editor, bool hideCodeSegmentPreviewInformString, TextSegment segment, int width, int height, bool removeIndent = true) : base (Gtk.WindowType.Popup) { this.HideCodeSegmentPreviewInformString = hideCodeSegmentPreviewInformString; this.Segment = segment; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/DashedLineMargin.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/DashedLineMargin.cs index 154111bc09..db52b966d9 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/DashedLineMargin.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/DashedLineMargin.cs @@ -30,7 +30,7 @@ namespace Mono.TextEditor { public class DashedLineMargin : Margin { - TextEditor editor; + MonoTextEditor editor; Cairo.Color color; public override double Width { @@ -39,7 +39,7 @@ namespace Mono.TextEditor } } - public DashedLineMargin (TextEditor editor) + public DashedLineMargin (MonoTextEditor editor) { this.editor = editor; } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldMarkerMargin.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldMarkerMargin.cs index 7a87bfe63a..68aa69fb9f 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldMarkerMargin.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldMarkerMargin.cs @@ -35,7 +35,7 @@ namespace Mono.TextEditor { public class FoldMarkerMargin : Margin { - TextEditor editor; + MonoTextEditor editor; DocumentLine lineHover; Pango.Layout layout; @@ -63,7 +63,7 @@ namespace Mono.TextEditor } } - public FoldMarkerMargin (TextEditor editor) + public FoldMarkerMargin (MonoTextEditor editor) { this.editor = editor; layout = PangoUtil.CreateLayout (editor); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldingScreenbackgroundRenderer.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldingScreenbackgroundRenderer.cs index 6c392f8ae7..c1803dbdaf 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldingScreenbackgroundRenderer.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/FoldingScreenbackgroundRenderer.cs @@ -31,7 +31,7 @@ namespace Mono.TextEditor { public class FoldingScreenbackgroundRenderer : IBackgroundRenderer, IDisposable { - TextEditor editor; + MonoTextEditor editor; List<FoldSegment> foldSegments; [Flags] enum Roles @@ -54,7 +54,7 @@ namespace Mono.TextEditor } } - public FoldingScreenbackgroundRenderer (TextEditor editor, IEnumerable<FoldSegment> foldSegments) + public FoldingScreenbackgroundRenderer (MonoTextEditor editor, IEnumerable<FoldSegment> foldSegments) { this.editor = editor; this.foldSegments = new List<FoldSegment> (foldSegments); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GtkWorkarounds.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GtkWorkarounds.cs index 01d2665829..b3dcc07170 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GtkWorkarounds.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GtkWorkarounds.cs @@ -34,7 +34,7 @@ using Gtk; namespace Mono.TextEditor { - public static class GtkWorkarounds + static class GtkWorkarounds { const string LIBOBJC ="/usr/lib/libobjc.dylib"; const string USER32DLL = "User32.dll"; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GutterMargin.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GutterMargin.cs index 1428c3fc0c..8c69267e8b 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GutterMargin.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/GutterMargin.cs @@ -34,13 +34,13 @@ namespace Mono.TextEditor { public class GutterMargin : Margin { - TextEditor editor; + MonoTextEditor editor; int width; int oldLineCountLog10 = -1; double fontHeight; - public GutterMargin (TextEditor editor) + public GutterMargin (MonoTextEditor editor) { this.editor = editor; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/HslColor.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/HslColor.cs index 6f7f446845..f8e525f939 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/HslColor.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/HslColor.cs @@ -30,7 +30,7 @@ using System.Collections.Generic; namespace Mono.TextEditor { - public struct HslColor + struct HslColor { public double H { get; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ITooltipProvider.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ITooltipProvider.cs index cd0cb8d027..6cdc53eccb 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ITooltipProvider.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/ITooltipProvider.cs @@ -50,5 +50,15 @@ namespace Mono.TextEditor this.ItemSegment = new TextSegment (offset, length); this.Item = item; } + + public override bool Equals (object obj) + { + return ItemSegment.Equals (((TooltipItem)obj).ItemSegment); + } + + public override int GetHashCode () + { + return ItemSegment.GetHashCode (); + } } } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/IconMargin.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/IconMargin.cs index 89f264056d..f3ab923632 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/IconMargin.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/IconMargin.cs @@ -33,11 +33,11 @@ namespace Mono.TextEditor { public class IconMargin : Margin { - TextEditor editor; + MonoTextEditor editor; Cairo.Color backgroundColor, separatorColor; const int marginWidth = 22; - public IconMargin (TextEditor editor) + public IconMargin (MonoTextEditor editor) { this.editor = editor; } @@ -136,7 +136,7 @@ namespace Mono.TextEditor public class BookmarkMarginDrawEventArgs : EventArgs { - public TextEditor Editor { + public MonoTextEditor Editor { get; private set; } @@ -166,7 +166,7 @@ namespace Mono.TextEditor private set; } - public BookmarkMarginDrawEventArgs (TextEditor editor, Cairo.Context context, DocumentLine line, int lineNumber, double xPos, double yPos) + public BookmarkMarginDrawEventArgs (MonoTextEditor editor, Cairo.Context context, DocumentLine line, int lineNumber, double xPos, double yPos) { this.Editor = editor; this.Context = context; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/Margin.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/Margin.cs index 9a132f34ee..fdd570e2c3 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/Margin.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/Margin.cs @@ -182,18 +182,18 @@ namespace Mono.TextEditor } } - public TextEditor Editor { + public MonoTextEditor Editor { get; private set; } - public MarginMouseEventArgs (TextEditor editor, Gdk.Event raw, uint button, double x, double y, Gdk.ModifierType modifierState) + public MarginMouseEventArgs (MonoTextEditor editor, Gdk.Event raw, uint button, double x, double y, Gdk.ModifierType modifierState) : this (editor, raw.Type, button, x, y, modifierState) { this.RawEvent = raw; } - public MarginMouseEventArgs (TextEditor editor, Gdk.EventType type, uint button, double x, double y, Gdk.ModifierType modifierState) + public MarginMouseEventArgs (MonoTextEditor editor, Gdk.EventType type, uint button, double x, double y, Gdk.ModifierType modifierState) { this.Editor = editor; this.Type = type; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextEditor.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/MonoTextEditor.cs index 9c6542011c..29c4bde505 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextEditor.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/MonoTextEditor.cs @@ -41,7 +41,7 @@ namespace Mono.TextEditor { [System.ComponentModel.Category("Mono.TextEditor")] [System.ComponentModel.ToolboxItem(true)] - public class TextEditor : Container + public class MonoTextEditor : Container { readonly TextArea textArea; @@ -60,21 +60,21 @@ namespace Mono.TextEditor } } - public TextEditor () : this(new TextDocument ()) + public MonoTextEditor () : this(new TextDocument ()) { } - public TextEditor (TextDocument doc) + public MonoTextEditor (TextDocument doc) : this (doc, null) { } - public TextEditor (TextDocument doc, ITextEditorOptions options) + public MonoTextEditor (TextDocument doc, ITextEditorOptions options) : this (doc, options, new SimpleEditMode ()) { } - public TextEditor (TextDocument doc, ITextEditorOptions options, EditMode initialMode) + public MonoTextEditor (TextDocument doc, ITextEditorOptions options, EditMode initialMode) { GtkWorkarounds.FixContainerLeak (this); WidgetFlags |= WidgetFlags.NoWindow; @@ -376,6 +376,9 @@ namespace Mono.TextEditor get { return textArea.Document; } + set { + textArea.Document = value; + } } public bool IsDisposed { @@ -723,7 +726,16 @@ namespace Mono.TextEditor textArea.SelectionAnchor = value; } } - + + public int SelectionLead { + get { + return textArea.SelectionLead; + } + set { + textArea.SelectionLead = value; + } + } + public IEnumerable<DocumentLine> SelectedLines { get { return textArea.SelectedLines; @@ -1203,6 +1215,8 @@ namespace Mono.TextEditor public double ColumnToX (DocumentLine line, int column) { + if (line == null) + throw new ArgumentNullException ("line"); return TextViewMargin.ColumnToX (line, column); } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextArea.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextArea.cs index ebb0580971..4a2415bfa4 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextArea.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextArea.cs @@ -76,6 +76,9 @@ namespace Mono.TextEditor get { return textEditorData.Document; } + set { + textEditorData.Document = value; + } } public bool IsDisposed { @@ -296,8 +299,8 @@ namespace Mono.TextEditor ResizeMode = ResizeMode.Queue; } - TextEditor editor; - internal void Initialize (TextEditor editor, TextDocument doc, ITextEditorOptions options, EditMode initialMode) + MonoTextEditor editor; + internal void Initialize (MonoTextEditor editor, TextDocument doc, ITextEditorOptions options, EditMode initialMode) { if (doc == null) throw new ArgumentNullException ("doc"); @@ -1479,8 +1482,10 @@ namespace Mono.TextEditor } else { double caretX = ColumnToX (Document.GetLine (p.Line), p.Column); double textWith = Allocation.Width - textViewMargin.XOffset; - if (this.textEditorData.HAdjustment.Value > caretX) { - this.textEditorData.HAdjustment.Value = caretX; + if (caretX < this.textEditorData.HAdjustment.Upper) { + this.textEditorData.HAdjustment.Value = 0; + } else if (this.textEditorData.HAdjustment.Value > caretX) { + this.textEditorData.HAdjustment.Value = System.Math.Max (0, caretX - this.textEditorData.HAdjustment.Upper / 2); } else if (this.textEditorData.HAdjustment.Value + textWith < caretX + TextViewMargin.CharWidth) { double adjustment = System.Math.Max (0, caretX - textWith + TextViewMargin.CharWidth); this.textEditorData.HAdjustment.Value = adjustment; @@ -1953,7 +1958,16 @@ namespace Mono.TextEditor this.textEditorData.SelectionAnchor = value; } } - + + public int SelectionLead { + get { + return this.textEditorData.SelectionLead; + } + set { + this.textEditorData.SelectionLead = value; + } + } + public IEnumerable<DocumentLine> SelectedLines { get { return this.textEditorData.SelectedLines; @@ -2277,7 +2291,7 @@ namespace Mono.TextEditor class CaretPulseAnimation : IAnimationDrawer { - TextEditor editor; + MonoTextEditor editor; public double Percent { get; set; } @@ -2294,7 +2308,7 @@ namespace Mono.TextEditor } } - public CaretPulseAnimation (TextEditor editor) + public CaretPulseAnimation (MonoTextEditor editor) { this.editor = editor; } @@ -2331,7 +2345,7 @@ namespace Mono.TextEditor public class RegionPulseAnimation : IAnimationDrawer { - TextEditor editor; + MonoTextEditor editor; public PulseKind Kind { get; set; } public double Percent { get; set; } @@ -2352,10 +2366,10 @@ namespace Mono.TextEditor } } - public RegionPulseAnimation (TextEditor editor, Gdk.Point position, Gdk.Size size) + public RegionPulseAnimation (MonoTextEditor editor, Gdk.Point position, Gdk.Size size) : this (editor, new Gdk.Rectangle (position, size)) {} - public RegionPulseAnimation (TextEditor editor, Gdk.Rectangle region) + public RegionPulseAnimation (MonoTextEditor editor, Gdk.Rectangle region) { if (region.X < 0 || region.Y < 0 || region.Width < 0 || region.Height < 0) throw new ArgumentException ("region is invalid"); @@ -2478,7 +2492,7 @@ namespace Mono.TextEditor set; } - public SearchHighlightPopupWindow (TextEditor editor) : base (editor) + public SearchHighlightPopupWindow (MonoTextEditor editor) : base (editor) { } @@ -2753,9 +2767,11 @@ namespace Mono.TextEditor tipItem = item; Gtk.Window tw = null; try { - tw = provider.ShowTooltipWindow (editor, nextTipOffset, nextTipModifierState, tipX + (int) TextViewMargin.XOffset, tipY, item); + tw = provider.CreateTooltipWindow (editor, nextTipOffset, nextTipModifierState, item); + if (tw != null) + provider.ShowTooltipWindow (editor, tw, nextTipOffset, nextTipModifierState, tipX + (int) TextViewMargin.XOffset, tipY, item); } catch (Exception e) { - Console.WriteLine ("-------- Exception while creating tooltip:"); + Console.WriteLine ("-------- Exception while creating tooltip: " + provider); Console.WriteLine (e); } if (tw == tipWindow) @@ -2821,9 +2837,12 @@ namespace Mono.TextEditor nextTipOffset = -1; } - void OnDocumentStateChanged (object s, EventArgs a) + void OnDocumentStateChanged (object s, DocumentChangeEventArgs args) { HideTooltip (); + var start = editor.Document.OffsetToLineNumber (args.Offset); + var end = editor.Document.OffsetToLineNumber (args.Offset + args.InsertionLength); + editor.Document.CommitMultipleLineUpdate (start, end); } void OnTextSet (object sender, EventArgs e) @@ -3047,12 +3066,12 @@ namespace Mono.TextEditor class SetCaret { - TextEditor view; + MonoTextEditor view; int line, column; bool highlightCaretLine; bool centerCaret; - public SetCaret (TextEditor view, int line, int column, bool highlightCaretLine, bool centerCaret) + public SetCaret (MonoTextEditor view, int line, int column, bool highlightCaretLine, bool centerCaret) { this.view = view; this.line = line; @@ -3122,12 +3141,12 @@ namespace Mono.TextEditor return Gtk.Widget.GType; } - internal List<TextEditor.EditorContainerChild> containerChildren = new List<TextEditor.EditorContainerChild> (); + internal List<MonoTextEditor.EditorContainerChild> containerChildren = new List<MonoTextEditor.EditorContainerChild> (); public void AddTopLevelWidget (Gtk.Widget widget, int x, int y) { widget.Parent = this; - TextEditor.EditorContainerChild info = new TextEditor.EditorContainerChild (this, widget); + MonoTextEditor.EditorContainerChild info = new MonoTextEditor.EditorContainerChild (this, widget); info.X = x; info.Y = y; containerChildren.Add (info); @@ -3218,7 +3237,7 @@ namespace Mono.TextEditor containerChildren.ForEach (child => child.Child.Unmap ()); } - void ResizeChild (Rectangle allocation, TextEditor.EditorContainerChild child) + void ResizeChild (Rectangle allocation, MonoTextEditor.EditorContainerChild child) { Requisition req = child.Child.SizeRequest (); var childRectangle = new Gdk.Rectangle (child.X, child.Y, req.Width, req.Height); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextViewMargin.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextViewMargin.cs index c055338beb..68441c45cd 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextViewMargin.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TextViewMargin.cs @@ -42,7 +42,7 @@ namespace Mono.TextEditor { public class TextViewMargin : Margin { - readonly TextEditor textEditor; + readonly MonoTextEditor textEditor; Pango.TabArray tabArray; Pango.Layout markerLayout, defaultLayout; Pango.Layout[] eolMarkerLayout; @@ -120,7 +120,7 @@ namespace Mono.TextEditor } - public TextViewMargin (TextEditor textEditor) + public TextViewMargin (MonoTextEditor textEditor) { if (textEditor == null) throw new ArgumentNullException ("textEditor"); @@ -1630,18 +1630,26 @@ namespace Mono.TextEditor TextRenderEndPosition = xPos + width, LineHeight = _lineHeight, - WholeLineWidth = textEditor.Allocation.Width - xPos + WholeLineWidth = textEditor.Allocation.Width - xPos, + + LineYRenderStartPosition = y }; foreach (TextLineMarker marker in line.Markers) { if (!marker.IsVisible) continue; - if (marker.DrawBackground (textEditor, cr, y, metrics)) { + if (marker.DrawBackground (textEditor, cr, metrics)) { isSelectionDrawn |= (marker.Flags & TextLineMarkerFlags.DrawsSelection) == TextLineMarkerFlags.DrawsSelection; } } + foreach (var marker in Document.GetTextSegmentMarkersAt (line).Where (m => m.IsVisible)) { + if (layout.Layout != null) + marker.DrawBackground (textEditor, cr, metrics, offset, offset + length); + } + + if (DecorateLineBg != null) DecorateLineBg (cr, layout, offset, length, xPos, y, selectionStartOffset, selectionEndOffset); @@ -1819,12 +1827,12 @@ namespace Mono.TextEditor } foreach (TextLineMarker marker in line.Markers.Where (m => m.IsVisible)) { if (layout.Layout != null) - marker.Draw (textEditor, cr, y, metrics); + marker.Draw (textEditor, cr, metrics); } foreach (var marker in Document.GetTextSegmentMarkersAt (line).Where (m => m.IsVisible)) { if (layout.Layout != null) - marker.Draw (textEditor, cr, layout.Layout, false, /*selected*/offset, offset + length, y, xPos, xPos + width); + marker.Draw (textEditor, cr, metrics, offset, offset + length); } position += System.Math.Floor (layout.LastLineWidth); @@ -2037,7 +2045,7 @@ namespace Mono.TextEditor textEditor.ClearSelection (); Caret.Location = clickLocation; InSelectionDrag = true; - textEditor.SetSelection (clickLocation, clickLocation); + textEditor.MainSelection = new Selection (clickLocation, clickLocation); } textEditor.RequestResetCaretBlink (); } @@ -2417,6 +2425,8 @@ namespace Mono.TextEditor public static int GetNextTabstop (TextEditorData textEditor, int currentColumn, int tabSize) { + if (tabSize == 0) + return currentColumn; int result = currentColumn - 1 + tabSize; return 1 + (result / tabSize) * tabSize; } @@ -2878,10 +2888,11 @@ namespace Mono.TextEditor var metrics = new EndOfLineMetrics { LineSegment = line, TextRenderEndPosition = TextStartPosition + position, - LineHeight = _lineHeight + LineHeight = _lineHeight, + LineYRenderStartPosition = y }; foreach (var marker in line.Markers) { - marker.DrawAfterEol (textEditor, cr, y, metrics); + marker.DrawAfterEol (textEditor, cr, metrics); } } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TooltipProvider.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TooltipProvider.cs index 20ea1309e9..c3467dcb75 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TooltipProvider.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/Gui/TooltipProvider.cs @@ -29,30 +29,26 @@ namespace Mono.TextEditor { public abstract class TooltipProvider { - public abstract TooltipItem GetItem (TextEditor editor, int offset); + public abstract TooltipItem GetItem (MonoTextEditor editor, int offset); - public virtual bool IsInteractive (TextEditor editor, Gtk.Window tipWindow) + public virtual bool IsInteractive (MonoTextEditor editor, Gtk.Window tipWindow) { return false; } - protected virtual void GetRequiredPosition (TextEditor editor, Gtk.Window tipWindow, out int requiredWidth, out double xalign) + protected virtual void GetRequiredPosition (MonoTextEditor editor, Gtk.Window tipWindow, out int requiredWidth, out double xalign) { requiredWidth = tipWindow.SizeRequest ().Width; xalign = 0.5; } - protected virtual Gtk.Window CreateTooltipWindow (TextEditor editor, int offset, Gdk.ModifierType modifierState, TooltipItem item) + public virtual Gtk.Window CreateTooltipWindow (MonoTextEditor editor, int offset, Gdk.ModifierType modifierState, TooltipItem item) { return null; } - public virtual Gtk.Window ShowTooltipWindow (TextEditor editor, int offset, Gdk.ModifierType modifierState, int mouseX, int mouseY, TooltipItem item) + public virtual Gtk.Window ShowTooltipWindow (MonoTextEditor editor, Gtk.Window tipWindow, int offset, Gdk.ModifierType modifierState, int mouseX, int mouseY, TooltipItem item) { - Gtk.Window tipWindow = CreateTooltipWindow (editor, offset, modifierState, item); - if (tipWindow == null) - return null; - int ox = 0, oy = 0; if (editor.GdkWindow != null) editor.GdkWindow.GetOrigin (out ox, out oy); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/HeightTree.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/HeightTree.cs index 7ea191115a..768a4c1a68 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/HeightTree.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/HeightTree.cs @@ -70,7 +70,9 @@ namespace Mono.TextEditor void HandleLineRemoved (object sender, LineEventArgs e) { - RemoveLine (e.Line.LineNumber); + Rebuild (); + OnLineUpdateFrom (new HeightChangedEventArgs (e.Line.LineNumber - 1)); + //RemoveLine (e.Line.LineNumber); } public void Dispose () diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/HelperMethods.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/HelperMethods.cs index 1b74818031..29c68acd36 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/HelperMethods.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/HelperMethods.cs @@ -34,7 +34,7 @@ using System.Collections.Generic; namespace Mono.TextEditor { - public static class HelperMethods + public static class PublicHelperMethods { public static TextSegment AdjustSegment (this TextSegment segment, DocumentChangeEventArgs args) { @@ -50,7 +50,10 @@ namespace Mono.TextEditor yield return segment.AdjustSegment (args); } } + } + static class HelperMethods + { public static T Kill<T>(this T gc) where T : IDisposable { if (gc != null) diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/ITextEditorOptions.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/ITextEditorOptions.cs index 3c5e8551e3..732f4d2302 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/ITextEditorOptions.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/ITextEditorOptions.cs @@ -29,21 +29,23 @@ using Mono.TextEditor.Highlighting; namespace Mono.TextEditor { - public enum ShowWhitespaces { + public enum ShowWhitespaces + { Never, Selection, Always } [Flags] - public enum IncludeWhitespaces { - None = 0, - Space = 1, - Tab = 2, + public enum IncludeWhitespaces + { + None = 0, + Space = 1, + Tab = 2, LineEndings = 4, - All = Space | Tab | LineEndings + All = Space | Tab | LineEndings } - + public interface ITextEditorOptions : IDisposable { double Zoom { get; set; } @@ -53,11 +55,11 @@ namespace Mono.TextEditor void ZoomIn (); void ZoomOut (); void ZoomReset (); - + string IndentationString { get; } - + IWordFindStrategy WordFindStrategy { get; set; } - + bool AllowTabsAfterNonTabs { get; set; } bool HighlightMatchingBracket { get; set; } bool TabsToSpaces { get; set; } @@ -80,21 +82,23 @@ namespace Mono.TextEditor bool DrawIndentationMarkers { get; set; } bool WrapLines { get; set; } - string FontName { get; set; } - Pango.FontDescription Font { get; } + string FontName { get; set; } + Pango.FontDescription Font { get; } string GutterFontName { get; set; } Pango.FontDescription GutterFont { get; } - - string ColorScheme { get; set; } + + string ColorScheme { get; set; } string DefaultEolMarker { get; set; } ShowWhitespaces ShowWhitespaces { get; set; } IncludeWhitespaces IncludeWhitespaces { get; set; } bool GenerateFormattingUndoStep { get; set; } + event EventHandler ZoomChanged; + ColorScheme GetColorStyle (); - + event EventHandler Changed; } } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/InsertionCursorEditMode.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/InsertionCursorEditMode.cs index b23ade4f51..f403816ab1 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/InsertionCursorEditMode.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/InsertionCursorEditMode.cs @@ -97,9 +97,9 @@ namespace Mono.TextEditor public class HelpWindowEditMode : SimpleEditMode { - protected new TextEditor editor; + protected new MonoTextEditor editor; - public new TextEditor Editor { + public new MonoTextEditor Editor { get { return editor; } @@ -199,7 +199,7 @@ namespace Mono.TextEditor get { return insertionPoints; } } - public InsertionCursorEditMode (TextEditor editor, List<InsertionPoint> insertionPoints) + public InsertionCursorEditMode (MonoTextEditor editor, List<InsertionPoint> insertionPoints) { this.editor = editor; this.insertionPoints = insertionPoints; @@ -324,7 +324,7 @@ namespace Mono.TextEditor public double GetLineIndentationStart () { - TextEditor editor = mode.editor; + MonoTextEditor editor = mode.editor; var lineAbove = editor.Document.GetLine (mode.CurrentInsertionPoint.Line - 1); var lineBelow = editor.Document.GetLine (mode.CurrentInsertionPoint.Line); @@ -356,7 +356,7 @@ namespace Mono.TextEditor public override void Draw (Cairo.Context cr, Cairo.Rectangle erea) { - TextEditor editor = mode.editor; + MonoTextEditor editor = mode.editor; double y = editor.LineToY (mode.CurrentInsertionPoint.Line) - editor.VAdjustment.Value; double x = GetLineIndentationStart (); diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/LineBackgroundMarker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/LineBackgroundMarker.cs index 3fead25185..f74f08ec90 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/LineBackgroundMarker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/LineBackgroundMarker.cs @@ -40,12 +40,12 @@ namespace Mono.TextEditor this.color = color; } - public override bool DrawBackground (TextEditor editor, Cairo.Context cr, double y, LineMetrics metrics) + public override bool DrawBackground (MonoTextEditor editor, Cairo.Context cr, LineMetrics metrics) { if (metrics.SelectionStart > 0) return true; cr.SetSourceColor (color); - cr.Rectangle (metrics.TextRenderStartPosition, y, metrics.TextRenderEndPosition - metrics.TextRenderStartPosition, editor.LineHeight); + cr.Rectangle (metrics.TextRenderStartPosition, metrics.LineYRenderStartPosition, metrics.TextRenderEndPosition - metrics.TextRenderStartPosition, editor.LineHeight); cr.Fill (); return true; } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/MarginMarker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/MarginMarker.cs index c8cd377311..26638df368 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/MarginMarker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/MarginMarker.cs @@ -128,7 +128,7 @@ namespace Mono.TextEditor /// <summary> /// Draws the foreground of the specified margin. /// </summary> - public virtual void DrawForeground (TextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) + public virtual void DrawForeground (MonoTextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) { } @@ -136,7 +136,7 @@ namespace Mono.TextEditor /// Draws the background of the specified margin. /// </summary> /// <returns>true, if the background is drawn. false if the margin should fallback to the default background renderer. </returns> - public virtual bool DrawBackground (TextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) + public virtual bool DrawBackground (MonoTextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) { return false; } @@ -147,7 +147,7 @@ namespace Mono.TextEditor /// <param name="editor">The text editor in which the event press occured.</param> /// <param name="margin">The margin in which the event occured.</param> /// <param name="args">The event arguments.</param> - public virtual void InformMousePress (TextEditor editor, Margin margin, MarginMouseEventArgs args) + public virtual void InformMousePress (MonoTextEditor editor, Margin margin, MarginMouseEventArgs args) { } @@ -157,7 +157,7 @@ namespace Mono.TextEditor /// <param name="editor">The text editor in which the event press occured.</param> /// <param name="margin">The margin in which the event occured.</param> /// <param name="args">The event arguments.</param> - public virtual void InformMouseRelease (TextEditor editor, Margin margin, MarginMouseEventArgs args) + public virtual void InformMouseRelease (MonoTextEditor editor, Margin margin, MarginMouseEventArgs args) { } @@ -167,7 +167,7 @@ namespace Mono.TextEditor /// <param name="editor">The text editor in which the event press occured.</param> /// <param name="margin">The margin in which the event occured.</param> /// <param name="args">The event arguments.</param> - public virtual void InformMouseHover (TextEditor editor, Margin margin, MarginMouseEventArgs args) + public virtual void InformMouseHover (MonoTextEditor editor, Margin margin, MarginMouseEventArgs args) { } } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorData.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorData.cs index a9c9e75adc..599ba88014 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorData.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorData.cs @@ -95,7 +95,7 @@ namespace Mono.TextEditor /// </summary> public event EventHandler<EditModeChangedEventArgs> EditModeChanged; - public TextEditor Parent { + public MonoTextEditor Parent { get; set; } @@ -207,24 +207,28 @@ namespace Mono.TextEditor caret.PositionChanged += CaretPositionChanged; options = TextEditorOptions.DefaultOptions; - document = doc; + AttachDocument (); + SearchEngine = new BasicSearchEngine (); + + HeightTree = new HeightTree (this); + HeightTree.Rebuild (); + IndentationTracker = new DefaultIndentationTracker (document); + } + + void AttachDocument () + { + if (document == null) + return; document.BeginUndo += OnBeginUndo; document.EndUndo += OnEndUndo; - document.Undone += DocumentHandleUndone; document.Redone += DocumentHandleRedone; document.LineChanged += HandleDocLineChanged; document.TextReplaced += HandleTextReplaced; - document.TextSet += HandleDocTextSet; document.Folded += HandleTextEditorDataDocumentFolded; document.FoldTreeUpdated += HandleFoldTreeUpdated; - SearchEngine = new BasicSearchEngine (); - - HeightTree = new HeightTree (this); - HeightTree.Rebuild (); - IndentationTracker = new DefaultIndentationTracker (document); } void HandleFoldTreeUpdated (object sender, EventArgs e) @@ -267,6 +271,12 @@ namespace Mono.TextEditor get { return document; } + set { + DetachDocument (); + document = value; + this.caret.SetDocument (document); + AttachDocument (); + } } void HandleTextReplaced (object sender, DocumentChangeEventArgs e) @@ -805,6 +815,26 @@ namespace Mono.TextEditor } } + public int SelectionLead { + get { + if (MainSelection.IsEmpty) + return -1; + return MainSelection.GetLeadOffset (this); + } + set { + DocumentLocation location = Document.OffsetToLocation (value); + if (mainSelection.IsEmpty) { + MainSelection = new Selection (location, location); + } else { + if (MainSelection.Anchor == location) { + MainSelection = MainSelection.WithAnchor (MainSelection.Lead); + } else { + MainSelection = MainSelection.WithLead (location); + } + } + } + } + /// <summary> /// Gets or sets the selection range. If nothing is selected (Caret.Offset, 0) is returned. /// </summary> @@ -888,6 +918,10 @@ namespace Mono.TextEditor public void SetSelection (int anchorOffset, int leadOffset) { + if (anchorOffset == leadOffset) { + MainSelection = Selection.Empty; + return; + } var anchor = document.OffsetToLocation (anchorOffset); var lead = document.OffsetToLocation (leadOffset); MainSelection = new Selection (anchor, lead); @@ -895,7 +929,7 @@ namespace Mono.TextEditor public void SetSelection (DocumentLocation anchor, DocumentLocation lead) { - MainSelection = new Selection (anchor, lead); + MainSelection = anchor == lead ? Selection.Empty : new Selection (anchor, lead); } public void SetSelection (int anchorLine, int anchorColumn, int leadLine, int leadColumn) diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorOptions.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorOptions.cs index 3028e70c6d..2721756861 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorOptions.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextEditorOptions.cs @@ -65,10 +65,11 @@ namespace Mono.TextEditor string colorStyle = DefaultColorStyle; Pango.FontDescription font, gutterFont; - double zoom = 1d; IWordFindStrategy wordFindStrategy = new EmacsWordFindStrategy (true); #region Zoom + static double zoom = 1d; + static event EventHandler StaticZoomChanged; const double ZOOM_FACTOR = 1.1f; const int ZOOM_MIN_POW = -4; @@ -78,7 +79,7 @@ namespace Mono.TextEditor public double Zoom { get { - return zoom; + return zoom; } set { value = System.Math.Min (ZOOM_MAX, System.Math.Max (ZOOM_MIN, value)); @@ -90,39 +91,39 @@ namespace Mono.TextEditor } if (zoom != value) { zoom = value; - DisposeFont (); - OnChanged (EventArgs.Empty); + StaticZoomChanged?.Invoke (this, EventArgs.Empty); } } } - + public event EventHandler ZoomChanged { add { StaticZoomChanged += value; } remove { StaticZoomChanged -= value; } } + public bool CanZoomIn { get { - return zoom < ZOOM_MAX - 0.000001d; + return Zoom < ZOOM_MAX - 0.000001d; } } public bool CanZoomOut { get { - return zoom > ZOOM_MIN + 0.000001d; + return Zoom > ZOOM_MIN + 0.000001d; } } public bool CanResetZoom { get { - return zoom != 1d; + return Zoom != 1d; } } public void ZoomIn () { - int oldPow = (int)System.Math.Round (System.Math.Log (zoom) / System.Math.Log (ZOOM_FACTOR)); + int oldPow = (int)System.Math.Round (System.Math.Log (Zoom) / System.Math.Log (ZOOM_FACTOR)); Zoom = System.Math.Pow (ZOOM_FACTOR, oldPow + 1); } public void ZoomOut () { - int oldPow = (int)System.Math.Round (System.Math.Log (zoom) / System.Math.Log (ZOOM_FACTOR)); + int oldPow = (int)System.Math.Round (System.Math.Log (Zoom) / System.Math.Log (ZOOM_FACTOR)); Zoom = System.Math.Pow (ZOOM_FACTOR, oldPow - 1); } @@ -345,7 +346,7 @@ namespace Mono.TextEditor } } - void DisposeFont () + protected void DisposeFont () { if (font != null) { font.Dispose (); @@ -543,7 +544,7 @@ namespace Mono.TextEditor public virtual void CopyFrom (TextEditorOptions other) { - zoom = other.zoom; + Zoom = other.Zoom; highlightMatchingBracket = other.highlightMatchingBracket; tabsToSpaces = other.tabsToSpaces; indentationSize = other.indentationSize; @@ -568,11 +569,23 @@ namespace Mono.TextEditor DisposeFont (); OnChanged (EventArgs.Empty); } - + + public TextEditorOptions () + { + StaticZoomChanged += HandleStaticZoomChanged; + } + public virtual void Dispose () { + StaticZoomChanged -= HandleStaticZoomChanged; } - + + void HandleStaticZoomChanged (object sender, EventArgs e) + { + DisposeFont (); + OnChanged (EventArgs.Empty); + } + protected void OnChanged (EventArgs args) { if (Changed != null) diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLineMarker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLineMarker.cs index 90f5d5fc52..a8eb35b235 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLineMarker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLineMarker.cs @@ -31,8 +31,8 @@ namespace Mono.TextEditor { public interface IExtendingTextLineMarker { - double GetLineHeight (TextEditor editor); - void Draw (TextEditor editor, Cairo.Context cr, int lineNr, Cairo.Rectangle lineArea); + double GetLineHeight (MonoTextEditor editor); + void Draw (MonoTextEditor editor, Cairo.Context cr, int lineNr, Cairo.Rectangle lineArea); } public interface IActionTextLineMarker @@ -40,9 +40,9 @@ namespace Mono.TextEditor /// <returns> /// true, if the mouse press was handled - false otherwise. /// </returns> - bool MousePressed (TextEditor editor, MarginMouseEventArgs args); + bool MousePressed (MonoTextEditor editor, MarginMouseEventArgs args); - void MouseHover (TextEditor editor, MarginMouseEventArgs args, TextLineMarkerHoverResult result); + void MouseHover (MonoTextEditor editor, MarginMouseEventArgs args, TextLineMarkerHoverResult result); } public class TextLineMarkerHoverResult @@ -90,6 +90,8 @@ namespace Mono.TextEditor public double LineHeight { get; internal set; } public double WholeLineWidth { get; internal set; } + + public double LineYRenderStartPosition { get; internal set; } } public class EndOfLineMetrics @@ -97,6 +99,7 @@ namespace Mono.TextEditor public DocumentLine LineSegment { get; internal set; } public double TextRenderEndPosition { get; internal set; } public double LineHeight { get; internal set; } + public double LineYRenderStartPosition { get; internal set; } } public class TextLineMarker @@ -116,7 +119,11 @@ namespace Mono.TextEditor get; set; } - + + public object Tag { + get; + set; + } bool isVisible = true; public virtual bool IsVisible { @@ -128,7 +135,7 @@ namespace Mono.TextEditor { } - public virtual void Draw (TextEditor editor, Cairo.Context cr, double y, LineMetrics metrics) + public virtual void Draw (MonoTextEditor editor, Cairo.Context cr, LineMetrics metrics) { } @@ -145,7 +152,7 @@ namespace Mono.TextEditor /// <param name="cr">The cairo context.</param> /// <param name="y">The y coordinate.</param> /// <param name="metrics">The line metrics.</param> - public virtual bool DrawBackground (TextEditor editor, Cairo.Context cr, double y, LineMetrics metrics) + public virtual bool DrawBackground (MonoTextEditor editor, Cairo.Context cr, LineMetrics metrics) { return false; } @@ -153,7 +160,7 @@ namespace Mono.TextEditor /// <summary> /// Is used to draw in the area after the visible text. /// </summary> - public virtual void DrawAfterEol (TextEditor textEditor, Cairo.Context cr, double y, EndOfLineMetrics lineHeight) + public virtual void DrawAfterEol (MonoTextEditor textEditor, Cairo.Context cr, EndOfLineMetrics metrics) { } } diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLinkEditMode.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLinkEditMode.cs index 16415127e2..017517c7ef 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLinkEditMode.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextLinkEditMode.cs @@ -188,7 +188,7 @@ namespace Mono.TextEditor TextLinkTooltipProvider tooltipProvider; - public TextLinkEditMode (TextEditor editor, int baseOffset, List<TextLink> links) + public TextLinkEditMode (MonoTextEditor editor, int baseOffset, List<TextLink> links) { this.editor = editor; this.links = links; @@ -547,7 +547,7 @@ namespace Mono.TextEditor this.mode = mode; } #region ITooltipProvider implementation - public override TooltipItem GetItem (TextEditor Editor, int offset) + public override TooltipItem GetItem (MonoTextEditor Editor, int offset) { int o = offset - mode.BaseOffset; for (int i = 0; i < mode.Links.Count; i++) { @@ -559,7 +559,7 @@ namespace Mono.TextEditor //return mode.Links.First (l => l.PrimaryLink != null && l.PrimaryLink.Offset <= o && o <= l.PrimaryLink.EndOffset); } - protected override Gtk.Window CreateTooltipWindow (TextEditor Editor, int offset, Gdk.ModifierType modifierState, TooltipItem item) + public override Gtk.Window CreateTooltipWindow (MonoTextEditor Editor, int offset, Gdk.ModifierType modifierState, TooltipItem item) { TextLink link = item.Item as TextLink; if (link == null || string.IsNullOrEmpty (link.Tooltip)) @@ -570,7 +570,7 @@ namespace Mono.TextEditor return window; } - protected override void GetRequiredPosition (TextEditor Editor, Gtk.Window tipWindow, out int requiredWidth, out double xalign) + protected override void GetRequiredPosition (MonoTextEditor Editor, Gtk.Window tipWindow, out int requiredWidth, out double xalign) { TooltipWindow win = (TooltipWindow)tipWindow; requiredWidth = win.SetMaxWidth (win.Screen.Width); @@ -594,7 +594,7 @@ namespace Mono.TextEditor IsVisible = true; } - public override bool DrawBackground (TextEditor editor, Cairo.Context cr, double y, LineMetrics metrics) + public override bool DrawBackground (MonoTextEditor editor, Cairo.Context cr, LineMetrics metrics) { int caretOffset = editor.Caret.Offset - BaseOffset; @@ -625,7 +625,7 @@ namespace Mono.TextEditor double x1 = metrics.TextRenderStartPosition + x_pos - 1; double x2 = metrics.TextRenderStartPosition + x_pos2 - 1 + 0.5; - cr.Rectangle (x1 + 0.5, y + 0.5, x2 - x1, editor.LineHeight - 1); + cr.Rectangle (x1 + 0.5, metrics.LineYRenderStartPosition + 0.5, x2 - x1, editor.LineHeight - 1); cr.SetSourceColor (fillGc); cr.FillPreserve (); @@ -649,7 +649,7 @@ namespace Mono.TextEditor return margin is GutterMargin; } - public override bool DrawBackground (TextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) + public override bool DrawBackground (MonoTextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) { var width = metrics.Width; @@ -661,7 +661,7 @@ namespace Mono.TextEditor return true; } - public override void DrawForeground (TextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) + public override void DrawForeground (MonoTextEditor editor, Cairo.Context cr, MarginDrawMetrics metrics) { var width = metrics.Width; var lineNumberBgGC = editor.ColorStyle.LineNumbers.Background; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextSegmentMarker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextSegmentMarker.cs index cb2c35d223..e76300b91a 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/TextSegmentMarker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/TextSegmentMarker.cs @@ -53,7 +53,11 @@ namespace Mono.TextEditor { } - public virtual void Draw (TextEditor editor, Cairo.Context cr, Pango.Layout layout, bool selected, int startOffset, int endOffset, double y, double startXPos, double endXPos) + public virtual void Draw (MonoTextEditor editor, Context cr, LineMetrics metrics, int startOffset, int endOffset) + { + } + + public virtual void DrawBackground (MonoTextEditor editor, Context cr, LineMetrics metrics, int startOffset, int endOffset) { } @@ -67,7 +71,7 @@ namespace Mono.TextEditor { void TransformChunks (List<Chunk> chunks); - void ChangeForeColor (TextEditor editor, Chunk chunk, ref Cairo.Color color); + void ChangeForeColor (MonoTextEditor editor, Chunk chunk, ref Cairo.Color color); } public class UnderlineTextSegmentMarker : TextSegmentMarker @@ -88,14 +92,16 @@ namespace Mono.TextEditor public Cairo.Color Color { get; set; } public bool Wave { get; set; } - public override void Draw (TextEditor editor, Cairo.Context cr, Pango.Layout layout, bool selected, int startOffset, int endOffset, double y, double startXPos, double endXPos) + public override void Draw (MonoTextEditor editor, Cairo.Context cr, LineMetrics metrics, int startOffset, int endOffset) { int markerStart = Segment.Offset; int markerEnd = Segment.EndOffset; if (markerEnd < startOffset || markerStart > endOffset) return; - - + var layout = metrics.Layout.Layout; + double startXPos = metrics.TextRenderStartPosition; + double endXPos = metrics.TextRenderEndPosition; + double y = metrics.LineYRenderStartPosition; if (editor.IsSomethingSelected) { var range = editor.SelectionRange; if (range.Contains (markerStart)) { @@ -121,7 +127,7 @@ namespace Mono.TextEditor InternalDraw (markerStart, markerEnd, editor, cr, layout, false, startOffset, endOffset, y, startXPos, endXPos); } - void InternalDraw (int markerStart, int markerEnd, TextEditor editor, Cairo.Context cr, Pango.Layout layout, bool selected, int startOffset, int endOffset, double y, double startXPos, double endXPos) + void InternalDraw (int markerStart, int markerEnd, MonoTextEditor editor, Cairo.Context cr, Pango.Layout layout, bool selected, int startOffset, int endOffset, double y, double startXPos, double endXPos) { if (markerStart >= markerEnd) return; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/UnderlineMarker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/UnderlineMarker.cs index 0e8f4240d2..06698c143b 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/UnderlineMarker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/UnderlineMarker.cs @@ -57,12 +57,13 @@ namespace Mono.TextEditor public int EndCol { get; set; } public bool Wave { get; set; } - public override void Draw (TextEditor editor, Cairo.Context cr, double y, LineMetrics metrics) + public override void Draw (MonoTextEditor editor, Cairo.Context cr, LineMetrics metrics) { var startOffset = metrics.TextStartOffset; int endOffset = metrics.TextEndOffset; double startXPos = metrics.TextRenderStartPosition; double endXPos = metrics.TextRenderEndPosition; + double y = metrics.LineYRenderStartPosition; var layout = metrics.Layout.Layout; int markerStart = LineSegment.Offset + System.Math.Max (StartCol - 1, 0); @@ -95,7 +96,7 @@ namespace Mono.TextEditor InternalDraw (markerStart, markerEnd, editor, cr, layout, false, startOffset, endOffset, y, startXPos, endXPos); } - void InternalDraw (int markerStart, int markerEnd, TextEditor editor, Cairo.Context cr, Pango.Layout layout, bool selected, int startOffset, int endOffset, double y, double startXPos, double endXPos) + void InternalDraw (int markerStart, int markerEnd, MonoTextEditor editor, Cairo.Context cr, Pango.Layout layout, bool selected, int startOffset, int endOffset, double y, double startXPos, double endXPos) { if (markerStart >= markerEnd) return; diff --git a/main/src/core/Mono.Texteditor/Mono.TextEditor/UrlMarker.cs b/main/src/core/Mono.Texteditor/Mono.TextEditor/UrlMarker.cs index 88469244a8..681fee2e66 100644 --- a/main/src/core/Mono.Texteditor/Mono.TextEditor/UrlMarker.cs +++ b/main/src/core/Mono.Texteditor/Mono.TextEditor/UrlMarker.cs @@ -100,12 +100,13 @@ namespace Mono.TextEditor line = null; } - public override void Draw (TextEditor editor, Cairo.Context cr, double y, LineMetrics metrics) + public override void Draw (MonoTextEditor editor, Cairo.Context cr, LineMetrics metrics) { var startOffset = metrics.TextStartOffset; int endOffset = metrics.TextEndOffset; double startXPos = metrics.TextRenderStartPosition; double endXPos = metrics.TextRenderEndPosition; + double y = metrics.LineYRenderStartPosition; var layout = metrics.Layout.Layout; int markerStart = line.Offset + startColumn; int markerEnd = line.Offset + endColumn; |