Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Notepads/Core/INotepadsCore.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved.
// See LICENSE file in the project root for license information.
// ---------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -33,6 +33,7 @@ public interface INotepadsCore
event EventHandler<ITextEditor> TextEditorLineEndingChanged;
event EventHandler<ITextEditor> TextEditorModeChanged;
event EventHandler<ITextEditor> TextEditorMovedToAnotherAppInstance;
event EventHandler<ITextEditor> TextEditorTextChanging;
event EventHandler<IReadOnlyList<IStorageItem>> StorageItemsDropped;
event KeyEventHandler TextEditorKeyDown;

Expand Down
11 changes: 10 additions & 1 deletion src/Notepads/Core/NotepadsCore.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved.
// See LICENSE file in the project root for license information.
// ---------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -44,6 +44,7 @@ public class NotepadsCore : INotepadsCore
public event EventHandler<ITextEditor> TextEditorLineEndingChanged;
public event EventHandler<ITextEditor> TextEditorModeChanged;
public event EventHandler<ITextEditor> TextEditorMovedToAnotherAppInstance;
public event EventHandler<ITextEditor> TextEditorTextChanging;
public event EventHandler<IReadOnlyList<IStorageItem>> StorageItemsDropped;

public event KeyEventHandler TextEditorKeyDown;
Expand Down Expand Up @@ -207,6 +208,7 @@ public ITextEditor CreateTextEditor(
textEditor.LineEndingChanged += TextEditor_OnLineEndingChanged;
textEditor.EncodingChanged += TextEditor_OnEncodingChanged;
textEditor.FileRenamed += TextEditor_OnFileRenamed;
textEditor.TextChanging += TextEditor_OnTextChanging;

return textEditor;
}
Expand Down Expand Up @@ -245,6 +247,7 @@ public void DeleteTextEditor(ITextEditor textEditor)
textEditor.LineEndingChanged -= TextEditor_OnLineEndingChanged;
textEditor.EncodingChanged -= TextEditor_OnEncodingChanged;
textEditor.FileRenamed -= TextEditor_OnFileRenamed;
textEditor.TextChanging -= TextEditor_OnTextChanging;
textEditor.Dispose();
}

Expand Down Expand Up @@ -574,6 +577,12 @@ private void TextEditor_OnFileRenamed(object sender, EventArgs e)
TextEditorRenamed?.Invoke(this, textEditor);
}

private void TextEditor_OnTextChanging(object sender, EventArgs e)
{
if (!(sender is ITextEditor textEditor)) return;
TextEditorTextChanging?.Invoke(this, textEditor);
}

#region DragAndDrop

private async void Sets_DragOver(object sender, DragEventArgs args)
Expand Down
4 changes: 3 additions & 1 deletion src/Notepads/Notepads.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -238,7 +238,9 @@
<Compile Include="Services\MRUService.cs" />
<Compile Include="Utilities\BrushUtility.cs" />
<Compile Include="Utilities\Downloader.cs" />
<Compile Include="Services\EnhancedAutosaveService.cs" />
<Compile Include="Services\LoggingService.cs" />
<Compile Include="Tests\EnhancedAutosaveTests.cs" />
<Compile Include="Services\NotepadsProtocolService.cs" />
<Compile Include="Services\NotificationCenter.cs" />
<Compile Include="Utilities\DialogManager.cs" />
Expand Down
30 changes: 29 additions & 1 deletion src/Notepads/Services/AppSettingsService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved.
// See LICENSE file in the project root for license information.
// ---------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -27,6 +27,7 @@ public static class AppSettingsService
public static event EventHandler<bool> OnStatusBarVisibilityChanged;
public static event EventHandler<bool> OnSessionBackupAndRestoreOptionChanged;
public static event EventHandler<bool> OnHighlightMisspelledWordsChanged;
public static event EventHandler<bool> OnAutosaveOptionChanged;

private static string _editorFontFamily;

Expand Down Expand Up @@ -239,6 +240,19 @@ public static bool IsSessionSnapshotEnabled
}
}

private static bool _isAutosaveEnabled;

public static bool IsAutosaveEnabled
{
get => _isAutosaveEnabled;
set
{
_isAutosaveEnabled = value;
OnAutosaveOptionChanged?.Invoke(null, value);
ApplicationSettingsStore.Write(SettingsKey.EditorEnableAutosaveBool, value);
}
}

private static bool _isHighlightMisspelledWordsEnabled;

public static bool IsHighlightMisspelledWordsEnabled
Expand Down Expand Up @@ -327,6 +341,8 @@ public static void Initialize()

InitializeSessionSnapshotSettings();

InitializeAutosaveSettings();

InitializeAppOpeningPreferencesSettings();

InitializeAppClosingPreferencesSettings();
Expand Down Expand Up @@ -368,6 +384,18 @@ private static void InitializeSessionSnapshotSettings()
}
}

private static void InitializeAutosaveSettings()
{
if (ApplicationSettingsStore.Read(SettingsKey.EditorEnableAutosaveBool) is bool enableAutosave)
{
_isAutosaveEnabled = enableAutosave;
}
else
{
_isAutosaveEnabled = false;
}
}

private static void InitializeLineEndingSettings()
{
if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultLineEndingStr) is string lineEndingStr &&
Expand Down
139 changes: 139 additions & 0 deletions src/Notepads/Services/EnhancedAutosaveService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// ---------------------------------------------------------------------------------------------
// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved.
// See LICENSE file in the project root for license information.
// ---------------------------------------------------------------------------------------------

namespace Notepads.Services
{
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Windows.Storage;

public class AutosaveSettings
{
public bool AutosaveEnabled { get; set; } = false;
public bool ShowSavedNotification { get; set; } = true;
}

public static class EnhancedAutosaveService
{
public static bool FeatureFlag_EnhancedAutosave = true;

private static string SettingsFilePath => Path.Combine(ApplicationData.Current.LocalFolder.Path, "settings", "autosave.json");

private static AutosaveSettings _settings = new AutosaveSettings();

public static bool IsAutosaveEnabled
{
get => _settings.AutosaveEnabled;
set
{
if (_settings.AutosaveEnabled != value)
{
_settings.AutosaveEnabled = value;
SaveSettings();
AutosaveStateChanged?.Invoke(null, value);
}
}
}

public static bool ShowSavedNotification
{
get => _settings.ShowSavedNotification;
set
{
if (_settings.ShowSavedNotification != value)
{
_settings.ShowSavedNotification = value;
SaveSettings();
NotificationSettingChanged?.Invoke(null, value);
}
}
}

public static event EventHandler<bool> AutosaveStateChanged;
public static event EventHandler<bool> NotificationSettingChanged;
public static event EventHandler<DateTime> LastSaveTimeChanged;

public static DateTime LastSaveTime { get; private set; }

private static BlockingCollection<Func<Task>> _saveQueue;

public static void Initialize()
{
if (!FeatureFlag_EnhancedAutosave) return;
LoadSettings();
_saveQueue = new BlockingCollection<Func<Task>>();
StartAsyncQueue();
}

private static void LoadSettings()
{
try
{
if (File.Exists(SettingsFilePath))
{
string json = File.ReadAllText(SettingsFilePath);
_settings = JsonSerializer.Deserialize<AutosaveSettings>(json) ?? new AutosaveSettings();
}
}
catch (Exception ex)
{
LoggingService.LogError($"[{nameof(EnhancedAutosaveService)}] LoadSettings error: {ex.Message}");
}
}

private static void SaveSettings()
{
try
{
string dir = Path.GetDirectoryName(SettingsFilePath);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
string json = JsonSerializer.Serialize(_settings);
File.WriteAllText(SettingsFilePath, json);
}
catch (Exception ex)
{
LoggingService.LogError($"[{nameof(EnhancedAutosaveService)}] SaveSettings error: {ex.Message}");
}
}

private static void StartAsyncQueue()
{
Task.Run(async () =>
{
foreach (var saveAction in _saveQueue.GetConsumingEnumerable())
{
try
{
var sw = Stopwatch.StartNew();
await saveAction();
sw.Stop();
LastSaveTime = DateTime.Now;
LastSaveTimeChanged?.Invoke(null, LastSaveTime);
LoggingService.LogInfo($"[{nameof(EnhancedAutosaveService)}] Save completed in {sw.ElapsedMilliseconds}ms. IO count: 1");
}
catch (Exception ex)
{
LoggingService.LogError($"[{nameof(EnhancedAutosaveService)}] AsyncQueue error: {ex.Message}");
}
}
});
}

public static void QueueSave(Func<Task> saveAction)
{
if (FeatureFlag_EnhancedAutosave && IsAutosaveEnabled && _saveQueue != null)
{
_saveQueue.Add(saveAction);
}
}
}
}
3 changes: 2 additions & 1 deletion src/Notepads/Settings/SettingsKey.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ---------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------
// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved.
// See LICENSE file in the project root for license information.
// ---------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -41,5 +41,6 @@ internal static class SettingsKey
internal static string EditorHighlightMisspelledWordsBool = "EditorHighlightMisspelledWordsBool";
internal static string EditorDefaultDisplayLineNumbersBool = "EditorDefaultDisplayLineNumbersBool";
internal static string EditorEnableSmartCopyBool = "EditorEnableSmartCopyBool";
internal static string EditorEnableAutosaveBool = "EditorEnableAutosaveBool";
}
}
91 changes: 91 additions & 0 deletions src/Notepads/Tests/EnhancedAutosaveTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Notepads.Services;
using Notepads.Views.MainPage;
using Windows.UI.Core;

namespace Notepads.Tests
{
public static class EnhancedAutosaveTests
{
public static async Task RunTestsAsync(NotepadsMainPage mainPage)
{
LoggingService.LogInfo("Starting EnhancedAutosaveTests...");

// Ensure EnhancedAutosave is enabled
EnhancedAutosaveService.FeatureFlag_EnhancedAutosave = true;
EnhancedAutosaveService.IsAutosaveEnabled = true;
EnhancedAutosaveService.ShowSavedNotification = true;

// 1. Changing a file triggers an immediate save event.
// Create a temp file and load it
StorageFile tempFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("test_autosave.txt", CreationCollisionOption.ReplaceExisting);
await mainPage.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await mainPage.OpenFileAsync(tempFile);

var editor = mainPage.GetTextEditor();

// Simulate typing
editor.TypeText("Automated Test Content");

LoggingService.LogInfo("Test 1: Content changed, waiting for autosave...");
});

// Wait for 300ms debounce + queue processing
await Task.Delay(1000);

// Check if file was saved
string savedContent = await FileIO.ReadTextAsync(tempFile);
if (savedContent == "Automated Test Content")
{
LoggingService.LogInfo("Test 1 Passed: File was successfully autosaved.");
}
else
{
LoggingService.LogError("Test 1 Failed: File was not autosaved.");
}

// 2. Status-bar icon updates color and label
// (Requires UI inspection, but we can verify the service state)
LoggingService.LogInfo($"Test 2: LastSaveTime is {EnhancedAutosaveService.LastSaveTime}");

// 3. Clicking the icon flips the persisted setting
EnhancedAutosaveService.IsAutosaveEnabled = false;
if (!EnhancedAutosaveService.IsAutosaveEnabled)
{
LoggingService.LogInfo("Test 3 Passed: Autosave disabled successfully.");
}

// 4. Notification toggle disabled when autosave is off
// Checked in UI bindings

// 5. No save popup appears when toggle is off, but file is still written
EnhancedAutosaveService.IsAutosaveEnabled = true;
EnhancedAutosaveService.ShowSavedNotification = false;

await mainPage.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var editor = mainPage.GetTextEditor();
editor.TypeText("Silent Save Content");
});

await Task.Delay(1000);

savedContent = await FileIO.ReadTextAsync(tempFile);
if (savedContent == "Silent Save Content")
{
LoggingService.LogInfo("Test 5 Passed: File was silently autosaved.");
}
else
{
LoggingService.LogError("Test 5 Failed: Silent autosave failed.");
}

// Cleanup
await tempFile.DeleteAsync();
LoggingService.LogInfo("EnhancedAutosaveTests completed.");
}
}
}
Loading