├── .gitattributes ├── .gitignore ├── AdditionValueConverter.cs ├── BoolArrayConverter.cs ├── CBAdornmentData.cs ├── CBEOptionPage.cs ├── CBEOptionPageControl.Designer.cs ├── CBEOptionPageControl.cs ├── CBEOptionPageControl.resx ├── CBETagControl.xaml ├── CBETagControl.xaml.cs ├── CBETagPackage.cs ├── CBETagger.cs ├── CBETaggerProvider.cs ├── CodeBlockEndTag.csproj ├── CodeBlockEndTag.sln ├── Extensions.cs ├── IFontAndColorDefaultsProvider.cs ├── IconMonikerSelector.cs ├── Key.snk ├── LICENSE.txt ├── Languages.cs ├── Microsoft.Internal.VisualStudio.Shell.Interop ├── FontAndColorsOptionPageDummy.cs └── Interfaces.cs ├── Microsoft.VisualStudio.Shell.Interop ├── FontAndColorDefaultsBase.cs ├── FontAndColorRegistrationAttribute.cs ├── FontResourceKey.cs ├── SafeNativeMethods.cs ├── Services.cs ├── ThemeColorsBase.cs └── VsColor.cs ├── Properties └── AssemblyInfo.cs ├── README.md ├── ReleaseNotes.txt ├── Resources ├── CBETagPackage.ico └── VSPreview.png ├── Shell ├── EndTagColors.cs └── FontAndColorDefaultsCSharpTags.cs ├── TextColorConverter.cs ├── VSPackage.resx ├── app.config └── source.extension.vsixmanifest /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ -------------------------------------------------------------------------------- /AdditionValueConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | using System.Globalization; 4 | 5 | namespace CodeBlockEndTag 6 | { 7 | internal class AdditionValueConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return System.Convert.ToDouble(value) + System.Convert.ToDouble(parameter); 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | return System.Convert.ToDouble(value) - System.Convert.ToDouble(parameter); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /BoolArrayConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | using System.Linq; 5 | 6 | namespace CodeBlockEndTag 7 | { 8 | internal class BoolArrayConverter : TypeConverter 9 | { 10 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 11 | { 12 | return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); 13 | } 14 | 15 | public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) 16 | { 17 | return destinationType == typeof(bool[]) || base.CanConvertTo(context, destinationType); 18 | } 19 | 20 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 21 | { 22 | // Bit string to bool array 23 | return value is not string v ? 24 | base.ConvertFrom(context, culture, value) : 25 | v.ToCharArray().Select(chr => chr == '1').ToArray(); 26 | } 27 | 28 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 29 | { 30 | if (destinationType != typeof(string) || value is not bool[] v) 31 | { 32 | return base.ConvertTo(context, culture, value, destinationType); 33 | } 34 | // bool array to bit string 35 | return string.Join("", v.Select(b => b ? '1' : '0')); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CBAdornmentData.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace CodeBlockEndTag 4 | { 5 | internal class CBAdornmentData 6 | { 7 | internal int StartPosition; 8 | internal int EndPosition; 9 | internal int HeaderStartPosition; 10 | 11 | internal readonly UIElement Adornment; 12 | 13 | internal CBAdornmentData(int start, int end, int headerStart, UIElement adornment) 14 | { 15 | StartPosition = start; 16 | EndPosition = end; 17 | HeaderStartPosition = headerStart; 18 | Adornment = adornment; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CBEOptionPage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Runtime.InteropServices; 5 | using System.Windows.Forms; 6 | using System.Linq; 7 | using Microsoft.VisualStudio.Shell.Settings; 8 | using Microsoft.VisualStudio.Settings; 9 | using System.Windows.Threading; 10 | 11 | namespace CodeBlockEndTag 12 | { 13 | [Guid("B009CDB7-6900-47DC-8403-285191252811")] 14 | public class CBEOptionPage : DialogPage 15 | { 16 | // Name of settings collection where bit array is stored 17 | private const string CollectionName = "CodeBlockEndTag"; 18 | 19 | public delegate void OptionChangedHandler(object sender); 20 | public event OptionChangedHandler OptionChanged; 21 | 22 | public CBEOptionPage() 23 | { 24 | // default: all languages are enabled 25 | SupportedLangActive = _supportedLangs.Select(_ => true).ToArray(); 26 | } 27 | 28 | #region supported languages 29 | 30 | // List of all supported languages 31 | // Never remove any! User preferences are stored for each array position 32 | private readonly SupportedLang[] _supportedLangs = { 33 | new() { Name = Languages.CSharp, DisplayName = "CSharp C#" }, 34 | /* 35 | All of these languages don't come with a decent TextStructureNavigator so they can't be used right now 36 | 37 | new() { Name = "C/C++", DisplayName = "C/C++" }, 38 | new() { Name = "PowerShell", DisplayName = "PowerShell" }, 39 | new() { Name = "JavaScript", DisplayName = "JavaScript" }, 40 | new() { Name = "CoffeeScript", DisplayName = "CoffeeScript" }, 41 | new() { Name = "TypeScript", DisplayName = "TypeScript" }, 42 | new() { Name = "SCSS", DisplayName = "SCSS/CSS" }, 43 | new() { Name = "LESS", DisplayName = "LESS" }, 44 | new() { Name = "SASS", DisplayName = "SASS" }, 45 | new() { Name = "HTML", DisplayName = "HTML(JS/CSS)" } 46 | */ 47 | }; 48 | 49 | /// 50 | /// Gets an array with all supported languages display names 51 | /// 52 | public string[] SupportedLangDisplayNames 53 | { 54 | get 55 | { 56 | return _supportedLangs.Select(l => l.DisplayName).ToArray(); 57 | } 58 | } 59 | 60 | /// 61 | /// Gets or sets the array storing whether the tagger is enabled for a language or not 62 | /// 63 | [TypeConverter(typeof(BoolArrayConverter))] 64 | public bool[] SupportedLangActive { get; set; } 65 | 66 | /// 67 | /// Enable or disable a supported language 68 | /// 69 | public void SetSupportedLangActive(int index, bool active) 70 | { 71 | if (index >= SupportedLangActive.Length) 72 | { 73 | bool[] a = SupportedLangActive; 74 | Array.Resize(ref a, index + 1); 75 | SupportedLangActive = a; 76 | } 77 | SupportedLangActive[index] = active; 78 | } 79 | 80 | /// 81 | /// Returns true if the given language is supported and active 82 | /// 83 | public bool IsLanguageSupported(string lang) 84 | { 85 | int index = Array.FindIndex(_supportedLangs, sl => sl.Name.Equals(lang)); 86 | if (index >= 0 && index < SupportedLangActive.Length) 87 | { 88 | return SupportedLangActive[index]; 89 | } 90 | return false; 91 | } 92 | 93 | #endregion 94 | 95 | #region other settings 96 | 97 | /// 98 | /// Gets or set the option: Enable CodeBlock End Tagger 99 | /// 100 | public bool CBETaggerEnabled 101 | { 102 | get { return cbeTaggerEnabled; } 103 | set 104 | { 105 | if (cbeTaggerEnabled != value) 106 | { 107 | cbeTaggerEnabled = value; 108 | OptionChanged?.Invoke(this); 109 | } 110 | } 111 | } 112 | private bool cbeTaggerEnabled = true; 113 | 114 | /// 115 | /// Gets or sets the option: Display Mode (Icon&Text / Icon / Text) 116 | /// 117 | public int CBEDisplayMode 118 | { 119 | get { return cbeDisplayMode; } 120 | set 121 | { 122 | if (cbeDisplayMode != value) 123 | { 124 | cbeDisplayMode = value; 125 | OptionChanged?.Invoke(this); 126 | } 127 | } 128 | } 129 | private int cbeDisplayMode = (int)DisplayModes.IconAndText; 130 | 131 | public enum DisplayModes : int 132 | { 133 | Text = 1, 134 | Icon = 2, 135 | IconAndText = 3 136 | } 137 | 138 | /// 139 | /// Gets or sets the option: Navigate on (Single-Click / Double-Click / CTRL+Click) 140 | /// 141 | public int CBEClickMode 142 | { 143 | get { return cbeClickMode; } 144 | set 145 | { 146 | if (cbeClickMode != value) 147 | { 148 | cbeClickMode = value; 149 | OptionChanged?.Invoke(this); 150 | } 151 | } 152 | } 153 | private int cbeClickMode = (int)ClickMode.SingleClick; 154 | 155 | public enum ClickMode : int 156 | { 157 | SingleClick = 1, 158 | DoubleClick = 2, 159 | CtrlClick = 3 160 | } 161 | 162 | /// 163 | /// Gets or sets the option: Show Tags when (Always / Header not visible) 164 | /// 165 | public int CBEVisibilityMode 166 | { 167 | get { return cbeVisibilityMode; } 168 | set 169 | { 170 | if (cbeVisibilityMode != value) 171 | { 172 | cbeVisibilityMode = value; 173 | OptionChanged?.Invoke(this); 174 | } 175 | } 176 | } 177 | private int cbeVisibilityMode = (int)VisibilityModes.HeaderNotVisible; 178 | 179 | public enum VisibilityModes : int 180 | { 181 | Always = 1, 182 | HeaderNotVisible = 2 183 | } 184 | 185 | #endregion 186 | 187 | #region save / load 188 | 189 | public override void SaveSettingsToStorage() 190 | { 191 | base.SaveSettingsToStorage(); 192 | 193 | Dispatcher.CurrentDispatcher.VerifyAccess(); 194 | 195 | var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider); 196 | var userSettingsStore = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); 197 | 198 | if (!userSettingsStore.CollectionExists(CollectionName)) 199 | userSettingsStore.CreateCollection(CollectionName); 200 | 201 | var converter = new BoolArrayConverter(); 202 | userSettingsStore.SetString( 203 | CollectionName, 204 | nameof(SupportedLangActive), 205 | converter.ConvertTo(SupportedLangActive, typeof(string)) as string); 206 | } 207 | 208 | public override void LoadSettingsFromStorage() 209 | { 210 | base.LoadSettingsFromStorage(); 211 | 212 | Dispatcher.CurrentDispatcher.VerifyAccess(); 213 | 214 | var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider); 215 | var userSettingsStore = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); 216 | 217 | if (!userSettingsStore.PropertyExists(CollectionName, nameof(SupportedLangActive))) 218 | return; 219 | 220 | var converter = new BoolArrayConverter(); 221 | SupportedLangActive = converter.ConvertFrom( 222 | userSettingsStore.GetString(CollectionName, nameof(SupportedLangActive))) as bool[]; 223 | } 224 | 225 | #endregion 226 | 227 | protected override IWin32Window Window 228 | { 229 | get 230 | { 231 | CBEOptionPageControl page = new(); 232 | page.optionsPage = this; 233 | page.Initialize(); 234 | return page; 235 | } 236 | } 237 | } 238 | 239 | public struct SupportedLang 240 | { 241 | public string Name { get; set; } 242 | public string DisplayName { get; set; } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /CBEOptionPageControl.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace CodeBlockEndTag 2 | { 3 | partial class CBEOptionPageControl 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.chkCBETaggerEnabled = new System.Windows.Forms.CheckBox(); 32 | this.cntVisibilityMode = new System.Windows.Forms.GroupBox(); 33 | this.rdbHeaderInvisible = new System.Windows.Forms.RadioButton(); 34 | this.rdbAlways = new System.Windows.Forms.RadioButton(); 35 | this.cntNavigateMode = new System.Windows.Forms.GroupBox(); 36 | this.rdbCtrlClick = new System.Windows.Forms.RadioButton(); 37 | this.rdbDoubleClick = new System.Windows.Forms.RadioButton(); 38 | this.rdbSingleClick = new System.Windows.Forms.RadioButton(); 39 | this.cntDisplayMode = new System.Windows.Forms.GroupBox(); 40 | this.rdbTextOnly = new System.Windows.Forms.RadioButton(); 41 | this.rdbIconOnly = new System.Windows.Forms.RadioButton(); 42 | this.rdbIconAndText = new System.Windows.Forms.RadioButton(); 43 | this.cntInfo = new System.Windows.Forms.GroupBox(); 44 | this.lblLink = new System.Windows.Forms.LinkLabel(); 45 | this.lblInfo = new System.Windows.Forms.Label(); 46 | this.cntLanguages = new System.Windows.Forms.GroupBox(); 47 | this.lviLanguages = new System.Windows.Forms.CheckedListBox(); 48 | this.lnkGitHub = new System.Windows.Forms.LinkLabel(); 49 | this.lblSuggestMore = new System.Windows.Forms.Label(); 50 | this.lblFont = new System.Windows.Forms.LinkLabel(); 51 | this.cntVisibilityMode.SuspendLayout(); 52 | this.cntNavigateMode.SuspendLayout(); 53 | this.cntDisplayMode.SuspendLayout(); 54 | this.cntInfo.SuspendLayout(); 55 | this.cntLanguages.SuspendLayout(); 56 | this.SuspendLayout(); 57 | // 58 | // chkCBETaggerEnabled 59 | // 60 | this.chkCBETaggerEnabled.AutoSize = true; 61 | this.chkCBETaggerEnabled.Location = new System.Drawing.Point(11, 7); 62 | this.chkCBETaggerEnabled.Margin = new System.Windows.Forms.Padding(6); 63 | this.chkCBETaggerEnabled.Name = "chkCBETaggerEnabled"; 64 | this.chkCBETaggerEnabled.Size = new System.Drawing.Size(306, 29); 65 | this.chkCBETaggerEnabled.TabIndex = 1; 66 | this.chkCBETaggerEnabled.Text = "enable CodeBlock End Tagger"; 67 | this.chkCBETaggerEnabled.UseVisualStyleBackColor = true; 68 | this.chkCBETaggerEnabled.CheckedChanged += new System.EventHandler(this.ChkCBETaggerEnabled_CheckedChanged); 69 | // 70 | // cntVisibilityMode 71 | // 72 | this.cntVisibilityMode.Controls.Add(this.rdbHeaderInvisible); 73 | this.cntVisibilityMode.Controls.Add(this.rdbAlways); 74 | this.cntVisibilityMode.Location = new System.Drawing.Point(44, 50); 75 | this.cntVisibilityMode.Margin = new System.Windows.Forms.Padding(6); 76 | this.cntVisibilityMode.Name = "cntVisibilityMode"; 77 | this.cntVisibilityMode.Padding = new System.Windows.Forms.Padding(6); 78 | this.cntVisibilityMode.Size = new System.Drawing.Size(367, 118); 79 | this.cntVisibilityMode.TabIndex = 7; 80 | this.cntVisibilityMode.TabStop = false; 81 | this.cntVisibilityMode.Text = "Tags visible"; 82 | // 83 | // rdbHeaderInvisible 84 | // 85 | this.rdbHeaderInvisible.AutoSize = true; 86 | this.rdbHeaderInvisible.Location = new System.Drawing.Point(29, 78); 87 | this.rdbHeaderInvisible.Margin = new System.Windows.Forms.Padding(6); 88 | this.rdbHeaderInvisible.Name = "rdbHeaderInvisible"; 89 | this.rdbHeaderInvisible.Size = new System.Drawing.Size(247, 29); 90 | this.rdbHeaderInvisible.TabIndex = 9; 91 | this.rdbHeaderInvisible.TabStop = true; 92 | this.rdbHeaderInvisible.Text = "When header not visible"; 93 | this.rdbHeaderInvisible.UseVisualStyleBackColor = true; 94 | this.rdbHeaderInvisible.CheckedChanged += new System.EventHandler(this.RdbHeaderInvisible_CheckedChanged); 95 | // 96 | // rdbAlways 97 | // 98 | this.rdbAlways.AutoSize = true; 99 | this.rdbAlways.Location = new System.Drawing.Point(29, 35); 100 | this.rdbAlways.Margin = new System.Windows.Forms.Padding(6); 101 | this.rdbAlways.Name = "rdbAlways"; 102 | this.rdbAlways.Size = new System.Drawing.Size(100, 29); 103 | this.rdbAlways.TabIndex = 8; 104 | this.rdbAlways.TabStop = true; 105 | this.rdbAlways.Text = "Always"; 106 | this.rdbAlways.UseVisualStyleBackColor = true; 107 | this.rdbAlways.CheckedChanged += new System.EventHandler(this.RdbAlways_CheckedChanged); 108 | // 109 | // cntNavigateMode 110 | // 111 | this.cntNavigateMode.Controls.Add(this.rdbCtrlClick); 112 | this.cntNavigateMode.Controls.Add(this.rdbDoubleClick); 113 | this.cntNavigateMode.Controls.Add(this.rdbSingleClick); 114 | this.cntNavigateMode.Location = new System.Drawing.Point(44, 179); 115 | this.cntNavigateMode.Margin = new System.Windows.Forms.Padding(6); 116 | this.cntNavigateMode.Name = "cntNavigateMode"; 117 | this.cntNavigateMode.Padding = new System.Windows.Forms.Padding(6); 118 | this.cntNavigateMode.Size = new System.Drawing.Size(367, 118); 119 | this.cntNavigateMode.TabIndex = 8; 120 | this.cntNavigateMode.TabStop = false; 121 | this.cntNavigateMode.Text = "Navigate on"; 122 | // 123 | // rdbCtrlClick 124 | // 125 | this.rdbCtrlClick.AutoSize = true; 126 | this.rdbCtrlClick.Location = new System.Drawing.Point(200, 35); 127 | this.rdbCtrlClick.Margin = new System.Windows.Forms.Padding(6); 128 | this.rdbCtrlClick.Name = "rdbCtrlClick"; 129 | this.rdbCtrlClick.Size = new System.Drawing.Size(144, 29); 130 | this.rdbCtrlClick.TabIndex = 8; 131 | this.rdbCtrlClick.TabStop = true; 132 | this.rdbCtrlClick.Text = "CTRL+Click"; 133 | this.rdbCtrlClick.UseVisualStyleBackColor = true; 134 | this.rdbCtrlClick.CheckedChanged += new System.EventHandler(this.RdbCtrlClick_CheckedChanged); 135 | // 136 | // rdbDoubleClick 137 | // 138 | this.rdbDoubleClick.AutoSize = true; 139 | this.rdbDoubleClick.Location = new System.Drawing.Point(29, 78); 140 | this.rdbDoubleClick.Margin = new System.Windows.Forms.Padding(6); 141 | this.rdbDoubleClick.Name = "rdbDoubleClick"; 142 | this.rdbDoubleClick.Size = new System.Drawing.Size(149, 29); 143 | this.rdbDoubleClick.TabIndex = 7; 144 | this.rdbDoubleClick.TabStop = true; 145 | this.rdbDoubleClick.Text = "Double-Click"; 146 | this.rdbDoubleClick.UseVisualStyleBackColor = true; 147 | this.rdbDoubleClick.CheckedChanged += new System.EventHandler(this.RdbDoubleClick_CheckedChanged); 148 | // 149 | // rdbSingleClick 150 | // 151 | this.rdbSingleClick.AutoSize = true; 152 | this.rdbSingleClick.Location = new System.Drawing.Point(29, 35); 153 | this.rdbSingleClick.Margin = new System.Windows.Forms.Padding(6); 154 | this.rdbSingleClick.Name = "rdbSingleClick"; 155 | this.rdbSingleClick.Size = new System.Drawing.Size(142, 29); 156 | this.rdbSingleClick.TabIndex = 6; 157 | this.rdbSingleClick.TabStop = true; 158 | this.rdbSingleClick.Text = "Single-Click"; 159 | this.rdbSingleClick.UseVisualStyleBackColor = true; 160 | this.rdbSingleClick.CheckedChanged += new System.EventHandler(this.RdbSingleClick_CheckedChanged); 161 | // 162 | // cntDisplayMode 163 | // 164 | this.cntDisplayMode.Controls.Add(this.rdbTextOnly); 165 | this.cntDisplayMode.Controls.Add(this.rdbIconOnly); 166 | this.cntDisplayMode.Controls.Add(this.rdbIconAndText); 167 | this.cntDisplayMode.Location = new System.Drawing.Point(44, 308); 168 | this.cntDisplayMode.Margin = new System.Windows.Forms.Padding(6); 169 | this.cntDisplayMode.Name = "cntDisplayMode"; 170 | this.cntDisplayMode.Padding = new System.Windows.Forms.Padding(6); 171 | this.cntDisplayMode.Size = new System.Drawing.Size(367, 162); 172 | this.cntDisplayMode.TabIndex = 9; 173 | this.cntDisplayMode.TabStop = false; 174 | this.cntDisplayMode.Text = "Display tag as"; 175 | // 176 | // rdbTextOnly 177 | // 178 | this.rdbTextOnly.AutoSize = true; 179 | this.rdbTextOnly.Location = new System.Drawing.Point(29, 120); 180 | this.rdbTextOnly.Margin = new System.Windows.Forms.Padding(6); 181 | this.rdbTextOnly.Name = "rdbTextOnly"; 182 | this.rdbTextOnly.Size = new System.Drawing.Size(117, 29); 183 | this.rdbTextOnly.TabIndex = 9; 184 | this.rdbTextOnly.TabStop = true; 185 | this.rdbTextOnly.Text = "Text only"; 186 | this.rdbTextOnly.UseVisualStyleBackColor = true; 187 | this.rdbTextOnly.CheckedChanged += new System.EventHandler(this.RdbTextOnly_CheckedChanged); 188 | // 189 | // rdbIconOnly 190 | // 191 | this.rdbIconOnly.AutoSize = true; 192 | this.rdbIconOnly.Location = new System.Drawing.Point(29, 78); 193 | this.rdbIconOnly.Margin = new System.Windows.Forms.Padding(6); 194 | this.rdbIconOnly.Name = "rdbIconOnly"; 195 | this.rdbIconOnly.Size = new System.Drawing.Size(115, 29); 196 | this.rdbIconOnly.TabIndex = 8; 197 | this.rdbIconOnly.TabStop = true; 198 | this.rdbIconOnly.Text = "Icon only"; 199 | this.rdbIconOnly.UseVisualStyleBackColor = true; 200 | this.rdbIconOnly.CheckedChanged += new System.EventHandler(this.RdbIconOnly_CheckedChanged); 201 | // 202 | // rdbIconAndText 203 | // 204 | this.rdbIconAndText.AutoSize = true; 205 | this.rdbIconAndText.Location = new System.Drawing.Point(29, 35); 206 | this.rdbIconAndText.Margin = new System.Windows.Forms.Padding(6); 207 | this.rdbIconAndText.Name = "rdbIconAndText"; 208 | this.rdbIconAndText.Size = new System.Drawing.Size(156, 29); 209 | this.rdbIconAndText.TabIndex = 7; 210 | this.rdbIconAndText.TabStop = true; 211 | this.rdbIconAndText.Text = "Icon and Text"; 212 | this.rdbIconAndText.UseVisualStyleBackColor = true; 213 | this.rdbIconAndText.CheckedChanged += new System.EventHandler(this.RdbIconAndText_CheckedChanged); 214 | // 215 | // cntInfo 216 | // 217 | this.cntInfo.Controls.Add(this.lblLink); 218 | this.cntInfo.Controls.Add(this.lblInfo); 219 | this.cntInfo.Dock = System.Windows.Forms.DockStyle.Bottom; 220 | this.cntInfo.Location = new System.Drawing.Point(0, 712); 221 | this.cntInfo.Margin = new System.Windows.Forms.Padding(6); 222 | this.cntInfo.Name = "cntInfo"; 223 | this.cntInfo.Padding = new System.Windows.Forms.Padding(6); 224 | this.cntInfo.Size = new System.Drawing.Size(807, 100); 225 | this.cntInfo.TabIndex = 10; 226 | this.cntInfo.TabStop = false; 227 | this.cntInfo.Text = "Info"; 228 | // 229 | // lblLink 230 | // 231 | this.lblLink.AutoSize = true; 232 | this.lblLink.LinkBehavior = System.Windows.Forms.LinkBehavior.AlwaysUnderline; 233 | this.lblLink.Location = new System.Drawing.Point(18, 63); 234 | this.lblLink.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0); 235 | this.lblLink.Name = "lblLink"; 236 | this.lblLink.Size = new System.Drawing.Size(157, 25); 237 | this.lblLink.TabIndex = 1; 238 | this.lblLink.TabStop = true; 239 | this.lblLink.Text = "PayPal Donation"; 240 | this.lblLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LblLink_LinkClicked); 241 | // 242 | // lblInfo 243 | // 244 | this.lblInfo.AutoSize = true; 245 | this.lblInfo.Location = new System.Drawing.Point(13, 31); 246 | this.lblInfo.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0); 247 | this.lblInfo.Name = "lblInfo"; 248 | this.lblInfo.Size = new System.Drawing.Size(645, 25); 249 | this.lblInfo.TabIndex = 0; 250 | this.lblInfo.Text = "This extension is 100% free to use. But you might buy me a drink or two ;)"; 251 | // 252 | // cntLanguages 253 | // 254 | this.cntLanguages.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 255 | | System.Windows.Forms.AnchorStyles.Left))); 256 | this.cntLanguages.Controls.Add(this.lviLanguages); 257 | this.cntLanguages.Controls.Add(this.lnkGitHub); 258 | this.cntLanguages.Controls.Add(this.lblSuggestMore); 259 | this.cntLanguages.Location = new System.Drawing.Point(434, 112); 260 | this.cntLanguages.Margin = new System.Windows.Forms.Padding(6); 261 | this.cntLanguages.Name = "cntLanguages"; 262 | this.cntLanguages.Padding = new System.Windows.Forms.Padding(6); 263 | this.cntLanguages.Size = new System.Drawing.Size(345, 589); 264 | this.cntLanguages.TabIndex = 10; 265 | this.cntLanguages.TabStop = false; 266 | this.cntLanguages.Text = "Enable for code type"; 267 | // 268 | // lviLanguages 269 | // 270 | this.lviLanguages.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 271 | | System.Windows.Forms.AnchorStyles.Left))); 272 | this.lviLanguages.FormattingEnabled = true; 273 | this.lviLanguages.Items.AddRange(new object[] { 274 | "Dummy"}); 275 | this.lviLanguages.Location = new System.Drawing.Point(11, 35); 276 | this.lviLanguages.Margin = new System.Windows.Forms.Padding(6); 277 | this.lviLanguages.Name = "lviLanguages"; 278 | this.lviLanguages.Size = new System.Drawing.Size(319, 472); 279 | this.lviLanguages.TabIndex = 11; 280 | this.lviLanguages.ItemCheck += new System.Windows.Forms.ItemCheckEventHandler(this.LviLanguages_ItemCheck); 281 | // 282 | // lnkGitHub 283 | // 284 | this.lnkGitHub.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 285 | this.lnkGitHub.AutoSize = true; 286 | this.lnkGitHub.LinkBehavior = System.Windows.Forms.LinkBehavior.AlwaysUnderline; 287 | this.lnkGitHub.Location = new System.Drawing.Point(11, 554); 288 | this.lnkGitHub.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0); 289 | this.lnkGitHub.Name = "lnkGitHub"; 290 | this.lnkGitHub.Size = new System.Drawing.Size(141, 25); 291 | this.lnkGitHub.TabIndex = 12; 292 | this.lnkGitHub.TabStop = true; 293 | this.lnkGitHub.Text = "Visit on GitHub"; 294 | this.lnkGitHub.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.LnkGitHub_LinkClicked); 295 | // 296 | // lblSuggestMore 297 | // 298 | this.lblSuggestMore.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 299 | this.lblSuggestMore.AutoSize = true; 300 | this.lblSuggestMore.Location = new System.Drawing.Point(7, 519); 301 | this.lblSuggestMore.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0); 302 | this.lblSuggestMore.Name = "lblSuggestMore"; 303 | this.lblSuggestMore.Size = new System.Drawing.Size(232, 25); 304 | this.lblSuggestMore.TabIndex = 11; 305 | this.lblSuggestMore.Text = "Feel free to suggest more"; 306 | // 307 | // lblFont 308 | // 309 | this.lblFont.AutoSize = true; 310 | this.lblFont.LinkBehavior = System.Windows.Forms.LinkBehavior.AlwaysUnderline; 311 | this.lblFont.Location = new System.Drawing.Point(445, 59); 312 | this.lblFont.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0); 313 | this.lblFont.Name = "lblFont"; 314 | this.lblFont.Size = new System.Drawing.Size(233, 25); 315 | this.lblFont.TabIndex = 13; 316 | this.lblFont.TabStop = true; 317 | this.lblFont.Text = "Change font, size or color"; 318 | this.lblFont.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lblFont_LinkClicked); 319 | // 320 | // CBEOptionPageControl 321 | // 322 | this.AutoScaleDimensions = new System.Drawing.SizeF(11F, 24F); 323 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 324 | this.Controls.Add(this.lblFont); 325 | this.Controls.Add(this.cntLanguages); 326 | this.Controls.Add(this.cntInfo); 327 | this.Controls.Add(this.cntDisplayMode); 328 | this.Controls.Add(this.cntNavigateMode); 329 | this.Controls.Add(this.cntVisibilityMode); 330 | this.Controls.Add(this.chkCBETaggerEnabled); 331 | this.Margin = new System.Windows.Forms.Padding(6); 332 | this.MinimumSize = new System.Drawing.Size(807, 812); 333 | this.Name = "CBEOptionPageControl"; 334 | this.Size = new System.Drawing.Size(807, 812); 335 | this.cntVisibilityMode.ResumeLayout(false); 336 | this.cntVisibilityMode.PerformLayout(); 337 | this.cntNavigateMode.ResumeLayout(false); 338 | this.cntNavigateMode.PerformLayout(); 339 | this.cntDisplayMode.ResumeLayout(false); 340 | this.cntDisplayMode.PerformLayout(); 341 | this.cntInfo.ResumeLayout(false); 342 | this.cntInfo.PerformLayout(); 343 | this.cntLanguages.ResumeLayout(false); 344 | this.cntLanguages.PerformLayout(); 345 | this.ResumeLayout(false); 346 | this.PerformLayout(); 347 | 348 | } 349 | 350 | #endregion 351 | 352 | private System.Windows.Forms.CheckBox chkCBETaggerEnabled; 353 | private System.Windows.Forms.GroupBox cntVisibilityMode; 354 | private System.Windows.Forms.RadioButton rdbHeaderInvisible; 355 | private System.Windows.Forms.RadioButton rdbAlways; 356 | private System.Windows.Forms.GroupBox cntNavigateMode; 357 | private System.Windows.Forms.RadioButton rdbDoubleClick; 358 | private System.Windows.Forms.RadioButton rdbSingleClick; 359 | private System.Windows.Forms.GroupBox cntDisplayMode; 360 | private System.Windows.Forms.RadioButton rdbTextOnly; 361 | private System.Windows.Forms.RadioButton rdbIconOnly; 362 | private System.Windows.Forms.RadioButton rdbIconAndText; 363 | private System.Windows.Forms.GroupBox cntInfo; 364 | private System.Windows.Forms.LinkLabel lblLink; 365 | private System.Windows.Forms.Label lblInfo; 366 | private System.Windows.Forms.GroupBox cntLanguages; 367 | private System.Windows.Forms.LinkLabel lnkGitHub; 368 | private System.Windows.Forms.Label lblSuggestMore; 369 | private System.Windows.Forms.CheckedListBox lviLanguages; 370 | private System.Windows.Forms.RadioButton rdbCtrlClick; 371 | private System.Windows.Forms.LinkLabel lblFont; 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /CBEOptionPageControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Windows.Forms; 4 | using Microsoft.Internal.VisualStudio.Shell.Interop; 5 | 6 | namespace CodeBlockEndTag 7 | { 8 | public partial class CBEOptionPageControl : UserControl 9 | { 10 | private const string DonateUrl = "https://www.paypal.com/donate?hosted_button_id=37PBGZPHXY8EC"; 11 | private const string GitHubUrl = "https://github.com/KhaosCoders/VSCodeBlockEndTag"; 12 | 13 | public CBEOptionPageControl() 14 | { 15 | InitializeComponent(); 16 | } 17 | 18 | internal CBEOptionPage optionsPage; 19 | 20 | public void Initialize() 21 | { 22 | chkCBETaggerEnabled.Checked = optionsPage.CBETaggerEnabled; 23 | rdbAlways.Checked = optionsPage.CBEVisibilityMode == (int)CBEOptionPage.VisibilityModes.Always; 24 | rdbHeaderInvisible.Checked = optionsPage.CBEVisibilityMode == (int)CBEOptionPage.VisibilityModes.HeaderNotVisible; 25 | 26 | rdbSingleClick.Checked = optionsPage.CBEClickMode == (int)CBEOptionPage.ClickMode.SingleClick; 27 | rdbDoubleClick.Checked = optionsPage.CBEClickMode == (int)CBEOptionPage.ClickMode.DoubleClick; 28 | rdbCtrlClick.Checked = optionsPage.CBEClickMode == (int)CBEOptionPage.ClickMode.CtrlClick; 29 | 30 | rdbIconAndText.Checked = optionsPage.CBEDisplayMode == (int)CBEOptionPage.DisplayModes.IconAndText; 31 | rdbIconOnly.Checked = optionsPage.CBEDisplayMode == (int)CBEOptionPage.DisplayModes.Icon; 32 | rdbTextOnly.Checked = optionsPage.CBEDisplayMode == (int)CBEOptionPage.DisplayModes.Text; 33 | 34 | lviLanguages.Items.Clear(); 35 | string[] langs = optionsPage.SupportedLangDisplayNames; 36 | for (int i = 0; i < langs.Length; i++) 37 | { 38 | lviLanguages.Items.Add(langs[i]); 39 | if (optionsPage.SupportedLangActive[i]) 40 | { 41 | lviLanguages.SetItemChecked(i, true); 42 | } 43 | } 44 | } 45 | 46 | private void LblLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 47 | { 48 | Process.Start(new ProcessStartInfo(DonateUrl)); 49 | } 50 | 51 | private void LnkGitHub_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 52 | { 53 | Process.Start(new ProcessStartInfo(GitHubUrl)); 54 | } 55 | 56 | private void ChkCBETaggerEnabled_CheckedChanged(object sender, EventArgs e) 57 | { 58 | optionsPage.CBETaggerEnabled = chkCBETaggerEnabled.Checked; 59 | } 60 | 61 | private void RdbAlways_CheckedChanged(object sender, EventArgs e) 62 | { 63 | if (rdbAlways.Checked) 64 | { 65 | optionsPage.CBEVisibilityMode = (int)CBEOptionPage.VisibilityModes.Always; 66 | } 67 | } 68 | 69 | private void RdbHeaderInvisible_CheckedChanged(object sender, EventArgs e) 70 | { 71 | if (rdbHeaderInvisible.Checked) 72 | { 73 | optionsPage.CBEVisibilityMode = (int)CBEOptionPage.VisibilityModes.HeaderNotVisible; 74 | } 75 | } 76 | 77 | private void RdbSingleClick_CheckedChanged(object sender, EventArgs e) 78 | { 79 | if (rdbSingleClick.Checked) 80 | { 81 | optionsPage.CBEClickMode = (int)CBEOptionPage.ClickMode.SingleClick; 82 | } 83 | } 84 | 85 | private void RdbDoubleClick_CheckedChanged(object sender, EventArgs e) 86 | { 87 | if (rdbDoubleClick.Checked) 88 | { 89 | optionsPage.CBEClickMode = (int)CBEOptionPage.ClickMode.DoubleClick; 90 | } 91 | } 92 | 93 | private void RdbCtrlClick_CheckedChanged(object sender, EventArgs e) 94 | { 95 | if (rdbCtrlClick.Checked) 96 | { 97 | optionsPage.CBEClickMode = (int)CBEOptionPage.ClickMode.CtrlClick; 98 | } 99 | } 100 | 101 | private void RdbIconAndText_CheckedChanged(object sender, EventArgs e) 102 | { 103 | if (rdbIconAndText.Checked) 104 | { 105 | optionsPage.CBEDisplayMode = (int)CBEOptionPage.DisplayModes.IconAndText; 106 | } 107 | } 108 | 109 | private void RdbIconOnly_CheckedChanged(object sender, EventArgs e) 110 | { 111 | if (rdbIconOnly.Checked) 112 | { 113 | optionsPage.CBEDisplayMode = (int)CBEOptionPage.DisplayModes.Icon; 114 | } 115 | } 116 | 117 | private void RdbTextOnly_CheckedChanged(object sender, EventArgs e) 118 | { 119 | if (rdbTextOnly.Checked) 120 | { 121 | optionsPage.CBEDisplayMode = (int)CBEOptionPage.DisplayModes.Text; 122 | } 123 | } 124 | 125 | private void LviLanguages_ItemCheck(object sender, ItemCheckEventArgs e) 126 | { 127 | optionsPage.SetSupportedLangActive(e.Index, e.NewValue == CheckState.Checked); 128 | } 129 | 130 | private void lblFont_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 131 | { 132 | CBETagPackage.Instance.ShowOptionPage(typeof(FontAndColorsOptionPageDummy)); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /CBEOptionPageControl.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /CBETagControl.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 19 | 75 | 76 | -------------------------------------------------------------------------------- /CBETagControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Imaging; 2 | using Microsoft.VisualStudio.Imaging.Interop; 3 | using System; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Input; 7 | using System.Windows.Media; 8 | using System.Windows.Threading; 9 | using Size = System.Windows.Size; 10 | 11 | namespace CodeBlockEndTag 12 | { 13 | /// 14 | /// Interaction logic for CBETagControl.xaml 15 | /// 16 | public partial class CBETagControl : UserControl 17 | { 18 | public string Text 19 | { 20 | get => (string)GetValue(TextProperty); 21 | set => SetValue(TextProperty, value); 22 | } 23 | public static readonly DependencyProperty TextProperty = 24 | DependencyProperty.Register("Text", typeof(string), typeof(CBETagControl), new PropertyMetadata("Unknown")); 25 | 26 | public object TextColor 27 | { 28 | get => (object)GetValue(TextColorProperty); 29 | set => SetValue(TextColorProperty, value); 30 | } 31 | public static readonly DependencyProperty TextColorProperty = DependencyProperty.Register( 32 | "TextColor", typeof(object), typeof(CBETagControl), new PropertyMetadata(Colors.Black)); 33 | 34 | public ImageMoniker IconMoniker 35 | { 36 | get => (ImageMoniker)GetValue(IconMonikerProperty); 37 | set => SetValue(IconMonikerProperty, value); 38 | } 39 | public static readonly DependencyProperty IconMonikerProperty = 40 | DependencyProperty.Register("IconMoniker", typeof(ImageMoniker), typeof(CBETagControl), new PropertyMetadata(KnownMonikers.QuestionMark)); 41 | 42 | public int DisplayMode 43 | { 44 | get => (int)GetValue(DisplayModeProperty); 45 | set => SetValue(DisplayModeProperty, value); 46 | } 47 | public static readonly DependencyProperty DisplayModeProperty = 48 | DependencyProperty.Register("DisplayMode", typeof(int), typeof(CBETagControl), new PropertyMetadata(0)); 49 | 50 | public double LineHeight 51 | { 52 | get => (double)GetValue(LineHeightProperty); 53 | set => SetValue(LineHeightProperty, value); 54 | } 55 | public static readonly DependencyProperty LineHeightProperty = 56 | DependencyProperty.Register("LineHeight", typeof(double), typeof(CBETagControl), new PropertyMetadata(9d)); 57 | 58 | internal CBAdornmentData AdornmentData { get; set; } 59 | 60 | internal delegate void TagClickHandler(CBAdornmentData adornment, bool jumpToHead); 61 | internal event TagClickHandler TagClicked; 62 | 63 | public CBETagControl() 64 | { 65 | DataContext = this; 66 | InitializeComponent(); 67 | } 68 | 69 | protected override Size MeasureOverride(Size constraint) 70 | { 71 | var paddingRight = LineHeight / 2; 72 | if (DisplayMode == 2) 73 | { 74 | // No label 75 | return new Size(LineHeight + 4 + paddingRight, LineHeight); 76 | } 77 | 78 | TextBlock tb = btnTag.Template.FindName("txtTag", btnTag) as TextBlock; 79 | return new Size(8 + LineHeight + (tb?.ActualWidth ?? 0) + paddingRight, LineHeight); 80 | } 81 | 82 | private DispatcherTimer buttonSingleClickTimeout; 83 | private bool buttonModifiersPressed; 84 | 85 | private void ButtonSingleClick(object sender, EventArgs e) 86 | { 87 | buttonSingleClickTimeout.Stop(); 88 | ButtonClicked(1); 89 | } 90 | 91 | private void Button_Click(object sender, RoutedEventArgs e) 92 | { 93 | buttonModifiersPressed = CheckModifiers(); 94 | if (buttonSingleClickTimeout == null) 95 | { 96 | buttonSingleClickTimeout = 97 | new DispatcherTimer( 98 | TimeSpan.FromSeconds(0.25), 99 | DispatcherPriority.Background, 100 | ButtonSingleClick, 101 | Dispatcher.CurrentDispatcher); 102 | } 103 | 104 | buttonSingleClickTimeout.Start(); 105 | } 106 | 107 | private void Button_DoubleClick(object sender, MouseButtonEventArgs e) 108 | { 109 | buttonSingleClickTimeout.Stop(); 110 | e.Handled = true; 111 | ButtonClicked(2); 112 | } 113 | 114 | private void ButtonClicked(int clickCount) 115 | { 116 | int neededClickCount = CBETagPackage.CBEClickMode switch 117 | { 118 | (int)CBEOptionPage.ClickMode.SingleClick or (int)CBEOptionPage.ClickMode.CtrlClick => 1, 119 | (int)CBEOptionPage.ClickMode.DoubleClick => 2, 120 | _ => 0, 121 | }; 122 | if (AdornmentData != null) 123 | { 124 | bool jumpToHead = (clickCount >= neededClickCount) && buttonModifiersPressed; 125 | TagClicked?.Invoke(AdornmentData, jumpToHead); 126 | } 127 | } 128 | 129 | private bool CheckModifiers() 130 | { 131 | return CBETagPackage.CBEClickMode != (int)CBEOptionPage.ClickMode.CtrlClick 132 | || Keyboard.IsKeyDown(Key.LeftCtrl) 133 | || Keyboard.IsKeyDown(Key.RightCtrl); 134 | } 135 | 136 | private void TxtTag_OnInitialized(object sender, EventArgs e) 137 | { 138 | this.InvalidateMeasure(); 139 | 140 | TextBlock tb = btnTag.Template.FindName("txtTag", btnTag) as TextBlock; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /CBETagPackage.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Copyright (c) Company. All rights reserved. 4 | // 5 | //------------------------------------------------------------------------------ 6 | 7 | using System; 8 | using System.Runtime.InteropServices; 9 | using Microsoft.VisualStudio; 10 | using Microsoft.VisualStudio.Shell; 11 | using Microsoft.VisualStudio.Utilities; 12 | using Microsoft.VisualStudio.Shell.Interop; 13 | using System.Collections.Generic; 14 | using System.ComponentModel.Design; 15 | using System.Globalization; 16 | using System.Threading; 17 | using System.Threading.Tasks; 18 | using EnvDTE; 19 | using EnvDTE80; 20 | using Microsoft.Win32; 21 | 22 | namespace CodeBlockEndTag 23 | { 24 | /// 25 | /// This is the class that implements the package exposed by this assembly. 26 | /// 27 | /// 28 | /// 29 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 30 | /// is to implement the IVsPackage interface and register itself with the shell. 31 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 32 | /// to do it: it derives from the Package class that provides the implementation of the 33 | /// IVsPackage interface and uses the registration attributes defined in the framework to 34 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 35 | /// utility what data to put into .pkgdef file. 36 | /// 37 | /// 38 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 39 | /// 40 | /// 41 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 42 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About 43 | [Guid(CBETagPackage.PackageGuidString)] 44 | // Add OptionPage to package 45 | [ProvideOptionPage(typeof(CBEOptionPage), "KC Extensions", "CodeBlock End Tagger", 113, 114, true)] 46 | // Load package at every (including none) project type 47 | [ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)] 48 | [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string, PackageAutoLoadFlags.BackgroundLoad)] 49 | [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionHasMultipleProjects_string, PackageAutoLoadFlags.BackgroundLoad)] 50 | [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionHasSingleProject_string, PackageAutoLoadFlags.BackgroundLoad)] 51 | [ProvideService(typeof(IFontAndColorDefaultsProvider))] 52 | [FontAndColorRegistration(typeof(IFontAndColorDefaultsProvider), Shell.FontAndColorDefaultsCSharpTags.CategoryNameString, Shell.FontAndColorDefaultsCSharpTags.CategoryGuidString)] 53 | public sealed class CBETagPackage : AsyncPackage, IVsFontAndColorDefaultsProvider, IFontAndColorDefaultsProvider 54 | { 55 | /// 56 | /// CBETagPackage GUID string. 57 | /// 58 | public const string PackageGuidString = "D7C91E0F-240B-4605-9F35-ACCF63A68623"; 59 | 60 | public delegate void PackageOptionChangedHandler(object sender); 61 | /// 62 | /// Event fired if any option in the OptionPage is changed 63 | /// 64 | public event PackageOptionChangedHandler PackageOptionChanged; 65 | 66 | /// 67 | /// Gets the singelton instance of the class 68 | /// 69 | public static CBETagPackage Instance { get; private set; } 70 | 71 | /// 72 | /// Reference on the package's option page 73 | /// 74 | private static CBEOptionPage _optionPage; 75 | 76 | /// 77 | /// Instance of ActivityLog 78 | /// 79 | public IVsActivityLog Log { get; private set; } 80 | 81 | /// 82 | /// Initializes a new instance of the class. 83 | /// 84 | public CBETagPackage() 85 | { 86 | Instance = this; 87 | } 88 | 89 | /// 90 | /// Gets a list of all possible content types in VisualStudio 91 | /// 92 | public static IList ContentTypes { get; private set; } 93 | 94 | /// 95 | /// Load the list of content types 96 | /// 97 | internal static void ReadContentTypes(IContentTypeRegistryService ContentTypeRegistryService) 98 | { 99 | if (ContentTypes != null) return; 100 | ContentTypes = new List(); 101 | foreach (var ct in ContentTypeRegistryService.ContentTypes) 102 | { 103 | if (ct.IsOfType("code")) 104 | ContentTypes.Add(ct); 105 | } 106 | } 107 | 108 | public static bool IsLanguageSupported(string lang) => _optionPage?.IsLanguageSupported(lang) ?? false; 109 | 110 | #region Option Values 111 | 112 | public static int CBEDisplayMode => _optionPage?.CBEDisplayMode ?? (int)CBEOptionPage.DisplayModes.IconAndText; 113 | 114 | public static int CBEVisibilityMode => _optionPage?.CBEVisibilityMode ?? (int)CBEOptionPage.VisibilityModes.Always; 115 | 116 | public static bool CBETaggerEnabled => _optionPage?.CBETaggerEnabled ?? false; 117 | 118 | public static int CBEClickMode => _optionPage?.CBEClickMode ?? (int)CBEOptionPage.ClickMode.DoubleClick; 119 | 120 | #endregion 121 | 122 | #region VS Build Version 123 | 124 | public static async Task GetVsVersionAsync() 125 | { 126 | DTE2 dte = (DTE2)await Instance.GetServiceAsync(typeof(DTE)); 127 | 128 | if (!double.TryParse(dte.Version, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture.NumberFormat, out double version)) 129 | { 130 | throw new Exception("Can't read Visual Studio Version!"); 131 | } 132 | return version; 133 | } 134 | 135 | public static async Task GetVsRegistryRootAsync() 136 | { 137 | DTE2 dte = (DTE2)await Instance.GetServiceAsync(typeof(DTE)); 138 | return dte.RegistryRoot; 139 | } 140 | 141 | #endregion 142 | 143 | #region Package Members 144 | 145 | /// 146 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 147 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 148 | /// 149 | protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 150 | { 151 | await base.InitializeAsync(cancellationToken, progress).ConfigureAwait(false); 152 | 153 | // Switches to the UI thread in order to consume some services used in command initialization 154 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 155 | 156 | Log = await GetServiceAsync(typeof(SVsActivityLog)) as IVsActivityLog; 157 | if (Log == null) 158 | { 159 | return; 160 | } 161 | 162 | Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), "InitializeAsync"); 163 | 164 | // ensure that we have instance 165 | new Shell.FontAndColorDefaultsCSharpTags(); 166 | 167 | Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), "Register IFontAndColorDefaultsProvider"); 168 | ((IServiceContainer)this).AddService(typeof(IFontAndColorDefaultsProvider), this, true); 169 | 170 | _optionPage = (CBEOptionPage)Instance.GetDialogPage(typeof(CBEOptionPage)); 171 | _optionPage.OptionChanged += Page_OptionChanged; 172 | 173 | // Update taggers, that were initialized before the package 174 | PackageOptionChanged?.Invoke(this); 175 | 176 | SubscribeForColorChangeEvents(); 177 | 178 | Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), "InitializeAsync ended"); 179 | } 180 | 181 | protected override void Dispose(bool disposing) 182 | { 183 | UnsubscribeFromColorChangeEvents(); 184 | 185 | base.Dispose(disposing); 186 | } 187 | 188 | private void Page_OptionChanged(object sender) => PackageOptionChanged?.Invoke(this); 189 | 190 | private static void FontAndColorsChanged(object sender, EventArgs args) 191 | { 192 | ThreadHelper.ThrowIfNotOnUIThread(); 193 | 194 | Shell.FontAndColorDefaultsCSharpTags.Instance.ReloadFontAndColors(); 195 | } 196 | 197 | private static void SubscribeForColorChangeEvents() 198 | { 199 | SystemEvents.DisplaySettingsChanged += FontAndColorsChanged; 200 | SystemEvents.PaletteChanged += FontAndColorsChanged; 201 | SystemEvents.UserPreferenceChanged += FontAndColorsChanged; 202 | } 203 | 204 | private static void UnsubscribeFromColorChangeEvents() 205 | { 206 | SystemEvents.DisplaySettingsChanged -= FontAndColorsChanged; 207 | SystemEvents.PaletteChanged -= FontAndColorsChanged; 208 | SystemEvents.UserPreferenceChanged -= FontAndColorsChanged; 209 | } 210 | 211 | #endregion 212 | 213 | #region IVsFontAndColorDefaultsProvider 214 | public int GetObject(ref Guid rguidCategory, out object ppObj) 215 | { 216 | Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), $"GetObject {rguidCategory}"); 217 | ThreadHelper.ThrowIfNotOnUIThread(); 218 | 219 | ppObj = rguidCategory == Shell.FontAndColorDefaultsCSharpTags.Instance.CategoryGuid ? Shell.FontAndColorDefaultsCSharpTags.Instance : null; 220 | 221 | Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), $"GetObject {rguidCategory} obj: {ppObj}"); 222 | return 0; 223 | } 224 | #endregion 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /CBETagger.cs: -------------------------------------------------------------------------------- 1 | // Thanks to https://github.com/jaredpar/ControlCharAdornmentSample/blob/master/CharDisplayTaggerSource.cs 2 | 3 | using CodeBlockEndTag.Shell; 4 | using Microsoft.VisualStudio.Text; 5 | using Microsoft.VisualStudio.Text.Editor; 6 | using Microsoft.VisualStudio.Text.Operations; 7 | using Microsoft.VisualStudio.Text.Tagging; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using System.Linq; 12 | using System.Windows.Controls; 13 | 14 | namespace CodeBlockEndTag 15 | { 16 | /// f 17 | /// This tagger provides editor tags that are inserted into the TextView (IntraTextAdornmentTags) 18 | /// The tags are added after each code block encapsulated by curly bracets: { ... } 19 | /// The tags will show the code blocks condition, or whatever serves as header for the block 20 | /// By clicking on a tag, the editor will jump to that code blocks header 21 | /// 22 | internal class CBETagger : ITagger, IDisposable 23 | { 24 | private static readonly ReadOnlyCollection> EmptyTagColllection = 25 | new(new List>()); 26 | 27 | #region Properties & Fields 28 | 29 | // EventHandler for ITagger tags changed event 30 | private EventHandler _changedEvent; 31 | 32 | /// 33 | /// Service by VisualStudio for fast navigation in structured texts 34 | /// 35 | private readonly ITextStructureNavigator _TextStructureNavigator; 36 | 37 | /// 38 | /// The TextView this tagger is assigned to 39 | /// 40 | private readonly IWpfTextView _TextView; 41 | 42 | /// 43 | /// This is a list of already created adornment tags used as cache 44 | /// 45 | private readonly List _adornmentCache = new(); 46 | 47 | /// 48 | /// This is the visible span of the textview 49 | /// 50 | private Span? _VisibleSpan; 51 | 52 | /// 53 | /// Is set, when the instance is disposed 54 | /// 55 | private bool _Disposed; 56 | 57 | #endregion 58 | 59 | #region Ctor 60 | 61 | /// 62 | /// Creates a new instance of CBRTagger 63 | /// 64 | /// the CBETaggerProvider that created the tagger 65 | /// the WpfTextView this tagger is assigned to 66 | /// the TextBuffer this tagger should work with 67 | internal CBETagger(CBETaggerProvider provider, IWpfTextView textView) 68 | { 69 | if (provider == null || textView == null) 70 | throw new ArgumentNullException("The arguments of CBETagger can't be null"); 71 | 72 | _TextView = textView; 73 | 74 | // Getting services provided by VisualStudio 75 | _TextStructureNavigator = provider.GetTextStructureNavigator(_TextView.TextBuffer); 76 | 77 | // Hook up events 78 | _TextView.TextBuffer.Changed += TextBuffer_Changed; 79 | _TextView.LayoutChanged += OnTextViewLayoutChanged; 80 | _TextView.Caret.PositionChanged += Caret_PositionChanged; 81 | 82 | // Listen for package events 83 | InitializePackage(); 84 | } 85 | 86 | #endregion 87 | 88 | #region TextBuffer changed 89 | 90 | private void Caret_PositionChanged(object sender, CaretPositionChangedEventArgs e) 91 | { 92 | var start = Math.Min(e.OldPosition.BufferPosition.Position, e.NewPosition.BufferPosition.Position); 93 | var end = Math.Max(e.OldPosition.BufferPosition.Position, e.NewPosition.BufferPosition.Position); 94 | if (start != end) 95 | { 96 | InvalidateSpan(new Span(start, end - start), false); 97 | } 98 | } 99 | 100 | private void TextBuffer_Changed(object sender, TextContentChangedEventArgs e) 101 | { 102 | foreach (var textChange in e.Changes) 103 | { 104 | OnTextChanged(textChange); 105 | } 106 | } 107 | 108 | private void OnTextChanged(ITextChange textChange) 109 | { 110 | // remove or update tags in adornment cache 111 | List remove = new(); 112 | foreach (var adornment in _adornmentCache) 113 | { 114 | if (!(adornment.HeaderStartPosition > textChange.OldEnd || adornment.EndPosition < textChange.OldPosition)) 115 | { 116 | remove.Add(adornment); 117 | } 118 | else if (adornment.HeaderStartPosition > textChange.OldEnd) 119 | { 120 | adornment.HeaderStartPosition += textChange.Delta; 121 | adornment.StartPosition += textChange.Delta; 122 | adornment.EndPosition += textChange.Delta; 123 | } 124 | } 125 | 126 | foreach (var adornment in remove) 127 | { 128 | RemoveFromCache(adornment); 129 | } 130 | } 131 | 132 | private void RemoveFromCache(CBAdornmentData adornment) 133 | { 134 | if (adornment.Adornment is CBETagControl tag) 135 | { 136 | tag.TagClicked -= Adornment_TagClicked; 137 | } 138 | _adornmentCache.Remove(adornment); 139 | } 140 | 141 | #endregion 142 | 143 | #region ITagger 144 | 145 | IEnumerable> ITagger.GetTags(NormalizedSnapshotSpanCollection spans) 146 | { 147 | // Check if content type (language) is supported and active for tagging 148 | if (CBETagPackage.IsLanguageSupported(_TextView.TextBuffer.ContentType.TypeName)) 149 | { 150 | // Second chance to hook up events 151 | InitializePackage(); 152 | 153 | foreach (var span in spans) 154 | { 155 | var tags = GetTags(span); 156 | foreach (var tag in tags) 157 | { 158 | yield return tag; 159 | } 160 | } 161 | } 162 | } 163 | 164 | event EventHandler ITagger.TagsChanged 165 | { 166 | add { _changedEvent += value; } 167 | remove { _changedEvent -= value; } 168 | } 169 | 170 | #endregion 171 | 172 | #region Tag placement 173 | 174 | internal ReadOnlyCollection> GetTags(SnapshotSpan span) 175 | { 176 | if (!CBETagPackage.CBETaggerEnabled || 177 | span.Snapshot != _TextView.TextBuffer.CurrentSnapshot || 178 | span.Length == 0) 179 | { 180 | return EmptyTagColllection; 181 | } 182 | 183 | // if big span, return only tags for visible area 184 | if (span.Length > 1000 && _VisibleSpan != null && _VisibleSpan.HasValue) 185 | { 186 | var overlap = span.Overlap(_VisibleSpan.Value); 187 | if (overlap != null && overlap.HasValue) 188 | { 189 | span = overlap.Value; 190 | if (span.Length == 0) 191 | return EmptyTagColllection; 192 | } 193 | } 194 | 195 | return GetTagsCore(span); 196 | } 197 | 198 | #if DEBUG 199 | private System.Diagnostics.Stopwatch _watch; 200 | #endif 201 | private ReadOnlyCollection> GetTagsCore(SnapshotSpan span) 202 | { 203 | var list = new List>(); 204 | var offset = span.Start.Position; 205 | var snapshot = span.Snapshot; 206 | 207 | // vars used in loop 208 | SnapshotSpan cbSpan; 209 | CBAdornmentData cbAdornmentData; 210 | CBETagControl tagElement; 211 | int cbStartPosition; 212 | int cbEndPosition; 213 | int cbHeaderPosition; 214 | string cbHeader; 215 | IntraTextAdornmentTag cbTag; 216 | SnapshotSpan cbSnapshotSpan; 217 | TagSpan cbTagSpan; 218 | bool isSingleLineComment = false; 219 | bool isMultiLineComment = false; 220 | 221 | #if DEBUG 222 | // Stop time 223 | _watch ??= new System.Diagnostics.Stopwatch(); 224 | _watch.Restart(); 225 | #endif 226 | 227 | try 228 | { 229 | // Find all closing bracets 230 | for (int i = 0; i < span.Length; i++) 231 | { 232 | var position = i + offset; 233 | var chr = snapshot[position]; 234 | 235 | // Skip comments 236 | switch (chr) 237 | { 238 | case '/': 239 | if (position > 0) 240 | { 241 | if (snapshot[position - 1] == '/') 242 | isSingleLineComment = true; 243 | if (snapshot[position - 1] == '*') 244 | { 245 | if (!isMultiLineComment) 246 | { 247 | // Multiline comment was not started in this span 248 | // Every tag until now was inside a comment 249 | foreach (var tag in list) 250 | { 251 | RemoveFromCache((tag.Tag.Adornment as CBETagControl)?.AdornmentData); 252 | } 253 | list.Clear(); 254 | } 255 | isMultiLineComment = false; 256 | } 257 | } 258 | break; 259 | case '*': 260 | if (position > 0 && snapshot[position - 1] == '/') 261 | isMultiLineComment = true; 262 | break; 263 | case (char)10: 264 | isSingleLineComment = false; 265 | break; 266 | case (char)13: 267 | isSingleLineComment = false; 268 | break; 269 | } 270 | 271 | if (chr != '}' || isSingleLineComment || isMultiLineComment) 272 | continue; 273 | 274 | // getting start and end position of code block 275 | cbEndPosition = position; 276 | if (position >= 0 && snapshot[position - 1] == '{') 277 | { 278 | // empty code block {} 279 | cbStartPosition = position - 1; 280 | cbSpan = new SnapshotSpan(snapshot, cbStartPosition, cbEndPosition - cbStartPosition); 281 | } 282 | else 283 | { 284 | // create inner span to navigate to get code block start 285 | cbSpan = _TextStructureNavigator.GetSpanOfEnclosing(new SnapshotSpan(snapshot, position - 1, 1)); 286 | cbStartPosition = cbSpan.Start; 287 | } 288 | 289 | // Don't display tag for code blocks on same line 290 | if (!snapshot.GetText(cbSpan).Contains('\n')) 291 | continue; 292 | 293 | // getting the code blocks header 294 | cbHeaderPosition = -1; 295 | if (snapshot[cbStartPosition] == '{') 296 | { 297 | // cbSpan does not contain the header 298 | cbHeader = GetCodeBlockHeader(cbSpan, out cbHeaderPosition); 299 | } 300 | else 301 | { 302 | // cbSpan does contain the header 303 | cbHeader = GetCodeBlockHeader(cbSpan, out cbHeaderPosition, position); 304 | } 305 | 306 | // Trim header 307 | if (!string.IsNullOrEmpty(cbHeader)) 308 | { 309 | cbHeader = cbHeader.Trim() 310 | .Replace(Environment.NewLine, "") 311 | .Replace('\t', ' '); 312 | // Strip unnecessary spaces 313 | while (cbHeader.Contains(" ")) 314 | { 315 | cbHeader = cbHeader.Replace(" ", " "); 316 | } 317 | } 318 | 319 | // Skip tag if option "only when header not visible" 320 | if (_VisibleSpan != null && !IsTagVisible(cbHeaderPosition, cbEndPosition, _VisibleSpan, snapshot)) 321 | { 322 | continue; 323 | } 324 | 325 | var iconMoniker = Microsoft.VisualStudio.Imaging.KnownMonikers.QuestionMark; 326 | if (CBETagPackage.CBEDisplayMode != (int)CBEOptionPage.DisplayModes.Text && 327 | !string.IsNullOrWhiteSpace(cbHeader) && !cbHeader.Contains("{")) 328 | { 329 | iconMoniker = IconMonikerSelector.SelectMoniker(cbHeader); 330 | } 331 | 332 | // use cache or create new tag 333 | cbAdornmentData = _adornmentCache 334 | .Find(o => 335 | o.StartPosition == cbStartPosition && 336 | o.EndPosition == cbEndPosition); 337 | 338 | if (cbAdornmentData?.Adornment != null) 339 | { 340 | tagElement = cbAdornmentData.Adornment as CBETagControl; 341 | } 342 | else 343 | { 344 | // create new adornment 345 | tagElement = new CBETagControl() 346 | { 347 | Text = cbHeader, 348 | IconMoniker = iconMoniker, 349 | DisplayMode = CBETagPackage.CBEDisplayMode 350 | }; 351 | 352 | tagElement.TagClicked += Adornment_TagClicked; 353 | 354 | cbAdornmentData = new CBAdornmentData(cbStartPosition, cbEndPosition, cbHeaderPosition, tagElement); 355 | tagElement.AdornmentData = cbAdornmentData; 356 | _adornmentCache.Add(cbAdornmentData); 357 | } 358 | 359 | tagElement.SetResourceReference(CBETagControl.LineHeightProperty, EndTagColors.FontSizeKey); 360 | tagElement.SetResourceReference(CBETagControl.TextColorProperty, EndTagColors.GetForegroundResourceKey(_TextView.TextBuffer.ContentType.TypeName)); 361 | 362 | // Add new tag to list 363 | cbTag = new IntraTextAdornmentTag(tagElement, null); 364 | cbSnapshotSpan = new SnapshotSpan(snapshot, position + 1, 0); 365 | cbTagSpan = new TagSpan(cbSnapshotSpan, cbTag); 366 | list.Add(cbTagSpan); 367 | } 368 | } 369 | catch (NullReferenceException) 370 | { 371 | // May happen, when closing a text editor 372 | } 373 | 374 | #if DEBUG 375 | _watch.Stop(); 376 | if (_watch.Elapsed.Milliseconds > 100) 377 | { 378 | System.Diagnostics.Debug.WriteLine("Time elapsed: " + _watch.Elapsed + 379 | " on Thread: " + System.Threading.Thread.CurrentThread.ManagedThreadId + 380 | " in Span: " + span.Start.Position + ":" + span.End.Position + " length: " + span.Length); 381 | } 382 | #endif 383 | 384 | return new ReadOnlyCollection>(list); 385 | } 386 | 387 | /// 388 | /// Capture the header of a code block 389 | /// Returns the text and outputs the start position within the snapshot 390 | /// 391 | private string GetCodeBlockHeader(SnapshotSpan cbSpan, out int headerStart, int maxEndPosition = 0) 392 | { 393 | if (maxEndPosition == 0) 394 | { 395 | maxEndPosition = cbSpan.Start; 396 | } 397 | 398 | var snapshot = cbSpan.Snapshot; 399 | var currentSpan = cbSpan; 400 | 401 | // set end of header to first start of code block { 402 | for (int i = cbSpan.Start; i < cbSpan.End; i++) 403 | { 404 | if (snapshot[i] == '{') 405 | { 406 | maxEndPosition = i; 407 | break; 408 | } 409 | } 410 | 411 | Span headerSpan, headerSpan2; 412 | string headerText, headerText2; 413 | int loops = 0; 414 | // check all enclosing spans until the header is complete 415 | do 416 | { 417 | // abort if in endless loop 418 | if (loops++ > 10) 419 | break; 420 | 421 | // get text of current span 422 | headerStart = currentSpan.Start; 423 | headerSpan = new Span(headerStart, Math.Min(maxEndPosition, currentSpan.Span.End) - headerStart); 424 | if (headerSpan.Length == 0) 425 | continue; 426 | headerText = snapshot.GetText(headerSpan); 427 | 428 | // found header if it begins with a letter or contains a lambda 429 | if (!string.IsNullOrWhiteSpace(headerText)) 430 | //&& (char.IsLetter(headerText[0]) || headerText[0]=='[' || headerText.Contains("=>"))) 431 | { 432 | // recognize "else if" too 433 | if (headerText.StartsWith("if") && ((currentSpan = _TextStructureNavigator.GetSpanOfEnclosing(currentSpan)) != default)) 434 | { 435 | // check what comes before the "if" 436 | headerSpan2 = new Span(currentSpan.Start, Math.Min(maxEndPosition, currentSpan.Span.End) - currentSpan.Start); 437 | headerText2 = snapshot.GetText(headerSpan2); 438 | if (headerText2.StartsWith("else")) 439 | { 440 | headerStart = headerSpan2.Start; 441 | headerText = headerText2; 442 | } 443 | } 444 | else if (headerText.Contains('\r') || headerText.Contains('\n')) 445 | { 446 | // skip annotations 447 | headerText = headerText.Replace('\r', '\n').Replace("\n\n", "\n"); 448 | string[] headerLines = headerText.Split('\n'); 449 | bool annotaions = true; 450 | int openBracets = 0; 451 | headerText = string.Empty; 452 | foreach (var line in headerLines) 453 | { 454 | if (!string.IsNullOrWhiteSpace(line)) 455 | { 456 | var trimmedline = line.Trim(); 457 | if (annotaions && (trimmedline[0] == '[' || openBracets > 0)) 458 | { 459 | openBracets += trimmedline.Count(c => c == '['); 460 | openBracets -= trimmedline.Count(c => c == ']'); 461 | continue; 462 | } 463 | annotaions = false; 464 | if (!string.IsNullOrWhiteSpace(headerText)) 465 | headerText += Environment.NewLine; 466 | headerText += trimmedline; 467 | } 468 | } 469 | } 470 | return headerText; 471 | } 472 | 473 | // get next enclosing span of current span 474 | } while ((currentSpan = _TextStructureNavigator.GetSpanOfEnclosing(currentSpan)) != default); 475 | 476 | // No header found 477 | headerStart = -1; 478 | return null; 479 | } 480 | 481 | #endregion 482 | 483 | #region Tag Clicked Handler 484 | 485 | /// 486 | /// Handles the click event on a tag 487 | /// 488 | private void Adornment_TagClicked(CBAdornmentData adornment, bool jumpToHead) 489 | { 490 | if (_TextView != null) 491 | { 492 | SnapshotPoint targetPoint; 493 | if (jumpToHead) 494 | { 495 | // Jump to header 496 | targetPoint = new SnapshotPoint(_TextView.TextBuffer.CurrentSnapshot, adornment.HeaderStartPosition); 497 | _TextView.DisplayTextLineContainingBufferPosition(targetPoint, 30, ViewRelativePosition.Top); 498 | } 499 | else 500 | { 501 | // Set caret behind closing bracet 502 | targetPoint = new SnapshotPoint(_TextView.TextBuffer.CurrentSnapshot, adornment.EndPosition + 1); 503 | } 504 | _TextView.Caret.MoveTo(targetPoint); 505 | } 506 | } 507 | 508 | #endregion 509 | 510 | #region Options changed 511 | 512 | /// 513 | /// Handles the event when any package option is changed 514 | /// 515 | private void OnPackageOptionChanged(object sender) 516 | { 517 | int start = Math.Max(0, (_VisibleSpan?.Start) ?? 0); 518 | int end = Math.Max(1, _VisibleSpan.HasValue ? _VisibleSpan.Value.End : 1); 519 | InvalidateSpan(new Span(start, end - start)); 520 | } 521 | 522 | /// 523 | /// Invalidates all cached tags within or after the given span 524 | /// 525 | private void InvalidateSpan(Span invalidateSpan, bool clearCache = true) 526 | { 527 | // Remove tags from cache 528 | if (clearCache) 529 | { 530 | _adornmentCache 531 | .Where(a => a.HeaderStartPosition >= invalidateSpan.Start || a.EndPosition >= invalidateSpan.Start) 532 | .ToList().ForEach(a => RemoveFromCache(a)); 533 | } 534 | 535 | // Invalidate span 536 | if (invalidateSpan.End <= _TextView.TextBuffer.CurrentSnapshot.Length) 537 | { 538 | _changedEvent?.Invoke(this, new SnapshotSpanEventArgs( 539 | new SnapshotSpan(_TextView.TextBuffer.CurrentSnapshot, invalidateSpan))); 540 | } 541 | } 542 | 543 | /// 544 | /// Hooks up events at the package 545 | /// Due to AsyncPackage usage, the Tagger may be initialized before the Package 546 | /// So be safe about this 547 | /// 548 | private void InitializePackage() 549 | { 550 | if (isPackageInitialized || CBETagPackage.Instance == null || _Disposed) return; 551 | 552 | CBETagPackage.Instance.PackageOptionChanged += OnPackageOptionChanged; 553 | FontAndColorDefaultsCSharpTags.Instance.EnsureFontAndColorsInitialized(); 554 | isPackageInitialized = true; 555 | } 556 | 557 | private bool isPackageInitialized; 558 | 559 | #endregion 560 | 561 | #region IDisposable 562 | 563 | /// 564 | /// Clean up all events and references 565 | /// 566 | private void Dispose(bool disposing) 567 | { 568 | if (_Disposed) 569 | { 570 | return; 571 | } 572 | 573 | if (disposing) 574 | { 575 | if (CBETagPackage.Instance != null) 576 | { 577 | CBETagPackage.Instance.PackageOptionChanged -= OnPackageOptionChanged; 578 | } 579 | if (_TextView != null) 580 | { 581 | _TextView.LayoutChanged -= OnTextViewLayoutChanged; 582 | if (_TextView.TextBuffer != null) 583 | { 584 | _TextView.TextBuffer.Changed -= TextBuffer_Changed; 585 | } 586 | } 587 | _Disposed = true; 588 | } 589 | } 590 | 591 | public void Dispose() 592 | { 593 | Dispose(true); 594 | GC.SuppressFinalize(this); 595 | } 596 | 597 | #endregion 598 | 599 | #region visibility of tags 600 | 601 | /// 602 | /// Checks if a tag's header is visible 603 | /// 604 | /// Start position of code block 605 | /// End position of code block 606 | /// the visible span in the textview 607 | /// reference to text snapshot. Used for caret check 608 | /// true if the tag is visible (or if all tags are shown) 609 | private bool IsTagVisible(int start, int end, Span? visibleSpan, ITextSnapshot snapshot) 610 | { 611 | bool isVisible = false; 612 | // Check general condition 613 | if (CBETagPackage.CBEVisibilityMode == (int)CBEOptionPage.VisibilityModes.Always 614 | || visibleSpan == null || !visibleSpan.HasValue) 615 | { 616 | isVisible = true; 617 | } 618 | // Check visible span 619 | if (!isVisible) 620 | { 621 | var val = visibleSpan.Value; 622 | isVisible = start < val.Start && end >= val.Start && end <= val.End; 623 | } 624 | // Check if caret is in this line 625 | if (isVisible && _TextView != null) 626 | { 627 | var caretIndex = _TextView.Caret.Position.BufferPosition.Position; 628 | var lineStart = Math.Min(caretIndex, end); 629 | var lineEnd = Math.Max(caretIndex, end); 630 | if (lineStart == lineEnd) 631 | { 632 | isVisible = false; 633 | } 634 | else if (lineStart >= 0 && lineEnd <= snapshot.Length) 635 | { 636 | string line = snapshot.GetText(lineStart, lineEnd - lineStart); 637 | if (!line.Contains('\n')) 638 | isVisible = false; 639 | } 640 | } 641 | return isVisible; 642 | } 643 | 644 | /// 645 | /// Returns the visible span for the given textview 646 | /// 647 | private Span? GetVisibleSpan(ITextView textView) 648 | { 649 | if (textView?.TextViewLines != null && textView.TextViewLines.Count > 2) 650 | { 651 | // Index 0 not yet visible 652 | var firstVisibleLine = textView.TextViewLines[1]; 653 | // Last index not visible, too 654 | var lastVisibleLine = textView.TextViewLines[textView.TextViewLines.Count - 2]; 655 | 656 | return new Span(firstVisibleLine.Start, lastVisibleLine.End - firstVisibleLine.Start); 657 | } 658 | return null; 659 | } 660 | 661 | #endregion 662 | 663 | #region TextView scrolling 664 | 665 | private void OnTextViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) 666 | { 667 | // get new visible span 668 | var visibleSpan = GetVisibleSpan(_TextView); 669 | if (visibleSpan == null || !visibleSpan.HasValue) 670 | return; 671 | 672 | // only if new visible span is different from old 673 | if (!_VisibleSpan.HasValue || 674 | _VisibleSpan.Value.Start != visibleSpan.Value.Start || 675 | _VisibleSpan.Value.End < visibleSpan.Value.End) 676 | { 677 | // invalidate new and/or old visible span 678 | List invalidSpans = new(); 679 | var newSpan = visibleSpan.Value; 680 | if (!_VisibleSpan.HasValue) 681 | { 682 | invalidSpans.Add(newSpan); 683 | } 684 | else 685 | { 686 | var oldSpan = _VisibleSpan.Value; 687 | // invalidate two spans if old and new do not overlap 688 | if (newSpan.Start > oldSpan.End || newSpan.End < oldSpan.Start) 689 | { 690 | invalidSpans.Add(newSpan); 691 | invalidSpans.Add(oldSpan); 692 | } 693 | else 694 | { 695 | // invalidate one big span (old and new joined) 696 | invalidSpans.Add(newSpan.Join(oldSpan)); 697 | } 698 | } 699 | 700 | _VisibleSpan = visibleSpan; 701 | 702 | // refresh tags 703 | foreach (var span in invalidSpans) 704 | { 705 | if (CBETagPackage.CBEVisibilityMode != (int)CBEOptionPage.VisibilityModes.Always) 706 | InvalidateSpan(span, false); 707 | } 708 | } 709 | } 710 | 711 | #endregion 712 | 713 | } 714 | } 715 | -------------------------------------------------------------------------------- /CBETaggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using Microsoft.VisualStudio.Text.Editor; 3 | using Microsoft.VisualStudio.Text.Operations; 4 | using Microsoft.VisualStudio.Text.Tagging; 5 | using Microsoft.VisualStudio.Utilities; 6 | using Microsoft.VisualStudio.Editor; 7 | using System.ComponentModel.Composition; 8 | 9 | namespace CodeBlockEndTag 10 | { 11 | /// 12 | /// This class serves as factory for VS to create the CBETagger 13 | /// 14 | [Export(typeof(IViewTaggerProvider))] 15 | [ContentType("code")] 16 | [TextViewRole(PredefinedTextViewRoles.Interactive)] 17 | [TagType(typeof(IntraTextAdornmentTag))] 18 | internal class CBETaggerProvider : IViewTaggerProvider 19 | { 20 | #region MEF-Imports: Services from VisualStudio 21 | #pragma warning disable CS0649 22 | // Disabled waring about default value. Value will be set by VS. 23 | 24 | [Import] 25 | internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; } 26 | 27 | [Import] 28 | internal ITextBufferFactoryService TextBufferFactory { get; set; } 29 | 30 | [Import] 31 | internal ITextSearchService TextSearchService { get; set; } 32 | 33 | #pragma warning restore CS0649 34 | #endregion 35 | 36 | /// 37 | /// Factory function for a CBETagger instance. 38 | /// Used by VS to create the tagger 39 | /// 40 | public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag 41 | { 42 | // only works with IWpfTextView 43 | if (textView is not IWpfTextView wpfTextView) 44 | { 45 | return null; 46 | } 47 | 48 | // provide the tag only on the top-level buffer 49 | if (textView.TextBuffer != buffer) 50 | { 51 | return null; 52 | } 53 | 54 | // return new instance of CBETagger 55 | return new CBETagger(this, wpfTextView) as ITagger; 56 | } 57 | 58 | /// 59 | /// Returns a TextStructureNavigator for the given TextBuffer 60 | /// This is a service by VisualStudio for fast navigation in structured texts 61 | /// 62 | internal ITextStructureNavigator GetTextStructureNavigator(ITextBuffer buffer) 63 | { 64 | return TextStructureNavigatorSelector.GetTextStructureNavigator(buffer); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CodeBlockEndTag.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 15.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | latest 7 | 8 | 9 | true 10 | 11 | 12 | 13 | 14 | 14.0 15 | 16 | 17 | 18 | true 19 | 20 | 21 | Key.snk 22 | 23 | 24 | 25 | Debug 26 | AnyCPU 27 | 2.0 28 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 29 | {52502793-A9E8-4055-8185-0000FDADB7EE} 30 | Library 31 | Properties 32 | CodeBlockEndTag 33 | CodeBlockEndTag 34 | v4.8 35 | true 36 | true 37 | false 38 | false 39 | true 40 | false 41 | 42 | 43 | true 44 | full 45 | false 46 | bin\Debug\ 47 | DEBUG;TRACE 48 | prompt 49 | 4 50 | 51 | 52 | pdbonly 53 | true 54 | bin\Release\ 55 | TRACE 56 | prompt 57 | 4 58 | 59 | 60 | 61 | 62 | 63 | 64 | Component 65 | 66 | 67 | UserControl 68 | 69 | 70 | CBEOptionPageControl.cs 71 | 72 | 73 | CBETagControl.xaml 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | Designer 102 | 103 | 104 | 105 | 106 | Designer 107 | MSBuild:Compile 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | Always 116 | true 117 | 118 | 119 | Always 120 | true 121 | 122 | 123 | Always 124 | true 125 | 126 | 127 | Always 128 | true 129 | 130 | 131 | 132 | 133 | CBEOptionPageControl.cs 134 | 135 | 136 | true 137 | VSPackage 138 | 139 | 140 | 141 | 142 | 16.11.0 143 | 144 | 145 | 15.0.1 146 | 147 | 148 | 16.10.10 149 | runtime; build; native; contentfiles; analyzers; buildtransitive 150 | all 151 | 152 | 153 | 17.0.63 154 | runtime; build; native; contentfiles; analyzers; buildtransitive 155 | all 156 | 157 | 158 | 17.0.5232 159 | runtime; build; native; contentfiles; analyzers; buildtransitive 160 | all 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 187 | -------------------------------------------------------------------------------- /CodeBlockEndTag.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeBlockEndTag", "CodeBlockEndTag.csproj", "{52502793-A9E8-4055-8185-0000FDADB7EE}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {52502793-A9E8-4055-8185-0000FDADB7EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {52502793-A9E8-4055-8185-0000FDADB7EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {52502793-A9E8-4055-8185-0000FDADB7EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {52502793-A9E8-4055-8185-0000FDADB7EE}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using System; 3 | 4 | namespace CodeBlockEndTag 5 | { 6 | internal static class Extensions 7 | { 8 | /// 9 | /// Joins another span with the current one 10 | /// 11 | public static Span Join(this Span s1, Span span) 12 | { 13 | int start = Math.Min(s1.Start, span.Start); 14 | int end = Math.Max(s1.End, span.End); 15 | return new Span(start, end - start); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /IFontAndColorDefaultsProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace CodeBlockEndTag 4 | { 5 | [Guid("5B100711-3B75-4F3A-B548-6E18C23E12FC")] 6 | internal interface IFontAndColorDefaultsProvider 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /IconMonikerSelector.cs: -------------------------------------------------------------------------------- 1 | // 2 | // For a complete list of all KnownMonikers see 3 | // http://glyphlist.azurewebsites.net/knownmonikers/ 4 | // 5 | 6 | using System.Linq; 7 | using Microsoft.VisualStudio.Imaging; 8 | using Microsoft.VisualStudio.Imaging.Interop; 9 | using System.Collections.Generic; 10 | 11 | namespace CodeBlockEndTag 12 | { 13 | internal sealed class IconMonikerSelector 14 | { 15 | private const string ModifierPublic = "public"; 16 | private const string ModifierPrivate = "private"; 17 | private const string ModifierProtected = "protected"; 18 | private const string ModifierInternal = "internal"; 19 | private const string ModifierSealed = "sealed"; 20 | private const string ModifierPartial = "partial"; 21 | private const string ModifierStatic = "static"; 22 | private const string ModifierConst = "const"; 23 | private const string ModifierReadonly = "readonly"; 24 | private const string ModifierExplicit = "explicit"; 25 | private const string ModifierFriend = "friend"; 26 | private const string ModifierInline = "inline"; 27 | private const string ModifierVolatile = "volatile"; 28 | private const string ModifierMutable = "mutable"; 29 | private const string ModifierVirtual = "virtual"; 30 | 31 | private static readonly string[] Modifiers = new string[] 32 | { 33 | ModifierPublic, ModifierPrivate, ModifierProtected, ModifierInternal, 34 | ModifierSealed, ModifierPartial, ModifierStatic, ModifierConst, ModifierReadonly, 35 | ModifierExplicit, ModifierFriend, ModifierInline, ModifierVolatile, ModifierMutable, 36 | ModifierVirtual 37 | }; 38 | 39 | private delegate ImageMoniker IconSelector(string keyword, string modifier); 40 | private static readonly Dictionary iconSelectors = new(); 41 | 42 | public static ImageMoniker SelectMoniker(string header) 43 | { 44 | ImageMoniker icon = KnownMonikers.QuestionMark; 45 | if (string.IsNullOrWhiteSpace(header)) return icon; 46 | 47 | // split words of header 48 | string[] words = header.Split(' '); 49 | if (words.Length == 0) return icon; 50 | 51 | // find first visibility modifier 52 | string modifier = GetModifier(words, out int modifierCount); 53 | int keywordIndex = modifierCount; 54 | if (words.Length <= keywordIndex) return icon; 55 | 56 | // take first keyword 57 | string keyword = words[keywordIndex].ToLower(); 58 | if (keyword.Contains('(')) 59 | { 60 | keyword = keyword.Substring(0, keyword.IndexOf('(')); 61 | } 62 | if (keyword.Contains('[')) 63 | { 64 | keyword = keyword.Substring(0, keyword.IndexOf('[')); 65 | } 66 | if (keyword.Contains('<')) 67 | { 68 | keyword = keyword.Substring(0, keyword.IndexOf('<')); 69 | } 70 | 71 | // setup keyword icons 72 | if (iconSelectors.Count == 0) 73 | InitIconSelectors(); 74 | 75 | // get icon by keyword 76 | if (iconSelectors.ContainsKey(keyword)) 77 | { 78 | return iconSelectors[keyword](keyword, modifier); 79 | } 80 | 81 | // icon for lambda 82 | else if (header.Contains("=>")) 83 | { 84 | return KnownMonikers.DelegatePublic; 85 | } 86 | 87 | // icon for method/ctor 88 | else if (words.Length - modifierCount >= 1 && header.Contains('(') && header.Contains(')')) 89 | { 90 | return modifier switch 91 | { 92 | ModifierPublic => KnownMonikers.MethodPublic, 93 | ModifierProtected => KnownMonikers.MethodProtected, 94 | ModifierInternal => KnownMonikers.MethodInternal, 95 | _ => KnownMonikers.MethodPrivate, 96 | }; 97 | } 98 | 99 | // icon for property 100 | else if (words.Length - modifierCount == 2) 101 | { 102 | return modifier switch 103 | { 104 | ModifierPublic => KnownMonikers.PropertyPublic, 105 | ModifierProtected => KnownMonikers.PropertyProtected, 106 | ModifierInternal => KnownMonikers.PropertyInternal, 107 | _ => KnownMonikers.PropertyPrivate, 108 | }; 109 | } 110 | 111 | return icon; 112 | } 113 | 114 | private static void InitIconSelectors() 115 | { 116 | iconSelectors.Add("namespace", new IconSelector((_, __) => KnownMonikers.Namespace)); 117 | iconSelectors.Add("class", new IconSelector((_, modifier) => 118 | { 119 | return modifier switch 120 | { 121 | ModifierPublic => KnownMonikers.ClassPublic, 122 | ModifierPrivate => KnownMonikers.ClassPrivate, 123 | ModifierProtected => KnownMonikers.ClassProtected, 124 | _ => KnownMonikers.ClassInternal, 125 | }; 126 | })); 127 | iconSelectors.Add("struct", new IconSelector((_, modifier) => 128 | { 129 | return modifier switch 130 | { 131 | ModifierPublic => KnownMonikers.StructurePublic, 132 | ModifierPrivate => KnownMonikers.StructurePrivate, 133 | ModifierProtected => KnownMonikers.StructureProtected, 134 | _ => KnownMonikers.StructureInternal, 135 | }; 136 | })); 137 | iconSelectors.Add("enum", new IconSelector((_, modifier) => 138 | { 139 | return modifier switch 140 | { 141 | ModifierPublic => KnownMonikers.EnumerationPublic, 142 | ModifierPrivate => KnownMonikers.EnumerationPrivate, 143 | ModifierProtected => KnownMonikers.EnumerationProtected, 144 | _ => KnownMonikers.EnumerationInternal, 145 | }; 146 | })); 147 | iconSelectors.Add("interface", new IconSelector((_, modifier) => 148 | { 149 | return modifier switch 150 | { 151 | ModifierPublic => KnownMonikers.InterfacePublic, 152 | ModifierPrivate => KnownMonikers.InterfacePrivate, 153 | ModifierProtected => KnownMonikers.InterfaceProtected, 154 | _ => KnownMonikers.InterfaceInternal, 155 | }; 156 | })); 157 | iconSelectors.Add("event", new IconSelector((_, modifier) => 158 | { 159 | return modifier switch 160 | { 161 | ModifierPublic => KnownMonikers.EventPublic, 162 | ModifierInternal => KnownMonikers.EventInternal, 163 | ModifierProtected => KnownMonikers.EventProtected, 164 | _ => KnownMonikers.EventPrivate, 165 | }; 166 | })); 167 | iconSelectors.Add("if", new IconSelector((_, __) => KnownMonikers.If)); 168 | iconSelectors.Add("else", new IconSelector((_, __) => KnownMonikers.If)); 169 | iconSelectors.Add("do", new IconSelector((_, __) => KnownMonikers.DoWhile)); 170 | iconSelectors.Add("while", new IconSelector((_, __) => KnownMonikers.While)); 171 | iconSelectors.Add("for", new IconSelector((_, __) => KnownMonikers.ForEachLoop)); 172 | iconSelectors.Add("foreach", new IconSelector((_, __) => KnownMonikers.ForEachLoop)); 173 | iconSelectors.Add("typedef", new IconSelector((_, __) => KnownMonikers.TypeDefinition)); 174 | iconSelectors.Add("new", new IconSelector((__, _) => KnownMonikers.NewItem)); 175 | iconSelectors.Add("switch", new IconSelector((_, __) => KnownMonikers.FlowSwitch)); 176 | iconSelectors.Add("try", new IconSelector((_, __) => KnownMonikers.TryCatch)); 177 | iconSelectors.Add("catch", new IconSelector((_, __) => KnownMonikers.TryCatch)); 178 | iconSelectors.Add("finally", new IconSelector((_, __) => KnownMonikers.FinalState)); 179 | iconSelectors.Add("unsafe", new IconSelector((_, __) => KnownMonikers.HotSpot)); 180 | iconSelectors.Add("using", new IconSelector((_, __) => KnownMonikers.RectangleSelection)); 181 | iconSelectors.Add("lock", new IconSelector((_, __) => KnownMonikers.Lock)); 182 | iconSelectors.Add("add", new IconSelector((_, __) => KnownMonikers.AddEvent)); 183 | iconSelectors.Add("remove", new IconSelector((_, __) => KnownMonikers.EventMissing)); 184 | iconSelectors.Add("get", new IconSelector((_, __) => KnownMonikers.ReturnParameter)); 185 | iconSelectors.Add("set", new IconSelector((_, __) => KnownMonikers.InsertParameter)); 186 | 187 | // C/C++ Icons 188 | iconSelectors.Add("union", new IconSelector((_, __) => KnownMonikers.Union)); 189 | iconSelectors.Add("template", new IconSelector((_, __) => KnownMonikers.Template)); 190 | iconSelectors.Add("synchronized", new IconSelector((_, __) => KnownMonikers.SynchronousMessage)); 191 | 192 | // PowerShell 193 | iconSelectors.Add("elseif", new IconSelector((_, __) => KnownMonikers.If)); 194 | iconSelectors.Add("begin", new IconSelector((_, __) => KnownMonikers.StartPoint)); 195 | iconSelectors.Add("process", new IconSelector((_, __) => KnownMonikers.Action)); 196 | iconSelectors.Add("end", new IconSelector((_, __) => KnownMonikers.EndPoint)); 197 | iconSelectors.Add("data", new IconSelector((_, __) => KnownMonikers.DataList)); 198 | iconSelectors.Add("dynamicparam", new IconSelector((_, __) => KnownMonikers.NewParameter)); 199 | iconSelectors.Add("filter", new IconSelector((_, __) => KnownMonikers.Filter)); 200 | iconSelectors.Add("function", new IconSelector((_, __) => KnownMonikers.MethodPublic)); 201 | iconSelectors.Add("workflow", new IconSelector((_, __) => KnownMonikers.WorkflowInterop)); 202 | iconSelectors.Add("inlinescript", new IconSelector((_, __) => KnownMonikers.Inline)); 203 | iconSelectors.Add("parallel", new IconSelector((_, __) => KnownMonikers.Parallel)); 204 | iconSelectors.Add("sequence", new IconSelector((_, __) => KnownMonikers.Sequence)); 205 | iconSelectors.Add("trap", new IconSelector((_, __) => KnownMonikers.TryCatch)); 206 | } 207 | 208 | private static string GetModifier(string[] words, out int modifierCount) 209 | { 210 | string modifier = string.Empty; 211 | modifierCount = 0; 212 | foreach (string word in words) 213 | { 214 | if (Modifiers.Contains(word)) 215 | { 216 | modifierCount++; 217 | if (string.IsNullOrWhiteSpace(modifier)) 218 | modifier = word; 219 | continue; 220 | } 221 | break; 222 | } 223 | return modifier; 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KhaosCoders/VSCodeBlockEndTag/9aeae006d1e5962a1964f3cb390815fb79553107/Key.snk -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 |  GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /Languages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace CodeBlockEndTag 8 | { 9 | internal static class Languages 10 | { 11 | public const string CSharp = "CSharp"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Microsoft.Internal.VisualStudio.Shell.Interop/FontAndColorsOptionPageDummy.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Microsoft.Internal.VisualStudio.Shell.Interop 4 | { 5 | [Guid("57F6B7D2-1436-11D1-883C-0000F87579D2")] 6 | internal class FontAndColorsOptionPageDummy 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Microsoft.Internal.VisualStudio.Shell.Interop/Interfaces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Microsoft.Internal.VisualStudio.Shell.Interop 5 | { 6 | [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0D915B59-2ED7-472A-9DE8-9161737EA1C5")] 7 | public interface SVsColorThemeService 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.Shell.Interop/FontAndColorDefaultsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows; 5 | using System.Windows.Media; 6 | using CodeBlockEndTag; 7 | using Microsoft.VisualStudio.TextManager.Interop; 8 | 9 | namespace Microsoft.VisualStudio.Shell.Interop 10 | { 11 | internal abstract class FontAndColorDefaultsBase : IVsFontAndColorDefaults, IVsFontAndColorEvents 12 | { 13 | #region ColorUsage 14 | [Flags] 15 | protected enum ColorUsage 16 | { 17 | Background = 1, 18 | Foreground = 2, 19 | } 20 | #endregion 21 | #region ColorEntry 22 | protected abstract class ColorEntry 23 | { 24 | readonly FontAndColorDefaultsBase _parent; 25 | 26 | public ColorEntry(FontAndColorDefaultsBase parent) 27 | { 28 | _parent = parent ?? throw new ArgumentNullException(nameof(parent)); 29 | 30 | DefaultBackground = Enumerable.Empty(); 31 | DefaultForeground = Enumerable.Empty(); 32 | } 33 | 34 | public string Name { get; protected set; } 35 | public string LocalizedName { get; protected set; } 36 | public string Description { get; protected set; } 37 | public ColorUsage Usage { get; protected set; } 38 | public IEnumerable DefaultBackground { get; protected set; } 39 | public IEnumerable DefaultForeground { get; protected set; } 40 | 41 | private bool TryGetDefaultBackground(out uint result) 42 | { 43 | ThreadHelper.ThrowIfNotOnUIThread(); 44 | 45 | return Services.TryGetThemeColor(_parent.CategoryGuid, Name, __THEMEDCOLORTYPE.TCT_Background, out result) || VsColor.TryGetValue(DefaultBackground, out result); 46 | } 47 | 48 | private bool TryGetDefaultForeground(out uint result) 49 | { 50 | ThreadHelper.ThrowIfNotOnUIThread(); 51 | 52 | return Services.TryGetThemeColor(_parent.CategoryGuid, Name, __THEMEDCOLORTYPE.TCT_Foreground, out result) || VsColor.TryGetValue(DefaultForeground, out result); 53 | } 54 | 55 | public AllColorableItemInfo CreateColorInfo() 56 | { 57 | ThreadHelper.ThrowIfNotOnUIThread(); 58 | try 59 | { 60 | if (!TryGetDefaultBackground(out var defaultBackgroundColor)) 61 | { 62 | defaultBackgroundColor = 0; 63 | } 64 | if (!TryGetDefaultForeground(out var defaultForegroundColor)) 65 | { 66 | defaultForegroundColor = 0; 67 | } 68 | 69 | var flags = __FCITEMFLAGS.FCIF_ALLOWCUSTOMCOLORS; 70 | if ((Usage & ColorUsage.Background) == ColorUsage.Background) 71 | { 72 | flags |= __FCITEMFLAGS.FCIF_ALLOWBGCHANGE; 73 | } 74 | if ((Usage & ColorUsage.Foreground) == ColorUsage.Foreground) 75 | { 76 | flags |= __FCITEMFLAGS.FCIF_ALLOWFGCHANGE; 77 | } 78 | 79 | return new AllColorableItemInfo 80 | { 81 | Info = new ColorableItemInfo 82 | { 83 | crBackground = 0x2000000, 84 | bBackgroundValid = 1, 85 | crForeground = 0x2000000, 86 | bForegroundValid = 1, 87 | dwFontFlags = 0, 88 | bFontFlagsValid = 1 89 | }, 90 | bstrName = Name, 91 | bNameValid = (Name != null) ? 1 : 0, 92 | bstrLocalizedName = LocalizedName, 93 | bLocalizedNameValid = (LocalizedName != null) ? 1 : 0, 94 | crAutoBackground = defaultBackgroundColor, 95 | bAutoBackgroundValid = 1, 96 | crAutoForeground = defaultForegroundColor, 97 | bAutoForegroundValid = 1, 98 | dwMarkerVisualStyle = 0, 99 | bMarkerVisualStyleValid = 1, 100 | eLineStyle = LINESTYLE.LI_NONE, 101 | bLineStyleValid = 1, 102 | fFlags = (uint)flags, 103 | bFlagsValid = 1, 104 | bstrDescription = Description, 105 | bDescriptionValid = (Description != null) ? 1 : 0, 106 | }; 107 | } 108 | catch (Exception e) 109 | { 110 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, this.ToString(), $"CreateColorInfo Error: {e}"); 111 | throw; 112 | } 113 | } 114 | 115 | public void UpdateResources(ResourceDictionary resources, Color? backgroundColor, Color? foregroundColor) 116 | { 117 | try 118 | { 119 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread 120 | if ((Usage & ColorUsage.Background) == ColorUsage.Background) 121 | { 122 | var key = new ThemeResourceKey(_parent.CategoryGuid, Name, ThemeResourceKeyType.BackgroundBrush); 123 | if (backgroundColor.HasValue) 124 | { 125 | var brush = new SolidColorBrush(backgroundColor.Value); 126 | brush.Freeze(); 127 | resources[key] = brush; 128 | } 129 | else 130 | { 131 | resources.Remove(key); 132 | } 133 | } 134 | 135 | if ((Usage & ColorUsage.Foreground) == ColorUsage.Foreground) 136 | { 137 | var key = new ThemeResourceKey(_parent.CategoryGuid, Name, ThemeResourceKeyType.ForegroundBrush); 138 | if (foregroundColor.HasValue) 139 | { 140 | var brush = new SolidColorBrush(foregroundColor.Value); 141 | 142 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), 143 | $"UpdateResources Name: {Name} Color: {foregroundColor.Value}"); 144 | brush.Freeze(); 145 | resources[key] = brush; 146 | } 147 | else 148 | { 149 | resources.Remove(key); 150 | } 151 | } 152 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread 153 | } 154 | catch (Exception e) 155 | { 156 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, this.ToString(), $"UpdateResources Error: {e}"); 157 | throw; 158 | } 159 | } 160 | } 161 | #endregion 162 | 163 | private readonly ResourceDictionary _resourceDictionary; 164 | 165 | protected FontAndColorDefaultsBase() 166 | { 167 | _resourceDictionary = new ResourceDictionary(); 168 | Application.Current.Resources.MergedDictionaries.Add(_resourceDictionary); 169 | } 170 | 171 | private bool _fontInfoInitialized; 172 | 173 | public void EnsureFontAndColorsInitialized() 174 | { 175 | ThreadHelper.ThrowIfNotOnUIThread(); 176 | 177 | if (!_fontInfoInitialized) 178 | { 179 | ReloadFontAndColors(); 180 | } 181 | } 182 | 183 | public void ReloadFontAndColors() 184 | { 185 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), 186 | $"ReloadFontAndColors"); 187 | ThreadHelper.ThrowIfNotOnUIThread(); 188 | 189 | _fontInfoInitialized = true; 190 | 191 | #region fill out temorary resources 192 | 193 | var colorStorage = FontAndColorStorage; 194 | var categoryGuid = CategoryGuid; 195 | var fflags = 196 | (uint)__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS | 197 | (uint)__FCSTORAGEFLAGS.FCSF_NOAUTOCOLORS | 198 | (uint)__FCSTORAGEFLAGS.FCSF_READONLY; 199 | 200 | if (!ErrorHandler.Succeeded(colorStorage.OpenCategory(ref categoryGuid, fflags))) 201 | { 202 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, this.ToString(), 203 | $"Unable to open category {categoryGuid} in color storage"); 204 | return; 205 | } 206 | 207 | try 208 | { 209 | var pLOGFONT = new LOGFONTW[1]; 210 | var pFontInfo = new FontInfo[1]; 211 | if (ErrorHandler.Succeeded(colorStorage.GetFont(pLOGFONT, pFontInfo)) && 212 | (pFontInfo[0].bFaceNameValid == 1)) 213 | { 214 | var fontInfoRef = pFontInfo[0]; 215 | var fontFamily = new FontFamily(fontInfoRef.bstrFaceName); 216 | var fontSize = ConvertFromPoint(fontInfoRef.wPointSize); 217 | 218 | _resourceDictionary[new FontResourceKey(CategoryGuid, FontResourceKeyType.FontFamily)] = fontFamily; 219 | _resourceDictionary[new FontResourceKey(CategoryGuid, FontResourceKeyType.FontSize)] = fontSize; 220 | } 221 | else 222 | { 223 | _resourceDictionary.Remove(new FontResourceKey(CategoryGuid, FontResourceKeyType.FontFamily)); 224 | _resourceDictionary.Remove(new FontResourceKey(CategoryGuid, FontResourceKeyType.FontSize)); 225 | } 226 | 227 | foreach (var colorEntry in ColorEntries) 228 | { 229 | Color? backgroundColor = null; 230 | Color? foregroundColor = null; 231 | 232 | var pColorInfo = new ColorableItemInfo[1]; 233 | if (!ErrorHandler.Succeeded(colorStorage.GetItem(colorEntry.Name, pColorInfo))) 234 | { 235 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, this.ToString(), 236 | $"Can't find color {colorEntry.Name} in color storage"); 237 | if (colorEntry.DefaultForeground.Any() && colorEntry.DefaultForeground.First().TryGetValue(out var foreground)) 238 | { 239 | foregroundColor = Services.CreateWpfColor(foreground); 240 | } 241 | if (colorEntry.DefaultBackground.Any() && colorEntry.DefaultBackground.First().TryGetValue(out var background)) 242 | { 243 | backgroundColor = Services.CreateWpfColor(background); 244 | } 245 | } 246 | else 247 | { 248 | if (pColorInfo[0].bBackgroundValid == 1) 249 | { 250 | backgroundColor = Services.CreateWpfColor(pColorInfo[0].crBackground); 251 | } 252 | 253 | if (pColorInfo[0].bForegroundValid == 1) 254 | { 255 | foregroundColor = Services.CreateWpfColor(pColorInfo[0].crForeground); 256 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), 257 | $"ReloadFontAndColors {pColorInfo[0].crForeground} => {foregroundColor}"); 258 | } 259 | } 260 | 261 | colorEntry.UpdateResources(_resourceDictionary, backgroundColor, foregroundColor); 262 | } 263 | } 264 | catch (Exception e) 265 | { 266 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, this.ToString(), $"ReloadFontAndColors Error: {e}"); 267 | throw; 268 | } 269 | finally 270 | { 271 | colorStorage.CloseCategory(); 272 | } 273 | #endregion 274 | } 275 | 276 | protected internal Guid CategoryGuid { get; protected set; } 277 | protected string CategoryName { private get; set; } 278 | protected FontInfo Font { private get; set; } 279 | protected IReadOnlyList ColorEntries { private get; set; } 280 | 281 | #region IVsFontAndColorDefaults 282 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread 283 | int IVsFontAndColorDefaults.GetBaseCategory(out Guid guidBase) 284 | { 285 | guidBase = Guid.Empty; 286 | return 0; 287 | } 288 | 289 | int IVsFontAndColorDefaults.GetCategoryName(out string stringName) 290 | { 291 | stringName = CategoryName; 292 | return 0; 293 | } 294 | 295 | int IVsFontAndColorDefaults.GetFlags(out uint flags) 296 | { 297 | flags = (int)(__FONTCOLORFLAGS.FCF_ONLYTTFONTS | __FONTCOLORFLAGS.FCF_SAVEALL); 298 | return 0; 299 | } 300 | 301 | int IVsFontAndColorDefaults.GetFont(FontInfo[] info) 302 | { 303 | info[0] = Font; 304 | return 0; 305 | } 306 | 307 | int IVsFontAndColorDefaults.GetItem(int index, AllColorableItemInfo[] info) 308 | { 309 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), $"GetItem: {index}"); 310 | try 311 | { 312 | if (0 > index || index >= ColorEntries.Count) 313 | { 314 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, this.ToString(), $"GetItem: failed"); 315 | return -2147467259; 316 | } 317 | 318 | info[0] = ColorEntries[index].CreateColorInfo(); 319 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), $"GetItem: Ok"); 320 | return 0; 321 | } 322 | catch (Exception e) 323 | { 324 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, this.ToString(), $"GetItem Error: {e}"); 325 | throw; 326 | } 327 | } 328 | 329 | int IVsFontAndColorDefaults.GetItemByName(string name, AllColorableItemInfo[] info) 330 | { 331 | foreach (var entry in ColorEntries) 332 | { 333 | if (entry.Name != name) continue; 334 | 335 | info[0] = entry.CreateColorInfo(); 336 | return 0; 337 | } 338 | return -2147467259; 339 | } 340 | 341 | int IVsFontAndColorDefaults.GetItemCount(out int items) 342 | { 343 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), $"GetItemCount: {ColorEntries.Count}"); 344 | items = ColorEntries.Count; 345 | return 0; 346 | } 347 | 348 | int IVsFontAndColorDefaults.GetPriority(out ushort priority) 349 | { 350 | priority = 0x100; 351 | return 0; 352 | } 353 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread 354 | #endregion 355 | 356 | #region IVsFontAndColorEvents 357 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread 358 | int IVsFontAndColorEvents.OnApply() 359 | { 360 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, this.ToString(), $"OnApply"); 361 | ReloadFontAndColors(); 362 | return 0; 363 | } 364 | 365 | public int OnFontChanged(ref Guid rguidCategory, FontInfo[] pInfo, LOGFONTW[] pLOGFONT, uint HFONT) 366 | { 367 | return 0; 368 | } 369 | 370 | // VS 2022 has a slightly different API here. Virtual is needed to allow VS 2022 to still load the package 371 | public virtual int OnFontChanged(ref Guid rguidCategory, FontInfo[] pInfo, LOGFONTW[] pLOGFONT, IntPtr HFONT) 372 | { 373 | return 0; 374 | } 375 | 376 | int IVsFontAndColorEvents.OnItemChanged(ref Guid category, string name, int item, ColorableItemInfo[] info, uint forground, uint background) 377 | { 378 | return 0; 379 | } 380 | 381 | int IVsFontAndColorEvents.OnReset(ref Guid category) 382 | { 383 | return 0; 384 | } 385 | 386 | int IVsFontAndColorEvents.OnResetToBaseCategory(ref Guid category) 387 | { 388 | return 0; 389 | } 390 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread 391 | #endregion 392 | 393 | #region static helpers 394 | protected static FontInfo CreateFontInfo(string fontFaceName, ushort pointSize, byte charSet) 395 | { 396 | return new FontInfo 397 | { 398 | bstrFaceName = fontFaceName, 399 | wPointSize = pointSize, 400 | iCharSet = charSet, 401 | bFaceNameValid = 1, 402 | bPointSizeValid = 1, 403 | bCharSetValid = 1 404 | }; 405 | } 406 | 407 | internal static double ConvertFromPoint(ushort pointSize) 408 | { 409 | return 96.0 * pointSize / 72; 410 | } 411 | #endregion 412 | 413 | private IVsFontAndColorStorage _fontAndColorStorage; 414 | 415 | protected IVsFontAndColorStorage FontAndColorStorage 416 | { 417 | get 418 | { 419 | ThreadHelper.ThrowIfNotOnUIThread(); 420 | 421 | return _fontAndColorStorage ??= Package.GetGlobalService(typeof(SVsFontAndColorStorage)) as IVsFontAndColorStorage; 422 | } 423 | } 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.Shell.Interop/FontAndColorRegistrationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.VisualStudio.Shell.Interop 4 | { 5 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 6 | internal sealed class FontAndColorRegistrationAttribute : RegistrationAttribute 7 | { 8 | public FontAndColorRegistrationAttribute(Type providerType, String name, String category) 9 | { 10 | Name = name; 11 | Provider = providerType.GUID; 12 | Category = new Guid(category); 13 | } 14 | 15 | public override void Register(RegistrationContext context) 16 | { 17 | if (context == null) return; 18 | 19 | context.Log.WriteLine("FontAndColors: Name:{0}, Category:{1:B}, Package:{2:B}", Name, Category, Provider); 20 | using var key = context.CreateKey($"FontAndColors\\{Name}"); 21 | key.SetValue("Category", Category.ToString("B")); 22 | key.SetValue("Package", Provider.ToString("B")); 23 | } 24 | 25 | public override void Unregister(RegistrationContext context) 26 | { 27 | context?.RemoveKey($"FontAndColors\\{Name}"); 28 | } 29 | 30 | public string Name { get; set; } 31 | public Guid Category { get; set; } 32 | public Guid Provider { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.Shell.Interop/FontResourceKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.VisualStudio.Shell.Interop 4 | { 5 | internal enum FontResourceKeyType 6 | { 7 | FontFamily, 8 | FontSize, 9 | } 10 | 11 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread 12 | internal sealed class FontResourceKey 13 | { 14 | public FontResourceKey(Guid category, FontResourceKeyType keyType) 15 | { 16 | Category = category; 17 | KeyType = keyType; 18 | } 19 | 20 | public override bool Equals(object obj) 21 | { 22 | if (obj is FontResourceKey key) 23 | { 24 | return Category == key.Category && KeyType == key.KeyType; 25 | } 26 | else 27 | { 28 | return false; 29 | } 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | return Category.GetHashCode() ^ (int)KeyType; 35 | } 36 | 37 | public override string ToString() 38 | { 39 | return $"{Category}.{KeyType}"; 40 | } 41 | 42 | public Guid Category { get; } 43 | public FontResourceKeyType KeyType { get; } 44 | } 45 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread 46 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.Shell.Interop/SafeNativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Security; 4 | 5 | namespace Microsoft.VisualStudio.Shell.Interop 6 | { 7 | [SuppressUnmanagedCodeSecurity] 8 | internal static class SafeNativeMethods 9 | { 10 | [DllImport("user32.dll")] 11 | internal static extern int GetSysColor(int nIndex); 12 | } 13 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.Shell.Interop/Services.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | using System.Windows.Media; 5 | using CodeBlockEndTag; 6 | using Microsoft.Internal.VisualStudio.Shell.Interop; 7 | 8 | namespace Microsoft.VisualStudio.Shell.Interop 9 | { 10 | static class Services 11 | { 12 | private static IVsUIShell2 _vsUiShell2; 13 | private static IVsUIShell5 _vsUiShell5; 14 | private static IVsFontAndColorUtilities _fontAndColorUtilities; 15 | private static dynamic _colorThemeService; 16 | 17 | private static Type _colorNameType; 18 | 19 | public static bool TryGetThemeColor(Guid colorCategory, string colorName, __THEMEDCOLORTYPE colorType, out uint result) 20 | { 21 | ThreadHelper.ThrowIfNotOnUIThread(); 22 | 23 | try 24 | { 25 | if (colorName == null) 26 | { 27 | throw new ArgumentNullException(nameof(colorName)); 28 | } 29 | 30 | var currentTheme = ColorThemeService?.CurrentTheme; 31 | _colorNameType ??= (currentTheme.GetType() as Type).GetMethod("get_Item").GetParameters()[0] 32 | .ParameterType; 33 | 34 | dynamic colorNameInst = Activator.CreateInstance(_colorNameType); 35 | colorNameInst.Category = colorCategory; 36 | colorNameInst.Name = colorName; 37 | 38 | var entry = currentTheme?[colorNameInst]; 39 | 40 | if (entry != null) 41 | { 42 | switch (colorType) 43 | { 44 | case __THEMEDCOLORTYPE.TCT_Background: 45 | result = entry.Background; 46 | return true; 47 | case __THEMEDCOLORTYPE.TCT_Foreground: 48 | result = entry.Foreground; 49 | return true; 50 | default: 51 | throw new InvalidEnumArgumentException(nameof(colorType), (int)colorType, 52 | typeof(__THEMEDCOLORTYPE)); 53 | } 54 | } 55 | 56 | result = 0; 57 | return false; 58 | } 59 | catch (Exception e) 60 | { 61 | CBETagPackage.Instance.Log.LogEntry((uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, "CBETagger.Services", $"TryGetThemeColor Error: {e}"); 62 | throw; 63 | } 64 | } 65 | 66 | public static Color CreateWpfColor(uint dwRgbValue) 67 | { 68 | var color = System.Drawing.ColorTranslator.FromWin32((int)dwRgbValue); 69 | return Color.FromArgb(color.A, color.R, color.G, color.B); 70 | } 71 | 72 | public static IVsUIShell2 VsUIShell2 73 | { 74 | get 75 | { 76 | ThreadHelper.ThrowIfNotOnUIThread(); 77 | 78 | return _vsUiShell2 ??= Package.GetGlobalService(typeof(SVsUIShell)) as IVsUIShell2; 79 | } 80 | } 81 | 82 | public static IVsUIShell5 VsUIShell5 83 | { 84 | get 85 | { 86 | ThreadHelper.ThrowIfNotOnUIThread(); 87 | 88 | return _vsUiShell5 ??= Package.GetGlobalService(typeof(SVsUIShell)) as IVsUIShell5; 89 | } 90 | } 91 | 92 | public static IVsFontAndColorUtilities FontAndColorUtilities 93 | { 94 | get 95 | { 96 | ThreadHelper.ThrowIfNotOnUIThread(); 97 | 98 | return _fontAndColorUtilities ??= Package.GetGlobalService(typeof(SVsFontAndColorStorage)) as IVsFontAndColorUtilities; 99 | } 100 | } 101 | 102 | public static dynamic ColorThemeService 103 | { 104 | get 105 | { 106 | ThreadHelper.ThrowIfNotOnUIThread(); 107 | 108 | return _colorThemeService ??= Package.GetGlobalService(typeof(SVsColorThemeService)); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.Shell.Interop/ThemeColorsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows; 5 | using System.Windows.Media; 6 | 7 | namespace Microsoft.VisualStudio.Shell.Interop 8 | { 9 | abstract class ThemeColorsBase 10 | { 11 | #region ColorEntryFlags 12 | [Flags] 13 | protected enum ColorUsage 14 | { 15 | Background = 1, 16 | Foreground = 2, 17 | } 18 | #endregion 19 | #region ColorEntry 20 | protected abstract class ColorEntry 21 | { 22 | readonly ThemeColorsBase m_parent; 23 | 24 | public ColorEntry(ThemeColorsBase parent) 25 | { 26 | m_parent = parent ?? throw new ArgumentNullException(nameof(parent)); 27 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread 28 | FallbackBackground = Enumerable.Empty(); 29 | FallbackForeground = Enumerable.Empty(); 30 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread 31 | } 32 | 33 | public string Name { get; protected set; } 34 | public ColorUsage Usage { get; protected set; } 35 | public IEnumerable FallbackBackground { get; protected set; } 36 | public IEnumerable FallbackForeground { get; protected set; } 37 | 38 | private Color GetBackgroundColor() 39 | { 40 | ThreadHelper.ThrowIfNotOnUIThread(); 41 | 42 | if (Services.TryGetThemeColor(m_parent.CategoryGuid, Name, __THEMEDCOLORTYPE.TCT_Background, out var result)) 43 | { 44 | return Services.CreateWpfColor(result); 45 | } 46 | 47 | if (VsColor.TryGetValue(FallbackBackground, out result)) 48 | { 49 | return Services.CreateWpfColor(result); 50 | } 51 | 52 | return Services.CreateWpfColor(0); 53 | } 54 | 55 | private Color GetForegroundColor() 56 | { 57 | ThreadHelper.ThrowIfNotOnUIThread(); 58 | 59 | if (Services.TryGetThemeColor(m_parent.CategoryGuid, Name, __THEMEDCOLORTYPE.TCT_Foreground, out var result)) 60 | { 61 | return Services.CreateWpfColor(result); 62 | } 63 | 64 | if (VsColor.TryGetValue(FallbackForeground, out result)) 65 | { 66 | return Services.CreateWpfColor(result); 67 | } 68 | 69 | return Services.CreateWpfColor(0); 70 | } 71 | 72 | public void ReloadColors(ResourceDictionary resources) 73 | { 74 | ThreadHelper.ThrowIfNotOnUIThread(); 75 | 76 | if ((Usage & ColorUsage.Background) == ColorUsage.Background) 77 | { 78 | var color = GetBackgroundColor(); 79 | var brush = new SolidColorBrush(color); 80 | brush.Freeze(); 81 | var key = new ThemeResourceKey(m_parent.CategoryGuid, Name, ThemeResourceKeyType.BackgroundBrush); 82 | resources[key] = brush; 83 | } 84 | 85 | if ((Usage & ColorUsage.Foreground) == ColorUsage.Foreground) 86 | { 87 | var color = GetForegroundColor(); 88 | var brush = new SolidColorBrush(color); 89 | brush.Freeze(); 90 | var key = new ThemeResourceKey(m_parent.CategoryGuid, Name, ThemeResourceKeyType.ForegroundBrush); 91 | resources[key] = brush; 92 | } 93 | } 94 | } 95 | #endregion 96 | 97 | private readonly ResourceDictionary _resourceDictionary; 98 | 99 | protected ThemeColorsBase() 100 | { 101 | _resourceDictionary = new ResourceDictionary(); 102 | Application.Current.Resources.MergedDictionaries.Add(_resourceDictionary); 103 | } 104 | 105 | private bool _fontInfoInitialized; 106 | 107 | public void EnsureFontAndColorsInitialized() 108 | { 109 | ThreadHelper.ThrowIfNotOnUIThread(); 110 | 111 | if (!_fontInfoInitialized) 112 | { 113 | ReloadColors(); 114 | } 115 | } 116 | 117 | public void ReloadColors() 118 | { 119 | ThreadHelper.ThrowIfNotOnUIThread(); 120 | 121 | _fontInfoInitialized = true; 122 | 123 | foreach (var colorEntry in ColorEntries) 124 | { 125 | colorEntry.ReloadColors(_resourceDictionary); 126 | } 127 | } 128 | 129 | protected Guid CategoryGuid { private get; set; } 130 | protected IReadOnlyList ColorEntries { private get; set; } 131 | } 132 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.Shell.Interop/VsColor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.TextManager.Interop; 4 | 5 | namespace Microsoft.VisualStudio.Shell.Interop 6 | { 7 | internal abstract class VsColor 8 | { 9 | public abstract bool TryGetValue(out uint result); 10 | 11 | public static bool TryGetValue(IEnumerable colors, out uint result) 12 | { 13 | ThreadHelper.ThrowIfNotOnUIThread(); 14 | 15 | if (colors == null) 16 | { 17 | throw new ArgumentNullException(nameof(colors)); 18 | } 19 | 20 | foreach (var color in colors) 21 | { 22 | if (color.TryGetValue(out result)) 23 | { 24 | return true; 25 | } 26 | } 27 | 28 | result = 0; 29 | return false; 30 | } 31 | } 32 | 33 | internal sealed class AutoColor : VsColor 34 | { 35 | public override bool TryGetValue(out uint result) 36 | { 37 | ThreadHelper.ThrowIfNotOnUIThread(); 38 | 39 | return ErrorHandler.Succeeded(Services.FontAndColorUtilities.EncodeAutomaticColor(out result)); 40 | } 41 | } 42 | 43 | internal sealed class IndexedColor : VsColor 44 | { 45 | private readonly COLORINDEX _index; 46 | 47 | public IndexedColor(COLORINDEX index) 48 | { 49 | _index = index; 50 | } 51 | public override bool TryGetValue(out uint result) 52 | { 53 | ThreadHelper.ThrowIfNotOnUIThread(); 54 | 55 | return ErrorHandler.Succeeded(Services.FontAndColorUtilities.GetRGBOfIndex(_index, out result)); 56 | } 57 | } 58 | 59 | internal sealed class RgbColor : VsColor 60 | { 61 | private readonly uint _value; 62 | 63 | public RgbColor(byte r, byte b, byte g) 64 | { 65 | _value = (uint)(r | (g << 8) | (b << 16)); 66 | } 67 | public override bool TryGetValue(out uint result) 68 | { 69 | result = _value; 70 | return true; 71 | } 72 | } 73 | 74 | internal sealed class SysColor : VsColor 75 | { 76 | private readonly int _index; 77 | 78 | public SysColor(int index) 79 | { 80 | _index = index; 81 | } 82 | public override bool TryGetValue(out uint result) 83 | { 84 | ThreadHelper.ThrowIfNotOnUIThread(); 85 | 86 | result = 0xff000000 | (uint)SafeNativeMethods.GetSysColor(_index); 87 | return true; 88 | } 89 | } 90 | 91 | internal sealed class VsSysColor : VsColor 92 | { 93 | private readonly int _index; 94 | 95 | public VsSysColor(__VSSYSCOLOREX index) 96 | { 97 | _index = (int)index; 98 | } 99 | public VsSysColor(__VSSYSCOLOREX2 index) 100 | { 101 | _index = (int)index; 102 | } 103 | public VsSysColor(__VSSYSCOLOREX3 index) 104 | { 105 | _index = (int)index; 106 | } 107 | public override bool TryGetValue(out uint result) 108 | { 109 | ThreadHelper.ThrowIfNotOnUIThread(); 110 | 111 | return ErrorHandler.Succeeded(Services.VsUIShell2.GetVSSysColorEx(_index, out result)); 112 | } 113 | } 114 | 115 | internal sealed class ThemeColor : VsColor 116 | { 117 | private readonly Guid _colorCategory; 118 | private readonly string _colorName; 119 | private readonly __THEMEDCOLORTYPE _colorType; 120 | 121 | public ThemeColor(ThemeResourceKey key) 122 | { 123 | _colorCategory = key.Category; 124 | _colorName = key.Name; 125 | _colorType = key.KeyType is ThemeResourceKeyType.BackgroundBrush or ThemeResourceKeyType.BackgroundColor ? __THEMEDCOLORTYPE.TCT_Background : __THEMEDCOLORTYPE.TCT_Foreground; 126 | } 127 | public override bool TryGetValue(out uint result) 128 | { 129 | ThreadHelper.ThrowIfNotOnUIThread(); 130 | 131 | return Services.TryGetThemeColor(_colorCategory, _colorName, _colorType, out result); 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CodeBlockEndTag")] 9 | [assembly: AssemblyDescription("Tags code blocks with an end tag")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Khaos-Coders")] 12 | [assembly: AssemblyProduct("CodeBlockEndTag")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("3.2.1.1")] 33 | [assembly: AssemblyFileVersion("3.2.1.1")] 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VSCodeBlockEndTag 2 | A Visual Studio 2017, 2019 and 2022 extension. It adds an end tag to each code block with information about the header / condition 3 | 4 | For more info see [VS Marketplace](https://marketplace.visualstudio.com/items?itemName=KhaosPrinz.CodeBlockEndTag) 5 | 6 | ## Support this project 7 | Donate via [PayPal](https://www.paypal.com/donate?hosted_button_id=37PBGZPHXY8EC) -------------------------------------------------------------------------------- /ReleaseNotes.txt: -------------------------------------------------------------------------------- 1 | Version 3.2.1.1: 2 | Fixed measure of tags sometimes is too small 3 | Fixed text color sometimes is black 4 | 5 | Version 3.2: 6 | Fixed crash in VS 2019 7 | Added support for Font And Colors options and themeing 8 | 9 | Version 3.1: 10 | Fixed VS 2022 install prerequisites 11 | 12 | Version 3.0: 13 | Added support for VisualStudio 2022 14 | 15 | Version 2.6: 16 | Fixed PayPal link (sorry guys ;P) 17 | 18 | Version 2.5: 19 | Fixed a NRE in CBETagger.Dispose(bool). Reported in VS 16.2.0 and 2017 20 | 21 | Version 2.4: 22 | Restored VisualStudio 2015 compatibility (lost in 2017!) 23 | 24 | Version 2.3: 25 | Fixed a NullReferenceException when closing an editor 26 | 27 | Version 2.2: 28 | Fixed VisualStudio Targets and Dependencies 29 | 30 | Version 2.1: 31 | Restored VisualStudio 2017 compatibility (second try) 32 | Officialy removed VisualStudio 2015 support 33 | 34 | Version 2.0.3: 35 | Restored VisualStudio 2017 compatibility 36 | 37 | Version 2.0.2: 38 | Fixed some bugs while start-up and tear-down phases 39 | 40 | Version 2.0.1: 41 | Support for VisualStudio 2019 42 | 43 | Version 1.5: 44 | Fixed tag overlaps code bug 45 | Added "Tag Size" Option for custom tag scale 46 | Added ability to 'click through' tags, when Double-Click option is set 47 | 48 | Version 1.4: 49 | Use bigger font size for tags 50 | Fixed an issue where keyboard inputs got lost after click on tag 51 | Fixed Visual Studio 2015 not showing tags 52 | 53 | Version 1.3: 54 | Fixed ArgumentOutOfRangeException on copy&paste 55 | Added ability to 'click through' tags, when CTRL+Click option is set 56 | Scale tags with editor line height (font size) 57 | 58 | Version 1.2: 59 | Visual Studio 2017 Support 60 | Added CTRL+Click to navigation option 61 | Strip unnecessary spaces from tags 62 | Hide tags when caret is in same line 63 | 64 | Version 1.1: 65 | Fixed ArgumentOutOfRangeException 66 | 67 | Version 1.0: 68 | Initial Release 69 | -------------------------------------------------------------------------------- /Resources/CBETagPackage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KhaosCoders/VSCodeBlockEndTag/9aeae006d1e5962a1964f3cb390815fb79553107/Resources/CBETagPackage.ico -------------------------------------------------------------------------------- /Resources/VSPreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KhaosCoders/VSCodeBlockEndTag/9aeae006d1e5962a1964f3cb390815fb79553107/Resources/VSPreview.png -------------------------------------------------------------------------------- /Shell/EndTagColors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | 5 | namespace CodeBlockEndTag.Shell 6 | { 7 | internal static class EndTagColors 8 | { 9 | static EndTagColors() 10 | { 11 | var category = new Guid(FontAndColorDefaultsCSharpTags.CategoryGuidString); 12 | 13 | FontFamilyKey = new FontResourceKey(category, FontResourceKeyType.FontFamily); 14 | FontSizeKey = new FontResourceKey(category, FontResourceKeyType.FontSize); 15 | CSharpEndTagForegroundBrushKey = new ThemeResourceKey(category, FontAndColorDefaultsCSharpTags.EntryNames.CSharpEndTag, ThemeResourceKeyType.ForegroundBrush); 16 | } 17 | 18 | public static ThemeResourceKey GetForegroundResourceKey(string lang) 19 | { 20 | return lang switch 21 | { 22 | Languages.CSharp => CSharpEndTagForegroundBrushKey, 23 | _ => CSharpEndTagForegroundBrushKey 24 | }; 25 | } 26 | 27 | public static FontResourceKey FontFamilyKey { get; } 28 | public static FontResourceKey FontSizeKey { get; } 29 | public static ThemeResourceKey CSharpEndTagForegroundBrushKey { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Shell/FontAndColorDefaultsCSharpTags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | 4 | namespace CodeBlockEndTag.Shell 5 | { 6 | internal class FontAndColorDefaultsCSharpTags : FontAndColorDefaultsBase 7 | { 8 | public const string CategoryGuidString = "2A178C76-9E0A-4158-B583-47DD0649F10F"; 9 | public const string CategoryNameString = "CodeBlockEndTag"; 10 | 11 | #region color entries 12 | 13 | internal static class EntryNames 14 | { 15 | public const string CSharpEndTag = "CSharpEndTag"; 16 | } 17 | 18 | sealed class CSharpEndTagEntry : ColorEntry 19 | { 20 | public CSharpEndTagEntry(FontAndColorDefaultsBase parent) : base(parent) 21 | { 22 | Name = EntryNames.CSharpEndTag; 23 | LocalizedName = "C# end tag"; 24 | Usage = ColorUsage.Foreground; 25 | DefaultForeground = new[] { new RgbColor(0x99, 0x99, 0x99) }; 26 | } 27 | } 28 | 29 | #endregion 30 | 31 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread 32 | public FontAndColorDefaultsCSharpTags() 33 | { 34 | Instance = this; 35 | CategoryGuid = new Guid(CategoryGuidString); 36 | CategoryName = CategoryNameString; 37 | Font = CreateFontInfo("Consolas", 9, 1); 38 | ColorEntries = new ColorEntry[] { 39 | new CSharpEndTagEntry(this), 40 | }; 41 | } 42 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread 43 | 44 | public static FontAndColorDefaultsCSharpTags Instance { get; private set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /TextColorConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows.Media; 5 | 6 | namespace CodeBlockEndTag 7 | { 8 | internal class TextColorConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | switch (value) 13 | { 14 | case Brush brush: 15 | return brush; 16 | case Color color: 17 | return new SolidColorBrush(color); 18 | case string str: 19 | return new SolidColorBrush((Color)ColorConverter.ConvertFromString(str)); 20 | default: 21 | return Colors.Black; 22 | } 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | KC CodeBlock End Tag Extension 122 | 123 | 124 | Adds an end tag to each code block with information about the header / condition 125 | 126 | 127 | KC Extensions 128 | 129 | 130 | CodeBlock End Tagger 131 | 132 | 133 | 134 | Resources\CBETagPackage.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 135 | 136 | -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | CodeBlockEndTag 6 | Adds an end tag to each code block with information about the header / condition 7 | https://github.com/KhaosCoders/VSCodeBlockEndTag 8 | LICENSE.txt 9 | ReleaseNotes.txt 10 | Resources\CBETagPackage.ico 11 | Resources\VSPreview.png 12 | code, block, codeblock, end, tag, tagger, bracket, brackets, info, information, condition, vscommand, vscommands 13 | 14 | 15 | 16 | 17 | 18 | amd64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------