diff --git a/src/TextMateSharp.Benchmarks/BigFileTokenizationBenchmark.cs b/src/TextMateSharp.Benchmarks/BigFileTokenizationBenchmark.cs index 6a0748d..83be144 100644 --- a/src/TextMateSharp.Benchmarks/BigFileTokenizationBenchmark.cs +++ b/src/TextMateSharp.Benchmarks/BigFileTokenizationBenchmark.cs @@ -1,13 +1,18 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using Perfolizer.Metrology; using System; using System.Collections.Generic; +using System.Globalization; using System.IO; - -using BenchmarkDotNet.Attributes; - using TextMateSharp.Grammars; namespace TextMateSharp.Benchmarks { + [Config(typeof(CustomBenchmarksConfig))] [MemoryDiagnoser] public class BigFileTokenizationBenchmark { @@ -20,10 +25,10 @@ public void Setup() // Walk up directories to find the solution root string? dir = AppDomain.CurrentDomain.BaseDirectory; string bigFilePath = ""; - + while (dir != null) { - string candidate = Path.Combine(dir, "src", "TextMateSharp.Demo", + string candidate = Path.Combine(dir, "src", "TextMateSharp.Demo", "testdata", "samplefiles", "bigfile.cs"); if (File.Exists(candidate)) { @@ -42,13 +47,13 @@ public void Setup() // Load the file into memory _content = File.ReadAllText(bigFilePath); - Console.WriteLine($"Loaded bigfile.cs"); + Console.WriteLine("Loaded bigfile.cs"); // Load the C# grammar RegistryOptions options = new RegistryOptions(ThemeName.DarkPlus); Registry.Registry registry = new Registry.Registry(options); _grammar = registry.LoadGrammar("source.cs"); - + if (_grammar == null) { throw new InvalidOperationException("Failed to load C# grammar"); @@ -94,5 +99,63 @@ public int TokenizeAllLines() yield return (lineStart, content.Length - lineStart); } } + + #region helper classes for benchmarks + + public sealed class CustomBenchmarksConfig : ManualConfig + { + public CustomBenchmarksConfig() + { + // Use the default summary style with size unit in kilobytes. + // We have a separate column to measure in bytes so we can measure even small differences in memory usage. + SummaryStyle = SummaryStyle.Default + .WithSizeUnit(SizeUnit.KB) + .WithCultureInfo(CultureInfo.CurrentCulture); + + AddColumn(new AllocatedBytesColumn()); + } + } + + public sealed class AllocatedBytesColumn : IColumn + { + public string Id => nameof(AllocatedBytesColumn); + + public string ColumnName => "Allocated B"; + + public bool AlwaysShow => true; + + public ColumnCategory Category => ColumnCategory.Custom; + + public int PriorityInCategory => 0; + + public bool IsNumeric => true; + + public UnitType UnitType => UnitType.Dimensionless; + + public string Legend => "Bytes allocated per operation"; + + public bool IsAvailable(Summary summary) => true; + + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + { + BenchmarkReport? report = summary[benchmarkCase]; + long? bytesAllocatedPerOperation = report?.GcStats.GetBytesAllocatedPerOperation(benchmarkCase); + if (!bytesAllocatedPerOperation.HasValue) + { + return "NA"; + } + + return bytesAllocatedPerOperation.Value.ToString("N0", style.CultureInfo); + } + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + => GetValue(summary, benchmarkCase, summary.Style); + + public override string ToString() => ColumnName; + } + + #endregion helper classes for benchmarks } } diff --git a/src/TextMateSharp.Grammars/GrammarNames.cs b/src/TextMateSharp.Grammars/GrammarNames.cs index b6ba7db..664cd2b 100644 --- a/src/TextMateSharp.Grammars/GrammarNames.cs +++ b/src/TextMateSharp.Grammars/GrammarNames.cs @@ -1,8 +1,8 @@ namespace TextMateSharp.Grammars { - internal class GrammarNames + internal static class GrammarNames { - internal static string[] SupportedGrammars = new string[] { + internal static readonly string[] SupportedGrammars = new string[] { "Asciidoc", "Bat", "Clojure", diff --git a/src/TextMateSharp.Grammars/Properties/AssemblyInfo.cs b/src/TextMateSharp.Grammars/Properties/AssemblyInfo.cs index c4511fe..278d7d3 100644 --- a/src/TextMateSharp.Grammars/Properties/AssemblyInfo.cs +++ b/src/TextMateSharp.Grammars/Properties/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("TextMateSharp.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001000db16f8de24159e7ee94e32addce2a9b60f3ea5be200ae7b5abbf8676705064a1b5a5a44d570a884bd86bd2d3e83411fb88914e00028bc7d4b5be1ba8fd8db4335e3ad911d0ef7e694cf433f3314e991100c72c7473641a9e3437deeab402c8f4a03fdf9c174cbae00142a28ce43475ca61f0016ede73dc778b5ed5a0344cfc2")] \ No newline at end of file +[assembly: InternalsVisibleTo("TextMateSharp.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001000db16f8de24159e7ee94e32addce2a9b60f3ea5be200ae7b5abbf8676705064a1b5a5a44d570a884bd86bd2d3e83411fb88914e00028bc7d4b5be1ba8fd8db4335e3ad911d0ef7e694cf433f3314e991100c72c7473641a9e3437deeab402c8f4a03fdf9c174cbae00142a28ce43475ca61f0016ede73dc778b5ed5a0344cfc2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/src/TextMateSharp/Internal/Grammars/AttributedScopeStack.cs b/src/TextMateSharp/Internal/Grammars/AttributedScopeStack.cs index 9b1f598..11d11b2 100644 --- a/src/TextMateSharp/Internal/Grammars/AttributedScopeStack.cs +++ b/src/TextMateSharp/Internal/Grammars/AttributedScopeStack.cs @@ -1,21 +1,20 @@ using System; using System.Collections.Generic; - using TextMateSharp.Themes; namespace TextMateSharp.Internal.Grammars { - public class AttributedScopeStack : IEquatable + internal sealed class AttributedScopeStack : IEquatable { - public AttributedScopeStack Parent { get; private set; } - public string ScopePath { get; private set; } - public int TokenAttributes { get; private set; } + internal AttributedScopeStack Parent { get; private set; } + internal string ScopePath { get; private set; } + internal int TokenAttributes { get; private set; } private List _cachedScopeNames; // Precomputed, per-node hash code (persistent structure => safe as long as instances are immutable) private readonly int _hashCode; - public AttributedScopeStack(AttributedScopeStack parent, string scopePath, int tokenAttributes) + internal AttributedScopeStack(AttributedScopeStack parent, string scopePath, int tokenAttributes) { Parent = parent; ScopePath = scopePath; diff --git a/src/TextMateSharp/Internal/Grammars/BalancedBracketSelectors.cs b/src/TextMateSharp/Internal/Grammars/BalancedBracketSelectors.cs index ecc1fb2..0061975 100644 --- a/src/TextMateSharp/Internal/Grammars/BalancedBracketSelectors.cs +++ b/src/TextMateSharp/Internal/Grammars/BalancedBracketSelectors.cs @@ -3,10 +3,10 @@ namespace TextMateSharp.Internal.Grammars { - public class BalancedBracketSelectors + public sealed class BalancedBracketSelectors { - private Predicate>[] _balancedBracketScopes; - private Predicate>[] _unbalancedBracketScopes; + private readonly Predicate>[] _balancedBracketScopes; + private readonly Predicate>[] _unbalancedBracketScopes; private bool _allowAny = false; @@ -18,17 +18,17 @@ public BalancedBracketSelectors( _unbalancedBracketScopes = CreateUnbalancedBracketScopes(unbalancedBracketScopes); } - public bool MatchesAlways() + internal bool MatchesAlways() { return _allowAny && _unbalancedBracketScopes.Length == 0; } - public bool MatchesNever() + internal bool MatchesNever() { return !_allowAny && _balancedBracketScopes.Length == 0; } - public bool Match(List scopes) + internal bool Match(List scopes) { foreach (var excluder in _unbalancedBracketScopes) { diff --git a/src/TextMateSharp/Internal/Grammars/BasicScopeAttributes.cs b/src/TextMateSharp/Internal/Grammars/BasicScopeAttributes.cs index 05939b3..25a7e17 100644 --- a/src/TextMateSharp/Internal/Grammars/BasicScopeAttributes.cs +++ b/src/TextMateSharp/Internal/Grammars/BasicScopeAttributes.cs @@ -4,13 +4,13 @@ namespace TextMateSharp.Internal.Grammars { - public class BasicScopeAttributes + internal sealed class BasicScopeAttributes { - public int LanguageId { get; private set; } - public int TokenType { get; private set; } /* OptionalStandardTokenType */ - public List ThemeData { get; private set; } + internal int LanguageId { get; private set; } + internal int TokenType { get; private set; } /* OptionalStandardTokenType */ + internal List ThemeData { get; private set; } - public BasicScopeAttributes( + internal BasicScopeAttributes( int languageId, int tokenType, List themeData) diff --git a/src/TextMateSharp/Internal/Grammars/BasicScopeAttributesProvider.cs b/src/TextMateSharp/Internal/Grammars/BasicScopeAttributesProvider.cs index e6bed5b..a3e91de 100644 --- a/src/TextMateSharp/Internal/Grammars/BasicScopeAttributesProvider.cs +++ b/src/TextMateSharp/Internal/Grammars/BasicScopeAttributesProvider.cs @@ -7,21 +7,21 @@ namespace TextMateSharp.Internal.Grammars { - public class BasicScopeAttributesProvider + internal sealed class BasicScopeAttributesProvider { private static BasicScopeAttributes _NULL_SCOPE_METADATA = new BasicScopeAttributes(0, 0, null); private static Regex STANDARD_TOKEN_TYPE_REGEXP = new Regex("\\b(comment|string|regex|meta\\.embedded)\\b"); - private int _initialLanguage; - private IThemeProvider _themeProvider; - private Dictionary _cache = new Dictionary(); + private readonly int _initialLanguage; + private readonly IThemeProvider _themeProvider; + private readonly Dictionary _cache = new Dictionary(); private BasicScopeAttributes _defaultAttributes; - private Dictionary _embeddedLanguages; - private Regex _embeddedLanguagesRegex; + private readonly Dictionary _embeddedLanguages; + private readonly Regex _embeddedLanguagesRegex; - public BasicScopeAttributesProvider(int initialLanguage, IThemeProvider themeProvider, + internal BasicScopeAttributesProvider(int initialLanguage, IThemeProvider themeProvider, Dictionary embeddedLanguages) { this._initialLanguage = initialLanguage; @@ -58,7 +58,7 @@ public BasicScopeAttributesProvider(int initialLanguage, IThemeProvider themePro reversedScopes.Reverse(); this._embeddedLanguagesRegex = new Regex( "^((" + - string.Join(")|(", escapedScopes) + + string.Join(")|(", reversedScopes) + "))($|\\.)"); } } @@ -72,12 +72,12 @@ public void OnDidChangeTheme() new List() { this._themeProvider.GetDefaults() }); } - public BasicScopeAttributes GetDefaultAttributes() + internal BasicScopeAttributes GetDefaultAttributes() { return this._defaultAttributes; } - public BasicScopeAttributes GetBasicScopeAttributes(string scopeName) + internal BasicScopeAttributes GetBasicScopeAttributes(string scopeName) { if (scopeName == null) { diff --git a/src/TextMateSharp/Internal/Grammars/LineTokenizer.cs b/src/TextMateSharp/Internal/Grammars/LineTokenizer.cs index 21d575c..16e2dde 100644 --- a/src/TextMateSharp/Internal/Grammars/LineTokenizer.cs +++ b/src/TextMateSharp/Internal/Grammars/LineTokenizer.cs @@ -203,7 +203,7 @@ private void ScanNext() if (beginWhileRule.WhileHasBackReferences) { _stack = _stack.WithEndRule( - beginWhileRule.getWhileWithResolvedBackReferences(_lineText, captureIndices)); + beginWhileRule.GetWhileWithResolvedBackReferences(_lineText, captureIndices)); } if (!hasAdvanced && beforePush.HasSameRuleAs(_stack)) diff --git a/src/TextMateSharp/Internal/Matcher/IMatchesName.cs b/src/TextMateSharp/Internal/Matcher/IMatchesName.cs index 3c36bfb..a7fd4ff 100644 --- a/src/TextMateSharp/Internal/Matcher/IMatchesName.cs +++ b/src/TextMateSharp/Internal/Matcher/IMatchesName.cs @@ -9,7 +9,7 @@ public interface IMatchesName bool Match(ICollection names, T scopes); } - public class NameMatcher : IMatchesName> + public sealed class NameMatcher : IMatchesName> { public static IMatchesName> Default = new NameMatcher(); diff --git a/src/TextMateSharp/Internal/Parser/PList.cs b/src/TextMateSharp/Internal/Parser/PList.cs index 606954f..62b39b9 100644 --- a/src/TextMateSharp/Internal/Parser/PList.cs +++ b/src/TextMateSharp/Internal/Parser/PList.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Text; @@ -55,7 +54,7 @@ private PListObject Create(PListObject parent, bool valueAsArray) public void EndElement(string tagName) { object value = null; - string text = this.text.ToString(); + string t = this.text.ToString(); if ("key".Equals(tagName)) { if (currObject == null || currObject.IsValueAsArray()) @@ -63,7 +62,7 @@ public void EndElement(string tagName) errors.Add("key can only be used inside an open dict element"); return; } - currObject.SetLastKey(text); + currObject.SetLastKey(t); return; } else if ("dict".Equals(tagName) || "array".Equals(tagName)) @@ -78,7 +77,7 @@ public void EndElement(string tagName) } else if ("string".Equals(tagName) || "data".Equals(tagName)) { - value = text; + value = t; } else if ("date".Equals(tagName)) { @@ -86,27 +85,21 @@ public void EndElement(string tagName) } else if ("integer".Equals(tagName)) { - try + if (!int.TryParse(t, out int i)) { - value = int.Parse(text); - } - catch (Exception) - { - errors.Add(text + " is not a integer"); + errors.Add(t + " is not an integer"); return; } + value = i; } else if ("real".Equals(tagName)) { - try - { - value = float.Parse(text); - } - catch (Exception) + if (!float.TryParse(t, out float f)) { - errors.Add(text + " is not a float"); + errors.Add(t + " is not a float"); return; } + value = f; } else if ("true".Equals(tagName)) { diff --git a/src/TextMateSharp/Internal/Rules/BeginEndRule.cs b/src/TextMateSharp/Internal/Rules/BeginEndRule.cs index 38d0259..b790171 100644 --- a/src/TextMateSharp/Internal/Rules/BeginEndRule.cs +++ b/src/TextMateSharp/Internal/Rules/BeginEndRule.cs @@ -1,23 +1,23 @@ +using Onigwrap; using System; using System.Collections.Generic; -using Onigwrap; namespace TextMateSharp.Internal.Rules { - public class BeginEndRule : Rule + public sealed class BeginEndRule : Rule { public List BeginCaptures { get; private set; } public bool EndHasBackReferences { get; private set; } public List EndCaptures { get; private set; } - public bool ApplyEndPatternLast { get; private set; } - public bool HasMissingPatterns { get; private set; } - public IList Patterns { get; private set; } + private bool ApplyEndPatternLast { get; } + internal bool HasMissingPatterns { get; private set; } + internal IList Patterns { get; private set; } - private RegExpSource _begin; - private RegExpSource _end; + private readonly RegExpSource _begin; + private readonly RegExpSource _end; private RegExpSourceList _cachedCompiledPatterns; - public BeginEndRule(RuleId id, string name, string contentName, string begin, List beginCaptures, + internal BeginEndRule(RuleId id, string name, string contentName, string begin, List beginCaptures, string end, List endCaptures, bool applyEndPatternLast, CompilePatternsResult patterns) : base(id, name, contentName) { diff --git a/src/TextMateSharp/Internal/Rules/BeginWhileRule.cs b/src/TextMateSharp/Internal/Rules/BeginWhileRule.cs index 99290e2..1ab18cb 100644 --- a/src/TextMateSharp/Internal/Rules/BeginWhileRule.cs +++ b/src/TextMateSharp/Internal/Rules/BeginWhileRule.cs @@ -1,23 +1,23 @@ +using Onigwrap; using System; using System.Collections.Generic; -using Onigwrap; namespace TextMateSharp.Internal.Rules { - public class BeginWhileRule : Rule + public sealed class BeginWhileRule : Rule { public List BeginCaptures { get; private set; } public List WhileCaptures { get; private set; } public bool WhileHasBackReferences { get; private set; } - public bool HasMissingPatterns { get; private set; } - public IListPatterns { get; private set; } + internal bool HasMissingPatterns { get; private set; } + internal IList Patterns { get; private set; } - private RegExpSource _begin; - private RegExpSource _while; + private readonly RegExpSource _begin; + private readonly RegExpSource _while; private RegExpSourceList _cachedCompiledPatterns; private RegExpSourceList _cachedCompiledWhilePatterns; - public BeginWhileRule(RuleId id, string name, string contentName, string begin, + internal BeginWhileRule(RuleId id, string name, string contentName, string begin, List beginCaptures, string whileStr, List whileCaptures, CompilePatternsResult patterns) : base(id, name, contentName) { @@ -34,7 +34,7 @@ public BeginWhileRule(RuleId id, string name, string contentName, string begin, _cachedCompiledWhilePatterns = null; } - public string getWhileWithResolvedBackReferences(ReadOnlyMemory lineText, IOnigCaptureIndex[] captureIndices) + public string GetWhileWithResolvedBackReferences(ReadOnlyMemory lineText, IOnigCaptureIndex[] captureIndices) { return this._while.ResolveBackReferences(lineText, captureIndices); } diff --git a/src/TextMateSharp/Internal/Rules/CaptureRule.cs b/src/TextMateSharp/Internal/Rules/CaptureRule.cs index 7377a8e..7cd8281 100644 --- a/src/TextMateSharp/Internal/Rules/CaptureRule.cs +++ b/src/TextMateSharp/Internal/Rules/CaptureRule.cs @@ -1,19 +1,17 @@ -using System; - namespace TextMateSharp.Internal.Rules { - public class CaptureRule : Rule + public sealed class CaptureRule : Rule { public RuleId RetokenizeCapturedWithRuleId { get; private set; } - public CaptureRule(RuleId id, string name, string contentName, RuleId retokenizeCapturedWithRuleId) : base(id, name, contentName) + internal CaptureRule(RuleId id, string name, string contentName, RuleId retokenizeCapturedWithRuleId) : base(id, name, contentName) { RetokenizeCapturedWithRuleId = retokenizeCapturedWithRuleId; } public override void CollectPatternsRecursive(IRuleRegistry grammar, RegExpSourceList sourceList, bool isFirst) { - + } public override CompiledRule Compile(IRuleRegistry grammar, string endRegexSource, bool allowA, bool allowG) diff --git a/src/TextMateSharp/Internal/Rules/CompilePatternsResult.cs b/src/TextMateSharp/Internal/Rules/CompilePatternsResult.cs new file mode 100644 index 0000000..836693c --- /dev/null +++ b/src/TextMateSharp/Internal/Rules/CompilePatternsResult.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace TextMateSharp.Internal.Rules +{ + internal sealed class CompilePatternsResult + { + internal IList Patterns { get; private set; } + internal bool HasMissingPatterns { get; private set; } + + internal CompilePatternsResult(IList patterns, bool hasMissingPatterns) + { + HasMissingPatterns = hasMissingPatterns; + Patterns = patterns; + } + } +} \ No newline at end of file diff --git a/src/TextMateSharp/Internal/Rules/ICompiledRule.cs b/src/TextMateSharp/Internal/Rules/CompiledRule.cs similarity index 73% rename from src/TextMateSharp/Internal/Rules/ICompiledRule.cs rename to src/TextMateSharp/Internal/Rules/CompiledRule.cs index 2152310..510d152 100644 --- a/src/TextMateSharp/Internal/Rules/ICompiledRule.cs +++ b/src/TextMateSharp/Internal/Rules/CompiledRule.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; using Onigwrap; +using System.Collections.Generic; namespace TextMateSharp.Internal.Rules { - public class CompiledRule + public sealed class CompiledRule { public OnigScanner Scanner { get; private set; } public IList Rules { get; private set; } - public CompiledRule(OnigScanner scanner, IList rules) + internal CompiledRule(OnigScanner scanner, IList rules) { Scanner = scanner; Rules = rules; diff --git a/src/TextMateSharp/Internal/Rules/ICompilePatternsResult.cs b/src/TextMateSharp/Internal/Rules/ICompilePatternsResult.cs deleted file mode 100644 index bd75726..0000000 --- a/src/TextMateSharp/Internal/Rules/ICompilePatternsResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; - -namespace TextMateSharp.Internal.Rules -{ - public class CompilePatternsResult - { - public IList Patterns { get; private set; } - public bool HasMissingPatterns { get; private set; } - - public CompilePatternsResult(IList patterns, bool hasMissingPatterns) - { - HasMissingPatterns = hasMissingPatterns; - Patterns = patterns; - } - } -} \ No newline at end of file diff --git a/src/TextMateSharp/Internal/Rules/IncludeOnlyRule.cs b/src/TextMateSharp/Internal/Rules/IncludeOnlyRule.cs index ddd74df..66d13fc 100644 --- a/src/TextMateSharp/Internal/Rules/IncludeOnlyRule.cs +++ b/src/TextMateSharp/Internal/Rules/IncludeOnlyRule.cs @@ -2,14 +2,14 @@ namespace TextMateSharp.Internal.Rules { - public class IncludeOnlyRule : Rule + internal sealed class IncludeOnlyRule : Rule { - public bool HasMissingPatterns { get; private set; } - public IList Patterns { get; private set; } + internal bool HasMissingPatterns { get; private set; } + internal IList Patterns { get; private set; } private RegExpSourceList _cachedCompiledPatterns; - public IncludeOnlyRule(RuleId id, string name, string contentName, CompilePatternsResult patterns) : base(id, name, contentName) + internal IncludeOnlyRule(RuleId id, string name, string contentName, CompilePatternsResult patterns) : base(id, name, contentName) { Patterns = patterns.Patterns; HasMissingPatterns = patterns.HasMissingPatterns; diff --git a/src/TextMateSharp/Internal/Rules/MatchRule.cs b/src/TextMateSharp/Internal/Rules/MatchRule.cs index baad820..a3aa6ca 100644 --- a/src/TextMateSharp/Internal/Rules/MatchRule.cs +++ b/src/TextMateSharp/Internal/Rules/MatchRule.cs @@ -2,14 +2,14 @@ namespace TextMateSharp.Internal.Rules { - public class MatchRule : Rule + public sealed class MatchRule : Rule { public List Captures { get; private set; } - private RegExpSource _match; + private readonly RegExpSource _match; private RegExpSourceList _cachedCompiledPatterns; - public MatchRule(RuleId id, string name, string match, List captures) : base(id, name, null) + internal MatchRule(RuleId id, string name, string match, List captures) : base(id, name, null) { this._match = new RegExpSource(match, this.Id); this.Captures = captures; diff --git a/src/TextMateSharp/Internal/Rules/RegExpSource.cs b/src/TextMateSharp/Internal/Rules/RegExpSource.cs index 6d63240..abbd3d1 100644 --- a/src/TextMateSharp/Internal/Rules/RegExpSource.cs +++ b/src/TextMateSharp/Internal/Rules/RegExpSource.cs @@ -7,24 +7,24 @@ namespace TextMateSharp.Internal.Rules { - public class RegExpSource + internal sealed class RegExpSource { - private static Regex HAS_BACK_REFERENCES = new Regex("\\\\(\\d+)"); - private static Regex BACK_REFERENCING_END = new Regex("\\\\(\\d+)"); + private static readonly Regex HAS_BACK_REFERENCES = new Regex("\\\\(\\d+)"); + private static readonly Regex BACK_REFERENCING_END = new Regex("\\\\(\\d+)"); - private RuleId _ruleId; + private readonly RuleId _ruleId; private bool _hasAnchor; - private bool _hasBackReferences; + private readonly bool _hasBackReferences; private RegExpSourceAnchorCache _anchorCache; private string _source; - public RegExpSource(string regExpSource, RuleId ruleId) : + internal RegExpSource(string regExpSource, RuleId ruleId) : this(regExpSource, ruleId, true) { } - public RegExpSource(string regExpSource, RuleId ruleId, bool handleAnchors) + internal RegExpSource(string regExpSource, RuleId ruleId, bool handleAnchors) { if (handleAnchors) { @@ -45,12 +45,12 @@ public RegExpSource(string regExpSource, RuleId ruleId, bool handleAnchors) this._hasBackReferences = HAS_BACK_REFERENCES.Match(this._source).Success; } - public RegExpSource Clone() + internal RegExpSource Clone() { return new RegExpSource(this._source, this._ruleId, true); } - public void SetSource(string newSource) + internal void SetSource(string newSource) { if (this._source.Equals(newSource)) { @@ -118,7 +118,7 @@ private void HandleAnchors(string regExpSource) } } - public string ResolveBackReferences(ReadOnlyMemory lineText, IOnigCaptureIndex[] captureIndices) + internal string ResolveBackReferences(ReadOnlyMemory lineText, IOnigCaptureIndex[] captureIndices) { List capturedValues = new List(captureIndices.Length); @@ -154,7 +154,7 @@ public string ResolveBackReferences(ReadOnlyMemory lineText, IOnigCaptureI return lineText.Span.ToString(); } - private string EscapeRegExpCharacters(string value) + private static string EscapeRegExpCharacters(string value) { int valueLen = value.Length; var sb = new StringBuilder(valueLen); @@ -257,7 +257,7 @@ private RegExpSourceAnchorCache BuildAnchorCache() A1_G1_result.ToString()); } - public string ResolveAnchors(bool allowA, bool allowG) + internal string ResolveAnchors(bool allowA, bool allowG) { if (!this._hasAnchor) return this._source; @@ -276,22 +276,22 @@ public string ResolveAnchors(bool allowA, bool allowG) return this._anchorCache.A0_G0; } - public bool HasAnchor() + internal bool HasAnchor() { return this._hasAnchor; } - public string GetSource() + internal string GetSource() { return this._source; } - public RuleId GetRuleId() + internal RuleId GetRuleId() { return this._ruleId; } - public bool HasBackReferences() + internal bool HasBackReferences() { return this._hasBackReferences; } diff --git a/src/TextMateSharp/Internal/Rules/RegExpSourceList.cs b/src/TextMateSharp/Internal/Rules/RegExpSourceList.cs index 3fc4b0d..47e4030 100644 --- a/src/TextMateSharp/Internal/Rules/RegExpSourceList.cs +++ b/src/TextMateSharp/Internal/Rules/RegExpSourceList.cs @@ -3,49 +3,41 @@ namespace TextMateSharp.Internal.Rules { - public class RegExpSourceList + public sealed class RegExpSourceList { - private class RegExpSourceListAnchorCache + private sealed class RegExpSourceListAnchorCache { - public CompiledRule A0_G0; - public CompiledRule A0_G1; - public CompiledRule A1_G0; - public CompiledRule A1_G1; + internal CompiledRule A0_G0; + internal CompiledRule A0_G1; + internal CompiledRule A1_G0; + internal CompiledRule A1_G1; } - private List _items; + private readonly List _items = new List(); private bool _hasAnchors; private CompiledRule _cached; - private RegExpSourceListAnchorCache _anchorCache; + private readonly RegExpSourceListAnchorCache _anchorCache = new RegExpSourceListAnchorCache(); - public RegExpSourceList() - { - this._items = new List(); - this._hasAnchors = false; - this._cached = null; - this._anchorCache = new RegExpSourceListAnchorCache(); - } - - public void Push(RegExpSource item) + internal void Push(RegExpSource item) { this._items.Add(item); this._hasAnchors = this._hasAnchors ? this._hasAnchors : item.HasAnchor(); } - public void UnShift(RegExpSource item) + internal void UnShift(RegExpSource item) { this._items.Insert(0, item); this._hasAnchors = this._hasAnchors ? this._hasAnchors : item.HasAnchor(); } - public int Length() + internal int Length() { return this._items.Count; } - public void SetSource(int index, string newSource) + internal void SetSource(int index, string newSource) { RegExpSource r = this._items[index]; if (!r.GetSource().Equals(newSource)) @@ -61,7 +53,7 @@ public void SetSource(int index, string newSource) } } - public CompiledRule Compile(bool allowA, bool allowG) + internal CompiledRule Compile(bool allowA, bool allowG) { if (!this._hasAnchors) { diff --git a/src/TextMateSharp/Internal/Rules/Rule.cs b/src/TextMateSharp/Internal/Rules/Rule.cs index a238361..3946aa7 100644 --- a/src/TextMateSharp/Internal/Rules/Rule.cs +++ b/src/TextMateSharp/Internal/Rules/Rule.cs @@ -1,5 +1,5 @@ -using System; using Onigwrap; +using System; using TextMateSharp.Internal.Utils; @@ -7,15 +7,15 @@ namespace TextMateSharp.Internal.Rules { public abstract class Rule { - public RuleId Id { get; private set; } + internal RuleId Id { get; private set; } - private bool _nameIsCapturing; - private string _name; + private readonly bool _nameIsCapturing; + private readonly string _name; - private bool _contentNameIsCapturing; - private string _contentName; + private readonly bool _contentNameIsCapturing; + private readonly string _contentName; - public Rule(RuleId id, string name, string contentName) + protected Rule(RuleId id, string name, string contentName) { Id = id; diff --git a/src/TextMateSharp/Internal/Rules/RuleFactory.cs b/src/TextMateSharp/Internal/Rules/RuleFactory.cs index e946214..ea21f9a 100644 --- a/src/TextMateSharp/Internal/Rules/RuleFactory.cs +++ b/src/TextMateSharp/Internal/Rules/RuleFactory.cs @@ -7,7 +7,7 @@ namespace TextMateSharp.Internal.Rules { public static class RuleFactory { - public static CaptureRule CreateCaptureRule(IRuleFactoryHelper helper, string name, string contentName, + private static CaptureRule CreateCaptureRule(IRuleFactoryHelper helper, string name, string contentName, RuleId retokenizeCapturedWithRuleId) { return (CaptureRule)helper.RegisterRule(id => new CaptureRule(id, name, contentName, retokenizeCapturedWithRuleId)); diff --git a/src/TextMateSharp/Internal/Rules/RuleId.cs b/src/TextMateSharp/Internal/Rules/RuleId.cs index f78b132..78764db 100644 --- a/src/TextMateSharp/Internal/Rules/RuleId.cs +++ b/src/TextMateSharp/Internal/Rules/RuleId.cs @@ -4,17 +4,17 @@ namespace TextMateSharp.Internal.Rules { public sealed class RuleId { - public static RuleId NO_RULE = new RuleId(0); + public static readonly RuleId NO_RULE = new RuleId(0); /** * This is a special constant to indicate that the end regexp matched. */ - public static RuleId END_RULE = new RuleId(-1); + public static readonly RuleId END_RULE = new RuleId(-1); /** * This is a special constant to indicate that the while regexp matched. */ - public static RuleId WHILE_RULE = new RuleId(-2); + public static readonly RuleId WHILE_RULE = new RuleId(-2); public static RuleId Of(int id) { @@ -23,7 +23,7 @@ public static RuleId Of(int id) return new RuleId(id); } - public int Id; + public int Id { get; } private RuleId(int id) { diff --git a/src/TextMateSharp/Internal/Themes/reader/ThemeReader.cs b/src/TextMateSharp/Internal/Themes/reader/ThemeReader.cs index 5e2f3a7..e0cc916 100644 --- a/src/TextMateSharp/Internal/Themes/reader/ThemeReader.cs +++ b/src/TextMateSharp/Internal/Themes/reader/ThemeReader.cs @@ -1,13 +1,11 @@ -using System.Collections.Generic; using System.IO; using TextMateSharp.Internal.Parser.Json; -using TextMateSharp.Registry; using TextMateSharp.Themes; namespace TextMateSharp.Internal.Themes.Reader { - public class ThemeReader + public static class ThemeReader { public static IRawTheme ReadThemeSync(StreamReader reader) { diff --git a/src/TextMateSharp/Model/TMModel.cs b/src/TextMateSharp/Model/TMModel.cs index 341d469..92d6ba7 100644 --- a/src/TextMateSharp/Model/TMModel.cs +++ b/src/TextMateSharp/Model/TMModel.cs @@ -838,22 +838,64 @@ public void RemoveModelTokensChangedListener(IModelTokensChangedListener listene } } + /// + /// Releases all resources used by the current instance of the class. + /// + /// This method should be called when the object is no longer needed to free up + /// resources. It is safe to call this method multiple times. After calling this method, + /// the object should not be used. public void Dispose() { - if (Interlocked.CompareExchange(ref _isDisposedFlag, 1, 0) != 0) - return; - - // Stop first (acquires _lock internally) without holding listeners. This preserves the global - // lock ordering _lock -> listeners used elsewhere (e.g. in AddModelTokensChangedListener) and - // avoids any lock inversion between _lock and listeners. - Stop(); + Dispose(true); + GC.SuppressFinalize(this); + } - lock (listeners) + /// + /// Releases the resources used by the object, enforcing a one-time dispose guard and optionally + /// disposing managed resources based on the flag. + /// + /// + /// This method ensures that the dispose logic runs only once, regardless of the value of + /// , preventing potential issues with multiple calls. When + /// is , it disposes of managed resources in + /// addition to any unmanaged resources. When , only unmanaged cleanup would be + /// performed (although this implementation currently has no unmanaged resources to release). It is intended + /// to be overridden in derived classes to provide custom disposal logic following the same pattern. + /// + /// + /// Indicates whether the method was invoked via an explicit call to or from a + /// finalizer/indirect path. If , the method may dispose of both managed and unmanaged + /// resources; if , it should only dispose of unmanaged resources. + /// + protected virtual void Dispose(bool isDisposing) + { + if (isDisposing) { - listeners.Clear(); - } + // Dispose managed resources + // DESIGN NOTE: The Interlocked.CompareExchange guard is intentionally placed inside the + // isDisposing branch, not at the top of this method. Because TMModel is abstract, a derived + // class may introduce a finalizer that calls Dispose(false). If the guard were at the top, + // a finalizer-first invocation would set the flag and block a subsequent Dispose(true) call, + // preventing managed resource cleanup. Placing the guard here ensures that managed disposal + // runs exactly once while leaving the unmanaged branch (currently empty) unaffected by the + // managed-disposal flag. If unmanaged resources are ever added, a separate guard should be + // introduced for that branch. + if (Interlocked.CompareExchange(ref _isDisposedFlag, 1, 0) != 0) + return; + + // Stop first (acquires _lock internally) without holding listeners. This preserves the global + // lock ordering _lock -> listeners used elsewhere (e.g. in AddModelTokensChangedListener) and + // avoids any lock inversion between _lock and listeners. + Stop(); - GetLines().Dispose(); + lock (listeners) + { + listeners.Clear(); + } + + GetLines().Dispose(); + } + // Free unmanaged resources (none currently) } private void Stop() diff --git a/src/TextMateSharp/Properties/AssemblyInfo.cs b/src/TextMateSharp/Properties/AssemblyInfo.cs index c4511fe..278d7d3 100644 --- a/src/TextMateSharp/Properties/AssemblyInfo.cs +++ b/src/TextMateSharp/Properties/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("TextMateSharp.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001000db16f8de24159e7ee94e32addce2a9b60f3ea5be200ae7b5abbf8676705064a1b5a5a44d570a884bd86bd2d3e83411fb88914e00028bc7d4b5be1ba8fd8db4335e3ad911d0ef7e694cf433f3314e991100c72c7473641a9e3437deeab402c8f4a03fdf9c174cbae00142a28ce43475ca61f0016ede73dc778b5ed5a0344cfc2")] \ No newline at end of file +[assembly: InternalsVisibleTo("TextMateSharp.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001000db16f8de24159e7ee94e32addce2a9b60f3ea5be200ae7b5abbf8676705064a1b5a5a44d570a884bd86bd2d3e83411fb88914e00028bc7d4b5be1ba8fd8db4335e3ad911d0ef7e694cf433f3314e991100c72c7473641a9e3437deeab402c8f4a03fdf9c174cbae00142a28ce43475ca61f0016ede73dc778b5ed5a0344cfc2")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file