├── .github
├── FUNDING.yml
└── workflows
│ └── stale.yml
├── AutoSaveFile64.sln
├── AutoSaveFile64
├── AutoSaveFile64.csproj
├── Properties
│ └── AssemblyInfo.cs
└── source.extension.vsixmanifest
├── AutoSaveFileShared
├── AutoSaveFilePackage.cs
├── AutoSaveFileShared.projitems
├── AutoSaveFileShared.shproj
├── Helper.cs
├── LoggerExtensionMethods.cs
├── NullLogger.cs
└── OptionPageGrid.cs
├── AutoSaveFileTests
├── AutoSaveFilePackageTests.cs
├── AutoSaveFileTests.csproj
├── Properties
│ └── AssemblyInfo.cs
└── app.config
├── LICENSE
├── README.md
└── options.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [hrai]
2 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues and PRs'
2 | on:
3 | schedule:
4 | - cron: '30 1 * * *'
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/stale@v4
11 | with:
12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
13 | stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
14 | close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
15 | close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
16 | stale-issue-label: 'no-issue-activity'
17 | stale-pr-label: 'no-pr-activity'
18 | days-before-issue-stale: 30
19 | days-before-pr-stale: 45
20 | days-before-issue-close: 5
21 | days-before-pr-close: 10
22 |
--------------------------------------------------------------------------------
/AutoSaveFile64.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31521.260
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoSaveFileTests", "AutoSaveFileTests\AutoSaveFileTests.csproj", "{3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}"
7 | EndProject
8 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "AutoSaveFileShared", "AutoSaveFileShared\AutoSaveFileShared.shproj", "{BFA82BA2-CF5E-4646-B562-1CD085BA5098}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoSaveFile64", "AutoSaveFile64\AutoSaveFile64.csproj", "{2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}"
11 | EndProject
12 | Global
13 | GlobalSection(SharedMSBuildProjectFiles) = preSolution
14 | AutoSaveFileShared\AutoSaveFileShared.projitems*{2cad1a53-4f5b-4418-9ba4-8667e54ccdc0}*SharedItemsImports = 4
15 | AutoSaveFileShared\AutoSaveFileShared.projitems*{bfa82ba2-cf5e-4646-b562-1cd085ba5098}*SharedItemsImports = 13
16 | EndGlobalSection
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Debug|x86 = Debug|x86
20 | Release|Any CPU = Release|Any CPU
21 | Release|x86 = Release|x86
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Debug|x86.ActiveCfg = Debug|Any CPU
27 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Debug|x86.Build.0 = Debug|Any CPU
28 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Release|x86.ActiveCfg = Release|Any CPU
31 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}.Release|x86.Build.0 = Release|Any CPU
32 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Debug|x86.ActiveCfg = Debug|x86
35 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Debug|x86.Build.0 = Debug|x86
36 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Release|x86.ActiveCfg = Release|x86
39 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}.Release|x86.Build.0 = Release|x86
40 | EndGlobalSection
41 | GlobalSection(SolutionProperties) = preSolution
42 | HideSolutionNode = FALSE
43 | EndGlobalSection
44 | GlobalSection(ExtensibilityGlobals) = postSolution
45 | SolutionGuid = {7C271736-38C8-4790-BEA5-7E41B834083A}
46 | EndGlobalSection
47 | EndGlobal
48 |
--------------------------------------------------------------------------------
/AutoSaveFile64/AutoSaveFile64.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 17.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 | {2CAD1A53-4F5B-4418-9BA4-8667E54CCDC0}
14 | Library
15 | Properties
16 | AutoSaveFile64
17 | AutoSaveFile64
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 | Designer
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | compile; build; native; contentfiles; analyzers; buildtransitive
62 |
63 |
64 |
65 |
66 |
67 |
68 |
75 |
76 |
--------------------------------------------------------------------------------
/AutoSaveFile64/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("AutoSaveFile64")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("AutoSaveFile64")]
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("1.0.0.0")]
33 | [assembly: AssemblyFileVersion("1.0.0.0")]
34 |
--------------------------------------------------------------------------------
/AutoSaveFile64/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Auto Save File
6 | This extension help you to save your changes as you make them without having to type Ctrl-S. The time delay is currently set to 5 seconds. Only exception is when the last added character was period[.].
7 |
8 | The file also gets saved if it loses focus.
9 |
10 | The time delay can be configured from the options panel.
11 | https://github.com/hrai/auto-save-vs-extension
12 | autosave, save
13 |
14 |
15 |
18 |
19 | amd64
20 |
21 |
22 | amd64
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/AutoSaveFileShared/AutoSaveFilePackage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Threading;
4 | using EnvDTE;
5 | using Microsoft.VisualStudio.Shell;
6 | using Microsoft.VisualStudio.Shell.Interop;
7 | using Microsoft.VisualStudio;
8 | using Task = System.Threading.Tasks.Task;
9 | using System.Collections.Generic;
10 | using System.Linq;
11 | using System.Runtime.CompilerServices;
12 |
13 | [assembly: InternalsVisibleTo("AutoSaveFileTests")]
14 | namespace AutoSaveFile
15 | {
16 | ///
17 | /// This is the class that implements the package exposed by this assembly.
18 | ///
19 | ///
20 | ///
21 | /// The minimum requirement for a class to be considered a valid package for Visual Studio
22 | /// is to implement the IVsPackage interface and register itself with the shell.
23 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
24 | /// to do it: it derives from the Package class that provides the implementation of the
25 | /// IVsPackage interface and uses the registration attributes defined in the framework to
26 | /// register itself and its components with the shell. These attributes tell the pkgdef creation
27 | /// utility what data to put into .pkgdef file.
28 | ///
29 | ///
30 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file.
31 | ///
32 | ///
33 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
34 | [Guid(PackageGuidString)]
35 | [ProvideService(typeof(AutoSaveFilePackage), IsAsyncQueryable = true)]
36 | [ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)]
37 | [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string, PackageAutoLoadFlags.BackgroundLoad)]
38 | [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionHasMultipleProjects_string, PackageAutoLoadFlags.BackgroundLoad)]
39 | [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionHasSingleProject_string, PackageAutoLoadFlags.BackgroundLoad)]
40 | [ProvideOptionPage(typeof(OptionPageGrid), "Auto Save File", "General", 0, 0, true)]
41 | public sealed class AutoSaveFilePackage : AsyncPackage
42 | {
43 | ///
44 | /// AutoSaveFilePackage GUID string.
45 | ///
46 | public const string PackageGuidString = "d520a8f3-cfd5-4ba3-a154-66b97d118c91";
47 |
48 | private TextEditorEvents _dteEditorEvents;
49 | private WindowEvents _dteWindowEvents;
50 | private Stack _stack;
51 | private Helper _helper;
52 |
53 | #region Package Members
54 |
55 | ///
56 | /// Initialisation of the package; this method is called right after the package is sited, so this is the place
57 | /// where you can put all the Initialisation code that rely on services provided by VisualStudio.
58 | ///
59 | /// A cancellation token to monitor for Initialisation cancellation, which can occur when VS is shutting down.
60 | /// A provider for progress updates.
61 | /// A task representing the async work of package Initialisation, or an already completed task if there is none. Do not return null from this method.
62 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
63 | {
64 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
65 | // When Initialised asynchronously, the current thread may be a background thread at this point.
66 | // Do any Initialisation that requires the UI thread after switching to the UI thread.
67 | //await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
68 |
69 | _helper = new Helper();
70 |
71 | GetLogger().LogInformation(GetPackageName(), "Initialising...");
72 | await base.InitializeAsync(cancellationToken, progress);
73 |
74 | try
75 | {
76 | await BindToLocalVisualStudioEventsAsync();
77 | BindToWindowEvents();
78 |
79 | _stack = new Stack();
80 |
81 | GetLogger().LogInformation(GetPackageName(), "Initialised.");
82 | }
83 | catch (Exception exception)
84 | {
85 | GetLogger().LogError(GetPackageName(), "Exception during initialisation", exception);
86 | }
87 | }
88 |
89 | private void BindToWindowEvents()
90 | {
91 | System.Windows.Application.Current.Deactivated += OnDeactivated;
92 | System.Windows.Application.Current.Exit += OnDeactivated;
93 | }
94 |
95 | private async Task BindToLocalVisualStudioEventsAsync()
96 | {
97 | var dte = (DTE)await GetServiceAsync(typeof(DTE));
98 | var _dteEvents = dte.Events;
99 |
100 | _dteEditorEvents = _dteEvents.TextEditorEvents;
101 | _dteWindowEvents = _dteEvents.WindowEvents;
102 |
103 | _dteEditorEvents.LineChanged += OnLineChanged;
104 | _dteWindowEvents.WindowActivated += OnWindowActivated;
105 | }
106 |
107 | private void OnLineChanged(TextPoint startPoint, TextPoint endPoint, int Hint)
108 | {
109 | var changedText = GetChangedText(startPoint, endPoint);
110 | if (changedText.Length != 0 && IsLastModifiedCharacterPeriod(changedText))
111 | {
112 | CancelPreviousSaveTask();
113 | return;
114 | }
115 |
116 | CancelPreviousSaveTask();
117 |
118 | var _cancellationTokenSource = new CancellationTokenSource();
119 | _stack.Push(_cancellationTokenSource);
120 |
121 | _ = Task.Run(async () =>
122 | {
123 | try
124 | {
125 | await WaitForUserConfiguredDelayAsync();
126 |
127 | if (!_cancellationTokenSource.IsCancellationRequested)
128 | {
129 | var dte = (DTE)await this.GetServiceAsync(typeof(DTE));
130 | var window = dte.ActiveWindow;
131 |
132 | Save(window);
133 | }
134 | }
135 | catch (Exception exception)
136 | {
137 | GetLogger().LogError(GetPackageName(), "Exception during line change event handling", exception);
138 | }
139 | });
140 | }
141 |
142 | private void OnDeactivated(object sender, System.EventArgs e)
143 | {
144 | try
145 | {
146 | var optionsPage = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
147 | var saveWhenVsLosesFocus = optionsPage.ShouldSaveAllFilesWhenVSLosesFocus;
148 |
149 | if (saveWhenVsLosesFocus)
150 | {
151 | var dte = (DTE)this.GetService(typeof(DTE));
152 | dte.ExecuteCommand("File.SaveAll");
153 | }
154 | }
155 | catch (Exception exception)
156 | {
157 | GetLogger().LogError(GetPackageName(), "Exception occurred while saving on window losing focus", exception);
158 | }
159 | }
160 |
161 | private void OnWindowActivated(Window gotFocus, Window lostFocus)
162 | {
163 | if (lostFocus != null)
164 | {
165 | var optionsPage = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
166 | var saveFileWhenItLosesFocus = optionsPage.ShouldSaveFileWhenItLosesFocus;
167 |
168 | if (saveFileWhenItLosesFocus)
169 | {
170 | Save(lostFocus);
171 | }
172 | }
173 | }
174 |
175 | private void Save(Window window)
176 | {
177 | var optionsPage = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
178 |
179 | if (_helper.ShouldSaveDocument(window, optionsPage))
180 | {
181 | window?.Document?.Save();
182 | window?.Project?.Save();
183 | }
184 | }
185 |
186 | private static string GetChangedText(TextPoint startPoint, TextPoint endPoint)
187 | {
188 | EditPoint editPoint = startPoint.CreateEditPoint();
189 | var content = editPoint.GetText(endPoint);
190 | return content;
191 | }
192 |
193 | private bool IsLastModifiedCharacterPeriod(string changedText)
194 | {
195 | return changedText.LastIndexOf('.') == changedText.Length - 1;
196 | }
197 |
198 | private async Task WaitForUserConfiguredDelayAsync()
199 | {
200 | await JoinableTaskFactory.SwitchToMainThreadAsync(DisposalToken);
201 |
202 | var optionsPage = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
203 | var timeDelayInSeconds = optionsPage.TimeDelay;
204 |
205 | await Task.Delay(1000 * timeDelayInSeconds);
206 | }
207 |
208 | private void CancelPreviousSaveTask()
209 | {
210 | if (_stack.Any())
211 | {
212 | _stack.Pop().Cancel();
213 | }
214 | }
215 |
216 | private string GetPackageName() => nameof(AutoSaveFilePackage);
217 |
218 | private IVsActivityLog GetLogger()
219 | {
220 | return this.GetService(typeof(SVsActivityLog)) as IVsActivityLog ?? new NullLogger();
221 | }
222 |
223 | #endregion
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/AutoSaveFileShared/AutoSaveFileShared.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | bfa82ba2-cf5e-4646-b562-1cd085ba5098
7 |
8 |
9 | AutoSaveFileShared
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Component
18 |
19 |
20 |
--------------------------------------------------------------------------------
/AutoSaveFileShared/AutoSaveFileShared.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | bfa82ba2-cf5e-4646-b562-1cd085ba5098
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/AutoSaveFileShared/Helper.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using EnvDTE;
3 | using System.Runtime.CompilerServices;
4 | using System.Linq;
5 | using System.Text.RegularExpressions;
6 | using System.Collections.Generic;
7 | using System;
8 |
9 | [assembly: InternalsVisibleTo("AutoSaveFileTests")]
10 | namespace AutoSaveFile
11 | {
12 | internal class Helper
13 | {
14 | internal string GetFileType(Window window)
15 | {
16 | var documentFullName = window.Document?.FullName;
17 |
18 | if (documentFullName == null)
19 | documentFullName = window.Project?.FullName;
20 |
21 | if (Path.HasExtension(documentFullName))
22 | return Path.GetExtension(documentFullName).Replace(".", "");
23 |
24 | return "";
25 | }
26 |
27 | public static bool IsFileReadOnly(string fileName)
28 | {
29 | FileInfo fInfo = new FileInfo(fileName);
30 | return fInfo.IsReadOnly;
31 | }
32 |
33 | internal bool ShouldSaveDocument(Window window, OptionPageGrid optionsPage)
34 | {
35 | var windowType = window.Kind;
36 |
37 | if (windowType == "Document")
38 | {
39 | var filePath = window.Document.FullName;
40 |
41 | if (File.Exists(filePath) && IsFileReadOnly(filePath))
42 | return false;
43 |
44 | //if (!DirectoryHasPermission(Path.GetDirectoryName(filePath), FileSystemRights.Write))
45 | // return false;
46 |
47 | var directoryList = GetConstituentFoldersFromPath(window);
48 | if (IsDocumentInIgnoredFolder(optionsPage.IgnoredFolders, directoryList))
49 | return false;
50 |
51 | var fileType = GetFileType(window);
52 | var ignoredFileTypes = optionsPage.IgnoredFileTypes?
53 | .ToLowerInvariant()
54 | .Split(',')
55 | .Select(str => str.Trim());
56 |
57 | if (ignoredFileTypes == null)
58 | return true;
59 |
60 | if (ignoredFileTypes != null && !ignoredFileTypes.Contains(fileType))
61 | return true;
62 | }
63 |
64 | return false;
65 | }
66 |
67 | /*
68 | private static bool DirectoryHasPermission(string directoryPath, FileSystemRights accessRight)
69 | {
70 | if (string.IsNullOrEmpty(directoryPath))
71 | return false;
72 |
73 | try
74 | {
75 | var rules = Directory.GetAccessControl(directoryPath).GetAccessRules(true, true, typeof(SecurityIdentifier));
76 | var identity = WindowsIdentity.GetCurrent();
77 |
78 | foreach (FileSystemAccessRule rule in rules)
79 | {
80 | if (!identity.Groups.Contains(rule.IdentityReference))
81 | continue;
82 |
83 | if ((accessRight & rule.FileSystemRights) != accessRight)
84 | continue;
85 |
86 | if (rule.AccessControlType != AccessControlType.Allow)
87 | continue;
88 |
89 | return true;
90 | }
91 | }
92 | catch { }
93 | return false;
94 | }
95 | */
96 |
97 | private IList GetConstituentFoldersFromPath(Window window)
98 | {
99 | var documentFullName = window.Document?.FullName;
100 |
101 | if (documentFullName == null)
102 | documentFullName = window.Project?.FullName;
103 |
104 | return documentFullName.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
105 | }
106 |
107 | private bool IsDocumentInIgnoredFolder(string ignoredFolders, IList directoryList)
108 | {
109 | foreach (string folder in ignoredFolders.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
110 | {
111 | var incl = folder.StartsWith("!");
112 | var cleanFolderName = folder.Substring(incl ? 1 : 0).Trim();
113 |
114 | cleanFolderName = cleanFolderName.Replace('\\', '/');
115 | cleanFolderName = Regex.Escape(cleanFolderName);
116 |
117 | return directoryList.Any(dirs => dirs.Contains(cleanFolderName));
118 | }
119 |
120 | return false;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/AutoSaveFileShared/LoggerExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.Shell.Interop;
3 |
4 | namespace AutoSaveFile
5 | {
6 | public static class LoggerExtensionMethods
7 | {
8 | public static void LogInformation(this IVsActivityLog logger, string packageName, string message)
9 | {
10 | logger.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, packageName, message);
11 | }
12 |
13 | public static void LogWarning(this IVsActivityLog logger, string packageName, string message)
14 | {
15 | logger.LogEntry((UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_WARNING, packageName, message);
16 | }
17 |
18 | public static void LogError(this IVsActivityLog logger, string packageName, string message, Exception exception)
19 | {
20 | logger.LogEntry(
21 | (UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
22 | packageName,
23 | $"{message}{Environment.NewLine}Exception = `{exception.Message}`{Environment.NewLine}StackTrace = `{exception.StackTrace}`");
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/AutoSaveFileShared/NullLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.Shell.Interop;
3 |
4 | namespace AutoSaveFile
5 | {
6 | internal class NullLogger : IVsActivityLog
7 | {
8 | public int LogEntry(uint actType, string pszSource, string pszDescription)
9 | {
10 | return 0;
11 | }
12 |
13 | public int LogEntryGuid(uint actType, string pszSource, string pszDescription, Guid guid)
14 | {
15 | return 0;
16 | }
17 |
18 | public int LogEntryHr(uint actType, string pszSource, string pszDescription, int hr)
19 | {
20 | return 0;
21 | }
22 |
23 | public int LogEntryGuidHr(uint actType, string pszSource, string pszDescription, Guid guid, int hr)
24 | {
25 | return 0;
26 | }
27 |
28 | public int LogEntryPath(uint actType, string pszSource, string pszDescription, string pszPath)
29 | {
30 | return 0;
31 | }
32 |
33 | public int LogEntryGuidPath(uint actType, string pszSource, string pszDescription, Guid guid, string pszPath)
34 | {
35 | return 0;
36 | }
37 |
38 | public int LogEntryHrPath(uint actType, string pszSource, string pszDescription, int hr, string pszPath)
39 | {
40 | return 0;
41 | }
42 |
43 | public int LogEntryGuidHrPath(uint actType, string pszSource, string pszDescription, Guid guid, int hr, string pszPath)
44 | {
45 | return 0;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/AutoSaveFileShared/OptionPageGrid.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Shell;
2 | using System.ComponentModel;
3 |
4 | namespace AutoSaveFile
5 | {
6 | public class OptionPageGrid : DialogPage
7 | {
8 | [Category("General")]
9 | [DisplayName("Time Delay")]
10 | [Description("Time delay in seconds for save")]
11 | public int TimeDelay { get; set; } = 5;
12 |
13 | [Category("General")]
14 | [DisplayName("Excluded File Types")]
15 | [Description("File types which will be ignored")]
16 | public string IgnoredFileTypes { get; set; }
17 |
18 | [Category("General")]
19 | [DisplayName("Excluded Folders")]
20 | [Description("Folders which will be ignored")]
21 | public string IgnoredFolders { get; set; } = "Microsoft Visual Studio;Windows Kits";
22 |
23 | [Category("General")]
24 | [DisplayName("Save All Files When VS Loses Focus")]
25 | [Description("True saves all the files when VS loses focus")]
26 | public bool ShouldSaveAllFilesWhenVSLosesFocus { get; set; } = true;
27 |
28 | [Category("General")]
29 | [DisplayName("Save File When It Loses Focus")]
30 | [Description("True saves the file when it loses focus")]
31 | public bool ShouldSaveFileWhenItLosesFocus { get; set; } = true;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/AutoSaveFileTests/AutoSaveFilePackageTests.cs:
--------------------------------------------------------------------------------
1 | using AutoSaveFile;
2 | using EnvDTE;
3 | using FluentAssertions;
4 | using Moq;
5 | using Xunit;
6 |
7 | namespace AutoSaveFileTests
8 | {
9 | public class HelperTests
10 | {
11 | [Fact]
12 | public void GetFileType_ReturnsDocumentExt_WhenDocumentPathIsAvailable()
13 | {
14 | var window = new Mock();
15 | var document = new Mock();
16 | window.Setup(win => win.Document).Returns(document.Object);
17 | document.Setup(doc => doc.FullName).Returns("c:\\test\\tester.cs");
18 |
19 | var sut = new Helper();
20 | var fileType = sut.GetFileType(window.Object);
21 | fileType.Should().Be("cs");
22 | }
23 |
24 | [Fact]
25 | public void GetFileType_ReturnsProjExt_WhenOnlyProjPathIsAvailable()
26 | {
27 | var window = new Mock();
28 | var project = new Mock();
29 | window.Setup(win => win.Project).Returns(project.Object);
30 | project.Setup(proj => proj.FullName).Returns("c:\\test\\tester.csproj");
31 |
32 | var sut = new Helper();
33 | var fileType = sut.GetFileType(window.Object);
34 | fileType.Should().Be("csproj");
35 | }
36 |
37 | [Fact]
38 | public void GetFileType_ReturnsEmptyString_WhenDocumentOrProjectIsNotAvailable()
39 | {
40 | var window = new Mock();
41 |
42 | var sut = new Helper();
43 | var fileType = sut.GetFileType(window.Object);
44 | fileType.Should().BeEmpty();
45 | }
46 |
47 | /* todo - complete this test
48 | [VsixFact]
49 | public void ShouldSaveDocument_ReturnsTrue_WhenIgnoredFileTypesIsEmpty()
50 | {
51 | var document = new Mock();
52 | document.Setup(doc => doc.FullName).Returns("c:\\test\\tester.cs");
53 |
54 | var windowMock = new Mock();
55 | windowMock.Setup(win => win.Document).Returns(document.Object);
56 | windowMock.SetupGet(win => win.Kind).Returns("Document");
57 |
58 | //var optionsPage = new OptionPageGrid { TimeDelay = 1, IgnoredFileTypes = null };
59 | var optionsPage = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
60 |
61 | var sut = new Helper();
62 | sut.ShouldSaveDocument(windowMock.Object, optionsPage).Should().BeTrue();
63 | }
64 | */
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/AutoSaveFileTests/AutoSaveFileTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {3630EE61-8A4D-4BBE-8FE3-9772EFC7FF9A}
8 | Library
9 | Properties
10 | AutoSaveFileTests
11 | AutoSaveFileTests
12 | v4.7.2
13 | 512
14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 15.0
16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
18 | False
19 | UnitTest
20 |
21 |
22 |
23 |
24 | true
25 | full
26 | false
27 | bin\Debug\
28 | DEBUG;TRACE
29 | prompt
30 | 4
31 |
32 |
33 | pdbonly
34 | true
35 | bin\Release\
36 | TRACE
37 | prompt
38 | 4
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | 5.9.0
59 |
60 |
61 | 17.0.32112.339
62 |
63 |
64 | 4.13.1
65 |
66 |
67 | 2.4.1
68 |
69 |
70 | 2.4.1
71 | runtime; build; native; contentfiles; analyzers; buildtransitive
72 | all
73 |
74 |
75 | 2.4.1
76 | runtime; build; native; contentfiles; analyzers; buildtransitive
77 | all
78 |
79 |
80 | 0.2.72
81 |
82 |
83 |
84 |
85 | {2cad1a53-4f5b-4418-9ba4-8667e54ccdc0}
86 | AutoSaveFile64
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/AutoSaveFileTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("AutoSaveFileTests")]
6 | [assembly: AssemblyDescription("")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("")]
9 | [assembly: AssemblyProduct("AutoSaveFileTests")]
10 | [assembly: AssemblyCopyright("Copyright © 2019")]
11 | [assembly: AssemblyTrademark("")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 |
16 | [assembly: Guid("3630ee61-8a4d-4bbe-8fe3-9772efc7ff9a")]
17 |
18 | // [assembly: AssemblyVersion("1.0.*")]
19 | [assembly: AssemblyVersion("1.0.0.0")]
20 | [assembly: AssemblyFileVersion("1.0.0.0")]
21 |
--------------------------------------------------------------------------------
/AutoSaveFileTests/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # auto-save-vs-extension
2 |
3 | A Visual Studio extension that automatically saves the file as you're working on it.
4 |
5 | There are 3 conditions when changed file/s is saved.
6 |
7 | | Condition | Respects ignored file types | Can be disabled? |
8 | | ----------------------------------------------------------- | --------------------------- | ---------------- |
9 | | The 5 seconds (default) have elapsed since the last change. | Yes | No |
10 | | The file loses focus. | Yes | Yes |
11 | | Visual Studio loses focus. All the changed files are saved. | No | Yes |
12 |
13 | ### Visual Studio Marketplace URLs
14 |
15 | - VS2019 - https://marketplace.visualstudio.com/items?itemName=HRai.AutoSaveFileOld
16 | - VS2022 - https://marketplace.visualstudio.com/items?itemName=HRai.AutoSaveFile
17 |
18 | ### Configurable Settings
19 |
20 | - The time delay can be configured from the options panel.
21 | - If you want to exclude some files from auto-saving, you can supply a list of comma-separated file extensions such as '_vb,json,config_'
22 | - If you want to save all the modified files when Visual Studio loses focus, then enable this to true. Set to True by default.
23 |
24 | ## How to contribute?
25 |
26 | ### Sponsor
27 |
28 | - [Donate/Sponsor](https://github.com/sponsors/hrai) the project
29 |
30 | ### Raise issues
31 |
32 | - Please feel free to raise issues
33 | - PRs are welcome! :)
34 |
--------------------------------------------------------------------------------
/options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrai/auto-save-vs-extension/db93be346c921f9acda933d5f1bf2762d241577d/options.png
--------------------------------------------------------------------------------