├── .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 | 16 | amd64 17 | 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 --------------------------------------------------------------------------------