diff --git a/src/platform/win32.rs b/src/platform/win32.rs index db63363..a304e41 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, default_ext) = { let doc_tab = state .docs .get(index) @@ -2756,10 +2758,15 @@ 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)? { + match save_file_dialog(hwnd, &display_name, default_ext)? { Some(path) => path, None => return Ok(false), } @@ -2993,6 +3000,16 @@ fn update_status(state: &AppState) { if doc_tab.doc.is_dirty { flags.push('*'); } + { + let ext = doc_tab + .lexer_override + .and_then(extension_for_lexer) + .unwrap_or(".txt"); + if !flags.is_empty() { + flags.push(' '); + } + flags.push_str(ext); + } if doc_tab.doc.large_file_mode { if !flags.is_empty() { flags.push(' '); @@ -4456,6 +4473,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, @@ -5862,9 +5899,19 @@ fn open_file_dialog(hwnd: HWND) -> Result> { ))) } -fn save_file_dialog(hwnd: HWND) -> 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() + .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 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, @@ -5872,6 +5919,7 @@ fn save_file_dialog(hwnd: HWND) -> Result> { lpstrFile: PWSTR(buffer.as_mut_ptr()), nMaxFile: buffer.len() as u32, lpstrFilter: PCWSTR(filter.as_ptr()), + lpstrDefExt: PCWSTR(def_ext_wide.as_ptr()), Flags: OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT, ..Default::default() };