OLD | NEW |
| (Empty) |
1 /// Copyright (c) Microsoft Corporation. All rights reserved. | |
2 | |
3 using System; | |
4 using System.Diagnostics; | |
5 using System.Globalization; | |
6 using System.IO; | |
7 using System.Runtime.InteropServices; | |
8 using Microsoft.VisualStudio; | |
9 using Microsoft.VisualStudio.OLE.Interop; | |
10 using Microsoft.VisualStudio.Shell.Interop; | |
11 using Microsoft.VisualStudio.TextManager.Interop; | |
12 | |
13 namespace Microsoft.VisualStudio.Project | |
14 { | |
15 /// <summary> | |
16 /// Provides support for single file generator. | |
17 /// </summary> | |
18 internal class SingleFileGenerator : ISingleFileGenerator, IVsGeneratorP
rogress | |
19 { | |
20 | |
21 #region fields | |
22 private bool gettingCheckoutStatus; | |
23 private bool runningGenerator; | |
24 private ProjectNode projectMgr; | |
25 #endregion | |
26 | |
27 #region ctors | |
28 /// <summary> | |
29 /// Overloadde ctor. | |
30 /// </summary> | |
31 /// <param name="ProjectNode">The associated project</param> | |
32 internal SingleFileGenerator(ProjectNode projectMgr) | |
33 { | |
34 this.projectMgr = projectMgr; | |
35 } | |
36 #endregion | |
37 | |
38 #region IVsGeneratorProgress Members | |
39 | |
40 public virtual int GeneratorError(int warning, uint level, strin
g err, uint line, uint col) | |
41 { | |
42 return VSConstants.E_NOTIMPL; | |
43 } | |
44 | |
45 public virtual int Progress(uint complete, uint total) | |
46 { | |
47 return VSConstants.E_NOTIMPL; | |
48 } | |
49 | |
50 #endregion | |
51 | |
52 #region ISingleFileGenerator | |
53 /// <summary> | |
54 /// Runs the generator on the current project item. | |
55 /// </summary> | |
56 /// <param name="document"></param> | |
57 /// <returns></returns> | |
58 public virtual void RunGenerator(string document) | |
59 { | |
60 // Go run the generator on that node, but only if the fi
le is dirty | |
61 // in the running document table. Otherwise there is no
need to rerun | |
62 // the generator because if the original document is not
dirty then | |
63 // the generated output should be already up to date. | |
64 uint itemid = VSConstants.VSITEMID_NIL; | |
65 IVsHierarchy hier = (IVsHierarchy)this.projectMgr; | |
66 if(document != null && hier != null && ErrorHandler.Succ
eeded(hier.ParseCanonicalName((string)document, out itemid))) | |
67 { | |
68 IVsHierarchy rdtHier; | |
69 IVsPersistDocData perDocData; | |
70 uint cookie; | |
71 if(this.VerifyFileDirtyInRdt((string)document, o
ut rdtHier, out perDocData, out cookie)) | |
72 { | |
73 // Run the generator on the indicated do
cument | |
74 FileNode node = (FileNode)this.projectMg
r.NodeFromItemId(itemid); | |
75 this.InvokeGenerator(node); | |
76 } | |
77 } | |
78 } | |
79 #endregion | |
80 | |
81 #region virtual methods | |
82 /// <summary> | |
83 /// Invokes the specified generator | |
84 /// </summary> | |
85 /// <param name="fileNode">The node on which to invoke the gener
ator.</param> | |
86 protected internal virtual void InvokeGenerator(FileNode fileNod
e) | |
87 { | |
88 if(fileNode == null) | |
89 { | |
90 throw new ArgumentNullException("fileNode"); | |
91 } | |
92 | |
93 SingleFileGeneratorNodeProperties nodeproperties = fileN
ode.NodeProperties as SingleFileGeneratorNodeProperties; | |
94 if(nodeproperties == null) | |
95 { | |
96 throw new InvalidOperationException(); | |
97 } | |
98 | |
99 string customToolProgID = nodeproperties.CustomTool; | |
100 if(string.IsNullOrEmpty(customToolProgID)) | |
101 { | |
102 return; | |
103 } | |
104 | |
105 string customToolNamespace = nodeproperties.CustomToolNa
mespace; | |
106 | |
107 try | |
108 { | |
109 if(!this.runningGenerator) | |
110 { | |
111 //Get the buffer contents for the curren
t node | |
112 string moniker = fileNode.GetMkDocument(
); | |
113 | |
114 this.runningGenerator = true; | |
115 | |
116 //Get the generator | |
117 IVsSingleFileGenerator generator; | |
118 int generateDesignTimeSource; | |
119 int generateSharedDesignTimeSource; | |
120 int generateTempPE; | |
121 SingleFileGeneratorFactory factory = new
SingleFileGeneratorFactory(this.projectMgr.ProjectGuid, this.projectMgr.Site); | |
122 ErrorHandler.ThrowOnFailure(factory.Crea
teGeneratorInstance(customToolProgID, out generateDesignTimeSource, out generate
SharedDesignTimeSource, out generateTempPE, out generator)); | |
123 | |
124 //Check to see if the generator supports
siting | |
125 IObjectWithSite objWithSite = generator
as IObjectWithSite; | |
126 if(objWithSite != null) | |
127 { | |
128 objWithSite.SetSite(fileNode.Ole
ServiceProvider); | |
129 } | |
130 | |
131 //Determine the namespace | |
132 if(string.IsNullOrEmpty(customToolNamesp
ace)) | |
133 { | |
134 customToolNamespace = this.Compu
teNamespace(moniker); | |
135 } | |
136 | |
137 //Run the generator | |
138 IntPtr[] output = new IntPtr[1]; | |
139 output[0] = IntPtr.Zero; | |
140 uint outPutSize; | |
141 string extension; | |
142 ErrorHandler.ThrowOnFailure(generator.De
faultExtension(out extension)); | |
143 | |
144 //Find if any dependent node exists | |
145 string dependentNodeName = Path.GetFileN
ameWithoutExtension(fileNode.FileName) + extension; | |
146 HierarchyNode dependentNode = fileNode.F
irstChild; | |
147 while(dependentNode != null) | |
148 { | |
149 if(string.Compare(dependentNode.
ItemNode.GetMetadata(ProjectFileConstants.DependentUpon), fileNode.FileName, Str
ingComparison.OrdinalIgnoreCase) == 0) | |
150 { | |
151 dependentNodeName = ((Fi
leNode)dependentNode).FileName; | |
152 break; | |
153 } | |
154 | |
155 dependentNode = dependentNode.Ne
xtSibling; | |
156 } | |
157 | |
158 //If you found a dependent node. | |
159 if(dependentNode != null) | |
160 { | |
161 //Then check out the node and de
pendent node from SCC | |
162 if(!this.CanEditFile(dependentNo
de.GetMkDocument())) | |
163 { | |
164 throw Marshal.GetExcepti
onForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); | |
165 } | |
166 } | |
167 else //It is a new node to be added to t
he project | |
168 { | |
169 // Check out the project file if
necessary. | |
170 if(!this.projectMgr.QueryEditPro
jectFile(false)) | |
171 { | |
172 throw Marshal.GetExcepti
onForHR(VSConstants.OLE_E_PROMPTSAVECANCELLED); | |
173 } | |
174 } | |
175 IVsTextStream stream; | |
176 string inputFileContents = this.GetBuffe
rContents(moniker, out stream); | |
177 | |
178 ErrorHandler.ThrowOnFailure(generator.Ge
nerate(moniker, inputFileContents, customToolNamespace, output, out outPutSize,
this)); | |
179 byte[] data = new byte[outPutSize]; | |
180 | |
181 if(output[0] != IntPtr.Zero) | |
182 { | |
183 Marshal.Copy(output[0], data, 0,
(int)outPutSize); | |
184 Marshal.FreeCoTaskMem(output[0])
; | |
185 } | |
186 | |
187 //Todo - Create a file and add it to the
Project | |
188 this.UpdateGeneratedCodeFile(fileNode, d
ata, (int)outPutSize, dependentNodeName); | |
189 } | |
190 } | |
191 finally | |
192 { | |
193 this.runningGenerator = false; | |
194 } | |
195 } | |
196 | |
197 /// <summary> | |
198 /// Computes the names space based on the folder for the Project
Item. It just replaces DirectorySeparatorCharacter | |
199 /// with "." for the directory in which the file is located. | |
200 /// </summary> | |
201 /// <returns>Returns the computed name space</returns> | |
202 protected virtual string ComputeNamespace(string projectItemPath
) | |
203 { | |
204 if(String.IsNullOrEmpty(projectItemPath)) | |
205 { | |
206 throw new ArgumentException(SR.GetString(SR.Para
meterCannotBeNullOrEmpty, CultureInfo.CurrentUICulture), "projectItemPath"); | |
207 } | |
208 | |
209 | |
210 string nspace = ""; | |
211 string filePath = Path.GetDirectoryName(projectItemPath)
; | |
212 string[] toks = filePath.Split(new char[] { ':', '\\' })
; | |
213 foreach(string tok in toks) | |
214 { | |
215 if(!String.IsNullOrEmpty(tok)) | |
216 { | |
217 string temp = tok.Replace(" ", ""); | |
218 nspace += (temp + "."); | |
219 } | |
220 } | |
221 nspace = nspace.Remove(nspace.LastIndexOf(".", StringCom
parison.Ordinal), 1); | |
222 return nspace; | |
223 } | |
224 | |
225 /// <summary> | |
226 /// This is called after the single file generator has been invo
ked to create or update the code file. | |
227 /// </summary> | |
228 /// <param name="fileNode">The node associated to the generator<
/param> | |
229 /// <param name="data">data to update the file with</param> | |
230 /// <param name="size">size of the data</param> | |
231 /// <param name="fileName">Name of the file to update or create<
/param> | |
232 /// <returns>full path of the file</returns> | |
233 protected virtual string UpdateGeneratedCodeFile(FileNode fileNo
de, byte[] data, int size, string fileName) | |
234 { | |
235 string filePath = Path.Combine(Path.GetDirectoryName(fil
eNode.GetMkDocument()), fileName); | |
236 IVsRunningDocumentTable rdt = this.projectMgr.GetService
(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; | |
237 | |
238 // Shouldn't this be an InvalidOperationException instea
d with some not to annoying errormessage to the user? | |
239 if(rdt == null) | |
240 { | |
241 ErrorHandler.ThrowOnFailure(VSConstants.E_FAIL); | |
242 } | |
243 | |
244 IVsHierarchy hier; | |
245 uint cookie; | |
246 uint itemid; | |
247 IntPtr docData = IntPtr.Zero; | |
248 ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocument((uin
t)(_VSRDTFLAGS.RDT_NoLock), filePath, out hier, out itemid, out docData, out coo
kie)); | |
249 if(docData != IntPtr.Zero) | |
250 { | |
251 Marshal.Release(docData); | |
252 IVsTextStream srpStream = null; | |
253 if(srpStream != null) | |
254 { | |
255 int oldLen = 0; | |
256 int hr = srpStream.GetSize(out oldLen); | |
257 if(ErrorHandler.Succeeded(hr)) | |
258 { | |
259 IntPtr dest = IntPtr.Zero; | |
260 try | |
261 { | |
262 dest = Marshal.AllocCoTa
skMem(data.Length); | |
263 Marshal.Copy(data, 0, de
st, data.Length); | |
264 ErrorHandler.ThrowOnFail
ure(srpStream.ReplaceStream(0, oldLen, dest, size / 2)); | |
265 } | |
266 finally | |
267 { | |
268 if(dest != IntPtr.Zero) | |
269 { | |
270 Marshal.Release(
dest); | |
271 } | |
272 } | |
273 } | |
274 } | |
275 } | |
276 else | |
277 { | |
278 using(FileStream generatedFileStream = File.Open
(filePath, FileMode.OpenOrCreate)) | |
279 { | |
280 generatedFileStream.Write(data, 0, size)
; | |
281 } | |
282 | |
283 EnvDTE.ProjectItem projectItem = fileNode.GetAut
omationObject() as EnvDTE.ProjectItem; | |
284 if(projectItem != null && (this.projectMgr.FindC
hild(fileNode.FileName) == null)) | |
285 { | |
286 projectItem.ProjectItems.AddFromFile(fil
ePath); | |
287 } | |
288 } | |
289 return filePath; | |
290 } | |
291 #endregion | |
292 | |
293 #region helpers | |
294 /// <summary> | |
295 /// Returns the buffer contents for a moniker. | |
296 /// </summary> | |
297 /// <returns>Buffer contents</returns> | |
298 private string GetBufferContents(string fileName, out IVsTextStr
eam srpStream) | |
299 { | |
300 Guid CLSID_VsTextBuffer = new Guid("{8E7B96A8-E33D-11d0-
A6D5-00C04FB67F6A}"); | |
301 string bufferContents = ""; | |
302 srpStream = null; | |
303 | |
304 IVsRunningDocumentTable rdt = this.projectMgr.GetService
(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; | |
305 if(rdt != null) | |
306 { | |
307 IVsHierarchy hier; | |
308 IVsPersistDocData persistDocData; | |
309 uint itemid, cookie; | |
310 bool docInRdt = true; | |
311 IntPtr docData = IntPtr.Zero; | |
312 int hr = NativeMethods.E_FAIL; | |
313 try | |
314 { | |
315 //Getting a read lock on the document. M
ust be released later. | |
316 hr = rdt.FindAndLockDocument((uint)_VSRD
TFLAGS.RDT_ReadLock, fileName, out hier, out itemid, out docData, out cookie); | |
317 if(ErrorHandler.Failed(hr) || docData ==
IntPtr.Zero) | |
318 { | |
319 Guid iid = VSConstants.IID_IUnkn
own; | |
320 cookie = 0; | |
321 docInRdt = false; | |
322 ILocalRegistry localReg = this.p
rojectMgr.GetService(typeof(SLocalRegistry)) as ILocalRegistry; | |
323 ErrorHandler.ThrowOnFailure(loca
lReg.CreateInstance(CLSID_VsTextBuffer, null, ref iid, (uint)CLSCTX.CLSCTX_INPRO
C_SERVER, out docData)); | |
324 } | |
325 | |
326 persistDocData = Marshal.GetObjectForIUn
known(docData) as IVsPersistDocData; | |
327 } | |
328 finally | |
329 { | |
330 if(docData != IntPtr.Zero) | |
331 { | |
332 Marshal.Release(docData); | |
333 } | |
334 } | |
335 | |
336 //Try to get the Text lines | |
337 IVsTextLines srpTextLines = persistDocData as IV
sTextLines; | |
338 if(srpTextLines == null) | |
339 { | |
340 // Try getting a text buffer provider fi
rst | |
341 IVsTextBufferProvider srpTextBufferProvi
der = persistDocData as IVsTextBufferProvider; | |
342 if(srpTextBufferProvider != null) | |
343 { | |
344 hr = srpTextBufferProvider.GetTe
xtBuffer(out srpTextLines); | |
345 } | |
346 } | |
347 | |
348 if(ErrorHandler.Succeeded(hr)) | |
349 { | |
350 srpStream = srpTextLines as IVsTextStrea
m; | |
351 if(srpStream != null) | |
352 { | |
353 // QI for IVsBatchUpdate and cal
l FlushPendingUpdates if they support it | |
354 IVsBatchUpdate srpBatchUpdate =
srpStream as IVsBatchUpdate; | |
355 if(srpBatchUpdate != null) | |
356 ErrorHandler.ThrowOnFail
ure(srpBatchUpdate.FlushPendingUpdates(0)); | |
357 | |
358 int lBufferSize = 0; | |
359 hr = srpStream.GetSize(out lBuff
erSize); | |
360 | |
361 if(ErrorHandler.Succeeded(hr)) | |
362 { | |
363 IntPtr dest = IntPtr.Zer
o; | |
364 try | |
365 { | |
366 // Note that Get
Stream returns Unicode to us so we don't need to do any conversions | |
367 dest = Marshal.A
llocCoTaskMem((lBufferSize + 1) * 2); | |
368 ErrorHandler.Thr
owOnFailure(srpStream.GetStream(0, lBufferSize, dest)); | |
369 //Get the conten
ts | |
370 bufferContents =
Marshal.PtrToStringUni(dest); | |
371 } | |
372 finally | |
373 { | |
374 if(dest != IntPt
r.Zero) | |
375 Marshal.
FreeCoTaskMem(dest); | |
376 } | |
377 } | |
378 } | |
379 | |
380 } | |
381 // Unlock the document in the RDT if necessary | |
382 if(docInRdt && rdt != null) | |
383 { | |
384 ErrorHandler.ThrowOnFailure(rdt.UnlockDo
cument((uint)(_VSRDTFLAGS.RDT_ReadLock | _VSRDTFLAGS.RDT_Unlock_NoSave), cookie)
); | |
385 } | |
386 | |
387 if(ErrorHandler.Failed(hr)) | |
388 { | |
389 // If this failed then it's probably not
a text file. In that case, | |
390 // we just read the file as a binary | |
391 bufferContents = File.ReadAllText(fileNa
me); | |
392 } | |
393 | |
394 | |
395 } | |
396 return bufferContents; | |
397 } | |
398 | |
399 /// <summary> | |
400 /// Returns TRUE if open and dirty. Note that documents can be o
pen without a | |
401 /// window frame so be careful. Returns the DocData and doc cook
ie if requested | |
402 /// </summary> | |
403 /// <param name="document">document path</param> | |
404 /// <param name="pHier">hierarchy</param> | |
405 /// <param name="ppDocData">doc data associated with document</p
aram> | |
406 /// <param name="cookie">item cookie</param> | |
407 /// <returns>True if FIle is dirty</returns> | |
408 private bool VerifyFileDirtyInRdt(string document, out IVsHierar
chy pHier, out IVsPersistDocData ppDocData, out uint cookie) | |
409 { | |
410 int ret = 0; | |
411 pHier = null; | |
412 ppDocData = null; | |
413 cookie = 0; | |
414 | |
415 IVsRunningDocumentTable rdt = this.projectMgr.GetService
(typeof(IVsRunningDocumentTable)) as IVsRunningDocumentTable; | |
416 if(rdt != null) | |
417 { | |
418 IntPtr docData; | |
419 uint dwCookie = 0; | |
420 IVsHierarchy srpHier; | |
421 uint itemid = VSConstants.VSITEMID_NIL; | |
422 | |
423 ErrorHandler.ThrowOnFailure(rdt.FindAndLockDocum
ent((uint)_VSRDTFLAGS.RDT_NoLock, document, out srpHier, out itemid, out docData
, out dwCookie)); | |
424 IVsPersistHierarchyItem srpIVsPersistHierarchyIt
em = srpHier as IVsPersistHierarchyItem; | |
425 if(srpIVsPersistHierarchyItem != null) | |
426 { | |
427 // Found in the RDT. See if it is dirty | |
428 try | |
429 { | |
430 ErrorHandler.ThrowOnFailure(srpI
VsPersistHierarchyItem.IsItemDirty(itemid, docData, out ret)); | |
431 cookie = dwCookie; | |
432 ppDocData = Marshal.GetObjectFor
IUnknown(docData) as IVsPersistDocData; | |
433 } | |
434 finally | |
435 { | |
436 if(docData != IntPtr.Zero) | |
437 { | |
438 Marshal.Release(docData)
; | |
439 } | |
440 | |
441 pHier = srpHier; | |
442 } | |
443 } | |
444 } | |
445 return (ret == 1); | |
446 } | |
447 #endregion | |
448 | |
449 | |
450 | |
451 | |
452 #region QueryEditQuerySave helpers | |
453 /// <summary> | |
454 /// This function asks to the QueryEditQuerySave service if it i
s possible to | |
455 /// edit the file. | |
456 /// </summary> | |
457 private bool CanEditFile(string documentMoniker) | |
458 { | |
459 Trace.WriteLine(string.Format(CultureInfo.CurrentCulture
, "\t**** CanEditFile called ****")); | |
460 | |
461 // Check the status of the recursion guard | |
462 if(this.gettingCheckoutStatus) | |
463 { | |
464 return false; | |
465 } | |
466 | |
467 try | |
468 { | |
469 // Set the recursion guard | |
470 this.gettingCheckoutStatus = true; | |
471 | |
472 // Get the QueryEditQuerySave service | |
473 IVsQueryEditQuerySave2 queryEditQuerySave = (IVs
QueryEditQuerySave2)this.projectMgr.GetService(typeof(SVsQueryEditQuerySave)); | |
474 | |
475 // Now call the QueryEdit method to find the edi
t status of this file | |
476 string[] documents = { documentMoniker }; | |
477 uint result; | |
478 uint outFlags; | |
479 | |
480 // Note that this function can popup a dialog to
ask the user to checkout the file. | |
481 // When this dialog is visible, it is possible t
o receive other request to change | |
482 // the file and this is the reason for the recur
sion guard. | |
483 int hr = queryEditQuerySave.QueryEditFiles( | |
484 0, // Flags | |
485 1, // Number of elements in
the array | |
486 documents, // Files to edit | |
487 null, // Input flags | |
488 null, // Input array of VSQEQS
_FILE_ATTRIBUTE_DATA | |
489 out result, // result of the checkou
t | |
490 out outFlags // Additional flags | |
491 ); | |
492 | |
493 if(ErrorHandler.Succeeded(hr) && (result == (uin
t)tagVSQueryEditResult.QER_EditOK)) | |
494 { | |
495 // In this case (and only in this case)
we can return true from this function. | |
496 return true; | |
497 } | |
498 } | |
499 finally | |
500 { | |
501 this.gettingCheckoutStatus = false; | |
502 } | |
503 | |
504 return false; | |
505 } | |
506 #endregion | |
507 } | |
508 } | |
OLD | NEW |