From 8341ea8b54afb102f349f9afd9f4e87933703e26 Mon Sep 17 00:00:00 2001 From: electricmessiah Date: Wed, 8 Jul 2026 17:59:54 -0400 Subject: [PATCH 1/4] Feat: prefill Save dialog with current tab name --- src/platform/win32.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/platform/win32.rs b/src/platform/win32.rs index db63363..7d46b7f 100644 --- a/src/platform/win32.rs +++ b/src/platform/win32.rs @@ -1,4 +1,6 @@ +use std::ffi::OsStr; use std::io::BufRead; +use std::os::windows::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -2748,7 +2750,7 @@ fn save_document_at( encoding_override: Option, force_save_as: bool, ) -> Result { - let (encoding, existing_path) = { + let (encoding, existing_path, display_name) = { let doc_tab = state .docs .get(index) @@ -2756,10 +2758,11 @@ fn save_document_at( ( encoding_override.unwrap_or(doc_tab.doc.encoding), doc_tab.doc.path.clone(), + doc_tab.doc.display_name.clone(), ) }; let path = if force_save_as || existing_path.is_none() { - match save_file_dialog(hwnd)? { + match save_file_dialog(hwnd, &display_name)? { Some(path) => path, None => return Ok(false), } @@ -5862,8 +5865,14 @@ fn open_file_dialog(hwnd: HWND) -> Result> { ))) } -fn save_file_dialog(hwnd: HWND) -> Result> { +fn save_file_dialog(hwnd: HWND, default_name: &str) -> Result> { let mut buffer = vec![0u16; 1024]; + let default_wide: Vec = OsStr::new(default_name) + .encode_wide() + .filter(|&unit| unit != 0) + .collect(); + let copy_len = default_wide.len().min(buffer.len() - 1); + buffer[..copy_len].copy_from_slice(&default_wide[..copy_len]); let filter = w!("All Files\0*.*\0\0"); let mut ofn = OPENFILENAMEW { From c05f9d3fb4304dc622d688535e187a316b2e0f8e Mon Sep 17 00:00:00 2001 From: electricmessiah Date: Wed, 8 Jul 2026 18:03:28 -0400 Subject: [PATCH 2/4] Fix: default Save dialog to .txt extension when none typed --- src/platform/win32.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/win32.rs b/src/platform/win32.rs index 7d46b7f..d9c1f04 100644 --- a/src/platform/win32.rs +++ b/src/platform/win32.rs @@ -5874,6 +5874,7 @@ fn save_file_dialog(hwnd: HWND, default_name: &str) -> Result> { let copy_len = default_wide.len().min(buffer.len() - 1); buffer[..copy_len].copy_from_slice(&default_wide[..copy_len]); let filter = w!("All Files\0*.*\0\0"); + let def_ext = w!("txt"); let mut ofn = OPENFILENAMEW { lStructSize: std::mem::size_of::() as u32, @@ -5881,6 +5882,7 @@ fn save_file_dialog(hwnd: HWND, default_name: &str) -> Result> { lpstrFile: PWSTR(buffer.as_mut_ptr()), nMaxFile: buffer.len() as u32, lpstrFilter: PCWSTR(filter.as_ptr()), + lpstrDefExt: PCWSTR(def_ext.as_ptr()), Flags: OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT, ..Default::default() }; From fd9c1bb75bd1aed2d9be4c1f920561f595c8b3d5 Mon Sep 17 00:00:00 2001 From: electricmessiah Date: Thu, 9 Jul 2026 01:09:02 -0400 Subject: [PATCH 3/4] Feat: show and default to the selected language's file extension Status bar now shows the extension (e.g. .py) whenever a language is explicitly selected via View > Language. Save/Save As now defaults to that same extension instead of always falling back to .txt. --- src/platform/win32.rs | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/platform/win32.rs b/src/platform/win32.rs index d9c1f04..a4a02b5 100644 --- a/src/platform/win32.rs +++ b/src/platform/win32.rs @@ -2750,7 +2750,7 @@ fn save_document_at( encoding_override: Option, force_save_as: bool, ) -> Result { - let (encoding, existing_path, display_name) = { + let (encoding, existing_path, display_name, default_ext) = { let doc_tab = state .docs .get(index) @@ -2759,10 +2759,14 @@ fn save_document_at( encoding_override.unwrap_or(doc_tab.doc.encoding), doc_tab.doc.path.clone(), doc_tab.doc.display_name.clone(), + doc_tab + .lexer_override + .and_then(extension_for_lexer) + .unwrap_or(".txt"), ) }; let path = if force_save_as || existing_path.is_none() { - match save_file_dialog(hwnd, &display_name)? { + match save_file_dialog(hwnd, &display_name, default_ext)? { Some(path) => path, None => return Ok(false), } @@ -2996,6 +3000,12 @@ fn update_status(state: &AppState) { if doc_tab.doc.is_dirty { flags.push('*'); } + if let Some(ext) = doc_tab.lexer_override.and_then(extension_for_lexer) { + if !flags.is_empty() { + flags.push(' '); + } + flags.push_str(ext); + } if doc_tab.doc.large_file_mode { if !flags.is_empty() { flags.push(' '); @@ -4459,6 +4469,26 @@ fn lexer_for_doc(doc: &Document) -> scintilla::LexerKind { } } +/// Canonical file extension (with leading dot) for a selected language, for +/// display and for prefilling the Save dialog. `None` for `Null` (no +/// language selected / plain text). +fn extension_for_lexer(kind: scintilla::LexerKind) -> Option<&'static str> { + match kind { + scintilla::LexerKind::Null => None, + scintilla::LexerKind::Cpp => Some(".cpp"), + scintilla::LexerKind::JavaScript => Some(".js"), + scintilla::LexerKind::Json => Some(".json"), + scintilla::LexerKind::Yaml => Some(".yaml"), + scintilla::LexerKind::PowerShell => Some(".ps1"), + scintilla::LexerKind::Python => Some(".py"), + scintilla::LexerKind::Html => Some(".html"), + scintilla::LexerKind::Xml => Some(".xml"), + scintilla::LexerKind::Css => Some(".css"), + scintilla::LexerKind::Properties => Some(".ini"), + scintilla::LexerKind::Markdown => Some(".md"), + } +} + fn create_doc_from_path( parent: HWND, instance: HINSTANCE, @@ -5865,7 +5895,7 @@ fn open_file_dialog(hwnd: HWND) -> Result> { ))) } -fn save_file_dialog(hwnd: HWND, default_name: &str) -> Result> { +fn save_file_dialog(hwnd: HWND, default_name: &str, default_ext: &str) -> Result> { let mut buffer = vec![0u16; 1024]; let default_wide: Vec = OsStr::new(default_name) .encode_wide() @@ -5874,7 +5904,10 @@ fn save_file_dialog(hwnd: HWND, default_name: &str) -> Result> { let copy_len = default_wide.len().min(buffer.len() - 1); buffer[..copy_len].copy_from_slice(&default_wide[..copy_len]); let filter = w!("All Files\0*.*\0\0"); - let def_ext = w!("txt"); + let def_ext_wide: Vec = OsStr::new(default_ext.trim_start_matches('.')) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); let mut ofn = OPENFILENAMEW { lStructSize: std::mem::size_of::() as u32, @@ -5882,7 +5915,7 @@ fn save_file_dialog(hwnd: HWND, default_name: &str) -> Result> { lpstrFile: PWSTR(buffer.as_mut_ptr()), nMaxFile: buffer.len() as u32, lpstrFilter: PCWSTR(filter.as_ptr()), - lpstrDefExt: PCWSTR(def_ext.as_ptr()), + lpstrDefExt: PCWSTR(def_ext_wide.as_ptr()), Flags: OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT, ..Default::default() }; From 3e8f385ac3489f2ebd724a464608159fba08afa4 Mon Sep 17 00:00:00 2001 From: electricmessiah Date: Sat, 11 Jul 2026 10:15:31 -0400 Subject: [PATCH 4/4] Fix: default the status bar language-extension display to .txt Untitled/plain-text tabs showed a blank extension in the status bar instead of falling back to .txt, inconsistent with the Save dialog's existing .txt default for the same case. --- src/platform/win32.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/platform/win32.rs b/src/platform/win32.rs index a4a02b5..a304e41 100644 --- a/src/platform/win32.rs +++ b/src/platform/win32.rs @@ -3000,7 +3000,11 @@ fn update_status(state: &AppState) { if doc_tab.doc.is_dirty { flags.push('*'); } - if let Some(ext) = doc_tab.lexer_override.and_then(extension_for_lexer) { + { + let ext = doc_tab + .lexer_override + .and_then(extension_for_lexer) + .unwrap_or(".txt"); if !flags.is_empty() { flags.push(' '); }