├── .gitignore
├── AlwaysAlignedVS.sln
├── AlwaysAlignedVS
├── About.xaml
├── About.xaml.cs
├── AlwaysAligned.vsct
├── AlwaysAlignedConfigurationService.cs
├── AlwaysAlignedPackage.cs
├── AlwaysAlignedVS.csproj
├── AlwaysAlignedVS.csproj.user
├── AppInfo.cs
├── CommandFilter.cs
├── ElasticTabstopsConverter.cs
├── ElasticTabstopsFormatter.cs
├── ElasticTabstopsProvider.cs
├── ElasticTabstopsSizeManager.cs
├── ExternalSettingsTracker.cs
├── GlobalSuppressions.cs
├── Guids.cs
├── Logo.xaml
├── Logo.xaml.cs
├── MiscGui.cs
├── PkgCmdID.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── Resources.Designer.cs
├── Resources.resx
├── Resources
│ ├── Images_32bit.png
│ ├── Package.ico
│ ├── icon.png
│ ├── logoframes.png
│ └── previewimage.png
├── SettingsDialog.xaml
├── SettingsDialog.xaml.cs
├── TextBufferToViewMapService.cs
├── TextMeasureService.cs
├── UpdateTabCreationListener.cs
├── VSPackage.resx
├── VsTextViewCreationListener.cs
├── WeakEvent.cs
├── WpfTextConnectionListener.cs
├── app.config
├── packages.config
└── source.extension.vsixmanifest
├── ElasticTabstopsConverterTest
├── ElasticTabstopsConverterTest.cs
├── ElasticTabstopsConverterTest.csproj
└── Properties
│ └── AssemblyInfo.cs
├── LICENSE.md
├── Local.testsettings
├── README.md
├── TraceAndTestImpact.testsettings
└── screencapture.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj
2 | [Bb]in
3 | *.suo
4 | *.[Cc]ache
5 | *.ncb
6 | *.log
7 | *.DS_Store
8 | *.bak
9 | *~
10 | [Tt]humbs.db
11 | TestResults
12 | packages
13 |
14 | # Visual Studio cache/options directory
15 | .vs/
16 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26730.10
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{789894EA-EFA3-4108-A5BA-49CF882F902E}"
7 | ProjectSection(SolutionItems) = preProject
8 | AlwaysAligned.vsmdi = AlwaysAligned.vsmdi
9 | Local.testsettings = Local.testsettings
10 | TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings
11 | EndProjectSection
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlwaysAlignedVS", "AlwaysAlignedVS\AlwaysAlignedVS.csproj", "{2939A962-7734-4BDA-937F-25DC423B3843}"
14 | EndProject
15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElasticTabstopsConverterTest", "ElasticTabstopsConverterTest\ElasticTabstopsConverterTest.csproj", "{7A729CF5-BE92-44A2-8A98-B58E92A3AC45}"
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Release|Any CPU = Release|Any CPU
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {2939A962-7734-4BDA-937F-25DC423B3843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {2939A962-7734-4BDA-937F-25DC423B3843}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {2939A962-7734-4BDA-937F-25DC423B3843}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {2939A962-7734-4BDA-937F-25DC423B3843}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {7A729CF5-BE92-44A2-8A98-B58E92A3AC45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {7A729CF5-BE92-44A2-8A98-B58E92A3AC45}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {7A729CF5-BE92-44A2-8A98-B58E92A3AC45}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {7A729CF5-BE92-44A2-8A98-B58E92A3AC45}.Release|Any CPU.Build.0 = Release|Any CPU
31 | EndGlobalSection
32 | GlobalSection(SolutionProperties) = preSolution
33 | HideSolutionNode = FALSE
34 | EndGlobalSection
35 | GlobalSection(ExtensibilityGlobals) = postSolution
36 | SolutionGuid = {1648C81C-E903-4AB3-9E8C-A52F0321BBD9}
37 | EndGlobalSection
38 | GlobalSection(TestCaseManagementSettings) = postSolution
39 | CategoryFile = AlwaysAligned.vsmdi
40 | EndGlobalSection
41 | EndGlobal
42 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/About.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | This is free software, released under the MIT License. To learn about elastic tabstops, see
24 | nickgravgaard.com/elastic-tabstops/
25 |
26 |
27 | Copyright © 2010-2017 Nick Gravgaard
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/About.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Shapes;
13 | using System.Windows.Navigation;
14 | using System.Diagnostics;
15 | using System.Reflection;
16 |
17 | namespace AlwaysAligned
18 | {
19 | ///
20 | /// Interaction logic for About.xaml
21 | ///
22 | public partial class About : Window
23 | {
24 | public About()
25 | {
26 | InitializeComponent();
27 |
28 | string informationalVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
29 | var fieldsParagraph = new Paragraph();
30 | fieldsParagraph.Inlines.Add(new Run("Version: "));
31 | fieldsParagraph.Inlines.Add(new Bold(new Run(informationalVersion)));
32 |
33 | AboutFlowDoc.Blocks.InsertBefore(AboutFlowDoc.Blocks.FirstBlock, fieldsParagraph);
34 | }
35 |
36 | private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
37 | {
38 | System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri));
39 | e.Handled = true;
40 | }
41 |
42 | private void Button_Click(object sender, RoutedEventArgs e)
43 | {
44 | this.Close();
45 | }
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/AlwaysAligned.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
39 |
40 |
41 |
45 |
46 |
49 |
50 |
51 |
52 |
63 |
64 |
65 |
66 |
68 |
69 |
81 |
93 |
105 |
117 |
118 |
119 |
120 |
121 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/AlwaysAlignedConfigurationService.cs:
--------------------------------------------------------------------------------
1 | using AlwaysAligned.Properties;
2 | using System;
3 |
4 | namespace AlwaysAligned
5 | {
6 | public class AlwaysAlignedConfiguration : ICloneable
7 | {
8 | public int MinimumCellWidth { get; set; }
9 | public int CellPadding { get; set; }
10 | public bool Enabled { get; set; }
11 | public bool ConvertOnLoadSave { get; set; }
12 |
13 | public object Clone()
14 | {
15 | return new AlwaysAlignedConfiguration
16 | {
17 | ConvertOnLoadSave = ConvertOnLoadSave,
18 | Enabled = Enabled,
19 | MinimumCellWidth = MinimumCellWidth,
20 | CellPadding = CellPadding
21 | };
22 | }
23 | }
24 |
25 | public class ConfigurationSavedEventArgs : EventArgs
26 | {
27 | public bool HasChanges { get; internal set; }
28 | }
29 |
30 | public class AlwaysAlignedConfigurationService
31 | {
32 | static readonly Lazy LazyInstance = new Lazy(() => new AlwaysAlignedConfigurationService());
33 |
34 | private readonly WeakEvent> _configurationSavedWeakEvent = new WeakEvent>();
35 |
36 | internal event EventHandler ConfigurationChanged
37 | {
38 | add
39 | {
40 | _configurationSavedWeakEvent.AddHandler(value);
41 | }
42 | remove
43 | {
44 | _configurationSavedWeakEvent.RemoveHandler(value);
45 | }
46 | }
47 |
48 | private AlwaysAlignedConfigurationService()
49 | {
50 |
51 | }
52 |
53 | public static AlwaysAlignedConfigurationService Instance
54 | {
55 | get
56 | {
57 | return LazyInstance.Value;
58 | }
59 | }
60 |
61 | internal AlwaysAlignedConfiguration Config;
62 |
63 | public AlwaysAlignedConfiguration GetConfiguration()
64 | {
65 | if (Config == null)
66 | {
67 | Config = new AlwaysAlignedConfiguration
68 | {
69 | Enabled = Settings.Default.Enabled,
70 | ConvertOnLoadSave = Settings.Default.ConvertOnLoadSave,
71 | MinimumCellWidth = Settings.Default.MinimumCellWidth,
72 | CellPadding = Settings.Default.CellPadding
73 | };
74 | }
75 | return Config;
76 | }
77 |
78 | public void Save()
79 | {
80 | Save(Config);
81 | }
82 |
83 | public void Save(AlwaysAlignedConfiguration config)
84 | {
85 | bool hasChanges = Settings.Default.Enabled != config.Enabled;
86 | Settings.Default.Enabled = config.Enabled;
87 |
88 | hasChanges = hasChanges || (Settings.Default.ConvertOnLoadSave != config.ConvertOnLoadSave);
89 | Settings.Default.ConvertOnLoadSave = config.ConvertOnLoadSave;
90 |
91 | hasChanges = hasChanges || (Settings.Default.MinimumCellWidth != config.MinimumCellWidth);
92 | Settings.Default.MinimumCellWidth = (int)config.MinimumCellWidth;
93 |
94 | hasChanges = hasChanges || (Settings.Default.CellPadding != config.CellPadding);
95 | Settings.Default.CellPadding = (int)config.CellPadding;
96 |
97 | Settings.Default.Save();
98 |
99 | //Refresh config
100 | Config = null;
101 | OnConfigurationSaved(new ConfigurationSavedEventArgs { HasChanges = hasChanges });
102 | }
103 |
104 | public string GetSettingsAsString()
105 | {
106 | var templ = "Enabled: {0}, ConvertOnLoadSave: {1}, MinimumCellWidth: {2}, CellPadding: {3}";
107 | if (Config == null)
108 | {
109 | return string.Format(templ,
110 | Settings.Default.Enabled,
111 | Settings.Default.ConvertOnLoadSave,
112 | Settings.Default.MinimumCellWidth,
113 | Settings.Default.CellPadding
114 | );
115 | }
116 | else
117 | {
118 | return string.Format(templ,
119 | Config.Enabled,
120 | Config.ConvertOnLoadSave,
121 | Config.MinimumCellWidth,
122 | Config.CellPadding
123 | );
124 | }
125 | }
126 |
127 | public void OnExternalConfigurationChanged()
128 | {
129 | OnConfigurationSaved(new ConfigurationSavedEventArgs { HasChanges = true });
130 | }
131 |
132 | private void OnConfigurationSaved(ConfigurationSavedEventArgs eventArgs)
133 | {
134 | _configurationSavedWeakEvent.Raise(this, eventArgs);
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/AlwaysAlignedPackage.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 | using Microsoft.VisualStudio.Shell;
3 | using Microsoft.VisualStudio.Shell.Interop;
4 | using System;
5 | using System.ComponentModel.Design;
6 | using System.Diagnostics;
7 | using System.Globalization;
8 | using System.Runtime.InteropServices;
9 |
10 | namespace AlwaysAligned
11 | {
12 | ///
13 | /// This is the class that implements the package exposed by this assembly.
14 | ///
15 | /// The minimum requirement for a class to be considered a valid package for Visual Studio
16 | /// is to implement the IVsPackage interface and register itself with the shell.
17 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
18 | /// to do it: it derives from the Package class that provides the implementation of the
19 | /// IVsPackage interface and uses the registration attributes defined in the framework to
20 | /// register itself and its components with the shell.
21 | ///
22 | // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
23 | // a package.
24 | [PackageRegistration(UseManagedResourcesOnly = true)]
25 | // This attribute is used to register the informations needed to show the this package
26 | // in the Help/About dialog of Visual Studio.
27 | [InstalledProductRegistration("#110", "#112", "2017.0.0", IconResourceID = 400)]
28 | // This attribute is needed to let the shell know that this package exposes some menus.
29 | [ProvideMenuResource("Menus.ctmenu", 1)]
30 | [Guid(GuidList.guidAlwaysAlignedPkgString)]
31 | [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.NoSolution)]
32 | public sealed class AlwaysAlignedPackage : Package
33 | {
34 | private static AlwaysAlignedPackage _package;
35 |
36 | public static AlwaysAlignedPackage Instance
37 | {
38 | get { return _package; }
39 | }
40 |
41 | ///
42 | /// Default constructor of the package.
43 | /// Inside this method you can place any initialization code that does not require
44 | /// any Visual Studio service because at this point the package object is created but
45 | /// not sited yet inside Visual Studio environment. The place to do all the other
46 | /// initialization is the Initialize method.
47 | ///
48 | public AlwaysAlignedPackage()
49 | {
50 | Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
51 | _package = this;
52 | }
53 |
54 | /////////////////////////////////////////////////////////////////////////////
55 | // Overriden Package Implementation
56 |
57 | ///
58 | /// Initialization of the package; this method is called right after the package is sited, so this is the place
59 | /// where you can put all the initilaization code that rely on services provided by VisualStudio.
60 | ///
61 | protected override void Initialize()
62 | {
63 | Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
64 |
65 | base.Initialize();
66 |
67 | AppInfo.appObject = (EnvDTE80.DTE2)ServiceProvider.GlobalProvider.GetService(typeof(DTE));
68 |
69 | ExternalSettingsTracker.Start();
70 |
71 | OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
72 | if (mcs != null)
73 | {
74 | // Create the command for the menu item.
75 | CommandID menuCommandID = new CommandID(GuidList.guidAlwaysAlignedCmdSet, (int)PkgCmdIDList.AlwaysAlignedMenu);
76 | OleMenuCommand menuItem = new OleMenuCommand(MenuItemCallback, menuCommandID);
77 | menuItem.BeforeQueryStatus += new EventHandler(OnBeforeQueryStatusSetMenuText);
78 | mcs.AddCommand(menuItem);
79 | }
80 | }
81 |
82 | private void MenuItemCallback(object sender, EventArgs e)
83 | {
84 | IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
85 | Guid clsid = Guid.Empty;
86 | int result;
87 |
88 | Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(
89 | uiShell.ShowMessageBox(
90 | 0, ref clsid,
91 | "FirstPackage",
92 | string.Format(CultureInfo.CurrentCulture,
93 | "Inside {0}.MenuItemCallback()", this.ToString()),
94 | string.Empty, 0,
95 | OLEMSGBUTTON.OLEMSGBUTTON_OK,
96 | OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
97 | OLEMSGICON.OLEMSGICON_INFO,
98 | 0, out result));
99 | }
100 |
101 | bool menuTextSet = false;
102 | private void OnBeforeQueryStatusSetMenuText(object sender, EventArgs e)
103 | {
104 | if (!menuTextSet)
105 | {
106 | var myCommand = sender as OleMenuCommand;
107 | if (myCommand != null)
108 | {
109 | myCommand.Text = "Always Aligned";
110 | menuTextSet = true;
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/AlwaysAlignedVS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | 2.0
8 | {2939A962-7734-4BDA-937F-25DC423B3843}
9 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
10 | Library
11 | Properties
12 | AlwaysAligned
13 | AlwaysAlignedVS
14 | True
15 |
16 |
17 | v4.6
18 | 15.0
19 |
20 |
21 |
22 |
23 | 4.0
24 | false
25 |
26 | publish\
27 | true
28 | Disk
29 | false
30 | Foreground
31 | 7
32 | Days
33 | false
34 | false
35 | true
36 | 0
37 | 1.0.0.%2a
38 | false
39 | true
40 |
41 |
42 | true
43 | full
44 | false
45 | bin\Debug\
46 | TRACE;DEBUG
47 | prompt
48 | 4
49 | false
50 |
51 |
52 | pdbonly
53 | true
54 | bin\Release\
55 | TRACE
56 | prompt
57 | 4
58 | true
59 | false
60 |
61 |
62 |
63 | False
64 |
65 |
66 | False
67 |
68 |
69 |
70 | True
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | True
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 | {00020430-0000-0000-C000-000000000046}
107 | 2
108 | 0
109 | 0
110 | primary
111 | False
112 | False
113 |
114 |
115 |
116 |
117 | About.xaml
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | Logo.xaml
130 |
131 |
132 |
133 | True
134 | True
135 | Settings.settings
136 |
137 |
138 | True
139 | True
140 | Resources.resx
141 |
142 |
143 |
144 |
145 |
146 |
147 | SettingsDialog.xaml
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | ResXFileCodeGenerator
159 | Resources.Designer.cs
160 | Designer
161 |
162 |
163 | true
164 | VSPackage
165 |
166 |
167 |
168 |
169 | Designer
170 |
171 |
172 |
173 | SettingsSingleFileGenerator
174 | Settings.Designer.cs
175 |
176 |
177 | Designer
178 |
179 |
180 |
181 |
182 | Menus.ctmenu
183 | Designer
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | Always
193 | true
194 |
195 |
196 | true
197 | Always
198 |
199 |
200 | true
201 | Always
202 |
203 |
204 |
205 |
206 |
207 | MSBuild:Compile
208 | Designer
209 |
210 |
211 | MSBuild:Compile
212 | Designer
213 |
214 |
215 | MSBuild:Compile
216 | Designer
217 |
218 |
219 |
220 |
221 | False
222 | Microsoft .NET Framework 4 %28x86 and x64%29
223 | true
224 |
225 |
226 | False
227 | .NET Framework 3.5 SP1 Client Profile
228 | false
229 |
230 |
231 | False
232 | .NET Framework 3.5 SP1
233 | false
234 |
235 |
236 | False
237 | Windows Installer 4.5
238 | true
239 |
240 |
241 |
242 | true
243 |
244 |
245 | 10.0
246 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
247 |
248 |
249 |
250 |
251 |
258 |
259 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/AlwaysAlignedVS.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Program
5 | C:\Program Files %28x86%29\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe
6 | /rootsuffix Exp
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | en-US
16 | false
17 |
18 |
19 | Program
20 | C:\Program Files %28x86%29\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.exe
21 | /rootsuffix Exp
22 |
23 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/AppInfo.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE80;
2 |
3 | namespace AlwaysAligned
4 | {
5 | static class AppInfo
6 | {
7 | public static DTE2 appObject = null;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/CommandFilter.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 | using Microsoft.VisualStudio;
3 | using Microsoft.VisualStudio.OLE.Interop;
4 | using Microsoft.VisualStudio.Text.Editor;
5 | using System;
6 |
7 | namespace AlwaysAligned
8 | {
9 | class CommandFilter : IOleCommandTarget
10 | {
11 | public CommandFilter(IWpfTextView view)
12 | {
13 | //changeMenuText();
14 | }
15 |
16 | internal IOleCommandTarget Next { get; set; }
17 |
18 | public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
19 | {
20 | if (pguidCmdGroup == GuidList.guidAlwaysAlignedCmdSet)
21 | {
22 | switch (nCmdID)
23 | {
24 | case PkgCmdIDList.cmdidSettings:
25 | _btnConfigure_Click();
26 | return VSConstants.S_OK;
27 | case PkgCmdIDList.cmdidConvertToSpaces:
28 | _btnAlignSpaces_Click();
29 | return VSConstants.S_OK;
30 | case PkgCmdIDList.cmdidConvertToElasticTabstops:
31 | _btnAlignTabsElastic_Click();
32 | return VSConstants.S_OK;
33 | case PkgCmdIDList.cmdidAbout:
34 | _btnInfoDialog_Click();
35 | return VSConstants.S_OK;
36 | default:
37 | break;
38 | }
39 | }
40 |
41 | return Next.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
42 | }
43 |
44 | public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
45 | {
46 | if (pguidCmdGroup == GuidList.guidAlwaysAlignedCmdSet)
47 | {
48 | switch (prgCmds[0].cmdID)
49 | {
50 | case PkgCmdIDList.cmdidSettings:
51 | prgCmds[0].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
52 | break;
53 | case PkgCmdIDList.cmdidConvertToSpaces:
54 | prgCmds[0].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
55 | break;
56 | case PkgCmdIDList.cmdidConvertToElasticTabstops:
57 | prgCmds[0].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
58 | break;
59 | case PkgCmdIDList.cmdidAbout:
60 | prgCmds[0].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
61 | break;
62 | default:
63 | break;
64 | }
65 | return VSConstants.S_OK;
66 | }
67 |
68 | return Next.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
69 | }
70 |
71 | private void _btnAlignSpaces_Click()
72 | {
73 | if (AppInfo.appObject.ActiveDocument == null) return;
74 |
75 | var doc = (TextDocument)AppInfo.appObject.ActiveDocument.Object("TextDocument");
76 | if (doc == null) return;
77 |
78 | string text = doc.StartPoint.CreateEditPoint().GetText(doc.EndPoint);
79 | string convertedText = ElasticTabstopsConverter.ToSpaces(text, doc.TabSize);
80 | doc.ReplaceText(text, convertedText);
81 | }
82 |
83 | private void _btnAlignTabsElastic_Click()
84 | {
85 | if (AppInfo.appObject.ActiveDocument == null) return;
86 |
87 | var doc = (TextDocument)AppInfo.appObject.ActiveDocument.Object("TextDocument");
88 | if (doc == null) return;
89 |
90 | string text = doc.StartPoint.CreateEditPoint().GetText(doc.EndPoint);
91 | string convertedText = ElasticTabstopsConverter.ToElasticTabstops(text, doc.TabSize);
92 | doc.ReplaceText(text, convertedText);
93 | }
94 |
95 | private void _btnConfigure_Click()
96 | {
97 | var sd = new SettingsDialog
98 | {
99 | WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen,
100 | ShowInTaskbar = false
101 | };
102 | sd.ShowDialog();
103 | }
104 |
105 | private void _btnInfoDialog_Click()
106 | {
107 | var sd = new About
108 | {
109 | WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen,
110 | ShowInTaskbar = false
111 | };
112 | sd.ShowDialog();
113 | }
114 |
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/ElasticTabstopsConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | // TODO: Rewrite this so it's based off my much nicer implementation at:
7 | // https://github.com/nickgravgaard/ElasticNotepad/blob/master/src/main/scala/elasticTabstops.scala
8 |
9 | namespace AlwaysAligned
10 | {
11 | public class Cell
12 | {
13 | public int IndexFromBOF;
14 | public int Length;
15 |
16 | public Cell()
17 | {
18 | Length = 0;
19 | }
20 |
21 | public Cell(int indexFromBOF, int length)
22 | {
23 | IndexFromBOF = indexFromBOF;
24 | Length = length;
25 | }
26 | }
27 |
28 | public class Line
29 | {
30 | public SortedDictionary Cells;
31 | public bool EndsInCR;
32 |
33 | public Line()
34 | {
35 | Cells = new SortedDictionary();
36 | EndsInCR = false;
37 | }
38 |
39 | public Line(SortedDictionary cells)
40 | {
41 | Cells = cells;
42 | EndsInCR = false;
43 | }
44 | }
45 |
46 |
47 | public class ElasticTabstopsConverter
48 | {
49 | public static bool CellExists(List lines, int lineNum, int cellNum)
50 | {
51 | return (lineNum < lines.Count) && (cellNum < lines[lineNum].Cells.Count);
52 | }
53 | public static bool CellExists(List> list, int lineNum, int cellNum)
54 | {
55 | return (lineNum < list.Count) && (cellNum < list[lineNum].Count);
56 | }
57 |
58 |
59 | private static int CalcFixedCellSize(int textLen, int tabSize)
60 | {
61 | if (tabSize > 0)
62 | {
63 | return ((int)Math.Ceiling((textLen + 2.0) / tabSize)) * tabSize;
64 | }
65 | return tabSize;
66 | }
67 |
68 |
69 | public static List GetLines(string text, int tabSize)
70 | {
71 | var lines = new List();
72 | var line = new Line();
73 | lines.Add(line);
74 | var inText = false;
75 | var previousCharIsSpace = false;
76 | var textLength = 0;
77 | var pos = 0;
78 | var startPos = 0;
79 | var startCharNum = 0;
80 | for (var charNum = 0; charNum < text.Length; charNum++)
81 | {
82 | var currentChar = text[charNum];
83 | switch (currentChar)
84 | {
85 | case '\r':
86 | {
87 | line.EndsInCR = true;
88 | break;
89 | }
90 | case '\n':
91 | {
92 | if (inText)
93 | {
94 | line.Cells.Add(startPos, new Cell(startCharNum, textLength));
95 | }
96 | line = new Line();
97 | lines.Add(line);
98 | pos = 0;
99 | inText = false;
100 | previousCharIsSpace = false;
101 | break;
102 | }
103 | case '\t':
104 | {
105 | if (inText)
106 | {
107 | if (previousCharIsSpace)
108 | {
109 | line.Cells.Add(startPos, new Cell(startCharNum, textLength - 1));
110 | }
111 | else
112 | {
113 | line.Cells.Add(startPos, new Cell(startCharNum, textLength));
114 | }
115 | inText = false;
116 | }
117 | previousCharIsSpace = false;
118 | int expand = tabSize - (pos % tabSize);
119 | pos += expand;
120 | break;
121 | }
122 | case ' ':
123 | {
124 | if (previousCharIsSpace && inText)
125 | {
126 | line.Cells.Add(startPos, new Cell(startCharNum, textLength - 1));
127 | inText = false;
128 | }
129 | previousCharIsSpace = true;
130 | textLength++;
131 | pos++;
132 | break;
133 | }
134 | default:
135 | {
136 | if (!inText)
137 | {
138 | startPos = pos;
139 | startCharNum = charNum;
140 | textLength = 0;
141 | }
142 | inText = true;
143 | previousCharIsSpace = false;
144 | textLength++;
145 | pos++;
146 | break;
147 | }
148 | }
149 | }
150 | if (inText)
151 | {
152 | line.Cells.Add(startPos, new Cell(startCharNum, textLength));
153 | }
154 | return lines;
155 | }
156 |
157 |
158 | enum AtPosResults
159 | {
160 | PastEndOfLine,
161 | CellStart,
162 | CellMiddle,
163 | Space,
164 | };
165 |
166 | static AtPosResults CellExistsAtPos(int position, SortedDictionary cells)
167 | {
168 | if (cells.Count == 0) return AtPosResults.PastEndOfLine;
169 |
170 | if (cells.ContainsKey(position)) return AtPosResults.CellStart;
171 |
172 | foreach (KeyValuePair cell in cells)
173 | {
174 | if (cell.Key > position)
175 | {
176 | return AtPosResults.Space;
177 | }
178 |
179 | if (position >= cell.Key && position <= cell.Key + cell.Value.Length + 2)
180 | {
181 | return AtPosResults.CellMiddle;
182 | }
183 | }
184 | return AtPosResults.PastEndOfLine;
185 | }
186 |
187 |
188 | static void InsertEmptyCells(ref List lines, int pos, int firstBlockLineNum, int lastBlockLineNum)
189 | {
190 | for (var blockLineNum = firstBlockLineNum; blockLineNum <= lastBlockLineNum; blockLineNum++)
191 | {
192 | lines[blockLineNum].Cells.Add(pos, new Cell());
193 | }
194 | }
195 |
196 |
197 | public static String ToElasticTabstops(String text, int tabSize = 4)
198 | {
199 | var lines = GetLines(text, tabSize);
200 | var maxCells = lines.Aggregate(0, (current, line) => Math.Max(line.Cells.Count, current));
201 |
202 | for (var lineNum = 0; lineNum < lines.Count; lineNum++)
203 | {
204 | var line = lines[lineNum];
205 | foreach (KeyValuePair cell in line.Cells)
206 | {
207 | int position = cell.Key;
208 | for (var i = lineNum + 1; i <= lines.Count - 1; i++)
209 | {
210 | var atPosResult = CellExistsAtPos(position, lines[i].Cells);
211 | if (atPosResult == AtPosResults.CellStart)
212 | {
213 | continue;
214 | }
215 | if (atPosResult == AtPosResults.Space)
216 | {
217 | lines[i].Cells.Add(position, new Cell());
218 | continue;
219 | }
220 | else
221 | {
222 | break;
223 | }
224 | }
225 | }
226 | }
227 |
228 | var maxPos = 0;
229 | foreach (var line in lines)
230 | {
231 | var cells = line.Cells;
232 | if (cells.Count > 0)
233 | {
234 | var lastCell = cells.Last();
235 | var lineEnd = lastCell.Key + lastCell.Value.Length;
236 | maxPos = Math.Max(maxPos, lineEnd);
237 | }
238 | }
239 |
240 | for (var pos = 0; pos < maxPos; pos += tabSize)
241 | {
242 | var startingNewBlock = true;
243 | var firstBlockLineNum = 0;
244 | var lastBlockLineNum = 0;
245 | var allSpaces = true;
246 |
247 | for (var lineNum = 0; lineNum < lines.Count; lineNum++)
248 | {
249 | var line = lines[lineNum];
250 |
251 | var atPosResult = CellExistsAtPos(pos, line.Cells);
252 | if (atPosResult == AtPosResults.Space || atPosResult == AtPosResults.CellMiddle)
253 | {
254 | if (atPosResult != AtPosResults.Space)
255 | {
256 | allSpaces = false;
257 | }
258 | if (startingNewBlock)
259 | {
260 | firstBlockLineNum = lineNum;
261 | startingNewBlock = false;
262 | }
263 | lastBlockLineNum = lineNum;
264 | }
265 | else
266 | {
267 | if (!startingNewBlock)
268 | {
269 | if (allSpaces)
270 | {
271 | InsertEmptyCells(ref lines, pos, firstBlockLineNum, lastBlockLineNum);
272 | }
273 | startingNewBlock = true;
274 | allSpaces = true;
275 | }
276 | }
277 | }
278 | if (!startingNewBlock && allSpaces)
279 | {
280 | InsertEmptyCells(ref lines, pos, firstBlockLineNum, lastBlockLineNum);
281 | }
282 | }
283 |
284 | var builder = new StringBuilder(text.Length);
285 | var lastLine = lines.Last();
286 | foreach (var line in lines)
287 | {
288 | if (line.Cells.Count > 0)
289 | {
290 | var lastCell = line.Cells.Last();
291 | foreach (var cell in line.Cells)
292 | {
293 | if (cell.Value.Length > 0)
294 | {
295 | builder.Append(text.Substring(cell.Value.IndexFromBOF, cell.Value.Length));
296 | }
297 | if (!cell.Equals(lastCell))
298 | {
299 | builder.Append('\t');
300 | }
301 | }
302 | }
303 | if (!line.Equals(lastLine))
304 | {
305 | if (line.EndsInCR)
306 | {
307 | builder.Append('\r');
308 | }
309 | builder.Append('\n');
310 | }
311 | }
312 | return builder.ToString();
313 | }
314 |
315 |
316 | public static String ToSpaces(String text, int tabSize = 4)
317 | {
318 | var textLines = text.Split('\n');
319 | IList> lines = textLines.Select(textLine => textLine.Split('\t')).Cast>().ToList();
320 |
321 | List> sizes = new List>();
322 | foreach (var line in lines)
323 | {
324 | List sizesLine = new List();
325 | foreach (var cell in line)
326 | {
327 | sizesLine.Add(CalcFixedCellSize(cell.Length, tabSize));
328 | }
329 | sizes.Add(sizesLine);
330 | }
331 |
332 | var maxCells = lines.Aggregate(0, (current, line) => Math.Max(current, line.Count));
333 | var nofLines = lines.Count;
334 |
335 | for (int cellNum = 0; cellNum < maxCells; cellNum++)
336 | {
337 | var startingNewBlock = true;
338 | int startRange = 0;
339 | int endRange = 0;
340 | int maxWidth = 0;
341 |
342 | for (int lineNum = 0; lineNum < nofLines; lineNum++)
343 | {
344 | if (CellExists(sizes, lineNum, cellNum) && CellExists(sizes, lineNum, cellNum + 1))
345 | {
346 | if (startingNewBlock)
347 | {
348 | startRange = lineNum;
349 | startingNewBlock = false;
350 | }
351 | maxWidth = Math.Max(maxWidth, sizes[lineNum][cellNum]);
352 | endRange = lineNum;
353 | }
354 | else
355 | {
356 |
357 | if (!startingNewBlock)
358 | {
359 | for (var blockcellNum = startRange; blockcellNum <= endRange; blockcellNum++)
360 | {
361 | sizes[blockcellNum][cellNum] = maxWidth;
362 | }
363 | startingNewBlock = true;
364 | maxWidth = 0;
365 | }
366 | }
367 | }
368 |
369 | if (!startingNewBlock)
370 | {
371 | for (int blockcellNum = startRange; blockcellNum <= endRange; blockcellNum++)
372 | {
373 | sizes[blockcellNum][cellNum] = maxWidth;
374 | }
375 | }
376 | }
377 |
378 | // build final string
379 | IList newText = new List();
380 | for (var lineNum = 0; lineNum < nofLines; lineNum++)
381 | {
382 | string newLine = "";
383 | for (var cellNum = 0; cellNum < lines[lineNum].Count; cellNum++)
384 | {
385 | newLine += lines[lineNum][cellNum];
386 | if (cellNum != lines[lineNum].Count - 1)
387 | {
388 | var nofSpaces = sizes[lineNum][cellNum] - lines[lineNum][cellNum].Length;
389 | newLine += new string(' ', nofSpaces);
390 | }
391 | }
392 | newText.Add(newLine);
393 | }
394 | return String.Join("\n", newText);
395 | }
396 |
397 | }
398 | }
399 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/ElasticTabstopsFormatter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text.Formatting;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows.Media.TextFormatting;
5 |
6 | namespace AlwaysAligned
7 | {
8 | ///
9 | /// Provides text formatting properties.
10 | ///
11 | internal class ElasticTabstopsFormatter : TextFormattingParagraphProperties
12 | {
13 | private readonly double[] _tabSizes;
14 |
15 | ///
16 | /// Creates an instance of ElasticTabstopsFormatter
17 | ///
18 | internal ElasticTabstopsFormatter(
19 | TextFormattingRunProperties textProperties,
20 | IFormattedLineSource formattedLineSource, double[] tabSizes)
21 | : base(textProperties, formattedLineSource.ColumnWidth * formattedLineSource.TabSize)
22 | {
23 | _tabSizes = tabSizes;
24 | }
25 |
26 | ///
27 | /// Gets a collection of tab definitions.
28 | ///
29 | public override IList Tabs
30 | {
31 | get
32 | {
33 | var tabList = _tabSizes.Select((ts, i) => new TextTabProperties(TextTabAlignment.Left, ts, 0, 0)).ToList();
34 | return tabList;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/ElasticTabstopsProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using Microsoft.VisualStudio.Text.Editor;
3 | using Microsoft.VisualStudio.Text.Formatting;
4 | using Microsoft.VisualStudio.Utilities;
5 | using System.ComponentModel.Composition;
6 | using System.Linq;
7 | using System.Windows.Media.TextFormatting;
8 |
9 | namespace AlwaysAligned
10 | {
11 | ///
12 | /// Creates ElasticTabstopsFormatter classes
13 | /// to be used when lines on the view are being formatted.
14 | ///
15 | [Export(typeof(ITextParagraphPropertiesFactoryService))]
16 | [ContentType("text")]
17 | [TextViewRole(PredefinedTextViewRoles.Document)]
18 | internal class ElasticTabstopsProvider : ITextParagraphPropertiesFactoryService
19 | {
20 | [Import]
21 | private ITextBufferToViewMapService _textBufferToViewMapService = null;
22 |
23 | ///
24 | /// Creates an ElasticTabstopsFormatters for
25 | /// the provided configuration.
26 | ///
27 | public TextParagraphProperties Create(IFormattedLineSource formattedLineSource, TextFormattingRunProperties textProperties,
28 | IMappingSpan line, IMappingPoint lineStart, int lineSegment)
29 | {
30 | if (!AlwaysAlignedConfigurationService.Instance.GetConfiguration().Enabled)
31 | {
32 | return new TextFormattingParagraphProperties(textProperties, formattedLineSource.ColumnWidth * formattedLineSource.TabSize);
33 | }
34 |
35 | IWpfTextView textView = _textBufferToViewMapService.GetViewByFormattedLineSource(formattedLineSource);
36 | //View is not initialized yet
37 | if (textView == null)
38 | {
39 | return new TextFormattingParagraphProperties(textProperties, formattedLineSource.ColumnWidth * formattedLineSource.TabSize);
40 | }
41 | var manager = ElasticTabstopsSizeManager.Get(textView);
42 |
43 | ITextSnapshot textSnapshot = formattedLineSource.SourceTextSnapshot;
44 | ITextBuffer textBuffer = textSnapshot.TextBuffer;
45 |
46 | var normalizedspancoll = line.GetSpans(textBuffer);
47 | ITextSnapshotLine currentLine = textSnapshot.GetLineFromPosition(normalizedspancoll.First().Start.Position);
48 |
49 | //Get tab offset calculated by ElasticTabstopsSizeManager
50 | double[] tabOffsets = manager.GetTabOffsets(currentLine);
51 |
52 | return new ElasticTabstopsFormatter(textProperties, formattedLineSource, tabOffsets);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/ElasticTabstopsSizeManager.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using Microsoft.VisualStudio.Text.Editor;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | namespace AlwaysAligned
8 | {
9 | ///
10 | /// Represents class that calculates elastic tabstops for TextBuffer
11 | ///
12 | internal class ElasticTabstopsSizeManager
13 | {
14 | #region Inner Definitions
15 |
16 | private enum CalculateDirection
17 | {
18 | Down,
19 | Up,
20 | DownUp
21 | }
22 |
23 | ///
24 | /// Keep Elastic Tabstop data for a line
25 | ///
26 | private class ElasticTabstopsLine
27 | {
28 | ///
29 | /// Gets or sets Elastic Columns in the line
30 | ///
31 | internal ElasticTabstopsColumn[] ElasticColumns { get; set; }
32 |
33 | ///
34 | /// Returns elastic column information if exists, or null otherwise
35 | ///
36 | internal ElasticTabstopsColumn GetColumnOrDefault(int colNum)
37 | {
38 | if (ElasticColumns.Length > colNum)
39 | return ElasticColumns[colNum];
40 | return null;
41 | }
42 |
43 | ///
44 | /// Returns true if given column is the last column in the line,
45 | /// false otherwise
46 | ///
47 | internal bool IsLastColumnInLine(int colNum)
48 | {
49 | return colNum + 1 == ElasticColumns.Length;
50 | }
51 |
52 | ///
53 | /// Returns true if current line offsets changed regarding to given line
54 | /// false otherwise
55 | ///
56 | internal bool ChangedRegardingTo(ElasticTabstopsLine oldLine)
57 | {
58 | if (ElasticColumns == oldLine.ElasticColumns)
59 | return false;
60 |
61 | if (ElasticColumns.Length != oldLine.ElasticColumns.Length)
62 | return true;
63 |
64 | return ElasticColumns.Where((etc, i) => etc.ChangedRegardingTo(oldLine.ElasticColumns[i])).Any();
65 | }
66 | }
67 |
68 | ///
69 | /// Keeps data about ElasticColumn
70 | ///
71 | private class ElasticTabstopsColumn
72 | {
73 | ///
74 | /// Gets or sets start position of this column in the TextBuffer
75 | ///
76 | internal int Start { get; set; }
77 |
78 | ///
79 | /// Gets or sets text length in the line
80 | ///
81 | internal int ColumnTextLength { get; set; }
82 | ///
83 | /// Gets or sets Tab Offset of the line
84 | ///
85 | internal ColumnSizeInfo TabOffset { get; set; }
86 |
87 | ///
88 | /// Returns true if column contains changed, false otherwise
89 | ///
90 | internal bool ChangedRegardingTo(ElasticTabstopsColumn elasticTabstopsColumn)
91 | {
92 | if (this == elasticTabstopsColumn)
93 | return false;
94 |
95 | if (ColumnTextLength != elasticTabstopsColumn.ColumnTextLength)
96 | return true;
97 |
98 | return TabOffset.ChangedRegardingTo(elasticTabstopsColumn.TabOffset);
99 | }
100 | }
101 |
102 | ///
103 | /// Keep data about tab ofset
104 | ///
105 | private class ColumnSizeInfo
106 | {
107 | ///
108 | /// Real tab offset of the column
109 | ///
110 | internal double TabOffset { get; set; }
111 | ///
112 | /// Column real width
113 | ///
114 | internal double ColumnWidth { get; set; }
115 |
116 | ///
117 | /// returns true if column size contains change, false otherwise
118 | ///
119 | ///
120 | ///
121 | internal bool ChangedRegardingTo(ColumnSizeInfo columnSizeInfo)
122 | {
123 | return Math.Abs(TabOffset - columnSizeInfo.TabOffset) > 1.0E-10 || Math.Abs(ColumnWidth - columnSizeInfo.ColumnWidth) > 1.0E-10;
124 | }
125 | }
126 |
127 | #endregion
128 |
129 | private List _elasticTabstopsLinesCache;
130 | private readonly IWpfTextView _textView;
131 |
132 | private double _minCellWidth;
133 | private double _paddingWidth;
134 | private readonly TextMeasureService _textMeasureService;
135 | ///
136 | /// Initialize new instance of ElasticTabstopsSizeManager for IWpfTextView
137 | ///
138 | private ElasticTabstopsSizeManager(IWpfTextView textView)
139 | {
140 | _textView = textView;
141 | _textMeasureService = TextMeasureService.Create(_textView);
142 |
143 | InvalidateTabstops();
144 | }
145 |
146 | ///
147 | /// Get tab offsets for line
148 | ///
149 | internal double[] GetTabOffsets(ITextSnapshotLine line)
150 | {
151 | //VS is trying to Format textbuffer with old version,
152 | //not actual for AlwaysAligned
153 | if (line.LineNumber >= _elasticTabstopsLinesCache.Count)
154 | {
155 | return new double[0];
156 | }
157 | //returns tab offsets from cache
158 | var tabOffsets = _elasticTabstopsLinesCache[line.LineNumber].ElasticColumns.TakeWhile(etc => etc.TabOffset != null).Select(etc => etc.TabOffset.TabOffset).ToArray();
159 | return tabOffsets;
160 | }
161 |
162 | ///
163 | /// Invalidates tabs cache depending given changes, and return changed line numbers
164 | ///
165 | internal void InvalidateChanges()
166 | {
167 | InvalidateTabstops();
168 | }
169 |
170 | ///
171 | /// Invalidates tabs cache depending given changes, and return changed line numbers
172 | ///
173 | /// changed made
174 | internal void InvalidateChanges(INormalizedTextChangeCollection changes)
175 | {
176 | if (!changes.Any()) return;
177 |
178 | #region Old
179 |
180 | var firstChange = changes.First();
181 |
182 | var start = Math.Min(firstChange.OldSpan.Start, firstChange.NewSpan.Start);
183 | var end = Math.Max(firstChange.OldSpan.End, firstChange.NewSpan.End);
184 |
185 | foreach (var change in changes)
186 | {
187 | var lineNumber = _textView.TextSnapshot.GetLineNumberFromPosition(change.NewPosition);
188 |
189 | if (change.LineCountDelta > 0)
190 | {
191 | _elasticTabstopsLinesCache.InsertRange(lineNumber,
192 | Enumerable.Range(0, change.LineCountDelta).Select(
193 | c => new ElasticTabstopsLine()));
194 | }
195 | else if (change.LineCountDelta < 0)
196 | {
197 | _elasticTabstopsLinesCache.RemoveRange(lineNumber, -change.LineCountDelta);
198 | }
199 |
200 | start = Math.Min(start, Math.Min(change.OldSpan.Start, change.NewSpan.Start));
201 | end = Math.Max(end, Math.Max(change.OldSpan.End, change.NewSpan.End));
202 | }
203 |
204 | var topLine = _textView.TextSnapshot.GetLineFromPosition(start);
205 | var topLineNumber = topLine.LineNumber;
206 |
207 | if (changes.IncludesLineChanges && topLineNumber != 0)
208 | {
209 | topLineNumber--;
210 | topLine = _textView.TextSnapshot.GetLineFromLineNumber(topLineNumber);
211 | }
212 |
213 | while (topLineNumber > 0
214 | && topLine.Start != topLine.End)
215 | {
216 | topLineNumber--;
217 | topLine = _textView.TextSnapshot.GetLineFromLineNumber(topLineNumber);
218 | }
219 |
220 | end = Math.Min(end, Math.Max(0, _textView.TextSnapshot.Length - 1));
221 |
222 | var bottomLine = _textView.TextSnapshot.GetLineFromPosition(end);
223 | var bottomLineNumber = bottomLine.LineNumber;
224 |
225 | if (changes.IncludesLineChanges && bottomLineNumber < _textView.TextSnapshot.LineCount - 1)
226 | {
227 | bottomLineNumber++;
228 | bottomLine = _textView.TextSnapshot.GetLineFromLineNumber(bottomLineNumber);
229 | }
230 |
231 |
232 | while (bottomLineNumber < _textView.TextSnapshot.LineCount - 1
233 | && bottomLine.Start != bottomLine.End)
234 | {
235 | bottomLineNumber++;
236 | bottomLine = _textView.TextSnapshot.GetLineFromLineNumber(bottomLineNumber);
237 | }
238 |
239 | #endregion
240 |
241 | //InvalidateChanges();
242 | for (var i = topLineNumber; i <= bottomLineNumber; i++)
243 | {
244 | _elasticTabstopsLinesCache[i] = new ElasticTabstopsLine();
245 | }
246 |
247 | for (var i = topLineNumber; i <= bottomLineNumber; i++)
248 | {
249 | var line = _textView.TextSnapshot.GetLineFromLineNumber(i);
250 | CalculateTabOffsets(line, CalculateDirection.Down, false);
251 | }
252 |
253 | }
254 |
255 | ///
256 | /// Invalidates tabstops cache for textBuffer
257 | ///
258 | private void InvalidateTabstops()
259 | {
260 | _minCellWidth = AlwaysAlignedConfigurationService.Instance.GetConfiguration().MinimumCellWidth;
261 | _paddingWidth = AlwaysAlignedConfigurationService.Instance.GetConfiguration().CellPadding;
262 |
263 | ITextSnapshot textSnapshot = _textView.TextSnapshot;
264 | //Create empty ElasticTabstopsLine for each line
265 | _elasticTabstopsLinesCache = Enumerable.Range(0, textSnapshot.LineCount).Select(i => new ElasticTabstopsLine()).ToList();
266 |
267 | //Build _elasticTabstopsLinesCache by calculating from top to down
268 | foreach (var line in textSnapshot.Lines)
269 | {
270 | CalculateTabOffsets(line, CalculateDirection.Down, false);
271 | }
272 | }
273 |
274 | ///
275 | /// Calculate tab offsets for line in a given direction
276 | ///
277 | private void CalculateTabOffsets(ITextSnapshotLine line, CalculateDirection direction, bool forceInvalidate)
278 | {
279 | //Calculates tab offset for a given line for the given direction
280 | ElasticTabstopsLine elasticLine = GetElasticTabstopsLine(line, forceInvalidate);
281 |
282 | for (int colNumber = 0; colNumber < elasticLine.ElasticColumns.Length; colNumber++)
283 | {
284 | ElasticTabstopsColumn column = elasticLine.ElasticColumns[colNumber];
285 |
286 | //Tab offset is allready calculated during other line calculation
287 | if (!forceInvalidate && column.TabOffset != null)
288 | continue;
289 |
290 | //Assign the same ColumnTabOffset to all columns in the same block
291 | ColumnSizeInfo colTabOffset = new ColumnSizeInfo
292 | {
293 | TabOffset = CalculateInitialTabOffset(elasticLine, colNumber),
294 | ColumnWidth = CalculateInitialWidth(elasticLine, colNumber)
295 | };
296 |
297 | column.TabOffset = colTabOffset;
298 |
299 | switch (direction)
300 | {
301 | case CalculateDirection.Up:
302 | CalculateTabOffsetUp(line, colNumber, colTabOffset);
303 | break;
304 | case CalculateDirection.Down:
305 | CalculateTabOffsetDown(line, colNumber, colTabOffset);
306 | break;
307 | case CalculateDirection.DownUp:
308 | CalculateTabOffsetDown(line, colNumber, colTabOffset);
309 | CalculateTabOffsetUp(line, colNumber, colTabOffset);
310 | break;
311 | default:
312 | throw new ArgumentException("direction");
313 | }
314 | }
315 | }
316 |
317 | ///
318 | /// Calculate tab offsets by going down
319 | ///
320 | private void CalculateTabOffsetDown(ITextSnapshotLine curLine, int colNumber, ColumnSizeInfo colTabOffset)
321 | {
322 | int curLineNumber = curLine.LineNumber + 1;
323 |
324 | ITextSnapshot textSnapshot = _textView.TextSnapshot;
325 |
326 | ElasticTabstopsLine elasticLine = _elasticTabstopsLinesCache[curLine.LineNumber];
327 | bool isLastColumnInLine = elasticLine.IsLastColumnInLine(colNumber);
328 |
329 | while ( curLineNumber < textSnapshot.LineCount)
330 | {
331 | ITextSnapshotLine downLine = textSnapshot.GetLineFromLineNumber(curLineNumber);
332 | ElasticTabstopsLine downElasticLine = GetElasticTabstopsLine(downLine);
333 |
334 | if (downElasticLine.IsLastColumnInLine(colNumber) != isLastColumnInLine)
335 | {
336 | break;
337 | }
338 |
339 | ElasticTabstopsColumn downColumn = downElasticLine.GetColumnOrDefault(colNumber);
340 |
341 | if (downColumn == null)
342 | {
343 | break;
344 | }
345 |
346 | downColumn.TabOffset = colTabOffset;
347 | ShrinkTabOffset(downElasticLine, colNumber);
348 | curLineNumber++;
349 | }
350 | }
351 |
352 | ///
353 | /// Calculate tab offsets by going up
354 | ///
355 | private void CalculateTabOffsetUp(ITextSnapshotLine curLine, int colNumber, ColumnSizeInfo colTabOffset)
356 | {
357 | int curLineNumber = curLine.LineNumber - 1;
358 |
359 | ITextSnapshot textSnapshot = _textView.TextSnapshot;
360 |
361 | ElasticTabstopsLine elasticLine = _elasticTabstopsLinesCache[curLine.LineNumber];
362 | bool isLastColumnInLine = elasticLine.IsLastColumnInLine(colNumber);
363 |
364 | while (curLineNumber >= 0)
365 | {
366 | ITextSnapshotLine upLine = textSnapshot.GetLineFromLineNumber(curLineNumber);
367 | ElasticTabstopsLine upElasticLine = GetElasticTabstopsLine(upLine);
368 |
369 | if (upElasticLine.IsLastColumnInLine(colNumber) != isLastColumnInLine)
370 | {
371 | break;
372 | }
373 |
374 | ElasticTabstopsColumn upColumn = upElasticLine.GetColumnOrDefault(colNumber);
375 |
376 | if (upColumn == null)
377 | {
378 | break;
379 | }
380 |
381 | upColumn.TabOffset = colTabOffset;
382 | ShrinkTabOffset(upElasticLine, colNumber);
383 | curLineNumber--;
384 | }
385 | }
386 |
387 | ///
388 | /// Fix tab offset for a column if needed
389 | ///
390 | private void ShrinkTabOffset(ElasticTabstopsLine tabLine, int colNumber)
391 | {
392 | ElasticTabstopsColumn colTabOffset = tabLine.ElasticColumns[colNumber];
393 |
394 | double width = CalculateInitialWidth(tabLine, colNumber);
395 |
396 | if (colTabOffset.TabOffset.ColumnWidth < width)
397 | {
398 | colTabOffset.TabOffset.ColumnWidth = width;
399 | colTabOffset.TabOffset.TabOffset = CalculateInitialTabOffset(tabLine, colNumber);
400 | }
401 | }
402 |
403 | ///
404 | /// Calculates column width for a specific column in specific line
405 | ///
406 | private double CalculateInitialWidth(ElasticTabstopsLine elasticLine, int colNumber)
407 | {
408 | ITextSnapshot textSnapshot = _textView.TextSnapshot;
409 | ElasticTabstopsColumn column = elasticLine.ElasticColumns[colNumber];
410 | Span span = new Span(column.Start, column.ColumnTextLength);
411 | if (span.Start > textSnapshot.Length || span.End > textSnapshot.Length)
412 | {
413 | return 0;
414 | }
415 |
416 | SnapshotSpan columnSpan = new SnapshotSpan(textSnapshot, span);
417 |
418 | double columnWidth = _textMeasureService.GetWidth(columnSpan);
419 |
420 | return Math.Max(columnWidth, _minCellWidth);
421 | }
422 |
423 | ///
424 | /// Calulates tab offset depending column widthes before current column
425 | /// This method assume that column widthes before current column are calculated allready
426 | ///
427 | private double CalculateInitialTabOffset(ElasticTabstopsLine tabLine, int colNumber)
428 | {
429 | return tabLine.ElasticColumns.Take(colNumber).Sum(ct => ct.TabOffset.ColumnWidth) + colNumber * _paddingWidth;
430 | }
431 |
432 | ///
433 | /// Returns ElasticTabstopsLine with initialized ElasticColumns
434 | ///
435 | private ElasticTabstopsLine GetElasticTabstopsLine(ITextSnapshotLine line, bool forceInvalidateColumns = false)
436 | {
437 | ElasticTabstopsLine elasticTabstopsLine = _elasticTabstopsLinesCache[line.LineNumber];
438 | if (elasticTabstopsLine.ElasticColumns == null || forceInvalidateColumns)
439 | {
440 | string lineText = line.GetText();
441 | string[] tabSplits = lineText.Split('\t');
442 | elasticTabstopsLine.ElasticColumns = new ElasticTabstopsColumn[tabSplits.Length];
443 | int curPosInLine = line.Start.Position;
444 | for (int i = 0; i < tabSplits.Length; i++)
445 | {
446 | string ts = tabSplits[i];
447 | ElasticTabstopsColumn column = new ElasticTabstopsColumn {ColumnTextLength = ts.Length, Start = curPosInLine};
448 | //skeep tab
449 | curPosInLine += ts.Length + 1;
450 | elasticTabstopsLine.ElasticColumns[i] = column;
451 | }
452 | }
453 | return elasticTabstopsLine;
454 | }
455 |
456 | internal static ElasticTabstopsSizeManager Get(IWpfTextView textView)
457 | {
458 | if (textView == null)
459 | return null;
460 | ElasticTabstopsSizeManager outManager;
461 | textView.Properties.TryGetProperty(typeof(ElasticTabstopsSizeManager), out outManager);
462 | return outManager;
463 | }
464 |
465 | internal static ElasticTabstopsSizeManager Create(IWpfTextView textView)
466 | {
467 | if (textView == null)
468 | return null;
469 | return textView.Properties.GetOrCreateSingletonProperty(() => new ElasticTabstopsSizeManager(textView));
470 | }
471 | }
472 | }
473 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/ExternalSettingsTracker.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.OLE.Interop;
2 | using Microsoft.VisualStudio.Shell;
3 | using Microsoft.VisualStudio.TextManager.Interop;
4 | using System;
5 | using System.Linq;
6 |
7 | namespace AlwaysAligned
8 | {
9 | internal class ExternalSettingsTracker
10 | {
11 | public class TextManagerEvents : IVsTextManagerEvents
12 | {
13 | public void OnRegisterMarkerType(int iMarkerType)
14 | {
15 | }
16 |
17 | public void OnRegisterView(IVsTextView pView)
18 | {
19 | }
20 |
21 | public void OnUnregisterView(IVsTextView pView)
22 | {
23 | }
24 |
25 | private FONTCOLORPREFERENCES[] _pColorPrefs;
26 | public void OnUserPreferencesChanged(VIEWPREFERENCES[] pViewPrefs, FRAMEPREFERENCES[] pFramePrefs, LANGPREFERENCES[] pLangPrefs, FONTCOLORPREFERENCES[] pColorPrefs)
27 | {
28 | if (pColorPrefs == null || !pColorPrefs.Any())
29 | {
30 | _pColorPrefs = null;
31 | return;
32 | }
33 | if (_pColorPrefs != null
34 | && _pColorPrefs.Count() == pColorPrefs.Count()
35 | && _pColorPrefs.Zip(pColorPrefs, (fr1, fr2) => new Tuple(fr1, fr2)).All(
36 | t => CheckFontColorReferencesAreEqual(t.Item1, t.Item2)))
37 | {
38 | return;
39 | }
40 |
41 | _pColorPrefs = pColorPrefs;
42 |
43 | AlwaysAlignedConfigurationService.Instance.OnExternalConfigurationChanged();
44 | }
45 |
46 | private bool CheckFontColorReferencesAreEqual(FONTCOLORPREFERENCES fr1, FONTCOLORPREFERENCES fr2)
47 | {
48 | return fr1.hBoldViewFont == fr2.hBoldViewFont
49 | && fr1.hRegularViewFont == fr2.hRegularViewFont
50 | && fr1.pguidColorCategory == fr2.pguidColorCategory
51 | && fr1.pguidFontCategory == fr2.pguidFontCategory;
52 | }
53 | }
54 |
55 | public static void Start()
56 | {
57 | var textManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsTextManager)) as IVsTextManager;
58 |
59 | var container = (IConnectionPointContainer)textManager;
60 | IConnectionPoint textManagerEventsConnection = null;
61 | var eventGuid = typeof(IVsTextManagerEvents).GUID;
62 | if (container != null)
63 | {
64 | container.FindConnectionPoint(ref eventGuid, out textManagerEventsConnection);
65 | }
66 | var textManagerEvents = new TextManagerEvents();
67 | uint textManagerCookie;
68 | if (textManagerEventsConnection != null)
69 | {
70 | textManagerEventsConnection.Advise(textManagerEvents, out textManagerCookie);
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project. Project-level
3 | // suppressions either have no target or are given a specific target
4 | // and scoped to a namespace, type, member, etc.
5 | //
6 | // To add a suppression to this file, right-click the message in the
7 | // Error List, point to "Suppress Message(s)", and click "In Project
8 | // Suppression File". You do not need to add suppressions to this
9 | // file manually.
10 |
11 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")]
12 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Guids.cs:
--------------------------------------------------------------------------------
1 | // Guids.cs
2 | // MUST match guids.h
3 | using System;
4 |
5 | namespace AlwaysAligned
6 | {
7 | static class GuidList
8 | {
9 | public const string guidAlwaysAlignedPkgString = "c982f983-3dce-4114-91c0-e534dd039dda";
10 | public const string guidAlwaysAlignedCmdSetString = "234580c4-8a2c-4ae1-8e4f-5bc708b188fe";
11 |
12 | public static readonly Guid guidAlwaysAlignedCmdSet = new Guid(guidAlwaysAlignedCmdSetString);
13 | };
14 | }
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Logo.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Logo.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Threading;
5 |
6 | namespace AlwaysAligned
7 | {
8 | ///
9 | /// Interaction logic for Logo.xaml
10 | ///
11 | public partial class Logo : UserControl
12 | {
13 | DispatcherTimer dispatcherTimer = new DispatcherTimer();
14 | private int preAnimDelay = 6;
15 | private readonly int numFrames = 14;
16 | private int currentFrameNum = 0;
17 | private int logoHeight;
18 |
19 | public Logo()
20 | {
21 | InitializeComponent();
22 |
23 | logoHeight = (int)spritesheet.Source.Height / numFrames;
24 |
25 | dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
26 | dispatcherTimer.Interval = TimeSpan.FromMilliseconds(250);
27 | dispatcherTimer.Start();
28 |
29 | this.DataContext = this; // so we can bind LogoHeight
30 | }
31 |
32 | public double LogoHeight
33 | {
34 | get { return logoHeight; }
35 | }
36 |
37 | private void dispatcherTimer_Tick(object sender, EventArgs e)
38 | {
39 | if (preAnimDelay > 0)
40 | {
41 | preAnimDelay--;
42 | return;
43 | }
44 | currentFrameNum++;
45 | if (currentFrameNum >= numFrames - 1)
46 | {
47 | dispatcherTimer.Stop();
48 | }
49 | scrollViewer.ScrollToVerticalOffset(logoHeight * currentFrameNum);
50 | }
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/MiscGui.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 | using Microsoft.VisualStudio.Shell;
3 | using Microsoft.VisualStudio.Shell.Interop;
4 | using System;
5 |
6 | namespace AlwaysAligned
7 | {
8 | class MiscGui
9 | {
10 | internal abstract class EnvDTEConstants
11 | {
12 | public const string vsWindowKindOutput = "{34E76E81-EE4A-11D0-AE2E-00A0C90FFFC3}";
13 | }
14 |
15 | public static void WriteOutput(string outputText)
16 | {
17 | IVsOutputWindow outputWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;
18 |
19 | Guid guidGeneral = Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.GeneralPane_guid;
20 | IVsOutputWindowPane pane;
21 | int hr = outputWindow.CreatePane(guidGeneral, "General", 1, 0);
22 | hr = outputWindow.GetPane(guidGeneral, out pane);
23 | pane.Activate();
24 | pane.OutputString(outputText);
25 | pane.Activate();
26 |
27 | DTE dte = Package.GetGlobalService(typeof(SDTE)) as DTE;
28 | Window win = dte.Windows.Item(EnvDTEConstants.vsWindowKindOutput);
29 | win.Visible = true;
30 | }
31 |
32 | public static void ShowModal(string title, string caption, OLEMSGBUTTON msgbtn, OLEMSGDEFBUTTON msgdefbtn, OLEMSGICON msgicon)
33 | {
34 | ServiceProvider serviceProvider = new ServiceProvider(((DTE)Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE))) as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
35 | IVsUIShell uiShell = serviceProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
36 |
37 | var id = Guid.Empty;
38 | int result;
39 | uiShell.ShowMessageBox(
40 | 0, ref id,
41 | title, caption,
42 | string.Empty, 0,
43 | msgbtn, msgdefbtn, msgicon,
44 | 0, out result);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/PkgCmdID.cs:
--------------------------------------------------------------------------------
1 | // PkgCmdID.cs
2 | // MUST match PkgCmdID.h
3 |
4 | namespace AlwaysAligned
5 | {
6 | static class PkgCmdIDList
7 | {
8 | public const uint AlwaysAlignedMenu = 0x100;
9 | public const uint cmdidConvertToSpaces = 0x300;
10 | public const uint cmdidConvertToElasticTabstops = 0x301;
11 | public const uint cmdidSettings = 0x302;
12 | public const uint cmdidAbout = 0x303;
13 |
14 | };
15 | }
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Resources;
4 | using System.Runtime.CompilerServices;
5 | using System.Runtime.InteropServices;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("AlwaysAlignedVS")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("Always Aligned VS (elastic tabstops for Visual Studio)")]
15 | [assembly: AssemblyCopyright("Copyright © 2010-2017 Nick Gravgaard")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 | [assembly: ComVisible(false)]
19 | [assembly: CLSCompliant(false)]
20 | [assembly: NeutralResourcesLanguage("en-US")]
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 Revision and Build Numbers
30 | // by using the '*' as shown below:
31 |
32 | [assembly: AssemblyVersion("2017.0.0.*")]
33 | [assembly: AssemblyInformationalVersion("2017.0.0")]
34 | // [assembly: AssemblyFileVersion("0.0.0.0")]
35 |
36 | [assembly: InternalsVisibleTo("MyProject.Domain.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c5270c496849ad8424ebb61da8fc757eb1f7bca4e9efaa9a7b742dad21093784d4218f7a786e7ee4985266904ec76b992bda7c4ead130af05390c47c0504c93278e4da28bdf5fac8e35144f787479793085d5d2df821cfb915a44be90534df569531dc3d3fdc34d2c336d50be53211473af866ea701e475bd3c194a0a8b2b9a3")]
37 |
38 | //[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ElasticTabstopsConverterTest")]
39 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace AlwaysAligned.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 |
26 | [global::System.Configuration.UserScopedSettingAttribute()]
27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
28 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
29 | public bool Enabled {
30 | get {
31 | return ((bool)(this["Enabled"]));
32 | }
33 | set {
34 | this["Enabled"] = value;
35 | }
36 | }
37 |
38 | [global::System.Configuration.UserScopedSettingAttribute()]
39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
40 | [global::System.Configuration.DefaultSettingValueAttribute("False")]
41 | public bool ConvertOnLoadSave {
42 | get {
43 | return ((bool)(this["ConvertOnLoadSave"]));
44 | }
45 | set {
46 | this["ConvertOnLoadSave"] = value;
47 | }
48 | }
49 |
50 | [global::System.Configuration.UserScopedSettingAttribute()]
51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
52 | [global::System.Configuration.DefaultSettingValueAttribute("16")]
53 | public int MinimumCellWidth {
54 | get {
55 | return ((int)(this["MinimumCellWidth"]));
56 | }
57 | set {
58 | this["MinimumCellWidth"] = value;
59 | }
60 | }
61 |
62 | [global::System.Configuration.UserScopedSettingAttribute()]
63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
64 | [global::System.Configuration.DefaultSettingValueAttribute("16")]
65 | public int CellPadding {
66 | get {
67 | return ((int)(this["CellPadding"]));
68 | }
69 | set {
70 | this["CellPadding"] = value;
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | True
7 |
8 |
9 | False
10 |
11 |
12 | 16
13 |
14 |
15 | 16
16 |
17 |
18 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace AlwaysAligned {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AlwaysAligned.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to -----BEGIN PUBLIC KEY-----
65 | ///MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTR3rMSVbkuIIWsU1O/M
66 | ///MVZahfkwA+Vz+UYshxFfrfc4NOHGIElGWirXEtIp4OtufPgmMGpkBMqQwQQVtb8p
67 | ///fl0EVv+wq78RQm6HIrG0qUZJrPcx/Y7M5EQy5+qUfTVj0t1MB9Zi/2SmjaSqp4Uv
68 | ///Ka3syOcHvH92ZNm78PuL/h7hC4uTyJQR9+CdY1/LnfhloWwuK2nvFEoLWxbPEzou
69 | ///7UjRiqxfAEQlGs4fAKYtQGid4QBFgQ4LT8siCEbgE2lO8pFAtJvjJtaE6/SpJD5t
70 | ///R1IqIQgsqdg2Y+mSUymdZlN2OoWU5LRex1cKcecq9FcBFoILBmHOEBOewSPJ8NG8
71 | ///zQIDAQAB
72 | ///-----END PUBLIC KEY-----.
73 | ///
74 | internal static string pubkey_pem {
75 | get {
76 | return ResourceManager.GetString("pubkey_pem", resourceCulture);
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Resources.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 | -----BEGIN PUBLIC KEY-----
122 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqTR3rMSVbkuIIWsU1O/M
123 | MVZahfkwA+Vz+UYshxFfrfc4NOHGIElGWirXEtIp4OtufPgmMGpkBMqQwQQVtb8p
124 | fl0EVv+wq78RQm6HIrG0qUZJrPcx/Y7M5EQy5+qUfTVj0t1MB9Zi/2SmjaSqp4Uv
125 | Ka3syOcHvH92ZNm78PuL/h7hC4uTyJQR9+CdY1/LnfhloWwuK2nvFEoLWxbPEzou
126 | 7UjRiqxfAEQlGs4fAKYtQGid4QBFgQ4LT8siCEbgE2lO8pFAtJvjJtaE6/SpJD5t
127 | R1IqIQgsqdg2Y+mSUymdZlN2OoWU5LRex1cKcecq9FcBFoILBmHOEBOewSPJ8NG8
128 | zQIDAQAB
129 | -----END PUBLIC KEY-----
130 |
131 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Resources/Images_32bit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nick-gravgaard/AlwaysAlignedVS/b6c7fb2e938e87c2a14f1b2c682fac3222448168/AlwaysAlignedVS/Resources/Images_32bit.png
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Resources/Package.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nick-gravgaard/AlwaysAlignedVS/b6c7fb2e938e87c2a14f1b2c682fac3222448168/AlwaysAlignedVS/Resources/Package.ico
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nick-gravgaard/AlwaysAlignedVS/b6c7fb2e938e87c2a14f1b2c682fac3222448168/AlwaysAlignedVS/Resources/icon.png
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Resources/logoframes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nick-gravgaard/AlwaysAlignedVS/b6c7fb2e938e87c2a14f1b2c682fac3222448168/AlwaysAlignedVS/Resources/logoframes.png
--------------------------------------------------------------------------------
/AlwaysAlignedVS/Resources/previewimage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nick-gravgaard/AlwaysAlignedVS/b6c7fb2e938e87c2a14f1b2c682fac3222448168/AlwaysAlignedVS/Resources/previewimage.png
--------------------------------------------------------------------------------
/AlwaysAlignedVS/SettingsDialog.xaml:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/SettingsDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Input;
4 |
5 | namespace AlwaysAligned
6 | {
7 | ///
8 | /// Interaction logic for SettingsDialog.xaml
9 | ///
10 | public partial class SettingsDialog
11 | {
12 | private bool postInit = false;
13 | private AlwaysAlignedConfiguration config;
14 |
15 | public SettingsDialog()
16 | {
17 | config = (AlwaysAlignedConfiguration)AlwaysAlignedConfigurationService.Instance.GetConfiguration().Clone();
18 | InitializeComponent();
19 | postInit = true;
20 | }
21 |
22 | public AlwaysAlignedConfiguration Configuration
23 | {
24 | get
25 | {
26 | return config;
27 | }
28 | }
29 |
30 | private void widgetChanged(object sender, EventArgs e)
31 | {
32 | if (postInit)
33 | {
34 | saveButton.IsEnabled = true;
35 | }
36 | }
37 |
38 | private void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
39 | {
40 | int result;
41 | if (!int.TryParse(e.Text, out result))
42 | {
43 | e.Handled = true;
44 | }
45 | }
46 |
47 | private void saveButton_Click(object sender, RoutedEventArgs e)
48 | {
49 | AlwaysAlignedConfigurationService.Instance.Save(Configuration);
50 | this.Close();
51 | }
52 |
53 | private void dontSaveButton_Click(object sender, RoutedEventArgs e)
54 | {
55 | this.Close();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/TextBufferToViewMapService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using Microsoft.VisualStudio.Text.Editor;
3 | using Microsoft.VisualStudio.Text.Formatting;
4 | using Microsoft.VisualStudio.Utilities;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.ComponentModel.Composition;
8 | using System.Linq;
9 | using System.Windows;
10 |
11 | namespace AlwaysAligned
12 | {
13 | public class TextViewLoadedEventArgs : EventArgs
14 | {
15 | public TextViewLoadedEventArgs (IWpfTextView textView)
16 | {
17 | TextView = textView;
18 | }
19 | public IWpfTextView TextView { get; private set; }
20 | }
21 | public interface ITextBufferToViewMapService
22 | {
23 | IWpfTextView[] GetAllViews(ITextBuffer textBuffer);
24 | IWpfTextView GetViewByFormattedLineSource(IFormattedLineSource formattedLineSource);
25 | }
26 |
27 | [Export(typeof(ITextBufferToViewMapService))]
28 | [TextViewRole(PredefinedTextViewRoles.Document)]
29 | [ContentType("text")]
30 | [Name("FakeMarginProvider")]
31 | [Export(typeof(IWpfTextViewMarginProvider))]
32 | [Order(Before = PredefinedMarginNames.VerticalScrollBarContainer)]
33 | [MarginContainer(PredefinedMarginNames.RightControl)]
34 | public class TextBufferToViewMapService :
35 | ITextBufferToViewMapService, IWpfTextViewMarginProvider
36 | {
37 | ///
38 | /// Fake margin that keeps _wpfTextViewList list up to date
39 | ///
40 | private class FakeWpfTextViewMargin : FrameworkElement, IWpfTextViewMargin
41 | {
42 | private readonly TextBufferToViewMapService _textBufferToViewMapService;
43 | private readonly IWpfTextView _wpfTextView;
44 |
45 | public FakeWpfTextViewMargin(TextBufferToViewMapService textBufferToViewMapService, IWpfTextView iWpfTextView)
46 | {
47 | _textBufferToViewMapService = textBufferToViewMapService;
48 | _wpfTextView = iWpfTextView;
49 | _textBufferToViewMapService._wpfTextViewList.Add(_wpfTextView);
50 | }
51 | public FrameworkElement VisualElement
52 | {
53 | get { return this; }
54 | }
55 |
56 | public bool Enabled
57 | {
58 | get { return false; }
59 | }
60 |
61 | public ITextViewMargin GetTextViewMargin(string marginName)
62 | {
63 | return this;
64 | }
65 |
66 | public double MarginSize
67 | {
68 | get { return 0; }
69 | }
70 |
71 | public void Dispose()
72 | {
73 | _textBufferToViewMapService._wpfTextViewList.Remove(_wpfTextView);
74 | }
75 | }
76 |
77 | private readonly IList _wpfTextViewList = new List();
78 |
79 | public IWpfTextViewMargin CreateMargin(IWpfTextViewHost wpfTextViewHost, IWpfTextViewMargin marginContainer)
80 | {
81 | ElasticTabstopsSizeManager.Create(wpfTextViewHost.TextView);
82 | return new FakeWpfTextViewMargin(this, wpfTextViewHost.TextView);
83 | }
84 |
85 | ///
86 | /// Get all views for a given ITextBuffer
87 | ///
88 | public IWpfTextView[] GetAllViews(ITextBuffer textBuffer)
89 | {
90 | return _wpfTextViewList.Where(view => view.TextBuffer == textBuffer).ToArray();
91 | }
92 |
93 | ///
94 | /// Return view that contains given IFormattedLineSource
95 | ///
96 | public IWpfTextView GetViewByFormattedLineSource(IFormattedLineSource formattedLineSource)
97 | {
98 | return _wpfTextViewList.FirstOrDefault(view => view.FormattedLineSource == formattedLineSource);
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/TextMeasureService.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using Microsoft.VisualStudio.Text.Editor;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.Windows;
6 | using System.Windows.Media;
7 | using System.Windows.Media.TextFormatting;
8 |
9 | namespace AlwaysAligned
10 | {
11 | public class TextMeasureService
12 | {
13 | private readonly Dictionary _symbolWidthCache = new Dictionary();
14 | private IWpfTextView _textView;
15 | private TextRunProperties _defaultTextRunProperties;
16 |
17 | private TextMeasureService()
18 | {
19 | }
20 |
21 | public static TextMeasureService Create(IWpfTextView textView)
22 | {
23 | TextMeasureService tms = GetOrCreateProperty(textView);
24 |
25 | //font was changed, refresh cache
26 | if (tms._defaultTextRunProperties != textView.FormattedLineSource.DefaultTextProperties)
27 | {
28 | textView.Properties.RemoveProperty(typeof(TextMeasureService));
29 | tms = GetOrCreateProperty(textView);
30 | }
31 |
32 | return tms;
33 | }
34 |
35 | private static TextMeasureService GetOrCreateProperty(IWpfTextView textView)
36 | {
37 | TextMeasureService tms = textView.Properties.GetOrCreateSingletonProperty(() => new TextMeasureService
38 | {
39 | _textView = textView,
40 | _defaultTextRunProperties = textView.FormattedLineSource.DefaultTextProperties
41 | });
42 | return tms;
43 | }
44 |
45 | public double GetWidth(SnapshotSpan span)
46 | {
47 | double widthSum = 0;
48 | for (int i = 0; i < span.Length; i++)
49 | {
50 | var curPoint = span.Start + i;
51 | widthSum += GetWidthFromCache(curPoint);
52 | }
53 | return widthSum;
54 | }
55 |
56 | public double GetCharWidth(char ch) // TODO: can this be made private?
57 | {
58 | if (_symbolWidthCache.ContainsKey(ch))
59 | {
60 | return _symbolWidthCache[ch];
61 | }
62 | //emSize = emSize > 0 ? emSize : _textView.FormattedLineSource.DefaultTextProperties.FontRenderingEmSize;
63 | var formattedText = new FormattedText(
64 | ch.ToString(CultureInfo.InvariantCulture),
65 | _textView.FormattedLineSource.DefaultTextProperties.CultureInfo,
66 | FlowDirection.LeftToRight,
67 | _textView.FormattedLineSource.DefaultTextProperties.Typeface,
68 | _textView.FormattedLineSource.DefaultTextProperties.FontRenderingEmSize,
69 | _textView.FormattedLineSource.DefaultTextProperties.ForegroundBrush,
70 | _textView.FormattedLineSource.DefaultTextProperties.NumberSubstitution,
71 | TextFormattingMode.Display);
72 |
73 | _symbolWidthCache[ch] = formattedText.WidthIncludingTrailingWhitespace;
74 |
75 | return formattedText.WidthIncludingTrailingWhitespace;
76 | }
77 |
78 | private double GetWidthFromCache(SnapshotPoint point)
79 | {
80 | var ch = point.GetChar();
81 | return GetCharWidth(ch);
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/UpdateTabCreationListener.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using Microsoft.VisualStudio.Text.Editor;
3 | using Microsoft.VisualStudio.Utilities;
4 | using System;
5 | using System.ComponentModel.Composition;
6 |
7 | namespace AlwaysAligned
8 | {
9 | [Export(typeof(IWpfTextViewCreationListener))]
10 | [ContentType("text")]
11 | [TextViewRole(PredefinedTextViewRoles.Document)]
12 | public class UpdateTabCreationListener : IWpfTextViewCreationListener
13 | {
14 | private class UpdateTabListener
15 | {
16 | readonly IWpfTextView _textView;
17 | readonly ITextDataModel _dataModel;
18 |
19 | private INormalizedTextChangeCollection _lastChange;
20 |
21 | public UpdateTabListener(IWpfTextView textView)
22 | {
23 | _textView = textView;
24 | _dataModel = _textView.TextViewModel.DataModel;
25 |
26 | _textView.Closed += OnClosed;
27 | _dataModel.ContentTypeChanged += ChangedHighPriority;
28 |
29 | _textView.TextBuffer.PostChanged += OnBufferPostChanged;
30 | _textView.TextBuffer.Changed += OnBufferChanged;
31 |
32 | _textView.Caret.PositionChanged += CaretPositionChanged;
33 |
34 | AlwaysAlignedConfigurationService.Instance.ConfigurationChanged += InstanceConfigurationSaved;
35 |
36 | }
37 |
38 | private void OnBufferChanged(object sender, TextContentChangedEventArgs e)
39 | {
40 | _lastChange = e.Changes;
41 | }
42 |
43 | void CaretPositionChanged(object sender, CaretPositionChangedEventArgs args)
44 | {
45 | if (!AlwaysAlignedConfigurationService.Instance.Config.Enabled) return;
46 |
47 | if (_lastChange == null) return;
48 |
49 | if (!_textView.Caret.InVirtualSpace)
50 | {
51 | _lastChange = null;
52 | return;
53 | }
54 |
55 | var rtCount = Math.Max(_textView.Caret.Position.VirtualSpaces / _textView.FormattedLineSource.TabSize, 0);
56 | var rtEdit = _textView.TextBuffer.CreateEdit();
57 | rtEdit.Insert(_textView.Caret.ContainingTextViewLine.Start.Position, new string('\t', rtCount));
58 | rtEdit.Apply();
59 | var line = _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(_textView.Caret.Position.BufferPosition);
60 | _textView.Caret.MoveTo(line.End);
61 |
62 | }
63 |
64 | void InstanceConfigurationSaved(object sender, ConfigurationSavedEventArgs e)
65 | {
66 | if (!e.HasChanges) return;
67 |
68 | var tabStopsManager = ElasticTabstopsSizeManager.Get(_textView);
69 | tabStopsManager.InvalidateChanges();
70 |
71 | RefreshView();
72 | }
73 |
74 | void ChangedHighPriority(object sender, TextDataModelContentTypeChangedEventArgs e)
75 | {
76 | if (!e.AfterContentType.IsOfType("code"))
77 | {
78 | // we are no longer a "code" content type so unhook all the events so we can be garbage collected
79 | OnClosed(null, null);
80 | }
81 | }
82 |
83 | private void OnBufferPostChanged(object sender, EventArgs e)
84 | {
85 | if (!AlwaysAlignedConfigurationService.Instance.Config.Enabled) return;
86 |
87 | var tabStopsManager = ElasticTabstopsSizeManager.Get(_textView);
88 | if (tabStopsManager != null)
89 | {
90 | tabStopsManager.InvalidateChanges(_lastChange);
91 | RefreshView();
92 | }
93 | }
94 |
95 | private void RefreshView()
96 | {
97 | int oldTabSize = _textView.Options.GetOptionValue(DefaultOptions.TabSizeOptionId);
98 | _textView.Options.SetOptionValue(DefaultOptions.TabSizeOptionId, oldTabSize + 1);
99 | _textView.Options.SetOptionValue(DefaultOptions.TabSizeOptionId, oldTabSize);
100 | }
101 |
102 | private void OnClosed(object sender, EventArgs e)
103 | {
104 | _textView.Closed -= OnClosed;
105 | _textView.TextBuffer.PostChanged -= OnBufferPostChanged;
106 | _dataModel.ContentTypeChanged -= ChangedHighPriority;
107 | AlwaysAlignedConfigurationService.Instance.ConfigurationChanged -= InstanceConfigurationSaved;
108 |
109 | }
110 | }
111 |
112 | public void TextViewCreated(IWpfTextView textView)
113 | {
114 | new UpdateTabListener(textView);
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/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 | AlwaysAlignedVS
122 |
123 |
124 | An extension which implements elastic tabstops in Visual Studio.
125 |
126 |
127 |
128 | Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
129 |
130 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/VsTextViewCreationListener.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio;
2 | using Microsoft.VisualStudio.Editor;
3 | using Microsoft.VisualStudio.OLE.Interop;
4 | using Microsoft.VisualStudio.Text.Editor;
5 | using Microsoft.VisualStudio.TextManager.Interop;
6 | using Microsoft.VisualStudio.Utilities;
7 | using System.ComponentModel.Composition;
8 | using System.Diagnostics;
9 |
10 | namespace AlwaysAligned
11 | {
12 | [Export(typeof(IVsTextViewCreationListener))]
13 | [ContentType("code")]
14 | [TextViewRole(PredefinedTextViewRoles.Editable)]
15 | class VsTextViewCreationListener : IVsTextViewCreationListener
16 | {
17 | [Import]
18 | IVsEditorAdaptersFactoryService AdaptersFactory = null;
19 |
20 | public void VsTextViewCreated(IVsTextView textViewAdapter)
21 | {
22 | var wpfTextView = AdaptersFactory.GetWpfTextView(textViewAdapter);
23 | if (wpfTextView == null)
24 | {
25 | Debug.Fail("Unable to get IWpfTextView from text view adapter");
26 | return;
27 | }
28 |
29 | CommandFilter filter = new CommandFilter(wpfTextView);
30 |
31 | IOleCommandTarget next;
32 | if (ErrorHandler.Succeeded(textViewAdapter.AddCommandFilter(filter, out next)))
33 | {
34 | filter.Next = next;
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/WeakEvent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 |
5 | namespace AlwaysAligned
6 | {
7 | public class WeakDelegate : IEquatable
8 | {
9 | private readonly WeakReference _targetReference;
10 | private readonly MethodInfo _method;
11 |
12 | public WeakDelegate(Delegate realDelegate)
13 | {
14 | _targetReference = realDelegate.Target != null ? new WeakReference(realDelegate.Target) : null;
15 | _method = realDelegate.Method;
16 | }
17 |
18 | public TDelegate GetDelegate()
19 | {
20 | return (TDelegate)(object)GetDelegateInternal();
21 | }
22 |
23 | private Delegate GetDelegateInternal()
24 | {
25 | if (_targetReference != null)
26 | {
27 | return Delegate.CreateDelegate(typeof(TDelegate), _targetReference.Target, _method);
28 | }
29 | return Delegate.CreateDelegate(typeof(TDelegate), _method);
30 | }
31 |
32 | public bool IsAlive
33 | {
34 | get { return _targetReference == null || _targetReference.IsAlive; }
35 | }
36 |
37 | #region IEquatable Members
38 |
39 | public bool Equals(TDelegate other)
40 | {
41 | Delegate d = (Delegate)(object)other;
42 | return d != null
43 | && d.Target == _targetReference.Target
44 | && d.Method.Equals(_method);
45 | }
46 |
47 | #endregion
48 |
49 | internal void Invoke(params object[] args)
50 | {
51 | Delegate handler = GetDelegateInternal();
52 | handler.DynamicInvoke(args);
53 | }
54 | }
55 |
56 | public class WeakEvent
57 | {
58 | private readonly List> _handlers;
59 |
60 | public WeakEvent()
61 | {
62 | _handlers = new List>();
63 | }
64 |
65 | public virtual void AddHandler(TEventHandler handler)
66 | {
67 | Delegate d = (Delegate)(object)handler;
68 | _handlers.Add(new WeakDelegate(d));
69 | }
70 |
71 | public virtual void RemoveHandler(TEventHandler handler)
72 | {
73 | // also remove "dead" (garbage collected) handlers
74 | _handlers.RemoveAll(wd => !wd.IsAlive || wd.Equals(handler));
75 | }
76 |
77 | public virtual void Raise(object sender, EventArgs e)
78 | {
79 | var handlers = _handlers.ToArray();
80 | foreach (var weakDelegate in handlers)
81 | {
82 | if (weakDelegate.IsAlive)
83 | {
84 | weakDelegate.Invoke(sender, e);
85 | }
86 | else
87 | {
88 | _handlers.Remove(weakDelegate);
89 | }
90 | }
91 | }
92 |
93 | protected List> Handlers
94 | {
95 | get { return _handlers; }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/WpfTextConnectionListener.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using Microsoft.VisualStudio.Text.Editor;
3 | using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
4 | using Microsoft.VisualStudio.Utilities;
5 | using System;
6 | using System.Collections.ObjectModel;
7 | using System.ComponentModel.Composition;
8 | using System.IO;
9 |
10 | namespace AlwaysAligned
11 | {
12 | [Export(typeof(IWpfTextViewConnectionListener))]
13 | [ContentType("text")]
14 | [TextViewRole(PredefinedTextViewRoles.Document)]
15 | internal class WpfTextConnectionListener : IWpfTextViewConnectionListener
16 | {
17 | [Import]
18 | ITextDocumentFactoryService _textDocumentFactoryService = null;
19 |
20 | public void SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers)
21 | {
22 | ITextDocument textDocument;
23 | if (_textDocumentFactoryService.TryGetTextDocument(textView.TextBuffer, out textDocument))
24 | {
25 | textDocument.FileActionOccurred += delegate(object sender, TextDocumentFileActionEventArgs e)
26 | {
27 | if (e.FileActionType != FileActionTypes.ContentSavedToDisk) return;
28 |
29 | ITextDocument document = (ITextDocument)sender;
30 | ITextBuffer buffer = document.TextBuffer;
31 | string filePath = e.FilePath;
32 |
33 | FixBufferOnSave(buffer, textView.FormattedLineSource.TabSize, filePath);
34 | };
35 | }
36 | foreach (var buffer in subjectBuffers)
37 | {
38 | IEditorOptions options = textView.Properties.GetProperty(typeof(IEditorOptions));
39 | int tabSize = options.GetTabSize();
40 | FixBufferOnLoad(buffer, tabSize);
41 | }
42 |
43 | if(textDocument != null)
44 | {
45 | textDocument.UpdateDirtyState(false, DateTime.Now);
46 | }
47 | }
48 |
49 | public void SubjectBuffersDisconnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers)
50 | {
51 |
52 | }
53 |
54 | ///
55 | /// Called on buffer load
56 | ///
57 | private static void FixBufferOnLoad(ITextBuffer buffer, int tabSize)
58 | {
59 | if (!AlwaysAlignedConfigurationService.Instance.GetConfiguration().ConvertOnLoadSave) return;
60 |
61 | string str = buffer.CurrentSnapshot.GetText();
62 | if (str.Contains("\t"))
63 | {
64 | MiscGui.WriteOutput("Always Aligned is set to convert files from using spaces when loading, but at least one tab was found. The file will be left alone.");
65 | }
66 | else
67 | {
68 | string convertedText = ElasticTabstopsConverter.ToElasticTabstops(str, tabSize);
69 | ITextEdit tb = buffer.CreateEdit();
70 | tb.Replace(new Span(0, buffer.CurrentSnapshot.Length), convertedText);
71 | tb.Apply();
72 | }
73 | }
74 |
75 | ///
76 | /// Called on buffer save
77 | ///
78 | private void FixBufferOnSave(ITextBuffer buffer, int tabSize, string filePath)
79 | {
80 | if (!AlwaysAlignedConfigurationService.Instance.GetConfiguration().ConvertOnLoadSave) return;
81 |
82 | using (StreamWriter streamWriter = new StreamWriter(filePath))
83 | {
84 | string text = buffer.CurrentSnapshot.GetText();
85 | string convertedText = ElasticTabstopsConverter.ToSpaces(text, tabSize);
86 | streamWriter.Write(convertedText);
87 | }
88 | }
89 |
90 |
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/AlwaysAlignedVS/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Always Aligned VS
6 | Elastic tabstops for Visual Studio
7 | https://github.com/nickgravgaard/AlwaysAlignedVS
8 | ..\LICENSE.md
9 | Resources\icon.png
10 | Resources\previewimage.png
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/ElasticTabstopsConverterTest/ElasticTabstopsConverterTest.cs:
--------------------------------------------------------------------------------
1 | using AlwaysAligned;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | namespace ElasticTabstopsConverterTest
8 | {
9 |
10 |
11 | ///
12 | ///This is a test class for ElasticTabstopsConverterTest and is intended
13 | ///to contain all ElasticTabstopsConverterTest Unit Tests
14 | ///
15 | [TestClass()]
16 | public class ElasticTabstopsConverterTest
17 | {
18 | private const string ET_TEXT_1 = @"
19 | abc
20 |
21 | def
22 | ghi
23 |
24 | jkl
25 | mno
26 |
27 | pqr
28 | stu
29 |
30 | vwx
31 | ";
32 |
33 | private const string SPACE_TEXT_1 = @"
34 | abc
35 |
36 | def
37 | ghi
38 |
39 | jkl
40 | mno
41 |
42 | pqr
43 | stu
44 |
45 | vwx
46 | ";
47 |
48 | private const string ET_TEXT_2 = @"aaa
49 |
50 | abc
51 | def
52 | ghi
53 | jkl
54 |
55 | mno
56 | pqr
57 |
58 | stu
59 | vwx
60 | ";
61 |
62 | private const string SPACE_TEXT_2 = @"aaa
63 |
64 | abc
65 | def
66 | ghi
67 | jkl
68 |
69 | mno
70 | pqr
71 |
72 | stu
73 | vwx
74 | ";
75 |
76 | private const string ET_TEXT_3 = @"
77 | abc
78 | def
79 |
80 | ghi
81 | x jkl
82 |
83 | mno
84 | xxxxxxxxx pqr
85 | ";
86 |
87 | private const string SPACE_TEXT_3 = @"
88 | abc
89 | def
90 |
91 | ghi
92 | x jkl
93 |
94 | mno
95 | xxxxxxxxx pqr
96 | ";
97 |
98 | private List SPACE_TEXT_3_POSITIONS_CONTENTS = new List
99 | {
100 | new Line(),
101 | new Line(new SortedDictionary {{8, new Cell(SPACE_TEXT_3.IndexOf("abc"), "abc".Length)}}),
102 | new Line(new SortedDictionary {{8, new Cell(SPACE_TEXT_3.IndexOf("def"), "def".Length)}}),
103 | new Line(),
104 | new Line(new SortedDictionary {{8, new Cell(SPACE_TEXT_3.IndexOf("ghi"), "ghi".Length)}}),
105 | new Line(new SortedDictionary {{0, new Cell(SPACE_TEXT_3.IndexOf("x"), "x".Length)}, {8, new Cell(SPACE_TEXT_3.IndexOf("jkl"), "jkl".Length)}}),
106 | new Line(),
107 | new Line(new SortedDictionary {{16, new Cell(SPACE_TEXT_3.IndexOf("mno"), "mno".Length)}}),
108 | new Line(new SortedDictionary {{0, new Cell(SPACE_TEXT_3.IndexOf("xxxxxxxxx"), "xxxxxxxxx".Length)}, {16, new Cell(SPACE_TEXT_3.IndexOf("pqr"), "pqr".Length)}}),
109 | new Line(),
110 | };
111 |
112 | private const string ET_TEXT_4A = @"
113 | abc
114 | def ghi
115 | jkl mno
116 | pqr
117 | ";
118 |
119 | private const string SPACE_TEXT_4A = @"
120 | abc
121 | def ghi
122 | jkl mno
123 | pqr
124 | ";
125 |
126 | private const string ET_TEXT_4B = @"
127 | abc
128 | def ghi
129 | jkl mno
130 | pqr";
131 |
132 | private const string SPACE_TEXT_4B = @"
133 | abc
134 | def ghi
135 | jkl mno
136 | pqr";
137 |
138 | private const string ET_TEXT_5 = @"
139 | # commented out
140 | return something";
141 |
142 | private const string SPACE_TEXT_5_IN = @"
143 | # commented out
144 | return something";
145 |
146 | private const string SPACE_TEXT_5_OUT = @"
147 | # commented out
148 | return something";
149 |
150 | private const string ET_TEXT_6 = @"// eeeeeeee.cpp : Defines the entry point for the console application.
151 | //
152 |
153 | #include ""stdafx.h""
154 |
155 |
156 | int _tmain(int argc, _TCHAR* argv[])
157 | {
158 | return 0;
159 | kkkkkkkkkkkkkk kkkkkkkk
160 | llllllllllllllllllllll llllllllllll
161 |
162 | aa bb cc
163 | a b c
164 | }
165 |
166 | ";
167 |
168 | private const string SPACE_TEXT_6 = @"// eeeeeeee.cpp : Defines the entry point for the console application.
169 | //
170 |
171 | #include ""stdafx.h""
172 |
173 |
174 | int _tmain(int argc, _TCHAR* argv[])
175 | {
176 | return 0;
177 | kkkkkkkkkkkkkk kkkkkkkk
178 | llllllllllllllllllllll llllllllllll
179 |
180 | aa bb cc
181 | a b c
182 | }
183 |
184 | ";
185 |
186 | private const string ET_TEXT_7 = @" Hallo
187 | Pupallo
188 | Gugu gaga
189 | hhghga hghghhghg
190 | adsdasdasdasda ghghghgghghg
191 | ";
192 |
193 | private const string SPACE_TEXT_7_IN = @" Hallo
194 | Pupallo
195 | Gugu gaga
196 | hhghga hghghhghg
197 | adsdasdasdasda ghghghgghghg
198 | ";
199 |
200 | private const string SPACE_TEXT_7_OUT = @" Hallo
201 | Pupallo
202 | Gugu gaga
203 | hhghga hghghhghg
204 | adsdasdasdasda ghghghgghghg
205 | ";
206 |
207 | private const string ET_TEXT_8 = @" push
208 | (
209 | @{$self->{struct}},
210 | {
211 | source => $source,
212 | filename => $filename,
213 | pathname => $pathname,
214 | lang => $lang,
215 | level => $level,
216 | back => $back,
217 | url => $url,
218 | modified => $modified,
219 | id => Digest::MD5::md5_hex($url),
220 | file => $file,
221 | }
222 | );
223 | }
224 | ";
225 |
226 | private const string TAB_TEXT_8_IN = @" push
227 | (
228 | @{$self->{struct}},
229 | {
230 | source => $source,
231 | filename => $filename,
232 | pathname => $pathname,
233 | lang => $lang,
234 | level => $level,
235 | back => $back,
236 | url => $url,
237 | modified => $modified,
238 | id => Digest::MD5::md5_hex($url),
239 | file => $file,
240 | }
241 | );
242 | }
243 | ";
244 |
245 | private const string SPACE_TEXT_8_OUT = @" push
246 | (
247 | @{$self->{struct}},
248 | {
249 | source => $source,
250 | filename => $filename,
251 | pathname => $pathname,
252 | lang => $lang,
253 | level => $level,
254 | back => $back,
255 | url => $url,
256 | modified => $modified,
257 | id => Digest::MD5::md5_hex($url),
258 | file => $file,
259 | }
260 | );
261 | }
262 | ";
263 |
264 | private const string SPACE_TEXT_9 = @"
265 | /* Hopefully this Java program should demonstrate how elastic tabstops work. a*/
266 | /* Try inserting and deleting different parts of the text and watch as the tabstops move. b*/
267 | /* If you like this, please ask the writers of your text editor to implement it. c*/
268 | ";
269 |
270 | private List SPACE_TEXT_9_POSITIONS_CONTENTS = new List
271 | {
272 | new Line(),
273 | new Line(new SortedDictionary {{0, new Cell(SPACE_TEXT_9.IndexOf("/* Ho"), "/* Hopefully this Java program should demonstrate how elastic tabstops work.".Length)}, {91, new Cell(SPACE_TEXT_9.IndexOf("a*/"), "a*/".Length)}}),
274 | new Line(new SortedDictionary {{0, new Cell(SPACE_TEXT_9.IndexOf("/* Tr"), "/* Try inserting and deleting different parts of the text and watch as the tabstops move.".Length)}, {91, new Cell(SPACE_TEXT_9.IndexOf("b*/"), "b*/".Length)}}),
275 | new Line(new SortedDictionary {{0, new Cell(SPACE_TEXT_9.IndexOf("/* If"), "/* If you like this, please ask the writers of your text editor to implement it.".Length)}, {91, new Cell(SPACE_TEXT_9.IndexOf("c*/"), "c*/".Length)}}),
276 | new Line(),
277 | };
278 |
279 | private const string ET_FORMATTED_CODE = @"
280 | /* Hopefully this Java program should demonstrate how elastic tabstops work. */
281 | /* Try inserting and deleting different parts of the text and watch as the tabstops move. */
282 | /* If you like this, please ask the writers of your text editor to implement it. */
283 |
284 | #include
285 |
286 | struct ipc_perm
287 | {
288 | key_t key;
289 | ushort uid; /* owner euid and egid */
290 | ushort gid; /* group id */
291 | ushort cuid; /* creator euid and egid */
292 | cell-missing /* for test purposes */
293 | ushort mode; /* access modes */
294 | ushort seq; /* sequence number */
295 | };
296 |
297 | int someDemoCode( int fred,
298 | int wilma)
299 | {
300 | x(); /* try making */
301 | showTextGreeting(); /* this comment */
302 | doSomethingComplicated(); /* a bit longer */
303 | for (i = start; i < end; ++i)
304 | {
305 | if (isPrime(i))
306 | {
307 | ++numPrimes;
308 | }
309 | }
310 | return numPrimes;
311 | }
312 |
313 | ---- and now for something completely different: a table ----
314 |
315 | Title Author Publisher Year
316 | Generation X Douglas Coupland Abacus 1995
317 | Informagic Jean-Pierre Petit John Murray Ltd 1982
318 | The Cyberiad Stanislaw Lem Harcourt Publishers Ltd 1985
319 | The Selfish Gene Richard Dawkins Oxford University Press 2006
320 | ";
321 |
322 | private const string SPACE_FORMATTED_CODE_MIN_2 = @"
323 | /* Hopefully this Java program should demonstrate how elastic tabstops work. */
324 | /* Try inserting and deleting different parts of the text and watch as the tabstops move. */
325 | /* If you like this, please ask the writers of your text editor to implement it. */
326 |
327 | #include
328 |
329 | struct ipc_perm
330 | {
331 | key_t key;
332 | ushort uid; /* owner euid and egid */
333 | ushort gid; /* group id */
334 | ushort cuid; /* creator euid and egid */
335 | cell-missing /* for test purposes */
336 | ushort mode; /* access modes */
337 | ushort seq; /* sequence number */
338 | };
339 |
340 | int someDemoCode( int fred,
341 | int wilma)
342 | {
343 | x(); /* try making */
344 | showTextGreeting(); /* this comment */
345 | doSomethingComplicated(); /* a bit longer */
346 | for (i = start; i < end; ++i)
347 | {
348 | if (isPrime(i))
349 | {
350 | ++numPrimes;
351 | }
352 | }
353 | return numPrimes;
354 | }
355 |
356 | ---- and now for something completely different: a table ----
357 |
358 | Title Author Publisher Year
359 | Generation X Douglas Coupland Abacus 1995
360 | Informagic Jean-Pierre Petit John Murray Ltd 1982
361 | The Cyberiad Stanislaw Lem Harcourt Publishers Ltd 1985
362 | The Selfish Gene Richard Dawkins Oxford University Press 2006
363 | ";
364 |
365 | private const string SPACE_FORMATTED_CODE_MOD_8 = @"
366 | /* Hopefully this Java program should demonstrate how elastic tabstops work. */
367 | /* Try inserting and deleting different parts of the text and watch as the tabstops move. */
368 | /* If you like this, please ask the writers of your text editor to implement it. */
369 |
370 | #include
371 |
372 | struct ipc_perm
373 | {
374 | key_t key;
375 | ushort uid; /* owner euid and egid */
376 | ushort gid; /* group id */
377 | ushort cuid; /* creator euid and egid */
378 | cell-missing /* for test purposes */
379 | ushort mode; /* access modes */
380 | ushort seq; /* sequence number */
381 | };
382 |
383 | int someDemoCode( int fred,
384 | int wilma)
385 | {
386 | x(); /* try making */
387 | showTextGreeting(); /* this comment */
388 | doSomethingComplicated(); /* a bit longer */
389 | for (i = start; i < end; ++i)
390 | {
391 | if (isPrime(i))
392 | {
393 | ++numPrimes;
394 | }
395 | }
396 | return numPrimes;
397 | }
398 |
399 | ---- and now for something completely different: a table ----
400 |
401 | Title Author Publisher Year
402 | Generation X Douglas Coupland Abacus 1995
403 | Informagic Jean-Pierre Petit John Murray Ltd 1982
404 | The Cyberiad Stanislaw Lem Harcourt Publishers Ltd 1985
405 | The Selfish Gene Richard Dawkins Oxford University Press 2006
406 | ";
407 |
408 | private const string NO_STRING = "NO_STRING"; // ugh
409 |
410 | // this changed from dicts like: {'et_text': ET_TEXT_8, 'space_text': TAB_TEXT_8_IN, 'space_text_out': SPACE_TEXT_8_OUT, 'tab_size': 4}
411 | // to tuples like: (ET_TEXT_8, TAB_TEXT_8_IN, SPACE_TEXT_8_OUT, 4)
412 | private List> TEST_STRINGS_LIST = new List>
413 | {
414 | Tuple.Create(ET_FORMATTED_CODE, SPACE_FORMATTED_CODE_MOD_8, NO_STRING, 8),
415 | Tuple.Create(ET_TEXT_1, SPACE_TEXT_1, NO_STRING, 8),
416 | Tuple.Create(ET_TEXT_2, SPACE_TEXT_2, NO_STRING, 8),
417 | Tuple.Create(ET_TEXT_3, SPACE_TEXT_3, NO_STRING, 8),
418 | Tuple.Create(ET_TEXT_4A, SPACE_TEXT_4A, NO_STRING, 8),
419 | Tuple.Create(ET_TEXT_4B, SPACE_TEXT_4B, NO_STRING, 8),
420 | Tuple.Create(ET_TEXT_5, SPACE_TEXT_5_IN, SPACE_TEXT_5_OUT, 8),
421 | Tuple.Create(ET_TEXT_6, SPACE_TEXT_6, NO_STRING, 16),
422 | Tuple.Create(ET_TEXT_7, SPACE_TEXT_7_IN, SPACE_TEXT_7_OUT, 8),
423 | Tuple.Create(ET_TEXT_8, TAB_TEXT_8_IN, SPACE_TEXT_8_OUT, 4),
424 | };
425 |
426 | private TestContext testContextInstance;
427 |
428 | ///
429 | ///Gets or sets the test context which provides
430 | ///information about and functionality for the current test run.
431 | ///
432 | public TestContext TestContext
433 | {
434 | get
435 | {
436 | return testContextInstance;
437 | }
438 | set
439 | {
440 | testContextInstance = value;
441 | }
442 | }
443 |
444 | #region Additional test attributes
445 | //
446 | //You can use the following additional attributes as you write your tests:
447 | //
448 | //Use ClassInitialize to run code before running the first test in the class
449 | //[ClassInitialize()]
450 | //public static void MyClassInitialize(TestContext testContext)
451 | //{
452 | //}
453 | //
454 | //Use ClassCleanup to run code after all tests in a class have run
455 | //[ClassCleanup()]
456 | //public static void MyClassCleanup()
457 | //{
458 | //}
459 | //
460 | //Use TestInitialize to run code before running each test
461 | //[TestInitialize()]
462 | //public void MyTestInitialize()
463 | //{
464 | //}
465 | //
466 | //Use TestCleanup to run code after each test has run
467 | //[TestCleanup()]
468 | //public void MyTestCleanup()
469 | //{
470 | //}
471 | //
472 | #endregion
473 |
474 |
475 | ///
476 | ///A test for ToElasticTabstops
477 | ///
478 | [TestMethod()]
479 | public void ToElasticTabstopsTest()
480 | {
481 | foreach (Tuple testStrings in TEST_STRINGS_LIST)
482 | {
483 | string string1 = testStrings.Item1;
484 | string string2 = ElasticTabstopsConverter.ToElasticTabstops(testStrings.Item2, testStrings.Item4);
485 |
486 | Assert.AreEqual(string1, string2);
487 | }
488 | }
489 |
490 | ///
491 | ///A test for ToSpaces
492 | ///
493 | [TestMethod()]
494 | public void ToSpacesTest()
495 | {
496 | foreach (Tuple testStrings in TEST_STRINGS_LIST)
497 | {
498 | string string1;
499 | if (testStrings.Item3 == NO_STRING)
500 | {
501 | string1 = testStrings.Item2;
502 | }
503 | else
504 | {
505 | string1 = testStrings.Item3;
506 | }
507 | string string2 = ElasticTabstopsConverter.ToSpaces(testStrings.Item1, testStrings.Item4);
508 | Assert.AreEqual(string1, string2);
509 | }
510 | }
511 |
512 | ///
513 | ///A test for CellExists
514 | ///
515 | [TestMethod()]
516 | public void CellExistsTest()
517 | {
518 | List> listOfLists = new List>
519 | {
520 | new List {},
521 | new List {1},
522 | new List {2, 3},
523 | new List {4, 5, 6}
524 | };
525 | Assert.IsFalse(ElasticTabstopsConverter.CellExists(listOfLists, 0, 0));
526 | Assert.IsFalse(ElasticTabstopsConverter.CellExists(listOfLists, 1, 1));
527 | Assert.IsFalse(ElasticTabstopsConverter.CellExists(listOfLists, 2, 2));
528 | Assert.IsFalse(ElasticTabstopsConverter.CellExists(listOfLists, 3, 3));
529 | Assert.IsFalse(ElasticTabstopsConverter.CellExists(listOfLists, 4, 0));
530 | Assert.IsTrue(ElasticTabstopsConverter.CellExists(listOfLists, 1, 0));
531 | Assert.IsTrue(ElasticTabstopsConverter.CellExists(listOfLists, 2, 1));
532 | Assert.IsTrue(ElasticTabstopsConverter.CellExists(listOfLists, 3, 2));
533 | }
534 |
535 | string PositionsToString(List lines)
536 | {
537 | return String.Join("\n", from line in lines select String.Join("\t", from cell in line.Cells select cell.Value.ToString() + "|" + cell.Key));
538 | }
539 |
540 | ///
541 | ///A test for GetPositionsContents
542 | ///
543 | [TestMethod()]
544 | public void GetPositionsContentsTest()
545 | {
546 | string string1 = PositionsToString(SPACE_TEXT_3_POSITIONS_CONTENTS);
547 | string string2 = PositionsToString(ElasticTabstopsConverter.GetLines(SPACE_TEXT_3, 8));
548 | Assert.AreEqual(string1, string2);
549 |
550 | string1 = PositionsToString(SPACE_TEXT_9_POSITIONS_CONTENTS);
551 | string2 = PositionsToString(ElasticTabstopsConverter.GetLines(SPACE_TEXT_9, 8));
552 | Assert.AreEqual(string1, string2);
553 | }
554 | }
555 | }
556 |
--------------------------------------------------------------------------------
/ElasticTabstopsConverterTest/ElasticTabstopsConverterTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 |
7 |
8 | 2.0
9 | {7A729CF5-BE92-44A2-8A98-B58E92A3AC45}
10 | Library
11 | Properties
12 | ElasticTabstopsConverterTest
13 | ElasticTabstopsConverterTest
14 | v4.6
15 | 512
16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
17 |
18 |
19 |
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 | false
28 |
29 |
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 | false
37 |
38 |
39 |
40 | False
41 |
42 |
43 | False
44 |
45 |
46 | False
47 |
48 |
49 | False
50 |
51 |
52 |
53 | False
54 |
55 |
56 |
57 |
58 |
59 |
60 | False
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | False
69 |
70 |
71 |
72 |
73 | 3.5
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | False
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | {2939A962-7734-4BDA-937F-25DC423B3843}
94 | AlwaysAlignedVS
95 |
96 |
97 |
98 |
105 |
--------------------------------------------------------------------------------
/ElasticTabstopsConverterTest/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("ElasticTabstopsConverterTest")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ElasticTabstopsConverterTest")]
13 | [assembly: AssemblyCopyright("Copyright © 2010-2017 Nick Gravgaard")]
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 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("d8dcd7bb-489c-4206-8295-3babf8c8d473")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
36 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | **Copyright 2010-2017 Nick Gravgaard**
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/Local.testsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 | These are default test settings for a local test run.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Always Aligned VS
2 |
3 | **Stop wasting time manually lining up code.**
4 |
5 | Elastic tabstops are a fundamentally better way of handling text. Now you can use them in Visual Studio.
6 |
7 | 
8 |
9 | Elastic tabstops mean programmers just use one tab between columns rather than inserting the exact number of spaces/tabs on each line in the buffer to make things line up. Always Aligned brings elastic tabstops to Visual Studio while still allowing users to work with code that doesn't use them.
10 |
11 | ## Notes
12 |
13 | * After installation, change Visual Studio's settings to use tabs instead of spaces.
14 |
15 | * Consider adding elastic tabstops to your code standard to minimise whitespace changes in your version control system. Alternatively, if your code standard uses spaces, make sure you enable Always Aligned's "Convert to/from space based files" setting.
16 |
17 |
--------------------------------------------------------------------------------
/TraceAndTestImpact.testsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 | These are test settings for Trace and Test Impact.
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/screencapture.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nick-gravgaard/AlwaysAlignedVS/b6c7fb2e938e87c2a14f1b2c682fac3222448168/screencapture.gif
--------------------------------------------------------------------------------