6
6
using System . Threading ;
7
7
using System . Xml . Linq ;
8
8
using Microsoft . VisualStudio . Shell . Interop ;
9
+ using Newtonsoft . Json ;
9
10
using Newtonsoft . Json . Linq ;
10
11
11
12
namespace SmallSharp . Build
12
13
{
13
- class ActiveDocumentMonitor : MarshalByRefObject , IDisposable , IVsRunningDocTableEvents , IVsSelectionEvents
14
+ class ActiveDocumentMonitor : MarshalByRefObject , IDisposable , IVsRunningDocTableEvents , IVsSelectionEvents , IVsSolutionEvents
14
15
{
15
- FileSystemWatcher watcher ;
16
- readonly IServiceProvider services ;
17
-
16
+ IVsSolution ? solution ;
18
17
IVsRunningDocumentTable ? rdt ;
19
18
IVsMonitorSelection ? selection ;
19
+
20
+ uint solutionCookie ;
20
21
uint rdtCookie ;
21
22
uint selectionCookie ;
22
23
23
24
string launchProfilesPath ;
24
25
string userFile ;
25
- string flagFile ;
26
- Dictionary < string , string > startupFiles = new ( ) ;
26
+ Dictionary < string , string > startupFiles ;
27
+
28
+ string ? activeFile ;
27
29
28
- public ActiveDocumentMonitor ( string launchProfilesPath , string userFile , string flagFile , IServiceProvider services )
30
+ public ActiveDocumentMonitor ( string launchProfilesPath , string userFile ,
31
+ string [ ] startupFiles , IServiceProvider services )
29
32
{
30
33
this . launchProfilesPath = launchProfilesPath ;
31
34
this . userFile = userFile ;
32
- this . flagFile = flagFile ;
33
- this . services = services ;
34
-
35
- watcher = new FileSystemWatcher ( Path . GetDirectoryName ( launchProfilesPath ) )
36
- {
37
- NotifyFilter = NotifyFilters . LastWrite ,
38
- Filter = "launchSettings.json" ,
39
- } ;
40
-
41
- watcher . Changed += ( _ , _ ) => ReloadProfiles ( ) ;
42
- watcher . Created += ( _ , _ ) => ReloadProfiles ( ) ;
43
- watcher . EnableRaisingEvents = true ;
44
- ReloadProfiles ( ) ;
45
- }
35
+ this . startupFiles = startupFiles . ToDictionary ( x => x , StringComparer . OrdinalIgnoreCase ) ;
46
36
47
- public void Start ( )
48
- {
37
+ solution = ( IVsSolution ) services . GetService ( typeof ( SVsSolution ) ) ;
49
38
rdt = ( IVsRunningDocumentTable ) services . GetService ( typeof ( SVsRunningDocumentTable ) ) ;
50
- if ( rdt != null )
51
- rdt . AdviseRunningDocTableEvents ( this , out rdtCookie ) ;
52
-
53
39
selection = ( IVsMonitorSelection ) services . GetService ( typeof ( SVsShellMonitorSelection ) ) ;
54
- if ( selection != null )
55
- selection . AdviseSelectionEvents ( this , out selectionCookie ) ;
40
+
41
+ EnsureMonitoring ( ) ;
56
42
}
57
43
58
- public void Refresh ( string launchProfiles , string userFile , string flagFile )
44
+ public void Refresh ( string launchProfiles , string userFile , string [ ] startupFiles )
59
45
{
60
46
launchProfilesPath = launchProfiles ;
61
47
this . userFile = userFile ;
62
- this . flagFile = flagFile ;
63
- watcher . Path = Path . GetDirectoryName ( launchProfiles ) ;
64
- ReloadProfiles ( ) ;
65
- }
48
+ this . startupFiles = startupFiles . ToDictionary ( x => x , StringComparer . OrdinalIgnoreCase ) ;
66
49
67
- void ReloadProfiles ( )
68
- {
69
- if ( ! File . Exists ( launchProfilesPath ) )
70
- return ;
71
-
72
- var maxAttempts = 5 ;
73
- var exceptions = new List < Exception > ( ) ;
50
+ EnsureMonitoring ( ) ;
74
51
75
- for ( var i = 0 ; i < maxAttempts ; i ++ )
76
- {
77
- try
78
- {
79
- var json = JObject . Parse ( File . ReadAllText ( launchProfilesPath ) ) ;
80
- if ( json . Property ( "profiles" ) is not JProperty prop ||
81
- prop . Value is not JObject profiles )
82
- return ;
52
+ // For new files, we get the update before the new item is added to
53
+ // msbuild top-level files, so we retry on refresh
54
+ UpdateStartupFile ( activeFile ) ;
55
+ }
83
56
84
- startupFiles = profiles . Properties ( ) . Select ( p => p . Name )
85
- . ToDictionary ( x => x , StringComparer . OrdinalIgnoreCase ) ;
57
+ void EnsureMonitoring ( )
58
+ {
59
+ if ( solutionCookie == 0 && solution != null )
60
+ solution . AdviseSolutionEvents ( this , out solutionCookie ) ;
86
61
87
- return ;
88
- }
89
- catch ( Exception e )
90
- {
91
- exceptions . Add ( e ) ;
92
- Thread . Sleep ( 500 ) ;
93
- }
94
- }
62
+ if ( rdtCookie == 0 && rdt != null )
63
+ rdt . AdviseRunningDocTableEvents ( this , out rdtCookie ) ;
95
64
96
- // NOTE: check exceptions list to see why.
97
- Debug . Fail ( "Could not read launchSettings.json" ) ;
65
+ if ( selectionCookie == 0 && selection != null )
66
+ selection . AdviseSelectionEvents ( this , out selectionCookie ) ;
98
67
}
99
68
100
69
void UpdateStartupFile ( string ? path )
101
70
{
71
+ activeFile = path ;
72
+
102
73
if ( ! string . IsNullOrEmpty ( path ) &&
103
74
path ! . IndexOfAny ( Path . GetInvalidPathChars ( ) ) == - 1 &&
104
- Path . GetFileName ( path ) is string startupFile &&
105
- startupFiles . ContainsKey ( startupFile ) )
75
+ startupFiles . TryGetValue ( Path . GetFileName ( path ) , out var startupFile ) )
106
76
{
77
+ var settings = new JObject (
78
+ new JProperty ( "profiles" , new JObject (
79
+ new JProperty ( startupFile , new JObject (
80
+ new JProperty ( "commandName" , "Project" )
81
+ ) )
82
+ ) )
83
+ ) ;
84
+
85
+ var json = settings . ToString ( Formatting . Indented ) ;
86
+
87
+ // Only write if different content.
88
+ if ( File . Exists ( launchProfilesPath ) &&
89
+ File . ReadAllText ( launchProfilesPath ) == json )
90
+ return ;
91
+
92
+ File . WriteAllText ( launchProfilesPath , json ) ;
93
+
107
94
try
108
95
{
109
96
// Get the value as it was exists in the original dictionary,
110
97
// since it has to match what the source generator created in the
111
98
// launch profiles.
112
- startupFile = startupFiles [ startupFile ] ;
113
99
var xdoc = XDocument . Load ( userFile ) ;
114
100
var active = xdoc
115
101
. Descendants ( "{http://schemas.microsoft.com/developer/msbuild/2003}ActiveDebugProfile" )
116
102
. FirstOrDefault ( ) ;
117
103
118
- if ( active != null && active . Value != startupFile )
104
+ if ( active != null && ! startupFile . Equals ( active . Value , StringComparison . OrdinalIgnoreCase ) )
119
105
{
120
106
active . Value = startupFile ;
121
- // First save to flag file so we don't cause another open
122
- // attempt via the OpenStartupFile task.
123
- File . WriteAllText ( flagFile , startupFile ) ;
124
107
xdoc . Save ( userFile ) ;
125
108
}
126
109
}
@@ -131,15 +114,22 @@ void UpdateStartupFile(string? path)
131
114
}
132
115
}
133
116
134
- void IDisposable . Dispose ( )
117
+ public void Dispose ( )
135
118
{
119
+ if ( solutionCookie != 0 && solution != null )
120
+ Try ( ( ) => solution . UnadviseSolutionEvents ( solutionCookie ) ) ;
121
+
122
+ solutionCookie = 0 ;
123
+
136
124
if ( rdtCookie != 0 && rdt != null )
137
125
Try ( ( ) => rdt . UnadviseRunningDocTableEvents ( rdtCookie ) ) ;
138
126
127
+ rdtCookie = 0 ;
128
+
139
129
if ( selectionCookie != 0 && selection != null )
140
130
Try ( ( ) => selection . UnadviseSelectionEvents ( selectionCookie ) ) ;
141
131
142
- watcher . Dispose ( ) ;
132
+ selectionCookie = 0 ;
143
133
}
144
134
145
135
void Try ( Action action )
@@ -176,18 +166,32 @@ int IVsSelectionEvents.OnSelectionChanged(IVsHierarchy pHierOld, uint itemidOld,
176
166
return 0 ;
177
167
}
178
168
179
- int IVsRunningDocTableEvents . OnAfterFirstDocumentLock ( uint docCookie , uint dwRDTLockType , uint dwReadLocksRemaining , uint dwEditLocksRemaining ) => 0 ;
169
+ int IVsSolutionEvents . OnBeforeUnloadProject ( IVsHierarchy pRealHierarchy , IVsHierarchy pStubHierarchy )
170
+ {
171
+ Dispose ( ) ;
172
+ return 0 ;
173
+ }
180
174
181
- int IVsRunningDocTableEvents . OnBeforeLastDocumentUnlock ( uint docCookie , uint dwRDTLockType , uint dwReadLocksRemaining , uint dwEditLocksRemaining ) => 0 ;
175
+ int IVsSolutionEvents . OnBeforeCloseSolution ( object pUnkReserved )
176
+ {
177
+ Dispose ( ) ;
178
+ return 0 ;
179
+ }
182
180
181
+ int IVsRunningDocTableEvents . OnAfterFirstDocumentLock ( uint docCookie , uint dwRDTLockType , uint dwReadLocksRemaining , uint dwEditLocksRemaining ) => 0 ;
182
+ int IVsRunningDocTableEvents . OnBeforeLastDocumentUnlock ( uint docCookie , uint dwRDTLockType , uint dwReadLocksRemaining , uint dwEditLocksRemaining ) => 0 ;
183
183
int IVsRunningDocTableEvents . OnAfterSave ( uint docCookie ) => 0 ;
184
-
185
184
int IVsRunningDocTableEvents . OnBeforeDocumentWindowShow ( uint docCookie , int fFirstShow , IVsWindowFrame pFrame ) => 0 ;
186
-
187
185
int IVsRunningDocTableEvents . OnAfterDocumentWindowHide ( uint docCookie , IVsWindowFrame pFrame ) => 0 ;
188
-
189
186
int IVsSelectionEvents . OnElementValueChanged ( uint elementid , object varValueOld , object varValueNew ) => 0 ;
190
-
191
187
int IVsSelectionEvents . OnCmdUIContextChanged ( uint dwCmdUICookie , int fActive ) => 0 ;
188
+ int IVsSolutionEvents . OnAfterOpenProject ( IVsHierarchy pHierarchy , int fAdded ) => throw new NotImplementedException ( ) ;
189
+ int IVsSolutionEvents . OnQueryCloseProject ( IVsHierarchy pHierarchy , int fRemoving , ref int pfCancel ) => throw new NotImplementedException ( ) ;
190
+ int IVsSolutionEvents . OnBeforeCloseProject ( IVsHierarchy pHierarchy , int fRemoved ) => throw new NotImplementedException ( ) ;
191
+ int IVsSolutionEvents . OnAfterLoadProject ( IVsHierarchy pStubHierarchy , IVsHierarchy pRealHierarchy ) => throw new NotImplementedException ( ) ;
192
+ int IVsSolutionEvents . OnQueryUnloadProject ( IVsHierarchy pRealHierarchy , ref int pfCancel ) => throw new NotImplementedException ( ) ;
193
+ int IVsSolutionEvents . OnAfterOpenSolution ( object pUnkReserved , int fNewSolution ) => throw new NotImplementedException ( ) ;
194
+ int IVsSolutionEvents . OnQueryCloseSolution ( object pUnkReserved , ref int pfCancel ) => throw new NotImplementedException ( ) ;
195
+ int IVsSolutionEvents . OnAfterCloseSolution ( object pUnkReserved ) => throw new NotImplementedException ( ) ;
192
196
}
193
197
}
0 commit comments