diff --git a/edit-sharp/Plugins/BasicPlugins/SimplePlugin.cs b/edit-sharp/Plugins/BasicPlugins/SimplePlugin.cs index a9a872a..ea88e9c 100644 --- a/edit-sharp/Plugins/BasicPlugins/SimplePlugin.cs +++ b/edit-sharp/Plugins/BasicPlugins/SimplePlugin.cs @@ -15,21 +15,17 @@ public class DialogPlugin : IEditorPlugin public void Initialize(IEditorHost host) { - _host = host; - var result = _host.CreateDialog("MyCustomDialog", 50, 50); - Application.Run(result); + } public void OnFileOpened(string filePath) { - var result = _host.CreateDialog("MyCustomDialog", 50, 50); - Application.Run(result); + } public void OnTextChanged(string? newText) { - var result = _host.CreateDialog("MyCustomDialog", 50, 50); - Application.Run(result); + } } diff --git a/edit-sharp/Views/EditorView.cs b/edit-sharp/Views/EditorView.cs index d7d9ce9..6e4517b 100644 --- a/edit-sharp/Views/EditorView.cs +++ b/edit-sharp/Views/EditorView.cs @@ -15,10 +15,10 @@ public class EditorView : ITextEditor, IEditorHost private readonly Label _statusBar; private bool _wordWrap = true; private readonly List _plugins; - private string _lineEnding = "Unknown"; - private string _encoding = "Unknown"; - private string _lineCount = "Unknown"; - private string? _charCount = "Unknown"; + private string? _detectedLineEnding = null; // Line ending from current editing session + private string _encoding = "UTF-8"; // Default encoding + private bool _programmaticChange = false; // Flag to track programmatic changes + private string _lastText = string.Empty; // Track last text state public EditorView(Window parentWindow) { @@ -35,16 +35,8 @@ public EditorView(Window parentWindow) WordWrap = _wordWrap }; - TextView.TextChanged += () => - { - var content = TextView.Text.ToString(); - _lineCount = TextView.Lines.ToString(); - _charCount = content?.Length.ToString(); - _hasUnsavedChanges = true; - TextChanged?.Invoke(); - UpdateStatus($"Editing {Path.GetFileName(FilePath)} - Unsaved changes"); - foreach (var plugin in _plugins) plugin.OnTextChanged(TextView.Text.ToString()); - }; + // Use KeyPress event instead of TextChanged for keyboard input detection + TextView.KeyPress += OnKeyPress; _parentWindow.Add(TextView); @@ -62,13 +54,114 @@ public EditorView(Window parentWindow) _parentWindow.Add(_statusBar); } + private void OnKeyPress(View.KeyEventEventArgs args) + { + // Check if this is an actual text modification key + var key = args.KeyEvent.Key; + + // Filter out navigation and non-text keys + if (IsNavigationKey(key) || IsModifierOnlyKey(key)) + { + return; + } + + // Schedule the text change notification for after the key is processed + Application.MainLoop.AddTimeout(TimeSpan.FromMilliseconds(10), (_) => + { + var currentText = TextView.Text.ToString(); + if (currentText != _lastText && !_programmaticChange) + { + _lastText = currentText; + OnUserTextChanged(); + } + return false; // Don't repeat + }); + } + + private bool IsNavigationKey(Key key) + { + var keyWithoutMods = key & ~(Key.CtrlMask | Key.ShiftMask | Key.AltMask); + + return keyWithoutMods switch + { + Key.CursorUp or Key.CursorDown or Key.CursorLeft or Key.CursorRight or + Key.Home or Key.End or Key.PageUp or Key.PageDown or + Key.F1 or Key.F2 or Key.F3 or Key.F4 or Key.F5 or + Key.F6 or Key.F7 or Key.F8 or Key.F9 or Key.F10 or + Key.F11 or Key.F12 or Key.Esc or Key.Tab => true, + _ => false + }; + } + + private bool IsModifierOnlyKey(Key key) + { + // Check if only modifier keys are pressed (Ctrl, Alt, Shift alone) + return key == Key.CtrlMask || key == Key.AltMask || key == Key.ShiftMask; + } + + private void OnUserTextChanged() + { + var content = TextView.Text.ToString(); + _hasUnsavedChanges = true; + + // Detect line ending from user input + DetectLineEndingFromInput(content); + + TextChanged?.Invoke(); + UpdateWindowTitle(); + UpdateStatus(); + + // Notify plugins + foreach (var plugin in _plugins) + { + plugin.OnTextChanged(content); + } + } + + private void DetectLineEndingFromInput(string content) + { + if (string.IsNullOrEmpty(content)) + { + _detectedLineEnding = null; + return; + } + + // Only detect if we haven't detected yet or if content has line breaks + for (int i = 0; i < content.Length; i++) + { + if (content[i] == '\r') + { + if (i + 1 < content.Length && content[i + 1] == '\n') + { + _detectedLineEnding = "CRLF"; + return; + } + else + { + _detectedLineEnding = "CR"; + return; + } + } + else if (content[i] == '\n') + { + _detectedLineEnding = "LF"; + return; + } + } + } + public string? Text { get => TextView.Text.ToString(); set { + _programmaticChange = true; TextView.Text = value; + _lastText = value ?? string.Empty; _hasUnsavedChanges = false; + _programmaticChange = false; + UpdateWindowTitle(); + UpdateStatus(); // Update status after setting text } } @@ -86,8 +179,11 @@ public void NewFile() Text = string.Empty; FilePath = string.Empty; + _detectedLineEnding = null; + _encoding = "UTF-8"; _hasUnsavedChanges = false; - UpdateStatus("New file created"); + UpdateWindowTitle(); + UpdateStatus(); } public void OpenFile(string? path = null) @@ -113,12 +209,11 @@ public void OpenFile(string? path = null) var content = reader.ReadToEnd(); Text = content; _encoding = reader.CurrentEncoding.EncodingName; - _lineCount = TextView.Lines.ToString(); - _charCount = content.Length.ToString(); - _lineEnding = DetectLineEnding(content); + _detectedLineEnding = DetectLineEndingFromFile(content); FilePath = path; _hasUnsavedChanges = false; - UpdateStatus($"Opened: {Path.GetFileName(path)}"); + UpdateWindowTitle(); + UpdateStatus(); FileOpened?.Invoke(); foreach (var plugin in _plugins) plugin.OnFileOpened(path); } @@ -132,17 +227,41 @@ public void OpenFile(string? path = null) ShowError($"Error opening file: {ex.Message}"); } } - private static string DetectLineEnding(string content) + + private static string DetectLineEndingFromFile(string content) { - var crlf = content.Split("\r\n").Length - 1; - var lf = content.Split("\n").Length - 1; - var cr = content.Split("\r").Length - 1; + if (string.IsNullOrEmpty(content)) + return "Unknown"; + + var crlfCount = 0; + var lfCount = 0; + var crCount = 0; + + for (int i = 0; i < content.Length; i++) + { + if (content[i] == '\r') + { + if (i + 1 < content.Length && content[i + 1] == '\n') + { + crlfCount++; + i++; // Skip the \n + } + else + { + crCount++; + } + } + else if (content[i] == '\n') + { + lfCount++; + } + } - if (crlf > 0 && crlf >= lf && crlf >= cr) + if (crlfCount > 0 && crlfCount >= lfCount && crlfCount >= crCount) return "CRLF"; - if (lf > 0 && lf >= cr) + if (lfCount > 0 && lfCount >= crCount) return "LF"; - return cr > 0 ? "CR" : "Unknown"; + return crCount > 0 ? "CR" : "Unknown"; } public void SaveFile() @@ -161,7 +280,8 @@ public void SaveFile() { File.WriteAllText(FilePath, Text); _hasUnsavedChanges = false; - UpdateStatus($"Saved: {Path.GetFileName(FilePath)}"); + UpdateWindowTitle(); + UpdateStatus(); FileSaved?.Invoke(); } catch (Exception ex) @@ -199,8 +319,10 @@ public void CloseFile() Text = string.Empty; FilePath = string.Empty; + _detectedLineEnding = null; _hasUnsavedChanges = false; - UpdateStatus("File closed"); + UpdateWindowTitle(); + UpdateStatus(); } public bool CheckUnsavedChanges() @@ -276,13 +398,50 @@ public void ShowError(string message) MessageBox.ErrorQuery(50, 7, "Error", message, "OK"); } - public void UpdateStatus(string message) + public void UpdateStatus(string? customMessage = null) { if (!_showStatusBar) return; + var lineCount = TextView.Lines; var charCount = TextView.Text.Length; + var lineEnding = _detectedLineEnding ?? "---"; + + // Build status parts + var parts = new List(); + + // Custom message or default status + if (!string.IsNullOrEmpty(customMessage)) + { + parts.Add(customMessage); + } + else if (!string.IsNullOrEmpty(FilePath)) + { + parts.Add(Path.GetFileName(FilePath)); + } + else + { + parts.Add("Untitled"); + } + + // Add stats + parts.Add($"Lines: {lineCount}"); + parts.Add($"Chars: {charCount}"); + parts.Add($"Encoding: {_encoding}"); + parts.Add($"Line Ending: {lineEnding}"); + + _statusBar.Text = string.Join(" | ", parts); + } - _statusBar.Text = $"{message} [Lines: {lineCount}, Chars: {charCount}, {_encoding}, {_lineEnding}]"; + private void UpdateWindowTitle() + { + var fileName = string.IsNullOrEmpty(FilePath) ? "Untitled" : Path.GetFileName(FilePath); + var modifiedIndicator = _hasUnsavedChanges ? "● " : ""; + _parentWindow.Title = $"{modifiedIndicator}{fileName} - Edit Sharp"; + } + + public void SetStatusMessage(string message) + { + UpdateStatus(message); } public T CreateDialog(string title, int width, int height) where T : Dialog, new() @@ -301,20 +460,17 @@ public void UpdateStatus(string message) public event Action TextChanged; public event Action FileOpened; public event Action FileSaved; - public string CurrentFilePath { get; } + + public string CurrentFilePath => FilePath ?? string.Empty; public string? GetText() { return TextView.Text.ToString(); } - - public void SetStatusMessage(string message) - { - UpdateStatus(message); - } + public void AddMenuItem(string menuPath, Action action) { - + } -} +} \ No newline at end of file