├── .gitattributes
├── ClassLibrary
├── ClassLibrary.csproj
├── Config.cs
├── P4Command.cs
├── SolutionConfigForm.Designer.cs
├── SolutionConfigForm.cs
└── SolutionConfigForm.resx
├── LICENSE.txt
├── P4SimpleScc.sln
├── P4SimpleScc_2019
├── AsyncPackageHelpers
│ ├── AsyncPackageRegistrationAttribute.cs
│ ├── ExtensionMethods.cs
│ └── ProvideAutoLoadAttribute.cs
├── P4SimpleScc_2019.csproj
├── Properties
│ └── AssemblyInfo.cs
└── source.extension.vsixmanifest
├── P4SimpleScc_2022
├── AsyncPackageHelpers
│ ├── AsyncPackageRegistrationAttribute.cs
│ ├── ExtensionMethods.cs
│ └── ProvideAutoLoadAttribute.cs
├── P4SimpleScc_2022.csproj
├── Properties
│ └── AssemblyInfo.cs
└── source.extension.vsixmanifest
├── README.md
├── ReadMe
└── ReadMe.txt
├── SharedFiles
├── P4SimpleSccPackage.vsct
└── Resources
│ └── Command.png
├── SharedProject
├── ActionProviderFactory.cs
├── FileContextProviderFactory.cs
├── Guids.cs
├── ProvideSolutionProps.cs
├── ProvideSourceControlProvider.cs
├── SccProvider.cs
├── SccProviderService.cs
├── SharedProject.projitems
└── SharedProject.shproj
└── img
├── CheckOutDocument.png
├── CheckOutFile.png
├── FileCheckedOut.png
├── OutputWindow.png
├── SolutionConfigurationDialog.png
├── SolutionConfigurationMenu.png
└── Tools_Options_SourceControl.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/ClassLibrary/ClassLibrary.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}
8 | Library
9 | ClassLibrary
10 | ClassLibrary
11 | v4.7.2
12 | 512
13 | true
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Form
55 |
56 |
57 | SolutionConfigForm.cs
58 |
59 |
60 |
61 |
62 | SolutionConfigForm.cs
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/ClassLibrary/Config.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2022 - Jeffrey "botman" Broome
4 | //
5 |
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 |
10 | using System.IO;
11 | using System.Drawing;
12 |
13 | namespace ClassLibrary
14 | {
15 | public static class Config
16 | {
17 | public enum KEY // everything is 'int' unless otherwise noted
18 | {
19 | SolutionConfigType,
20 | SolutionConfigCheckOutOnEdit, // bool
21 | SolutionConfigPromptForCheckout, // bool
22 | SolutionConfigDialogPosX,
23 | SolutionConfigDialogPosY,
24 | SolutionConfigDialogP4Port, // string
25 | SolutionConfigDialogP4User, // string
26 | SolutionConfigDialogP4Client, // string
27 | SolutionConfigVerboseOutput, // bool
28 | SolutionConfigOutputEnabled, // bool
29 | };
30 |
31 | private static Dictionary ConfigDictionary = new Dictionary();
32 |
33 | public static void Load(string InputConfig)
34 | {
35 | ConfigDictionary = new Dictionary();
36 |
37 | string line;
38 |
39 | try
40 | {
41 | byte[] buffer = new byte[InputConfig.Length];
42 | System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
43 | encoding.GetBytes(InputConfig, 0, InputConfig.Length, buffer, 0);
44 |
45 | MemoryStream memory_stream = new MemoryStream(buffer);
46 |
47 | using (StreamReader sr = new StreamReader(memory_stream))
48 | {
49 | while (sr.Peek() >= 0)
50 | {
51 | line = sr.ReadLine().Trim();
52 |
53 | if (line.Length > 0) // not a blank line?
54 | {
55 | int pos = line.IndexOf("=");
56 |
57 | if ((pos > 0) && (pos < (line.Length-1))) // '=' must not be the first or last character of the line
58 | {
59 | string key = line.Substring(0, pos);
60 | string value = line.Substring(pos+1, (line.Length-pos)-1);
61 |
62 | ConfigDictionary.Add(key, value);
63 | }
64 | }
65 | }
66 | }
67 | }
68 | catch(Exception)
69 | {
70 | }
71 | }
72 |
73 | public static void Save(out string OutputString)
74 | {
75 | OutputString = "";
76 |
77 | try
78 | {
79 | foreach (KEY key in Enum.GetValues(typeof(KEY)))
80 | {
81 | string key_string = key.ToString();
82 | if (ConfigDictionary.ContainsKey(key_string))
83 | {
84 | OutputString += string.Format("{0}={1}", key_string, ConfigDictionary[key_string]) + "\n";
85 | }
86 | }
87 | }
88 | catch(Exception)
89 | {
90 | }
91 | }
92 |
93 | public static bool Get(KEY key, ref string value)
94 | {
95 | string key_string = key.ToString();
96 | if (ConfigDictionary.ContainsKey(key_string))
97 | {
98 | value = ConfigDictionary[key_string];
99 | return true;
100 | }
101 | return false;
102 | }
103 |
104 | public static void Set(KEY key, string value)
105 | {
106 | string key_string = key.ToString();
107 |
108 | if (ConfigDictionary.ContainsKey(key_string)) // if the key/value pair exists...
109 | {
110 | ConfigDictionary.Remove(key_string); // ...remove the old key/value pair
111 | }
112 |
113 | ConfigDictionary.Add(key_string, value); // add the key/value pair to the dictionary
114 | }
115 |
116 |
117 | public static bool Get(KEY key, ref int value)
118 | {
119 | string key_string = key.ToString();
120 | if (ConfigDictionary.ContainsKey(key_string))
121 | {
122 | value = 0;
123 | Int32.TryParse(ConfigDictionary[key_string], out value);
124 | return true;
125 | }
126 | return false;
127 | }
128 |
129 | public static void Set(KEY key, int value)
130 | {
131 | Set(key, value.ToString());
132 | }
133 |
134 |
135 | public static bool Get(KEY key, ref bool value)
136 | {
137 | string key_string = key.ToString();
138 | if (ConfigDictionary.ContainsKey(key_string))
139 | {
140 | value = ConfigDictionary[key_string] == "True";
141 | return true;
142 | }
143 | return false;
144 | }
145 |
146 | public static void Set(KEY key, bool value)
147 | {
148 | Set(key, value.ToString()); // set to 'False' or 'True'
149 | }
150 |
151 |
152 | public static bool Get(KEY key, ref List value)
153 | {
154 | value = new List();
155 |
156 | int count = 1;
157 | bool found = false;
158 | do
159 | {
160 | string key_string = string.Format("{0}{1}", key.ToString(), count);
161 | found = ConfigDictionary.ContainsKey(key_string);
162 | if (found)
163 | {
164 | value.Add(ConfigDictionary[key_string]);
165 | }
166 | count++;
167 | } while(found);
168 |
169 | return (value.Count() > 0);
170 | }
171 |
172 | public static void Set(KEY key, List StringList)
173 | {
174 | int count = 1;
175 | bool found = false;
176 | // remove ALL old key/value pairs from the dictionary (since the list parameter passed in can be different size than what's currently in dictionary)
177 | do
178 | {
179 | string key_string = string.Format("{0}{1}", key.ToString(), count);
180 | found = ConfigDictionary.ContainsKey(key_string);
181 | if (found)
182 | {
183 | ConfigDictionary.Remove(key_string);
184 | }
185 | count++;
186 | } while(found);
187 |
188 | // add the new key/value pairs to the dictionary
189 | for (int index = 0; index < StringList.Count; index++)
190 | {
191 | string key_string = string.Format("{0}{1}", key.ToString(), index+1);
192 | ConfigDictionary.Add(key_string, StringList[index]);
193 | }
194 | }
195 |
196 |
197 | public static bool Get(KEY key, ref List value)
198 | {
199 | value = new List();
200 |
201 | List StringList = new List();
202 |
203 | Get(key, ref StringList);
204 |
205 | for(int index = 0; index < StringList.Count; index++)
206 | {
207 | value.Add(Convert.ToInt32(StringList[index]));
208 | }
209 |
210 | return (value.Count() > 0);
211 | }
212 |
213 | public static void Set(KEY key, List IntList)
214 | {
215 | List StringList = new List();
216 |
217 | for(int index = 0; index < IntList.Count; index++)
218 | {
219 | StringList.Add(IntList[index].ToString());
220 | }
221 |
222 | Set(key, StringList);
223 | }
224 |
225 |
226 | public static bool Get(KEY key, ref Font value)
227 | {
228 | string FontString = "";
229 | if (Get(key, ref FontString))
230 | {
231 | string[] fields = FontString.Split(',');
232 |
233 | if (fields.Length == 4)
234 | {
235 | if ((fields[0].Substring(0,1) == "\"") && (fields[0].Substring(fields[0].Length - 1,1) == "\""))
236 | {
237 | fields[0] = fields[0].Substring(1, fields[0].Length - 2); // chop off the leading and trailing double quote
238 | }
239 |
240 | FontFamily font_family = new FontFamily(fields[0]);
241 |
242 | float size = float.Parse(fields[1]);
243 | int int_style = int.Parse(fields[2]);
244 |
245 | FontStyle style = FontStyle.Regular;
246 | if ((int_style & 1) != 0)
247 | {
248 | style = style | FontStyle.Bold;
249 | }
250 | if ((int_style & 2) != 0)
251 | {
252 | style = style | FontStyle.Italic;
253 | }
254 |
255 | GraphicsUnit graphics_unit = (GraphicsUnit) Enum.Parse(typeof(GraphicsUnit), fields[3]);
256 |
257 | value = new Font(font_family, size, style, graphics_unit);
258 | return true;
259 | }
260 | }
261 |
262 | return false;
263 | }
264 |
265 | public static void Set(KEY key, Font value)
266 | {
267 | int Style = 0; // Regular
268 | if (value.Style.HasFlag(FontStyle.Bold))
269 | {
270 | Style |= 1;
271 | }
272 | if (value.Style.HasFlag(FontStyle.Italic))
273 | {
274 | Style |= 2;
275 | }
276 |
277 | int GraphicsUnit = (int)value.Unit;
278 |
279 | Set(key, String.Format("\"{0}\",{1},{2},{3}", value.FontFamily.Name, value.SizeInPoints, Style, GraphicsUnit));
280 | }
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/ClassLibrary/P4Command.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2022 - Jeffrey "botman" Broome
4 | //
5 |
6 | using System;
7 | using System.Text;
8 |
9 | using System.Diagnostics;
10 | using System.Threading;
11 | using System.IO;
12 |
13 | namespace ClassLibrary
14 | {
15 | public class P4Command
16 | {
17 | private static string port;
18 | private static string user;
19 | private static string workspace;
20 |
21 | private StringBuilder stdoutBuilder;
22 | private StringBuilder stderrBuilder;
23 | private StringBuilder verboseBuilder;
24 |
25 | private bool StdOutDone; // wait until OnOutputDataReceived receives e.Data == null to know that stdout has terminated
26 | private bool StdErrDone; // wait until OnErrorDataReceived receives e.Data == null to know that stderr has terminated
27 |
28 | public enum CheckOutStatus
29 | {
30 | FileCheckedOut, // the file was successfully checked out of source control
31 | FileAlreadyCheckedOut, // the file was already checked out of source control
32 | FileNotInSourceControl, // the file does not exist in source control
33 | ErrorCheckingOutFile, // the file could not be checked out of source control
34 | }
35 |
36 | public P4Command()
37 | {
38 | }
39 |
40 | public static void SetEnv(string in_port, string in_user, string in_workspace) // set the default port, user and workspace
41 | {
42 | port = in_port;
43 | user = in_user;
44 | workspace = in_workspace;
45 | }
46 |
47 | public void Run(string command, out string stdout, out string stderr, out string verbose) // use the current port, user and workspace for the command
48 | {
49 | Run(command, port, user, workspace, out stdout, out stderr, out verbose);
50 | }
51 |
52 | public void Run(string command, string in_port, string in_user, string in_workspace, out string stdout, out string stderr, out string verbose) // use the specified port, user and workspace for the command
53 | {
54 | stdoutBuilder = new StringBuilder();
55 | stderrBuilder = new StringBuilder();
56 | verboseBuilder = new StringBuilder();
57 |
58 | bool bTimedOut = false;
59 |
60 | try
61 | {
62 | Process proc = new Process();
63 |
64 | StdOutDone = false;
65 | StdErrDone = false;
66 |
67 | ProcessStartInfo startInfo = new ProcessStartInfo("cmd.exe");
68 |
69 | string arguments = "p4.exe";
70 |
71 | if (in_port != null && in_port.Length > 0)
72 | {
73 | arguments += String.Format(" -p {0}", in_port);
74 | }
75 |
76 | if (in_user != null && in_user.Length > 0)
77 | {
78 | arguments += String.Format(" -u {0}", in_user);
79 | }
80 |
81 | if (in_workspace != null && in_workspace.Length > 0)
82 | {
83 | arguments += String.Format(" -c {0}", in_workspace);
84 | }
85 |
86 | startInfo.UseShellExecute = false;
87 | startInfo.CreateNoWindow = true;
88 |
89 | startInfo.RedirectStandardInput = false;
90 | startInfo.RedirectStandardOutput = true;
91 | startInfo.RedirectStandardError = true;
92 |
93 | startInfo.Arguments = "/C " + arguments + " " + command;
94 | verboseBuilder.Append("command: " + arguments + " " + command + "\n");
95 |
96 | proc.StartInfo = startInfo;
97 | proc.EnableRaisingEvents = true;
98 |
99 | proc.OutputDataReceived += OnOutputDataReceived;
100 | proc.ErrorDataReceived += OnErrorDataReceived;
101 |
102 | proc.Start();
103 | proc.BeginOutputReadLine();
104 | proc.BeginErrorReadLine();
105 |
106 | if (!proc.WaitForExit(5 * 60 * 1000)) // wait with a 5 minute timeout (in milliseconds)
107 | {
108 | bTimedOut = true;
109 | proc.Kill();
110 | }
111 |
112 | int output_timeout = 100;
113 | while (!StdOutDone || !StdErrDone) // wait until the output and error streams have been flushed (or a 1 second (1000 ms) timeout is reached)
114 | {
115 | Thread.Sleep(10);
116 |
117 | if (--output_timeout == 0)
118 | {
119 | break;
120 | }
121 | }
122 |
123 | proc.Close();
124 | }
125 | catch (Exception ex)
126 | {
127 | string message = String.Format("P4Command.Run exception: {0}", ex.Message);
128 | System.Console.WriteLine(message);
129 | }
130 |
131 | stdout = stdoutBuilder.ToString();
132 | stderr = stderrBuilder.ToString();
133 |
134 | verboseBuilder.Append("response: " + stdout);
135 | if (!stdout.EndsWith("\n"))
136 | {
137 | verboseBuilder.Append("\n");
138 | }
139 |
140 | verboseBuilder.Append("error: " + stderr);
141 | if (!stderr.EndsWith("\n"))
142 | {
143 | verboseBuilder.Append("\n");
144 | }
145 |
146 | verbose = verboseBuilder.ToString();
147 |
148 | if (bTimedOut) // did the P4 command take too long and time out?
149 | {
150 | stderr = "Perforce command timed out after waiting for 5 minutes. Check your Solution Configuration settings to make sure they are correct. You may want to try to manually check out or add the file in P4V.\n";
151 | }
152 | }
153 |
154 | private void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
155 | {
156 | if (e.Data != null)
157 | {
158 | stdoutBuilder.AppendLine(e.Data);
159 | }
160 | else
161 | {
162 | StdOutDone = true;
163 | }
164 | }
165 |
166 | private void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
167 | {
168 | if (e.Data != null)
169 | {
170 | stderrBuilder.AppendLine(e.Data);
171 | }
172 | else
173 | {
174 | StdErrDone = true;
175 | }
176 | }
177 |
178 | private bool GetField(string input, string fieldname, out string fieldvalue) // scans input looking for field and returns value of field if found
179 | {
180 | fieldvalue = "";
181 |
182 | if (fieldname == null || fieldname.Length == 0)
183 | {
184 | return false;
185 | }
186 |
187 | if (input == null || input.Length == 0)
188 | {
189 | return false;
190 | }
191 |
192 | string line;
193 |
194 | try
195 | {
196 | byte[] buffer = new byte[input.Length];
197 | System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
198 | encoding.GetBytes(input, 0, input.Length, buffer, 0);
199 |
200 | MemoryStream memory_stream = new MemoryStream(buffer);
201 |
202 | using (StreamReader sr = new StreamReader(memory_stream))
203 | {
204 | while (sr.Peek() >= 0)
205 | {
206 | line = sr.ReadLine().Trim();
207 |
208 | if (line.Length > 0) // not a blank line?
209 | {
210 | if (line[0] == '#')
211 | {
212 | continue; // skip comment lines
213 | }
214 |
215 | int pos = line.IndexOf(fieldname);
216 |
217 | if (pos == 0) // found a match?
218 | {
219 | fieldvalue = line.Substring(fieldname.Length, line.Length - fieldname.Length).Trim();
220 |
221 | return true;
222 | }
223 | }
224 | }
225 | }
226 | }
227 | catch(Exception)
228 | {
229 | }
230 |
231 | return false;
232 | }
233 |
234 | public void RunP4Set(string solutionDirectory, out string P4Port, out string P4User, out string P4Client, out string verbose)
235 | {
236 | string P4Config;
237 | RunP4Set(solutionDirectory, out P4Port, out P4User, out P4Client, out P4Config, out verbose);
238 | }
239 |
240 |
241 | public void RunP4Set(string solutionDirectory, out string P4Port, out string P4User, out string P4Client, out string P4Config, out string verbose)
242 | {
243 | P4Port = "";
244 | P4User = "";
245 | P4Client = "";
246 | P4Config = "";
247 | verbose = "";
248 |
249 | if (solutionDirectory == null || solutionDirectory == "")
250 | {
251 | return;
252 | }
253 |
254 | SetEnv(P4Port, P4User, P4Client);
255 |
256 | // the "p4.exe set" command must be run from the solution directory (to get the proper settings from the .p4config file)
257 | string CurrentDirectory = Directory.GetCurrentDirectory(); // save the current directory
258 | Directory.SetCurrentDirectory(solutionDirectory);
259 |
260 | P4Command p4cmd = new ClassLibrary.P4Command();
261 |
262 | p4cmd.Run("set", out string stdout, out string stderr, out verbose); // note: "p4 set" does not require a port, user, password or workspace.
263 |
264 | Directory.SetCurrentDirectory(CurrentDirectory); // restore the saved current directory
265 |
266 | if (stdout != null && stdout.Length > 0)
267 | {
268 | StringReader reader = new StringReader(stdout);
269 | string line;
270 | while ((line = reader.ReadLine()) != null)
271 | {
272 | if (line.Contains("P4PORT="))
273 | {
274 | int pos = line.IndexOf(" (");
275 | if (pos >= 0) // if the line contains " (" then strip it and the following text
276 | {
277 | line = line.Remove(pos).Trim();
278 | }
279 |
280 | P4Port = line.Substring(7);
281 | }
282 | else if (line.Contains("P4USER="))
283 | {
284 | int pos = line.IndexOf(" (");
285 | if (pos >= 0) // if the line contains " (" then strip it and the following text
286 | {
287 | line = line.Remove(pos).Trim();
288 | }
289 |
290 | P4User = line.Substring(7);
291 | }
292 | else if (line.Contains("P4CLIENT="))
293 | {
294 | int pos = line.IndexOf(" (");
295 | if (pos >= 0) // if the line contains " (" then strip it and the following text
296 | {
297 | line = line.Remove(pos).Trim();
298 | }
299 |
300 | P4Client = line.Substring(9);
301 | }
302 | else if (line.Contains("P4CONFIG="))
303 | {
304 | int pos = line.IndexOf(" (");
305 | if (pos >= 0) // if the line contains " (" then strip it and the following text
306 | {
307 | line = line.Remove(pos).Trim();
308 | }
309 |
310 | P4Config = line.Substring(9);
311 | }
312 | }
313 | }
314 | }
315 |
316 | public void ServerConnect(out string stdout, out string stderr, out string verbose, out bool bIsNotAllWrite)
317 | {
318 | verbose = "";
319 | bIsNotAllWrite = true;
320 |
321 | Run("info -s", port, "", "", out stdout, out stderr, out string info_verbose); // 'info' needs to run on the specified server (to make sure the server is valid)
322 | verbose += info_verbose;
323 |
324 | if (stderr != null && stderr.Length > 0)
325 | {
326 | return;
327 | }
328 |
329 | string command = String.Format("users {0}", user);
330 | Run(command, port, user, "", out stdout, out stderr, out string users_verbose); // get the user information from the specified server (to make sure the user is valid)
331 | verbose += users_verbose;
332 |
333 | if (stderr != null && stderr.Length > 0)
334 | {
335 | return;
336 | }
337 |
338 | command = String.Format("client -o {0}", workspace); // get the user's workspace from the specified server and then validate it
339 | Run(command, port, user, "", out stdout, out stderr, out string client_verbose);
340 | verbose += client_verbose;
341 |
342 | if (stderr != null && stderr.Length > 0)
343 | {
344 | return;
345 | }
346 |
347 | // Parse output looking for "Access:" and "Root:" to validate the workspace settings on this machine
348 | string Access = "";
349 | if (!GetField(stdout, "Access:", out Access))
350 | {
351 | stderr = String.Format("P4CLIENT workspace '{0}' is not valid on server.\n", workspace);
352 | return;
353 | }
354 |
355 | string Root = "";
356 | if (!GetField(stdout, "Root:", out Root))
357 | {
358 | stderr = String.Format("P4CLIENT workspace '{0}' is not valid on server.\n", workspace);
359 | return;
360 | }
361 |
362 | string Options = "";
363 | if (!GetField(stdout, "Options:", out Options))
364 | {
365 | stderr = String.Format("P4CLIENT workspace '{0}' is not valid on server.\n", workspace);
366 | return;
367 | }
368 |
369 | bIsNotAllWrite = Options.Contains("noallwrite") ? true : false;
370 |
371 | // check if Root directory exists on this machine
372 | if (!Directory.Exists(Root))
373 | {
374 | stderr = String.Format("workspace Root: folder '{0}' does not exist on this machine.\n", Root);
375 | return;
376 | }
377 | }
378 |
379 | public bool IsCheckedOut(string Filename, out string stdout, out string stderr, out string verbose)
380 | {
381 | // see if the file is checked out (for edit, not for integrate)
382 | string command = String.Format("fstat -T \"action\" \"{0}\"", Filename);
383 | Run(command, out stdout, out stderr, out verbose);
384 |
385 | if (stderr == null || stderr.Length == 0) // ignore errors here, if no error, check if file is open for edit
386 | {
387 | string action = "";
388 | GetField(stdout, "... action", out action);
389 |
390 | if (action == "edit") // if already open for edit then we don't need to do anything
391 | {
392 | return true;
393 | }
394 | else if (action == "add") // if open for add then we don't need to do anything (GitHub issue #4)
395 | {
396 | return true;
397 | }
398 | }
399 |
400 | return false;
401 | }
402 |
403 | public CheckOutStatus CheckOutFile(string Filename, out string stdout, out string stderr, out string verbose)
404 | {
405 | verbose = "";
406 |
407 | // see if the file exists in source control
408 | string command = String.Format("fstat -T \"clientFile\" \"{0}\"", Filename);
409 | Run(command, out stdout, out stderr, out string clientFile_verbose);
410 | verbose += clientFile_verbose;
411 |
412 | if (stderr != null && stderr.Length > 0)
413 | {
414 | if (stderr.Contains("is not under client's root") || stderr.Contains("not in client view") || stderr.Contains("no such file")) // if file is outside client's workspace, or file does not exist in source control...
415 | {
416 | return CheckOutStatus.FileNotInSourceControl;
417 | }
418 | else
419 | {
420 | return CheckOutStatus.ErrorCheckingOutFile;
421 | }
422 | }
423 |
424 | // see if the file is already checked out
425 | command = String.Format("fstat -T \"action\" \"{0}\"", Filename);
426 | Run(command, out stdout, out stderr, out string action_verbose);
427 | verbose += action_verbose;
428 |
429 | if (stderr == null || stderr.Length == 0) // ignore errors here, if no error, check if file is open for edit
430 | {
431 | string action = "";
432 | GetField(stdout, "... action", out action);
433 |
434 | if (action == "edit") // if already open for edit then we don't need to do anything
435 | {
436 | return CheckOutStatus.FileAlreadyCheckedOut;
437 | }
438 | }
439 |
440 | command = String.Format("edit \"{0}\"", Filename);
441 | Run(command, out stdout, out stderr, out string edit_verbose);
442 | verbose += edit_verbose;
443 |
444 | if (stderr != null && stderr.Length > 0)
445 | {
446 | return CheckOutStatus.ErrorCheckingOutFile;
447 | }
448 |
449 | return CheckOutStatus.FileCheckedOut;
450 | }
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/ClassLibrary/SolutionConfigForm.Designer.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace ClassLibrary
3 | {
4 | partial class SolutionConfigForm
5 | {
6 | ///
7 | /// Required designer variable.
8 | ///
9 | private System.ComponentModel.IContainer components = null;
10 |
11 | ///
12 | /// Clean up any resources being used.
13 | ///
14 | /// true if managed resources should be disposed; otherwise, false.
15 | protected override void Dispose(bool disposing)
16 | {
17 | if (disposing && (components != null))
18 | {
19 | components.Dispose();
20 | }
21 | base.Dispose(disposing);
22 | }
23 |
24 | #region Windows Form Designer generated code
25 |
26 | ///
27 | /// Required method for Designer support - do not modify
28 | /// the contents of this method with the code editor.
29 | ///
30 | private void InitializeComponent()
31 | {
32 | this.panel1 = new System.Windows.Forms.Panel();
33 | this.textBoxP4Client = new System.Windows.Forms.TextBox();
34 | this.textBoxP4User = new System.Windows.Forms.TextBox();
35 | this.textBoxP4Port = new System.Windows.Forms.TextBox();
36 | this.label7 = new System.Windows.Forms.Label();
37 | this.label6 = new System.Windows.Forms.Label();
38 | this.label5 = new System.Windows.Forms.Label();
39 | this.label4 = new System.Windows.Forms.Label();
40 | this.label3 = new System.Windows.Forms.Label();
41 | this.label2 = new System.Windows.Forms.Label();
42 | this.label1 = new System.Windows.Forms.Label();
43 | this.radioButtonManual = new System.Windows.Forms.RadioButton();
44 | this.radioButtonAutomatic = new System.Windows.Forms.RadioButton();
45 | this.radioButtonDisabled = new System.Windows.Forms.RadioButton();
46 | this.panel2 = new System.Windows.Forms.Panel();
47 | this.radioButtonOnSave = new System.Windows.Forms.RadioButton();
48 | this.radioButtonOnModify = new System.Windows.Forms.RadioButton();
49 | this.buttonOK = new System.Windows.Forms.Button();
50 | this.buttonCancel = new System.Windows.Forms.Button();
51 | this.checkBoxPromptForCheckout = new System.Windows.Forms.CheckBox();
52 | this.checkBoxVerboseOutput = new System.Windows.Forms.CheckBox();
53 | this.checkBoxOutputEnabled = new System.Windows.Forms.CheckBox();
54 | this.panel1.SuspendLayout();
55 | this.panel2.SuspendLayout();
56 | this.SuspendLayout();
57 | //
58 | // panel1
59 | //
60 | this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
61 | this.panel1.Controls.Add(this.textBoxP4Client);
62 | this.panel1.Controls.Add(this.textBoxP4User);
63 | this.panel1.Controls.Add(this.textBoxP4Port);
64 | this.panel1.Controls.Add(this.label7);
65 | this.panel1.Controls.Add(this.label6);
66 | this.panel1.Controls.Add(this.label5);
67 | this.panel1.Controls.Add(this.label4);
68 | this.panel1.Controls.Add(this.label3);
69 | this.panel1.Controls.Add(this.label2);
70 | this.panel1.Controls.Add(this.label1);
71 | this.panel1.Controls.Add(this.radioButtonManual);
72 | this.panel1.Controls.Add(this.radioButtonAutomatic);
73 | this.panel1.Controls.Add(this.radioButtonDisabled);
74 | this.panel1.Location = new System.Drawing.Point(12, 12);
75 | this.panel1.Name = "panel1";
76 | this.panel1.Size = new System.Drawing.Size(506, 261);
77 | this.panel1.TabIndex = 0;
78 | //
79 | // textBoxP4Client
80 | //
81 | this.textBoxP4Client.Location = new System.Drawing.Point(76, 223);
82 | this.textBoxP4Client.Name = "textBoxP4Client";
83 | this.textBoxP4Client.ReadOnly = true;
84 | this.textBoxP4Client.Size = new System.Drawing.Size(416, 20);
85 | this.textBoxP4Client.TabIndex = 5;
86 | //
87 | // textBoxP4User
88 | //
89 | this.textBoxP4User.Location = new System.Drawing.Point(76, 197);
90 | this.textBoxP4User.Name = "textBoxP4User";
91 | this.textBoxP4User.ReadOnly = true;
92 | this.textBoxP4User.Size = new System.Drawing.Size(416, 20);
93 | this.textBoxP4User.TabIndex = 4;
94 | //
95 | // textBoxP4Port
96 | //
97 | this.textBoxP4Port.Location = new System.Drawing.Point(76, 171);
98 | this.textBoxP4Port.Name = "textBoxP4Port";
99 | this.textBoxP4Port.ReadOnly = true;
100 | this.textBoxP4Port.Size = new System.Drawing.Size(416, 20);
101 | this.textBoxP4Port.TabIndex = 3;
102 | //
103 | // label7
104 | //
105 | this.label7.AutoSize = true;
106 | this.label7.Location = new System.Drawing.Point(9, 226);
107 | this.label7.Name = "label7";
108 | this.label7.Size = new System.Drawing.Size(61, 13);
109 | this.label7.TabIndex = 12;
110 | this.label7.Text = "P4CLIENT:";
111 | //
112 | // label6
113 | //
114 | this.label6.AutoSize = true;
115 | this.label6.Location = new System.Drawing.Point(9, 200);
116 | this.label6.Name = "label6";
117 | this.label6.Size = new System.Drawing.Size(53, 13);
118 | this.label6.TabIndex = 11;
119 | this.label6.Text = "P4USER:";
120 | //
121 | // label5
122 | //
123 | this.label5.AutoSize = true;
124 | this.label5.Location = new System.Drawing.Point(9, 174);
125 | this.label5.Name = "label5";
126 | this.label5.Size = new System.Drawing.Size(53, 13);
127 | this.label5.TabIndex = 10;
128 | this.label5.Text = "P4PORT:";
129 | //
130 | // label4
131 | //
132 | this.label4.AutoSize = true;
133 | this.label4.Location = new System.Drawing.Point(9, 140);
134 | this.label4.Name = "label4";
135 | this.label4.Size = new System.Drawing.Size(306, 13);
136 | this.label4.TabIndex = 9;
137 | this.label4.Text = "Enter the P4PORT, P4USER and P4CLIENT settings manually.";
138 | //
139 | // label3
140 | //
141 | this.label3.AutoSize = true;
142 | this.label3.Location = new System.Drawing.Point(9, 94);
143 | this.label3.Name = "label3";
144 | this.label3.Size = new System.Drawing.Size(382, 13);
145 | this.label3.TabIndex = 8;
146 | this.label3.Text = "environment, or from previous \'p4 set\' settings, or from the .p4config file setti" +
147 | "ngs.";
148 | //
149 | // label2
150 | //
151 | this.label2.AutoSize = true;
152 | this.label2.Location = new System.Drawing.Point(9, 78);
153 | this.label2.Name = "label2";
154 | this.label2.Size = new System.Drawing.Size(394, 13);
155 | this.label2.TabIndex = 7;
156 | this.label2.Text = "Uses \'p4 set\' to get P4PORT, P4USER and P4CLIENT settings from the Windows";
157 | //
158 | // label1
159 | //
160 | this.label1.AutoSize = true;
161 | this.label1.Location = new System.Drawing.Point(9, 32);
162 | this.label1.Name = "label1";
163 | this.label1.Size = new System.Drawing.Size(184, 13);
164 | this.label1.TabIndex = 6;
165 | this.label1.Text = "Disable P4SimpleScc for this solution.";
166 | //
167 | // radioButtonManual
168 | //
169 | this.radioButtonManual.AutoSize = true;
170 | this.radioButtonManual.Location = new System.Drawing.Point(12, 120);
171 | this.radioButtonManual.Name = "radioButtonManual";
172 | this.radioButtonManual.Size = new System.Drawing.Size(101, 17);
173 | this.radioButtonManual.TabIndex = 2;
174 | this.radioButtonManual.Text = "Manual Settings";
175 | this.radioButtonManual.UseVisualStyleBackColor = true;
176 | this.radioButtonManual.CheckedChanged += new System.EventHandler(this.radioButtonManual_CheckedChanged);
177 | //
178 | // radioButtonAutomatic
179 | //
180 | this.radioButtonAutomatic.AutoSize = true;
181 | this.radioButtonAutomatic.Location = new System.Drawing.Point(12, 58);
182 | this.radioButtonAutomatic.Name = "radioButtonAutomatic";
183 | this.radioButtonAutomatic.Size = new System.Drawing.Size(72, 17);
184 | this.radioButtonAutomatic.TabIndex = 1;
185 | this.radioButtonAutomatic.Text = "Automatic";
186 | this.radioButtonAutomatic.UseVisualStyleBackColor = true;
187 | this.radioButtonAutomatic.CheckedChanged += new System.EventHandler(this.radioButtonAutomatic_CheckedChanged);
188 | //
189 | // radioButtonDisabled
190 | //
191 | this.radioButtonDisabled.AutoSize = true;
192 | this.radioButtonDisabled.Location = new System.Drawing.Point(12, 12);
193 | this.radioButtonDisabled.Name = "radioButtonDisabled";
194 | this.radioButtonDisabled.Size = new System.Drawing.Size(66, 17);
195 | this.radioButtonDisabled.TabIndex = 0;
196 | this.radioButtonDisabled.TabStop = true;
197 | this.radioButtonDisabled.Text = "Disabled";
198 | this.radioButtonDisabled.UseVisualStyleBackColor = true;
199 | this.radioButtonDisabled.CheckedChanged += new System.EventHandler(this.radioButtonDisabled_CheckedChanged);
200 | //
201 | // panel2
202 | //
203 | this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
204 | this.panel2.Controls.Add(this.radioButtonOnSave);
205 | this.panel2.Controls.Add(this.radioButtonOnModify);
206 | this.panel2.Location = new System.Drawing.Point(12, 288);
207 | this.panel2.Name = "panel2";
208 | this.panel2.Size = new System.Drawing.Size(506, 66);
209 | this.panel2.TabIndex = 1;
210 | //
211 | // radioButtonOnSave
212 | //
213 | this.radioButtonOnSave.AutoSize = true;
214 | this.radioButtonOnSave.Location = new System.Drawing.Point(12, 35);
215 | this.radioButtonOnSave.Name = "radioButtonOnSave";
216 | this.radioButtonOnSave.Size = new System.Drawing.Size(136, 17);
217 | this.radioButtonOnSave.TabIndex = 1;
218 | this.radioButtonOnSave.Text = "Check out files on save";
219 | this.radioButtonOnSave.UseVisualStyleBackColor = true;
220 | this.radioButtonOnSave.CheckedChanged += new System.EventHandler(this.radioButtonOnSave_CheckedChanged);
221 | //
222 | // radioButtonOnModify
223 | //
224 | this.radioButtonOnModify.AutoSize = true;
225 | this.radioButtonOnModify.Location = new System.Drawing.Point(12, 12);
226 | this.radioButtonOnModify.Name = "radioButtonOnModify";
227 | this.radioButtonOnModify.Size = new System.Drawing.Size(143, 17);
228 | this.radioButtonOnModify.TabIndex = 0;
229 | this.radioButtonOnModify.TabStop = true;
230 | this.radioButtonOnModify.Text = "Check out files on modify";
231 | this.radioButtonOnModify.UseVisualStyleBackColor = true;
232 | this.radioButtonOnModify.CheckedChanged += new System.EventHandler(this.radioButtonOnModify_CheckedChanged);
233 | //
234 | // buttonOK
235 | //
236 | this.buttonOK.Location = new System.Drawing.Point(325, 422);
237 | this.buttonOK.Name = "buttonOK";
238 | this.buttonOK.Size = new System.Drawing.Size(79, 29);
239 | this.buttonOK.TabIndex = 4;
240 | this.buttonOK.Text = "OK";
241 | this.buttonOK.UseVisualStyleBackColor = true;
242 | this.buttonOK.Click += new System.EventHandler(this.buttonOK_Click);
243 | //
244 | // buttonCancel
245 | //
246 | this.buttonCancel.Location = new System.Drawing.Point(421, 422);
247 | this.buttonCancel.Name = "buttonCancel";
248 | this.buttonCancel.Size = new System.Drawing.Size(79, 29);
249 | this.buttonCancel.TabIndex = 5;
250 | this.buttonCancel.Text = "Cancel";
251 | this.buttonCancel.UseVisualStyleBackColor = true;
252 | this.buttonCancel.Click += new System.EventHandler(this.buttonCancel_Click);
253 | //
254 | // checkBoxPromptForCheckout
255 | //
256 | this.checkBoxPromptForCheckout.AutoSize = true;
257 | this.checkBoxPromptForCheckout.Location = new System.Drawing.Point(25, 369);
258 | this.checkBoxPromptForCheckout.Name = "checkBoxPromptForCheckout";
259 | this.checkBoxPromptForCheckout.Size = new System.Drawing.Size(248, 17);
260 | this.checkBoxPromptForCheckout.TabIndex = 2;
261 | this.checkBoxPromptForCheckout.Text = "Prompt for permission before checking out files.";
262 | this.checkBoxPromptForCheckout.UseVisualStyleBackColor = true;
263 | this.checkBoxPromptForCheckout.CheckedChanged += new System.EventHandler(this.checkBoxPromptForCheckout_CheckedChanged);
264 | //
265 | // checkBoxVerboseOutput
266 | //
267 | this.checkBoxVerboseOutput.AutoSize = true;
268 | this.checkBoxVerboseOutput.Location = new System.Drawing.Point(144, 399);
269 | this.checkBoxVerboseOutput.Name = "checkBoxVerboseOutput";
270 | this.checkBoxVerboseOutput.Size = new System.Drawing.Size(226, 17);
271 | this.checkBoxVerboseOutput.TabIndex = 3;
272 | this.checkBoxVerboseOutput.Text = "Verbose Output ( for debugging purposes )";
273 | this.checkBoxVerboseOutput.UseVisualStyleBackColor = true;
274 | this.checkBoxVerboseOutput.CheckedChanged += new System.EventHandler(this.checkBoxVerboseOutput_CheckedChanged);
275 | //
276 | // checkBoxOutputEnabled
277 | //
278 | this.checkBoxOutputEnabled.AutoSize = true;
279 | this.checkBoxOutputEnabled.Location = new System.Drawing.Point(25, 399);
280 | this.checkBoxOutputEnabled.Name = "checkBoxOutputEnabled";
281 | this.checkBoxOutputEnabled.Size = new System.Drawing.Size(100, 17);
282 | this.checkBoxOutputEnabled.TabIndex = 6;
283 | this.checkBoxOutputEnabled.Text = "Output Enabled";
284 | this.checkBoxOutputEnabled.UseVisualStyleBackColor = true;
285 | this.checkBoxOutputEnabled.CheckedChanged += new System.EventHandler(this.checkBoxOutputEnabled_CheckedChanged);
286 | //
287 | // SolutionConfigForm
288 | //
289 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
290 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
291 | this.ClientSize = new System.Drawing.Size(531, 469);
292 | this.Controls.Add(this.checkBoxOutputEnabled);
293 | this.Controls.Add(this.checkBoxVerboseOutput);
294 | this.Controls.Add(this.checkBoxPromptForCheckout);
295 | this.Controls.Add(this.buttonCancel);
296 | this.Controls.Add(this.buttonOK);
297 | this.Controls.Add(this.panel2);
298 | this.Controls.Add(this.panel1);
299 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
300 | this.MaximizeBox = false;
301 | this.MinimizeBox = false;
302 | this.Name = "SolutionConfigForm";
303 | this.Text = "Solution Configuration";
304 | this.Shown += new System.EventHandler(this.OnShown);
305 | this.Move += new System.EventHandler(this.OnMove);
306 | this.panel1.ResumeLayout(false);
307 | this.panel1.PerformLayout();
308 | this.panel2.ResumeLayout(false);
309 | this.panel2.PerformLayout();
310 | this.ResumeLayout(false);
311 | this.PerformLayout();
312 |
313 | }
314 |
315 | #endregion
316 |
317 | private System.Windows.Forms.Panel panel1;
318 | private System.Windows.Forms.RadioButton radioButtonManual;
319 | private System.Windows.Forms.RadioButton radioButtonAutomatic;
320 | private System.Windows.Forms.RadioButton radioButtonDisabled;
321 | private System.Windows.Forms.Panel panel2;
322 | private System.Windows.Forms.RadioButton radioButtonOnSave;
323 | private System.Windows.Forms.RadioButton radioButtonOnModify;
324 | private System.Windows.Forms.Label label4;
325 | private System.Windows.Forms.Label label3;
326 | private System.Windows.Forms.Label label2;
327 | private System.Windows.Forms.Label label1;
328 | private System.Windows.Forms.TextBox textBoxP4Client;
329 | private System.Windows.Forms.TextBox textBoxP4User;
330 | private System.Windows.Forms.TextBox textBoxP4Port;
331 | private System.Windows.Forms.Label label7;
332 | private System.Windows.Forms.Label label6;
333 | private System.Windows.Forms.Label label5;
334 | private System.Windows.Forms.Button buttonOK;
335 | private System.Windows.Forms.Button buttonCancel;
336 | private System.Windows.Forms.CheckBox checkBoxPromptForCheckout;
337 | private System.Windows.Forms.CheckBox checkBoxVerboseOutput;
338 | private System.Windows.Forms.CheckBox checkBoxOutputEnabled;
339 | }
340 | }
--------------------------------------------------------------------------------
/ClassLibrary/SolutionConfigForm.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright 2022 - Jeffrey "botman" Broome
4 | //
5 |
6 | using System;
7 | using System.Drawing;
8 | using System.Windows.Forms;
9 |
10 | namespace ClassLibrary
11 | {
12 | public partial class SolutionConfigForm : Form
13 | {
14 | private bool bWindowInitComplete; // set when form window is done initializing
15 |
16 | public int PosX = -1;
17 | public int PosY = -1;
18 |
19 | private string solutionDirectory = "";
20 |
21 | public int SolutionConfigType = 0;
22 | public bool bCheckOutOnEdit = true;
23 | public bool bPromptForCheckout = false;
24 | public bool bVerboseOutput = false;
25 | public bool bOutputEnabled = false;
26 |
27 | public string P4Port = "";
28 | public string P4User = "";
29 | public string P4Client = "";
30 |
31 | // save the manual settings that are passed in so we can display them (if needed) when switching solution configurations
32 | private string ManualP4Port = "";
33 | private string ManualP4User = "";
34 | private string ManualP4Client = "";
35 |
36 | public delegate void VerboseOutputDelegate(string message);
37 | private VerboseOutputDelegate VerboseOutput = null;
38 |
39 | public SolutionConfigForm(int InPosX, int InPosY, string InSolutionDirectory, int InSolutionConfigType, bool bInCheckOutOnEdit, bool bInPromptForCheckout, bool bInVerboseOutput, bool bInOutputEnabled, string InP4Port, string InP4User, string InP4Client, VerboseOutputDelegate InVerboseOutput)
40 | {
41 | bWindowInitComplete = false; // we aren't done initializing the window yet
42 |
43 | InitializeComponent();
44 |
45 | VerboseOutput = new VerboseOutputDelegate(InVerboseOutput);
46 |
47 | solutionDirectory = InSolutionDirectory;
48 | SolutionConfigType = InSolutionConfigType;
49 | bCheckOutOnEdit = bInCheckOutOnEdit;
50 | bPromptForCheckout = bInPromptForCheckout;
51 | bVerboseOutput = bInVerboseOutput;
52 | bOutputEnabled = bInOutputEnabled;
53 |
54 | P4Port = InP4Port;
55 | P4User = InP4User;
56 | P4Client = InP4Client;
57 |
58 | if (SolutionConfigType == 0) // disabled
59 | {
60 | radioButtonDisabled.Checked = true;
61 | }
62 | else if (SolutionConfigType == 1) // automatic
63 | {
64 | radioButtonAutomatic.Checked = true;
65 | }
66 | else if (SolutionConfigType == 2) // manual
67 | {
68 | ManualP4Port = P4Port;
69 | ManualP4User = P4User;
70 | ManualP4Client = P4Client;
71 |
72 | radioButtonManual.Checked = true;
73 | }
74 |
75 | if (bCheckOutOnEdit)
76 | {
77 | radioButtonOnModify.Checked = true;
78 | }
79 | else
80 | {
81 | radioButtonOnSave.Checked = true;
82 | }
83 |
84 | checkBoxPromptForCheckout.Checked = bPromptForCheckout;
85 | checkBoxVerboseOutput.Checked = bVerboseOutput;
86 | checkBoxOutputEnabled.Checked = bOutputEnabled;
87 |
88 | checkBoxVerboseOutput.Enabled = bOutputEnabled;
89 |
90 | PosX = InPosX;
91 | PosY = InPosY;
92 |
93 | if ((PosX != -1) && (PosY != -1))
94 | {
95 | this.StartPosition = FormStartPosition.Manual;
96 | this.Location = new Point(PosX, PosY);
97 | }
98 | else // otherwise, center the window on the parent form
99 | {
100 | this.StartPosition = FormStartPosition.CenterParent;
101 | }
102 | }
103 |
104 | private void OnShown(object sender, EventArgs e)
105 | {
106 | bWindowInitComplete = true; // window initialization is complete
107 | }
108 |
109 | private void OnMove(object sender, EventArgs e)
110 | {
111 | if (bWindowInitComplete)
112 | {
113 | PosX = Location.X;
114 | PosY = Location.Y;
115 | }
116 | }
117 |
118 | private void TextBoxEnable(bool bEnable)
119 | {
120 | textBoxP4Port.ReadOnly = !bEnable;
121 | textBoxP4User.ReadOnly = !bEnable;
122 | textBoxP4Client.ReadOnly = !bEnable;
123 | }
124 |
125 | private void SetTextBoxText()
126 | {
127 | textBoxP4Port.Text = P4Port;
128 | textBoxP4User.Text = P4User;
129 | textBoxP4Client.Text = P4Client;
130 | }
131 |
132 | private void radioButtonDisabled_CheckedChanged(object sender, EventArgs e)
133 | {
134 | if (radioButtonDisabled.Checked)
135 | {
136 | SolutionConfigType = 0; // disabled
137 |
138 | P4Port = "";
139 | P4User = "";
140 | P4Client = "";
141 |
142 | TextBoxEnable(false);
143 | SetTextBoxText();
144 | }
145 | }
146 |
147 | private void radioButtonAutomatic_CheckedChanged(object sender, EventArgs e)
148 | {
149 | if (radioButtonAutomatic.Checked)
150 | {
151 | SolutionConfigType = 1; // automatic
152 |
153 | P4Command p4 = new P4Command();
154 | p4.RunP4Set(solutionDirectory, out P4Port, out P4User, out P4Client, out string verbose);
155 |
156 | // if we have enabled the 'bVerboseOutput' setting locally in this dialog box, we need to output the results of the "p4 set" command here...
157 | if ((VerboseOutput != null) && bOutputEnabled && bVerboseOutput)
158 | {
159 | VerboseOutput(verbose);
160 | }
161 |
162 | TextBoxEnable(false);
163 | SetTextBoxText();
164 | }
165 | }
166 |
167 | private void radioButtonManual_CheckedChanged(object sender, EventArgs e)
168 | {
169 | if (radioButtonManual.Checked)
170 | {
171 | SolutionConfigType = 2; // manual settings
172 |
173 | // default to the previously set manual settings
174 | P4Port = ManualP4Port;
175 | P4User = ManualP4User;
176 | P4Client = ManualP4Client;
177 |
178 | TextBoxEnable(true);
179 | SetTextBoxText();
180 | }
181 | else
182 | {
183 | // save the previous manual settings
184 | ManualP4Port = textBoxP4Port.Text;
185 | ManualP4User = textBoxP4User.Text;
186 | ManualP4Client = textBoxP4Client.Text;
187 | }
188 | }
189 |
190 | private void radioButtonOnModify_CheckedChanged(object sender, EventArgs e)
191 | {
192 | if (!bWindowInitComplete)
193 | {
194 | return;
195 | }
196 |
197 | if (radioButtonOnModify.Checked)
198 | {
199 | bCheckOutOnEdit = true;
200 | }
201 | }
202 |
203 | private void radioButtonOnSave_CheckedChanged(object sender, EventArgs e)
204 | {
205 | if (!bWindowInitComplete)
206 | {
207 | return;
208 | }
209 |
210 | if (radioButtonOnSave.Checked)
211 | {
212 | bCheckOutOnEdit = false;
213 | }
214 | }
215 |
216 | private void checkBoxPromptForCheckout_CheckedChanged(object sender, EventArgs e)
217 | {
218 | bPromptForCheckout = checkBoxPromptForCheckout.Checked;
219 | }
220 |
221 | private void checkBoxOutputEnabled_CheckedChanged(object sender, EventArgs e)
222 | {
223 | bOutputEnabled = checkBoxOutputEnabled.Checked;
224 | checkBoxVerboseOutput.Enabled = bOutputEnabled;
225 | }
226 |
227 | private void checkBoxVerboseOutput_CheckedChanged(object sender, EventArgs e)
228 | {
229 | bVerboseOutput = checkBoxVerboseOutput.Checked;
230 | }
231 |
232 | private void buttonOK_Click(object sender, EventArgs e)
233 | {
234 | if (SolutionConfigType != 0)
235 | {
236 | if (textBoxP4Port.Text == "" || textBoxP4User.Text == "" || textBoxP4Client.Text == "")
237 | {
238 | string message = "P4PORT, P4USER or P4CLIENT is blank. These must not be blank for things to work properly. If you are using Windows environment variables, you will need to restart Visual Studio after changing them. Changes via 'p4 set' or from the .p4config file do not require restarting Visual Studio.";
239 | string caption = "Invalid Settings";
240 | MessageBox.Show(message, caption, MessageBoxButtons.OK);
241 |
242 | return;
243 | }
244 | }
245 |
246 | if (SolutionConfigType == 2) // manual settings?
247 | {
248 | P4Port = textBoxP4Port.Text;
249 | P4User = textBoxP4User.Text;
250 | P4Client = textBoxP4Client.Text;
251 | }
252 |
253 | this.DialogResult = DialogResult.OK;
254 |
255 | Close();
256 | }
257 |
258 | private void buttonCancel_Click(object sender, EventArgs e)
259 | {
260 | this.DialogResult = DialogResult.Cancel;
261 |
262 | Close();
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/ClassLibrary/SolutionConfigForm.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 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2022 Jeffrey "botman" Broome
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 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5 | 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.
--------------------------------------------------------------------------------
/P4SimpleScc.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.32002.261
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "P4SimpleScc_2019", "P4SimpleScc_2019\P4SimpleScc_2019.csproj", "{6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "P4SimpleScc_2022", "P4SimpleScc_2022\P4SimpleScc_2022.csproj", "{A739EBFE-8820-4E3E-B9B5-23A1202506A6}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary", "ClassLibrary\ClassLibrary.csproj", "{147CB2FB-A361-49EA-A025-5E1A37AA12EF}"
11 | EndProject
12 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedProject", "SharedProject\SharedProject.shproj", "{64CD1D5B-0E68-4A05-9535-BF12013DD743}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedFiles", "SharedFiles", "{E48A1386-AF13-48F4-8E50-8BC6D2C31097}"
15 | ProjectSection(SolutionItems) = preProject
16 | SharedFiles\P4SimpleSccPackage.vsct = SharedFiles\P4SimpleSccPackage.vsct
17 | EndProjectSection
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resources", "Resources", "{785ABA1B-4A2A-4D94-884F-4D585E4C0D50}"
20 | ProjectSection(SolutionItems) = preProject
21 | SharedFiles\Resources\Command.png = SharedFiles\Resources\Command.png
22 | EndProjectSection
23 | EndProject
24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadMe", "ReadMe", "{406A1291-CEC3-4255-90FC-1CB714ED57DC}"
25 | ProjectSection(SolutionItems) = preProject
26 | ReadMe\ReadMe.txt = ReadMe\ReadMe.txt
27 | EndProjectSection
28 | EndProject
29 | Global
30 | GlobalSection(SharedMSBuildProjectFiles) = preSolution
31 | SharedProject\SharedProject.projitems*{64cd1d5b-0e68-4a05-9535-bf12013dd743}*SharedItemsImports = 13
32 | SharedProject\SharedProject.projitems*{6b141b6a-8ace-4b1e-93fb-dde01c2f6bad}*SharedItemsImports = 4
33 | SharedProject\SharedProject.projitems*{a739ebfe-8820-4e3e-b9b5-23a1202506a6}*SharedItemsImports = 4
34 | EndGlobalSection
35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
36 | Debug|Any CPU = Debug|Any CPU
37 | Debug|x86 = Debug|x86
38 | Release|Any CPU = Release|Any CPU
39 | Release|x86 = Release|x86
40 | EndGlobalSection
41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
42 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Debug|x86.ActiveCfg = Debug|x86
45 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Debug|x86.Build.0 = Debug|x86
46 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Release|x86.ActiveCfg = Release|x86
49 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}.Release|x86.Build.0 = Release|x86
50 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Debug|x86.ActiveCfg = Debug|x86
53 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Debug|x86.Build.0 = Debug|x86
54 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Release|x86.ActiveCfg = Release|x86
57 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}.Release|x86.Build.0 = Release|x86
58 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Debug|x86.ActiveCfg = Debug|Any CPU
61 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Debug|x86.Build.0 = Debug|Any CPU
62 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Release|x86.ActiveCfg = Release|Any CPU
65 | {147CB2FB-A361-49EA-A025-5E1A37AA12EF}.Release|x86.Build.0 = Release|Any CPU
66 | EndGlobalSection
67 | GlobalSection(SolutionProperties) = preSolution
68 | HideSolutionNode = FALSE
69 | EndGlobalSection
70 | GlobalSection(NestedProjects) = preSolution
71 | {785ABA1B-4A2A-4D94-884F-4D585E4C0D50} = {E48A1386-AF13-48F4-8E50-8BC6D2C31097}
72 | EndGlobalSection
73 | GlobalSection(ExtensibilityGlobals) = postSolution
74 | SolutionGuid = {312D0A8F-D1B9-4EAF-B353-4D9F9DC82E53}
75 | EndGlobalSection
76 | EndGlobal
77 |
--------------------------------------------------------------------------------
/P4SimpleScc_2019/AsyncPackageHelpers/AsyncPackageRegistrationAttribute.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) Microsoft Corporation. All rights reserved.
4 | //
5 | //------------------------------------------------------------------------------
6 |
7 | namespace Microsoft.VisualStudio.AsyncPackageHelpers {
8 |
9 | using System;
10 | using System.Globalization;
11 | using System.IO;
12 | using System.ComponentModel;
13 | using System.ComponentModel.Design;
14 | using Microsoft.VisualStudio.Shell;
15 | using Microsoft.VisualStudio.Shell.Interop;
16 |
17 | ///
18 | /// This attribute is defined on a package to get it to be registered. It
19 | /// is internal because packages are meant to be registered, so it is
20 | /// implicit just by having a package in the assembly.
21 | ///
22 | [AttributeUsage(AttributeTargets.Class, Inherited=true, AllowMultiple=false)]
23 | public sealed class AsyncPackageRegistrationAttribute : RegistrationAttribute
24 | {
25 | private RegistrationMethod registrationMethod = RegistrationMethod.Default;
26 | private bool useManagedResources = false;
27 | private bool allowsBackgroundLoad = false;
28 | private string satellitePath = null;
29 |
30 | ///
31 | /// Select between specifying the Codebase entry or the Assembly entry in the registry.
32 | /// This can be overriden during registration
33 | ///
34 | public RegistrationMethod RegisterUsing
35 | {
36 | get
37 | {
38 | return registrationMethod;
39 | }
40 | set
41 | {
42 | registrationMethod = value;
43 | }
44 | }
45 |
46 | ///
47 | /// For managed resources, there should not be a native ui dll registered.
48 | ///
49 | public bool UseManagedResourcesOnly
50 | {
51 | get { return useManagedResources; }
52 | set { useManagedResources = value; }
53 | }
54 |
55 | ///
56 | /// Package is safe to load on a background thread.
57 | ///
58 | public bool AllowsBackgroundLoading
59 | {
60 | get { return allowsBackgroundLoad; }
61 | set { allowsBackgroundLoad = value; }
62 | }
63 |
64 | ///
65 | /// To specify a resource dll located in a different location then the default,
66 | /// set this property. This can be useful if your package is installed in the GAC.
67 | /// If this is not set, the directory where the package is located will be use.
68 | ///
69 | /// Note that the dll should be located at the following path:
70 | /// SatellitePath\lcid\PackageDllNameUI.dll
71 | ///
72 | public string SatellitePath
73 | {
74 | get { return satellitePath; }
75 | set { satellitePath = value; }
76 | }
77 |
78 | private string RegKeyName(RegistrationContext context)
79 | {
80 | return String.Format(CultureInfo.InvariantCulture, "Packages\\{0}", context.ComponentType.GUID.ToString("B"));
81 | }
82 |
83 | ///
84 | /// Called to register this attribute with the given context. The context
85 | /// contains the location where the registration information should be placed.
86 | /// it also contains such as the type being registered, and path information.
87 | ///
88 | /// This method is called both for registration and unregistration. The difference is
89 | /// that unregistering just uses a hive that reverses the changes applied to it.
90 | ///
91 | ///
92 | /// Contains the location where the registration information should be placed.
93 | /// It also contains other information such as the type being registered
94 | /// and path of the assembly.
95 | ///
96 | public override void Register(RegistrationContext context) {
97 | Type t = context.ComponentType;
98 |
99 | Key packageKey = null;
100 | try
101 | {
102 | packageKey = context.CreateKey(RegKeyName(context));
103 |
104 | //use a friendly description if it exists.
105 | DescriptionAttribute attr = TypeDescriptor.GetAttributes(t)[typeof(DescriptionAttribute)] as DescriptionAttribute;
106 | if (attr != null && !String.IsNullOrEmpty(attr.Description)) {
107 | packageKey.SetValue(string.Empty, attr.Description);
108 | }
109 | else {
110 | packageKey.SetValue(string.Empty, t.Name);
111 | }
112 |
113 | packageKey.SetValue("InprocServer32", context.InprocServerPath);
114 | packageKey.SetValue("Class", t.FullName);
115 |
116 | // If specified on the command line, let the command line option override
117 | if (context.RegistrationMethod != RegistrationMethod.Default)
118 | {
119 | registrationMethod = context.RegistrationMethod;
120 | }
121 |
122 | // Select registration method
123 | switch (registrationMethod)
124 | {
125 | case RegistrationMethod.Assembly:
126 | case RegistrationMethod.Default:
127 | packageKey.SetValue("Assembly", t.Assembly.FullName);
128 | break;
129 |
130 | case RegistrationMethod.CodeBase:
131 | packageKey.SetValue("CodeBase", context.CodeBase);
132 | break;
133 | }
134 |
135 | Key childKey = null;
136 | if (!useManagedResources)
137 | {
138 | try
139 | {
140 | childKey = packageKey.CreateSubkey("SatelliteDll");
141 |
142 | // Register the satellite dll
143 | string satelliteDllPath;
144 | if (SatellitePath != null)
145 | {
146 | // Use provided path
147 | satelliteDllPath = context.EscapePath(SatellitePath);
148 | }
149 | else
150 | {
151 | // Default to package path
152 | satelliteDllPath = context.ComponentPath;
153 | }
154 | childKey.SetValue("Path", satelliteDllPath);
155 | childKey.SetValue("DllName", String.Format(CultureInfo.InvariantCulture, "{0}UI.dll", Path.GetFileNameWithoutExtension(t.Assembly.ManifestModule.Name)));
156 | }
157 | finally
158 | {
159 | if (childKey != null)
160 | childKey.Close();
161 | }
162 | }
163 |
164 | if (allowsBackgroundLoad)
165 | {
166 | packageKey.SetValue("AllowsBackgroundLoad", true);
167 | }
168 |
169 | if (typeof(IVsPackageDynamicToolOwner).IsAssignableFrom(context.ComponentType) ||
170 | typeof(IVsPackageDynamicToolOwnerEx).IsAssignableFrom(context.ComponentType))
171 | {
172 | packageKey.SetValue("SupportsDynamicToolOwner", Microsoft.VisualStudio.PlatformUI.Boxes.BooleanTrue);
173 | }
174 | }
175 | finally
176 | {
177 | if (packageKey != null)
178 | packageKey.Close();
179 | }
180 | }
181 |
182 | ///
183 | /// Unregister this package.
184 | ///
185 | ///
186 | public override void Unregister(RegistrationContext context)
187 | {
188 | context.RemoveKey(RegKeyName(context));
189 | }
190 |
191 | }
192 | }
193 |
194 |
--------------------------------------------------------------------------------
/P4SimpleScc_2019/AsyncPackageHelpers/ExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | //using System.Runtime.InteropServices;
3 | //using Microsoft.VisualStudio.Shell.Interop;
4 | using System.Threading.Tasks;
5 | using Microsoft.VisualStudio.Shell;
6 |
7 | namespace Microsoft.VisualStudio.AsyncPackageHelpers
8 | {
9 | public static class ExtensionMethods
10 | {
11 | ///
12 | /// Helper method to use async/await with IAsyncServiceProvider implementation
13 | ///
14 | /// IAsyncServciceProvider instance
15 | /// Type of the Visual Studio service requested
16 | /// Service object as type of T
17 | public static async Task GetServiceAsync(this Shell.Interop.IAsyncServiceProvider asyncServiceProvider, Type serviceType) where T : class
18 | {
19 | T returnValue = null;
20 |
21 | await ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
22 | {
23 | object serviceInstance = null;
24 | Guid serviceTypeGuid = serviceType.GUID;
25 | serviceInstance = await asyncServiceProvider.QueryServiceAsync(ref serviceTypeGuid);
26 |
27 | // We have to make sure we are on main UI thread before trying to cast as underlying implementation
28 | // can be an STA COM object and doing a cast would require calling QueryInterface/AddRef marshaling
29 | // to main thread via COM.
30 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
31 | returnValue = serviceInstance as T;
32 | });
33 |
34 | return returnValue;
35 | }
36 |
37 | ///
38 | /// Gets if async package is supported in the current instance of Visual Studio
39 | ///
40 | /// an IServiceProvider instance, usually a Package instance
41 | /// true if async packages are supported
42 | public static bool IsAsyncPackageSupported(this IServiceProvider serviceProvider)
43 | {
44 | Shell.Interop.IAsyncServiceProvider asyncServiceProvider = serviceProvider.GetService(typeof(Shell.Interop.SAsyncServiceProvider)) as Shell.Interop.IAsyncServiceProvider;
45 | return asyncServiceProvider != null;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/P4SimpleScc_2019/AsyncPackageHelpers/ProvideAutoLoadAttribute.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) Microsoft Corporation, All rights reserved.
4 | // This code sample is provided "AS IS" without warranty of any kind,
5 | // it is not recommended for use in a production environment.
6 | //
7 | //------------------------------------------------------------------------------
8 |
9 | namespace Microsoft.VisualStudio.AsyncPackageHelpers
10 | {
11 |
12 | using System;
13 | using System.Globalization;
14 | using Microsoft.VisualStudio.Shell;
15 |
16 | [Flags]
17 | public enum PackageAutoLoadFlags
18 | {
19 | ///
20 | /// Indicates no special auto-load behavior. This is the default flag value if not specified.
21 | ///
22 | None = 0x0,
23 |
24 | ///
25 | /// When set package will not auto load in newer Visual Studio versions with rule based UI contexts
26 | ///
27 | SkipWhenUIContextRulesActive = 0x1,
28 |
29 | ///
30 | /// When set, if the associated package is marked as allowing background loads (via the ),
31 | /// then the package will be loaded asynchronously, in the background, when the associated UI context is triggered.
32 | ///
33 | BackgroundLoad = 0x2
34 | }
35 |
36 | ///
37 | /// This attribute registers the package as an extender. The GUID passed in determines
38 | /// what is being extended. The attributes on a package do not control the behavior of
39 | /// the package, but they can be used by registration tools to register the proper
40 | /// information with Visual Studio.
41 | ///
42 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
43 | public sealed class ProvideAutoLoadAttribute : RegistrationAttribute
44 | {
45 |
46 | private Guid loadGuid = Guid.Empty;
47 |
48 | ///
49 | /// Specify that the package should get loaded when this context is active.
50 | ///
51 | /// Context which should trigger the loading of your package.
52 | public ProvideAutoLoadAttribute(string cmdUiContextGuid) : this(cmdUiContextGuid, PackageAutoLoadFlags.None)
53 | {
54 | }
55 |
56 | ///
57 | /// Specify that the package should get loaded when this context is active.
58 | ///
59 | /// Context which should trigger the loading of your package.
60 | public ProvideAutoLoadAttribute(string cmdUiContextGuid, PackageAutoLoadFlags flags = PackageAutoLoadFlags.None)
61 | {
62 | loadGuid = new Guid(cmdUiContextGuid);
63 | Flags = flags;
64 | }
65 |
66 | ///
67 | /// Context Guid which triggers the loading of the package.
68 | ///
69 | public Guid LoadGuid
70 | {
71 | get
72 | {
73 | return loadGuid;
74 | }
75 | }
76 |
77 | ///
78 | /// Specifies the options for package auto load entry
79 | ///
80 | public PackageAutoLoadFlags Flags
81 | {
82 | get;
83 | private set;
84 | }
85 |
86 | ///
87 | /// The reg key name of this AutoLoad.
88 | ///
89 | private string RegKeyName
90 | {
91 | get
92 | {
93 | return string.Format(CultureInfo.InvariantCulture, "AutoLoadPackages\\{0}", loadGuid.ToString("B"));
94 | }
95 | }
96 |
97 | ///
98 | /// Called to register this attribute with the given context. The context
99 | /// contains the location where the registration information should be placed.
100 | /// it also contains such as the type being registered, and path information.
101 | ///
102 | public override void Register(RegistrationContext context)
103 | {
104 | using (Key childKey = context.CreateKey(RegKeyName))
105 | {
106 | childKey.SetValue(context.ComponentType.GUID.ToString("B"), (int)Flags);
107 | }
108 | }
109 |
110 | ///
111 | /// Unregister this AutoLoad specification.
112 | ///
113 | public override void Unregister(RegistrationContext context)
114 | {
115 | context.RemoveValue(RegKeyName, context.ComponentType.GUID.ToString("B"));
116 | }
117 | }
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/P4SimpleScc_2019/P4SimpleScc_2019.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 | Debug
10 | AnyCPU
11 | 2.0
12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
13 | {6B141B6A-8ACE-4B1E-93FB-DDE01C2F6BAD}
14 | Library
15 | Properties
16 | P4SimpleScc_2019
17 | P4SimpleScc_2019
18 | v4.7.2
19 | true
20 | true
21 | true
22 | false
23 | false
24 | true
25 | true
26 | Program
27 | $(DevEnvDir)devenv.exe
28 | /rootsuffix Exp
29 |
30 |
31 | true
32 | full
33 | false
34 | bin\Debug\
35 | TRACE;DEBUG
36 | prompt
37 | 4
38 |
39 |
40 | pdbonly
41 | true
42 | bin\Release\
43 | TRACE
44 | prompt
45 | 4
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Designer
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | P4SimplePackage.vsct
67 | Menus.ctmenu
68 |
69 |
70 |
71 |
72 | LICENSE.txt
73 | Always
74 | true
75 |
76 |
77 |
78 |
79 | {147cb2fb-a361-49ea-a025-5e1a37aa12ef}
80 | ClassLibrary
81 |
82 |
83 |
84 |
85 | 16.10.31321.278
86 |
87 |
88 | 16.10.31321.278
89 |
90 |
91 | 16.10.26
92 |
93 |
94 | 15.0.392
95 |
96 |
97 | 15.0.392
98 |
99 |
100 |
101 |
102 |
103 |
110 |
--------------------------------------------------------------------------------
/P4SimpleScc_2019/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("P4SimpleScc")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("P4SimpleScc")]
13 | [assembly: AssemblyCopyright("")]
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 | // 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 Build and Revision Numbers
30 | // by using the '*' as shown below:
31 | // [assembly: AssemblyVersion("1.0.*")]
32 | [assembly: AssemblyVersion("2.2.0.0")]
33 | [assembly: AssemblyFileVersion("2.2.0.0")]
34 |
--------------------------------------------------------------------------------
/P4SimpleScc_2019/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | P4SimpleScc
6 | P4SimpleScc source code control provider
7 | LICENSE.txt
8 | https://github.com/botman99/P4SimpleScc
9 | https://github.com/botman99/P4SimpleScc/releases
10 | Perforce; Scc; source; control
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/P4SimpleScc_2022/AsyncPackageHelpers/AsyncPackageRegistrationAttribute.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) Microsoft Corporation. All rights reserved.
4 | //
5 | //------------------------------------------------------------------------------
6 |
7 | namespace Microsoft.VisualStudio.AsyncPackageHelpers {
8 |
9 | using System;
10 | using System.Globalization;
11 | using System.IO;
12 | using System.ComponentModel;
13 | using System.ComponentModel.Design;
14 | using Microsoft.VisualStudio.Shell;
15 | using Microsoft.VisualStudio.Shell.Interop;
16 |
17 | ///
18 | /// This attribute is defined on a package to get it to be registered. It
19 | /// is internal because packages are meant to be registered, so it is
20 | /// implicit just by having a package in the assembly.
21 | ///
22 | [AttributeUsage(AttributeTargets.Class, Inherited=true, AllowMultiple=false)]
23 | public sealed class AsyncPackageRegistrationAttribute : RegistrationAttribute
24 | {
25 | private RegistrationMethod registrationMethod = RegistrationMethod.Default;
26 | private bool useManagedResources = false;
27 | private bool allowsBackgroundLoad = false;
28 | private string satellitePath = null;
29 |
30 | ///
31 | /// Select between specifying the Codebase entry or the Assembly entry in the registry.
32 | /// This can be overriden during registration
33 | ///
34 | public RegistrationMethod RegisterUsing
35 | {
36 | get
37 | {
38 | return registrationMethod;
39 | }
40 | set
41 | {
42 | registrationMethod = value;
43 | }
44 | }
45 |
46 | ///
47 | /// For managed resources, there should not be a native ui dll registered.
48 | ///
49 | public bool UseManagedResourcesOnly
50 | {
51 | get { return useManagedResources; }
52 | set { useManagedResources = value; }
53 | }
54 |
55 | ///
56 | /// Package is safe to load on a background thread.
57 | ///
58 | public bool AllowsBackgroundLoading
59 | {
60 | get { return allowsBackgroundLoad; }
61 | set { allowsBackgroundLoad = value; }
62 | }
63 |
64 | ///
65 | /// To specify a resource dll located in a different location then the default,
66 | /// set this property. This can be useful if your package is installed in the GAC.
67 | /// If this is not set, the directory where the package is located will be use.
68 | ///
69 | /// Note that the dll should be located at the following path:
70 | /// SatellitePath\lcid\PackageDllNameUI.dll
71 | ///
72 | public string SatellitePath
73 | {
74 | get { return satellitePath; }
75 | set { satellitePath = value; }
76 | }
77 |
78 | private string RegKeyName(RegistrationContext context)
79 | {
80 | return String.Format(CultureInfo.InvariantCulture, "Packages\\{0}", context.ComponentType.GUID.ToString("B"));
81 | }
82 |
83 | ///
84 | /// Called to register this attribute with the given context. The context
85 | /// contains the location where the registration information should be placed.
86 | /// it also contains such as the type being registered, and path information.
87 | ///
88 | /// This method is called both for registration and unregistration. The difference is
89 | /// that unregistering just uses a hive that reverses the changes applied to it.
90 | ///
91 | ///
92 | /// Contains the location where the registration information should be placed.
93 | /// It also contains other information such as the type being registered
94 | /// and path of the assembly.
95 | ///
96 | public override void Register(RegistrationContext context) {
97 | Type t = context.ComponentType;
98 |
99 | Key packageKey = null;
100 | try
101 | {
102 | packageKey = context.CreateKey(RegKeyName(context));
103 |
104 | //use a friendly description if it exists.
105 | DescriptionAttribute attr = TypeDescriptor.GetAttributes(t)[typeof(DescriptionAttribute)] as DescriptionAttribute;
106 | if (attr != null && !String.IsNullOrEmpty(attr.Description)) {
107 | packageKey.SetValue(string.Empty, attr.Description);
108 | }
109 | else {
110 | packageKey.SetValue(string.Empty, t.Name);
111 | }
112 |
113 | packageKey.SetValue("InprocServer32", context.InprocServerPath);
114 | packageKey.SetValue("Class", t.FullName);
115 |
116 | // If specified on the command line, let the command line option override
117 | if (context.RegistrationMethod != RegistrationMethod.Default)
118 | {
119 | registrationMethod = context.RegistrationMethod;
120 | }
121 |
122 | // Select registration method
123 | switch (registrationMethod)
124 | {
125 | case RegistrationMethod.Assembly:
126 | case RegistrationMethod.Default:
127 | packageKey.SetValue("Assembly", t.Assembly.FullName);
128 | break;
129 |
130 | case RegistrationMethod.CodeBase:
131 | packageKey.SetValue("CodeBase", context.CodeBase);
132 | break;
133 | }
134 |
135 | Key childKey = null;
136 | if (!useManagedResources)
137 | {
138 | try
139 | {
140 | childKey = packageKey.CreateSubkey("SatelliteDll");
141 |
142 | // Register the satellite dll
143 | string satelliteDllPath;
144 | if (SatellitePath != null)
145 | {
146 | // Use provided path
147 | satelliteDllPath = context.EscapePath(SatellitePath);
148 | }
149 | else
150 | {
151 | // Default to package path
152 | satelliteDllPath = context.ComponentPath;
153 | }
154 | childKey.SetValue("Path", satelliteDllPath);
155 | childKey.SetValue("DllName", String.Format(CultureInfo.InvariantCulture, "{0}UI.dll", Path.GetFileNameWithoutExtension(t.Assembly.ManifestModule.Name)));
156 | }
157 | finally
158 | {
159 | if (childKey != null)
160 | childKey.Close();
161 | }
162 | }
163 |
164 | if (allowsBackgroundLoad)
165 | {
166 | packageKey.SetValue("AllowsBackgroundLoad", true);
167 | }
168 |
169 | if (typeof(IVsPackageDynamicToolOwner).IsAssignableFrom(context.ComponentType) ||
170 | typeof(IVsPackageDynamicToolOwnerEx).IsAssignableFrom(context.ComponentType))
171 | {
172 | packageKey.SetValue("SupportsDynamicToolOwner", Microsoft.VisualStudio.PlatformUI.Boxes.BooleanTrue);
173 | }
174 | }
175 | finally
176 | {
177 | if (packageKey != null)
178 | packageKey.Close();
179 | }
180 | }
181 |
182 | ///
183 | /// Unregister this package.
184 | ///
185 | ///
186 | public override void Unregister(RegistrationContext context)
187 | {
188 | context.RemoveKey(RegKeyName(context));
189 | }
190 |
191 | }
192 | }
193 |
194 |
--------------------------------------------------------------------------------
/P4SimpleScc_2022/AsyncPackageHelpers/ExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | //using System.Runtime.InteropServices;
3 | //using Microsoft.VisualStudio.Shell.Interop;
4 | using System.Threading.Tasks;
5 | using Microsoft.VisualStudio.Shell;
6 |
7 | namespace Microsoft.VisualStudio.AsyncPackageHelpers
8 | {
9 | public static class ExtensionMethods
10 | {
11 | ///
12 | /// Helper method to use async/await with IAsyncServiceProvider implementation
13 | ///
14 | /// IAsyncServciceProvider instance
15 | /// Type of the Visual Studio service requested
16 | /// Service object as type of T
17 | public static async Task GetServiceAsync(this Shell.Interop.COMAsyncServiceProvider.IAsyncServiceProvider asyncServiceProvider, Type serviceType) where T : class
18 | {
19 | T returnValue = null;
20 |
21 | await ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
22 | {
23 | object serviceInstance = null;
24 | Guid serviceTypeGuid = serviceType.GUID;
25 | serviceInstance = await asyncServiceProvider.QueryServiceAsync(ref serviceTypeGuid);
26 |
27 | // We have to make sure we are on main UI thread before trying to cast as underlying implementation
28 | // can be an STA COM object and doing a cast would require calling QueryInterface/AddRef marshaling
29 | // to main thread via COM.
30 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
31 | returnValue = serviceInstance as T;
32 | });
33 |
34 | return returnValue;
35 | }
36 |
37 | ///
38 | /// Gets if async package is supported in the current instance of Visual Studio
39 | ///
40 | /// an IServiceProvider instance, usually a Package instance
41 | /// true if async packages are supported
42 | public static bool IsAsyncPackageSupported(this IServiceProvider serviceProvider)
43 | {
44 | Shell.Interop.COMAsyncServiceProvider.IAsyncServiceProvider asyncServiceProvider = serviceProvider.GetService(typeof(Shell.Interop.SAsyncServiceProvider)) as Shell.Interop.COMAsyncServiceProvider.IAsyncServiceProvider;
45 | return asyncServiceProvider != null;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/P4SimpleScc_2022/AsyncPackageHelpers/ProvideAutoLoadAttribute.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) Microsoft Corporation, All rights reserved.
4 | // This code sample is provided "AS IS" without warranty of any kind,
5 | // it is not recommended for use in a production environment.
6 | //
7 | //------------------------------------------------------------------------------
8 |
9 | namespace Microsoft.VisualStudio.AsyncPackageHelpers
10 | {
11 |
12 | using System;
13 | using System.Globalization;
14 | using Microsoft.VisualStudio.Shell;
15 |
16 | [Flags]
17 | public enum PackageAutoLoadFlags
18 | {
19 | ///
20 | /// Indicates no special auto-load behavior. This is the default flag value if not specified.
21 | ///
22 | None = 0x0,
23 |
24 | ///
25 | /// When set package will not auto load in newer Visual Studio versions with rule based UI contexts
26 | ///
27 | SkipWhenUIContextRulesActive = 0x1,
28 |
29 | ///
30 | /// When set, if the associated package is marked as allowing background loads (via the ),
31 | /// then the package will be loaded asynchronously, in the background, when the associated UI context is triggered.
32 | ///
33 | BackgroundLoad = 0x2
34 | }
35 |
36 | ///
37 | /// This attribute registers the package as an extender. The GUID passed in determines
38 | /// what is being extended. The attributes on a package do not control the behavior of
39 | /// the package, but they can be used by registration tools to register the proper
40 | /// information with Visual Studio.
41 | ///
42 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
43 | public sealed class ProvideAutoLoadAttribute : RegistrationAttribute
44 | {
45 |
46 | private Guid loadGuid = Guid.Empty;
47 |
48 | ///
49 | /// Specify that the package should get loaded when this context is active.
50 | ///
51 | /// Context which should trigger the loading of your package.
52 | public ProvideAutoLoadAttribute(string cmdUiContextGuid) : this(cmdUiContextGuid, PackageAutoLoadFlags.None)
53 | {
54 | }
55 |
56 | ///
57 | /// Specify that the package should get loaded when this context is active.
58 | ///
59 | /// Context which should trigger the loading of your package.
60 | public ProvideAutoLoadAttribute(string cmdUiContextGuid, PackageAutoLoadFlags flags = PackageAutoLoadFlags.None)
61 | {
62 | loadGuid = new Guid(cmdUiContextGuid);
63 | Flags = flags;
64 | }
65 |
66 | ///
67 | /// Context Guid which triggers the loading of the package.
68 | ///
69 | public Guid LoadGuid
70 | {
71 | get
72 | {
73 | return loadGuid;
74 | }
75 | }
76 |
77 | ///
78 | /// Specifies the options for package auto load entry
79 | ///
80 | public PackageAutoLoadFlags Flags
81 | {
82 | get;
83 | private set;
84 | }
85 |
86 | ///
87 | /// The reg key name of this AutoLoad.
88 | ///
89 | private string RegKeyName
90 | {
91 | get
92 | {
93 | return string.Format(CultureInfo.InvariantCulture, "AutoLoadPackages\\{0}", loadGuid.ToString("B"));
94 | }
95 | }
96 |
97 | ///
98 | /// Called to register this attribute with the given context. The context
99 | /// contains the location where the registration information should be placed.
100 | /// it also contains such as the type being registered, and path information.
101 | ///
102 | public override void Register(RegistrationContext context)
103 | {
104 | using (Key childKey = context.CreateKey(RegKeyName))
105 | {
106 | childKey.SetValue(context.ComponentType.GUID.ToString("B"), (int)Flags);
107 | }
108 | }
109 |
110 | ///
111 | /// Unregister this AutoLoad specification.
112 | ///
113 | public override void Unregister(RegistrationContext context)
114 | {
115 | context.RemoveValue(RegKeyName, context.ComponentType.GUID.ToString("B"));
116 | }
117 | }
118 | }
119 |
120 |
--------------------------------------------------------------------------------
/P4SimpleScc_2022/P4SimpleScc_2022.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 | Debug
10 | AnyCPU
11 | 2.0
12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
13 | {A739EBFE-8820-4E3E-B9B5-23A1202506A6}
14 | Library
15 | Properties
16 | P4SimpleScc_2022
17 | P4SimpleScc_2022
18 | v4.7.2
19 | true
20 | true
21 | true
22 | false
23 | false
24 | true
25 | true
26 | Program
27 | $(DevEnvDir)devenv.exe
28 | /rootsuffix Exp
29 |
30 |
31 | true
32 | full
33 | false
34 | bin\Debug\
35 | DEBUG;TRACE
36 | prompt
37 | 4
38 |
39 |
40 | pdbonly
41 | true
42 | bin\Release\
43 | TRACE
44 | prompt
45 | 4
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Designer
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | P4SimplePackage.vsct
74 | Menus.ctmenu
75 |
76 |
77 |
78 |
79 | LICENSE.txt
80 | Always
81 | true
82 |
83 |
84 |
85 |
86 | {147cb2fb-a361-49ea-a025-5e1a37aa12ef}
87 | ClassLibrary
88 |
89 |
90 |
91 |
92 | 17.11.20
93 |
94 |
95 | 17.11.40262
96 |
97 |
98 | 15.0.392
99 |
100 |
101 | 15.0.392
102 |
103 |
104 |
105 |
106 |
107 |
114 |
--------------------------------------------------------------------------------
/P4SimpleScc_2022/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("P4SimpleScc_2022")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("P4SimpleScc_2022")]
13 | [assembly: AssemblyCopyright("")]
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 | // 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 Build and Revision Numbers
30 | // by using the '*' as shown below:
31 | // [assembly: AssemblyVersion("1.0.*")]
32 | [assembly: AssemblyVersion("2.2.0.0")]
33 | [assembly: AssemblyFileVersion("2.2.0.0")]
34 |
--------------------------------------------------------------------------------
/P4SimpleScc_2022/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | P4SimpleScc
6 | P4SimpleScc source code control provider
7 | LICENSE.txt
8 | https://github.com/botman99/P4SimpleScc
9 | https://github.com/botman99/P4SimpleScc/releases
10 | Perforce; Scc; source; control
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # P4SimpleScc
2 |
3 | **P4SimpleScc** is a Visual Studio Extension Source Control Provider for Perforce (Helix Core).
4 |
5 | The "simple" part of this extension is that it only does **one** thing. It checks files out from a Perforce server when they are modified (or when they are saved). It doesn't do anything else. It doesn't show status of checked out files. It doesn't submit changelists of files. It doesn't diff files. It doesn't revert files. It doesn't shelve files. It doesn't show history or time-lapse view of files. It doesn't show a revision graph of files. All of those functions can be performed in P4V instead of implementing that same interface in Visual Studio.
6 |
7 | If all you want is to check out files when modified, and you want something small, fast and efficient, then P4SimpleScc does that task well.
8 |
9 | To use P4SimpleScc, just install the extension (there's one for Visual Studio 2019 and one for Visual Studio 2022), then start up Visual Studio and click on "Tools -> Options" from the main menu. Expand the 'Source Control' group and select 'P4SimpleScc' from the 'Current source control plug-in:' drop down and click "OK". That will enable the P4SimpleScc provider.
10 |
11 | 
12 |
13 | While the P4SimpleScc provider is active, you can click on 'Extensions' in the main menu and you should see a 'P4SimpleScc' menu (this menu is hidden if P4SimpleScc is not the active source control provider). If a solution is loaded, you can then click on the 'Solution Configuration' menu item to enable source control on the currently loaded solution.
14 |
15 | 
16 |
17 | The solution configuration dialog looks like this:
18 |
19 | 
20 |
21 | There are three choices at the top, 'Disabled, 'Automatic' and 'Manual'. 'Disable' will disable P4SimpleScc provider control for this solution. 'Automatic' will use the console command "p4 set" to automatically gather the settings for P4PORT, P4USER and P4CLIENT which can come from the Windows environment, from a previous 'p4 set' command, or from a .p4config file (as long as the P4CONFIG setting has previously been set using 'p4 set P4CONFIG=' followed by the .p4config file name). You can also use 'Manual' to set the 'P4PORT', 'P4USER' and 'P4CLIENT' settings manually. Each of these settings must be non-blank and will be validated with the Perforce (Helix Core) server to make sure they are correct. P4PORT will be the server name and port number (separated by a ':'). P4USER will be your username on the Perforce server. P4CLIENT will be your workspace name on the Perforce server for where you have the solution and files (although the solution .sln file doesn't have to be under Perforce control).
22 |
23 | You can also select whether you want to check out files when they are modified or wait and check them out when the changes to the file have been saved. Waiting until the file is saved means that you can "undo" changes to a file if you modified it by mistake and it won't be checked out from Perforce until you save it, so you won't have to revert that file later. If you had selected 'Check out files on modify' it would have been checked out as soon as you accidentally modified it.
24 |
25 | There is an option to prompt you with a dialog to verify that it is okay to check out each file before doing so (otherwise, the file will automatically be checked out upon modify/save).
26 |
27 | There is also an option to enable or disable the 'P4SimpleScc' Output pane in the Output Window. By default, this output is disabled. If you enable output, you can also enable 'Verbose Output' which outputs the response of all P4 commands that are issued to the server by the P4SimpleScc extension. The 'Verbose Output' is normally not needed unless you are trying to debug why some operation in P4SimpleScc is failing.
28 |
29 | If you have 'Output Enabled' checked in the Solution Configuration, when the P4SimpleScc provider is enabled, you can see status messages from it by opening the Output window ("View -> Output" from the main menu, or Ctrl-Alt-O). Then select 'P4SimpleScc' in the 'Show output from:' drop down.
30 |
31 | 
32 |
33 | You can manually check out file by selecting one or more files in the Solution Explorer view, right clicking and selecting "Check Out File".
34 |
35 | 
36 |
37 | You can also open a file in the document editor and right click on the filename tab and select "Check Out File" from there.
38 |
39 | 
40 |
41 | If you right click on a file and the file is already checked out, it will show "Check Out File" as being disabled.
42 |
43 | 
44 |
45 | **NOTE:** If you have the P4VS extension installed, you may need to disable it (you don't need to uninstall, just disable) since the P4VS extension seems to want to make itself the active source control provider even if another provider was active or is controlling that solution.
46 |
47 | See the [Releases](https://github.com/botman99/P4SimpleScc/releases) page to download the latest release.
48 |
49 | * Author: Jeffrey "botman" Broome
50 | * License: [MIT](http://opensource.org/licenses/mit-license.php)
51 |
--------------------------------------------------------------------------------
/ReadMe/ReadMe.txt:
--------------------------------------------------------------------------------
1 |
2 | This Visual Studio extension was created using shared projects as described here:
3 |
4 | https://docs.microsoft.com/en-us/visualstudio/extensibility/migration/update-visual-studio-extension?view=vs-2022#use-shared-projects-for-multi-targeting
5 |
6 | To build (and debug) the VS2019 extension, open the .sln file using Visual Studio 2019.
7 | To build (and debug) the VS2022 extension, open the .sln file using Visual Studio 2022.
8 |
9 |
10 | The ClassLibrary was originally created as a Windows Forms App (.NET Framework) Project and then
11 | the 'Output type:' in the Properties was changed to "Class Library". This was necessary because
12 | both of the VISX projects need access to the Windows Forms Dialog(s) and Visual Studio won't let
13 | you add Windows Forms Dialogs (using .NET Framework) to the SharedProject.
14 |
15 | After being switched from "Windows Forms App" to "Class Library", things like 'Program.cs' and
16 | 'App.config' were removed from the new project (since these aren't needed for a class library).
17 |
18 | Any additional classes that are needed by both the VISX project's classes and by the Windows
19 | Forms classes (such as P4Command.cs) need to also be added to the ClassLibrary so that these are
20 | accessible by both projects.
21 |
22 |
23 | The origin of the Source Control Provider code came from the VSSDK sample here:
24 | https://github.com/microsoft/VSSDK-Extensibility-Samples/tree/master/ArchivedSamples/Source_Code_Control_Provider
25 |
26 | The AsyncPackageHelpers code came from the 'Backwards Compatible AsyncPackage 2013 code here:
27 | https://github.com/microsoft/VSSDK-Extensibility-Samples/tree/master/Backwards_Compatible_AsyncPackage_2013/BackwardsCompatibleAsyncPackage/AsyncPackageHelpers
28 |
29 | This was needed to get the extension to auto-load and auto-execute on load so that this
30 | extension could detect when a solution was loaded that was using P4SimpleScc as the source
31 | control provider.
32 |
33 | See this page for some migration issues from VS2019 and VS2022:
34 |
35 | https://docs.microsoft.com/en-us/visualstudio/extensibility/migration/breaking-api-list?view=vs-2022
36 |
37 |
--------------------------------------------------------------------------------
/SharedFiles/P4SimpleSccPackage.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 |
34 |
35 |
36 |
42 |
43 |
49 |
50 |
51 |
55 |
56 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
75 |
77 |
78 |
85 |
94 |
95 |
106 |
107 |
117 |
118 |
119 |
120 |
121 |
122 |
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 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/SharedFiles/Resources/Command.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/SharedFiles/Resources/Command.png
--------------------------------------------------------------------------------
/SharedProject/ActionProviderFactory.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright - Jeffrey "botman" Broome
4 | //
5 |
6 | using System;
7 | using System.Collections.Generic;
8 | using System.ComponentModel.Design;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using Microsoft.VisualStudio.Workspace;
12 | using Microsoft.VisualStudio.Workspace.Extensions.VS;
13 |
14 |
15 | namespace P4SimpleScc
16 | {
17 | [ExportFileContextActionProvider((FileContextActionProviderOptions)VsCommandActionProviderOptions.SupportVsCommands, ProviderType, ProviderPriority.Normal, GuidList.SourceFileContextType)]
18 | public class ActionProviderFactory : IWorkspaceProviderFactory, IVsCommandActionProvider
19 | {
20 | // Unique Guid for WordCountActionProvider.
21 | private const string ProviderType = "0DD39C9C-3DE4-4B9C-BE19-7D011341A65B";
22 |
23 | private static readonly Guid ProviderCommandGroup = GuidList.GuidOpenFolderExtensibilityPackageCmdSet;
24 |
25 | public IFileContextActionProvider CreateProvider(IWorkspace workspaceContext)
26 | {
27 | return new ActionProvider(workspaceContext);
28 | }
29 |
30 | public IReadOnlyCollection GetSupportedVsCommands()
31 | {
32 | return new List
33 | {
34 | new CommandID(GuidList.GuidOpenFolderExtensibilityPackageCmdSet, SccProvider.icmdWorkspaceCheckOutFile)
35 | };
36 | }
37 |
38 | internal class ActionProvider : IFileContextActionProvider
39 | {
40 | private IWorkspace workspaceContext;
41 |
42 | internal ActionProvider(IWorkspace workspaceContext)
43 | {
44 | this.workspaceContext = workspaceContext;
45 | }
46 |
47 | public Task> GetActionsAsync(string filePath, FileContext fileContext, CancellationToken cancellationToken)
48 | {
49 | bool bIsCheckedOut = SccProvider.IsCheckedOut(filePath, out string stderr);
50 |
51 | if (!bIsCheckedOut) // if the file is not already checked out, then add the menu item to check out the file
52 | {
53 | return Task.FromResult>(new IFileContextAction[]
54 | {
55 | new MyContextAction(
56 | fileContext,
57 | new Tuple(ProviderCommandGroup, SccProvider.icmdWorkspaceCheckOutFile),
58 | "My Action",
59 | async (fCtxt, progress, ct) =>
60 | {
61 | await DoWorkAsync(workspaceContext, fileContext, filePath);
62 | }
63 | )
64 | });
65 | }
66 | else // if the file is already checked out, we return an empty list, SccProvider.QueryStatus will intercept the menu command and disable it (grey it out)
67 | {
68 | return Task.FromResult>(new IFileContextAction[]
69 | {
70 | });
71 | }
72 | }
73 |
74 | internal static async Task DoWorkAsync(IWorkspace workspaceContext, FileContext fileContext, string filePath)
75 | {
76 | await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
77 |
78 | SccProvider.CheckOutFile(filePath);
79 |
80 | // invoke the FileContextChanged event to force the menu to be recreated (since the action above will change the state of the file)
81 | await fileContext.OnFileContextChanged.InvokeAsync(null, new EventArgs());
82 | }
83 |
84 | internal class MyContextAction : IFileContextAction, IVsCommandItem
85 | {
86 | private Func, CancellationToken, Task> executeAction;
87 |
88 | internal MyContextAction(
89 | FileContext fileContext,
90 | Tuple command,
91 | string displayName,
92 | Func, CancellationToken, Task> executeAction)
93 | {
94 | this.executeAction = executeAction;
95 | this.Source = fileContext;
96 | this.CommandGroup = command.Item1;
97 | this.CommandId = command.Item2;
98 | this.DisplayName = displayName;
99 | }
100 |
101 | public Guid CommandGroup { get; }
102 | public uint CommandId { get; }
103 | public string DisplayName { get; }
104 | public FileContext Source { get; }
105 |
106 | public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
107 | {
108 | await this.executeAction(this.Source, progress, cancellationToken);
109 | return new FileContextActionResult(true);
110 | }
111 |
112 | }
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/SharedProject/FileContextProviderFactory.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright - Jeffrey "botman" Broome
4 | //
5 |
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Microsoft.VisualStudio.Workspace;
11 | using Task = System.Threading.Tasks.Task;
12 |
13 | namespace P4SimpleScc
14 | {
15 | ///
16 | /// File context provider for TXT files.
17 | ///
18 | [ExportFileContextProvider(ProviderType, GuidList.SourceFileContextType)]
19 | public class FileContextProviderFactory : IWorkspaceProviderFactory
20 | {
21 | // Unique Guid for FileContextProvider.
22 | private const string ProviderType = "F0970AFD-11A4-41BF-8BEE-EF3FA288FB97";
23 |
24 | ///
25 | public IFileContextProvider CreateProvider(IWorkspace workspaceContext)
26 | {
27 | return new FileContextProvider(workspaceContext);
28 | }
29 |
30 | private class FileContextProvider : IFileContextProvider
31 | {
32 | private IWorkspace workspaceContext;
33 |
34 | internal FileContextProvider(IWorkspace workspaceContext)
35 | {
36 | this.workspaceContext = workspaceContext;
37 | }
38 |
39 | ///
40 | public async Task> GetContextsForFileAsync(string filePath, CancellationToken cancellationToken)
41 | {
42 | var fileContexts = new List();
43 |
44 | fileContexts.Add(new FileContext(
45 | new Guid(ProviderType),
46 | new Guid(GuidList.SourceFileContextType),
47 | filePath + "\n",
48 | Array.Empty()));
49 |
50 | return await Task.FromResult(fileContexts.ToArray());
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/SharedProject/Guids.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright - Jeffrey "botman" Broome
4 | //
5 |
6 | using System;
7 |
8 | namespace P4SimpleScc
9 | {
10 | ///
11 | /// This class is used only to expose the list of Guids used by this package.
12 | /// This list of guids must match the set of Guids used inside the VSCT file.
13 | ///
14 | public static class GuidList
15 | {
16 | // Now define the list of guids as public static members.
17 |
18 | // Unique ID of the source control provider; this is also used as the command UI context to show/hide the pacakge UI
19 | public static readonly Guid guidSccProvider = new Guid("{B205A1B6-0000-4A1C-8680-97FD2219C692}");
20 | // The guid of the source control provider service (implementing IVsSccProvider interface)
21 | public static readonly Guid guidSccProviderService = new Guid("{B205A1B6-1000-4A1C-8680-97FD2219C692}");
22 | // The guid of the source control provider package (implementing IVsPackage interface)
23 | public static readonly Guid guidSccProviderPkg = new Guid("{B205A1B6-2000-4A1C-8680-97FD2219C692}");
24 |
25 | // Other guids for menus and commands
26 | public static readonly Guid guidSccProviderCmdSet = new Guid("{B205A1B6-9463-474A-807D-17F40BCFBB17}");
27 |
28 | public static readonly Guid GuidOpenFolderExtensibilityPackageCmdSet = new Guid("e37cc989-b956-4a50-9515-b0395b288e4a");
29 |
30 | // Guid to associate file action factories.
31 | public const string SourceFileContextType = "2C4D13FF-FEA9-4AEC-A48E-17FD9D70E594";
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/SharedProject/ProvideSolutionProps.cs:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 | THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
5 | ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
6 | IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
7 | PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
8 |
9 | ***************************************************************************/
10 |
11 | using System;
12 | using System.Globalization;
13 | using Microsoft.VisualStudio.Shell;
14 |
15 | namespace P4SimpleScc
16 | {
17 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
18 | internal sealed class ProvideSolutionProps : RegistrationAttribute
19 | {
20 | private string _propName;
21 |
22 | public ProvideSolutionProps(string propName)
23 | {
24 | _propName = propName;
25 | }
26 |
27 | public override void Register(RegistrationContext context)
28 | {
29 | context.Log.WriteLine(string.Format(CultureInfo.InvariantCulture, "ProvideSolutionProps: ({0} = {1})", context.ComponentType.GUID.ToString("B"), PropName));
30 |
31 | Key childKey = null;
32 |
33 | try
34 | {
35 | childKey = context.CreateKey(string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", "SolutionPersistence", PropName));
36 |
37 | childKey.SetValue(string.Empty, context.ComponentType.GUID.ToString("B"));
38 | }
39 | finally
40 | {
41 | if (childKey != null) childKey.Close();
42 | }
43 | }
44 |
45 | public override void Unregister(RegistrationContext context)
46 | {
47 | context.RemoveKey(string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", "SolutionPersistence", PropName));
48 | }
49 |
50 | public string PropName { get { return _propName; } }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/SharedProject/ProvideSourceControlProvider.cs:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 | THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
5 | ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
6 | IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
7 | PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
8 |
9 | ***************************************************************************/
10 |
11 | using System;
12 | using System.Globalization;
13 | using MsVsShell = Microsoft.VisualStudio.Shell;
14 |
15 | namespace P4SimpleScc
16 | {
17 | ///
18 | /// This attribute registers the source control provider.
19 | ///
20 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
21 | public sealed class ProvideSourceControlProvider : MsVsShell.RegistrationAttribute
22 | {
23 | private string _regName = null;
24 | private string _uiName = null;
25 |
26 | ///
27 | /// Constructor
28 | ///
29 | public ProvideSourceControlProvider(string regName, string uiName)
30 | {
31 | _regName = regName;
32 | _uiName = uiName;
33 | }
34 |
35 | ///
36 | /// Get the friendly name of the provider (written in registry)
37 | ///
38 | public string RegName
39 | {
40 | get { return _regName; }
41 | }
42 |
43 | ///
44 | /// Get the unique guid identifying the provider
45 | ///
46 | public Guid RegGuid
47 | {
48 | get { return GuidList.guidSccProvider; }
49 | }
50 |
51 | ///
52 | /// Get the UI name of the provider (string resource ID)
53 | ///
54 | public string UIName
55 | {
56 | get { return _uiName; }
57 | }
58 |
59 | ///
60 | /// Get the package containing the UI name of the provider
61 | ///
62 | public Guid UINamePkg
63 | {
64 | get { return GuidList.guidSccProviderPkg; }
65 | }
66 |
67 | ///
68 | /// Get the guid of the provider's service
69 | ///
70 | public Guid SccProviderService
71 | {
72 | get { return GuidList.guidSccProviderService; }
73 | }
74 |
75 | ///
76 | /// Called to register this attribute with the given context. The context
77 | /// contains the location where the registration inforomation should be placed.
78 | /// It also contains other information such as the type being registered and path information.
79 | ///
80 | public override void Register(RegistrationContext context)
81 | {
82 | // Write to the context's log what we are about to do
83 | context.Log.WriteLine(string.Format(CultureInfo.CurrentCulture, "P4SimpleSccProvider:\t\t{0}\n", RegName));
84 |
85 | // Declare the source control provider, its name, the provider's service
86 | // and aditionally the packages implementing this provider
87 | using (Key sccProviders = context.CreateKey("SourceControlProviders"))
88 | {
89 | using (Key sccProviderKey = sccProviders.CreateSubkey(RegGuid.ToString("B")))
90 | {
91 | sccProviderKey.SetValue("", RegName);
92 | sccProviderKey.SetValue("Service", SccProviderService.ToString("B"));
93 |
94 | using (Key sccProviderNameKey = sccProviderKey.CreateSubkey("Name"))
95 | {
96 | sccProviderNameKey.SetValue("", UIName);
97 | sccProviderNameKey.SetValue("Package", UINamePkg.ToString("B"));
98 |
99 | sccProviderNameKey.Close();
100 | }
101 |
102 | // Additionally, you can create a "Packages" subkey where you can enumerate the dll
103 | // that are used by the source control provider, something like "Package1"="P4SimpleSccProvider.dll"
104 | // but this is not a requirement.
105 | sccProviderKey.Close();
106 | }
107 |
108 | sccProviders.Close();
109 | }
110 | }
111 |
112 | ///
113 | /// Unregister the source control provider
114 | ///
115 | /// The Registration Context to be unregistered
116 | public override void Unregister(RegistrationContext context)
117 | {
118 | context.RemoveKey("SourceControlProviders\\" + GuidList.guidSccProviderPkg.ToString("B"));
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/SharedProject/SccProvider.cs:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // Copyright - Jeffrey "botman" Broome
4 | //
5 |
6 | using System;
7 | using System.IO;
8 | using System.Collections.Generic;
9 | using System.Diagnostics;
10 | using System.Globalization;
11 | using System.Runtime.InteropServices;
12 | using System.ComponentModel.Design;
13 | using Microsoft.VisualStudio.Shell.Interop;
14 | using Microsoft.VisualStudio.OLE.Interop;
15 | using Microsoft.VisualStudio;
16 | using Microsoft.VisualStudio.Settings;
17 | using Microsoft.VisualStudio.Shell.Settings;
18 | using PackageAutoLoadFlags = Microsoft.VisualStudio.AsyncPackageHelpers.PackageAutoLoadFlags;
19 |
20 | using MsVsShell = Microsoft.VisualStudio.Shell;
21 |
22 | using ClassLibrary;
23 |
24 | namespace P4SimpleScc
25 | {
26 | ///
27 | /// This is the class that implements the package exposed by this assembly.
28 | ///
29 | ///
30 | ///
31 | /// The minimum requirement for a class to be considered a valid package for Visual Studio
32 | /// is to implement the IVsPackage interface and register itself with the shell.
33 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
34 | /// to do it: it derives from the Package class that provides the implementation of the
35 | /// IVsPackage interface and uses the registration attributes defined in the framework to
36 | /// register itself and its components with the shell. These attributes tell the pkgdef creation
37 | /// utility what data to put into .pkgdef file.
38 | ///
39 | ///
40 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file.
41 | ///
42 | ///
43 | // Declare that resources for the package are to be found in the managed assembly resources, and not in a satellite dll
44 | [MsVsShell.PackageRegistration(UseManagedResourcesOnly = true)]
45 | // Register the resource ID of the CTMENU section (generated from compiling the VSCT file), so the IDE will know how to merge this package's menus with the rest of the IDE when "devenv /setup" is run
46 | // The menu resource ID needs to match the ResourceName number defined in the csproj project file in the VSCTCompile section
47 | // Everytime the version number changes VS will automatically update the menus on startup; if the version doesn't change, you will need to run manually "devenv /setup /rootsuffix:Exp" to see VSCT changes reflected in IDE
48 | [MsVsShell.ProvideMenuResource("Menus.ctmenu", 1)]
49 | // Register the source control provider's service (implementing IVsScciProvider interface)
50 | [MsVsShell.ProvideService(typeof(SccProviderService), ServiceName = "P4SimpleScc")]
51 | // Register the source control provider to be visible in Tools/Options/SourceControl/Plugin dropdown selector
52 | [@ProvideSourceControlProvider("P4SimpleScc", "#100")]
53 |
54 | // Pre-load the package when the command UI context is asserted (the provider will be automatically loaded after restarting the shell if it was active last time the shell was shutdown)
55 | [Microsoft.VisualStudio.AsyncPackageHelpers.ProvideAutoLoad("B205A1B6-0000-4A1C-8680-97FD2219C692",PackageAutoLoadFlags.BackgroundLoad)]
56 | [Microsoft.VisualStudio.AsyncPackageHelpers.AsyncPackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
57 | [Microsoft.VisualStudio.AsyncPackageHelpers.ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)]
58 | [Microsoft.VisualStudio.AsyncPackageHelpers.ProvideAutoLoad(VSConstants.UICONTEXT.SolutionOpening_string, PackageAutoLoadFlags.BackgroundLoad)]
59 |
60 | // Register the key used for persisting solution properties, so the IDE will know to load the source control package when opening a controlled solution containing properties written by this package
61 | [ProvideSolutionProps(_strSolutionPersistanceKey)]
62 | // Declare the package guid
63 | [Guid("B205A1B6-2000-4A1C-8680-97FD2219C692")] // this is GuidList.guidSccProviderPkg
64 | public sealed class SccProvider : MsVsShell.Package,
65 | IOleCommandTarget,
66 | IVsPersistSolutionProps // We'll write properties in the solution file to track when solution is controlled; the interface needs to be implemented by the package object
67 | {
68 | // The service provider implemented by the package
69 | private SccProviderService sccService = null;
70 |
71 | // The name of this provider (to be written in solution and project files)
72 | // As a best practice, to be sure the provider has an unique name, a guid like the provider guid can be used as a part of the name
73 | private const string _strProviderName = "P4SimpleScc:{B205A1B6-2000-4A1C-8680-97FD2219C692}"; // this is GuidList.guidSccProviderPkg (same as this class's Guid)
74 |
75 | // The name of the solution section used to persist provider options (should be unique)
76 | private const string _strSolutionPersistanceKey = "P4SimpleSccProviderSolutionProperties";
77 |
78 | // The name of the section in the solution user options file used to persist user-specific options (should be unique, shorter than 31 characters and without dots)
79 | private const string _strSolutionUserOptionsKey = "P4SimpleSccProvider";
80 |
81 | // The names of the properties stored by the provider in the solution file
82 | private const string _strSolutionControlledProperty = "SolutionIsControlled";
83 | private const string _strSolutionBindingsProperty = "SolutionBindings";
84 |
85 | private static MsVsShell.Package package;
86 |
87 | public static Guid OutputPaneGuid = Guid.Empty;
88 |
89 | public string solutionDirectory = "";
90 | public string solutionFile = "";
91 | public string solutionUserOptions = "";
92 |
93 | public bool bSolutionLoadedOutputDone = false;
94 |
95 | public bool bIsWorkspace = false; // 'true' if "Open Folder" was used to open this project, 'false' if project was opened using a Solution file
96 | public string WindowActivatedFilename = "";
97 |
98 | public bool bReadUserOptionsCalled = false; // this allows us to determine if user settings were attempted to be loaded
99 | public bool bUserSettingsWasEmpty = false; // previously, if P4SimpleScc was disabled, settings were removed from the .sou file in the .vs folder, this bool will be 'true' if the settings are missing
100 |
101 | // P4SimpleScc solution configuration settings...
102 | public static int SolutionConfigType = 0; // 0 = disabled, 1 = automatic, 2 = manual settings
103 | public bool bCheckOutOnEdit = true;
104 | public bool bPromptForCheckout = false;
105 | public string P4Port = "";
106 | public string P4User = "";
107 | public string P4Client = "";
108 | public static bool bVerboseOutput = false;
109 | public static bool bOutputEnabled = false;
110 |
111 | public static bool P4SimpleSccConfigDirty = false; // has the solution configuration for this solution been modified (and needs to be saved)?
112 |
113 | public static bool bIsNotAllWrite = true;
114 |
115 | private static object OpututQueueLock = new object();
116 | private static List OutputQueueList = new List();
117 | private System.Windows.Threading.DispatcherTimer OutputQueueTimer = null;
118 |
119 | private static char[] InvalidChars;
120 | private List FilenameList;
121 |
122 | private CommandID menuCommandId;
123 | private CommandID menuCheckOutFileCommandId;
124 |
125 | ///
126 | /// Command ID.
127 | ///
128 | public const int icmdSolutionConfiguration = 0x0100;
129 | public const int icmdCheckOutFile = 0x101;
130 | public const int icmdWorkspaceCheckOutFile = 0x0501;
131 |
132 |
133 | ///
134 | /// Constructor
135 | ///
136 | public SccProvider()
137 | {
138 | // The provider implements the IVsPersistSolutionProps interface which is derived from IVsPersistSolutionOpts,
139 | // The base class MsVsShell.Package also implements IVsPersistSolutionOpts, so we're overriding its functionality
140 | // Therefore, to persist user options in the suo file we will not use the set of AddOptionKey/OnLoadOptions/OnSaveOptions
141 | // set of functions, but instead we'll use the IVsPersistSolutionProps functions directly.
142 | }
143 |
144 | #region Package Members
145 |
146 | ///
147 | /// Get an instance of the desired service type. (Calls Package.GetService)
148 | ///
149 | /// The type of service to retrieve
150 | /// An instance of the requested service, or null if the service could not be found
151 | public new object GetService(Type serviceType)
152 | {
153 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
154 |
155 | return base.GetService(serviceType);
156 | }
157 |
158 | ///
159 | /// Initialize the Provider
160 | ///
161 | protected override void Initialize()
162 | {
163 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
164 |
165 | package = this;
166 |
167 | base.Initialize();
168 |
169 | InvalidChars = Path.GetInvalidPathChars(); // get characters not allowed in file paths
170 |
171 | // Proffer the source control service implemented by the provider
172 | sccService = new SccProviderService(this);
173 | ((IServiceContainer)this).AddService(typeof(SccProviderService), sccService, true);
174 |
175 | var dte = this.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
176 | dte.Events.WindowEvents.WindowActivated += WindowEventsOnWindowActivated;
177 |
178 | // Add our command handlers for menu (commands must exist in the .vsct file)
179 | MsVsShell.OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as MsVsShell.OleMenuCommandService;
180 | if (mcs != null)
181 | {
182 | // ToolWindow Command
183 | menuCommandId = new CommandID(GuidList.guidSccProviderCmdSet, icmdSolutionConfiguration);
184 | MenuCommand menuCmd = new MenuCommand(new EventHandler(Exec_menuCommand), menuCommandId);
185 | mcs.AddCommand(menuCmd);
186 |
187 | menuCheckOutFileCommandId = new CommandID(GuidList.guidSccProviderCmdSet, icmdCheckOutFile);
188 | MenuCommand menuCheckOutFileCmd = new MenuCommand(new EventHandler(Exec_menuCheckOutFileCommand), menuCheckOutFileCommandId);
189 | mcs.AddCommand(menuCheckOutFileCmd);
190 | }
191 |
192 | bool bP4SimpleSccEnabled = false;
193 |
194 | SettingsManager settingsManager = new ShellSettingsManager(this);
195 | if (settingsManager != null)
196 | {
197 | WritableSettingsStore store = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings);
198 | if (store.CollectionExists(@"P4SimpleScc"))
199 | {
200 | bP4SimpleSccEnabled = store.GetBoolean("P4SimpleScc", "bP4SimpleSccEnabled");
201 | }
202 | }
203 |
204 | if (bP4SimpleSccEnabled) // only enable this source control provider by default if it was enabled the last time Visual Studio was shut down
205 | {
206 | // Register the provider with the source control manager
207 | // If the package is to become active, this will also callback on OnActiveStateChange and the menu commands will be enabled
208 | IVsRegisterScciProvider rscp = (IVsRegisterScciProvider)GetService(typeof(IVsRegisterScciProvider));
209 | if (rscp != null)
210 | {
211 | rscp.RegisterSourceControlProvider(GuidList.guidSccProvider);
212 | }
213 | }
214 |
215 | if (GetSolutionFileName() != null) // this is the path that will be taken when the solution is opened by double clicking or being specified as a commandline argument when launching Visual Studio
216 | {
217 | if (solutionDirectory != null && solutionDirectory.Length > 0) // was solution already opened by the time we were loaded?
218 | {
219 | IVsSolutionPersistence solPersistence = (IVsSolutionPersistence)GetService(typeof(IVsSolutionPersistence));
220 | if (solPersistence != null)
221 | {
222 | // see if there are User Opts for this solution (this will load the Config settings and enable this provider if this solution is controlled by this provider
223 | solPersistence.LoadPackageUserOpts(this, _strSolutionUserOptionsKey);
224 | }
225 |
226 | if (!bSolutionLoadedOutputDone && (solutionDirectory != null) && (solutionDirectory.Length > 0))
227 | {
228 | string message = String.Format("Loaded solution: {0}\n", solutionFile);
229 | SccProvider.P4SimpleSccQueueOutput(message);
230 |
231 | bSolutionLoadedOutputDone = true;
232 | }
233 |
234 | AfterOpenSolutionOrFolder();
235 | }
236 | }
237 |
238 | OutputQueueTimer = new System.Windows.Threading.DispatcherTimer();
239 | OutputQueueTimer.Interval = TimeSpan.FromMilliseconds(100);
240 | OutputQueueTimer.Tick += OnTimerEvent;
241 | OutputQueueTimer.Start();
242 | }
243 |
244 | ///
245 | /// Unregister from receiving Solution Events and Project Documents, then release the resources used by the Package object
246 | ///
247 | /// true if the object is being disposed, false if it is being finalized
248 | protected override void Dispose(bool disposing)
249 | {
250 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
251 |
252 | sccService.Dispose();
253 |
254 | base.Dispose(disposing);
255 | }
256 |
257 | ///
258 | /// Checks whether the provider is invoked in command line mode
259 | ///
260 | public bool InCommandLineMode()
261 | {
262 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
263 |
264 | IVsShell shell = (IVsShell)GetService(typeof(SVsShell));
265 | if (shell != null)
266 | {
267 | object pvar;
268 | if (shell.GetProperty((int)__VSSPROPID.VSSPROPID_IsInCommandLineMode, out pvar) == VSConstants.S_OK &&
269 | (bool)pvar)
270 | {
271 | return true;
272 | }
273 | }
274 |
275 | return false;
276 | }
277 |
278 | ///
279 | /// This function is called by the IVsSccProvider service implementation when the active state of the provider changes
280 | /// If the package needs to refresh UI or perform other tasks, this is a good place to add the code
281 | ///
282 | public void OnActiveStateChange(bool bIsEnabled)
283 | {
284 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
285 |
286 | MsVsShell.OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as MsVsShell.OleMenuCommandService;
287 | if (mcs != null)
288 | {
289 | var menuCommand = mcs.FindCommand(menuCommandId);
290 | menuCommand.Enabled = bIsEnabled;
291 | menuCommand.Visible = bIsEnabled;
292 | }
293 |
294 | // store user settings to indicate if this source control provider was last enabled or disabled when Visual Studio shut down (so we can read it when it starts up again)
295 | SettingsManager settingsManager = new ShellSettingsManager(this);
296 | if (settingsManager != null)
297 | {
298 | WritableSettingsStore store = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings);
299 | if (!store.CollectionExists(@"P4SimpleScc"))
300 | {
301 | store.CreateCollection(@"P4SimpleScc");
302 | }
303 |
304 | store.SetBoolean("P4SimpleScc", "bP4SimpleSccEnabled", bIsEnabled);
305 | }
306 |
307 | if (bIsEnabled)
308 | {
309 | IVsRegisterScciProvider rscp = (IVsRegisterScciProvider)GetService(typeof(IVsRegisterScciProvider));
310 | if (rscp != null)
311 | {
312 | rscp.RegisterSourceControlProvider(GuidList.guidSccProvider);
313 | }
314 | }
315 | }
316 |
317 | ///
318 | /// Returns the name of the source control provider
319 | ///
320 | public string ProviderName
321 | {
322 | get { return _strProviderName; }
323 | }
324 |
325 | #endregion // Package Members
326 |
327 |
328 | #region Source Control Utility Functions
329 |
330 | ///
331 | /// Returns the filename of the solution
332 | ///
333 | public string GetSolutionFileName()
334 | {
335 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
336 |
337 | IVsSolution sol = (IVsSolution)GetService(typeof(SVsSolution));
338 | if (sol != null)
339 | {
340 | if (sol.GetSolutionInfo(out solutionDirectory, out solutionFile, out solutionUserOptions) == VSConstants.S_OK)
341 | {
342 | return solutionFile;
343 | }
344 | }
345 |
346 | return null;
347 | }
348 |
349 | #endregion // Source Control Utility Functions
350 |
351 |
352 | //--------------------------------------------------------------------------------
353 | // IVsPersistSolutionProps specific functions
354 | //--------------------------------------------------------------------------------
355 | #region IVsPersistSolutionProps interface functions
356 |
357 | public int SaveUserOptions(IVsSolutionPersistence pPersistence)
358 | {
359 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
360 |
361 | // The shell will create a stream for the section of interest, and will call back the provider on
362 | // IVsPersistSolutionProps.WriteUserOptions() to save specific options under the specified key.
363 | if (P4SimpleSccConfigDirty)
364 | {
365 | pPersistence.SavePackageUserOpts(this, _strSolutionUserOptionsKey);
366 | }
367 |
368 | return VSConstants.S_OK;
369 | }
370 |
371 | public int LoadUserOptions(IVsSolutionPersistence pPersistence, uint grfLoadOpts)
372 | {
373 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
374 |
375 | // Note this can be during opening a new solution, or may be during merging of 2 solutions.
376 | // The provider calls the shell back to let it know which options keys from the suo file were written by this provider.
377 | // If the shell will find in the suo file a section that belong to this package, it will create a stream,
378 | // and will call back the provider on IVsPersistSolutionProps.ReadUserOptions() to read specific options
379 | // under that option key.
380 | pPersistence.LoadPackageUserOpts(this, _strSolutionUserOptionsKey);
381 |
382 | return VSConstants.S_OK;
383 | }
384 |
385 | public int WriteUserOptions(IStream pOptionsStream, string pszKey)
386 | {
387 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
388 |
389 | // This function gets called by the shell to let the package write user options under the specified key.
390 | // The key was declared in SaveUserOptions(), when the shell started saving the suo file.
391 | Debug.Assert(pszKey.CompareTo(_strSolutionUserOptionsKey) == 0, "The shell called to read an key that doesn't belong to this package");
392 |
393 | if (pszKey == _strSolutionUserOptionsKey)
394 | {
395 | Config.Set(Config.KEY.SolutionConfigType, SolutionConfigType);
396 |
397 | Config.Save(out string config_string);
398 |
399 | byte[] buffer = new byte[config_string.Length];
400 | uint BytesWritten = 0;
401 |
402 | System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
403 | encoding.GetBytes(config_string, 0, config_string.Length, buffer, 0);
404 |
405 | pOptionsStream.Write(buffer, (uint)config_string.Length, out BytesWritten);
406 |
407 | P4SimpleSccConfigDirty = false;
408 | }
409 |
410 | return VSConstants.S_OK;
411 | }
412 |
413 | public int ReadUserOptions(IStream pOptionsStream, string pszKey)
414 | {
415 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
416 |
417 | // This function is called by the shell if the _strSolutionUserOptionsKey section declared
418 | // in LoadUserOptions() as being written by this package has been found in the suo file.
419 | // Note this can be during opening a new solution, or may be during merging of 2 solutions.
420 | // A good source control provider may need to persist this data until OnAfterOpenSolution or OnAfterMergeSolution is called
421 |
422 | bReadUserOptionsCalled = true;
423 |
424 | string config_string = "";
425 |
426 | if (pszKey == _strSolutionUserOptionsKey)
427 | {
428 | uint buffer_size = 4096; // allocate enough space for longest possible configuration string
429 | byte[] buffer = new byte[buffer_size];
430 | uint BytesRead = 0;
431 |
432 | pOptionsStream.Read(buffer, buffer_size, out BytesRead);
433 |
434 | config_string = System.Text.Encoding.UTF8.GetString(buffer).TrimEnd('\0'); // trim nulls from end of string
435 |
436 | if (config_string != "") // process the config settings
437 | {
438 | Config.Load(config_string);
439 |
440 | Config.Get(Config.KEY.SolutionConfigType, ref SolutionConfigType);
441 | Config.Get(Config.KEY.SolutionConfigCheckOutOnEdit, ref bCheckOutOnEdit);
442 | Config.Get(Config.KEY.SolutionConfigPromptForCheckout, ref bPromptForCheckout);
443 |
444 | // NOTE: We don't initialize P4Port, P4User or P4Client here as these are handled in SetP4SettingsForSolution based on SolutionConfigType setting
445 |
446 | Config.Get(Config.KEY.SolutionConfigVerboseOutput, ref bVerboseOutput);
447 | Config.Get(Config.KEY.SolutionConfigOutputEnabled, ref bOutputEnabled);
448 | }
449 | else
450 | {
451 | bUserSettingsWasEmpty = true; // no settings in the .suo file means P4SimpleScc was previously disabled (prior to verson 2.1)
452 | }
453 |
454 | if (bOutputEnabled)
455 | {
456 | CreateOutputPane();
457 | }
458 |
459 | GetSolutionFileName();
460 |
461 | if (!bSolutionLoadedOutputDone && (solutionDirectory != null) && (solutionDirectory.Length > 0))
462 | {
463 | string message = String.Format("Loaded solution: {0}\n", solutionFile);
464 | SccProvider.P4SimpleSccQueueOutput(message);
465 |
466 | bSolutionLoadedOutputDone = true;
467 | }
468 |
469 | if (SolutionConfigType != 0)
470 | {
471 | IVsRegisterScciProvider rscp = (IVsRegisterScciProvider)GetService(typeof(IVsRegisterScciProvider));
472 | rscp.RegisterSourceControlProvider(GuidList.guidSccProvider);
473 |
474 | SetP4SettingsForSolution(solutionDirectory);
475 |
476 | ServerConnect();
477 | }
478 | }
479 |
480 | return VSConstants.S_OK;
481 | }
482 |
483 | public int QuerySaveSolutionProps(IVsHierarchy pHierarchy, VSQUERYSAVESLNPROPS[] pqsspSave)
484 | {
485 | // we don't use the solution props as they modify the .sln file (which may or may not be under source control)
486 | return VSConstants.S_OK;
487 | }
488 |
489 | public int SaveSolutionProps(IVsHierarchy pHierarchy, IVsSolutionPersistence pPersistence)
490 | {
491 | // we don't use the solution props as they modify the .sln file (which may or may not be under source control)
492 | return VSConstants.S_OK;
493 | }
494 |
495 | public int WriteSolutionProps(IVsHierarchy pHierarchy, string pszKey, IPropertyBag pPropBag)
496 | {
497 | // we don't use the solution props as they modify the .sln file (which may or may not be under source control)
498 | return VSConstants.S_OK;
499 | }
500 |
501 | public int ReadSolutionProps(IVsHierarchy pHierarchy, string pszProjectName, string pszProjectMk, string pszKey, int fPreLoad, IPropertyBag pPropBag)
502 | {
503 | // we don't use the solution props as they modify the .sln file (which may or may not be under source control)
504 | return VSConstants.S_OK;
505 | }
506 |
507 | public int OnProjectLoadFailure(IVsHierarchy pStubHierarchy, string pszProjectName, string pszProjectMk, string pszKey)
508 | {
509 | return VSConstants.S_OK;
510 | }
511 |
512 | #endregion // IVsPersistSolutionProps
513 |
514 | public void AfterOpenSolutionOrFolder()
515 | {
516 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
517 |
518 | if (bReadUserOptionsCalled && bUserSettingsWasEmpty) // dirty hack to handle P4SimpleScc being disabled for a solution prior to version 2.1
519 | {
520 | P4SimpleSccConfigDirty = true; // we need to save these settings (so that we save the 'disabled' setting)
521 | }
522 |
523 | if (GetSolutionFileName() != null && !String.IsNullOrEmpty(solutionDirectory))
524 | {
525 | if (!bReadUserOptionsCalled) // don't automatically change anything if previous solution settings exists
526 | {
527 | // We may have a P4CONFIG file (https://www.perforce.com/manuals/v23.1/cmdref/Content/CmdRef/P4CONFIG.html),
528 | // in which case we want to default the solution config to automatic.
529 | // Start with checking whether P4 is even configured to look for one.
530 | ClassLibrary.P4Command p4 = new ClassLibrary.P4Command();
531 | p4.RunP4Set(solutionDirectory, out string P4Port, out string P4User, out string P4Client, out string P4Config, out string verbose);
532 | if (!string.IsNullOrEmpty(P4Config))
533 | {
534 | // The P4CONFIG environment variable is set, now go look for the file.
535 | string currentPath = solutionDirectory;
536 |
537 | // Loop until we reach the root directory.
538 | while (!string.IsNullOrEmpty(currentPath) && Directory.Exists(currentPath))
539 | {
540 | string filePath = Path.Combine(currentPath, P4Config);
541 |
542 | if (File.Exists(filePath))
543 | {
544 | // P4CONFIG exists! Start out defaulting to automatic and set up P4 settings.
545 | SolutionConfigType = 1;
546 | SetP4SettingsForSolution(solutionDirectory);
547 |
548 | p4.ServerConnect(out string stdout, out string stderr, out verbose, out bool bIsNotAllWrite);
549 |
550 | if (stderr != null && stderr.Length > 0) // if the P4CONFIG settings were not valid...
551 | {
552 | SolutionConfigType = 0; // revert back to P4SimpleScc being disabled
553 | }
554 | else
555 | {
556 | P4SimpleSccConfigDirty = true; // we need to save these settings
557 | }
558 |
559 | break;
560 | }
561 |
562 | var parentDir = Directory.GetParent(currentPath);
563 | if (parentDir == null)
564 | {
565 | break;
566 | }
567 | currentPath = parentDir.FullName;
568 | }
569 | }
570 | }
571 | }
572 | }
573 |
574 |
575 | #region Source Control Command Enabling
576 |
577 | public int QueryStatus(ref Guid guidCmdGroup, uint cCmds, OLECMD[] prgCmds, System.IntPtr pCmdText)
578 | {
579 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
580 |
581 | Debug.Assert(cCmds == 1, "Multiple commands");
582 | Debug.Assert(prgCmds != null, "NULL argument");
583 |
584 | if ((prgCmds == null))
585 | return VSConstants.E_INVALIDARG;
586 |
587 | // Filter out commands that are not defined by this package
588 | if ((guidCmdGroup != GuidList.guidSccProviderCmdSet) && (guidCmdGroup != GuidList.GuidOpenFolderExtensibilityPackageCmdSet))
589 | {
590 | return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED); ;
591 | }
592 |
593 | OLECMDF cmdf = OLECMDF.OLECMDF_SUPPORTED;
594 |
595 | // All source control commands needs to be hidden and disabled when the provider is not active
596 | if (!sccService.Active)
597 | {
598 | cmdf = cmdf | OLECMDF.OLECMDF_INVISIBLE;
599 | cmdf = cmdf & ~(OLECMDF.OLECMDF_ENABLED);
600 |
601 | prgCmds[0].cmdf = (uint)cmdf;
602 | return VSConstants.S_OK;
603 | }
604 |
605 | // Process our Commands
606 | switch (prgCmds[0].cmdID)
607 | {
608 | case icmdSolutionConfiguration:
609 | cmdf |= OLECMDF.OLECMDF_ENABLED;
610 | break;
611 |
612 | case icmdCheckOutFile:
613 | if (SolutionConfigType == 0)
614 | {
615 | cmdf = cmdf | OLECMDF.OLECMDF_INVISIBLE;
616 | cmdf = cmdf & ~(OLECMDF.OLECMDF_ENABLED);
617 | }
618 | else
619 | {
620 | cmdf |= QueryStatus_icmdCheckOutFile();
621 | }
622 | break;
623 |
624 | case icmdWorkspaceCheckOutFile:
625 | cmdf |= OLECMDF.OLECMDF_SUPPORTED;
626 | break;
627 |
628 | default:
629 | return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
630 | }
631 |
632 | prgCmds[0].cmdf = (uint)cmdf;
633 |
634 | return VSConstants.S_OK;
635 | }
636 |
637 | OLECMDF QueryStatus_icmdCheckOutFile()
638 | {
639 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
640 |
641 | if (GetSolutionFileName() == null)
642 | {
643 | return OLECMDF.OLECMDF_INVISIBLE;
644 | }
645 |
646 | bool bAllFilesAreCheckedOut = true; // assume all files are checked out
647 | FilenameList = new List();
648 |
649 | if (bIsWorkspace) // is a Workspace open (i.e. "Open Folder"), if not, we assume we opened a Solution
650 | {
651 | if (WindowActivatedFilename != "")
652 | {
653 | FilenameList.Add(WindowActivatedFilename);
654 |
655 | bool bIsCheckedOut = IsCheckedOut(WindowActivatedFilename, out string stderr);
656 |
657 | bool bShouldIgnoreStatus = false;
658 | if (stderr.Contains("is not under client's root") || stderr.Contains("not in client view") || stderr.Contains("no such file")) // if file is outside client's workspace, or file does not exist in source control...
659 | {
660 | bShouldIgnoreStatus = true; // don't prevent file from being modified (since not under workspace or not under source control)
661 | }
662 |
663 | if (!bIsCheckedOut && !bShouldIgnoreStatus)
664 | {
665 | bAllFilesAreCheckedOut = false;
666 | }
667 | }
668 | }
669 | else
670 | {
671 | IList sel = GetSelectedNodes();
672 |
673 | foreach (VSITEMSELECTION item in sel)
674 | {
675 | try
676 | {
677 | string Filename = "";
678 |
679 | if ((item.pHier == null) || ((item.pHier as IVsSolution) != null))
680 | {
681 | Filename = GetSolutionFileName();
682 | }
683 | else if ((IVsProject)item.pHier != null)
684 | {
685 | IVsProject Project = (IVsProject)item.pHier;
686 |
687 | Project.GetMkDocument(item.itemid, out Filename);
688 | }
689 |
690 | if (Filename != "")
691 | {
692 | FilenameList.Add(Filename);
693 |
694 | bool bIsCheckedOut = IsCheckedOut(Filename, out string stderr);
695 |
696 | bool bShouldIgnoreStatus = false;
697 | if (stderr.Contains("is not under client's root") || stderr.Contains("not in client view") || stderr.Contains("no such file")) // if file is outside client's workspace, or file does not exist in source control...
698 | {
699 | bShouldIgnoreStatus = true; // don't prevent file from being modified (since not under workspace or not under source control)
700 | }
701 |
702 | if (!bIsCheckedOut && !bShouldIgnoreStatus)
703 | {
704 | bAllFilesAreCheckedOut = false;
705 | }
706 | }
707 | }
708 | catch (Exception ex)
709 | {
710 | Debug.WriteLine(string.Format(CultureInfo.CurrentUICulture, "Exception: ex.message = {0}", ex.Message));
711 | }
712 | }
713 | }
714 |
715 | if (bAllFilesAreCheckedOut)
716 | {
717 | return OLECMDF.OLECMDF_SUPPORTED;
718 | }
719 |
720 | return OLECMDF.OLECMDF_ENABLED;
721 | }
722 |
723 | ///
724 | /// Gets the list of directly selected VSITEMSELECTION objects
725 | ///
726 | /// A list of VSITEMSELECTION objects
727 | private IList GetSelectedNodes()
728 | {
729 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
730 |
731 | // Retrieve shell interface in order to get current selection
732 | IVsMonitorSelection monitorSelection = this.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection;
733 | Debug.Assert(monitorSelection != null, "Could not get the IVsMonitorSelection object from the services exposed by this project");
734 | if (monitorSelection == null)
735 | {
736 | throw new InvalidOperationException();
737 | }
738 |
739 | List selectedNodes = new List();
740 | IntPtr hierarchyPtr = IntPtr.Zero;
741 | IntPtr selectionContainer = IntPtr.Zero;
742 | try
743 | {
744 | // Get the current project hierarchy, project item, and selection container for the current selection
745 | // If the selection spans multiple hierachies, hierarchyPtr is Zero
746 | uint itemid;
747 | IVsMultiItemSelect multiItemSelect = null;
748 | ErrorHandler.ThrowOnFailure(monitorSelection.GetCurrentSelection(out hierarchyPtr, out itemid, out multiItemSelect, out selectionContainer));
749 |
750 | if (itemid != VSConstants.VSITEMID_SELECTION)
751 | {
752 | // We only care if there are nodes selected in the tree
753 | if (itemid != VSConstants.VSITEMID_NIL)
754 | {
755 | if (hierarchyPtr == IntPtr.Zero)
756 | {
757 | // Solution is selected
758 | VSITEMSELECTION vsItemSelection;
759 | vsItemSelection.pHier = null;
760 | vsItemSelection.itemid = itemid;
761 | selectedNodes.Add(vsItemSelection);
762 | }
763 | else
764 | {
765 | IVsHierarchy hierarchy = (IVsHierarchy)Marshal.GetObjectForIUnknown(hierarchyPtr);
766 | // Single item selection
767 | VSITEMSELECTION vsItemSelection;
768 | vsItemSelection.pHier = hierarchy;
769 | vsItemSelection.itemid = itemid;
770 | selectedNodes.Add(vsItemSelection);
771 | }
772 | }
773 | }
774 | else
775 | {
776 | if (multiItemSelect != null)
777 | {
778 | // This is a multiple item selection.
779 |
780 | //Get number of items selected and also determine if the items are located in more than one hierarchy
781 | uint numberOfSelectedItems;
782 | int isSingleHierarchyInt;
783 | ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectionInfo(out numberOfSelectedItems, out isSingleHierarchyInt));
784 | bool isSingleHierarchy = (isSingleHierarchyInt != 0);
785 |
786 | // Now loop all selected items and add them to the list
787 | Debug.Assert(numberOfSelectedItems > 0, "Bad number of selected itemd");
788 | if (numberOfSelectedItems > 0)
789 | {
790 | VSITEMSELECTION[] vsItemSelections = new VSITEMSELECTION[numberOfSelectedItems];
791 | ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectedItems(0, numberOfSelectedItems, vsItemSelections));
792 | foreach (VSITEMSELECTION vsItemSelection in vsItemSelections)
793 | {
794 | selectedNodes.Add(vsItemSelection);
795 | }
796 | }
797 | }
798 | }
799 | }
800 | finally
801 | {
802 | if (hierarchyPtr != IntPtr.Zero)
803 | {
804 | Marshal.Release(hierarchyPtr);
805 | }
806 | if (selectionContainer != IntPtr.Zero)
807 | {
808 | Marshal.Release(selectionContainer);
809 | }
810 | }
811 |
812 | return selectedNodes;
813 | }
814 |
815 | #endregion // Source Control Command Enabling
816 |
817 |
818 | #region Source Control Commands Execution
819 |
820 | private void Exec_menuCommand(object sender, EventArgs e)
821 | {
822 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
823 |
824 | GetSolutionFileName();
825 |
826 | // see if we have a solution loaded or not
827 | if (solutionDirectory == null || solutionDirectory == "")
828 | {
829 | string message = "There is no solution loaded. You need to load a solution before applying settings.";
830 | MsVsShell.VsShellUtilities.ShowMessageBox(package, message, "P4SimpleScc Warning", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
831 |
832 | return;
833 | }
834 |
835 | int pos_x = -1;
836 | Config.Get(Config.KEY.SolutionConfigDialogPosX, ref pos_x);
837 | int pos_y = -1;
838 | Config.Get(Config.KEY.SolutionConfigDialogPosY, ref pos_y);
839 |
840 | SolutionConfigForm Dialog = new SolutionConfigForm(pos_x, pos_y, solutionDirectory, SolutionConfigType, bCheckOutOnEdit, bPromptForCheckout, bVerboseOutput, bOutputEnabled, P4Port, P4User, P4Client, VerboseOutput);
841 |
842 | System.Windows.Forms.DialogResult result = Dialog.ShowDialog();
843 |
844 | if (Dialog.PosX != pos_x || Dialog.PosY != pos_y)
845 | {
846 | Config.Set(Config.KEY.SolutionConfigDialogPosX, Dialog.PosX);
847 | Config.Set(Config.KEY.SolutionConfigDialogPosY, Dialog.PosY);
848 |
849 | P4SimpleSccConfigDirty = true; // we need to save these settings
850 | }
851 |
852 | if (result == System.Windows.Forms.DialogResult.OK)
853 | {
854 | // set the global configuration settings
855 | SolutionConfigType = Dialog.SolutionConfigType;
856 | bCheckOutOnEdit = Dialog.bCheckOutOnEdit;
857 | bPromptForCheckout = Dialog.bPromptForCheckout;
858 | bVerboseOutput = Dialog.bVerboseOutput;
859 | bOutputEnabled = Dialog.bOutputEnabled;
860 |
861 | if (bOutputEnabled && (OutputPaneGuid == Guid.Empty)) // if output is enabled and there's no output pane, then create one
862 | {
863 | CreateOutputPane();
864 | }
865 | else if (!bOutputEnabled && (OutputPaneGuid != Guid.Empty)) // else if output is not enabled and there is an output pane, then remove it
866 | {
867 | RemoveOutputPane();
868 | }
869 |
870 | if (Dialog.SolutionConfigType == 2) // manual settings
871 | {
872 | P4Port = Dialog.P4Port;
873 | P4User = Dialog.P4User;
874 | P4Client = Dialog.P4Client;
875 | }
876 | else
877 | {
878 | P4Port = "";
879 | P4User = "";
880 | P4Client = "";
881 | }
882 |
883 | // save all the config settings
884 | Config.Set(Config.KEY.SolutionConfigType, SolutionConfigType);
885 | Config.Set(Config.KEY.SolutionConfigCheckOutOnEdit, bCheckOutOnEdit);
886 | Config.Set(Config.KEY.SolutionConfigPromptForCheckout, bPromptForCheckout);
887 |
888 | // these will be blank for all configurations except 'manual settings' (SetP4SettingsForSolution will re-initialize them at runtime)
889 | Config.Set(Config.KEY.SolutionConfigDialogP4Port, P4Port);
890 | Config.Set(Config.KEY.SolutionConfigDialogP4User, P4User);
891 | Config.Set(Config.KEY.SolutionConfigDialogP4Client, P4Client);
892 |
893 | Config.Set(Config.KEY.SolutionConfigVerboseOutput, bVerboseOutput);
894 | Config.Set(Config.KEY.SolutionConfigOutputEnabled, bOutputEnabled);
895 |
896 | P4SimpleSccConfigDirty = true; // we need to write these to the solution .suo file
897 |
898 | SetP4SettingsForSolution(solutionDirectory); // update the settings based on the new SolutionConfigType
899 |
900 | ServerConnect(); // attempt to connect to the server using the new settings
901 | }
902 |
903 | }
904 |
905 | private void Exec_menuCheckOutFileCommand(object sender, EventArgs e)
906 | {
907 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
908 |
909 | foreach (string Filename in FilenameList)
910 | {
911 | CheckOutFile(Filename);
912 | }
913 | }
914 |
915 | #endregion // Source Control Commands Execution
916 |
917 |
918 | public void SetP4SettingsForSolution(string solutionDirectory)
919 | {
920 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
921 |
922 | if (solutionDirectory != null && solutionDirectory.Length > 0) // only set the P4 settings if we have a valid solution directory
923 | {
924 | // clear previous settings and load everything from the config settings for this solution
925 | P4Port = "";
926 | P4User = "";
927 | P4Client = "";
928 |
929 | if (SolutionConfigType == 1) // if automatic settings
930 | {
931 | P4Command p4 = new P4Command();
932 | p4.RunP4Set(solutionDirectory, out P4Port, out P4User, out P4Client, out string verbose);
933 |
934 | if (bVerboseOutput) // we don't need to worry about stderr output here since "p4 set" should never fail to run (as long as P4V is installed)
935 | {
936 | if (!verbose.EndsWith("\n"))
937 | {
938 | verbose += "\n";
939 | }
940 | P4SimpleSccQueueOutput(verbose);
941 | }
942 | }
943 | else if (SolutionConfigType == 2) // if manual settings
944 | {
945 | // always override all settings with the manual settings (from the Config class)
946 | Config.Get(Config.KEY.SolutionConfigDialogP4Port, ref P4Port);
947 | Config.Get(Config.KEY.SolutionConfigDialogP4User, ref P4User);
948 | Config.Get(Config.KEY.SolutionConfigDialogP4Client, ref P4Client);
949 | }
950 |
951 | string message = "";
952 |
953 | if (SolutionConfigType == 0)
954 | {
955 | message = String.Format("P4SimpleScc is disabled for this solution.\n");
956 | }
957 | else if (SolutionConfigType == 1)
958 | {
959 | message = String.Format("Using automatic settings: P4PORT={0}, P4USER={1}, P4CLIENT={2}\n", P4Port, P4User, P4Client);
960 | }
961 | else if (SolutionConfigType == 2)
962 | {
963 | message = String.Format("Using manual settings: P4PORT={0}, P4USER={1}, P4CLIENT={2}\n", P4Port, P4User, P4Client);
964 | }
965 |
966 | if (message != "")
967 | {
968 | P4SimpleSccQueueOutput(message);
969 | }
970 |
971 | // set the P4Command environment variables for future "p4" commands (like "p4 edit", etc.)
972 | P4Command.SetEnv(P4Port, P4User, P4Client);
973 | }
974 | }
975 |
976 | public void ServerConnect()
977 | {
978 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
979 |
980 | if (SolutionConfigType != 0) // not disabled?
981 | {
982 | P4Command p4 = new P4Command();
983 |
984 | p4.ServerConnect(out string stdout, out string stderr, out string verbose, out bIsNotAllWrite);
985 |
986 | if (bVerboseOutput)
987 | {
988 | if (!verbose.EndsWith("\n"))
989 | {
990 | verbose += "\n";
991 | }
992 | P4SimpleSccQueueOutput(verbose);
993 | }
994 |
995 | if (stderr != null && stderr.Length > 0)
996 | {
997 | P4SimpleSccQueueOutput("Connection to server failed!\n");
998 | P4SimpleSccQueueOutput(stderr);
999 |
1000 | string message = "Connection to server failed.\n" + stderr;
1001 | MsVsShell.VsShellUtilities.ShowMessageBox(package, message, "P4SimpleScc Error", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
1002 | }
1003 | else
1004 | {
1005 | P4SimpleSccQueueOutput("Connection to server successful.\n");
1006 | }
1007 | }
1008 | }
1009 |
1010 | public static bool IsCheckedOut(string Filename, out string stderr)
1011 | {
1012 | stderr = "";
1013 | bool status = false;
1014 |
1015 | if (SolutionConfigType != 0) // not disabled?
1016 | {
1017 | if (bIsNotAllWrite)
1018 | {
1019 | FileInfo info = new FileInfo(Filename);
1020 |
1021 | if (!info.IsReadOnly)
1022 | {
1023 | return true;
1024 | }
1025 | }
1026 |
1027 | P4Command p4 = new P4Command();
1028 |
1029 | status = p4.IsCheckedOut(Filename, out string stdout, out stderr, out string verbose); // ignore stderr here since failure will be treated as if file is not checked out
1030 |
1031 | if (bVerboseOutput)
1032 | {
1033 | if (!verbose.EndsWith("\n"))
1034 | {
1035 | verbose += "\n";
1036 | }
1037 | P4SimpleSccQueueOutput(verbose);
1038 | }
1039 | }
1040 |
1041 | return status;
1042 | }
1043 |
1044 | public static bool CheckOutFile(string Filename)
1045 | {
1046 | if (SolutionConfigType != 0) // not disabled?
1047 | {
1048 | if (File.Exists(Filename)) // if the file doesn't exist, then it can't be in source control (i.e. brand new file)
1049 | {
1050 | P4Command p4 = new P4Command();
1051 |
1052 | P4Command.CheckOutStatus status = p4.CheckOutFile(Filename, out string stdout, out string stderr, out string verbose);
1053 |
1054 | if (bVerboseOutput)
1055 | {
1056 | if (!verbose.EndsWith("\n"))
1057 | {
1058 | verbose += "\n";
1059 | }
1060 | P4SimpleSccQueueOutput(verbose);
1061 | }
1062 |
1063 | // if file already checked out, or file not in client's root (outside workspace), or file is not in source control (brand new file, not yet added to source control)
1064 | if (status == P4Command.CheckOutStatus.FileAlreadyCheckedOut || status == P4Command.CheckOutStatus.FileNotInSourceControl)
1065 | {
1066 | return true;
1067 | }
1068 |
1069 | if (stderr != null && stderr.Length > 0)
1070 | {
1071 | string message = String.Format("Check out of '{0}' failed.\n", Filename);
1072 | P4SimpleSccQueueOutput(message);
1073 | P4SimpleSccQueueOutput(stderr);
1074 |
1075 | string dialog_message = message + stderr;
1076 | MsVsShell.VsShellUtilities.ShowMessageBox(package, dialog_message, "P4SimpleScc Error", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
1077 |
1078 | return false;
1079 | }
1080 | else
1081 | {
1082 | string message = String.Format("Check out of '{0}' successful.\n", Filename);
1083 | P4SimpleSccQueueOutput(message);
1084 |
1085 | return true;
1086 | }
1087 | }
1088 | }
1089 |
1090 | return true; // return good status if P4SimpleScc is disabled for this solution
1091 | }
1092 |
1093 | public void VerboseOutput(string message)
1094 | {
1095 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
1096 |
1097 | if (OutputPaneGuid == Guid.Empty)
1098 | {
1099 | CreateOutputPane();
1100 | }
1101 |
1102 | string output = message;
1103 |
1104 | if (!message.EndsWith("\n")) // add newline character at end if there isn't one
1105 | {
1106 | output += "\n";
1107 | }
1108 |
1109 | P4SimpleSccQueueOutput(output, true);
1110 | }
1111 |
1112 | ///
1113 | /// This function will create the P4SimpleScc Output window (for logging purposes for P4 commands)
1114 | ///
1115 | private void CreateOutputPane()
1116 | {
1117 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
1118 |
1119 | if (OutputPaneGuid != Guid.Empty)
1120 | {
1121 | return;
1122 | }
1123 |
1124 | // Create a new output pane.
1125 | IVsOutputWindow output = GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;
1126 |
1127 | OutputPaneGuid = Guid.NewGuid();
1128 |
1129 | bool visible = true;
1130 | bool clearWithSolution = false;
1131 | output.CreatePane(ref OutputPaneGuid, "P4SimpleScc", Convert.ToInt32(visible), Convert.ToInt32(clearWithSolution));
1132 | }
1133 |
1134 | ///
1135 | /// This function will remove the P4SimpleScc Output window (for logging purposes for P4 commands)
1136 | ///
1137 | private void RemoveOutputPane()
1138 | {
1139 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
1140 |
1141 | IVsOutputWindow output = GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;
1142 |
1143 | // Remove the existing output pane
1144 | output.DeletePane(ref OutputPaneGuid);
1145 |
1146 | OutputPaneGuid = Guid.Empty;
1147 | }
1148 |
1149 | ///
1150 | /// This function will output a line of text to the P4SimpleScc Output window (for logging purposes for P4 commands)
1151 | ///
1152 | /// text string to be output to the P4SimpleScc Output window (user must include \n character for newline at the end of the text string)
1153 | public static void P4SimpleSccQueueOutput(string text, bool bForceOutput = false) // user must include \n newline characters
1154 | {
1155 | if (bOutputEnabled || bForceOutput)
1156 | {
1157 | string OutputMessage = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss - ") + text;
1158 |
1159 | lock (OpututQueueLock)
1160 | {
1161 | OutputQueueList.Add(OutputMessage);
1162 | }
1163 | }
1164 | }
1165 |
1166 | private void OnTimerEvent(object sender, EventArgs e)
1167 | {
1168 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
1169 |
1170 | lock (OpututQueueLock)
1171 | {
1172 | IVsOutputWindow output = GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;
1173 |
1174 | if (output != null)
1175 | {
1176 | // Retrieve the output pane.
1177 | output.GetPane(ref OutputPaneGuid, out IVsOutputWindowPane OutputPane);
1178 |
1179 | if (OutputPane != null)
1180 | {
1181 | foreach( string message in OutputQueueList)
1182 | {
1183 | OutputPane.OutputString(message);
1184 | }
1185 | }
1186 | }
1187 |
1188 | OutputQueueList.Clear();
1189 | }
1190 | }
1191 |
1192 | private void WindowEventsOnWindowActivated(EnvDTE.Window gotFocus, EnvDTE.Window lostFocus)
1193 | {
1194 | MsVsShell.ThreadHelper.ThrowIfNotOnUIThread();
1195 |
1196 | if (gotFocus.Kind != "Document")
1197 | {
1198 | return; //It's not a document (e.g. it's a tool window)
1199 | }
1200 |
1201 | WindowActivatedFilename = gotFocus.Document.FullName;
1202 | }
1203 | }
1204 | }
1205 |
--------------------------------------------------------------------------------
/SharedProject/SccProviderService.cs:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 | THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
5 | ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
6 | IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
7 | PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
8 |
9 | ***************************************************************************/
10 |
11 | // SccProviderService.cs : Implementation of Sample Source Control Provider Service
12 |
13 | using System;
14 | using System.IO;
15 | using System.Diagnostics;
16 | using System.Globalization;
17 | using System.Runtime.InteropServices;
18 | using Microsoft.VisualStudio.Shell.Interop;
19 | using Microsoft.VisualStudio;
20 | using Microsoft.VisualStudio.Shell;
21 |
22 | namespace P4SimpleScc
23 | {
24 | [Guid("B205A1B6-1000-4A1C-8680-97FD2219C692")]
25 | public class SccProviderService :
26 | IVsSccProvider, // Required for provider registration with source control manager
27 | IVsSccManager2, // Base source control functionality interface
28 | IVsSolutionEvents, // We'll register for solution events, these are useful for source control
29 | IVsSolutionEvents7, // We'll register for folder events, these are useful for source control
30 | IVsQueryEditQuerySave2, // Required to allow editing of controlled files
31 | IDisposable
32 | {
33 | // Whether the provider is active or not
34 | private bool _active = false;
35 | // The service and source control provider
36 | private SccProvider _sccProvider = null;
37 | // The cookie for solution events
38 | private uint _vsSolutionEventsCookie;
39 |
40 |
41 | #region SccProvider Service initialization/unitialization
42 |
43 | public SccProviderService(SccProvider sccProvider)
44 | {
45 | ThreadHelper.ThrowIfNotOnUIThread();
46 |
47 | Debug.Assert(null != sccProvider);
48 | _sccProvider = sccProvider;
49 |
50 | // Subscribe to solution events
51 | IVsSolution sol = (IVsSolution)_sccProvider.GetService(typeof(SVsSolution));
52 | sol.AdviseSolutionEvents(this, out _vsSolutionEventsCookie);
53 | Debug.Assert(VSConstants.VSCOOKIE_NIL != _vsSolutionEventsCookie);
54 | }
55 |
56 | ///
57 | /// Unregister from receiving Solution Events and Project Documents
58 | ///
59 | public void Dispose()
60 | {
61 | ThreadHelper.ThrowIfNotOnUIThread();
62 |
63 | // Unregister from receiving solution events
64 | if (VSConstants.VSCOOKIE_NIL != _vsSolutionEventsCookie)
65 | {
66 | IVsSolution sol = (IVsSolution)_sccProvider.GetService(typeof(SVsSolution));
67 | sol.UnadviseSolutionEvents(_vsSolutionEventsCookie);
68 | _vsSolutionEventsCookie = VSConstants.VSCOOKIE_NIL;
69 | }
70 | }
71 |
72 | #endregion // SccProvider
73 |
74 |
75 | //----------------------------------------------------------------------------
76 | // IVsSccProvider specific functions
77 | //--------------------------------------------------------------------------------
78 | #region IVsSccProvider interface functions
79 |
80 | public int SetActive()
81 | {
82 | ThreadHelper.ThrowIfNotOnUIThread();
83 |
84 | Debug.WriteLine(string.Format(CultureInfo.CurrentUICulture, "The source control provider is now active"));
85 |
86 | _active = true;
87 | _sccProvider.OnActiveStateChange(true);
88 |
89 | return VSConstants.S_OK;
90 | }
91 |
92 | public int SetInactive()
93 | {
94 | ThreadHelper.ThrowIfNotOnUIThread();
95 |
96 | Debug.WriteLine(string.Format(CultureInfo.CurrentUICulture, "The source control provider is now inactive"));
97 |
98 | _active = false;
99 | _sccProvider.OnActiveStateChange(false);
100 |
101 | return VSConstants.S_OK;
102 | }
103 |
104 | public int AnyItemsUnderSourceControl(out int pfResult)
105 | {
106 | if (!_active)
107 | {
108 | pfResult = 0;
109 | }
110 | else
111 | {
112 | // Although the parameter is an int, it's in reality a BOOL value, so let's return 0/1 values
113 | pfResult = (SccProvider.SolutionConfigType != 0) ? 1 : 0;
114 | }
115 |
116 | return VSConstants.S_OK;
117 | }
118 |
119 | #endregion // IVsSccProvider
120 |
121 |
122 | //----------------------------------------------------------------------------
123 | // IVsSccManager2 specific functions
124 | //--------------------------------------------------------------------------------
125 | #region IVsSccManager2 interface functions
126 |
127 | public int RegisterSccProject(IVsSccProject2 pscp2Project, string pszSccProjectName, string pszSccAuxPath, string pszSccLocalPath, string pszProvider)
128 | {
129 | ThreadHelper.ThrowIfNotOnUIThread();
130 |
131 | if (pszProvider.CompareTo(_sccProvider.ProviderName)!=0)
132 | {
133 | // If the provider name controlling this project is not our provider, the user may be adding to a
134 | // solution controlled by this provider an existing project controlled by some other provider.
135 | // We'll deny the registration with scc in such case.
136 | return VSConstants.E_FAIL;
137 | }
138 |
139 | if (pscp2Project == null)
140 | {
141 | Debug.WriteLine(string.Format(CultureInfo.CurrentUICulture, "Solution {0} is registering with source control", _sccProvider.GetSolutionFileName()));
142 | }
143 |
144 | return VSConstants.S_OK;
145 | }
146 |
147 | public int UnregisterSccProject(IVsSccProject2 pscp2Project)
148 | {
149 | return VSConstants.S_OK;
150 | }
151 |
152 | public int GetSccGlyph(int cFiles, string[] rgpszFullPaths, VsStateIcon[] rgsiGlyphs, uint[] rgdwSccStatus)
153 | {
154 | return VSConstants.S_OK;
155 | }
156 |
157 | public int GetSccGlyphFromStatus(uint dwSccStatus, VsStateIcon[] psiGlyph)
158 | {
159 | psiGlyph[0] = VsStateIcon.STATEICON_BLANK;
160 | return VSConstants.S_OK;
161 | }
162 |
163 | public int IsInstalled(out int pbInstalled)
164 | {
165 | // All source control packages should always return S_OK and set pbInstalled to nonzero
166 | pbInstalled = 1;
167 | return VSConstants.S_OK;
168 | }
169 |
170 | public int BrowseForProject(out string pbstrDirectory, out int pfOK)
171 | {
172 | // Obsolete method
173 | pbstrDirectory = null;
174 | pfOK = 0;
175 | return VSConstants.E_NOTIMPL;
176 | }
177 |
178 | public int CancelAfterBrowseForProject()
179 | {
180 | // Obsolete method
181 | return VSConstants.E_NOTIMPL;
182 | }
183 |
184 | #endregion // IVsSccManager2
185 |
186 |
187 | //----------------------------------------------------------------------------
188 | // IVsSolutionEvents specific functions
189 | //--------------------------------------------------------------------------------
190 | #region IVsSolutionEvents interface functions
191 |
192 | public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
193 | {
194 | return VSConstants.S_OK;
195 | }
196 |
197 | public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
198 | {
199 | return VSConstants.S_OK;
200 | }
201 |
202 | public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
203 | {
204 | return VSConstants.S_OK;
205 | }
206 |
207 | public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
208 | {
209 | return VSConstants.S_OK;
210 | }
211 |
212 | public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
213 | {
214 | return VSConstants.S_OK;
215 | }
216 |
217 | public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
218 | {
219 | return VSConstants.S_OK;
220 | }
221 |
222 | public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
223 | {
224 | ThreadHelper.ThrowIfNotOnUIThread();
225 | _sccProvider.AfterOpenSolutionOrFolder();
226 |
227 | _sccProvider.bIsWorkspace = false; // we opened a solution, not a folder
228 |
229 | return VSConstants.S_OK;
230 | }
231 |
232 | public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
233 | {
234 | return VSConstants.S_OK;
235 | }
236 |
237 | public int OnBeforeCloseSolution(object pUnkReserved)
238 | {
239 | ThreadHelper.ThrowIfNotOnUIThread();
240 | return OnBeforeCloseSolutionOrFolder();
241 | }
242 |
243 | public int OnAfterCloseSolution(object pUnkReserved)
244 | {
245 | return VSConstants.S_OK;
246 | }
247 |
248 | #endregion // IVsSolutionEvents
249 |
250 |
251 | //----------------------------------------------------------------------------
252 | // IVsSolutionEvents7 specific functions
253 | //--------------------------------------------------------------------------------
254 | #region IVsSolutionEvents7 interface functions
255 | public void OnAfterOpenFolder(string folderPath)
256 | {
257 | ThreadHelper.ThrowIfNotOnUIThread();
258 | _sccProvider.AfterOpenSolutionOrFolder();
259 |
260 | _sccProvider.bIsWorkspace = true; // we opened a folder, not a solution
261 | }
262 |
263 | public void OnQueryCloseFolder(string folderPath, ref int pfCancel)
264 | {
265 | }
266 |
267 | public void OnBeforeCloseFolder(string folderPath)
268 | {
269 | ThreadHelper.ThrowIfNotOnUIThread();
270 | OnBeforeCloseSolutionOrFolder();
271 | }
272 |
273 | public void OnAfterCloseFolder(string folderPath)
274 | {
275 | }
276 |
277 | [Obsolete("This API is no longer supported by Visual Studio.")]
278 | public void OnAfterLoadAllDeferredProjects()
279 | { }
280 | #endregion // IVsSolutionEvents7
281 |
282 | //----------------------------------------------------------------------------
283 | // IVsQueryEditQuerySave2 specific functions
284 | //--------------------------------------------------------------------------------
285 | #region IVsQueryEditQuerySave2 interface functions
286 |
287 | public int QueryEditFiles(uint rgfQueryEdit, int cFiles, string[] rgpszMkDocuments, uint[] rgrgf, VSQEQS_FILE_ATTRIBUTE_DATA[] rgFileInfo, out uint pfEditVerdict, out uint prgfMoreInfo)
288 | {
289 | ThreadHelper.ThrowIfNotOnUIThread();
290 |
291 | // Initialize output variables
292 | pfEditVerdict = (uint)tagVSQueryEditResult.QER_EditOK;
293 | prgfMoreInfo = 0;
294 |
295 | if ((rgfQueryEdit & (uint)tagVSQueryEditFlags.QEF_ReportOnly) != 0) // ignore any report query
296 | {
297 | return VSConstants.S_OK;
298 | }
299 |
300 | // In non-UI mode just allow the edit, because the user cannot be asked what to do with the file
301 | if (_sccProvider.InCommandLineMode())
302 | {
303 | return VSConstants.S_OK;
304 | }
305 |
306 | if (SccProvider.SolutionConfigType != 0) // if not disabled...
307 | {
308 | if (_sccProvider.bCheckOutOnEdit)
309 | {
310 | try
311 | {
312 | //Iterate through all the files
313 | for (int iFile = 0; iFile < cFiles; iFile++)
314 | {
315 | uint fEditVerdict = (uint)tagVSQueryEditResult.QER_EditNotOK; // assume not okay until known otherwise
316 | uint fMoreInfo = 0;
317 |
318 | bool fileExists = File.Exists(rgpszMkDocuments[iFile]);
319 |
320 | if (!fileExists) // if the file doesn't exist then it's okay to edit as this is a brand new file (not saved yet)
321 | {
322 | fEditVerdict = (uint)tagVSQueryEditResult.QER_EditOK;
323 | }
324 | else
325 | {
326 | bool bIsCheckoutOkay = true;
327 | bool bIsCheckedOut = SccProvider.IsCheckedOut(rgpszMkDocuments[iFile], out string stderr);
328 |
329 | bool bShouldIgnoreStatus = false;
330 | if (stderr.Contains("is not under client's root") || stderr.Contains("not in client view") || stderr.Contains("no such file")) // if file is outside client's workspace, or file does not exist in source control...
331 | {
332 | bShouldIgnoreStatus = true; // don't prevent file from being modified (since not under workspace or not under source control)
333 | }
334 |
335 | if (_sccProvider.bPromptForCheckout && !bIsCheckedOut && !bShouldIgnoreStatus) // if prompt for permission to check out and file is not checked out...
336 | {
337 | IVsUIShell uiShell = ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
338 |
339 | string message = String.Format("Do you want to check out: '{0}'?", rgpszMkDocuments[iFile]);
340 | if (!VsShellUtilities.PromptYesNo(message, "Warning", OLEMSGICON.OLEMSGICON_WARNING, uiShell))
341 | {
342 | bIsCheckoutOkay = false;
343 |
344 | pfEditVerdict = (uint)tagVSQueryEditResult.QER_NoEdit_UserCanceled;
345 | prgfMoreInfo = (uint)tagVSQueryEditResultFlags.QER_CheckoutCanceledOrFailed;
346 | }
347 | }
348 |
349 | if (bIsCheckoutOkay)
350 | {
351 | if (bIsCheckedOut || bShouldIgnoreStatus || SccProvider.CheckOutFile(rgpszMkDocuments[iFile])) // if file is already checked out or if we were able to check out the file, then edit is okay
352 | {
353 | fEditVerdict = (uint)tagVSQueryEditResult.QER_EditOK;
354 | fMoreInfo = (uint)tagVSQueryEditResultFlags.QER_MaybeCheckedout;
355 | }
356 | else
357 | {
358 | // if we couldn't check out the file, it's okay to edit it in memory, when the file is saved, it will try to check it out and dislay a "Save As" dialog if it couldn't be checked out
359 | fEditVerdict = (uint)tagVSQueryEditResult.QER_EditOK;
360 | fMoreInfo = (uint)tagVSQueryEditResultFlags.QER_InMemoryEdit;
361 | }
362 | }
363 | }
364 |
365 | // It's a bit unfortunate that we have to return only one set of flags for all the files involved in the operation
366 | // The edit can continue if all the files were approved for edit
367 | prgfMoreInfo |= fMoreInfo;
368 | pfEditVerdict |= fEditVerdict;
369 | }
370 | }
371 | catch(Exception)
372 | {
373 | // If an exception was caught, do not allow the edit
374 | pfEditVerdict = (uint)tagVSQueryEditResult.QER_EditNotOK;
375 | prgfMoreInfo = (uint)tagVSQueryEditResultFlags.QER_EditNotPossible;
376 | }
377 | }
378 | }
379 |
380 | return VSConstants.S_OK;
381 | }
382 |
383 | public int QuerySaveFiles(uint rgfQuerySave, int cFiles, string[] rgpszMkDocuments, uint[] rgrgf, VSQEQS_FILE_ATTRIBUTE_DATA[] rgFileInfo, out uint pdwQSResult)
384 | {
385 | ThreadHelper.ThrowIfNotOnUIThread();
386 |
387 | // Initialize output variables
388 | // It's a bit unfortunate that we have to return only one set of flags for all the files involved in the operation
389 | // The last file will win setting this flag
390 | pdwQSResult = (uint)tagVSQuerySaveResult.QSR_SaveOK;
391 |
392 | // In non-UI mode attempt to silently flip the attributes of files or check them out
393 | // and allow the save, because the user cannot be asked what to do with the file
394 | if (_sccProvider.InCommandLineMode())
395 | {
396 | rgfQuerySave = rgfQuerySave | (uint)tagVSQuerySaveFlags.QSF_SilentMode;
397 | }
398 |
399 | if (SccProvider.SolutionConfigType != 0) // if not disabled...
400 | {
401 | if (!_sccProvider.bCheckOutOnEdit)
402 | {
403 | try
404 | {
405 | //Iterate through all the files
406 | for (int iFile = 0; iFile < cFiles; iFile++)
407 | {
408 | bool fileExists = File.Exists(rgpszMkDocuments[iFile]); // if the file doesn't exist, then we don't need to check it out (it's a brand new file)
409 |
410 | if (fileExists)
411 | {
412 | bool bIsCheckoutOkay = true;
413 | bool bIsCheckedOut = SccProvider.IsCheckedOut(rgpszMkDocuments[iFile], out string stderr);
414 |
415 | bool bShouldIgnoreStatus = false;
416 | if (stderr.Contains("is not under client's root") || stderr.Contains("not in client view") || stderr.Contains("no such file")) // if file is outside client's workspace, or file does not exist in source control...
417 | {
418 | bShouldIgnoreStatus = true; // don't prevent file from being modified (since not under workspace or not under source control)
419 | }
420 |
421 | if (_sccProvider.bPromptForCheckout && !bIsCheckedOut && !bShouldIgnoreStatus) // if prompt for permission to check out and file is not checked out...
422 | {
423 | IVsUIShell uiShell = ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
424 |
425 | string message = String.Format("Do you want to check out: '{0}'?", rgpszMkDocuments[iFile]);
426 | if (!VsShellUtilities.PromptYesNo(message, "Warning", OLEMSGICON.OLEMSGICON_WARNING, uiShell))
427 | {
428 | bIsCheckoutOkay = false;
429 | pdwQSResult = (uint)tagVSQuerySaveResult.QSR_NoSave_UserCanceled;
430 | }
431 | }
432 |
433 | if (bIsCheckoutOkay && !bIsCheckedOut && !bShouldIgnoreStatus && !SccProvider.CheckOutFile(rgpszMkDocuments[iFile])) // if file exists and we couldn't check it out, then it's not okay to save the file
434 | {
435 | pdwQSResult = (uint)tagVSQuerySaveResult.QSR_ForceSaveAs; // force a "Save As" dialog to save the file
436 | }
437 | }
438 | }
439 | }
440 | catch (Exception)
441 | {
442 | // If an exception was caught, do not allow the save
443 | pdwQSResult = (uint)tagVSQuerySaveResult.QSR_NoSave_Cancel;
444 | }
445 | }
446 | }
447 |
448 | return VSConstants.S_OK;
449 | }
450 |
451 | public int QuerySaveFile(string pszMkDocument, uint rgf, VSQEQS_FILE_ATTRIBUTE_DATA[] pFileInfo, out uint pdwQSResult)
452 | {
453 | ThreadHelper.ThrowIfNotOnUIThread();
454 |
455 | // Delegate to the other QuerySave function
456 | string[] rgszDocuements = new string[1];
457 | uint[] rgrgf = new uint[1];
458 | rgszDocuements[0] = pszMkDocument;
459 | rgrgf[0] = rgf;
460 |
461 | return QuerySaveFiles(((uint)tagVSQuerySaveFlags.QSF_DefaultOperation), 1, rgszDocuements, rgrgf, pFileInfo, out pdwQSResult);
462 | }
463 |
464 | public int DeclareReloadableFile(string pszMkDocument, uint rgf, VSQEQS_FILE_ATTRIBUTE_DATA[] pFileInfo)
465 | {
466 | return VSConstants.S_OK;
467 | }
468 |
469 | public int DeclareUnreloadableFile(string pszMkDocument, uint rgf, VSQEQS_FILE_ATTRIBUTE_DATA[] pFileInfo)
470 | {
471 | return VSConstants.S_OK;
472 | }
473 |
474 | public int OnAfterSaveUnreloadableFile(string pszMkDocument, uint rgf, VSQEQS_FILE_ATTRIBUTE_DATA[] pFileInfo)
475 | {
476 | return VSConstants.S_OK;
477 | }
478 |
479 | public int IsReloadable(string pszMkDocument, out int pbResult)
480 | {
481 | // Since we're not tracking which files are reloadable and which not, consider everything reloadable
482 | pbResult = 1;
483 |
484 | return VSConstants.S_OK;
485 | }
486 |
487 | public int BeginQuerySaveBatch()
488 | {
489 | return VSConstants.S_OK;
490 | }
491 |
492 | public int EndQuerySaveBatch()
493 | {
494 | return VSConstants.S_OK;
495 | }
496 |
497 | #endregion // IVsQueryEditQuerySave2
498 |
499 | private int OnBeforeCloseSolutionOrFolder()
500 | {
501 | ThreadHelper.ThrowIfNotOnUIThread();
502 |
503 | if (_sccProvider.GetSolutionFileName() != null)
504 | {
505 | if (_sccProvider.solutionDirectory != null && _sccProvider.solutionDirectory.Length > 0)
506 | {
507 | string message = String.Format("Unloading solution: {0}\n", _sccProvider.solutionFile);
508 | SccProvider.P4SimpleSccQueueOutput(message);
509 | }
510 | }
511 |
512 | // set all configuration settings back to uninitialized
513 | SccProvider.SolutionConfigType = 0;
514 | _sccProvider.bCheckOutOnEdit = true;
515 | _sccProvider.bPromptForCheckout = false;
516 |
517 | _sccProvider.P4Port = "";
518 | _sccProvider.P4User = "";
519 | _sccProvider.P4Client = "";
520 |
521 | _sccProvider.solutionDirectory = "";
522 | _sccProvider.solutionFile = "";
523 |
524 | _sccProvider.bSolutionLoadedOutputDone = false;
525 |
526 | _sccProvider.bIsWorkspace = false;
527 | _sccProvider.WindowActivatedFilename = "";
528 |
529 | _sccProvider.bReadUserOptionsCalled = false;
530 | _sccProvider.bUserSettingsWasEmpty = false;
531 |
532 | return VSConstants.S_OK;
533 | }
534 |
535 | ///
536 | /// Returns whether this source control provider is the active scc provider.
537 | ///
538 | public bool Active
539 | {
540 | get { return _active; }
541 | }
542 |
543 | }
544 | }
545 |
--------------------------------------------------------------------------------
/SharedProject/SharedProject.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | 64cd1d5b-0e68-4a05-9535-bf12013dd743
7 |
8 |
9 | SharedProject
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SharedProject/SharedProject.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 64cd1d5b-0e68-4a05-9535-bf12013dd743
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/img/CheckOutDocument.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/img/CheckOutDocument.png
--------------------------------------------------------------------------------
/img/CheckOutFile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/img/CheckOutFile.png
--------------------------------------------------------------------------------
/img/FileCheckedOut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/img/FileCheckedOut.png
--------------------------------------------------------------------------------
/img/OutputWindow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/img/OutputWindow.png
--------------------------------------------------------------------------------
/img/SolutionConfigurationDialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/img/SolutionConfigurationDialog.png
--------------------------------------------------------------------------------
/img/SolutionConfigurationMenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/img/SolutionConfigurationMenu.png
--------------------------------------------------------------------------------
/img/Tools_Options_SourceControl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botman99/P4SimpleScc/29c5785b53b7e03bf085e71f39e10ef0de6fb997/img/Tools_Options_SourceControl.png
--------------------------------------------------------------------------------