77using Microsoft . VisualStudio . Text ;
88using Microsoft . VisualStudio . Text . Editor ;
99using Microsoft . VisualStudio . TextManager . Interop ;
10+ using Microsoft . VisualStudio . Utilities ;
1011using System ;
1112using System . ComponentModel ;
13+ using System . ComponentModel . Composition ;
1214using System . Diagnostics . CodeAnalysis ;
1315using System . Runtime . InteropServices ;
1416using 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}
0 commit comments