Skip to content

Commit 24d7faf

Browse files
committed
Added GtmListener.
1 parent 3336d6d commit 24d7faf

2 files changed

Lines changed: 129 additions & 121 deletions

File tree

src/GtmExtension/GtmPackage.cs

Lines changed: 110 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
using Microsoft.VisualStudio.Text;
88
using Microsoft.VisualStudio.Text.Editor;
99
using Microsoft.VisualStudio.TextManager.Interop;
10+
using Microsoft.VisualStudio.Utilities;
1011
using System;
1112
using System.ComponentModel;
13+
using System.ComponentModel.Composition;
1214
using System.Diagnostics.CodeAnalysis;
1315
using System.Runtime.InteropServices;
1416
using System.Threading;
@@ -41,16 +43,6 @@ namespace GtmExtension
4143
[ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] // Load the extension when a solution is open.
4244
public sealed class GtmPackage : AsyncPackage
4345
{
44-
private string gtmExe, prevPath, status;
45-
private IVsStatusbar statusBar;
46-
private IVsEditorAdaptersFactoryService editor;
47-
private WindowEvents windowEvents;
48-
private DocumentEvents documentEvents;
49-
private IVsTextManager textManager;
50-
private IWpfTextView wpfTextView;
51-
private DateTime lastUpdate;
52-
private static readonly TimeSpan updateInterval = TimeSpan.FromSeconds(30.0);
53-
5446
/// <summary>
5547
/// GtmPackage GUID string.
5648
/// </summary>
@@ -67,6 +59,53 @@ public GtmPackage()
6759
// initialization is the Initialize method.
6860
}
6961

62+
#region Package Members
63+
64+
/// <summary>
65+
/// Initialization of the package; this method is called right after the package is sited, so this is the place
66+
/// where you can put all the initialization code that rely on services provided by VisualStudio.
67+
/// </summary>
68+
/// <param name="cancellationToken">A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.</param>
69+
/// <param name="progress">A provider for progress updates.</param>
70+
/// <returns>A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.</returns>
71+
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
72+
{
73+
}
74+
75+
#endregion
76+
}
77+
78+
[Export(typeof(IVsTextViewCreationListener))]
79+
[ContentType("text")]
80+
[TextViewRole(PredefinedTextViewRoles.Editable)]
81+
public sealed class AnotherListener : IVsTextViewCreationListener
82+
{
83+
public void VsTextViewCreated(IVsTextView textViewAdapter)
84+
{
85+
}
86+
}
87+
88+
[Export(typeof(IVsTextViewCreationListener))]
89+
[ContentType("text")]
90+
[TextViewRole(PredefinedTextViewRoles.Editable)]
91+
public sealed class GtmListener : IVsTextViewCreationListener
92+
{
93+
private bool initialized;
94+
private string gtmExe, prevPath, status;
95+
private DateTime lastUpdate;
96+
private IVsEditorAdaptersFactoryService editor;
97+
private DocumentEvents documentEvents;
98+
private DTE dte;
99+
private IComponentModel componentModel;
100+
private IVsStatusbar statusbar;
101+
private IVsUIShell uiShell;
102+
private static readonly TimeSpan updateInterval = TimeSpan.FromSeconds(30.0);
103+
104+
#region Imports
105+
[Import]
106+
public SVsServiceProvider ServiceProvider { get; set; }
107+
#endregion
108+
70109
#region Helper Functions
71110

72111
private static Process ExecuteProcess(string exeName, string arguments)
@@ -107,128 +146,97 @@ private static bool ExistsOnPath(string exeName)
107146
return Execute("where", exeName) == 0;
108147
}
109148

110-
private async Task ShowErrorAsync(string message)
149+
private void ShowError(string message)
111150
{
112-
// Switch to UI thread.
113-
await JoinableTaskFactory.SwitchToMainThreadAsync();
114-
115151
// Show message box.
116-
var uiShell = (IVsUIShell)await GetServiceAsync(typeof(SVsUIShell));
117-
if (uiShell == null) { return; }
118152
Guid clsid = Guid.Empty;
119153
ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox(0, ref clsid, "GtmExtension", message, string.Empty, 0,
120154
OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_CRITICAL, 0, out var result));
121155
}
122156

157+
private string GetFilePath(ITextView textView)
158+
{
159+
return textView.TextBuffer.Properties.GetProperty<ITextDocument>(typeof(ITextDocument)).FilePath;
160+
}
161+
162+
private void GetService<T>(ref T field) { field = GetService<T>(); }
163+
private T GetService<T>() => GetService<T, T>();
164+
private I GetService<S, I>()
165+
{
166+
var service = ServiceProvider.GetService(typeof(S));
167+
if (service == null) { throw new InvalidOperationException($"No {typeof(S).Name}."); }
168+
return (I)service;
169+
}
170+
123171
#endregion
124172

125-
#region Package Members
173+
#region Event handlers
126174

127-
/// <summary>
128-
/// Initialization of the package; this method is called right after the package is sited, so this is the place
129-
/// where you can put all the initialization code that rely on services provided by VisualStudio.
130-
/// </summary>
131-
/// <param name="cancellationToken">A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.</param>
132-
/// <param name="progress">A provider for progress updates.</param>
133-
/// <returns>A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.</returns>
134-
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
175+
public void VsTextViewCreated(IVsTextView textView)
135176
{
177+
Initialize();
178+
179+
var wpfTextView = editor.GetWpfTextView(textView);
180+
wpfTextView.LayoutChanged += WpfTextView_LayoutChanged;
181+
wpfTextView.Caret.PositionChanged += Caret_PositionChanged;
182+
183+
Update(GetFilePath(wpfTextView));
184+
}
185+
private void WpfTextView_LayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
186+
{
187+
Update(GetFilePath((ITextView)sender));
188+
}
189+
private void Caret_PositionChanged(object sender, CaretPositionChangedEventArgs e)
190+
{
191+
Update(GetFilePath(e.TextView));
192+
}
193+
private void DocumentEvents_DocumentSaved(Document Document)
194+
{
195+
Update(Document.FullName, force: true);
196+
}
197+
198+
#endregion
199+
200+
private void Initialize()
201+
{
202+
if (initialized) { return; }
203+
initialized = true;
204+
205+
// Get services.
206+
GetService(ref uiShell);
207+
GetService(ref statusbar);
208+
componentModel = GetService<SComponentModel, IComponentModel>();
209+
GetService(ref dte);
210+
editor = componentModel.GetService<IVsEditorAdaptersFactoryService>();
211+
136212
// Try to find executable `gtm`.
137213
if (ExistsOnPath("gtm"))
138214
{
139215
gtmExe = "gtm";
140216
}
141-
142-
// When initialized asynchronously, the current thread may be a background thread at this point.
143-
// Do any initialization that requires the UI thread after switching to the UI thread.
144-
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
145-
146-
// Show error if we don't find `gtm` on PATH.
147-
if (gtmExe == null)
217+
else
148218
{
149-
await ShowErrorAsync("We couldn't find gtm executable.");
219+
ShowError("We couldn't find gtm executable.");
150220
return;
151221
}
152222

153223
// Verify version.
154224
if (!ExecuteForOutput(gtmExe, "verify \">= 1.1.0\"").Contains("true"))
155225
{
156-
await ShowErrorAsync("Old version of gtm is installed. Please install at least version 1.1.0");
226+
ShowError("Old version of gtm is installed. Please install at least version 1.1.0");
157227
return;
158228
}
159229

160-
// Get the status bar.
161-
statusBar = (IVsStatusbar)await GetServiceAsync(typeof(SVsStatusbar));
162-
if (statusBar == null) { throw new InvalidOperationException("No status bar."); }
163-
164-
// Unfroze it if it's frozen.
165-
ErrorHandler.ThrowOnFailure(statusBar.IsFrozen(out var frozen));
230+
// Unfroze status bar if it's frozen.
231+
ErrorHandler.ThrowOnFailure(statusbar.IsFrozen(out var frozen));
166232
if (frozen != 0)
167233
{
168-
ErrorHandler.ThrowOnFailure(statusBar.FreezeOutput(0));
234+
ErrorHandler.ThrowOnFailure(statusbar.FreezeOutput(0));
169235
}
170236

171-
// Get DTE.
172-
var dte = (DTE)await GetServiceAsync(typeof(DTE));
173-
if (dte == null) { throw new InvalidOperationException("No DTE."); }
174-
175-
// Get text manager.
176-
textManager = (IVsTextManager)await GetServiceAsync(typeof(SVsTextManager));
177-
if (textManager == null) { throw new InvalidOperationException("No TextManager."); }
178-
179-
// Get editor adapters.
180-
var componentModel = (IComponentModel)await GetServiceAsync(typeof(SComponentModel));
181-
if (componentModel == null) { throw new InvalidOperationException("No ComponentModel."); }
182-
editor = componentModel.GetService<IVsEditorAdaptersFactoryService>();
183-
184-
// Subscribe to events. We keep the events objects so that they don't get GC'ed.
185-
windowEvents = dte.Events.WindowEvents;
186-
windowEvents.WindowActivated += WindowEvents_WindowActivated;
187-
237+
// Subscribe to events. We keep the events object so that it doesn't get GC'ed.
188238
documentEvents = dte.Events.DocumentEvents;
189239
documentEvents.DocumentSaved += DocumentEvents_DocumentSaved;
190-
191-
Subscribe();
192-
}
193-
194-
private void WindowEvents_WindowActivated(Window GotFocus, Window LostFocus)
195-
{
196-
Subscribe();
197-
}
198-
private string GetFilePath(ITextView textView)
199-
{
200-
return textView.TextBuffer.Properties.GetProperty<ITextDocument>(typeof(ITextDocument)).FilePath;
201-
}
202-
private void Subscribe()
203-
{
204-
// Unsubscribe the previously focused window.
205-
if (wpfTextView != null)
206-
{
207-
wpfTextView.Caret.PositionChanged -= Caret_PositionChanged;
208-
wpfTextView.LayoutChanged -= WpfTextView_LayoutChanged;
209-
wpfTextView = null;
210-
}
211-
212-
// Subsribe the currently focused window.
213-
ErrorHandler.ThrowOnFailure(textManager.GetActiveView(0, null, out IVsTextView textView));
214-
wpfTextView = editor.GetWpfTextView(textView);
215-
wpfTextView.Caret.PositionChanged += Caret_PositionChanged;
216-
wpfTextView.LayoutChanged += WpfTextView_LayoutChanged;
217-
218-
Update(GetFilePath(wpfTextView));
219-
}
220-
221-
private void WpfTextView_LayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
222-
{
223-
Update(GetFilePath(wpfTextView));
224-
}
225-
private void Caret_PositionChanged(object sender, CaretPositionChangedEventArgs e)
226-
{
227-
Update(GetFilePath(e.TextView));
228-
}
229-
private void DocumentEvents_DocumentSaved(Document Document)
230-
{
231-
Update(Document.FullName, force: true);
232240
}
233241
private void Update(string path, bool force = false)
234242
{
@@ -240,17 +248,16 @@ private void Update(string path, bool force = false)
240248
status = ExecuteForOutput(gtmExe, $"record --status \"{path}\"");
241249
if (!string.IsNullOrWhiteSpace(status))
242250
{
243-
statusBar.SetText($"GTM: {status}*");
251+
statusbar.SetText($"GTM: {status}*");
244252
}
245253

246254
prevPath = path;
247255
}
248256
else if (!string.IsNullOrWhiteSpace(status))
249257
{
250-
statusBar.SetText($"GTM: {status}");
258+
statusbar.SetText($"GTM: {status}");
251259
}
252260
lastUpdate = time;
253261
}
254-
#endregion
255262
}
256263
}
Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
3-
<Metadata>
4-
<Identity Id="GtmExtension.78062a42-0a99-47d0-a6e2-200a84959b52" Version="1.0" Language="en-US" Publisher="Jan Joneš" />
5-
<DisplayName>GtmExtension</DisplayName>
6-
<Description>Empty VSIX Project.</Description>
7-
</Metadata>
8-
<Installation>
9-
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0, 16.0)" />
10-
</Installation>
11-
<Dependencies>
12-
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
13-
<Dependency Id="Microsoft.VisualStudio.MPF.15.0" DisplayName="Visual Studio MPF 15.0" d:Source="Installed" Version="[15.0]" />
14-
</Dependencies>
15-
<Prerequisites>
16-
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0,16.0)" DisplayName="Visual Studio core editor" />
17-
</Prerequisites>
18-
<Assets>
19-
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
20-
</Assets>
3+
<Metadata>
4+
<Identity Id="GtmExtension.78062a42-0a99-47d0-a6e2-200a84959b52" Version="1.0" Language="en-US" Publisher="Jan Joneš" />
5+
<DisplayName>GtmExtension</DisplayName>
6+
<Description>Empty VSIX Project.</Description>
7+
</Metadata>
8+
<Installation>
9+
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[15.0, 16.0)" />
10+
</Installation>
11+
<Dependencies>
12+
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
13+
<Dependency Id="Microsoft.VisualStudio.MPF.15.0" DisplayName="Visual Studio MPF 15.0" d:Source="Installed" Version="[15.0]" />
14+
</Dependencies>
15+
<Prerequisites>
16+
<Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[15.0,16.0)" DisplayName="Visual Studio core editor" />
17+
</Prerequisites>
18+
<Assets>
19+
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
20+
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
21+
</Assets>
2122
</PackageManifest>

0 commit comments

Comments
 (0)