diff --git a/ExpandRegions/ExpandRegions.Vsix/source.extension.vsixmanifest b/ExpandRegions/ExpandRegions.Vsix/source.extension.vsixmanifest index 3efc9fb..6e6e025 100644 --- a/ExpandRegions/ExpandRegions.Vsix/source.extension.vsixmanifest +++ b/ExpandRegions/ExpandRegions.Vsix/source.extension.vsixmanifest @@ -3,7 +3,7 @@ ExpandRegions - This extension expands all regions in C# and Visual Basic when a file is opened. It's a slimmed down version of "I Hate #Regions" for VS2017. + This extension expands all regions in C# and Visual Basic when a file is opened. It's a slimmed down version of \"I Hate #Regions\" for VS2017. license.txt diff --git a/ExpandRegions/ExpandRegions/Classifications/ActiveRegionClassification.cs b/ExpandRegions/ExpandRegions/Classifications/ActiveRegionClassification.cs new file mode 100644 index 0000000..c41b763 --- /dev/null +++ b/ExpandRegions/ExpandRegions/Classifications/ActiveRegionClassification.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.Composition; +using System.Windows.Media; + +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace ExpandRegions.Classifications +{ + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = Constants.ActiveRegionClassificationTypeNames)] + [Name(Constants.ActiveRegionName)] + [DisplayName(Constants.ActiveRegionName)] + [UserVisible(true)] + [Order(After = Constants.OrderAfterPriority, Before = Constants.OrderBeforePriority)] + internal sealed class ActiveRegionClassification : ClassificationFormatDefinition + { + #region Initialization + + // Methods + public ActiveRegionClassification() + { + ForegroundColor = Colors.DarkGray; + } + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/Classifications/ClassificationFormatDefinition.cs b/ExpandRegions/ExpandRegions/Classifications/ClassificationFormatDefinition.cs new file mode 100644 index 0000000..ca20f2d --- /dev/null +++ b/ExpandRegions/ExpandRegions/Classifications/ClassificationFormatDefinition.cs @@ -0,0 +1,166 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Media; + +using Microsoft.VisualStudio.Text.Classification; + +namespace ExpandRegions.Classifications +{ + internal abstract class ClassificationFormatDefinition : EditorFormatDefinition + { + #region Protected Methods + + protected override ResourceDictionary CreateResourceDictionaryFromDefinition() + { + ResourceDictionary resourceDictionary = new ResourceDictionary(); + AddOverridableProperties(resourceDictionary); + if (ForegroundBrush != null) + { + resourceDictionary["Foreground"] = ForegroundBrush; + if (ForegroundBrush.Opacity != 1.0) + { + resourceDictionary["ForegroundOpacity"] = ForegroundBrush.Opacity; + } + } + if (BackgroundBrush != null) + { + resourceDictionary["Background"] = BackgroundBrush; + if (BackgroundBrush.Opacity != 1.0) + { + resourceDictionary["BackgroundOpacity"] = BackgroundBrush.Opacity; + } + } + if (FontTypeface != null) + { + resourceDictionary.Add("Typeface", FontTypeface); + if (FontTypeface.Weight == FontWeights.Bold) + { + resourceDictionary["IsBold"] = true; + } + if (FontTypeface.Style == FontStyles.Italic) + { + resourceDictionary["IsItalic"] = true; + } + } + if (FontRenderingSize.HasValue) + { + resourceDictionary.Add("FontRenderingSize", FontRenderingSize.Value); + } + if (FontHintingSize.HasValue) + { + resourceDictionary.Add("FontHintingSize", FontHintingSize.Value); + } + if (TextDecorations != null) + { + resourceDictionary.Add("TextDecorations", TextDecorations); + } + if (TextEffects != null) + { + resourceDictionary.Add("TextEffects", TextEffects); + } + if (CultureInfo != null) + { + resourceDictionary.Add("CultureInfo", CultureInfo); + } + return resourceDictionary; + } + + #endregion + + #region Private Methods + + private void AddOverridableProperties(ResourceDictionary resourceDictionary) + { + if (ForegroundOpacity.HasValue) + { + resourceDictionary.Add("ForegroundOpacity", ForegroundOpacity.Value); + } + if (BackgroundOpacity.HasValue) + { + resourceDictionary.Add("BackgroundOpacity", BackgroundOpacity.Value); + } + if (IsBold.HasValue) + { + resourceDictionary.Add("IsBold", IsBold.Value); + } + if (IsItalic.HasValue) + { + resourceDictionary.Add("IsItalic", IsItalic.Value); + } + if (ForegroundColor.HasValue) + { + resourceDictionary["Foreground"] = new SolidColorBrush(ForegroundColor.Value); + } + if (BackgroundColor.HasValue) + { + resourceDictionary["Background"] = new SolidColorBrush(BackgroundColor.Value); + } + } + + #endregion + + #region Public Properties + + public double? BackgroundOpacity + { + get; + protected set; + } + + public CultureInfo CultureInfo + { + get; + protected set; + } + + public double? FontHintingSize + { + get; + protected set; + } + + public double? FontRenderingSize + { + get; + protected set; + } + + public Typeface FontTypeface + { + get; + protected set; + } + + public double? ForegroundOpacity + { + get; + protected set; + } + + public bool? IsBold + { + get; + protected set; + } + + public bool? IsItalic + { + get; + protected set; + } + + public TextDecorationCollection TextDecorations + { + get; + protected set; + } + + public TextEffectCollection TextEffects + { + get; + protected set; + } + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/Classifications/ClassifierProvider.cs b/ExpandRegions/ExpandRegions/Classifications/ClassifierProvider.cs new file mode 100644 index 0000000..8d7af3c --- /dev/null +++ b/ExpandRegions/ExpandRegions/Classifications/ClassifierProvider.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.Composition; + +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace ExpandRegions.Classifications +{ + internal sealed class ClassifierProvider + { + #region Internal Fields + + [Export] + [Name(Constants.ActiveRegionClassificationTypeNames)] + internal static ClassificationTypeDefinition ActiveRegionDefinition; + + [Export] + [Name(Constants.InactiveRegionClassificationTypeNames)] + internal static ClassificationTypeDefinition InactiveRegionDefinition; + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/Classifications/InactiveRegionClassification.cs b/ExpandRegions/ExpandRegions/Classifications/InactiveRegionClassification.cs new file mode 100644 index 0000000..5af3411 --- /dev/null +++ b/ExpandRegions/ExpandRegions/Classifications/InactiveRegionClassification.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.Composition; +using System.Windows.Media; + +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace ExpandRegions.Classifications +{ + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = Constants.InactiveRegionClassificationTypeNames)] + [Name(Constants.InactiveRegionName)] + [DisplayName(Constants.InactiveRegionName)] + [UserVisible(true)] + [Order(After =Constants.OrderAfterPriority, Before = Constants.OrderBeforePriority)] + internal sealed class InactiveRegionClassification : ClassificationFormatDefinition + { + #region Initialization + + // Methods + public InactiveRegionClassification() + { + ForegroundColor = Colors.Gray; + FontRenderingSize = 10; + ForegroundOpacity = 0.5; + } + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/Constants.cs b/ExpandRegions/ExpandRegions/Constants.cs new file mode 100644 index 0000000..cd1aa37 --- /dev/null +++ b/ExpandRegions/ExpandRegions/Constants.cs @@ -0,0 +1,25 @@ +namespace ExpandRegions +{ + internal static class Constants + { + #region Internal Fields + + internal const char RegionIndicatorCharacter = '#'; + internal const string RegionIndicator = "#region"; + + internal const string CSharpContentType = "CSharp"; + internal const string BasicContentType = "Basic"; + + internal const string TextViewRole = "DOCUMENT"; + + internal const string ActiveRegionName = "Active Region"; + internal const string InactiveRegionName = "Inactive Region"; + internal const string ActiveRegionClassificationTypeNames = "activeRegion"; + internal const string InactiveRegionClassificationTypeNames = "inactiveRegion"; + + internal const string OrderAfterPriority = "Default Priority"; + internal const string OrderBeforePriority = "High Priority"; + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/ExpandRegions.csproj b/ExpandRegions/ExpandRegions/ExpandRegions.csproj index 2630064..6116f9a 100644 --- a/ExpandRegions/ExpandRegions/ExpandRegions.csproj +++ b/ExpandRegions/ExpandRegions/ExpandRegions.csproj @@ -50,17 +50,29 @@ ..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.15.0.26201\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll True + + + + + + + + + + + + diff --git a/ExpandRegions/ExpandRegions/RegionTextViewHandler.cs b/ExpandRegions/ExpandRegions/RegionTextViewHandler.cs index b45b1a0..f724512 100644 --- a/ExpandRegions/ExpandRegions/RegionTextViewHandler.cs +++ b/ExpandRegions/ExpandRegions/RegionTextViewHandler.cs @@ -1,56 +1,82 @@ using System; +using System.Diagnostics.CodeAnalysis; + using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Outlining; namespace ExpandRegions { - class RegionTextViewHandler + public class RegionTextViewHandler { + #region Private Fields + private IWpfTextView _textView; private IOutliningManager _outliningManager; - public static void CreateHandler(IWpfTextView textView, IOutliningManagerService outliningManagerService) - { - new RegionTextViewHandler(textView, outliningManagerService); - } + #endregion + + #region Initialization private RegionTextViewHandler(IWpfTextView textView, IOutliningManagerService outliningManagerService) { - _outliningManager = outliningManagerService.GetOutliningManager(textView); + if (_outliningManager == null) { return; } + _textView = textView; _outliningManager.RegionsCollapsed += OnRegionsCollapsed; _textView.Closed += OnClosed; + } + + #endregion + #region Public Methods + + [SuppressMessage("ReSharper", "ObjectCreationAsStatement")] + public static void CreateHandler(IWpfTextView textView, IOutliningManagerService outliningManagerService) + { + new RegionTextViewHandler(textView, outliningManagerService); } + #endregion + + #region Private Methods + private void OnClosed(object sender, EventArgs e) { if (_outliningManager != null) { _outliningManager.RegionsCollapsed -= OnRegionsCollapsed; } + if (_textView != null) { _textView.Closed -= OnClosed; } + _textView = null; _outliningManager = null; } - + private void OnRegionsCollapsed(object sender, RegionsCollapsedEventArgs e) { - foreach (var cr in e.CollapsedRegions) + foreach (ICollapsed collapsed in e.CollapsedRegions) { - _outliningManager.Expand(cr); + try + { + _outliningManager.Expand(collapsed); + } + catch (InvalidOperationException) + { + } } + _outliningManager.RegionsCollapsed -= OnRegionsCollapsed; } - + #endregion } -} +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/Tags/RegionTag.cs b/ExpandRegions/ExpandRegions/Tags/RegionTag.cs new file mode 100644 index 0000000..186b4d5 --- /dev/null +++ b/ExpandRegions/ExpandRegions/Tags/RegionTag.cs @@ -0,0 +1,17 @@ +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Tagging; + +namespace ExpandRegions.Tags +{ + public sealed class RegionTag : ClassificationTag + { + #region Initialization + + public RegionTag(IClassificationType type) + : base(type) + { + } + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/Tags/RegionTagger.cs b/ExpandRegions/ExpandRegions/Tags/RegionTagger.cs new file mode 100644 index 0000000..3f9c257 --- /dev/null +++ b/ExpandRegions/ExpandRegions/Tags/RegionTagger.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; + +namespace ExpandRegions.Tags +{ + public class RegionTagger : ITagger + { + #region Private Fields + + private int _oldLineNumber = -1; + private SnapshotSpan _oldSnapshotSpan; + + private static readonly object _lock = new object(); + + #endregion + + #region Initialization + + public RegionTagger(ITextView view, ITextBuffer sourceBuffer, IClassificationTypeRegistryService classificationTypeRegistryService) + { + View = view; + SourceBuffer = sourceBuffer; + ClassificationTypeRegistryService = classificationTypeRegistryService; + + view.Caret.PositionChanged += CaretPositionChanged; + view.LayoutChanged += ViewLayoutChanged; + } + + #endregion + + #region Private Methods + + private void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e) + { + UpdateAtCaretPosition(e.NewPosition); + } + + + private static bool IsRegionOrEndRegion(SnapshotSpan snapshotSpan) + { + return Regex.IsMatch(snapshotSpan.GetText().Trim().ToLower(), @"^#\W*(end\W*)*region"); + } + + private void UpdateAtCaretPosition(CaretPosition newPosition) + { + SnapshotPoint? point = newPosition.Point.GetPoint(SourceBuffer, newPosition.Affinity); + + if (!point.HasValue) + return; + + ITextSnapshotLine lineFromPosition = point.Value.Snapshot.GetLineFromPosition(point.Value.Position); + + lock (_lock) + { + if (lineFromPosition.LineNumber != _oldLineNumber) + { + SnapshotSpan snapshotSpan = new SnapshotSpan(lineFromPosition.Snapshot, lineFromPosition.Start.Position, lineFromPosition.Length); + bool isRegionOrEndRegion = IsRegionOrEndRegion(snapshotSpan); + + EventHandler tagsChanged = TagsChanged; + if (tagsChanged != null) + { + if (isRegionOrEndRegion) + { + tagsChanged(this, new SnapshotSpanEventArgs(snapshotSpan)); + } + if (!_oldSnapshotSpan.IsEmpty) + { + tagsChanged(this, new SnapshotSpanEventArgs(_oldSnapshotSpan)); + } + } + + _oldSnapshotSpan = snapshotSpan; + _oldLineNumber = lineFromPosition.LineNumber; + } + } + } + + private void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) + { + if (e.NewViewState.EditSnapshot != e.OldViewState.EditSnapshot) + { + UpdateAtCaretPosition(View.Caret.Position); + } + } + + #endregion + + #region Private Properties + + private IClassificationTypeRegistryService ClassificationTypeRegistryService + { + get; + } + + private ITextBuffer SourceBuffer + { + get; + } + + private ITextView View + { + get; + } + + #endregion + + #region Interface Implementation: ITagger + + public event EventHandler TagsChanged; + + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection snapShotSpans) + { + foreach (SnapshotSpan snapshotSpan in snapShotSpans.Where(IsRegionOrEndRegion)) + { + SnapshotPoint? point = View.Caret.Position.Point.GetPoint(SourceBuffer, View.Caret.Position.Affinity); + + int pointPosition = point.HasValue ? snapshotSpan.Snapshot.GetLineNumberFromPosition(point.Value) : -1; + int lineNumberFromPosition = snapshotSpan.Snapshot.GetLineNumberFromPosition(snapshotSpan.Start); + int index = snapshotSpan.GetText().IndexOf(Constants.RegionIndicatorCharacter); + + SnapshotSpan newSpan = new SnapshotSpan(snapshotSpan.Snapshot, (int)snapshotSpan.Start + index, snapshotSpan.Length - index); + + string classificationTypeNames = lineNumberFromPosition != pointPosition + ? Constants.InactiveRegionClassificationTypeNames + : Constants.ActiveRegionClassificationTypeNames; + + yield return new TagSpan(newSpan, new RegionTag(ClassificationTypeRegistryService.GetClassificationType(classificationTypeNames))); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/Tags/RegionTaggerProvider.cs b/ExpandRegions/ExpandRegions/Tags/RegionTaggerProvider.cs new file mode 100644 index 0000000..9c61a30 --- /dev/null +++ b/ExpandRegions/ExpandRegions/Tags/RegionTaggerProvider.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.Composition; + +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; + +namespace ExpandRegions.Tags +{ + [Export(typeof(IViewTaggerProvider))] + [ContentType(Constants.CSharpContentType)] + [ContentType(Constants.BasicContentType)] + [TagType(typeof(RegionTag))] + public class RegionTaggerProvider : IViewTaggerProvider + { + #region Internal Properties + + [Import] + internal IClassificationTypeRegistryService ClassificationTypeRegistryService + { + get; + set; + } + + #endregion + + #region Interface Implementation: IViewTaggerProvider + + public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag + { + if (textView.TextBuffer != buffer) + { + return null; + } + + return new RegionTagger(textView, buffer, ClassificationTypeRegistryService) as ITagger; + } + + #endregion + } +} \ No newline at end of file diff --git a/ExpandRegions/ExpandRegions/TextViewCreationListener.cs b/ExpandRegions/ExpandRegions/TextViewCreationListener.cs index 69f5366..6f91425 100644 --- a/ExpandRegions/ExpandRegions/TextViewCreationListener.cs +++ b/ExpandRegions/ExpandRegions/TextViewCreationListener.cs @@ -1,19 +1,18 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.Composition; + using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Outlining; using Microsoft.VisualStudio.Utilities; namespace ExpandRegions { - [TextViewRole("DOCUMENT"), ContentType("CSharp"), ContentType("Basic"), Export(typeof(IWpfTextViewCreationListener))] + [TextViewRole(Constants.TextViewRole)] + [ContentType(Constants.CSharpContentType)] + [ContentType(Constants.BasicContentType)] + [Export(typeof(IWpfTextViewCreationListener))] public class TextViewCreationListener : IWpfTextViewCreationListener { - + #region Public Properties [Import(typeof(IOutliningManagerService))] public IOutliningManagerService OutliningManagerService @@ -22,13 +21,20 @@ public IOutliningManagerService OutliningManagerService set; } + #endregion + + #region Interface Implementation: IWpfTextViewCreationListener + public void TextViewCreated(IWpfTextView textView) { if (textView == null || OutliningManagerService == null) { return; } + RegionTextViewHandler.CreateHandler(textView, OutliningManagerService); } + + #endregion } -} +} \ No newline at end of file