From 006f465d1bf4a37c8470028bc47876b5f5f4c272 Mon Sep 17 00:00:00 2001 From: Jenia Dysin Date: Sat, 23 Aug 2025 13:40:19 +0000 Subject: [PATCH 01/27] Add ability to select single/multiple line(s) by clicking the line number and or dragging across --- src/tui.rs | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/tui.rs b/src/tui.rs index 78308f0d679..58749a6e6b0 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -2312,11 +2312,46 @@ impl<'a> Context<'a, '_> { } } } + // Clicking on the left margin (gutter) should select the whole line. + let margin_rect = Rect { + left: inner.left, + top: inner.top, + right: inner.left + tb.margin_width(), + bottom: inner.bottom, + }; - self.set_input_consumed(); - return make_cursor_visible; - } + if margin_rect.contains(self.tui.mouse_down_position) { + // If the user is dragging the mouse after pressing in the margin, + // update the selection to span from the initial click line to the current mouse line. + if self.tui.mouse_is_drag { + // Ensure there's a selection anchor at the original cursor position. + if !tb.has_selection() { + tb.start_selection(); + } + let drag_pos = Point { x: 0, y: pos.y }; + tb.selection_update_visual(drag_pos); + tc.preferred_column = tb.cursor_visual_pos().x; + make_cursor_visible = true; + } else { + // Single click: either extend selection (Shift) or select the clicked line. + let click_pos = pos; + if self.input_mouse_modifiers.contains(kbmod::SHIFT) { + tb.selection_update_visual(click_pos); + } else { + tb.cursor_move_to_visual(click_pos); + tb.select_line(); + } + + tc.preferred_column = tb.cursor_visual_pos().x; + make_cursor_visible = true; + } + + // Consume input once and return. + self.set_input_consumed(); + return make_cursor_visible; + } + } if !tc.has_focus { return false; } From 759bba610d91b220779eeda94e9d4b020433d90c Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 25 Aug 2025 19:54:53 +0200 Subject: [PATCH 02/27] Move build.rs into its own directory (#623) --- Cargo.toml | 1 + build.rs | 315 ----------------------------------------------- build/helpers.rs | 11 ++ build/i18n.rs | 201 ++++++++++++++++++++++++++++++ build/main.rs | 123 ++++++++++++++++++ 5 files changed, 336 insertions(+), 315 deletions(-) delete mode 100644 build.rs create mode 100644 build/helpers.rs create mode 100644 build/i18n.rs create mode 100644 build/main.rs diff --git a/Cargo.toml b/Cargo.toml index ac4026bc4cd..792aa41d4fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/microsoft/edit" homepage = "https://github.com/microsoft/edit" license = "MIT" categories = ["text-editors"] +build = "build/main.rs" [[bench]] name = "lib" diff --git a/build.rs b/build.rs deleted file mode 100644 index 2034ff83616..00000000000 --- a/build.rs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#![allow(irrefutable_let_patterns)] - -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::env::VarError; -use std::io::Write as _; - -#[derive(Clone, Copy, PartialEq, Eq)] -enum TargetOs { - Windows, - MacOS, - Unix, -} - -fn main() { - let target_os = match env_opt("CARGO_CFG_TARGET_OS").as_str() { - "windows" => TargetOs::Windows, - "macos" | "ios" => TargetOs::MacOS, - _ => TargetOs::Unix, - }; - - compile_i18n(); - configure_icu(target_os); - #[cfg(windows)] - configure_windows_binary(target_os); -} - -fn compile_i18n() { - const PATH: &str = "i18n/edit.toml"; - - let i18n = std::fs::read_to_string(PATH).unwrap(); - let i18n = toml_span::parse(&i18n).expect("Failed to parse i18n file"); - let root = i18n.as_table().unwrap(); - let mut languages = Vec::new(); - let mut aliases = Vec::new(); - let mut translations: BTreeMap> = BTreeMap::new(); - - for (k, v) in root.iter() { - match &k.name[..] { - "__default__" => { - const ERROR: &str = "i18n: __default__ must be [str]"; - languages = Vec::from_iter( - v.as_array() - .expect(ERROR) - .iter() - .map(|lang| lang.as_str().expect(ERROR).to_string()), - ); - } - "__alias__" => { - const ERROR: &str = "i18n: __alias__ must be str->str"; - aliases.extend(v.as_table().expect(ERROR).iter().map(|(alias, lang)| { - (alias.to_string(), lang.as_str().expect(ERROR).to_string()) - })); - } - _ => { - const ERROR: &str = "i18n: LocId must be str->str"; - translations.insert( - k.name.to_string(), - HashMap::from_iter( - v.as_table().expect(ERROR).iter().map(|(k, v)| { - (k.name.to_string(), v.as_str().expect(ERROR).to_string()) - }), - ), - ); - } - } - } - - // Use EDIT_CFG_LANGUAGES for the language list if it is set. - if let cfg_languages = env_opt("EDIT_CFG_LANGUAGES") - && !cfg_languages.is_empty() - { - languages = cfg_languages.split(',').map(|lang| lang.to_string()).collect(); - } - - // Ensure English as the fallback language is always present. - if !languages.iter().any(|l| l == "en") { - languages.push("en".to_string()); - } - - // Normalize language tags for use in source code (i.e. no "-"). - for lang in &mut languages { - if lang.is_empty() { - panic!("i18n: empty language tag"); - } - for c in unsafe { lang.as_bytes_mut() } { - *c = match *c { - b'A'..=b'Z' | b'a'..=b'z' => c.to_ascii_lowercase(), - b'-' => b'_', - b'_' => b'_', - _ => panic!("i18n: language tag \"{lang}\" must be [a-zA-Z_-]"), - } - } - } - - // * Validate that there are no duplicate language tags. - // * Validate that all language tags are valid. - // * Merge the aliases into the languages list. - let mut languages_with_aliases: Vec<_>; - { - let mut specified = HashSet::new(); - for lang in &languages { - if !specified.insert(lang.as_str()) { - panic!("i18n: duplicate language tag \"{lang}\""); - } - } - - let mut available = HashSet::new(); - for v in translations.values() { - for lang in v.keys() { - available.insert(lang.as_str()); - } - } - - let mut invalid = Vec::new(); - for lang in &languages { - if !available.contains(lang.as_str()) { - invalid.push(lang.as_str()); - } - } - if !invalid.is_empty() { - panic!("i18n: invalid language tags {invalid:?}"); - } - - languages_with_aliases = languages.iter().map(|l| (l.clone(), l.clone())).collect(); - for (alias, lang) in aliases { - if specified.contains(lang.as_str()) && !specified.contains(alias.as_str()) { - languages_with_aliases.push((alias, lang)); - } - } - } - - // Sort languages by: - // - "en" first, because it'll map to `LangId::en == 0`, which is the default. - // - then alphabetically - // - but tags with subtags (e.g. "zh_hans") before those without (e.g. "zh"). - { - fn sort(a: &String, b: &String) -> std::cmp::Ordering { - match (a == "en", b == "en") { - (true, false) => std::cmp::Ordering::Less, - (false, true) => std::cmp::Ordering::Greater, - _ => { - let (a0, a1) = a.split_once('_').unwrap_or((a, "xxxxxx")); - let (b0, b1) = b.split_once('_').unwrap_or((b, "xxxxxx")); - match a0.cmp(b0) { - std::cmp::Ordering::Equal => a1.cmp(b1), - ord => ord, - } - } - } - } - languages.sort_unstable_by(sort); - languages_with_aliases.sort_unstable_by(|a, b| sort(&a.0, &b.0)); - } - - // Generate the source code for the i18n data. - { - let out_dir = env_opt("OUT_DIR"); - let mut out = std::fs::File::create(format!("{out_dir}/i18n_edit.rs")).unwrap(); - let mut writer = std::io::BufWriter::new(&mut out); - - _ = write!( - writer, - "// This file is generated by build.rs. Do not edit it manually.\n\ - \n\ - #[derive(Clone, Copy, PartialEq, Eq)]\n\ - pub enum LocId {{\n", - ); - - for (k, _) in translations.iter() { - _ = writeln!(writer, " {k},"); - } - - _ = write!( - writer, - "}}\n\ - \n\ - #[allow(non_camel_case_types)]\n\ - #[derive(Clone, Copy, PartialEq, Eq)]\n\ - pub enum LangId {{\n", - ); - - for lang in &languages { - _ = writeln!(writer, " {lang},"); - } - - _ = write!( - writer, - "}}\n\ - \n\ - const LANGUAGES: &[(&str, LangId)] = &[\n" - ); - - for (alias, lang) in &languages_with_aliases { - _ = writeln!(writer, " ({alias:?}, LangId::{lang}),"); - } - - _ = write!( - writer, - "];\n\ - \n\ - const TRANSLATIONS: [[&str; {}]; {}] = [\n", - translations.len(), - languages.len(), - ); - - for lang in &languages { - _ = writeln!(writer, " ["); - for (_, v) in translations.iter() { - const DEFAULT: &String = &String::new(); - let v = v.get(lang).or_else(|| v.get("en")).unwrap_or(DEFAULT); - _ = writeln!(writer, " {v:?},"); - } - _ = writeln!(writer, " ],"); - } - - _ = writeln!(writer, "];"); - } - - println!("cargo::rerun-if-env-changed=EDIT_CFG_LANGUAGES"); - println!("cargo::rerun-if-changed={PATH}"); -} - -fn configure_icu(target_os: TargetOs) { - let icuuc_soname = env_opt("EDIT_CFG_ICUUC_SONAME"); - let icui18n_soname = env_opt("EDIT_CFG_ICUI18N_SONAME"); - let cpp_exports = env_opt("EDIT_CFG_ICU_CPP_EXPORTS"); - let renaming_version = env_opt("EDIT_CFG_ICU_RENAMING_VERSION"); - let renaming_auto_detect = env_opt("EDIT_CFG_ICU_RENAMING_AUTO_DETECT"); - - // If none of the `EDIT_CFG_ICU*` environment variables are set, - // we default to enabling `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` on UNIX. - // This slightly improves portability at least in the cases where the SONAMEs match our defaults. - let renaming_auto_detect = if !renaming_auto_detect.is_empty() { - renaming_auto_detect.parse::().unwrap() - } else { - target_os == TargetOs::Unix - && icuuc_soname.is_empty() - && icui18n_soname.is_empty() - && cpp_exports.is_empty() - && renaming_version.is_empty() - }; - if renaming_auto_detect && !renaming_version.is_empty() { - // It makes no sense to specify an explicit version and also ask for auto-detection. - panic!( - "Either `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` or `EDIT_CFG_ICU_RENAMING_VERSION` must be set, but not both" - ); - } - - let icuuc_soname = if !icuuc_soname.is_empty() { - &icuuc_soname - } else { - match target_os { - TargetOs::Windows => "icuuc.dll", - TargetOs::MacOS => "libicucore.dylib", - TargetOs::Unix => "libicuuc.so", - } - }; - let icui18n_soname = if !icui18n_soname.is_empty() { - &icui18n_soname - } else { - match target_os { - TargetOs::Windows => "icuin.dll", - TargetOs::MacOS => "libicucore.dylib", - TargetOs::Unix => "libicui18n.so", - } - }; - let icu_export_prefix = - if !cpp_exports.is_empty() && cpp_exports.parse::().unwrap() { "_" } else { "" }; - let icu_export_suffix = - if !renaming_version.is_empty() { format!("_{renaming_version}") } else { String::new() }; - - println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUUC_SONAME"); - println!("cargo::rustc-env=EDIT_CFG_ICUUC_SONAME={icuuc_soname}"); - println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUI18N_SONAME"); - println!("cargo::rustc-env=EDIT_CFG_ICUI18N_SONAME={icui18n_soname}"); - println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_PREFIX"); - println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_PREFIX={icu_export_prefix}"); - println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_SUFFIX"); - println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_SUFFIX={icu_export_suffix}"); - println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_RENAMING_AUTO_DETECT"); - println!("cargo::rustc-check-cfg=cfg(edit_icu_renaming_auto_detect)"); - if renaming_auto_detect { - println!("cargo::rustc-cfg=edit_icu_renaming_auto_detect"); - } -} - -#[cfg(windows)] -fn configure_windows_binary(target_os: TargetOs) { - if target_os != TargetOs::Windows { - return; - } - - const PATH: &str = "src/bin/edit/edit.exe.manifest"; - println!("cargo::rerun-if-changed={PATH}"); - winresource::WindowsResource::new() - .set_manifest_file(PATH) - .set("FileDescription", "Microsoft Edit") - .set("LegalCopyright", "© Microsoft Corporation. All rights reserved.") - .set_icon("assets/edit.ico") - .compile() - .unwrap(); -} - -fn env_opt(name: &str) -> String { - match std::env::var(name) { - Ok(value) => value, - Err(VarError::NotPresent) => String::new(), - Err(VarError::NotUnicode(_)) => { - panic!("Environment variable `{name}` is not valid Unicode") - } - } -} diff --git a/build/helpers.rs b/build/helpers.rs new file mode 100644 index 00000000000..7453c34a0a9 --- /dev/null +++ b/build/helpers.rs @@ -0,0 +1,11 @@ +use std::env::VarError; + +pub fn env_opt(name: &str) -> String { + match std::env::var(name) { + Ok(value) => value, + Err(VarError::NotPresent) => String::new(), + Err(VarError::NotUnicode(_)) => { + panic!("Environment variable `{name}` is not valid Unicode") + } + } +} diff --git a/build/i18n.rs b/build/i18n.rs new file mode 100644 index 00000000000..4089902e313 --- /dev/null +++ b/build/i18n.rs @@ -0,0 +1,201 @@ +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::fmt::Write as _; + +use crate::helpers::env_opt; + +pub fn generate(definitions: &str) -> String { + let i18n = toml_span::parse(definitions).expect("Failed to parse i18n file"); + let root = i18n.as_table().unwrap(); + let mut languages = Vec::new(); + let mut aliases = Vec::new(); + let mut translations: BTreeMap> = BTreeMap::new(); + + for (k, v) in root.iter() { + match &k.name[..] { + "__default__" => { + const ERROR: &str = "i18n: __default__ must be [str]"; + languages = Vec::from_iter( + v.as_array() + .expect(ERROR) + .iter() + .map(|lang| lang.as_str().expect(ERROR).to_string()), + ); + } + "__alias__" => { + const ERROR: &str = "i18n: __alias__ must be str->str"; + aliases.extend(v.as_table().expect(ERROR).iter().map(|(alias, lang)| { + (alias.to_string(), lang.as_str().expect(ERROR).to_string()) + })); + } + _ => { + const ERROR: &str = "i18n: LocId must be str->str"; + translations.insert( + k.name.to_string(), + HashMap::from_iter( + v.as_table().expect(ERROR).iter().map(|(k, v)| { + (k.name.to_string(), v.as_str().expect(ERROR).to_string()) + }), + ), + ); + } + } + } + + // Use EDIT_CFG_LANGUAGES for the language list if it is set. + if let cfg_languages = env_opt("EDIT_CFG_LANGUAGES") + && !cfg_languages.is_empty() + { + languages = cfg_languages.split(',').map(|lang| lang.to_string()).collect(); + } + + // Ensure English as the fallback language is always present. + if !languages.iter().any(|l| l == "en") { + languages.push("en".to_string()); + } + + // Normalize language tags for use in source code (i.e. no "-"). + for lang in &mut languages { + if lang.is_empty() { + panic!("i18n: empty language tag"); + } + for c in unsafe { lang.as_bytes_mut() } { + *c = match *c { + b'A'..=b'Z' | b'a'..=b'z' => c.to_ascii_lowercase(), + b'-' => b'_', + b'_' => b'_', + _ => panic!("i18n: language tag \"{lang}\" must be [a-zA-Z_-]"), + } + } + } + + // * Validate that there are no duplicate language tags. + // * Validate that all language tags are valid. + // * Merge the aliases into the languages list. + let mut languages_with_aliases: Vec<_>; + { + let mut specified = HashSet::new(); + for lang in &languages { + if !specified.insert(lang.as_str()) { + panic!("i18n: duplicate language tag \"{lang}\""); + } + } + + let mut available = HashSet::new(); + for v in translations.values() { + for lang in v.keys() { + available.insert(lang.as_str()); + } + } + + let mut invalid = Vec::new(); + for lang in &languages { + if !available.contains(lang.as_str()) { + invalid.push(lang.as_str()); + } + } + if !invalid.is_empty() { + panic!("i18n: invalid language tags {invalid:?}"); + } + + languages_with_aliases = languages.iter().map(|l| (l.clone(), l.clone())).collect(); + for (alias, lang) in aliases { + if specified.contains(lang.as_str()) && !specified.contains(alias.as_str()) { + languages_with_aliases.push((alias, lang)); + } + } + } + + // Sort languages by: + // - "en" first, because it'll map to `LangId::en == 0`, which is the default. + // - then alphabetically + // - but tags with subtags (e.g. "zh_hans") before those without (e.g. "zh"). + { + fn sort(a: &String, b: &String) -> std::cmp::Ordering { + match (a == "en", b == "en") { + (true, false) => std::cmp::Ordering::Less, + (false, true) => std::cmp::Ordering::Greater, + _ => { + let (a0, a1) = a.split_once('_').unwrap_or((a, "xxxxxx")); + let (b0, b1) = b.split_once('_').unwrap_or((b, "xxxxxx")); + match a0.cmp(b0) { + std::cmp::Ordering::Equal => a1.cmp(b1), + ord => ord, + } + } + } + } + languages.sort_unstable_by(sort); + languages_with_aliases.sort_unstable_by(|a, b| sort(&a.0, &b.0)); + } + + let mut out = String::new(); + + // Generate the source code for the i18n data. + { + _ = write!( + out, + "\ +// This file is generated by build.rs. Do not edit it manually. + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum LocId {{", + ); + + for (k, _) in translations.iter() { + _ = writeln!(out, " {k},"); + } + + _ = write!( + out, + "\ +}} + +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum LangId {{ +", + ); + + for lang in &languages { + _ = writeln!(out, " {lang},"); + } + + _ = write!( + out, + "\ +}} + +const LANGUAGES: &[(&str, LangId)] = &[ +" + ); + + for (alias, lang) in &languages_with_aliases { + _ = writeln!(out, " ({alias:?}, LangId::{lang}),"); + } + + _ = write!( + out, + "\ +]; + +const TRANSLATIONS: [[&str; {}]; {}] = [ +", + translations.len(), + languages.len(), + ); + + for lang in &languages { + _ = writeln!(out, " ["); + for (_, v) in translations.iter() { + const DEFAULT: &String = &String::new(); + let v = v.get(lang).or_else(|| v.get("en")).unwrap_or(DEFAULT); + _ = writeln!(out, " {v:?},"); + } + _ = writeln!(out, " ],"); + } + + _ = writeln!(out, "];"); + } + + out +} diff --git a/build/main.rs b/build/main.rs new file mode 100644 index 00000000000..fb1d8d157f9 --- /dev/null +++ b/build/main.rs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![allow(irrefutable_let_patterns)] + +use crate::helpers::env_opt; + +mod helpers; +mod i18n; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum TargetOs { + Windows, + MacOS, + Unix, +} + +fn main() { + let target_os = match env_opt("CARGO_CFG_TARGET_OS").as_str() { + "windows" => TargetOs::Windows, + "macos" | "ios" => TargetOs::MacOS, + _ => TargetOs::Unix, + }; + + compile_i18n(); + configure_icu(target_os); + #[cfg(windows)] + configure_windows_binary(target_os); +} + +fn compile_i18n() { + const PATH: &str = "i18n/edit.toml"; + + let i18n = std::fs::read_to_string(PATH).unwrap(); + let contents = i18n::generate(&i18n); + let out_dir = env_opt("OUT_DIR"); + let path = format!("{out_dir}/i18n_edit.rs"); + std::fs::write(path, contents).unwrap(); + + println!("cargo::rerun-if-env-changed=EDIT_CFG_LANGUAGES"); + println!("cargo::rerun-if-changed={PATH}"); +} + +fn configure_icu(target_os: TargetOs) { + let icuuc_soname = env_opt("EDIT_CFG_ICUUC_SONAME"); + let icui18n_soname = env_opt("EDIT_CFG_ICUI18N_SONAME"); + let cpp_exports = env_opt("EDIT_CFG_ICU_CPP_EXPORTS"); + let renaming_version = env_opt("EDIT_CFG_ICU_RENAMING_VERSION"); + let renaming_auto_detect = env_opt("EDIT_CFG_ICU_RENAMING_AUTO_DETECT"); + + // If none of the `EDIT_CFG_ICU*` environment variables are set, + // we default to enabling `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` on UNIX. + // This slightly improves portability at least in the cases where the SONAMEs match our defaults. + let renaming_auto_detect = if !renaming_auto_detect.is_empty() { + renaming_auto_detect.parse::().unwrap() + } else { + target_os == TargetOs::Unix + && icuuc_soname.is_empty() + && icui18n_soname.is_empty() + && cpp_exports.is_empty() + && renaming_version.is_empty() + }; + if renaming_auto_detect && !renaming_version.is_empty() { + // It makes no sense to specify an explicit version and also ask for auto-detection. + panic!( + "Either `EDIT_CFG_ICU_RENAMING_AUTO_DETECT` or `EDIT_CFG_ICU_RENAMING_VERSION` must be set, but not both" + ); + } + + let icuuc_soname = if !icuuc_soname.is_empty() { + &icuuc_soname + } else { + match target_os { + TargetOs::Windows => "icuuc.dll", + TargetOs::MacOS => "libicucore.dylib", + TargetOs::Unix => "libicuuc.so", + } + }; + let icui18n_soname = if !icui18n_soname.is_empty() { + &icui18n_soname + } else { + match target_os { + TargetOs::Windows => "icuin.dll", + TargetOs::MacOS => "libicucore.dylib", + TargetOs::Unix => "libicui18n.so", + } + }; + let icu_export_prefix = + if !cpp_exports.is_empty() && cpp_exports.parse::().unwrap() { "_" } else { "" }; + let icu_export_suffix = + if !renaming_version.is_empty() { format!("_{renaming_version}") } else { String::new() }; + + println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUUC_SONAME"); + println!("cargo::rustc-env=EDIT_CFG_ICUUC_SONAME={icuuc_soname}"); + println!("cargo::rerun-if-env-changed=EDIT_CFG_ICUI18N_SONAME"); + println!("cargo::rustc-env=EDIT_CFG_ICUI18N_SONAME={icui18n_soname}"); + println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_PREFIX"); + println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_PREFIX={icu_export_prefix}"); + println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_EXPORT_SUFFIX"); + println!("cargo::rustc-env=EDIT_CFG_ICU_EXPORT_SUFFIX={icu_export_suffix}"); + println!("cargo::rerun-if-env-changed=EDIT_CFG_ICU_RENAMING_AUTO_DETECT"); + println!("cargo::rustc-check-cfg=cfg(edit_icu_renaming_auto_detect)"); + if renaming_auto_detect { + println!("cargo::rustc-cfg=edit_icu_renaming_auto_detect"); + } +} + +#[cfg(windows)] +fn configure_windows_binary(target_os: TargetOs) { + if target_os != TargetOs::Windows { + return; + } + + const PATH: &str = "src/bin/edit/edit.exe.manifest"; + println!("cargo::rerun-if-changed={PATH}"); + winresource::WindowsResource::new() + .set_manifest_file(PATH) + .set("FileDescription", "Microsoft Edit") + .set("LegalCopyright", "© Microsoft Corporation. All rights reserved.") + .set_icon("assets/edit.ico") + .compile() + .unwrap(); +} From 670a56de8188157fa0c537b90b1e01d7d19b0a16 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 25 Aug 2025 20:18:00 +0200 Subject: [PATCH 03/27] Various minor improvements (#625) --- src/arena/scratch.rs | 149 +++++++++++++------- src/arena/string.rs | 8 ++ src/hash.rs | 20 +++ src/helpers.rs | 2 +- tools/grapheme-table-gen/Cargo.lock | 195 +++++++++++++-------------- tools/grapheme-table-gen/Cargo.toml | 14 +- tools/grapheme-table-gen/src/main.rs | 41 ++++-- 7 files changed, 257 insertions(+), 172 deletions(-) diff --git a/src/arena/scratch.rs b/src/arena/scratch.rs index bb91ead7ebe..d2805095b4f 100644 --- a/src/arena/scratch.rs +++ b/src/arena/scratch.rs @@ -9,57 +9,6 @@ use super::{Arena, release}; use crate::apperr; use crate::helpers::*; -static mut S_SCRATCH: [release::Arena; 2] = - const { [release::Arena::empty(), release::Arena::empty()] }; - -/// Initialize the scratch arenas with a given capacity. -/// Call this before using [`scratch_arena`]. -pub fn init(capacity: usize) -> apperr::Result<()> { - unsafe { - for s in &mut S_SCRATCH[..] { - *s = release::Arena::new(capacity)?; - } - } - Ok(()) -} - -/// Need an arena for temporary allocations? [`scratch_arena`] got you covered. -/// Call [`scratch_arena`] and it'll return an [`Arena`] that resets when it goes out of scope. -/// -/// --- -/// -/// Most methods make just two kinds of allocations: -/// * Interior: Temporary data that can be deallocated when the function returns. -/// * Exterior: Data that is returned to the caller and must remain alive until the caller stops using it. -/// -/// Such methods only have two lifetimes, for which you consequently also only need two arenas. -/// ...even if your method calls other methods recursively! This is because the exterior allocations -/// of a callee are simply interior allocations to the caller, and so on, recursively. -/// -/// This works as long as the two arenas flip/flop between being used as interior/exterior allocator -/// along the callstack. To ensure that is the case, we use a recursion counter in debug builds. -/// -/// This approach was described among others at: -/// -/// # Safety -/// -/// If your function takes an [`Arena`] argument, you **MUST** pass it to `scratch_arena` as `Some(&arena)`. -pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> { - unsafe { - #[cfg(test)] - if S_SCRATCH[0].is_empty() { - init(128 * 1024 * 1024).unwrap(); - } - - #[cfg(debug_assertions)] - let conflict = conflict.map(|a| a.delegate_target_unchecked()); - - let index = opt_ptr_eq(conflict, Some(&S_SCRATCH[0])) as usize; - let arena = &S_SCRATCH[index]; - ScratchArena::new(arena) - } -} - /// Borrows an [`Arena`] for temporary allocations. /// /// See [`scratch_arena`]. @@ -115,3 +64,101 @@ impl Deref for ScratchArena<'_> { self.arena } } + +mod single_threaded { + use super::*; + + static mut S_SCRATCH: [release::Arena; 2] = + const { [release::Arena::empty(), release::Arena::empty()] }; + + /// Initialize the scratch arenas with a given capacity. + /// Call this before using [`scratch_arena`]. + #[allow(dead_code)] + pub fn init(capacity: usize) -> apperr::Result<()> { + unsafe { + for s in &mut S_SCRATCH[..] { + *s = release::Arena::new(capacity)?; + } + } + Ok(()) + } + + /// Need an arena for temporary allocations? [`scratch_arena`] got you covered. + /// Call [`scratch_arena`] and it'll return an [`Arena`] that resets when it goes out of scope. + /// + /// --- + /// + /// Most methods make just two kinds of allocations: + /// * Interior: Temporary data that can be deallocated when the function returns. + /// * Exterior: Data that is returned to the caller and must remain alive until the caller stops using it. + /// + /// Such methods only have two lifetimes, for which you consequently also only need two arenas. + /// ...even if your method calls other methods recursively! This is because the exterior allocations + /// of a callee are simply interior allocations to the caller, and so on, recursively. + /// + /// This works as long as the two arenas flip/flop between being used as interior/exterior allocator + /// along the callstack. To ensure that is the case, we use a recursion counter in debug builds. + /// + /// This approach was described among others at: + /// + /// # Safety + /// + /// If your function takes an [`Arena`] argument, you **MUST** pass it to `scratch_arena` as `Some(&arena)`. + #[allow(dead_code)] + pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> { + unsafe { + #[cfg(debug_assertions)] + let conflict = conflict.map(|a| a.delegate_target_unchecked()); + + let index = opt_ptr_eq(conflict, Some(&S_SCRATCH[0])) as usize; + let arena = &S_SCRATCH[index]; + ScratchArena::new(arena) + } + } +} + +mod multi_threaded { + use std::cell::Cell; + use std::ptr; + + use super::*; + + thread_local! { + static S_SCRATCH: [Cell; 2] = + const { [Cell::new(release::Arena::empty()), Cell::new(release::Arena::empty())] }; + } + + /// Does nothing. + #[allow(dead_code)] + pub fn init(_: usize) -> apperr::Result<()> { + Ok(()) + } + + /// See `single_threaded::scratch_arena`. + #[allow(dead_code)] + pub fn scratch_arena(conflict: Option<&Arena>) -> ScratchArena<'static> { + #[cfg(debug_assertions)] + let conflict = conflict.map(|a| a.delegate_target_unchecked()); + + #[cold] + fn init(s: &[Cell; 2]) { + for s in s { + s.set(release::Arena::new(128 * 1024 * 1024).unwrap()); + } + } + + S_SCRATCH.with(|s| { + let index = ptr::eq(opt_ptr(conflict), s[0].as_ptr()) as usize; + let arena = unsafe { &*s[index].as_ptr() }; + if arena.is_empty() { + init(s); + } + ScratchArena::new(arena) + }) + } +} + +#[cfg(test)] +pub use multi_threaded::*; +#[cfg(not(test))] +pub use single_threaded::*; diff --git a/src/arena/string.rs b/src/arena/string.rs index e7b91f54ab4..e2ac650b02b 100644 --- a/src/arena/string.rs +++ b/src/arena/string.rs @@ -227,12 +227,20 @@ impl fmt::Debug for ArenaString<'_> { } } +impl PartialEq> for ArenaString<'_> { + fn eq(&self, other: &ArenaString) -> bool { + self.as_str() == other.as_str() + } +} + impl PartialEq<&str> for ArenaString<'_> { fn eq(&self, other: &&str) -> bool { self.as_str() == *other } } +impl Eq for ArenaString<'_> {} + impl Deref for ArenaString<'_> { type Target = str; diff --git a/src/hash.rs b/src/hash.rs index 0634529bc57..84bc4f04499 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -3,6 +3,26 @@ //! Provides fast, non-cryptographic hash functions. +use std::hash::Hasher; + +/// A [`Hasher`] implementation for the wyhash algorithm. +/// +/// NOTE that you DO NOT want to use this for hashing mere strings/slices. +/// The stdlib [`Hash`] implementation for them calls [`Hasher::write`] twice, +/// once for the contents and once for a length prefix / `0xff` suffix. +#[derive(Default, Clone, Copy)] +pub struct WyHash(u64); + +impl Hasher for WyHash { + fn finish(&self) -> u64 { + self.0 + } + + fn write(&mut self, bytes: &[u8]) { + self.0 = hash(self.0, bytes); + } +} + /// The venerable wyhash hash function. /// /// It's fast, has good statistical properties, and is in the public domain. diff --git a/src/helpers.rs b/src/helpers.rs index b8e30a3c03c..bd433588881 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -160,7 +160,7 @@ where #[inline(always)] #[allow(clippy::ptr_eq)] -fn opt_ptr(a: Option<&T>) -> *const T { +pub fn opt_ptr(a: Option<&T>) -> *const T { unsafe { mem::transmute(a) } } diff --git a/tools/grapheme-table-gen/Cargo.lock b/tools/grapheme-table-gen/Cargo.lock index f063166afdf..3bd26ecf038 100644 --- a/tools/grapheme-table-gen/Cargo.lock +++ b/tools/grapheme-table-gen/Cargo.lock @@ -19,49 +19,49 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-link", ] [[package]] @@ -97,9 +97,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "grapheme-table-gen" @@ -115,14 +115,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -138,15 +139,15 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.5" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -154,15 +155,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "num-traits" @@ -175,9 +176,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "pico-args" @@ -187,27 +188,27 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -215,9 +216,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -229,6 +230,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "shlex" version = "1.3.0" @@ -237,9 +244,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.91" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -248,26 +255,27 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -279,9 +287,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -289,9 +297,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -302,79 +310,68 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-implement" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" +name = "windows-interface" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "windows-result" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "windows-strings" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] diff --git a/tools/grapheme-table-gen/Cargo.toml b/tools/grapheme-table-gen/Cargo.toml index 46cf013cf05..083d70551b4 100644 --- a/tools/grapheme-table-gen/Cargo.toml +++ b/tools/grapheme-table-gen/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "grapheme-table-gen" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] -anyhow = "1.0.95" -chrono = "0.4.39" -indoc = "2.0.5" -pico-args = { version = "0.5.0", features = ["eq-separator"] } -rayon = "1.10.0" -roxmltree = { version = "0.20.0", default-features = false, features = ["std"] } +anyhow = "1.0" +chrono = "0.4" +indoc = "2.0" +pico-args = { version = "0.5", features = ["eq-separator"] } +rayon = "1.10" +roxmltree = { version = "0.20", default-features = false, features = ["std"] } diff --git a/tools/grapheme-table-gen/src/main.rs b/tools/grapheme-table-gen/src/main.rs index 69eca2d4e2f..60f248b2b08 100644 --- a/tools/grapheme-table-gen/src/main.rs +++ b/tools/grapheme-table-gen/src/main.rs @@ -805,7 +805,7 @@ fn extract_values_from_ucd(doc: &roxmltree::Document, out: &Output) -> anyhow::R "LV" => ClusterBreak::HangulLV, // Hangul Syllable Type LV "LVT" => ClusterBreak::HangulLVT, // Hangul Syllable Type LVT _ => bail!( - "Unrecognized GCB {:?} for U+{:04X} to U+{:04X}", + "Unrecognized GCB={} for U+{:04X} to U+{:04X}", char_attributes.grapheme_cluster_break, range.start(), range.end() @@ -818,7 +818,7 @@ fn extract_values_from_ucd(doc: &roxmltree::Document, out: &Output) -> anyhow::R // and treat it as an alias of EXTEND, but with the special GB11 properties. if cb != ClusterBreak::Other { bail!( - "Unexpected GCB {:?} with ExtPict=Y for U+{:04X} to U+{:04X}", + "Unexpected GCB={} with ExtPict=Y for U+{:04X} to U+{:04X}", char_attributes.grapheme_cluster_break, range.start(), range.end() @@ -828,24 +828,37 @@ fn extract_values_from_ucd(doc: &roxmltree::Document, out: &Output) -> anyhow::R cb = ClusterBreak::ExtPic; } - cb = match char_attributes.indic_conjunct_break { - "None" | "Extend" => cb, - "Linker" => ClusterBreak::InCBLinker, - "Consonant" => ClusterBreak::InCBConsonant, - _ => bail!( - "Unrecognized InCB {:?} for U+{:04X} to U+{:04X}", - char_attributes.indic_conjunct_break, - range.start(), - range.end() - ), - }; + if !matches!(char_attributes.indic_conjunct_break, "None" | "Extend") { + // If it's not None/Extend, it's Linker/Consonant, and currently + // all of them are GCB=EX/XX. Since we treat them almost like extenders, + // we need to revisit our assumptions if this ever changes. + if !matches!(cb, ClusterBreak::Other | ClusterBreak::Extend) { + bail!( + "Unexpected GCB={} with InCB={} for U+{:04X} to U+{:04X}", + char_attributes.grapheme_cluster_break, + char_attributes.indic_conjunct_break, + range.start(), + range.end() + ); + } + cb = match char_attributes.indic_conjunct_break { + "Linker" => ClusterBreak::InCBLinker, + "Consonant" => ClusterBreak::InCBConsonant, + _ => bail!( + "Unrecognized InCB={} for U+{:04X} to U+{:04X}", + char_attributes.indic_conjunct_break, + range.start(), + range.end() + ), + }; + } let mut cw = match char_attributes.east_asian { "N" | "Na" | "H" => CharacterWidth::Narrow, // Half-width, Narrow, Neutral "F" | "W" => CharacterWidth::Wide, // Wide, Full-width "A" => ambiguous_value, // Ambiguous _ => bail!( - "Unrecognized ea {:?} for U+{:04X} to U+{:04X}", + "Unrecognized ea={} for U+{:04X} to U+{:04X}", char_attributes.east_asian, range.start(), range.end() From 554a6bcde249c3bd747a9094e56fad1376504fee Mon Sep 17 00:00:00 2001 From: four-poetic-drew <163131293+four-poetic-drew@users.noreply.github.com> Date: Thu, 28 Aug 2025 06:49:45 +0800 Subject: [PATCH 04/27] Allow opening directories via the CLI (#577) Co-authored-by: Leonard Hecker --- src/bin/edit/main.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/bin/edit/main.rs b/src/bin/edit/main.rs index f21ae84ebc0..326c88a02ee 100644 --- a/src/bin/edit/main.rs +++ b/src/bin/edit/main.rs @@ -225,7 +225,8 @@ fn run() -> apperr::Result<()> { fn handle_args(state: &mut State) -> apperr::Result { let scratch = scratch_arena(None); let mut paths: Vec = Vec::new_in(&*scratch); - let mut cwd = env::current_dir()?; + let cwd = env::current_dir()?; + let mut dir = None; let mut parse_args = true; // The best CLI argument parser in the world. @@ -251,7 +252,10 @@ fn handle_args(state: &mut State) -> apperr::Result { let p = cwd.join(Path::new(&arg)); let p = path::normalize(&p); - if !p.is_dir() { + if p.is_dir() { + state.wants_file_picker = StateFilePicker::Open; + dir = Some(p); + } else { paths.push(p); } } @@ -259,9 +263,6 @@ fn handle_args(state: &mut State) -> apperr::Result { for p in &paths { state.documents.add_file_path(p)?; } - if let Some(parent) = paths.first().and_then(|p| p.parent()) { - cwd = parent.to_path_buf(); - } if let Some(mut file) = sys::open_stdin_if_redirected() { let doc = state.documents.add_untitled()?; @@ -273,7 +274,13 @@ fn handle_args(state: &mut State) -> apperr::Result { state.documents.add_untitled()?; } - state.file_picker_pending_dir = DisplayablePathBuf::from_path(cwd); + if dir.is_none() + && let Some(parent) = paths.last().and_then(|p| p.parent()) + { + dir = Some(parent.to_path_buf()); + } + + state.file_picker_pending_dir = DisplayablePathBuf::from_path(dir.unwrap_or(cwd)); Ok(false) } From d42ae10f212f4957f24d45aae744f1ed88b968c8 Mon Sep 17 00:00:00 2001 From: hev Date: Fri, 29 Aug 2025 06:31:10 +0800 Subject: [PATCH 05/27] Replace vseq/vand with their immediate-form variants (#630) --- src/simd/lines_bwd.rs | 16 ++++++++-------- src/simd/lines_fwd.rs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/simd/lines_bwd.rs b/src/simd/lines_bwd.rs index 9a21a1edf12..581e69954b7 100644 --- a/src/simd/lines_bwd.rs +++ b/src/simd/lines_bwd.rs @@ -280,7 +280,7 @@ unsafe fn lines_bwd_lsx( } } - let lf = lsx_vrepli_b(b'\n' as i32); + const LF: i32 = b'\n' as i32; let line_stop = line_stop.min(line); let off = end.addr() & 15; if off != 0 && off < end.offset_from_unsigned(beg) { @@ -295,11 +295,11 @@ unsafe fn lines_bwd_lsx( let v3 = lsx_vld::<32>(chunk_start as *const _); let v4 = lsx_vld::<48>(chunk_start as *const _); - let mut sum = lsx_vrepli_b(0); - sum = lsx_vsub_b(sum, lsx_vseq_b(v1, lf)); - sum = lsx_vsub_b(sum, lsx_vseq_b(v2, lf)); - sum = lsx_vsub_b(sum, lsx_vseq_b(v3, lf)); - sum = lsx_vsub_b(sum, lsx_vseq_b(v4, lf)); + let mut sum = lsx_vldi::<0>(); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v1)); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v2)); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v3)); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v4)); let sum = horizontal_sum(sum); let line_next = line - sum as CoordType; @@ -314,9 +314,9 @@ unsafe fn lines_bwd_lsx( while end.offset_from_unsigned(beg) >= 16 { let chunk_start = end.sub(16); let v = lsx_vld::<0>(chunk_start as *const _); - let c = lsx_vseq_b(v, lf); + let c = lsx_vseqi_b::(v); - let ones = lsx_vand_v(c, lsx_vrepli_b(1)); + let ones = lsx_vandi_b::<1>(c); let sum = horizontal_sum(ones); let line_next = line - sum as CoordType; diff --git a/src/simd/lines_fwd.rs b/src/simd/lines_fwd.rs index ddf9e9515be..86f333992a7 100644 --- a/src/simd/lines_fwd.rs +++ b/src/simd/lines_fwd.rs @@ -285,7 +285,7 @@ unsafe fn lines_fwd_lsx( } } - let lf = lsx_vrepli_b(b'\n' as i32); + const LF: i32 = b'\n' as i32; let off = beg.align_offset(16); if off != 0 && off < end.offset_from_unsigned(beg) { (beg, line) = lines_fwd_fallback(beg, beg.add(off), line, line_stop); @@ -298,11 +298,11 @@ unsafe fn lines_fwd_lsx( let v3 = lsx_vld::<32>(beg as *const _); let v4 = lsx_vld::<48>(beg as *const _); - let mut sum = lsx_vrepli_b(0); - sum = lsx_vsub_b(sum, lsx_vseq_b(v1, lf)); - sum = lsx_vsub_b(sum, lsx_vseq_b(v2, lf)); - sum = lsx_vsub_b(sum, lsx_vseq_b(v3, lf)); - sum = lsx_vsub_b(sum, lsx_vseq_b(v4, lf)); + let mut sum = lsx_vldi(0); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v1)); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v2)); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v3)); + sum = lsx_vsub_b(sum, lsx_vseqi_b::(v4)); let sum = horizontal_sum(sum); let line_next = line + sum as CoordType; @@ -316,9 +316,9 @@ unsafe fn lines_fwd_lsx( while end.offset_from_unsigned(beg) >= 16 { let v = lsx_vld::<0>(beg as *const _); - let c = lsx_vseq_b(v, lf); + let c = lsx_vseqi_b::(v); - let ones = lsx_vand_v(c, lsx_vrepli_b(1)); + let ones = lsx_vandi_b::<1>(c); let sum = horizontal_sum(ones); let line_next = line + sum as CoordType; From d3d5dd1b92247b115ff8277826e4b6bb6908890d Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 5 Sep 2025 12:58:47 -0500 Subject: [PATCH 06/27] windows: fix the compatibility section of the manifest (#635) So, it turns out that `supportedOS` was being ignored because it was taken to be in the default `asm.v1` namespace. :) --- src/bin/edit/edit.exe.manifest | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/bin/edit/edit.exe.manifest b/src/bin/edit/edit.exe.manifest index b9724078272..bfc0e30176c 100644 --- a/src/bin/edit/edit.exe.manifest +++ b/src/bin/edit/edit.exe.manifest @@ -2,7 +2,6 @@ SegmentHeap - + - + From dbda63b1262a2b11a2a7dbf6ed3768b67ef77855 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 9 Sep 2025 15:24:09 -0500 Subject: [PATCH 07/27] sys/win: display a useful error message when SetConsoleMode fails (#639) edit will now display specific error messages when the console fails to support `ENABLE_VIRTUAL_TERMINAL_INPUT`. The user will be gently reprimanded for not using the modern console host. It is technically possible to run edit on OpenConsole (or another third- party console host!)--even on Windows 8.1--where it will work properly. --- src/sys/windows.rs | 76 ++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/sys/windows.rs b/src/sys/windows.rs index 48b44942d2c..6ec8f76b89f 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -10,6 +10,7 @@ use std::path::{Path, PathBuf}; use std::ptr::{self, NonNull, null, null_mut}; use std::{mem, time}; +use windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER; use windows_sys::Win32::Storage::FileSystem; use windows_sys::Win32::System::Diagnostics::Debug; use windows_sys::Win32::System::{Console, IO, LibraryLoader, Memory, Threading}; @@ -71,6 +72,9 @@ const CONSOLE_READ_NOWAIT: u16 = 0x0002; const INVALID_CONSOLE_MODE: u32 = u32::MAX; +// Locally-defined error codes follow the HRESULT format, but they have bit 29 set to indicate that they are Customer error codes. +const ERROR_UNSUPPORTED_LEGACY_CONSOLE: u32 = 0xE0010001; + struct State { read_console_input_ex: ReadConsoleInputExW, stdin: Foundation::HANDLE, @@ -160,21 +164,20 @@ pub fn switch_modes() -> apperr::Result<()> { return Err(get_last_error()); } - check_bool_return(Console::SetConsoleCtrlHandler(Some(console_ctrl_handler), 1))?; - - STATE.stdin_cp_old = Console::GetConsoleCP(); - STATE.stdout_cp_old = Console::GetConsoleOutputCP(); check_bool_return(Console::GetConsoleMode(STATE.stdin, &raw mut STATE.stdin_mode_old))?; check_bool_return(Console::GetConsoleMode(STATE.stdout, &raw mut STATE.stdout_mode_old))?; - check_bool_return(Console::SetConsoleCP(Globalization::CP_UTF8))?; - check_bool_return(Console::SetConsoleOutputCP(Globalization::CP_UTF8))?; - check_bool_return(Console::SetConsoleMode( + match check_bool_return(Console::SetConsoleMode( STATE.stdin, Console::ENABLE_WINDOW_INPUT | Console::ENABLE_EXTENDED_FLAGS | Console::ENABLE_VIRTUAL_TERMINAL_INPUT, - ))?; + )) { + Err(e) if e == gle_to_apperr(ERROR_INVALID_PARAMETER) => { + Err(apperr::Error::Sys(ERROR_UNSUPPORTED_LEGACY_CONSOLE)) + } + other => other, + }?; check_bool_return(Console::SetConsoleMode( STATE.stdout, Console::ENABLE_PROCESSED_OUTPUT @@ -183,6 +186,14 @@ pub fn switch_modes() -> apperr::Result<()> { | Console::DISABLE_NEWLINE_AUTO_RETURN, ))?; + check_bool_return(Console::SetConsoleCtrlHandler(Some(console_ctrl_handler), 1))?; + + STATE.stdin_cp_old = Console::GetConsoleCP(); + STATE.stdout_cp_old = Console::GetConsoleOutputCP(); + + check_bool_return(Console::SetConsoleCP(Globalization::CP_UTF8))?; + check_bool_return(Console::SetConsoleOutputCP(Globalization::CP_UTF8))?; + Ok(()) } } @@ -724,31 +735,36 @@ pub(crate) fn io_error_to_apperr(err: std::io::Error) -> apperr::Error { /// Formats a platform error code into a human-readable string. pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result { - unsafe { - let mut ptr: *mut u8 = null_mut(); - let len = Debug::FormatMessageA( - Debug::FORMAT_MESSAGE_ALLOCATE_BUFFER - | Debug::FORMAT_MESSAGE_FROM_SYSTEM - | Debug::FORMAT_MESSAGE_IGNORE_INSERTS, - null(), - code, - 0, - &mut ptr as *mut *mut _ as *mut _, - 0, - null_mut(), - ); + match code { + ERROR_UNSUPPORTED_LEGACY_CONSOLE => { + write!(f, "This application does not support the legacy console.") + } + _ => unsafe { + let mut ptr: *mut u8 = null_mut(); + let len = Debug::FormatMessageA( + Debug::FORMAT_MESSAGE_ALLOCATE_BUFFER + | Debug::FORMAT_MESSAGE_FROM_SYSTEM + | Debug::FORMAT_MESSAGE_IGNORE_INSERTS, + null(), + code, + 0, + &mut ptr as *mut *mut _ as *mut _, + 0, + null_mut(), + ); - write!(f, "Error {code:#08x}")?; + write!(f, "Error {code:#08x}")?; - if len > 0 { - let msg = str_from_raw_parts(ptr, len as usize); - let msg = msg.trim_ascii(); - let msg = msg.replace(['\r', '\n'], " "); - write!(f, ": {msg}")?; - Foundation::LocalFree(ptr as *mut _); - } + if len > 0 { + let msg = str_from_raw_parts(ptr, len as usize); + let msg = msg.trim_ascii(); + let msg = msg.replace(['\r', '\n'], " "); + write!(f, ": {msg}")?; + Foundation::LocalFree(ptr as *mut _); + } - Ok(()) + Ok(()) + }, } } From 2c51581971c274254cb922a94668181e86b8dffb Mon Sep 17 00:00:00 2001 From: Emir SARI Date: Mon, 13 Oct 2025 17:47:17 +0300 Subject: [PATCH 08/27] Update Turkish translations (#655) --- i18n/edit.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/i18n/edit.toml b/i18n/edit.toml index 6c0fd10b6bc..e59fb4fdeeb 100644 --- a/i18n/edit.toml +++ b/i18n/edit.toml @@ -661,6 +661,7 @@ pl = "Zaznacz wszystko" pt_br = "Selecionar Tudo" ro = "Selectează tot" ru = "Выделить всё" +tr = "Tümünü Seç" uk = "Вибрати все" zh_hans = "全选" zh_hant = "全選" @@ -769,6 +770,7 @@ nl = "Ga naar Bestand" pt_br = "Ir para arquivo…" ro = "Mergi la fișier…" ru = "Перейти к файлу…" +tr = "Dosyaya Git…" uk = "Перейти до файлу…" zh_hans = "转到文件…" zh_hant = "跳至檔案…" @@ -1297,7 +1299,7 @@ ru = "Регистр" sr = "Razlikuj mala/velika slova" sv = "Versaler" tk = "Gabat gelýän ýagdaýy" -tr = "BÜYÜK/Küçük Harf" +tr = "BÜYÜK/küçük harf" uk = "Регістр" zh_hans = "区分大小写" zh_hant = "區分大小寫" @@ -1327,7 +1329,7 @@ ru = "Слово" sr = "Cela reč" sv = "Hela ord" tk = "Bütin söz" -tr = "Tam Sözcük" +tr = "Tam sözcük" uk = "Ціле слово" zh_hans = "全字匹配" zh_hant = "全字匹配" @@ -1357,7 +1359,7 @@ ru = "RegEx" sr = "Koristi regularni izraz" sv = "RegEx" tk = "Yzygiderli aňlatmalary ulan" -tr = "RegEx" +tr = "RegEx kullan" uk = "RegEx" zh_hans = "正则" zh_hant = "正則" From 95c1badd83443f88d05e1cd9a4e8dee770426529 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 14 Oct 2025 19:53:51 +0200 Subject: [PATCH 09/27] Fix Rust nightly builds (#668) `panic_immediate_abort` is being stablized as `panic = immediate-abort` (yay!). See: https://github.com/rust-lang/rust/issues/147286 Closes #657 --- .cargo/release-nightly.toml | 16 ++++++++++++++++ README.md | 4 +++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .cargo/release-nightly.toml diff --git a/.cargo/release-nightly.toml b/.cargo/release-nightly.toml new file mode 100644 index 00000000000..f294dca85b6 --- /dev/null +++ b/.cargo/release-nightly.toml @@ -0,0 +1,16 @@ +[profile.release] +panic = "immediate-abort" + +[target.'cfg(all(target_os = "windows", target_env = "msvc"))'] +rustflags = [ + "-Ctarget-feature=+crt-static", + "-Clink-args=/DEFAULTLIB:ucrt.lib", + "-Clink-args=/NODEFAULTLIB:vcruntime.lib", + "-Clink-args=/NODEFAULTLIB:msvcrt.lib", + "-Clink-args=/NODEFAULTLIB:libucrt.lib", +] + +[unstable] +panic-immediate-abort = true +build-std = ["std", "panic_abort"] +build-std-features = ["default", "optimize_for_size"] diff --git a/README.md b/README.md index 3cc9702711f..623c6dfd59c 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,9 @@ winget install Microsoft.Edit * Install the nightly toolchain: `rustup install nightly` * Alternatively, set the environment variable `RUSTC_BOOTSTRAP=1` * Clone the repository -* For a release build, run: `cargo build --config .cargo/release.toml --release` +* For a release build, run: + * Rust 1.90 or earlier: `cargo build --config .cargo/release.toml --release` + * otherwise: `cargo build --config .cargo/release-nightly.toml --release` ### Build Configuration From 87595e2461002e21a43b91e3e47bf552e1a1bc90 Mon Sep 17 00:00:00 2001 From: Miteigi Date: Sat, 18 Oct 2025 00:08:57 +0700 Subject: [PATCH 10/27] Add Vietnamese translations (#669) --- i18n/edit.toml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/i18n/edit.toml b/i18n/edit.toml index e59fb4fdeeb..1bcf4964b2c 100644 --- a/i18n/edit.toml +++ b/i18n/edit.toml @@ -60,6 +60,7 @@ sr = "U redu" sv = "OK" tk = "Bolýar" tr = "Tamam" +vi = "OK" zh_hans = "确定" zh_hant = "確定" @@ -90,6 +91,7 @@ sv = "Ja" tk = "Hawa" tr = "Evet" uk = "Так" +vi = "Có" zh_hans = "是" zh_hant = "是" @@ -120,6 +122,7 @@ sv = "Nej" tk = "Ýok" tr = "Hayır" uk = "Ні" +vi = "Không" zh_hans = "否" zh_hant = "否" @@ -150,6 +153,7 @@ sv = "Avbryt" tk = "Ýatyr" tr = "İptal" uk = "Скасувати" +vi = "Hủy" zh_hans = "取消" zh_hant = "取消" @@ -179,6 +183,7 @@ sr = "Uvek" sv = "Alltid" tr = "Her Zaman" uk = "Завжди" +vi = "Luôn" zh_hans = "总是" zh_hant = "總是" @@ -209,6 +214,7 @@ sv = "Fil" tk = "Faýl" tr = "Dosya" uk = "Файл" +vi = "Tệp" zh_hans = "文件" zh_hant = "檔案" @@ -238,6 +244,7 @@ sv = "Ny fil…" tk = "Täze faýl…" tr = "Yeni Dosya" uk = "Новий файл" +vi = "Tệp mới" zh_hans = "新建文件" zh_hant = "新增檔案" @@ -267,6 +274,7 @@ sv = "Öppna fil…" tk = "Faýly aç…" tr = "Dosya Aç…" uk = "Відкрити файл…" +vi = "Mở tệp…" zh_hans = "打开文件…" zh_hant = "開啟檔案…" @@ -296,6 +304,7 @@ sv = "Spara" tk = "Sakla" tr = "Kaydet" uk = "Зберегти" +vi = "Lưu" zh_hans = "保存" zh_hant = "儲存" @@ -325,6 +334,7 @@ sv = "Spara som…" tk = "Başga görnüşde sakla…" tr = "Farklı Kaydet…" uk = "Зберегти як…" +vi = "Lưu thành…" zh_hans = "另存为…" zh_hant = "另存新檔…" @@ -354,6 +364,7 @@ sv = "Stäng editor" tk = "Düzeldijini ýap" tr = "Dosyayı Kapat" uk = "Закрити файл" +vi = "Đóng tệp" zh_hans = "关闭文件" zh_hant = "關閉檔案" @@ -383,6 +394,7 @@ sv = "Avsluta" tk = "Çyk" tr = "Çıkış" uk = "Вихід" +vi = "Thoát" zh_hans = "退出" zh_hant = "退出" @@ -407,6 +419,7 @@ ru = "Перейти к строке:столбцу…" tk = "Setire / sütüne geç…" tr = "Satıra:Sütuna Git…" uk = "Перейти на рядок:стовпчик…" +vi = "Đi tới dòng:cột…" zh_hans = "转到行:列…" zh_hant = "跳至行:列…" @@ -437,6 +450,7 @@ sv = "Redigera" tk = "Düzelt" tr = "Düzen" uk = "Редагувати" +vi = "Chỉnh sửa" zh_hans = "编辑" zh_hant = "編輯" @@ -466,6 +480,7 @@ sv = "Ångra" tk = "Yza gaýtar" tr = "Geri Al" uk = "Скасувати" +vi = "Hoàn tác" zh_hans = "撤销" zh_hant = "復原" @@ -495,6 +510,7 @@ sv = "Gör om" tk = "Gaýtadan işle" tr = "Yinele" uk = "Повторити" +vi = "Làm lại" zh_hans = "重做" zh_hant = "重做" @@ -524,6 +540,7 @@ sv = "Klipp ut" tk = "Kes" tr = "Kes" uk = "Вирізати" +vi = "Cắt" zh_hans = "剪切" zh_hant = "剪下" @@ -553,6 +570,7 @@ sv = "Kopiera" tk = "Göçür" tr = "Kopyala" uk = "Копіювати" +vi = "Sao chép" zh_hans = "复制" zh_hant = "複製" @@ -582,6 +600,7 @@ sv = "Klistra in" tk = "Ýelme" tr = "Yapıştır" uk = "Вставити" +vi = "Dán" zh_hans = "粘贴" zh_hant = "貼上" @@ -611,6 +630,7 @@ sv = "Sök" tk = "Gözle" tr = "Bul" uk = "Знайти" +vi = "Tìm kiếm" zh_hans = "查找" zh_hant = "尋找" @@ -640,6 +660,7 @@ sv = "Ersätt" tk = "Çalyş" tr = "Değiştir" uk = "Замінити" +vi = "Thay thế" zh_hans = "替换" zh_hant = "取代" @@ -663,6 +684,7 @@ ro = "Selectează tot" ru = "Выделить всё" tr = "Tümünü Seç" uk = "Вибрати все" +vi = "Chọn tất cả" zh_hans = "全选" zh_hant = "全選" @@ -693,6 +715,7 @@ sv = "Visa" tk = "Gör" tr = "Görünüm" uk = "Вигляд" +vi = "Xem" zh_hans = "视图" zh_hant = "檢視" @@ -722,6 +745,7 @@ sv = "Fokusera statusfält" tk = "Ýagdaý setirine üns ber" tr = "Durum Çubuğuna Odaklan" uk = "Фокус на рядок стану" +vi = "Đặt tiêu điểm vào thanh trạng thái" zh_hans = "聚焦状态栏" zh_hant = "聚焦狀態列" @@ -751,6 +775,7 @@ sv = "Radbrytning" tk = "Söz birikdirme" tr = "Sözcük Kaydır" uk = "Переніс слів" +vi = "Ngắt dòng tự động" zh_hans = "自动换行" zh_hant = "自動換行" @@ -772,6 +797,7 @@ ro = "Mergi la fișier…" ru = "Перейти к файлу…" tr = "Dosyaya Git…" uk = "Перейти до файлу…" +vi = "Đi tới tệp…" zh_hans = "转到文件…" zh_hant = "跳至檔案…" @@ -802,6 +828,7 @@ sv = "Hjälp" tk = "Kömek" tr = "Yardım" uk = "Допомога" +vi = "Trợ giúp" zh_hans = "帮助" zh_hant = "幫助" @@ -831,6 +858,7 @@ sv = "Om" tk = "Programma barada" tr = "Hakkında" uk = "Про програму" +vi = "Giới thiệu" zh_hans = "关于" zh_hant = "關於" @@ -860,6 +888,7 @@ sv = "Osparade ändringar" tk = "Saklanmadyk üýtgeşmeler" tr = "Kaydedilmemiş Değişiklikler" uk = "Незбережені зміни" +vi = "Các thay đổi chưa được lưu" zh_hans = "未保存的更改" zh_hant = "未儲存的變更" @@ -889,6 +918,7 @@ sv = "Vill du spara de ändringar du gjort?" tk = "Eden üýtgeşmeleriňizi saklamak isleýärsiňizmi?" tr = "Yaptığınız değişiklikleri kaydetmek istiyor musunuz?" uk = "Зберегти внесені зміни?" +vi = "Bạn có muốn lưu các thay đổi bạn đã thực hiện không?" zh_hans = "您要保存所做的更改吗?" zh_hant = "您要保存所做的變更嗎?" @@ -918,6 +948,7 @@ sv = "Spara" tk = "Sakla" tr = "Kaydet" uk = "Зберегти" +vi = "Lưu" zh_hans = "保存" zh_hant = "儲存" @@ -946,6 +977,7 @@ sv = "Spara inte" tk = "Saklama" tr = "Kaydetme" uk = "Не зберігати" +vi = "Không lưu" zh_hans = "不保存" zh_hant = "不儲存" @@ -975,6 +1007,7 @@ sv = "Om" tk = "Programma barada" tr = "Hakkında" uk = "Про програму" +vi = "Giới thiệu" zh_hans = "关于" zh_hant = "關於" @@ -1004,6 +1037,7 @@ sv = "Version: " tk = "Wersiýa: " tr = "Sürüm: " uk = "Версія: " +vi = "Phiên bản: " zh_hans = "版本: " zh_hant = "版本: " @@ -1034,6 +1068,7 @@ sv = "Texten du kopierar delas med terminalens urklipp." tk = "Göçürilen ýazgy terminal paneline geçirilýär." tr = "Kopyaladığınız metin terminal panosuyla paylaşılır." uk = "Скопійований текст доступний в буфері обміну терміналу." +vi = "Văn bản bạn sao chép sẽ được chia sẻ với bảng tạm của terminal." zh_hans = "你复制的文本将共享到终端剪贴板。" zh_hant = "您複製的文字將會與終端機剪貼簿分享。" @@ -1064,6 +1099,7 @@ sv = "Du kopierade {size}, vilket kan ta lång tid att dela." tk = "Paýlaşmak üçin köp wagt alyp bilýän {size} göçürdiňiz." tr = "{size} kopyaladınız, paylaşmak uzun sürebilir." uk = "Скопійовано {size}, передача може зайняти дещо часу." +vi = "Bạn đã sao chép {size}, việc chia sẻ có thể mất nhiều thời gian." zh_hans = "你复制了 {size},共享可能需要较长时间。" zh_hant = "您已複製 {size},共享可能需要較長時間。" @@ -1094,6 +1130,7 @@ sv = "Vill du skicka det ändå?" tk = "Her niçigem bolsa ibermek isleýärsiňizmi?" tr = "Yine de göndermek istiyor musunuz?" uk = "Все одно надіслати?" +vi = "Bạn vẫn muốn gửi chứ?" zh_hans = "仍要发送吗?" zh_hant = "仍要傳送嗎?" @@ -1124,6 +1161,7 @@ sv = "Texten du kopierade är för stor för att delas." tk = "Göçürilen ýazgy paýlaşmak üçin gaty uly." tr = "Kopyaladığınız metin paylaşmak için çok büyük." uk = "Скопійований текст завеликий для передачі." +vi = "Văn bản bạn sao chép quá lớn, không thể chia sẻ." zh_hans = "你复制的文本过大,无法共享。" zh_hant = "您複製的文字過大,無法分享。" @@ -1153,6 +1191,7 @@ sv = "Varning" tk = "Duýduryş" tr = "Uyarı" uk = "Попередження" +vi = "Cảnh báo" zh_hans = "警告" zh_hant = "警告" @@ -1182,6 +1221,7 @@ sv = "Fel" tk = "Ýalňyş" tr = "Hata" uk = "Помилка" +vi = "Lỗi" zh_hans = "错误" zh_hant = "錯誤" @@ -1211,6 +1251,7 @@ sv = "Denna åtgärd kräver ICU-biblioteket" tk = "Bu amal ICU kitaphanasyny talap edýär" tr = "Bu işlem ICU kütüphanesini gerektirir" uk = "Ця операція потребує наявності бібліотеки ICU" +vi = "Thao tác này cần thư viện ICU" zh_hans = "此操作需要 ICU 库" zh_hant = "此操作需要 ICU 庫" @@ -1241,6 +1282,7 @@ sv = "Sök:" tk = "Gözle:" tr = "Bul:" uk = "Знайти:" +vi = "Tìm:" zh_hans = "查找:" zh_hant = "尋找:" @@ -1271,6 +1313,7 @@ sv = "Ersätt:" tk = "Çalyş:" tr = "Değiştir:" uk = "Заміна:" +vi = "Thay thế:" zh_hans = "替换:" zh_hant = "替換:" @@ -1301,6 +1344,7 @@ sv = "Versaler" tk = "Gabat gelýän ýagdaýy" tr = "BÜYÜK/küçük harf" uk = "Регістр" +vi = "Phân biệt chữ hoa/thường" zh_hans = "区分大小写" zh_hant = "區分大小寫" @@ -1331,6 +1375,7 @@ sv = "Hela ord" tk = "Bütin söz" tr = "Tam sözcük" uk = "Ціле слово" +vi = "Khớp toàn bộ từ" zh_hans = "全字匹配" zh_hant = "全字匹配" @@ -1361,6 +1406,7 @@ sv = "RegEx" tk = "Yzygiderli aňlatmalary ulan" tr = "RegEx kullan" uk = "RegEx" +vi = "Sử dụng biểu thức chính quy" zh_hans = "正则" zh_hant = "正則" @@ -1391,6 +1437,7 @@ sv = "Ersätt alla" tk = "Hemmesini çalyş" tr = "Tümünü Değiştir" uk = "Замінити все" +vi = "Thay thế tất cả" zh_hans = "全部替换" zh_hant = "全部取代" @@ -1421,6 +1468,7 @@ sv = "Stäng" tk = "Ýap" tr = "Kapat" uk = "Закрити" +vi = "Đóng" zh_hans = "关闭" zh_hant = "關閉" @@ -1450,6 +1498,7 @@ sv = "Öppna med enkodning" tk = "Kodlamak bilen gaýtadan aç" tr = "Kodlamayla Yeniden Aç…" uk = "Відкрити наново з кодуванням…" +vi = "Mở lại với mã hóa…" zh_hans = "使用编码重新打开…" zh_hant = "使用編碼重新打開…" @@ -1479,6 +1528,7 @@ sv = "Konvertera till enkodning" tk = "Koda öwür" tr = "Kodlamaya Dönüştür…" uk = "Перетворити в кодування…" +vi = "Chuyển đổi sang mã hóa…" zh_hans = "转换为编码…" zh_hant = "轉換為編碼…" @@ -1508,6 +1558,7 @@ sv = "Flikar" tk = "Tabler" tr = "Sekme" uk = "Табуляція" +vi = "Tab" zh_hans = "制表符" zh_hant = "製表符" @@ -1537,6 +1588,7 @@ sv = "Mellanslag" tk = "Boşluklar" tr = "Boşluk" uk = "Пробіли" +vi = "Dấu cách" zh_hans = "空格" zh_hant = "空格" @@ -1566,6 +1618,7 @@ sv = "Mapp:" tk = "Bukja:" tr = "Klasör:" uk = "Папка:" +vi = "Thư mục:" zh_hans = "文件夹:" zh_hant = "資料夾:" @@ -1595,6 +1648,7 @@ sv = "Filnamn:" tk = "Faýl ady:" tr = "Dosya adı:" uk = "Ім'я файлу:" +vi = "Tên tệp:" zh_hans = "文件名:" zh_hant = "檔案名稱:" @@ -1624,6 +1678,7 @@ sv = "Bekräfta Spara som" tk = "Saklamagy tassykla…»" tr = "Farklı Kaydetmeyi Onayla" uk = "Підтвердьте «Зберегти як…»" +vi = "Xác nhận Lưu thành" zh_hans = "确认另存为" zh_hant = "確認另存新檔" @@ -1653,5 +1708,6 @@ sv = "Filen finns redan. Vill du skriva över den?" tk = "Faýl eýýäm bar. Ony gaýtadan ýazmak isleýärsiňizmi?" tr = "Dosya zaten var. Üzerine yazmak istiyor musunuz?" uk = "Файл вже існує. Перезаписати?" +vi = "Tệp đã tồn tại. Bạn có muốn ghi đè không?" zh_hans = "文件已存在。要覆盖它吗?" zh_hant = "檔案已存在。要覆蓋它嗎?" From 71cbdd0096625e46ee319d8195f562fe87d9d255 Mon Sep 17 00:00:00 2001 From: Tiago Mouta <79537410+OMouta@users.noreply.github.com> Date: Fri, 31 Oct 2025 20:09:55 +0000 Subject: [PATCH 11/27] Add Portuguese (pt-PT) translations (#688) --- i18n/edit.toml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/i18n/edit.toml b/i18n/edit.toml index 1bcf4964b2c..4492200d173 100644 --- a/i18n/edit.toml +++ b/i18n/edit.toml @@ -54,6 +54,7 @@ ja = "OK" ko = "확인" nl = "OK" pt_br = "OK" +pt_pt = "OK" ro = "OK" ru = "ОК" sr = "U redu" @@ -84,6 +85,7 @@ ko = "예" nl = "Ja" pl = "Tak" pt_br = "Sim" +pt_pt = "Sim" ro = "Da" ru = "Да" sr = "Da" @@ -115,6 +117,7 @@ ko = "아니오" nl = "Nee" pl = "Nie" pt_br = "Não" +pt_pt = "Não" ro = "Nu" ru = "Нет" sr = "Ne" @@ -146,6 +149,7 @@ ko = "취소" nl = "Annuleer" pl = "Anuluj" pt_br = "Cancelar" +pt_pt = "Cancelar" ro = "Anulare" ru = "Отмена" sr = "Otkaži" @@ -177,6 +181,7 @@ ko = "항상" nl = "Altijd" pl = "Zawsze" pt_br = "Sempre" +pt_pt = "Sempre" ro = "Întotdeauna" ru = "Всегда" sr = "Uvek" @@ -207,6 +212,7 @@ ko = "파일" nl = "Bestand" pl = "Plik" pt_br = "Arquivo" +pt_pt = "Ficheiro" ro = "Fișier" ru = "Файл" sr = "Fajl" @@ -237,6 +243,7 @@ ko = "새 파일" nl = "Nieuw Bestand" pl = "Nowy plik" pt_br = "Novo Arquivo" +pt_pt = "Novo ficheiro" ro = "Fișier nou" ru = "Новый файл" sr = "Novi fajl…" @@ -267,6 +274,7 @@ ko = "파일 열기…" nl = "Open bestand…" pl = "Otwórz plik…" pt_br = "Abrir o Arquivo…" +pt_pt = "Abrir ficheiro…" ro = "Deschide fișier…" ru = "Открыть файл…" sr = "Otvori fajl…" @@ -297,6 +305,7 @@ ko = "저장" nl = "Opslaan" pl = "Zapisz" pt_br = "Salvar" +pt_pt = "Guardar" ro = "Salvează" ru = "Сохранить" sr = "Sačuvaj" @@ -327,6 +336,7 @@ ko = "다른 이름으로 저장…" nl = "Opslaan Als…" pl = "Zapisz jako…" pt_br = "Salvar Como…" +pt_pt = "Guardar como…" ro = "Salvează ca…" ru = "Сохранить как…" sr = "Sačuvaj kao…" @@ -357,6 +367,7 @@ ko = "파일 닫기" nl = "Sluit Bestand" pl = "Zamknij plik" pt_br = "Fechar arquivo" +pt_pt = "Fechar ficheiro" ro = "Închide fișier" ru = "Закрыть файл" sr = "Zatvori uređivač" @@ -387,6 +398,7 @@ ko = "종료" nl = "Afsluiten" pl = "Zakończ" pt_br = "Sair" +pt_pt = "Sair" ro = "Ieșire" ru = "Выход" sr = "Izlaz" @@ -414,6 +426,7 @@ ko = "행:열로 이동…" nl = "Ga naar Regel:Kolom…" pl = "Idź do wiersza:kolumny…" pt_br = "Ir para a Linha:Coluna…" +pt_pt = "Ir para a linha:Coluna…" ro = "Mergi la linie:coloană…" ru = "Перейти к строке:столбцу…" tk = "Setire / sütüne geç…" @@ -443,6 +456,7 @@ ko = "편집" nl = "Bewerken" pl = "Edycja" pt_br = "Editar" +pt_pt = "Editar" ro = "Editare" ru = "Правка" sr = "Uredi" @@ -473,6 +487,7 @@ ko = "실행 취소" nl = "Ongedaan Maken" pl = "Cofnij" pt_br = "Desfazer" +pt_pt = "Anular" ro = "Anulează" ru = "Отменить" sr = "Poništi" @@ -503,6 +518,7 @@ ko = "다시 실행" nl = "Opnieuw Uitvoeren" pl = "Ponów" pt_br = "Refazer" +pt_pt = "Refazer" ro = "Refă" ru = "Повторить" sr = "Vrati" @@ -533,6 +549,7 @@ ko = "잘라내기" nl = "Knippen" pl = "Wytnij" pt_br = "Recortar" +pt_pt = "Cortar" ro = "Decupează" ru = "Вырезать" sr = "Iseci" @@ -563,6 +580,7 @@ ko = "복사" nl = "Kopiëren" pl = "Kopiuj" pt_br = "Copiar" +pt_pt = "Copiar" ro = "Copiază" ru = "Копировать" sr = "Kopiraj" @@ -593,6 +611,7 @@ ko = "붙여넣기" nl = "Plakken" pl = "Wklej" pt_br = "Colar" +pt_pt = "Colar" ro = "Lipește" ru = "Вставить" sr = "Nalepi" @@ -623,6 +642,7 @@ ko = "찾기" nl = "Zoeken" pl = "Znajdź" pt_br = "Localizar" +pt_pt = "Procurar" ro = "Găsește" ru = "Найти" sr = "Pronađi" @@ -653,6 +673,7 @@ ko = "바꾸기" nl = "Vervangen" pl = "Zamień" pt_br = "Substituir" +pt_pt = "Substituir" ro = "Înlocuiește" ru = "Заменить" sr = "Zameni" @@ -680,6 +701,7 @@ ko = "모두 선택" nl = "Alles Selecteren" pl = "Zaznacz wszystko" pt_br = "Selecionar Tudo" +pt_pt = "Selecionar tudo" ro = "Selectează tot" ru = "Выделить всё" tr = "Tümünü Seç" @@ -708,6 +730,7 @@ ko = "보기" nl = "Weergave" pl = "Widok" pt_br = "Exibir" +pt_pt = "Ver" ro = "Vizualizare" ru = "Вид" sr = "Prikaz" @@ -738,6 +761,7 @@ ko = "상태 표시줄로 포커스 이동" nl = "Statusbalk Focussen" pl = "Przejdź na dolny pasek" pt_br = "Focar na Barra de Status" +pt_pt = "Focar na barra de estado" ro = "Focalizare pe bara de stare" ru = "Фокус на строку состояния" sr = "Fokusiraj statusnu traku" @@ -768,6 +792,7 @@ ko = "자동 줄 바꿈" nl = "Tekstomloop" pl = "Zawijanie wierszy" pt_br = "Quebra De Texto" +pt_pt = "Quebra de linha" ro = "Încadrare text" ru = "Перенос слов" sr = "Prelom reči" @@ -793,6 +818,7 @@ ja = "ファイルへ移動…" ko = "파일로 이동…" nl = "Ga naar Bestand" pt_br = "Ir para arquivo…" +pt_pt = "Ir para ficheiro…" ro = "Mergi la fișier…" ru = "Перейти к файлу…" tr = "Dosyaya Git…" @@ -821,6 +847,7 @@ ko = "도움말" nl = "Help" pl = "Pomoc" pt_br = "Ajuda" +pt_pt = "Ajuda" ro = "Ajutor" ru = "Помощь" sr = "Pomoć" @@ -851,6 +878,7 @@ ko = "정보" nl = "Over" pl = "O programie" pt_br = "Sobre" +pt_pt = "Sobre" ro = "Despre" ru = "О программе" sr = "O programu" @@ -881,6 +909,7 @@ ko = "저장되지 않은 변경 사항" nl = "Onopgeslagen Wijzigingen" pl = "Masz niezapisane zmiany" pt_br = "Alterações não Salvas" +pt_pt = "Alterações não guardadas" ro = "Modificări nesalvate" ru = "Несохраненные изменения" sr = "Nesačuvane izmene" @@ -911,6 +940,7 @@ ko = "변경한 내용을 저장하시겠습니까?" nl = "Wilt u de gemaakte wijzigingen opslaan?" pl = "Czy chcesz zapisać wprowadzone modyfikacje?" pt_br = "Deseja salvar as alterações feitas?" +pt_pt = "Quer guardar as alterações efetuadas?" ro = "Doriți să salvați modificările efectuate?" ru = "Вы хотите сохранить внесённые изменения?" sr = "Želite li da sačuvate izmene?" @@ -941,6 +971,7 @@ ko = "저장" nl = "Opslaan" pl = "Zapisz" pt_br = "Salvar" +pt_pt = "Guardar" ro = "Salvează" ru = "Сохранить" sr = "Sačuvaj" @@ -970,6 +1001,7 @@ ko = "저장 안 함" nl = "Niet Opslaan" pl = "Nie zapisuj" pt_br = "Não Salvar" +pt_pt = "Não guardar" ro = "Nu salva" ru = "Не сохранять" sr = "Nemoj sačuvati" @@ -1000,6 +1032,7 @@ ko = "정보" nl = "Over" pl = "O programie" pt_br = "Sobre" +pt_pt = "Sobre" ro = "Despre" ru = "О программе" sr = "O programu" @@ -1030,6 +1063,7 @@ ko = "버전: " nl = "Versie: " pl = "Wersja: " pt_br = "Versão: " +pt_pt = "Versão: " ro = "Versiune: " ru = "Версия: " sr = "Verzija: " @@ -1061,6 +1095,7 @@ ko = "복사한 텍스트가 터미널 클립보드와 공유됩니다." nl = "Tekst die u kopieert wordt gedeeld met de terminal." pl = "Skopiowany tekst jest przekazywany do schowka konsoli." pt_br = "O texto copiado é compartilhado com a área de transferência do terminal." +pt_pt = "O texto que copia é partilhado com a área de transferência do terminal." ro = "Textul copiat este partajat cu clipboard-ul terminalului." ru = "Скопированный текст передаётся в буфер обмена терминала." sr = "Tekst koji kopirate se deli sa terminalom." @@ -1092,6 +1127,7 @@ ko = "{size}를 복사했습니다. 공유하는 데 시간이 오래 걸릴 수 nl = "U heeft {size} gekopieerd, wat misschien lang kan duren om te delen." pl = "Skopiowano {size}, co może zająć dużo czasu." pt_br = "Você copiou {size}, o que pode demorar para compartilhar." +pt_pt = "Copiou {size}, o que pode demorar a ser partilhado." ro = "Ai copiat {size}, ceea ce poate dura mult timp pentru a fi partajat." ru = "Вы скопировали {size}; передача может занять много времени." sr = "Kopirali ste {size}, deljenje može potrajati." @@ -1123,6 +1159,7 @@ ko = "그래도 전송하시겠습니까?" nl = "Wilt u het toch verzenden?" pl = "Czy nadal chcesz to przesłać?" pt_br = "Deseja enviar mesmo assim?" +pt_pt = "Quer enviá-lo mesmo assim?" ro = "Vrei să-l trimiți oricum?" ru = "Отправить в любом случае?" sr = "Želite li da ga ipak pošaljete?" @@ -1154,6 +1191,7 @@ ko = "복사한 텍스트가 너무 커서 공유할 수 없습니다." nl = "De tekst die u kopieerde is te lang om te delen." pl = "Skopiowany tekst jest zbyt duży, żeby go przesłać." pt_br = "O texto copiado é grande demais para ser compartilhado." +pt_pt = "O texto que copiou é demasiado grande para ser partilhado." ro = "Textul copiat este prea mare pentru a fi partajat." ru = "Скопированный текст слишком велик для передачи." sr = "Tekst koji ste kopirali je prevelik za deljenje." @@ -1184,6 +1222,7 @@ ko = "경고" nl = "Waarschuwing" pl = "Ostrzeżenie" pt_br = "Aviso" +pt_pt = "Aviso" ro = "Avertisment" ru = "Предупреждение" sr = "Upozorenje" @@ -1214,6 +1253,7 @@ ko = "오류" nl = "Fout" pl = "Błąd" pt_br = "Erro" +pt_pt = "Erro" ro = "Eroare" ru = "Ошибка" sr = "Greška" @@ -1244,6 +1284,7 @@ ko = "이 작업에는 ICU 라이브러리가 필요합니다" nl = "Deze bewerking vereist de ICU-bibliotheek" pl = "Ta operacja wymaga biblioteki ICU" pt_br = "Esta operação requer a biblioteca ICU" +pt_pt = "Esta operação requer a biblioteca ICU" ro = "Această operațiune necesită biblioteca ICU" ru = "Эта операция требует наличия библиотеки ICU" sr = "Ova operacija zahteva ICU biblioteku" @@ -1275,6 +1316,7 @@ ko = "찾기:" nl = "Zoeken:" pl = "Znajdź:" pt_br = "Localizar:" +pt_pt = "Procurar:" ro = "Găsește:" ru = "Найти:" sr = "Pronađi:" @@ -1306,6 +1348,7 @@ ko = "바꾸기:" nl = "Vervang:" pl = "Zamień:" pt_br = "Substituir:" +pt_pt = "Substituir:" ro = "Înlocuiește:" ru = "Замена:" sr = "Zameni:" @@ -1337,6 +1380,7 @@ ko = "대소문자" nl = "Hoofdlettergevoelig" pl = "Wielkość liter" pt_br = "Maiús/Minús" +pt_pt = "Diferenciar maiúsculas/minúsculas" ro = "Diferențiere Majuscule/minuscule" ru = "Регистр" sr = "Razlikuj mala/velika slova" @@ -1368,6 +1412,7 @@ ko = "전체 단어" nl = "Volledig Woord" pl = "Całe słowa" pt_br = "Palavra" +pt_pt = "Palavra inteira" ro = "Cuvânt întreg" ru = "Слово" sr = "Cela reč" @@ -1399,6 +1444,7 @@ ko = "정규식" nl = "RegEx" pl = "RegEx" pt_br = "RegEx" +pt_pt = "Expressão regular" ro = "RegEx" ru = "RegEx" sr = "Koristi regularni izraz" @@ -1430,6 +1476,7 @@ ko = "모두 바꾸기" nl = "Vervang Alle" pl = "Zamień wszystko" pt_br = "Substituir Tudo" +pt_pt = "Substituir tudo" ro = "Înlocuiește tot" ru = "Заменить все" sr = "Zameni sve" @@ -1461,6 +1508,7 @@ ko = "닫기" nl = "Sluiten" pl = "Zamknij" pt_br = "Fechar" +pt_pt = "Fechar" ro = "Închide" ru = "Закрыть" sr = "Zatvori" @@ -1491,6 +1539,7 @@ ko = "인코딩으로 다시 열기…" nl = "Heropen met codering…" pl = "Otwórz ponownie z kodowaniem…" pt_br = "Reabrir com a codificação…" +pt_pt = "Reabrir com codificação…" ro = "Redeschide cu codificare…" ru = "Открыть снова с кодировкой…" sr = "Ponovo otvori sa kodiranjem" @@ -1521,6 +1570,7 @@ ko = "인코딩으로 변환…" nl = "Zet om naar codering…" pl = "Konwertuj na kodowanie…" pt_br = "Converter para a codificação…" +pt_pt = "Converter para codificação…" ro = "Convertire în codificare…" ru = "Преобразовать в кодировку…" sr = "Pretvori u kodiranje" @@ -1551,6 +1601,7 @@ ko = "탭" nl = "Tabs" pl = "Tabulatory" pt_br = "Tabs" +pt_pt = "Tabulações" ro = "Tabulări" ru = "Табы" sr = "Tabulatori" @@ -1581,6 +1632,7 @@ ko = "공백" nl = "Spaties" pl = "Spacje" pt_br = "Espaços" +pt_pt = "Espaços" ro = "Spații" ru = "Пробелы" sr = "Razmaci" @@ -1611,6 +1663,7 @@ ko = "폴더:" nl = "Folder:" pl = "Lokalizacja:" pt_br = "Pasta:" +pt_pt = "Pasta:" ro = "Dosar:" ru = "Папка:" sr = "Fascikla:" @@ -1641,6 +1694,7 @@ ko = "파일 이름:" nl = "Bestandnaam:" pl = "Nazwa pliku:" pt_br = "Nome do Arquivo:" +pt_pt = "Nome do ficheiro:" ro = "Nume fișier:" ru = "Имя файла:" sr = "Ime fajla:" @@ -1671,6 +1725,7 @@ ko = "다른 이름으로 저장 확인" nl = "Bevestig Opslaan Als" pl = "Potwierdź zapisanie jako" pt_br = "Confirmar Salvar Como" +pt_pt = "Confirmar Guardar como" ro = "Confirmă Salvează ca" ru = "Подтвердите «Сохранить как…»" sr = "Potvrdi Sačuvaj kao" @@ -1701,6 +1756,7 @@ ko = "파일이 이미 존재합니다. 덮어쓰시겠습니까?" nl = "Bestand bestaat al. Wilt u het overschrijven?" pl = "Plik już istnieje. Czy chcesz go nadpisać?" pt_br = "O arquivo já existe. Deseja substituí-lo?" +pt_pt = "O ficheiro já existe. Quer sobrescrevê-lo?" ro = "Fișierul există deja. Doriți să-l suprascrieți?" ru = "Файл уже существует. Перезаписать?" sr = "Fajl već postoji. Želite li da ga prepišete?" From abbff84627f99ba25c8a477988185969536c1940 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 1 Dec 2025 21:51:55 +0100 Subject: [PATCH 12/27] Move arena & helpers into their own crate (#694) This will allow us to use the `Arena` in `build.rs`. This changeset also contains a version bump of all dependencies. --- Cargo.lock | 404 +++++++++++------- Cargo.toml | 60 +-- crates/edit/Cargo.toml | 52 +++ {benches => crates/edit/benches}/lib.rs | 5 +- {build => crates/edit/build}/helpers.rs | 3 + {build => crates/edit/build}/i18n.rs | 3 + {build => crates/edit/build}/main.rs | 21 +- {src => crates/edit/src}/apperr.rs | 9 + {src => crates/edit/src}/base64.rs | 5 +- .../edit/src}/bin/edit/documents.rs | 0 .../edit/src}/bin/edit/draw_editor.rs | 0 .../edit/src}/bin/edit/draw_filepicker.rs | 2 +- .../edit/src}/bin/edit/draw_menubar.rs | 2 +- .../edit/src}/bin/edit/draw_statusbar.rs | 5 +- .../edit/src}/bin/edit/edit.exe.manifest | 0 .../edit/src}/bin/edit/localization.rs | 2 +- {src => crates/edit/src}/bin/edit/main.rs | 5 +- {src => crates/edit/src}/bin/edit/state.rs | 0 {src => crates/edit/src}/buffer/gap_buffer.rs | 10 +- {src => crates/edit/src}/buffer/line_cache.rs | 0 {src => crates/edit/src}/buffer/mod.rs | 2 +- {src => crates/edit/src}/buffer/navigation.rs | 0 {src => crates/edit/src}/cell.rs | 0 {src => crates/edit/src}/clipboard.rs | 0 {src => crates/edit/src}/document.rs | 3 +- {src => crates/edit/src}/framebuffer.rs | 9 +- {src => crates/edit/src}/fuzzy.rs | 3 +- {src => crates/edit/src}/hash.rs | 0 {src => crates/edit/src}/helpers.rs | 0 {src => crates/edit/src}/icu.rs | 6 +- {src => crates/edit/src}/input.rs | 0 {src => crates/edit/src}/lib.rs | 3 - {src => crates/edit/src}/oklab.rs | 0 {src => crates/edit/src}/path.rs | 0 {src => crates/edit/src}/simd/lines_bwd.rs | 0 {src => crates/edit/src}/simd/lines_fwd.rs | 0 {src => crates/edit/src}/simd/memchr2.rs | 7 +- {src => crates/edit/src}/simd/memset.rs | 0 {src => crates/edit/src}/simd/mod.rs | 0 {src => crates/edit/src}/sys/mod.rs | 0 {src => crates/edit/src}/sys/unix.rs | 80 +--- {src => crates/edit/src}/sys/windows.rs | 80 +--- {src => crates/edit/src}/tui.rs | 8 +- .../edit/src}/unicode/measurement.rs | 0 {src => crates/edit/src}/unicode/mod.rs | 0 {src => crates/edit/src}/unicode/tables.rs | 0 {src => crates/edit/src}/unicode/utf8.rs | 0 {src => crates/edit/src}/vt.rs | 0 crates/stdext/Cargo.toml | 11 + {src => crates/stdext/src}/arena/debug.rs | 9 +- {src => crates/stdext/src}/arena/mod.rs | 0 {src => crates/stdext/src}/arena/release.rs | 8 +- {src => crates/stdext/src}/arena/scratch.rs | 6 +- {src => crates/stdext/src}/arena/string.rs | 11 +- crates/stdext/src/helpers.rs | 171 ++++++++ crates/stdext/src/lib.rs | 12 + crates/stdext/src/sys/mod.rs | 17 + crates/stdext/src/sys/unix.rs | 73 ++++ crates/stdext/src/sys/windows.rs | 63 +++ crates/unicode-gen/Cargo.toml | 16 + .../unicode-gen}/README.md | 0 .../unicode-gen}/src/main.rs | 0 .../unicode-gen}/src/rules.rs | 0 tools/grapheme-table-gen/Cargo.lock | 377 ---------------- tools/grapheme-table-gen/Cargo.toml | 12 - 65 files changed, 799 insertions(+), 776 deletions(-) create mode 100644 crates/edit/Cargo.toml rename {benches => crates/edit/benches}/lib.rs (98%) rename {build => crates/edit/build}/helpers.rs (80%) rename {build => crates/edit/build}/i18n.rs (98%) rename {build => crates/edit/build}/main.rs (89%) rename {src => crates/edit/src}/apperr.rs (72%) rename {src => crates/edit/src}/base64.rs (98%) rename {src => crates/edit/src}/bin/edit/documents.rs (100%) rename {src => crates/edit/src}/bin/edit/draw_editor.rs (100%) rename {src => crates/edit/src}/bin/edit/draw_filepicker.rs (99%) rename {src => crates/edit/src}/bin/edit/draw_menubar.rs (99%) rename {src => crates/edit/src}/bin/edit/draw_statusbar.rs (99%) rename {src => crates/edit/src}/bin/edit/edit.exe.manifest (100%) rename {src => crates/edit/src}/bin/edit/localization.rs (95%) rename {src => crates/edit/src}/bin/edit/main.rs (99%) rename {src => crates/edit/src}/bin/edit/state.rs (100%) rename {src => crates/edit/src}/buffer/gap_buffer.rs (97%) rename {src => crates/edit/src}/buffer/line_cache.rs (100%) rename {src => crates/edit/src}/buffer/mod.rs (99%) rename {src => crates/edit/src}/buffer/navigation.rs (100%) rename {src => crates/edit/src}/cell.rs (100%) rename {src => crates/edit/src}/clipboard.rs (100%) rename {src => crates/edit/src}/document.rs (98%) rename {src => crates/edit/src}/framebuffer.rs (99%) rename {src => crates/edit/src}/fuzzy.rs (99%) rename {src => crates/edit/src}/hash.rs (100%) rename {src => crates/edit/src}/helpers.rs (100%) rename {src => crates/edit/src}/icu.rs (99%) rename {src => crates/edit/src}/input.rs (100%) rename {src => crates/edit/src}/lib.rs (96%) rename {src => crates/edit/src}/oklab.rs (100%) rename {src => crates/edit/src}/path.rs (100%) rename {src => crates/edit/src}/simd/lines_bwd.rs (100%) rename {src => crates/edit/src}/simd/lines_fwd.rs (100%) rename {src => crates/edit/src}/simd/memchr2.rs (98%) rename {src => crates/edit/src}/simd/memset.rs (100%) rename {src => crates/edit/src}/simd/mod.rs (100%) rename {src => crates/edit/src}/sys/mod.rs (100%) rename {src => crates/edit/src}/sys/unix.rs (88%) rename {src => crates/edit/src}/sys/windows.rs (90%) rename {src => crates/edit/src}/tui.rs (99%) rename {src => crates/edit/src}/unicode/measurement.rs (100%) rename {src => crates/edit/src}/unicode/mod.rs (100%) rename {src => crates/edit/src}/unicode/tables.rs (100%) rename {src => crates/edit/src}/unicode/utf8.rs (100%) rename {src => crates/edit/src}/vt.rs (100%) create mode 100644 crates/stdext/Cargo.toml rename {src => crates/stdext/src}/arena/debug.rs (95%) rename {src => crates/stdext/src}/arena/mod.rs (100%) rename {src => crates/stdext/src}/arena/release.rs (98%) rename {src => crates/stdext/src}/arena/scratch.rs (97%) rename {src => crates/stdext/src}/arena/string.rs (96%) create mode 100644 crates/stdext/src/helpers.rs create mode 100644 crates/stdext/src/lib.rs create mode 100644 crates/stdext/src/sys/mod.rs create mode 100644 crates/stdext/src/sys/unix.rs create mode 100644 crates/stdext/src/sys/windows.rs create mode 100644 crates/unicode-gen/Cargo.toml rename {tools/grapheme-table-gen => crates/unicode-gen}/README.md (100%) rename {tools/grapheme-table-gen => crates/unicode-gen}/src/main.rs (100%) rename {tools/grapheme-table-gen => crates/unicode-gen}/src/rules.rs (100%) delete mode 100644 tools/grapheme-table-gen/Cargo.lock delete mode 100644 tools/grapheme-table-gen/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index b27fd66dea4..da1473dc64e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,22 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -19,21 +28,21 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] -name = "autocfg" -version = "1.5.0" +name = "anyhow" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] -name = "bitflags" -version = "2.9.1" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bumpalo" @@ -49,10 +58,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.30" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -60,9 +70,22 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] [[package]] name = "ciborium" @@ -93,18 +116,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstyle", "clap_lex", @@ -112,9 +135,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "criterion" @@ -188,6 +217,7 @@ dependencies = [ "libc", "serde", "serde_json", + "stdext", "toml-span", "windows-sys", "winresource", @@ -200,26 +230,66 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasip2", ] [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", ] [[package]] @@ -239,9 +309,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ "getrandom", "libc", @@ -249,9 +319,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -259,21 +329,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "num-traits" @@ -296,6 +366,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pkg-config" version = "0.3.32" @@ -332,18 +408,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -356,9 +432,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -366,9 +442,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -376,9 +452,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -388,9 +464,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -399,15 +475,24 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -426,18 +511,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -446,14 +541,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -468,11 +564,18 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "stdext" +version = "0.0.0" +dependencies = [ + "libc", +] + [[package]] name = "syn" -version = "2.0.104" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -491,18 +594,30 @@ dependencies = [ [[package]] name = "toml-span" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d36acfca70d66f9b5f9c4786fec60096c3594169bf77b8d4207174dc862e6a4" +checksum = "5c6532e5b62b652073bff0e2050ef57e4697a853be118d6c57c32b59fffdeaab" dependencies = [ "smallvec", ] +[[package]] +name = "unicode-gen" +version = "0.0.0" +dependencies = [ + "anyhow", + "chrono", + "indoc", + "pico-args", + "rayon", + "roxmltree", +] + [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "version_check" @@ -521,45 +636,32 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -567,31 +669,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -599,102 +701,114 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "windows_i686_gnu" -version = "0.52.6" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] [[package]] -name = "windows_i686_msvc" -version = "0.52.6" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" +name = "winresource" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "ca6d1a5aac12a0cccc4dea310c464cae005a78eadfa72fc3bc45fe696ebfbb9d" +dependencies = [ + "version_check", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] -name = "winresource" -version = "0.1.23" +name = "zerocopy" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcacf11b6f48dd21b9ba002f991bdd5de29b2da8cc2800412f4b80f677e4957" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "version_check", + "zerocopy-derive", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "zerocopy-derive" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ - "bitflags", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -717,9 +831,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 792aa41d4fa..fb53ab464f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,13 @@ -[package] -name = "edit" -version = "1.2.1" +[workspace] +default-members = ["crates/edit"] +members = ["crates/*"] +resolver = "2" + +[workspace.package] edition = "2024" -rust-version = "1.87" -readme = "README.md" -repository = "https://github.com/microsoft/edit" -homepage = "https://github.com/microsoft/edit" license = "MIT" -categories = ["text-editors"] -build = "build/main.rs" - -[[bench]] -name = "lib" -harness = false - -[features] -debug-latency = [] +repository = "https://github.com/microsoft/edit" +rust-version = "1.88" # We use `opt-level = "s"` as it significantly reduces binary size. # We could then use the `#[optimize(speed)]` attribute for spot optimizations. @@ -34,35 +26,7 @@ incremental = true # Improves re-compile times codegen-units = 16 # Make compiling criterion faster (16 is the default, but profile.release sets it to 1) lto = "thin" # Similarly, speed up linking by a ton -[dependencies] - -[target.'cfg(unix)'.dependencies] -libc = "0.2" - -[build-dependencies] -# The default toml crate bundles its dependencies with bad compile times. Thanks. -# Thankfully toml-span exists. FWIW the alternative is yaml-rust (without the 2 suffix). -toml-span = { version = "0.5", default-features = false } - -[target.'cfg(windows)'.build-dependencies] -winresource = { version = "0.1.22", default-features = false } - -[target.'cfg(windows)'.dependencies.windows-sys] -version = "0.59" -features = [ - "Win32_Globalization", - "Win32_Security", - "Win32_Storage_FileSystem", - "Win32_System_Console", - "Win32_System_Diagnostics_Debug", - "Win32_System_IO", - "Win32_System_LibraryLoader", - "Win32_System_Memory", - "Win32_System_Threading", -] - -[dev-dependencies] -criterion = { version = "0.7", features = ["html_reports"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } -zstd = { version = "0.13", default-features = false } +[workspace.dependencies] +edit = { path = "./crates/edit" } +stdext = { path = "./crates/stdext" } +unicode-gen = { path = "./crates/unicode-gen" } diff --git a/crates/edit/Cargo.toml b/crates/edit/Cargo.toml new file mode 100644 index 00000000000..90c47b6c882 --- /dev/null +++ b/crates/edit/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "edit" +version = "1.2.1" + +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +build = "build/main.rs" +categories = ["text-editors"] + +[[bench]] +name = "lib" +harness = false + +[features] +debug-latency = [] + +[dependencies] +stdext.workspace = true + +[target.'cfg(unix)'.dependencies] +libc = "0.2" + +[build-dependencies] +stdext.workspace = true +# The default toml crate bundles its dependencies with bad compile times. Thanks. +# Thankfully toml-span exists. FWIW the alternative is yaml-rust (without the 2 suffix). +toml-span = { version = "0.6", default-features = false } + +[target.'cfg(windows)'.build-dependencies] +winresource = { version = "0.1", default-features = false } + +[target.'cfg(windows)'.dependencies.windows-sys] +version = "0.61" +features = [ + "Win32_Globalization", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_System_Diagnostics_Debug", + "Win32_System_IO", + "Win32_System_LibraryLoader", + "Win32_System_Threading", +] + +[dev-dependencies] +criterion = { version = "0.7", features = ["html_reports"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +zstd = { version = "0.13", default-features = false } diff --git a/benches/lib.rs b/crates/edit/benches/lib.rs similarity index 98% rename from benches/lib.rs rename to crates/edit/benches/lib.rs index b06bd0675f3..4c8fcc37df3 100644 --- a/benches/lib.rs +++ b/crates/edit/benches/lib.rs @@ -8,8 +8,9 @@ use std::{mem, vec}; use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use edit::helpers::*; use edit::simd::MemsetSafe; -use edit::{arena, buffer, hash, oklab, simd, unicode}; +use edit::{buffer, hash, oklab, simd, unicode}; use serde::Deserialize; +use stdext::arena; #[derive(Deserialize)] pub struct EditingTracePatch(pub usize, pub usize, pub String); @@ -29,7 +30,7 @@ pub struct EditingTraceData { } fn bench_buffer(c: &mut Criterion) { - let data = include_bytes!("../assets/editing-traces/rustcode.json.zst"); + let data = include_bytes!("../../../assets/editing-traces/rustcode.json.zst"); let data = zstd::decode_all(Cursor::new(data)).unwrap(); let data: EditingTraceData = serde_json::from_slice(&data).unwrap(); let mut patches_with_coords = Vec::new(); diff --git a/build/helpers.rs b/crates/edit/build/helpers.rs similarity index 80% rename from build/helpers.rs rename to crates/edit/build/helpers.rs index 7453c34a0a9..9690a48f7ce 100644 --- a/build/helpers.rs +++ b/crates/edit/build/helpers.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use std::env::VarError; pub fn env_opt(name: &str) -> String { diff --git a/build/i18n.rs b/crates/edit/build/i18n.rs similarity index 98% rename from build/i18n.rs rename to crates/edit/build/i18n.rs index 4089902e313..faa0e2f6ff5 100644 --- a/build/i18n.rs +++ b/crates/edit/build/i18n.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Write as _; diff --git a/build/main.rs b/crates/edit/build/main.rs similarity index 89% rename from build/main.rs rename to crates/edit/build/main.rs index fb1d8d157f9..4acb58a8781 100644 --- a/build/main.rs +++ b/crates/edit/build/main.rs @@ -16,6 +16,8 @@ enum TargetOs { } fn main() { + stdext::arena::init(128 * 1024 * 1024).unwrap(); + let target_os = match env_opt("CARGO_CFG_TARGET_OS").as_str() { "windows" => TargetOs::Windows, "macos" | "ios" => TargetOs::MacOS, @@ -29,16 +31,16 @@ fn main() { } fn compile_i18n() { - const PATH: &str = "i18n/edit.toml"; + let i18n_path = "../../i18n/edit.toml"; - let i18n = std::fs::read_to_string(PATH).unwrap(); + let i18n = std::fs::read_to_string(i18n_path).unwrap(); let contents = i18n::generate(&i18n); let out_dir = env_opt("OUT_DIR"); let path = format!("{out_dir}/i18n_edit.rs"); - std::fs::write(path, contents).unwrap(); + std::fs::write(&path, contents).unwrap(); println!("cargo::rerun-if-env-changed=EDIT_CFG_LANGUAGES"); - println!("cargo::rerun-if-changed={PATH}"); + println!("cargo::rerun-if-changed={i18n_path}"); } fn configure_icu(target_os: TargetOs) { @@ -111,13 +113,16 @@ fn configure_windows_binary(target_os: TargetOs) { return; } - const PATH: &str = "src/bin/edit/edit.exe.manifest"; - println!("cargo::rerun-if-changed={PATH}"); + let manifest_path = "src/bin/edit/edit.exe.manifest"; + let icon_path = "../../assets/edit.ico"; + winresource::WindowsResource::new() - .set_manifest_file(PATH) + .set_manifest_file(manifest_path) .set("FileDescription", "Microsoft Edit") .set("LegalCopyright", "© Microsoft Corporation. All rights reserved.") - .set_icon("assets/edit.ico") + .set_icon(icon_path) .compile() .unwrap(); + + println!("cargo::rerun-if-changed={manifest_path}"); } diff --git a/src/apperr.rs b/crates/edit/src/apperr.rs similarity index 72% rename from src/apperr.rs rename to crates/edit/src/apperr.rs index faa1aa41bb8..8427f2f19e9 100644 --- a/src/apperr.rs +++ b/crates/edit/src/apperr.rs @@ -3,6 +3,7 @@ //! Provides a transparent error type for edit. +use std::alloc::AllocError; use std::{io, result}; use crate::sys; @@ -40,3 +41,11 @@ impl From for Error { sys::io_error_to_apperr(err) } } + +impl From for Error { + fn from(_: AllocError) -> Self { + // TODO: Technically this breaks if the AllocError isn't recent. By then, the errno may + // have been tained. But the stdlib AllocError is a bad type with no way to carry info. + sys::get_last_error() + } +} diff --git a/src/base64.rs b/crates/edit/src/base64.rs similarity index 98% rename from src/base64.rs rename to crates/edit/src/base64.rs index bce13c62751..944e2c8cc67 100644 --- a/src/base64.rs +++ b/crates/edit/src/base64.rs @@ -3,7 +3,7 @@ //! Base64 facilities. -use crate::arena::ArenaString; +use stdext::arena::ArenaString; const CHARSET: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -79,8 +79,9 @@ pub fn encode(dst: &mut ArenaString, src: &[u8]) { #[cfg(test)] mod tests { + use stdext::arena::{Arena, ArenaString}; + use super::encode; - use crate::arena::{Arena, ArenaString}; #[test] fn test_basic() { diff --git a/src/bin/edit/documents.rs b/crates/edit/src/bin/edit/documents.rs similarity index 100% rename from src/bin/edit/documents.rs rename to crates/edit/src/bin/edit/documents.rs diff --git a/src/bin/edit/draw_editor.rs b/crates/edit/src/bin/edit/draw_editor.rs similarity index 100% rename from src/bin/edit/draw_editor.rs rename to crates/edit/src/bin/edit/draw_editor.rs diff --git a/src/bin/edit/draw_filepicker.rs b/crates/edit/src/bin/edit/draw_filepicker.rs similarity index 99% rename from src/bin/edit/draw_filepicker.rs rename to crates/edit/src/bin/edit/draw_filepicker.rs index 3fae635104c..94adb033129 100644 --- a/src/bin/edit/draw_filepicker.rs +++ b/crates/edit/src/bin/edit/draw_filepicker.rs @@ -5,12 +5,12 @@ use std::cmp::Ordering; use std::fs; use std::path::{Path, PathBuf}; -use edit::arena::scratch_arena; use edit::framebuffer::IndexedColor; use edit::helpers::*; use edit::input::{kbmod, vk}; use edit::tui::*; use edit::{icu, path}; +use stdext::arena::scratch_arena; use crate::localization::*; use crate::state::*; diff --git a/src/bin/edit/draw_menubar.rs b/crates/edit/src/bin/edit/draw_menubar.rs similarity index 99% rename from src/bin/edit/draw_menubar.rs rename to crates/edit/src/bin/edit/draw_menubar.rs index 9fe8b7cefb6..8f14f70c1ed 100644 --- a/src/bin/edit/draw_menubar.rs +++ b/crates/edit/src/bin/edit/draw_menubar.rs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use edit::arena_format; use edit::helpers::*; use edit::input::{kbmod, vk}; use edit::tui::*; +use stdext::arena_format; use crate::localization::*; use crate::state::*; diff --git a/src/bin/edit/draw_statusbar.rs b/crates/edit/src/bin/edit/draw_statusbar.rs similarity index 99% rename from src/bin/edit/draw_statusbar.rs rename to crates/edit/src/bin/edit/draw_statusbar.rs index f7a631ace12..8250e44daed 100644 --- a/src/bin/edit/draw_statusbar.rs +++ b/crates/edit/src/bin/edit/draw_statusbar.rs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use edit::arena::scratch_arena; use edit::framebuffer::{Attributes, IndexedColor}; use edit::fuzzy::score_fuzzy; use edit::helpers::*; +use edit::icu; use edit::input::vk; use edit::tui::*; -use edit::{arena_format, icu}; +use stdext::arena::scratch_arena; +use stdext::arena_format; use crate::localization::*; use crate::state::*; diff --git a/src/bin/edit/edit.exe.manifest b/crates/edit/src/bin/edit/edit.exe.manifest similarity index 100% rename from src/bin/edit/edit.exe.manifest rename to crates/edit/src/bin/edit/edit.exe.manifest diff --git a/src/bin/edit/localization.rs b/crates/edit/src/bin/edit/localization.rs similarity index 95% rename from src/bin/edit/localization.rs rename to crates/edit/src/bin/edit/localization.rs index aeecec7aa56..2e3eed8943e 100644 --- a/src/bin/edit/localization.rs +++ b/crates/edit/src/bin/edit/localization.rs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use edit::arena::scratch_arena; use edit::helpers::AsciiStringHelpers; use edit::sys; +use stdext::arena::scratch_arena; include!(concat!(env!("OUT_DIR"), "/i18n_edit.rs")); diff --git a/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs similarity index 99% rename from src/bin/edit/main.rs rename to crates/edit/src/bin/edit/main.rs index 326c88a02ee..b374e0ade4c 100644 --- a/src/bin/edit/main.rs +++ b/crates/edit/src/bin/edit/main.rs @@ -22,16 +22,17 @@ use draw_editor::*; use draw_filepicker::*; use draw_menubar::*; use draw_statusbar::*; -use edit::arena::{self, Arena, ArenaString, scratch_arena}; use edit::framebuffer::{self, IndexedColor}; use edit::helpers::{CoordType, KIBI, MEBI, MetricFormatter, Rect, Size}; use edit::input::{self, kbmod, vk}; use edit::oklab::StraightRgba; use edit::tui::*; use edit::vt::{self, Token}; -use edit::{apperr, arena_format, base64, path, sys, unicode}; +use edit::{apperr, base64, path, sys, unicode}; use localization::*; use state::*; +use stdext::arena::{self, Arena, ArenaString, scratch_arena}; +use stdext::arena_format; #[cfg(target_pointer_width = "32")] const SCRATCH_ARENA_CAPACITY: usize = 128 * MEBI; diff --git a/src/bin/edit/state.rs b/crates/edit/src/bin/edit/state.rs similarity index 100% rename from src/bin/edit/state.rs rename to crates/edit/src/bin/edit/state.rs diff --git a/src/buffer/gap_buffer.rs b/crates/edit/src/buffer/gap_buffer.rs similarity index 97% rename from src/buffer/gap_buffer.rs rename to crates/edit/src/buffer/gap_buffer.rs index df1e38d3ace..4f84d712638 100644 --- a/src/buffer/gap_buffer.rs +++ b/crates/edit/src/buffer/gap_buffer.rs @@ -5,9 +5,11 @@ use std::ops::Range; use std::ptr::{self, NonNull}; use std::slice; +use stdext::sys::{virtual_commit, virtual_release, virtual_reserve}; + +use crate::apperr; use crate::document::{ReadableDocument, WriteableDocument}; use crate::helpers::*; -use crate::{apperr, sys}; #[cfg(target_pointer_width = "32")] const LARGE_CAPACITY: usize = 128 * MEBI; @@ -31,7 +33,7 @@ impl Drop for BackingBuffer { fn drop(&mut self) { unsafe { if let Self::VirtualMemory(ptr, reserve) = *self { - sys::virtual_release(ptr, reserve); + virtual_release(ptr, reserve); } } } @@ -73,7 +75,7 @@ impl GapBuffer { buffer = BackingBuffer::Vec(Vec::new()); } else { reserve = LARGE_CAPACITY; - text = unsafe { sys::virtual_reserve(reserve)? }; + text = unsafe { virtual_reserve(reserve)? }; buffer = BackingBuffer::VirtualMemory(text, reserve); } @@ -195,7 +197,7 @@ impl GapBuffer { match &mut self.buffer { BackingBuffer::VirtualMemory(ptr, _) => unsafe { - if sys::virtual_commit(ptr.add(bytes_old), bytes_new - bytes_old).is_err() { + if virtual_commit(ptr.add(bytes_old), bytes_new - bytes_old).is_err() { return; } }, diff --git a/src/buffer/line_cache.rs b/crates/edit/src/buffer/line_cache.rs similarity index 100% rename from src/buffer/line_cache.rs rename to crates/edit/src/buffer/line_cache.rs diff --git a/src/buffer/mod.rs b/crates/edit/src/buffer/mod.rs similarity index 99% rename from src/buffer/mod.rs rename to crates/edit/src/buffer/mod.rs index 6f0a714744e..9ea448542f6 100644 --- a/src/buffer/mod.rs +++ b/crates/edit/src/buffer/mod.rs @@ -35,8 +35,8 @@ use std::rc::Rc; use std::str; pub use gap_buffer::GapBuffer; +use stdext::arena::{Arena, ArenaString, scratch_arena}; -use crate::arena::{Arena, ArenaString, scratch_arena}; use crate::cell::SemiRefCell; use crate::clipboard::Clipboard; use crate::document::{ReadableDocument, WriteableDocument}; diff --git a/src/buffer/navigation.rs b/crates/edit/src/buffer/navigation.rs similarity index 100% rename from src/buffer/navigation.rs rename to crates/edit/src/buffer/navigation.rs diff --git a/src/cell.rs b/crates/edit/src/cell.rs similarity index 100% rename from src/cell.rs rename to crates/edit/src/cell.rs diff --git a/src/clipboard.rs b/crates/edit/src/clipboard.rs similarity index 100% rename from src/clipboard.rs rename to crates/edit/src/clipboard.rs diff --git a/src/document.rs b/crates/edit/src/document.rs similarity index 98% rename from src/document.rs rename to crates/edit/src/document.rs index 697bcea0346..c7def366d30 100644 --- a/src/document.rs +++ b/crates/edit/src/document.rs @@ -8,7 +8,8 @@ use std::mem; use std::ops::Range; use std::path::PathBuf; -use crate::arena::{ArenaString, scratch_arena}; +use stdext::arena::{ArenaString, scratch_arena}; + use crate::helpers::ReplaceRange as _; /// An abstraction over reading from text containers. diff --git a/src/framebuffer.rs b/crates/edit/src/framebuffer.rs similarity index 99% rename from src/framebuffer.rs rename to crates/edit/src/framebuffer.rs index b26a715d661..b02f867b95f 100644 --- a/src/framebuffer.rs +++ b/crates/edit/src/framebuffer.rs @@ -9,7 +9,8 @@ use std::ops::{BitOr, BitXor}; use std::ptr; use std::slice::ChunksExact; -use crate::arena::{Arena, ArenaString}; +use stdext::arena::{Arena, ArenaString}; + use crate::helpers::{CoordType, Point, Rect, Size}; use crate::oklab::StraightRgba; use crate::simd::{MemsetSafe, memset}; @@ -53,6 +54,12 @@ pub enum IndexedColor { Foreground, } +impl> From for IndexedColor { + fn from(value: T) -> Self { + unsafe { std::mem::transmute(value.into() & 0xF) } + } +} + /// Number of indices used by [`IndexedColor`]. pub const INDEXED_COLORS_COUNT: usize = 18; diff --git a/src/fuzzy.rs b/crates/edit/src/fuzzy.rs similarity index 99% rename from src/fuzzy.rs rename to crates/edit/src/fuzzy.rs index b3dbf6f5264..2dc450f9ccb 100644 --- a/src/fuzzy.rs +++ b/crates/edit/src/fuzzy.rs @@ -7,7 +7,8 @@ use std::vec; -use crate::arena::{Arena, scratch_arena}; +use stdext::arena::{Arena, scratch_arena}; + use crate::icu; const NO_MATCH: i32 = 0; diff --git a/src/hash.rs b/crates/edit/src/hash.rs similarity index 100% rename from src/hash.rs rename to crates/edit/src/hash.rs diff --git a/src/helpers.rs b/crates/edit/src/helpers.rs similarity index 100% rename from src/helpers.rs rename to crates/edit/src/helpers.rs diff --git a/src/icu.rs b/crates/edit/src/icu.rs similarity index 99% rename from src/icu.rs rename to crates/edit/src/icu.rs index 9687794a9aa..1be796019f7 100644 --- a/src/icu.rs +++ b/crates/edit/src/icu.rs @@ -10,10 +10,12 @@ use std::mem::MaybeUninit; use std::ops::Range; use std::ptr::{null, null_mut}; -use crate::arena::{Arena, ArenaString, scratch_arena}; +use stdext::arena::{Arena, ArenaString, scratch_arena}; +use stdext::arena_format; + use crate::buffer::TextBuffer; use crate::unicode::Utf8Chars; -use crate::{apperr, arena_format, sys}; +use crate::{apperr, sys}; #[derive(Clone, Copy)] pub struct Encoding { diff --git a/src/input.rs b/crates/edit/src/input.rs similarity index 100% rename from src/input.rs rename to crates/edit/src/input.rs diff --git a/src/lib.rs b/crates/edit/src/lib.rs similarity index 96% rename from src/lib.rs rename to crates/edit/src/lib.rs index 4a150da197b..59e3df0ed02 100644 --- a/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -17,9 +17,6 @@ )] #![allow(clippy::missing_transmute_annotations, clippy::new_without_default, stable_features)] -#[macro_use] -pub mod arena; - pub mod apperr; pub mod base64; pub mod buffer; diff --git a/src/oklab.rs b/crates/edit/src/oklab.rs similarity index 100% rename from src/oklab.rs rename to crates/edit/src/oklab.rs diff --git a/src/path.rs b/crates/edit/src/path.rs similarity index 100% rename from src/path.rs rename to crates/edit/src/path.rs diff --git a/src/simd/lines_bwd.rs b/crates/edit/src/simd/lines_bwd.rs similarity index 100% rename from src/simd/lines_bwd.rs rename to crates/edit/src/simd/lines_bwd.rs diff --git a/src/simd/lines_fwd.rs b/crates/edit/src/simd/lines_fwd.rs similarity index 100% rename from src/simd/lines_fwd.rs rename to crates/edit/src/simd/lines_fwd.rs diff --git a/src/simd/memchr2.rs b/crates/edit/src/simd/memchr2.rs similarity index 98% rename from src/simd/memchr2.rs rename to crates/edit/src/simd/memchr2.rs index 3e1708d0cc7..0514da05c4c 100644 --- a/src/simd/memchr2.rs +++ b/crates/edit/src/simd/memchr2.rs @@ -225,8 +225,9 @@ unsafe fn memchr2_neon(needle1: u8, needle2: u8, mut beg: *const u8, end: *const mod tests { use std::slice; + use stdext::sys::{virtual_commit, virtual_reserve}; + use super::*; - use crate::sys; #[test] fn test_empty() { @@ -265,8 +266,8 @@ mod tests { const PAGE_SIZE: usize = 64 * 1024; // 64 KiB to cover many architectures. // 3 pages: uncommitted, committed, uncommitted - let ptr = sys::virtual_reserve(PAGE_SIZE * 3).unwrap(); - sys::virtual_commit(ptr.add(PAGE_SIZE), PAGE_SIZE).unwrap(); + let ptr = virtual_reserve(PAGE_SIZE * 3).unwrap(); + virtual_commit(ptr.add(PAGE_SIZE), PAGE_SIZE).unwrap(); slice::from_raw_parts_mut(ptr.add(PAGE_SIZE).as_ptr(), PAGE_SIZE) }; diff --git a/src/simd/memset.rs b/crates/edit/src/simd/memset.rs similarity index 100% rename from src/simd/memset.rs rename to crates/edit/src/simd/memset.rs diff --git a/src/simd/mod.rs b/crates/edit/src/simd/mod.rs similarity index 100% rename from src/simd/mod.rs rename to crates/edit/src/simd/mod.rs diff --git a/src/sys/mod.rs b/crates/edit/src/sys/mod.rs similarity index 100% rename from src/sys/mod.rs rename to crates/edit/src/sys/mod.rs diff --git a/src/sys/unix.rs b/crates/edit/src/sys/unix.rs similarity index 88% rename from src/sys/unix.rs rename to crates/edit/src/sys/unix.rs index f3b067bd036..e2078128e5f 100644 --- a/src/sys/unix.rs +++ b/crates/edit/src/sys/unix.rs @@ -14,24 +14,11 @@ use std::path::Path; use std::ptr::{self, NonNull, null_mut}; use std::{thread, time}; -use crate::arena::{Arena, ArenaString, scratch_arena}; -use crate::helpers::*; -use crate::{apperr, arena_format}; - -#[cfg(target_os = "netbsd")] -const fn desired_mprotect(flags: c_int) -> c_int { - // NetBSD allows an mmap(2) caller to specify what protection flags they - // will use later via mprotect. It does not allow a caller to move from - // PROT_NONE to PROT_READ | PROT_WRITE. - // - // see PROT_MPROTECT in man 2 mmap - flags << 3 -} +use stdext::arena::{Arena, ArenaString, scratch_arena}; +use stdext::arena_format; -#[cfg(not(target_os = "netbsd"))] -const fn desired_mprotect(_: c_int) -> c_int { - libc::PROT_NONE -} +use crate::apperr; +use crate::helpers::*; struct State { stdin: libc::c_int, @@ -379,58 +366,6 @@ pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result { } } -/// Reserves a virtual memory region of the given size. -/// To commit the memory, use `virtual_commit`. -/// To release the memory, use `virtual_release`. -/// -/// # Safety -/// -/// This function is unsafe because it uses raw pointers. -/// Don't forget to release the memory when you're done with it or you'll leak it. -pub unsafe fn virtual_reserve(size: usize) -> apperr::Result> { - unsafe { - let ptr = libc::mmap( - null_mut(), - size, - desired_mprotect(libc::PROT_READ | libc::PROT_WRITE), - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - -1, - 0, - ); - if ptr.is_null() || ptr::eq(ptr, libc::MAP_FAILED) { - Err(errno_to_apperr(libc::ENOMEM)) - } else { - Ok(NonNull::new_unchecked(ptr as *mut u8)) - } - } -} - -/// Releases a virtual memory region of the given size. -/// -/// # Safety -/// -/// This function is unsafe because it uses raw pointers. -/// Make sure to only pass pointers acquired from `virtual_reserve`. -pub unsafe fn virtual_release(base: NonNull, size: usize) { - unsafe { - libc::munmap(base.cast().as_ptr(), size); - } -} - -/// Commits a virtual memory region of the given size. -/// -/// # Safety -/// -/// This function is unsafe because it uses raw pointers. -/// Make sure to only pass pointers acquired from `virtual_reserve` -/// and to pass a size less than or equal to the size passed to `virtual_reserve`. -pub unsafe fn virtual_commit(base: NonNull, size: usize) -> apperr::Result<()> { - unsafe { - let status = libc::mprotect(base.cast().as_ptr(), size, libc::PROT_READ | libc::PROT_WRITE); - if status != 0 { Err(errno_to_apperr(libc::ENOMEM)) } else { Ok(()) } - } -} - unsafe fn load_library(name: *const c_char) -> apperr::Result> { unsafe { NonNull::new(libc::dlopen(name, libc::RTLD_LAZY)) @@ -612,6 +547,11 @@ fn errno() -> i32 { ManuallyDrop::new(std::io::Error::last_os_error()).raw_os_error().unwrap_or(0) } +#[cold] +pub fn get_last_error() -> apperr::Error { + errno_to_apperr(errno()) +} + #[inline] pub(crate) fn io_error_to_apperr(err: std::io::Error) -> apperr::Error { errno_to_apperr(err.raw_os_error().unwrap_or(0)) @@ -640,5 +580,5 @@ const fn errno_to_apperr(no: c_int) -> apperr::Error { } fn check_int_return(ret: libc::c_int) -> apperr::Result { - if ret < 0 { Err(errno_to_apperr(errno())) } else { Ok(ret) } + if ret < 0 { Err(get_last_error()) } else { Ok(ret) } } diff --git a/src/sys/windows.rs b/crates/edit/src/sys/windows.rs similarity index 90% rename from src/sys/windows.rs rename to crates/edit/src/sys/windows.rs index 6ec8f76b89f..03e9ca7da1b 100644 --- a/src/sys/windows.rs +++ b/crates/edit/src/sys/windows.rs @@ -10,15 +10,15 @@ use std::path::{Path, PathBuf}; use std::ptr::{self, NonNull, null, null_mut}; use std::{mem, time}; +use stdext::arena::{Arena, ArenaString, scratch_arena}; use windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER; use windows_sys::Win32::Storage::FileSystem; use windows_sys::Win32::System::Diagnostics::Debug; -use windows_sys::Win32::System::{Console, IO, LibraryLoader, Memory, Threading}; +use windows_sys::Win32::System::{Console, IO, LibraryLoader, Threading}; use windows_sys::Win32::{Foundation, Globalization}; -use windows_sys::w; +use windows_sys::core::*; use crate::apperr; -use crate::arena::{Arena, ArenaString, scratch_arena}; use crate::helpers::*; macro_rules! w_env { @@ -56,7 +56,7 @@ type ReadConsoleInputExW = unsafe extern "system" fn( n_length: u32, lp_number_of_events_read: *mut u32, w_flags: u16, -) -> Foundation::BOOL; +) -> BOOL; unsafe extern "system" fn read_console_input_ex_placeholder( _: Foundation::HANDLE, @@ -64,7 +64,7 @@ unsafe extern "system" fn read_console_input_ex_placeholder( _: u32, _: *mut u32, _: u16, -) -> Foundation::BOOL { +) -> BOOL { panic!(); } @@ -101,7 +101,7 @@ static mut STATE: State = State { wants_exit: false, }; -extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> Foundation::BOOL { +extern "system" fn console_ctrl_handler(_ctrl_type: u32) -> BOOL { unsafe { STATE.wants_exit = true; IO::CancelIoEx(STATE.stdin, null()); @@ -516,70 +516,6 @@ pub fn canonicalize(path: &Path) -> std::io::Result { Ok(path) } -/// Reserves a virtual memory region of the given size. -/// To commit the memory, use [`virtual_commit`]. -/// To release the memory, use [`virtual_release`]. -/// -/// # Safety -/// -/// This function is unsafe because it uses raw pointers. -/// Don't forget to release the memory when you're done with it or you'll leak it. -pub unsafe fn virtual_reserve(size: usize) -> apperr::Result> { - unsafe { - #[allow(unused_assignments, unused_mut)] - let mut base = null_mut(); - - // In debug builds, we use fixed addresses to aid in debugging. - // Makes it possible to immediately tell which address space a pointer belongs to. - #[cfg(all(debug_assertions, not(target_pointer_width = "32")))] - { - static mut S_BASE_GEN: usize = 0x0000100000000000; // 16 TiB - S_BASE_GEN += 0x0000001000000000; // 64 GiB - base = S_BASE_GEN as *mut _; - } - - check_ptr_return(Memory::VirtualAlloc( - base, - size, - Memory::MEM_RESERVE, - Memory::PAGE_READWRITE, - ) as *mut u8) - } -} - -/// Releases a virtual memory region of the given size. -/// -/// # Safety -/// -/// This function is unsafe because it uses raw pointers. -/// Make sure to only pass pointers acquired from [`virtual_reserve`]. -pub unsafe fn virtual_release(base: NonNull, _size: usize) { - unsafe { - // NOTE: `VirtualFree` fails if the pointer isn't - // a valid base address or if the size isn't zero. - Memory::VirtualFree(base.as_ptr() as *mut _, 0, Memory::MEM_RELEASE); - } -} - -/// Commits a virtual memory region of the given size. -/// -/// # Safety -/// -/// This function is unsafe because it uses raw pointers. -/// Make sure to only pass pointers acquired from [`virtual_reserve`] -/// and to pass a size less than or equal to the size passed to [`virtual_reserve`]. -pub unsafe fn virtual_commit(base: NonNull, size: usize) -> apperr::Result<()> { - unsafe { - check_ptr_return(Memory::VirtualAlloc( - base.as_ptr() as *mut _, - size, - Memory::MEM_COMMIT, - Memory::PAGE_READWRITE, - )) - .map(|_| ()) - } -} - unsafe fn get_module(name: *const u16) -> apperr::Result> { unsafe { check_ptr_return(LibraryLoader::GetModuleHandleW(name)) } } @@ -719,7 +655,7 @@ fn wide_to_utf8<'a>(arena: &'a Arena, wide: &[u16]) -> ArenaString<'a> { } #[cold] -fn get_last_error() -> apperr::Error { +pub fn get_last_error() -> apperr::Error { unsafe { gle_to_apperr(Foundation::GetLastError()) } } @@ -773,7 +709,7 @@ pub fn apperr_is_not_found(err: apperr::Error) -> bool { err == gle_to_apperr(Foundation::ERROR_FILE_NOT_FOUND) } -fn check_bool_return(ret: Foundation::BOOL) -> apperr::Result<()> { +fn check_bool_return(ret: BOOL) -> apperr::Result<()> { if ret == 0 { Err(get_last_error()) } else { Ok(()) } } diff --git a/src/tui.rs b/crates/edit/src/tui.rs similarity index 99% rename from src/tui.rs rename to crates/edit/src/tui.rs index 58749a6e6b0..356e4e9a851 100644 --- a/src/tui.rs +++ b/crates/edit/src/tui.rs @@ -92,7 +92,7 @@ //! use edit::helpers::Size; //! use edit::input::Input; //! use edit::tui::*; -//! use edit::{arena, arena_format}; +//! use stdext::{arena, arena_format}; //! //! struct State { //! counter: i32, @@ -149,7 +149,9 @@ use std::collections::HashSet; use std::fmt::Write as _; use std::{iter, mem, ptr, time}; -use crate::arena::{Arena, ArenaString, scratch_arena}; +use stdext::arena::{Arena, ArenaString, scratch_arena}; +use stdext::arena_format; + use crate::buffer::{CursorMovement, MoveLineDirection, RcTextBuffer, TextBuffer, TextBufferCell}; use crate::cell::*; use crate::clipboard::Clipboard; @@ -159,7 +161,7 @@ use crate::hash::*; use crate::helpers::*; use crate::input::{InputKeyMod, kbmod, vk}; use crate::oklab::StraightRgba; -use crate::{apperr, arena_format, input, simd, unicode}; +use crate::{apperr, input, simd, unicode}; const ROOT_ID: u64 = 0x14057B7EF767814F; // Knuth's MMIX constant const SHIFT_TAB: InputKey = vk::TAB.with_modifiers(kbmod::SHIFT); diff --git a/src/unicode/measurement.rs b/crates/edit/src/unicode/measurement.rs similarity index 100% rename from src/unicode/measurement.rs rename to crates/edit/src/unicode/measurement.rs diff --git a/src/unicode/mod.rs b/crates/edit/src/unicode/mod.rs similarity index 100% rename from src/unicode/mod.rs rename to crates/edit/src/unicode/mod.rs diff --git a/src/unicode/tables.rs b/crates/edit/src/unicode/tables.rs similarity index 100% rename from src/unicode/tables.rs rename to crates/edit/src/unicode/tables.rs diff --git a/src/unicode/utf8.rs b/crates/edit/src/unicode/utf8.rs similarity index 100% rename from src/unicode/utf8.rs rename to crates/edit/src/unicode/utf8.rs diff --git a/src/vt.rs b/crates/edit/src/vt.rs similarity index 100% rename from src/vt.rs rename to crates/edit/src/vt.rs diff --git a/crates/stdext/Cargo.toml b/crates/stdext/Cargo.toml new file mode 100644 index 00000000000..6291567feb7 --- /dev/null +++ b/crates/stdext/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "stdext" +version = "0.0.0" + +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[target.'cfg(unix)'.dependencies] +libc = "0.2" diff --git a/src/arena/debug.rs b/crates/stdext/src/arena/debug.rs similarity index 95% rename from src/arena/debug.rs rename to crates/stdext/src/arena/debug.rs index 48083baa059..3c9144b39af 100644 --- a/src/arena/debug.rs +++ b/crates/stdext/src/arena/debug.rs @@ -4,11 +4,10 @@ #![allow(clippy::missing_safety_doc, clippy::mut_from_ref)] use std::alloc::{AllocError, Allocator, Layout}; -use std::mem::{self, MaybeUninit}; +use std::mem::MaybeUninit; use std::ptr::NonNull; use super::release; -use crate::apperr; /// A debug wrapper for [`release::Arena`]. /// @@ -19,7 +18,7 @@ use crate::apperr; /// It is completely valid for the same [`release::Arena`] to be borrowed multiple times at once, /// *as long as* you only use the most recent borrow. Bad example: /// ```should_panic -/// use edit::arena::scratch_arena; +/// use stdext::arena::scratch_arena; /// /// let mut scratch1 = scratch_arena(None); /// let mut scratch2 = scratch_arena(None); @@ -63,14 +62,14 @@ impl Arena { Self::Owned { arena: release::Arena::empty() } } - pub fn new(capacity: usize) -> apperr::Result { + pub fn new(capacity: usize) -> Result { Ok(Self::Owned { arena: release::Arena::new(capacity)? }) } pub(super) fn delegated(delegate: &release::Arena) -> Self { let borrow = delegate.borrows.get() + 1; delegate.borrows.set(borrow); - Self::Delegated { delegate: unsafe { mem::transmute(delegate) }, borrow } + Self::Delegated { delegate: unsafe { &*(delegate as *const _) }, borrow } } #[inline] diff --git a/src/arena/mod.rs b/crates/stdext/src/arena/mod.rs similarity index 100% rename from src/arena/mod.rs rename to crates/stdext/src/arena/mod.rs diff --git a/src/arena/release.rs b/crates/stdext/src/arena/release.rs similarity index 98% rename from src/arena/release.rs rename to crates/stdext/src/arena/release.rs index 7e4ebcf216b..c1441c2e8c6 100644 --- a/src/arena/release.rs +++ b/crates/stdext/src/arena/release.rs @@ -5,15 +5,13 @@ use std::alloc::{AllocError, Allocator, Layout}; use std::cell::Cell; -use std::hint::cold_path; use std::mem::MaybeUninit; use std::ptr::{self, NonNull}; use std::{mem, slice}; -use crate::helpers::*; -use crate::{apperr, sys}; +use crate::{cold_path, sys}; -const ALLOC_CHUNK_SIZE: usize = 64 * KIBI; +const ALLOC_CHUNK_SIZE: usize = 64 * 1024; /// An arena allocator. /// @@ -64,7 +62,7 @@ impl Arena { } } - pub fn new(capacity: usize) -> apperr::Result { + pub fn new(capacity: usize) -> Result { let capacity = (capacity.max(1) + ALLOC_CHUNK_SIZE - 1) & !(ALLOC_CHUNK_SIZE - 1); let base = unsafe { sys::virtual_reserve(capacity)? }; diff --git a/src/arena/scratch.rs b/crates/stdext/src/arena/scratch.rs similarity index 97% rename from src/arena/scratch.rs rename to crates/stdext/src/arena/scratch.rs index d2805095b4f..7c519dfad10 100644 --- a/src/arena/scratch.rs +++ b/crates/stdext/src/arena/scratch.rs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use std::alloc::AllocError; use std::ops::Deref; #[cfg(debug_assertions)] use super::debug; use super::{Arena, release}; -use crate::apperr; use crate::helpers::*; /// Borrows an [`Arena`] for temporary allocations. @@ -74,7 +74,7 @@ mod single_threaded { /// Initialize the scratch arenas with a given capacity. /// Call this before using [`scratch_arena`]. #[allow(dead_code)] - pub fn init(capacity: usize) -> apperr::Result<()> { + pub fn init(capacity: usize) -> Result<(), AllocError> { unsafe { for s in &mut S_SCRATCH[..] { *s = release::Arena::new(capacity)?; @@ -130,7 +130,7 @@ mod multi_threaded { /// Does nothing. #[allow(dead_code)] - pub fn init(_: usize) -> apperr::Result<()> { + pub fn init(_: usize) -> Result<(), AllocError> { Ok(()) } diff --git a/src/arena/string.rs b/crates/stdext/src/arena/string.rs similarity index 96% rename from src/arena/string.rs rename to crates/stdext/src/arena/string.rs index e2ac650b02b..8216133ba2f 100644 --- a/src/arena/string.rs +++ b/crates/stdext/src/arena/string.rs @@ -3,6 +3,7 @@ use std::fmt; use std::ops::{Bound, Deref, DerefMut, RangeBounds}; +use std::str::Utf8Error; use super::Arena; use crate::helpers::*; @@ -35,6 +36,10 @@ impl<'a> ArenaString<'a> { res } + pub fn from_utf8(arena: &'a Arena, vec: &[u8]) -> Result { + Ok(Self::from_str(arena, str::from_utf8(vec)?)) + } + /// It says right here that you checked if `bytes` is valid UTF-8 /// and you are sure it is. Presto! Here's an `ArenaString`! /// @@ -120,6 +125,10 @@ impl<'a> ArenaString<'a> { self.vec.as_slice() } + pub fn leak(self) -> &'a str { + unsafe { str::from_utf8_unchecked(self.vec.leak()) } + } + /// Returns a mutable reference to the contents of this `String`. /// /// # Safety @@ -279,7 +288,7 @@ impl fmt::Write for ArenaString<'_> { macro_rules! arena_format { ($arena:expr, $($arg:tt)*) => {{ use std::fmt::Write as _; - let mut output = $crate::arena::ArenaString::new_in($arena); + let mut output = stdext::arena::ArenaString::new_in($arena); output.write_fmt(format_args!($($arg)*)).unwrap(); output }} diff --git a/crates/stdext/src/helpers.rs b/crates/stdext/src/helpers.rs new file mode 100644 index 00000000000..556206ae6ed --- /dev/null +++ b/crates/stdext/src/helpers.rs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Random assortment of helpers I didn't know where to put. + +use std::alloc::Allocator; +use std::mem::{self, MaybeUninit}; +use std::ops::{Bound, Range, RangeBounds}; +use std::{fmt, ptr, slice, str}; + +pub const KILO: usize = 1000; +pub const MEGA: usize = 1000 * 1000; +pub const GIGA: usize = 1000 * 1000 * 1000; + +pub const KIBI: usize = 1024; +pub const MEBI: usize = 1024 * 1024; +pub const GIBI: usize = 1024 * 1024 * 1024; + +pub struct MetricFormatter(pub T); + +impl fmt::Display for MetricFormatter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut value = self.0; + let mut suffix = "B"; + if value >= GIGA { + value /= GIGA; + suffix = "GB"; + } else if value >= MEGA { + value /= MEGA; + suffix = "MB"; + } else if value >= KILO { + value /= KILO; + suffix = "kB"; + } + write!(f, "{value}{suffix}") + } +} + +#[inline(always)] +#[cold] +pub const fn cold_path() {} + +/// [`std::cmp::minmax`] is unstable, as per usual. +pub fn minmax(v1: T, v2: T) -> [T; 2] +where + T: Ord, +{ + if v2 < v1 { [v2, v1] } else { [v1, v2] } +} + +#[inline(always)] +#[allow(clippy::ptr_eq)] +pub fn opt_ptr(a: Option<&T>) -> *const T { + unsafe { mem::transmute(a) } +} + +/// Surprisingly, there's no way in Rust to do a `ptr::eq` on `Option<&T>`. +/// Uses `unsafe` so that the debug performance isn't too bad. +#[inline(always)] +#[allow(clippy::ptr_eq)] +pub fn opt_ptr_eq(a: Option<&T>, b: Option<&T>) -> bool { + opt_ptr(a) == opt_ptr(b) +} + +/// Creates a `&str` from a pointer and a length. +/// Exists, because `std::str::from_raw_parts` is unstable, par for the course. +/// +/// # Safety +/// +/// The given data must be valid UTF-8. +/// The given data must outlive the returned reference. +#[inline] +#[must_use] +pub const unsafe fn str_from_raw_parts<'a>(ptr: *const u8, len: usize) -> &'a str { + unsafe { str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)) } +} + +/// [`<[T]>::copy_from_slice`] panics if the two slices have different lengths. +/// This one just returns the copied amount. +pub fn slice_copy_safe(dst: &mut [T], src: &[T]) -> usize { + let len = src.len().min(dst.len()); + unsafe { ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), len) }; + len +} + +/// [`Vec::splice`] results in really bad assembly. +/// This doesn't. Don't use [`Vec::splice`]. +pub trait ReplaceRange { + fn replace_range>(&mut self, range: R, src: &[T]); +} + +impl ReplaceRange for Vec { + fn replace_range>(&mut self, range: R, src: &[T]) { + let start = match range.start_bound() { + Bound::Included(&start) => start, + Bound::Excluded(start) => start + 1, + Bound::Unbounded => 0, + }; + let end = match range.end_bound() { + Bound::Included(end) => end + 1, + Bound::Excluded(&end) => end, + Bound::Unbounded => usize::MAX, + }; + vec_replace_impl(self, start..end, src); + } +} + +fn vec_replace_impl(dst: &mut Vec, range: Range, src: &[T]) { + unsafe { + let dst_len = dst.len(); + let src_len = src.len(); + let off = range.start.min(dst_len); + let del_len = range.end.saturating_sub(off).min(dst_len - off); + + if del_len == 0 && src_len == 0 { + return; // nothing to do + } + + let tail_len = dst_len - off - del_len; + let new_len = dst_len - del_len + src_len; + + if src_len > del_len { + dst.reserve(src_len - del_len); + } + + // NOTE: drop_in_place() is not needed here, because T is constrained to Copy. + + // SAFETY: as_mut_ptr() must called after reserve() to ensure that the pointer is valid. + let ptr = dst.as_mut_ptr().add(off); + + // Shift the tail. + if tail_len > 0 && src_len != del_len { + ptr::copy(ptr.add(del_len), ptr.add(src_len), tail_len); + } + + // Copy in the replacement. + ptr::copy_nonoverlapping(src.as_ptr(), ptr, src_len); + dst.set_len(new_len); + } +} + +/// Turns a [`&[u8]`] into a [`&[MaybeUninit]`]. +#[inline(always)] +pub const fn slice_as_uninit_ref(slice: &[T]) -> &[MaybeUninit] { + unsafe { slice::from_raw_parts(slice.as_ptr() as *const MaybeUninit, slice.len()) } +} + +/// Turns a [`&mut [T]`] into a [`&mut [MaybeUninit]`]. +#[inline(always)] +pub const fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { + unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut MaybeUninit, slice.len()) } +} + +/// Helpers for ASCII string comparisons. +pub trait AsciiStringHelpers { + /// Tests if a string starts with a given ASCII prefix. + /// + /// This function name really is a mouthful, but it's a combination + /// of [`str::starts_with`] and [`str::eq_ignore_ascii_case`]. + fn starts_with_ignore_ascii_case(&self, prefix: &str) -> bool; +} + +impl AsciiStringHelpers for str { + fn starts_with_ignore_ascii_case(&self, prefix: &str) -> bool { + // Casting to bytes first ensures we skip any UTF8 boundary checks. + // Since the comparison is ASCII, we don't need to worry about that. + let s = self.as_bytes(); + let p = prefix.as_bytes(); + p.len() <= s.len() && s[..p.len()].eq_ignore_ascii_case(p) + } +} diff --git a/crates/stdext/src/lib.rs b/crates/stdext/src/lib.rs new file mode 100644 index 00000000000..d7226d7e19c --- /dev/null +++ b/crates/stdext/src/lib.rs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Arena allocators. Small and fast. + +#![feature(allocator_api)] + +pub mod arena; +pub mod sys; + +mod helpers; +pub use helpers::*; diff --git a/crates/stdext/src/sys/mod.rs b/crates/stdext/src/sys/mod.rs new file mode 100644 index 00000000000..91d59881248 --- /dev/null +++ b/crates/stdext/src/sys/mod.rs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Platform abstractions. + +#[cfg(unix)] +mod unix; +#[cfg(windows)] +mod windows; + +#[cfg(not(windows))] +pub use std::fs::canonicalize; + +#[cfg(unix)] +pub use unix::*; +#[cfg(windows)] +pub use windows::*; diff --git a/crates/stdext/src/sys/unix.rs b/crates/stdext/src/sys/unix.rs new file mode 100644 index 00000000000..dd634ca8c72 --- /dev/null +++ b/crates/stdext/src/sys/unix.rs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::alloc::AllocError; +use std::ffi::c_int; +use std::ptr::{self, NonNull, null_mut}; + +/// Reserves a virtual memory region of the given size. +/// To commit the memory, use `virtual_commit`. +/// To release the memory, use `virtual_release`. +/// +/// # Safety +/// +/// This function is unsafe because it uses raw pointers. +/// Don't forget to release the memory when you're done with it or you'll leak it. +pub unsafe fn virtual_reserve(size: usize) -> Result, AllocError> { + unsafe { + let ptr = libc::mmap( + null_mut(), + size, + desired_mprotect(libc::PROT_READ | libc::PROT_WRITE), + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + if ptr.is_null() || ptr::eq(ptr, libc::MAP_FAILED) { + Err(AllocError) + } else { + Ok(NonNull::new_unchecked(ptr as *mut u8)) + } + } +} + +#[cfg(target_os = "netbsd")] +const fn desired_mprotect(flags: c_int) -> c_int { + // NetBSD allows an mmap(2) caller to specify what protection flags they + // will use later via mprotect. It does not allow a caller to move from + // PROT_NONE to PROT_READ | PROT_WRITE. + // + // see PROT_MPROTECT in man 2 mmap + flags << 3 +} + +#[cfg(not(target_os = "netbsd"))] +const fn desired_mprotect(_: c_int) -> c_int { + libc::PROT_NONE +} + +/// Releases a virtual memory region of the given size. +/// +/// # Safety +/// +/// This function is unsafe because it uses raw pointers. +/// Make sure to only pass pointers acquired from `virtual_reserve`. +pub unsafe fn virtual_release(base: NonNull, size: usize) { + unsafe { + libc::munmap(base.cast().as_ptr(), size); + } +} + +/// Commits a virtual memory region of the given size. +/// +/// # Safety +/// +/// This function is unsafe because it uses raw pointers. +/// Make sure to only pass pointers acquired from `virtual_reserve` +/// and to pass a size less than or equal to the size passed to `virtual_reserve`. +pub unsafe fn virtual_commit(base: NonNull, size: usize) -> Result<(), AllocError> { + unsafe { + let status = libc::mprotect(base.cast().as_ptr(), size, libc::PROT_READ | libc::PROT_WRITE); + if status != 0 { Err(AllocError) } else { Ok(()) } + } +} diff --git a/crates/stdext/src/sys/windows.rs b/crates/stdext/src/sys/windows.rs new file mode 100644 index 00000000000..e008814efcb --- /dev/null +++ b/crates/stdext/src/sys/windows.rs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::alloc::AllocError; +use std::ptr::{NonNull, null_mut}; + +const MEM_COMMIT: u32 = 0x00001000; +const MEM_RELEASE: u32 = 0x00008000; +const MEM_RESERVE: u32 = 0x00002000; +const PAGE_READWRITE: u32 = 0x04; + +unsafe extern "system" { + fn VirtualAlloc( + lpAddress: *mut u8, + dwSize: usize, + flAllocationType: u32, + flProtect: u32, + ) -> *mut u8; + fn VirtualFree(lpAddress: *mut u8, dwSize: usize, dwFreeType: u32) -> i32; +} + +/// Reserves a virtual memory region of the given size. +/// To commit the memory, use [`virtual_commit`]. +/// To release the memory, use [`virtual_release`]. +/// +/// # Safety +/// +/// This function is unsafe because it uses raw pointers. +/// Don't forget to release the memory when you're done with it or you'll leak it. +pub unsafe fn virtual_reserve(size: usize) -> Result, AllocError> { + unsafe { + let res = VirtualAlloc(null_mut(), size, MEM_RESERVE, PAGE_READWRITE); + if res.is_null() { Err(AllocError) } else { Ok(NonNull::new_unchecked(res)) } + } +} + +/// Releases a virtual memory region of the given size. +/// +/// # Safety +/// +/// This function is unsafe because it uses raw pointers. +/// Make sure to only pass pointers acquired from [`virtual_reserve`]. +pub unsafe fn virtual_release(base: NonNull, _size: usize) { + unsafe { + // NOTE: `VirtualFree` fails if the pointer isn't + // a valid base address or if the size isn't zero. + VirtualFree(base.as_ptr() as *mut _, 0, MEM_RELEASE); + } +} + +/// Commits a virtual memory region of the given size. +/// +/// # Safety +/// +/// This function is unsafe because it uses raw pointers. +/// Make sure to only pass pointers acquired from [`virtual_reserve`] +/// and to pass a size less than or equal to the size passed to [`virtual_reserve`]. +pub unsafe fn virtual_commit(base: NonNull, size: usize) -> Result<(), AllocError> { + unsafe { + let res = VirtualAlloc(base.as_ptr() as *mut _, size, MEM_COMMIT, PAGE_READWRITE); + if res.is_null() { Err(AllocError) } else { Ok(()) } + } +} diff --git a/crates/unicode-gen/Cargo.toml b/crates/unicode-gen/Cargo.toml new file mode 100644 index 00000000000..c8dde6b0c95 --- /dev/null +++ b/crates/unicode-gen/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "unicode-gen" +version = "0.0.0" + +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +anyhow = "1.0" +chrono = "0.4" +indoc = "2.0" +pico-args = { version = "0.5", features = ["eq-separator"] } +rayon = "1.10" +roxmltree = { version = "0.21", default-features = false, features = ["std"] } diff --git a/tools/grapheme-table-gen/README.md b/crates/unicode-gen/README.md similarity index 100% rename from tools/grapheme-table-gen/README.md rename to crates/unicode-gen/README.md diff --git a/tools/grapheme-table-gen/src/main.rs b/crates/unicode-gen/src/main.rs similarity index 100% rename from tools/grapheme-table-gen/src/main.rs rename to crates/unicode-gen/src/main.rs diff --git a/tools/grapheme-table-gen/src/rules.rs b/crates/unicode-gen/src/rules.rs similarity index 100% rename from tools/grapheme-table-gen/src/rules.rs rename to crates/unicode-gen/src/rules.rs diff --git a/tools/grapheme-table-gen/Cargo.lock b/tools/grapheme-table-gen/Cargo.lock deleted file mode 100644 index 3bd26ecf038..00000000000 --- a/tools/grapheme-table-gen/Cargo.lock +++ /dev/null @@ -1,377 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "cc" -version = "1.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "grapheme-table-gen" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "indoc", - "pico-args", - "rayon", - "roxmltree", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "indoc" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.175" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rayon" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "syn" -version = "2.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] diff --git a/tools/grapheme-table-gen/Cargo.toml b/tools/grapheme-table-gen/Cargo.toml deleted file mode 100644 index 083d70551b4..00000000000 --- a/tools/grapheme-table-gen/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "grapheme-table-gen" -version = "0.1.0" -edition = "2024" - -[dependencies] -anyhow = "1.0" -chrono = "0.4" -indoc = "2.0" -pico-args = { version = "0.5", features = ["eq-separator"] } -rayon = "1.10" -roxmltree = { version = "0.20", default-features = false, features = ["std"] } From 78adb81a2a54aecaa910329e0ec2b8e21c9f198f Mon Sep 17 00:00:00 2001 From: Mohammad Abu Mattar Date: Tue, 2 Dec 2025 00:15:54 +0300 Subject: [PATCH 13/27] i18n: Add Arabic translations (#634) Co-authored-by: MKAbuMattar --- i18n/edit.toml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/i18n/edit.toml b/i18n/edit.toml index 4492200d173..7628c4e3352 100644 --- a/i18n/edit.toml +++ b/i18n/edit.toml @@ -38,6 +38,7 @@ it = "Maiusc" # Used as a common dialog button [Ok] en = "Ok" +ar = "حسناً" bn = "ঠিক আছে" cs = "Ok" da = "OK" @@ -68,6 +69,7 @@ zh_hant = "確定" # Used as a common dialog button [Yes] en = "Yes" +ar = "نعم" bn = "হ্যাঁ" cs = "Ano" da = "Ja" @@ -100,6 +102,7 @@ zh_hant = "是" # Used as a common dialog button [No] en = "No" +ar = "لا" bn = "না" cs = "Ne" da = "Nej" @@ -132,6 +135,7 @@ zh_hant = "否" # Used as a common dialog button [Cancel] en = "Cancel" +ar = "إلغاء" bn = "বাতিল" cs = "Zrušit" da = "Annuller" @@ -164,6 +168,7 @@ zh_hant = "取消" # Used as a common dialog button [Always] en = "Always" +ar = "دائماً" bn = "সবসময়" cs = "Vždy" da = "Altid" @@ -195,6 +200,7 @@ zh_hant = "總是" # A menu bar item [File] en = "File" +ar = "ملف" bn = "ফাইল" cs = "Soubor" da = "Filer" @@ -226,6 +232,7 @@ zh_hant = "檔案" [FileNew] en = "New File" +ar = "ملف جديد" bn = "নতুন ফাইল…" cs = "Nový soubor" da = "Ny fil…" @@ -257,6 +264,7 @@ zh_hant = "新增檔案" [FileOpen] en = "Open File…" +ar = "فتح ملف…" bn = "ফাইল খুলুন…" cs = "Otevřít soubor…" da = "Åbn fil…" @@ -288,6 +296,7 @@ zh_hant = "開啟檔案…" [FileSave] en = "Save" +ar = "حفظ" bn = "সংরক্ষণ" cs = "Uložit" da = "Gem" @@ -319,6 +328,7 @@ zh_hant = "儲存" [FileSaveAs] en = "Save As…" +ar = "حفظ باسم…" bn = "অন্য নামে সংরক্ষণ…" cs = "Uložit jako…" da = "Gem som…" @@ -350,6 +360,7 @@ zh_hant = "另存新檔…" [FileClose] en = "Close File" +ar = "إغلاق الملف" bn = "এডিটর বন্ধ করুন" cs = "Zavřít editor" da = "Luk editor" @@ -381,6 +392,7 @@ zh_hant = "關閉檔案" [FileExit] en = "Exit" +ar = "خروج" bn = "প্রস্থান" cs = "Ukončit" da = "Afslut" @@ -412,6 +424,7 @@ zh_hant = "退出" [FileGoto] en = "Go to Line:Column…" +ar = "الانتقال إلى السطر:العمود…" cs = "Přejít na řádek/sloupec…" de = "Gehe zu Zeile:Spalte…" el = "Μετάβαση σε γραμμή:στήλη…" @@ -439,6 +452,7 @@ zh_hant = "跳至行:列…" # A menu bar item [Edit] en = "Edit" +ar = "تعديل" bn = "সম্পাদনা" cs = "Úpravy" da = "Rediger" @@ -470,6 +484,7 @@ zh_hant = "編輯" [EditUndo] en = "Undo" +ar = "تراجع" bn = "পূর্বাবস্থায় ফেরান" cs = "Zpět" da = "Fortryd" @@ -501,6 +516,7 @@ zh_hant = "復原" [EditRedo] en = "Redo" +ar = "إعادة" bn = "পুনরায় করুন" cs = "Znovu" da = "Gentag" @@ -532,6 +548,7 @@ zh_hant = "重做" [EditCut] en = "Cut" +ar = "قص" bn = "কাট" cs = "Vyjmout" da = "Klip" @@ -563,6 +580,7 @@ zh_hant = "剪下" [EditCopy] en = "Copy" +ar = "نسخ" bn = "কপি" cs = "Kopírovat" da = "Kopier" @@ -594,6 +612,7 @@ zh_hant = "複製" [EditPaste] en = "Paste" +ar = "لصق" bn = "পেস্ট" cs = "Vložit" da = "Sæt ind" @@ -625,6 +644,7 @@ zh_hant = "貼上" [EditFind] en = "Find" +ar = "بحث" bn = "অনুসন্ধান" cs = "Najít" da = "Find" @@ -656,6 +676,7 @@ zh_hant = "尋找" [EditReplace] en = "Replace" +ar = "استبدال" bn = "প্রতিস্থাপন" cs = "Nahradit" da = "Erstat" @@ -687,6 +708,7 @@ zh_hant = "取代" [EditSelectAll] en = "Select All" +ar = "تحديد الكل" cs = "Vybrat vše" de = "Alles auswählen" el = "Επιλογή όλων" @@ -713,6 +735,7 @@ zh_hant = "全選" # A menu bar item [View] en = "View" +ar = "عرض" bn = "দেখুন" cs = "Zobrazit" da = "Vis" @@ -744,6 +767,7 @@ zh_hant = "檢視" [ViewFocusStatusbar] en = "Focus Statusbar" +ar = "تركيز شريط الحالة" bn = "স্ট্যাটাসবারে ফোকাস করুন" cs = "Zaměřit na stavový řádek" da = "Fokuser statuslinje" @@ -775,6 +799,7 @@ zh_hant = "聚焦狀態列" [ViewWordWrap] en = "Word Wrap" +ar = "التفاف النص" bn = "শব্দ মোড়ানো" cs = "Zalamování" da = "Tekstombrydning" @@ -806,6 +831,7 @@ zh_hant = "自動換行" [ViewGoToFile] en = "Go to File…" +ar = "الانتقال إلى ملف…" cs = "Výběr dokumentu…" de = "Gehe zu Datei…" es = "Ir a archivo…" @@ -830,6 +856,7 @@ zh_hant = "跳至檔案…" # A menu bar item [Help] en = "Help" +ar = "مساعدة" bn = "সাহায্য" cs = "Nápověda" da = "Hjælp" @@ -861,6 +888,7 @@ zh_hant = "幫助" [HelpAbout] en = "About" +ar = "حول" bn = "সম্পর্কে" cs = "O programu" da = "Om" @@ -892,6 +920,7 @@ zh_hant = "關於" [UnsavedChangesDialogTitle] en = "Unsaved Changes" +ar = "تغييرات غير محفوظة" bn = "অসংরক্ষিত পরিবর্তন" cs = "Neuložené změny" da = "Ikke gemte ændringer" @@ -923,6 +952,7 @@ zh_hant = "未儲存的變更" [UnsavedChangesDialogDescription] en = "Do you want to save the changes you made?" +ar = "هل تريد حفظ التغييرات التي أجريتها؟" bn = "আপনি কি আপনার করা পরিবর্তনগুলি সংরক্ষণ করতে চান?" cs = "Chcete uložit provedené změny?" da = "Vil du gemme de ændringer, du har foretaget?" @@ -954,6 +984,7 @@ zh_hant = "您要保存所做的變更嗎?" [UnsavedChangesDialogYes] en = "Save" +ar = "حفظ" bn = "সংরক্ষণ" cs = "Uložit" da = "Gem" @@ -985,6 +1016,7 @@ zh_hant = "儲存" [UnsavedChangesDialogNo] en = "Don't Save" +ar = "لا تحفظ" bn = "সংরক্ষণ করবেন না" cs = "Neukládat" da = "Gem ikke" @@ -1015,6 +1047,7 @@ zh_hant = "不儲存" [AboutDialogTitle] en = "About" +ar = "حول" bn = "সম্পর্কে" cs = "O programu" da = "Om" @@ -1046,6 +1079,7 @@ zh_hant = "關於" [AboutDialogVersion] en = "Version: " +ar = "الإصدار: " bn = "সংস্করণ: " cs = "Verze: " da = "Version: " @@ -1078,6 +1112,7 @@ zh_hant = "版本: " # Shown when the clipboard size exceeds the limit for OSC 52 [LargeClipboardWarningLine1] en = "Text you copy is shared with the terminal clipboard." +ar = "النص الذي تقوم بنسخه تتم مشاركته مع حافظة الطرفية." bn = "আপনার কপি করা টেক্সট টার্মিনাল ক্লিপবোর্ডের সাথে শেয়ার করা হয়।" cs = "Text, který zkopírujete, je sdílen s terminálovou schránkou." da = "Tekst, du kopierer, deles med terminalens udklipsholder." @@ -1110,6 +1145,7 @@ zh_hant = "您複製的文字將會與終端機剪貼簿分享。" # Shown when the clipboard size exceeds the limit for OSC 52 [LargeClipboardWarningLine2] en = "You copied {size} which may take a long time to share." +ar = "لقد قمت بنسخ {size} مما قد يستغرق وقتًا طويلاً للمشاركة." bn = "আপনি {size} কপি করেছেন যা শেয়ার করতে দীর্ঘ সময় লাগতে পারে।" cs = "Zkopírovali jste {size}, což může trvat dlouho." da = "Du har kopieret {size}, som muligvis tager lang tid at dele." @@ -1142,6 +1178,7 @@ zh_hant = "您已複製 {size},共享可能需要較長時間。" # Shown when the clipboard size exceeds the limit for OSC 52 [LargeClipboardWarningLine3] en = "Do you want to send it anyway?" +ar = "هل تريد إرساله على أي حال؟" bn = "আপনি কি এটি যাইহোক পাঠাতে চান?" cs = "Chcete to přesto odeslat?" da = "Vil du sende det alligevel?" @@ -1174,6 +1211,7 @@ zh_hant = "仍要傳送嗎?" # As an alternative to LargeClipboardWarningLine2 and 3 [SuperLargeClipboardWarning] en = "The text you copied is too large to be shared." +ar = "النص الذي قمت بنسخه كبير جدًا للمشاركة." bn = "আপনার কপি করা টেক্সট শেয়ার করার জন্য খুব বড়।" cs = "Text, který jste zkopírovali, je příliš velký na to, aby mohl být sdílen." da = "Den kopierede tekst er for stor til at blive delt." @@ -1205,6 +1243,7 @@ zh_hant = "您複製的文字過大,無法分享。" [WarningDialogTitle] en = "Warning" +ar = "تحذير" bn = "সতর্কবার্তা" cs = "Upozornění" da = "Advarsel" @@ -1236,6 +1275,7 @@ zh_hant = "警告" [ErrorDialogTitle] en = "Error" +ar = "خطأ" bn = "ত্রুটি" cs = "Chyba" da = "Fejl" @@ -1267,6 +1307,7 @@ zh_hant = "錯誤" [ErrorIcuMissing] en = "This operation requires the ICU library" +ar = "تتطلب هذه العملية مكتبة ICU" bn = "এই অপারেশনের জন্য ICU লাইব্রেরি প্রয়োজন" cs = "Tato operace vyžaduje knihovnu ICU" da = "Denne operation kræver ICU-biblioteket" @@ -1299,6 +1340,7 @@ zh_hant = "此操作需要 ICU 庫" # For input field [SearchNeedleLabel] en = "Find:" +ar = "بحث:" bn = "অনুসন্ধান:" cs = "Najít:" da = "Find:" @@ -1331,6 +1373,7 @@ zh_hant = "尋找:" # For input field [SearchReplacementLabel] en = "Replace:" +ar = "استبدال:" bn = "প্রতিস্থাপন:" cs = "Nahradit:" da = "Erstat:" @@ -1363,6 +1406,7 @@ zh_hant = "替換:" # Toggle [SearchMatchCase] en = "Match Case" +ar = "مطابقة حالة الأحرف" bn = "কেস মিলান" cs = "Velká/malá písmena" da = "Store/små" @@ -1395,6 +1439,7 @@ zh_hant = "區分大小寫" # Toggle [SearchWholeWord] en = "Whole Word" +ar = "كلمة كاملة" bn = "সম্পূর্ণ শব্দ" cs = "Celé slovo" da = "Hele ordet" @@ -1427,6 +1472,7 @@ zh_hant = "全字匹配" # Toggle [SearchUseRegex] en = "Use Regex" +ar = "استخدام التعبيرات النمطية" bn = "Regex ব্যবহার করুন" cs = "Použít regulární výraz" da = "RegEx" @@ -1459,6 +1505,7 @@ zh_hant = "正則" # Button [SearchReplaceAll] en = "Replace All" +ar = "استبدال الكل" bn = "সব প্রতিস্থাপন করুন" cs = "Nahradit vše" da = "Erstat alle" @@ -1491,6 +1538,7 @@ zh_hant = "全部取代" # Button [SearchClose] en = "Close" +ar = "إغلاق" bn = "বন্ধ করুন" cs = "Zavřít" da = "Luk" @@ -1522,6 +1570,7 @@ zh_hant = "關閉" [EncodingReopen] en = "Reopen with encoding…" +ar = "إعادة فتح مع الترميز…" bn = "এনকোডিং সহ পুনরায় খুলুন" cs = "Znovu otevřít s kódováním…" da = "Genåbn med enkodning" @@ -1553,6 +1602,7 @@ zh_hant = "使用編碼重新打開…" [EncodingConvert] en = "Convert to encoding…" +ar = "تحويل إلى الترميز…" bn = "এনকোডিং-এ রূপান্তর করুন" cs = "Převést na kódování…" da = "Konverter til enkodning" @@ -1584,6 +1634,7 @@ zh_hant = "轉換為編碼…" [IndentationTabs] en = "Tabs" +ar = "علامات التبويب" bn = "ট্যাব" cs = "Tabulátory" da = "Tabs" @@ -1615,6 +1666,7 @@ zh_hant = "製表符" [IndentationSpaces] en = "Spaces" +ar = "المسافات" bn = "স্পেস" cs = "Mezery" da = "Mellemrum" @@ -1646,6 +1698,7 @@ zh_hant = "空格" [SaveAsDialogPathLabel] en = "Folder:" +ar = "المجلد:" bn = "ফোল্ডার:" cs = "Složka:" da = "Mappe:" @@ -1677,6 +1730,7 @@ zh_hant = "資料夾:" [SaveAsDialogNameLabel] en = "File name:" +ar = "اسم الملف:" bn = "ফাইলের নাম:" cs = "Název souboru:" da = "Filnavn:" @@ -1708,6 +1762,7 @@ zh_hant = "檔案名稱:" [FileOverwriteWarning] en = "Confirm Save As" +ar = "تأكيد الحفظ باسم…" bn = "অন্য নামে সংরক্ষণ নিশ্চিত করুন" cs = "Potvrdit Uložit jako" da = "Bekræft gem som" @@ -1739,6 +1794,7 @@ zh_hant = "確認另存新檔" [FileOverwriteWarningDescription] en = "File already exists. Do you want to overwrite it?" +ar = "الملف موجود بالفعل. هل تريد استبداله؟" bn = "ফাইল ইতিমধ্যে বিদ্যমান। আপনি কি এটি ওভাররাইট করতে চান?" cs = "Soubor již existuje. Chcete jej přepsat?" da = "Filen eksisterer allerede. Vil du overskrive den?" From 62be408cdfb5e6197c77003d7bc009633d84b6a9 Mon Sep 17 00:00:00 2001 From: Mr Flamel <148793343+mrFlamel@users.noreply.github.com> Date: Mon, 1 Dec 2025 23:16:34 +0200 Subject: [PATCH 14/27] i18n: Add Estonian translations (#693) --- i18n/edit.toml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/i18n/edit.toml b/i18n/edit.toml index 7628c4e3352..adedf8ceb3a 100644 --- a/i18n/edit.toml +++ b/i18n/edit.toml @@ -74,6 +74,7 @@ bn = "হ্যাঁ" cs = "Ano" da = "Ja" de = "Ja" +ee = "Jah" el = "Ναι" es = "Sí" fa = "بله" @@ -107,6 +108,7 @@ bn = "না" cs = "Ne" da = "Nej" de = "Nein" +ee = "Ei" el = "Όχι" es = "No" fa = "خیر" @@ -140,6 +142,7 @@ bn = "বাতিল" cs = "Zrušit" da = "Annuller" de = "Abbrechen" +ee = "Tühista" el = "Άκυρο" es = "Cancelar" fa = "لغو" @@ -173,6 +176,7 @@ bn = "সবসময়" cs = "Vždy" da = "Altid" de = "Immer" +ee = "Alati" el = "Πάντα" es = "Siempre" fa = "همیشه" @@ -205,6 +209,7 @@ bn = "ফাইল" cs = "Soubor" da = "Filer" de = "Datei" +ee = "Fail" el = "Αρχείο" es = "Archivo" fa = "پرونده" @@ -237,6 +242,7 @@ bn = "নতুন ফাইল…" cs = "Nový soubor" da = "Ny fil…" de = "Neue Datei" +ee = "Uus fail" el = "Νέο αρχείο" es = "Nuevo archivo" fa = "پروندهٔ نو…" @@ -269,6 +275,7 @@ bn = "ফাইল খুলুন…" cs = "Otevřít soubor…" da = "Åbn fil…" de = "Datei öffnen…" +ee = "Ava fail…" el = "Άνοιγμα αρχείου…" es = "Abrir archivo…" fa = "باز کردن پرونده…" @@ -301,6 +308,7 @@ bn = "সংরক্ষণ" cs = "Uložit" da = "Gem" de = "Speichern" +ee = "Salvesta" el = "Αποθήκευση" es = "Guardar" fa = "ذخیره کردن" @@ -333,6 +341,7 @@ bn = "অন্য নামে সংরক্ষণ…" cs = "Uložit jako…" da = "Gem som…" de = "Speichern unter…" +ee = "Salvesta kui…" el = "Αποθήκευση ως…" es = "Guardar como…" fa = "ذخیره کردن به‌عنوان…" @@ -365,6 +374,7 @@ bn = "এডিটর বন্ধ করুন" cs = "Zavřít editor" da = "Luk editor" de = "Datei schließen" +ee = "Sulge fail" el = "Κλείσιμο επεξεργαστή" es = "Cerrar archivo" fa = "بستن ویراستار" @@ -397,6 +407,7 @@ bn = "প্রস্থান" cs = "Ukončit" da = "Afslut" de = "Beenden" +ee = "Välju" el = "Έξοδος" es = "Salir" fa = "خروج" @@ -427,6 +438,7 @@ en = "Go to Line:Column…" ar = "الانتقال إلى السطر:العمود…" cs = "Přejít na řádek/sloupec…" de = "Gehe zu Zeile:Spalte…" +ee = "Mine reale:veergu…" el = "Μετάβαση σε γραμμή:στήλη…" es = "Ir a línea:columna…" fi = "Siirry kohtaan rivi:sarake" @@ -457,6 +469,7 @@ bn = "সম্পাদনা" cs = "Úpravy" da = "Rediger" de = "Bearbeiten" +ee = "Muuda" el = "Επεξεργασία" es = "Editar" fa = "ویرایش" @@ -489,6 +502,7 @@ bn = "পূর্বাবস্থায় ফেরান" cs = "Zpět" da = "Fortryd" de = "Rückgängig" +ee = "Võta tagasi" el = "Αναίρεση" es = "Deshacer" fa = "واگرد" @@ -521,6 +535,7 @@ bn = "পুনরায় করুন" cs = "Znovu" da = "Gentag" de = "Wiederholen" +ee = "Tee uuesti" el = "Επανάληψη" es = "Rehacer" fa = "از نو" @@ -553,6 +568,7 @@ bn = "কাট" cs = "Vyjmout" da = "Klip" de = "Ausschneiden" +ee = "Lõika" el = "Αποκοπή" es = "Cortar" fa = "برش" @@ -585,6 +601,7 @@ bn = "কপি" cs = "Kopírovat" da = "Kopier" de = "Kopieren" +ee = "Kopeeri" el = "Αντιγραφή" es = "Copiar" fa = "رونوشت" @@ -617,6 +634,7 @@ bn = "পেস্ট" cs = "Vložit" da = "Sæt ind" de = "Einfügen" +ee = "Kleebi" el = "Επικόλληση" es = "Pegar" fa = "چسپاندن" @@ -649,6 +667,7 @@ bn = "অনুসন্ধান" cs = "Najít" da = "Find" de = "Suchen" +ee = "Otsi" el = "Εύρεση" es = "Buscar" fa = "یافتن" @@ -681,6 +700,7 @@ bn = "প্রতিস্থাপন" cs = "Nahradit" da = "Erstat" de = "Ersetzen" +ee = "Asenda" el = "Αντικατάσταση" es = "Reemplazar" fa = "جایگزینی" @@ -711,6 +731,7 @@ en = "Select All" ar = "تحديد الكل" cs = "Vybrat vše" de = "Alles auswählen" +ee = "Vali kõik" el = "Επιλογή όλων" es = "Seleccionar todo" fi = "Valitse kaikki" @@ -740,6 +761,7 @@ bn = "দেখুন" cs = "Zobrazit" da = "Vis" de = "Ansicht" +ee = "Vaade" el = "Προβολή" es = "Ver" fa = "نمایش" @@ -772,6 +794,7 @@ bn = "স্ট্যাটাসবারে ফোকাস করুন" cs = "Zaměřit na stavový řádek" da = "Fokuser statuslinje" de = "Statusleiste fokussieren" +ee = "Fokuseeri staatuseribale" el = "Εστίαση στη γραμμή κατάστασης" es = "Enfocar barra de estado" fa = "کانون به نوار وضعیت" @@ -804,6 +827,7 @@ bn = "শব্দ মোড়ানো" cs = "Zalamování" da = "Tekstombrydning" de = "Zeilenumbruch" +ee = "Murra rida" el = "Αυτόματη επιστροφή σε νέα γραμμή" es = "Ajuste de línea" fa = "سطربندی خطوط" @@ -834,6 +858,7 @@ en = "Go to File…" ar = "الانتقال إلى ملف…" cs = "Výběr dokumentu…" de = "Gehe zu Datei…" +ee = "Mine faili … juurde" es = "Ir a archivo…" fi = "Siirry tiedostoon…" fr = "Aller au fichier…" @@ -861,6 +886,7 @@ bn = "সাহায্য" cs = "Nápověda" da = "Hjælp" de = "Hilfe" +ee = "Abi" el = "Βοήθεια" es = "Ayuda" fa = "کمک" @@ -893,6 +919,7 @@ bn = "সম্পর্কে" cs = "O programu" da = "Om" de = "Über" +ee = "Teave" el = "Σχετικά με" es = "Acerca de" fa = "درباره" @@ -925,6 +952,7 @@ bn = "অসংরক্ষিত পরিবর্তন" cs = "Neuložené změny" da = "Ikke gemte ændringer" de = "Ungespeicherte Änderungen" +ee = "Salvestamata muudatused" el = "Μη αποθηκευμένες αλλαγές" es = "Cambios sin guardar" fa = "تغییرات ذخیره‌نشده" @@ -957,6 +985,7 @@ bn = "আপনি কি আপনার করা পরিবর্তনগ cs = "Chcete uložit provedené změny?" da = "Vil du gemme de ændringer, du har foretaget?" de = "Möchten Sie die vorgenommenen Änderungen speichern?" +ee = "Kas sa soovid tehtud muudatused salvestada?" el = "Θέλετε να αποθηκεύσετε τις αλλαγές που κάνατε;" es = "¿Desea guardar los cambios realizados?" fa = "مایلید تغییرات ذخیره شود؟" @@ -989,6 +1018,7 @@ bn = "সংরক্ষণ" cs = "Uložit" da = "Gem" de = "Speichern" +ee = "Salvesta" el = "Αποθήκευση" es = "Guardar" fa = "ذخیره" @@ -1021,6 +1051,7 @@ bn = "সংরক্ষণ করবেন না" cs = "Neukládat" da = "Gem ikke" de = "Nicht speichern" +ee = "Ära salvesta" el = "Να μην γίνει αποθήκευση" es = "No guardar" fi = "Älä tallenna" @@ -1052,6 +1083,7 @@ bn = "সম্পর্কে" cs = "O programu" da = "Om" de = "Über" +ee = "Teave" el = "Σχετικά με" es = "Acerca de" fa = "درباره" @@ -1084,6 +1116,7 @@ bn = "সংস্করণ: " cs = "Verze: " da = "Version: " de = "Version: " +ee = "Versioon: " el = "Έκδοση: " es = "Versión: " fa = "نسخه: " @@ -1117,6 +1150,7 @@ bn = "আপনার কপি করা টেক্সট টার্মি cs = "Text, který zkopírujete, je sdílen s terminálovou schránkou." da = "Tekst, du kopierer, deles med terminalens udklipsholder." de = "Der kopierte Text wird mit der Terminal-Zwischenablage geteilt." +ee = "Sinu kopeeritud tekst on jagatud terminali lõikelauaga." el = "Το κείμενο που αντιγράφετε κοινοποιείται με το πρόχειρο του τερματικού." es = "El texto que copies se comparte con el portapapeles del terminal." fa = "متن رونوشت شده با تخته یادداشت پایانه به اشتراک گذاشته شد." @@ -1150,6 +1184,7 @@ bn = "আপনি {size} কপি করেছেন যা শেয়ার cs = "Zkopírovali jste {size}, což může trvat dlouho." da = "Du har kopieret {size}, som muligvis tager lang tid at dele." de = "Sie haben {size} kopiert. Das Weitergeben könnte länger dauern." +ee = "Sa kopeerisid {size}, mis võib võtta kaua aega, et jagada." el = "Έχετε αντιγράψει {size}, η κοινοποίηση μπορεί να διαρκέσει πολύ." es = "Copiaste {size}, lo que puede tardar en compartirse." fa = "شما {size} رونوشت کرده‌اید که ممکن است برای اشتراک زمان گیرد." @@ -1183,6 +1218,7 @@ bn = "আপনি কি এটি যাইহোক পাঠাতে চা cs = "Chcete to přesto odeslat?" da = "Vil du sende det alligevel?" de = "Möchten Sie es trotzdem senden?" +ee = "Kas soovid seda ikkagi saata?" el = "Θέλετε να το στείλετε ούτως ή άλλως;" es = "¿Desea enviarlo de todas formas?" fa = "مایلید آن را به هر روی ارسال کنید؟" @@ -1216,6 +1252,7 @@ bn = "আপনার কপি করা টেক্সট শেয়ার ক cs = "Text, který jste zkopírovali, je příliš velký na to, aby mohl být sdílen." da = "Den kopierede tekst er for stor til at blive delt." de = "Der kopierte Text ist zu groß, um geteilt zu werden." +ee = "Sinu kopeeritud tekst on jagamiseks liiga suur." el = "Το κείμενο που αντιγράψατε είναι πολύ μεγάλο για να κοινοποιηθεί." es = "El texto que copiaste es demasiado grande para compartirse." fa = "متن رونوشت شده بززگتر از آن است که به اشتراک گذاشته شود." @@ -1248,6 +1285,7 @@ bn = "সতর্কবার্তা" cs = "Upozornění" da = "Advarsel" de = "Warnung" +ee = "Hoiatus" el = "Προειδοποίηση" es = "Advertencia" fa = "هشدار" @@ -1280,6 +1318,7 @@ bn = "ত্রুটি" cs = "Chyba" da = "Fejl" de = "Fehler" +ee = "Viga" el = "Σφάλμα" es = "Error" fa = "خطا" @@ -1312,6 +1351,7 @@ bn = "এই অপারেশনের জন্য ICU লাইব্রে cs = "Tato operace vyžaduje knihovnu ICU" da = "Denne operation kræver ICU-biblioteket" de = "Diese Operation erfordert die ICU-Bibliothek" +ee = "See operatsioon nõuab ICU teeki" el = "Αυτή η λειτουργία απαιτεί τη βιβλιοθήκη ICU" es = "Esta operación requiere la biblioteca ICU" fa = "این عمل به کتابخانهٔ ICU نیاز دارد" @@ -1345,6 +1385,7 @@ bn = "অনুসন্ধান:" cs = "Najít:" da = "Find:" de = "Suchen:" +ee = "Otsi:" el = "Εύρεση:" es = "Buscar:" fa = "جستجو:" @@ -1378,6 +1419,7 @@ bn = "প্রতিস্থাপন:" cs = "Nahradit:" da = "Erstat:" de = "Ersetzen:" +ee = "Asenda:" el = "Αντικατάσταση:" es = "Reemplazar:" fa = "جایگزین:" @@ -1411,6 +1453,7 @@ bn = "কেস মিলান" cs = "Velká/malá písmena" da = "Store/små" de = "Groß/Klein" +ee = "Jälgi suur- ja väiketähti" el = "Μεγάλα/μικρά" es = "May/Min" fa = "تطبیق حالت حروف" @@ -1444,6 +1487,7 @@ bn = "সম্পূর্ণ শব্দ" cs = "Celé slovo" da = "Hele ordet" de = "Ganzes Wort" +ee = "Kogu sõna" el = "Ολόκληρη λέξη" es = "Palabra" fa = "تمام کلمه" @@ -1477,6 +1521,7 @@ bn = "Regex ব্যবহার করুন" cs = "Použít regulární výraz" da = "RegEx" de = "RegEx" +ee = "RegEx" el = "RegEx" es = "RegEx" fa = "عبارت باقاعده" @@ -1510,6 +1555,7 @@ bn = "সব প্রতিস্থাপন করুন" cs = "Nahradit vše" da = "Erstat alle" de = "Alle ersetzen" +ee = "Asenda kõik" el = "Αντικατάσταση όλων" es = "Reemplazar todo" fa = "جایگزینی همه" @@ -1543,6 +1589,7 @@ bn = "বন্ধ করুন" cs = "Zavřít" da = "Luk" de = "Schließen" +ee = "Sulge" el = "Κλείσιμο" es = "Cerrar" fa = "بستن" @@ -1575,6 +1622,7 @@ bn = "এনকোডিং সহ পুনরায় খুলুন" cs = "Znovu otevřít s kódováním…" da = "Genåbn med enkodning" de = "Mit Kodierung erneut öffnen…" +ee = "Ava uuesti kodeeringus…" el = "Επαναφορά με κωδικοποίηση…" es = "Reabrir con codificación…" fa = "با کدبندی مجدد باز کن" @@ -1607,6 +1655,7 @@ bn = "এনকোডিং-এ রূপান্তর করুন" cs = "Převést na kódování…" da = "Konverter til enkodning" de = "In Kodierung konvertieren…" +ee = "Teisenda kodeeringusse…" el = "Μετατροπή σε κωδικοποίηση…" es = "Convertir a otra codificación…" fa = "تبدیل به کدبندی" @@ -1639,6 +1688,7 @@ bn = "ট্যাব" cs = "Tabulátory" da = "Tabs" de = "Tabs" +ee = "Tab" el = "Tabs" es = "Tabulaciones" fa = "برگه‌ها" @@ -1671,6 +1721,7 @@ bn = "স্পেস" cs = "Mezery" da = "Mellemrum" de = "Leerzeichen" +ee = "Tühikud" el = "Κενά" es = "Espacios" fa = "فاصله‌ها" @@ -1703,6 +1754,7 @@ bn = "ফোল্ডার:" cs = "Složka:" da = "Mappe:" de = "Ordner:" +ee = "Kaust:" el = "Φάκελος:" es = "Carpeta:" fa = "پوشه:" @@ -1735,6 +1787,7 @@ bn = "ফাইলের নাম:" cs = "Název souboru:" da = "Filnavn:" de = "Dateiname:" +ee = "Faili nimi:" el = "Όνομα αρχείου:" es = "Nombre de archivo:" fa = "نام پرونده:" @@ -1767,6 +1820,7 @@ bn = "অন্য নামে সংরক্ষণ নিশ্চিত ক cs = "Potvrdit Uložit jako" da = "Bekræft gem som" de = "Speichern unter bestätigen" +ee = "Kinnita salvestamine kui" el = "Επιβεβαίωση αποθήκευσης ως…" es = "Confirmar Guardar como" fa = "پذیرفتن ذخیره به‌عنوان" @@ -1799,6 +1853,7 @@ bn = "ফাইল ইতিমধ্যে বিদ্যমান। আপ cs = "Soubor již existuje. Chcete jej přepsat?" da = "Filen eksisterer allerede. Vil du overskrive den?" de = "Datei existiert bereits. Möchten Sie sie überschreiben?" +ee = "Fail on juba olemas. Kas sa soovid selle üle kirjutada?" el = "Το αρχείο υπάρχει ήδη. Θέλετε να το αντικαταστήσετε;" es = "El archivo ya existe. ¿Desea sobrescribirlo?" fa = "پرونده در حال حاضر وجود دارد. مایلید آن را جایگزین کنید؟" From 033a674223cbdf1e7f14f358af2681c6e998a4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?VenusGirl=E2=9D=A4?= Date: Tue, 2 Dec 2025 06:17:14 +0900 Subject: [PATCH 15/27] Improve Korean translations (#663) --- i18n/edit.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/i18n/edit.toml b/i18n/edit.toml index adedf8ceb3a..701d0e95ef2 100644 --- a/i18n/edit.toml +++ b/i18n/edit.toml @@ -118,7 +118,7 @@ hu = "Nem" is = "Nei" it = "No" ja = "いいえ" -ko = "아니오" +ko = "아니요" nl = "Nee" pl = "Nie" pt_br = "Não" @@ -447,7 +447,7 @@ hu = "Ugrás ide sor:oszlop…" is = "Fara í línu/dálk…" it = "Vai a riga:colonna…" ja = "行:列へ移動…" -ko = "행:열로 이동…" +ko = "줄:열로 이동…" nl = "Ga naar Regel:Kolom…" pl = "Idź do wiersza:kolumny…" pt_br = "Ir para a Linha:Coluna…" @@ -1160,7 +1160,7 @@ hu = "A kimásolt szöveg megosztásra kerül a terminál vágólapjával." is = "Textanum sem þú afritar er deilt með skipanalínu-klippiborðinu." it = "Il testo copiato viene condiviso con gli appunti del terminale." ja = "コピーしたテキストはターミナルのクリップボードと共有されます。" -ko = "복사한 텍스트가 터미널 클립보드와 공유됩니다." +ko = "복사한 텍스트는 터미널 클립보드와 공유됩니다." nl = "Tekst die u kopieert wordt gedeeld met de terminal." pl = "Skopiowany tekst jest przekazywany do schowka konsoli." pt_br = "O texto copiado é compartilhado com a área de transferência do terminal." @@ -1194,7 +1194,7 @@ hu = "{size} méretű adatot másoltál, amelynek megosztása hosszabb ideig tar is = "Þú afritaðir {size}, sem gæti tekið langan tíma að deila." it = "Hai copiato {size}, potrebbe richiedere molto tempo per condividerlo." ja = "{size} をコピーしました。共有に時間がかかる可能性があります。" -ko = "{size}를 복사했습니다. 공유하는 데 시간이 오래 걸릴 수 있습니다." +ko = "{size}를 복사했으며, 공유하는 데 시간이 오래 걸릴 수 있습니다." nl = "U heeft {size} gekopieerd, wat misschien lang kan duren om te delen." pl = "Skopiowano {size}, co może zająć dużo czasu." pt_br = "Você copiou {size}, o que pode demorar para compartilhar." @@ -1228,7 +1228,7 @@ hu = "El szeretnéd küldeni ennek ellenére?" is = "Viltu samt senda það?" it = "Vuoi inviarlo comunque?" ja = "それでも送信しますか?" -ko = "그래도 전송하시겠습니까?" +ko = "그래도 보내시겠습니까?" nl = "Wilt u het toch verzenden?" pl = "Czy nadal chcesz to przesłać?" pt_br = "Deseja enviar mesmo assim?" From f467ba1e787cb24d4017e4b5f99a42bfbedd38ba Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 1 Dec 2025 22:24:27 +0100 Subject: [PATCH 16/27] Fix multiple issues found under Linux (#706) Fixed: * `sighandler_t` warning in nightly * cppdbg + gdb pretty printing * UTF8 parsing for SGR mouse coords --- .vscode/launch.json | 10 ++++++---- crates/edit/src/input.rs | 32 ++++++++++++++++++++------------ crates/edit/src/sys/unix.rs | 2 +- crates/edit/src/vt.rs | 18 +++++++----------- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d11a903eace..1c334dbae21 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,18 +10,20 @@ "program": "${workspaceFolder}/target/debug/edit", "cwd": "${workspaceFolder}", "args": [ - "${workspaceFolder}/src/bin/edit/main.rs" + "${workspaceFolder}/crates/edit/src/bin/edit/main.rs" ], }, { - "name": "Launch Debug (GDB/LLDB)", + "name": "Launch Debug (GDB)", "preLaunchTask": "rust: cargo build", "type": "cppdbg", "request": "launch", + "miDebuggerPath": "rust-gdb", + "externalConsole": true, "program": "${workspaceFolder}/target/debug/edit", "cwd": "${workspaceFolder}", "args": [ - "${workspaceFolder}/src/bin/edit/main.rs" + "${workspaceFolder}/crates/edit/src/bin/edit/main.rs" ], }, { @@ -32,7 +34,7 @@ "program": "${workspaceFolder}/target/debug/edit", "cwd": "${workspaceFolder}", "args": [ - "${workspaceFolder}/src/bin/edit/main.rs" + "${workspaceFolder}/crates/edit/src/bin/edit/main.rs" ], } ] diff --git a/crates/edit/src/input.rs b/crates/edit/src/input.rs index 9e6c12a2a9c..bff72fc9b8a 100644 --- a/crates/edit/src/input.rs +++ b/crates/edit/src/input.rs @@ -268,7 +268,7 @@ pub struct Parser { bracketed_paste: bool, bracketed_paste_buf: Vec, x10_mouse_want: bool, - x10_mouse_buf: [u8; 3], + x10_mouse_buf: [char; 3], x10_mouse_len: usize, } @@ -281,7 +281,7 @@ impl Parser { bracketed_paste: false, bracketed_paste_buf: Vec::new(), x10_mouse_want: false, - x10_mouse_buf: [0; 3], + x10_mouse_buf: ['\0'; 3], x10_mouse_len: 0, } } @@ -535,27 +535,35 @@ impl<'input> Stream<'_, '_, 'input> { /// This is so puzzling to me. The existence of this function makes me unhappy. #[cold] fn parse_x10_mouse_coordinates(&mut self) -> Option> { - self.parser.x10_mouse_len += - self.stream.read(&mut self.parser.x10_mouse_buf[self.parser.x10_mouse_len..]); + while self.parser.x10_mouse_len < 3 && !self.stream.done() { + self.parser.x10_mouse_buf[self.parser.x10_mouse_len] = self.stream.next_char(); + self.parser.x10_mouse_len += 1; + } if self.parser.x10_mouse_len < 3 { return None; } - let button = self.parser.x10_mouse_buf[0] & 0b11; - let modifier = self.parser.x10_mouse_buf[0] & 0b11100; + let b = self.parser.x10_mouse_buf[0] as u32; let x = self.parser.x10_mouse_buf[1] as CoordType - 0x21; let y = self.parser.x10_mouse_buf[2] as CoordType - 0x21; - let action = match button { + let action = match b & 0b11 { 0 => InputMouseState::Left, 1 => InputMouseState::Middle, 2 => InputMouseState::Right, _ => InputMouseState::None, }; - let modifiers = match modifier { - 4 => kbmod::SHIFT, - 8 => kbmod::ALT, - 16 => kbmod::CTRL, - _ => kbmod::NONE, + let modifiers = { + let mut m = kbmod::NONE; + if (b & 0b00100) != 0 { + m |= kbmod::SHIFT; + } + if (b & 0b01000) != 0 { + m |= kbmod::ALT; + } + if (b & 0b10000) != 0 { + m |= kbmod::CTRL; + } + m }; self.parser.x10_mouse_want = false; diff --git a/crates/edit/src/sys/unix.rs b/crates/edit/src/sys/unix.rs index e2078128e5f..71b9edc7d74 100644 --- a/crates/edit/src/sys/unix.rs +++ b/crates/edit/src/sys/unix.rs @@ -63,7 +63,7 @@ pub fn switch_modes() -> apperr::Result<()> { // Set STATE.inject_resize to true whenever we get a SIGWINCH. let mut sigwinch_action: libc::sigaction = mem::zeroed(); - sigwinch_action.sa_sigaction = sigwinch_handler as libc::sighandler_t; + sigwinch_action.sa_sigaction = sigwinch_handler as *const () as libc::sighandler_t; check_int_return(libc::sigaction(libc::SIGWINCH, &sigwinch_action, null_mut()))?; // Get the original terminal modes so we can disable raw mode on exit. diff --git a/crates/edit/src/vt.rs b/crates/edit/src/vt.rs index ffa3a1648a9..caf31633766 100644 --- a/crates/edit/src/vt.rs +++ b/crates/edit/src/vt.rs @@ -128,17 +128,13 @@ impl<'input> Stream<'_, 'input> { self.off } - /// Reads and consumes raw bytes from the input. - pub fn read(&mut self, dst: &mut [u8]) -> usize { - let bytes = self.input.as_bytes(); - let off = self.off.min(bytes.len()); - let len = dst.len().min(bytes.len() - off); - dst[..len].copy_from_slice(&bytes[off..off + len]); - self.off += len; - len + /// Returns `true` if the input has been fully parsed. + pub fn done(&self) -> bool { + self.off >= self.input.len() } - fn decode_next(&mut self) -> char { + /// Decodes and consumes the next UTF-8 character from the input. + pub fn next_char(&mut self) -> char { let mut iter = Utf8Chars::new(self.input.as_bytes(), self.off); let c = iter.next().unwrap_or('\0'); self.off = iter.offset(); @@ -190,7 +186,7 @@ impl<'input> Stream<'_, 'input> { return Some(Token::Text(&input[beg..self.off])); } }, - State::Esc => match self.decode_next() { + State::Esc => match self.next_char() { '[' => { self.parser.state = State::Csi; self.parser.csi.private_byte = '\0'; @@ -216,7 +212,7 @@ impl<'input> Stream<'_, 'input> { }, State::Ss3 => { self.parser.state = State::Ground; - return Some(Token::SS3(self.decode_next())); + return Some(Token::SS3(self.next_char())); } State::Csi => { loop { From b998adbd2582a670049c7359812a35d0bd6dd59f Mon Sep 17 00:00:00 2001 From: viyic Date: Tue, 2 Dec 2025 04:33:40 +0700 Subject: [PATCH 17/27] Add Indonesian translation (#629) --- i18n/edit.toml | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/i18n/edit.toml b/i18n/edit.toml index 701d0e95ef2..01248b1577a 100644 --- a/i18n/edit.toml +++ b/i18n/edit.toml @@ -49,6 +49,7 @@ fa = "پذیرفتن" fi = "OK" fr = "OK" hu = "OK" +id = "OK" is = "Í lagi" it = "OK" ja = "OK" @@ -81,6 +82,7 @@ fa = "بله" fi = "Kyllä" fr = "Oui" hu = "Igen" +id = "Ya" is = "Já" it = "Sì" ja = "はい" @@ -115,6 +117,7 @@ fa = "خیر" fi = "Ei" fr = "Non" hu = "Nem" +id = "Tidak" is = "Nei" it = "No" ja = "いいえ" @@ -149,6 +152,7 @@ fa = "لغو" fi = "Peruuta" fr = "Annuler" hu = "Mégse" +id = "Batal" is = "Hætta við" it = "Annulla" ja = "キャンセル" @@ -183,6 +187,7 @@ fa = "همیشه" fi = "Aina" fr = "Toujours" hu = "Mindig" +id = "Selalu" is = "Alltaf" it = "Sempre" ja = "常に" @@ -249,6 +254,7 @@ fa = "پروندهٔ نو…" fi = "Uusi tiedosto" fr = "Nouveau fichier" hu = "Új fájl" +id = "File Baru" is = "Ný skrá…" it = "Nuovo file" ja = "新規ファイル" @@ -282,6 +288,7 @@ fa = "باز کردن پرونده…" fi = "Avaa tiedosto…" fr = "Ouvrir un fichier…" hu = "Fájl megnyitása…" +id = "Buka File…" is = "Opna skrá…" it = "Apri file…" ja = "ファイルを開く…" @@ -315,6 +322,7 @@ fa = "ذخیره کردن" fi = "Tallenna" fr = "Enregistrer" hu = "Mentés" +id = "Simpan" is = "Vista" it = "Salva" ja = "保存" @@ -348,6 +356,7 @@ fa = "ذخیره کردن به‌عنوان…" fi = "Tallenna nimellä…" fr = "Enregistrer sous…" hu = "Mentés másként…" +id = "Simpan Sebagai…" is = "Vista sem…" it = "Salva come…" ja = "名前を付けて保存…" @@ -381,6 +390,7 @@ fa = "بستن ویراستار" fi = "Sulje tiedosto" fr = "Fermer le fichier" hu = "Fájl bezárása" +id = "Tutup File" is = "Loka ritli" it = "Chiudi file" ja = "ファイルを閉じる" @@ -414,6 +424,7 @@ fa = "خروج" fi = "Lopeta" fr = "Quitter" hu = "Kilépés" +id = "Keluar" is = "Hætta" it = "Esci" ja = "終了" @@ -444,6 +455,7 @@ es = "Ir a línea:columna…" fi = "Siirry kohtaan rivi:sarake" fr = "Aller à la ligne:colonne…" hu = "Ugrás ide sor:oszlop…" +id = "Pergi ke Baris:Kolom…" is = "Fara í línu/dálk…" it = "Vai a riga:colonna…" ja = "行:列へ移動…" @@ -509,6 +521,7 @@ fa = "واگرد" fi = "Kumoa" fr = "Annuler" hu = "Visszavonás" +id = "Batalkan" is = "Afturkalla" it = "Annulla" ja = "元に戻す" @@ -542,6 +555,7 @@ fa = "از نو" fi = "Tee uudelleen" fr = "Rétablir" hu = "Újra" +id = "Ulangi" is = "Endurtaka" it = "Ripeti" ja = "やり直し" @@ -575,6 +589,7 @@ fa = "برش" fi = "Leikkaa" fr = "Couper" hu = "Kivágás" +id = "Potong" is = "Klippa" it = "Taglia" ja = "切り取り" @@ -608,6 +623,7 @@ fa = "رونوشت" fi = "Kopioi" fr = "Copier" hu = "Másolás" +id = "Salin" is = "Afrita" it = "Copia" ja = "コピー" @@ -641,6 +657,7 @@ fa = "چسپاندن" fi = "Liitä" fr = "Coller" hu = "Beillesztés" +id = "Tempel" is = "Líma" it = "Incolla" ja = "貼り付け" @@ -674,6 +691,7 @@ fa = "یافتن" fi = "Etsi" fr = "Rechercher" hu = "Keresés" +id = "Temukan" is = "Finna" it = "Trova" ja = "検索" @@ -707,6 +725,7 @@ fa = "جایگزینی" fi = "Korvaa" fr = "Remplacer" hu = "Csere" +id = "Ganti" is = "Skipta út" it = "Sostituisci" ja = "置換" @@ -737,6 +756,7 @@ es = "Seleccionar todo" fi = "Valitse kaikki" fr = "Tout sélectionner" hu = "Az összes kijelölése" +id = "Pilih Semua" is = "Velja allt" it = "Seleziona tutto" ja = "すべて選択" @@ -768,6 +788,7 @@ fa = "نمایش" fi = "Näytä" fr = "Affichage" hu = "Nézet" +id = "Tampilan" is = "Sýn" it = "Visualizza" ja = "表示" @@ -801,6 +822,7 @@ fa = "کانون به نوار وضعیت" fi = "Fokusoi tilapalkki" fr = "Activer la barre d’état" hu = "Fókusz a státuszsorra" +id = "Fokus pada Bar Status" is = "Fókus á stöðuslá" it = "Attiva barra di stato" ja = "ステータスバーにフォーカス" @@ -834,6 +856,7 @@ fa = "سطربندی خطوط" fi = "Automaattinen rivitys" fr = "Retour automatique à la ligne" hu = "Sortörés" +id = "Bungkus Kata" is = "Orðskipting" it = "A capo automatico" ja = "折り返し" @@ -863,6 +886,7 @@ es = "Ir a archivo…" fi = "Siirry tiedostoon…" fr = "Aller au fichier…" hu = "Ugrás fájlra…" +id = "Pergi ke File…" is = "Fara í skrá…" it = "Vai al file…" ja = "ファイルへ移動…" @@ -893,6 +917,7 @@ fa = "کمک" fi = "Ohje" fr = "Aide" hu = "Súgó" +id = "Bantuan" is = "Hjálp" it = "Aiuto" ja = "ヘルプ" @@ -926,6 +951,7 @@ fa = "درباره" fi = "Tietoja" fr = "À propos" hu = "Névjegy" +id = "Tentang" is = "Um" it = "Informazioni" ja = "情報" @@ -959,6 +985,7 @@ fa = "تغییرات ذخیره‌نشده" fi = "Tallentamattomia muutoksia" fr = "Modifications non enregistrées" hu = "Mentetlen változtatások" +id = "Perubahan yang Belum Disimpan" is = "Óvistaðar breytingar" it = "Modifiche non salvate" ja = "未保存の変更" @@ -992,6 +1019,7 @@ fa = "مایلید تغییرات ذخیره شود؟" fi = "Haluatko tallentaa tekemäsi muutokset?" fr = "Voulez-vous enregistrer les modifications apportées ?" hu = "Szeretnéd menteni a módosításokat?" +id = "Apakah Anda ingin menyimpan perubahan yang Anda buat?" is = "Viltu vista breytingarnar sem þú gerðir?" it = "Vuoi salvare le modifiche apportate?" ja = "変更内容を保存しますか?" @@ -1025,6 +1053,7 @@ fa = "ذخیره" fi = "Tallenna" fr = "Enregistrer" hu = "Mentés" +id = "Simpan" is = "Vista" it = "Salva" ja = "保存する" @@ -1057,6 +1086,7 @@ es = "No guardar" fi = "Älä tallenna" fr = "Ne pas enregistrer" hu = "Ne mentse" +id = "Jangan Simpan" is = "Ekki vista" it = "Non salvare" ja = "保存しない" @@ -1090,6 +1120,7 @@ fa = "درباره" fi = "Tietoja" fr = "À propos" hu = "Névjegy" +id = "Tentang" is = "Um" it = "Informazioni" ja = "情報" @@ -1123,6 +1154,7 @@ fa = "نسخه: " fi = "Versio: " fr = "Version : " hu = "Verzió: " +id = "Versi: " is = "Útgáfa: " it = "Versione: " ja = "バージョン: " @@ -1157,6 +1189,7 @@ fa = "متن رونوشت شده با تخته یادداشت پایانه به fi = "Kopioitu teksti jaetaan terminaalin leikepöydän kanssa." fr = "Le texte que vous copiez est partagé avec le presse-papiers du terminal." hu = "A kimásolt szöveg megosztásra kerül a terminál vágólapjával." +id = "Teks yang Anda salin dibagikan dengan clipboard terminal." is = "Textanum sem þú afritar er deilt með skipanalínu-klippiborðinu." it = "Il testo copiato viene condiviso con gli appunti del terminale." ja = "コピーしたテキストはターミナルのクリップボードと共有されます。" @@ -1191,6 +1224,7 @@ fa = "شما {size} رونوشت کرده‌اید که ممکن است برای fi = "Kopioit {size} jonka jakamisessa voi kestää kauan." fr = "Vous avez copié {size}, ce qui peut être long à partager." hu = "{size} méretű adatot másoltál, amelynek megosztása hosszabb ideig tarthat." +id = "Anda menyalin {size} yang mungkin butuh waktu lama untuk dibagikan." is = "Þú afritaðir {size}, sem gæti tekið langan tíma að deila." it = "Hai copiato {size}, potrebbe richiedere molto tempo per condividerlo." ja = "{size} をコピーしました。共有に時間がかかる可能性があります。" @@ -1225,6 +1259,7 @@ fa = "مایلید آن را به هر روی ارسال کنید؟" fi = "Haluatko lähettää sen silti?" fr = "Voulez-vous quand même l’envoyer ?" hu = "El szeretnéd küldeni ennek ellenére?" +id = "Apakah Anda tetap ingin mengirimkannya?" is = "Viltu samt senda það?" it = "Vuoi inviarlo comunque?" ja = "それでも送信しますか?" @@ -1259,6 +1294,7 @@ fa = "متن رونوشت شده بززگتر از آن است که به اشت fi = "Kopioimasi on teksti on liian suuri jaettavaksi." fr = "Le texte que vous avez copié est trop volumineux pour être partagé." hu = "A kimásolt szöveg túl nagy a megosztáshoz." +id = "Teks yang Anda salin terlalu besar untuk dibagikan." is = "Textinn sem þú afritaðir er of stór til að deila." it = "Il testo copiato è troppo grande per essere condiviso." ja = "コピーしたテキストは大きすぎて共有できません。" @@ -1292,6 +1328,7 @@ fa = "هشدار" fi = "Varoitus" fr = "Avertissement" hu = "Figyelmeztetés" +id = "Peringatan" is = "Aðvörun" it = "Avviso" ja = "警告" @@ -1325,6 +1362,7 @@ fa = "خطا" fi = "Virhe" fr = "Erreur" hu = "Hiba" +id = "Galat" is = "Villa" it = "Errore" ja = "エラー" @@ -1358,6 +1396,7 @@ fa = "این عمل به کتابخانهٔ ICU نیاز دارد" fi = "Tämä toiminto vaatii ICU-kirjaston" fr = "Cette opération nécessite la bibliothèque ICU" hu = "Ezen művelethez szükséges az ICU könyvtár" +id = "Operasi ini memerlukan pustaka ICU" is = "Þessi aðgerð krefst að ICU safnið sé til staðar" it = "Questa operazione richiede la libreria ICU" ja = "この操作にはICUライブラリが必要です" @@ -1392,6 +1431,7 @@ fa = "جستجو:" fi = "Etsi:" fr = "Rechercher :" hu = "Keresés:" +id = "Temukan:" is = "Finna:" it = "Trova:" ja = "検索:" @@ -1426,6 +1466,7 @@ fa = "جایگزین:" fi = "Korvaa:" fr = "Remplacer :" hu = "Csere:" +id = "Ganti dengan:" is = "Skipta út með:" it = "Sostituire:" ja = "置換:" @@ -1460,6 +1501,7 @@ fa = "تطبیق حالت حروف" fi = "Ota huomioon kirjainkoko" fr = "Resp. la casse" hu = "Kisbetű/nagybetű" +id = "Sesuaikan Jenis Huruf" is = "Greina há/lágstafi" it = "Maius/minus" ja = "大/小文字を区別" @@ -1494,6 +1536,7 @@ fa = "تمام کلمه" fi = "Koko sana" fr = "Mot entier" hu = "Teljes szó" +id = "Seluruh Kata" is = "Heil orð" it = "Parola" ja = "単語全体" @@ -1528,6 +1571,7 @@ fa = "عبارت باقاعده" fi = "Regex" fr = "RegEx" hu = "RegEx" +id = "Gunakan Regex" is = "Reglulegar segðir" it = "RegEx" ja = "正規表現" @@ -1562,6 +1606,7 @@ fa = "جایگزینی همه" fi = "Korvaa kaikki" fr = "Remplacer tout" hu = "Az összes cseréje" +id = "Ganti Semua" is = "Skipta öllu út" it = "Sostituisci tutto" ja = "すべて置換" @@ -1596,6 +1641,7 @@ fa = "بستن" fi = "Sulje" fr = "Fermer" hu = "Bezárás" +id = "Tutup" is = "Loka" it = "Chiudi" ja = "閉じる" @@ -1629,6 +1675,7 @@ fa = "با کدبندی مجدد باز کن" fi = "Avaa uudelleen koodauksella…" fr = "Rouvrir avec un encodage différent…" hu = "Újranyitás más kódolással…" +id = "Buka kembali dengan pengkodean…" is = "Enduropna með stafakóðun" it = "Riapri con codifica…" ja = "指定エンコーディングで再度開く…" @@ -1662,6 +1709,7 @@ fa = "تبدیل به کدبندی" fi = "Muunna koodaukselle…" fr = "Convertir vers l’encodage…" hu = "Átkonvertálás más karakterkódolásra…" +id = "Konversi ke pengkodean…" is = "Umbreyta í stafakóðun" it = "Converti in codifica…" ja = "エンコーディングを変換…" @@ -1695,6 +1743,7 @@ fa = "برگه‌ها" fi = "Sarkaimet" fr = "Tabulations" hu = "Tabulátorok" +id = "Tab" is = "Bilstafir" it = "Tabulazioni" ja = "タブ" @@ -1728,6 +1777,7 @@ fa = "فاصله‌ها" fi = "Välilyönnit" fr = "Espaces" hu = "Szóközök" +id = "Spasi" is = "Bil" it = "Spazi" ja = "スペース" @@ -1761,6 +1811,7 @@ fa = "پوشه:" fi = "Kansio:" fr = "Dossier :" hu = "Mappa:" +id = "Folder:" is = "Mappa:" it = "Cartella:" ja = "フォルダ:" @@ -1794,6 +1845,7 @@ fa = "نام پرونده:" fi = "Tiedostonimi:" fr = "Nom du fichier :" hu = "Fájlnév:" +id = "Nama file:" is = "Skráarnafn:" it = "Nome del file:" ja = "ファイル名:" @@ -1827,6 +1879,7 @@ fa = "پذیرفتن ذخیره به‌عنوان" fi = "Vahvista tallentaminen nimellä" fr = "Confirmer Enregistrer sous" hu = "Mentés másként megerősítése" +id = "Konfirmasi Simpan Sebagai" is = "Staðfestu vista sem" it = "Conferma Salva con nome" ja = "名前を付けて保存の確認" @@ -1860,6 +1913,7 @@ fa = "پرونده در حال حاضر وجود دارد. مایلید آن ر fi = "Tiedosto on jo olemassa. Haluatko ylikirjoittaa sen?" fr = "Le fichier existe déjà. Voulez-vous l’écraser ?" hu = "A fájl már létezik. Felül szeretnéd írni?" +id = "File sudah ada. Ingin menggantikannya?" is = "Skráin er þegar til. Viltu yfirskrifa hana?" it = "Il file esiste già. Vuoi sovrascriverlo?" ja = "ファイルは既に存在します。上書きしますか?" From 6c5d33cd91b30cbd5afc9d66bcad5aad6c729a5e Mon Sep 17 00:00:00 2001 From: Stefan Date: Mon, 5 Jan 2026 15:12:26 +0100 Subject: [PATCH 18/27] Update man page for edit command to version 1.2.1 (#727) Alt does not work in macOS. --- assets/manpage/edit.1 | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/assets/manpage/edit.1 b/assets/manpage/edit.1 index e14bc1bf53e..facbffb7a9d 100644 --- a/assets/manpage/edit.1 +++ b/assets/manpage/edit.1 @@ -1,4 +1,4 @@ -.TH EDIT 1 "version 1.0" "May 2025" +.TH EDIT 1 "version 1.2.1" "December 2025" .SH NAME edit \- a simple text editor .SH SYNOPSIS @@ -6,7 +6,7 @@ edit \- a simple text editor .SH DESCRIPTION edit is a simple text editor inspired by MS-DOS edit. .SH EDITING -Edit is an interactive mode-less editor. Use Alt-F to access the menus. +Edit is an interactive mode-less editor. Use F10 to access the menus. .SH ARGUMENTS .TP \fIFILE[:LINE[:COLUMN]]\fP @@ -19,8 +19,9 @@ Print the help message. \fB\-v\fP, \fB\-\-version\fP Print the version number. .SH COPYRIGHT -Copyright (c) Microsoft Corporation. +Copyright \(co Microsoft Corporation. .br Licensed under the MIT License. .SH SEE ALSO -https://github.com/microsoft/edit +.UR https://github.com/microsoft/edit +.UE From d0a63d18f5b364a66ce339a381b89add0fbb1018 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 5 Jan 2026 17:50:36 +0100 Subject: [PATCH 19/27] Fix colors with Terminal.app's Clear Dark theme (#728) --- .vscode/launch.json | 23 ++++++++++++++++++----- crates/edit/Cargo.toml | 1 + crates/edit/src/bin/edit/main.rs | 6 +++--- crates/edit/src/framebuffer.rs | 22 ++++++++++++++++------ crates/edit/src/helpers.rs | 3 +++ crates/edit/src/sys/unix.rs | 4 ++-- crates/stdext/src/arena/string.rs | 19 +++++++++++++++++++ 7 files changed, 62 insertions(+), 16 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1c334dbae21..7142960c4f9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch Debug (Windows)", + "name": "Launch edit (Windows)", "preLaunchTask": "rust: cargo build", "type": "cppvsdbg", "request": "launch", @@ -14,7 +14,7 @@ ], }, { - "name": "Launch Debug (GDB)", + "name": "Launch edit (GDB, Linux)", "preLaunchTask": "rust: cargo build", "type": "cppdbg", "request": "launch", @@ -27,15 +27,28 @@ ], }, { - "name": "Launch Debug (LLDB)", + // NOTE for macOS: In order for this task to work you have to: + // 1. Run the "Fix externalConsole on macOS" task once + // 2. Add the following to your VS Code settings: + // "lldb-dap.environment": { + // "LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY": "YES" + // } + "name": "Launch edit (lldb-dap, macOS)", "preLaunchTask": "rust: cargo build", - "type": "lldb", + "type": "lldb-dap", "request": "launch", "program": "${workspaceFolder}/target/debug/edit", "cwd": "${workspaceFolder}", "args": [ "${workspaceFolder}/crates/edit/src/bin/edit/main.rs" ], - } + }, + { + // This is a workaround for https://github.com/microsoft/vscode-cpptools/issues/5079 + "name": "Fix externalConsole on macOS", + "type": "node-terminal", + "request": "launch", + "command": "osascript -e 'tell application \"Terminal\"\ndo script \"echo hello\"\nend tell'" + }, ] } diff --git a/crates/edit/Cargo.toml b/crates/edit/Cargo.toml index 90c47b6c882..f96ceb7d531 100644 --- a/crates/edit/Cargo.toml +++ b/crates/edit/Cargo.toml @@ -15,6 +15,7 @@ name = "lib" harness = false [features] +# Display editor latency in the top-right corner debug-latency = [] [dependencies] diff --git a/crates/edit/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs index b374e0ade4c..7e02ec82b41 100644 --- a/crates/edit/src/bin/edit/main.rs +++ b/crates/edit/src/bin/edit/main.rs @@ -12,8 +12,6 @@ mod localization; mod state; use std::borrow::Cow; -#[cfg(feature = "debug-latency")] -use std::fmt::Write; use std::path::{Path, PathBuf}; use std::time::Duration; use std::{env, process}; @@ -23,7 +21,7 @@ use draw_filepicker::*; use draw_menubar::*; use draw_statusbar::*; use edit::framebuffer::{self, IndexedColor}; -use edit::helpers::{CoordType, KIBI, MEBI, MetricFormatter, Rect, Size}; +use edit::helpers::*; use edit::input::{self, kbmod, vk}; use edit::oklab::StraightRgba; use edit::tui::*; @@ -178,6 +176,8 @@ fn run() -> apperr::Result<()> { #[cfg(feature = "debug-latency")] { + use std::fmt::Write as _; + // Print the number of passes and latency in the top right corner. let time_end = std::time::Instant::now(); let status = time_end - time_beg; diff --git a/crates/edit/src/framebuffer.rs b/crates/edit/src/framebuffer.rs index b02f867b95f..f2225e15edd 100644 --- a/crates/edit/src/framebuffer.rs +++ b/crates/edit/src/framebuffer.rs @@ -107,6 +107,8 @@ pub struct Framebuffer { /// of the palette as [dark, light], unless the palette is recognized /// as a light them, in which case it swaps them. auto_colors: [StraightRgba; 2], + /// Above this lightness value, we consider a color to be "light". + auto_color_threshold: f32, /// A cache table for previously contrasted colors. /// See: contrast_colors: [Cell<(StraightRgba, StraightRgba)>; CACHE_TABLE_SIZE], @@ -125,6 +127,7 @@ impl Framebuffer { DEFAULT_THEME[IndexedColor::Black as usize], DEFAULT_THEME[IndexedColor::BrightWhite as usize], ], + auto_color_threshold: 0.5, contrast_colors: [const { Cell::new((StraightRgba::zero(), StraightRgba::zero())) }; CACHE_TABLE_SIZE], background_fill: DEFAULT_THEME[IndexedColor::Background as usize], @@ -145,7 +148,17 @@ impl Framebuffer { self.indexed_colors[IndexedColor::Black as usize], self.indexed_colors[IndexedColor::BrightWhite as usize], ]; - if !Self::is_dark(self.auto_colors[0]) { + + // It's not guaranteed that Black is actually dark and BrightWhite light (vice versa for a light theme). + // Such is the case with macOS 26's "Clear Dark" theme (and probably a lot other themes). + // Its black is #35424C (l=0.3716; oof!) and bright white is #E5EFF5 (l=0.9464). + // If we have a color such as #43698A (l=0.5065), which is l>0.5 ("light") and need a contrasting color, + // we need that to be #E5EFF5, even though that's also l>0.5. With a midpoint of 0.659, we get that right. + let lightness = self.auto_colors.map(|c| c.as_oklab().lightness()); + self.auto_color_threshold = (lightness[0] + lightness[1]) * 0.5; + + // Ensure [0] is dark and [1] is light. + if lightness[0] > lightness[1] { self.auto_colors.swap(0, 1); } } @@ -346,15 +359,12 @@ impl Framebuffer { #[cold] fn contrasted_slow(&self, color: StraightRgba) -> StraightRgba { let idx = (color.to_ne() as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT; - let contrast = self.auto_colors[Self::is_dark(color) as usize]; + let is_dark = color.as_oklab().lightness() < self.auto_color_threshold; + let contrast = self.auto_colors[is_dark as usize]; self.contrast_colors[idx].set((color, contrast)); contrast } - fn is_dark(color: StraightRgba) -> bool { - color.as_oklab().lightness() < 0.5 - } - /// Blends the given sRGB color onto the background bitmap. /// /// TODO: The current approach blends foreground/background independently, diff --git a/crates/edit/src/helpers.rs b/crates/edit/src/helpers.rs index bd433588881..40590733da9 100644 --- a/crates/edit/src/helpers.rs +++ b/crates/edit/src/helpers.rs @@ -82,6 +82,9 @@ pub struct Size { } impl Size { + pub const MIN: Self = Self { width: 0, height: 0 }; + pub const MAX: Self = Self { width: CoordType::MAX, height: CoordType::MAX }; + pub fn as_rect(&self) -> Rect { Rect { left: 0, top: 0, right: self.width, bottom: self.height } } diff --git a/crates/edit/src/sys/unix.rs b/crates/edit/src/sys/unix.rs index 71b9edc7d74..07514325d5c 100644 --- a/crates/edit/src/sys/unix.rs +++ b/crates/edit/src/sys/unix.rs @@ -11,7 +11,7 @@ use std::fs::File; use std::mem::{self, ManuallyDrop, MaybeUninit}; use std::os::fd::{AsRawFd as _, FromRawFd as _}; use std::path::Path; -use std::ptr::{self, NonNull, null_mut}; +use std::ptr::{NonNull, null_mut}; use std::{thread, time}; use stdext::arena::{Arena, ArenaString, scratch_arena}; @@ -203,7 +203,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option ArenaString<'a> { } } + #[must_use] + pub fn from_iter>(arena: &'a Arena, iter: T) -> Self { + let mut s = Self::new_in(arena); + s.extend(iter); + s + } + /// It's empty. pub fn is_empty(&self) -> bool { self.vec.is_empty() @@ -284,6 +291,18 @@ impl fmt::Write for ArenaString<'_> { } } +impl Extend for ArenaString<'_> { + fn extend>(&mut self, iter: I) { + let iterator = iter.into_iter(); + let (lower_bound, _) = iterator.size_hint(); + self.reserve(lower_bound); + iterator.for_each(move |c| self.push(c)); + } + + // TODO: This is where I'd put `extend_one` and `extend_reserve` impls, *but as always*, + // essential stdlib functions are unstable and that means we can't have them. +} + #[macro_export] macro_rules! arena_format { ($arena:expr, $($arg:tt)*) => {{ From 5bafc16b16f86f40e2da5f70fb05316e9c9ec71f Mon Sep 17 00:00:00 2001 From: Cal <107954129+UnnaturalTwilight@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:33:37 -0500 Subject: [PATCH 20/27] Use platform line ending when opening single line files (#739) --- crates/edit/src/buffer/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/edit/src/buffer/mod.rs b/crates/edit/src/buffer/mod.rs index 9ea448542f6..5f4ca052f24 100644 --- a/crates/edit/src/buffer/mod.rs +++ b/crates/edit/src/buffer/mod.rs @@ -769,8 +769,8 @@ impl TextBuffer { } } - // We'll assume CRLF if more than half of the lines end in CRLF. - let newlines_are_crlf = crlf_count >= lines / 2; + // We'll assume CRLF if more than half of the lines end in CRLF. If there is only a single line, we'll use the platform default. + let newlines_are_crlf = if lines == 0 { cfg!(windows) } else { crlf_count > lines / 2 }; // We'll assume tabs if there are more lines starting with tabs than with spaces. let indent_with_tabs = tab_indentations > space_indentations; From e3a6f2daeaa712c8e96afec838cab26d697707ec Mon Sep 17 00:00:00 2001 From: Sergio Triana Escobedo <43504437+stescobedo92@users.noreply.github.com> Date: Tue, 20 Jan 2026 13:10:55 -0600 Subject: [PATCH 21/27] Create parent directories when saving to a non-existent path (#738) Closes #737 Co-authored-by: Leonard Hecker --- crates/edit/src/bin/edit/documents.rs | 11 +++++++++++ crates/edit/src/sys/windows.rs | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/edit/src/bin/edit/documents.rs b/crates/edit/src/bin/edit/documents.rs index 33fc8cf5a76..4366417af62 100644 --- a/crates/edit/src/bin/edit/documents.rs +++ b/crates/edit/src/bin/edit/documents.rs @@ -3,6 +3,7 @@ use std::collections::LinkedList; use std::ffi::OsStr; +use std::fs; use std::fs::File; use std::path::{Path, PathBuf}; @@ -210,6 +211,16 @@ impl DocumentManager { } pub fn open_for_writing(path: &Path) -> apperr::Result { + // Error handling for directory creation and file writing + + // It is worth doing an existence check because it is significantly + // faster than calling mkdir() and letting it fail (at least on Windows). + if let Some(parent) = path.parent() + && !parent.exists() + { + fs::create_dir_all(parent)?; + } + File::create(path).map_err(apperr::Error::from) } diff --git a/crates/edit/src/sys/windows.rs b/crates/edit/src/sys/windows.rs index 03e9ca7da1b..380153b407e 100644 --- a/crates/edit/src/sys/windows.rs +++ b/crates/edit/src/sys/windows.rs @@ -706,7 +706,9 @@ pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Re /// Checks if the given error is a "file not found" error. pub fn apperr_is_not_found(err: apperr::Error) -> bool { - err == gle_to_apperr(Foundation::ERROR_FILE_NOT_FOUND) + const FNF: apperr::Error = gle_to_apperr(Foundation::ERROR_FILE_NOT_FOUND); + const PNF: apperr::Error = gle_to_apperr(Foundation::ERROR_PATH_NOT_FOUND); + err == FNF || err == PNF } fn check_bool_return(ret: BOOL) -> apperr::Result<()> { From 10e93cb350e24668b8e908faa49316544ed7f78d Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 20 Jan 2026 22:11:44 +0100 Subject: [PATCH 22/27] Add proper multithreading support to Arena (#741) The main change is adding multithreading support in order to make JSON unit tests work properly. The TLS overhead is not _that bad. Other changes: * Switch to `io::Result`, because `AllocError` doesn't transmit error codes (meh!) * Reduce x86 commit chunk size to 32KiB * Improved performance slightly by inlining harder (`alloc_uninit`) and outlining the result unwrap (`alloc_raw_bump`) --- crates/edit/src/bin/edit/apperr.rs | 35 +++++++++++++++++++ crates/stdext/Cargo.toml | 3 ++ crates/stdext/src/arena/debug.rs | 5 +-- crates/stdext/src/arena/fs.rs | 55 ++++++++++++++++++++++++++++++ crates/stdext/src/arena/mod.rs | 10 +++--- crates/stdext/src/arena/release.rs | 36 ++++++++++--------- crates/stdext/src/arena/scratch.rs | 30 +++++++++------- crates/stdext/src/arena/string.rs | 7 ++-- crates/stdext/src/sys/unix.rs | 10 +++--- crates/stdext/src/sys/windows.rs | 14 +++++--- 10 files changed, 158 insertions(+), 47 deletions(-) create mode 100644 crates/edit/src/bin/edit/apperr.rs create mode 100644 crates/stdext/src/arena/fs.rs diff --git a/crates/edit/src/bin/edit/apperr.rs b/crates/edit/src/bin/edit/apperr.rs new file mode 100644 index 00000000000..baa8d071a8e --- /dev/null +++ b/crates/edit/src/bin/edit/apperr.rs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::io; + +use edit::{buffer, icu}; + +#[derive(Debug)] +pub enum Error { + Io(io::Error), + Icu(icu::Error), +} + +pub type Result = std::result::Result; + +impl From for Error { + fn from(err: io::Error) -> Self { + Self::Io(err) + } +} + +impl From for Error { + fn from(err: icu::Error) -> Self { + Self::Icu(err) + } +} + +impl From for Error { + fn from(err: buffer::IoError) -> Self { + match err { + buffer::IoError::Io(e) => Self::Io(e), + buffer::IoError::Icu(e) => Self::Icu(e), + } + } +} diff --git a/crates/stdext/Cargo.toml b/crates/stdext/Cargo.toml index 6291567feb7..8feec1485fd 100644 --- a/crates/stdext/Cargo.toml +++ b/crates/stdext/Cargo.toml @@ -7,5 +7,8 @@ license.workspace = true repository.workspace = true rust-version.workspace = true +[features] +single-threaded = [] + [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/crates/stdext/src/arena/debug.rs b/crates/stdext/src/arena/debug.rs index 3c9144b39af..851474e546f 100644 --- a/crates/stdext/src/arena/debug.rs +++ b/crates/stdext/src/arena/debug.rs @@ -4,6 +4,7 @@ #![allow(clippy::missing_safety_doc, clippy::mut_from_ref)] use std::alloc::{AllocError, Allocator, Layout}; +use std::io; use std::mem::MaybeUninit; use std::ptr::NonNull; @@ -62,7 +63,7 @@ impl Arena { Self::Owned { arena: release::Arena::empty() } } - pub fn new(capacity: usize) -> Result { + pub fn new(capacity: usize) -> io::Result { Ok(Self::Owned { arena: release::Arena::new(capacity)? }) } @@ -113,7 +114,7 @@ impl Arena { unsafe impl Allocator for Arena { fn allocate(&self, layout: Layout) -> Result, AllocError> { - self.delegate_target().alloc_raw(layout.size(), layout.align()) + Ok(self.delegate_target().alloc_raw(layout.size(), layout.align())) } fn allocate_zeroed(&self, layout: Layout) -> Result, AllocError> { diff --git a/crates/stdext/src/arena/fs.rs b/crates/stdext/src/arena/fs.rs new file mode 100644 index 00000000000..58396233c80 --- /dev/null +++ b/crates/stdext/src/arena/fs.rs @@ -0,0 +1,55 @@ +use std::fs::File; +use std::io::{self, Read}; +use std::mem::MaybeUninit; +use std::path::Path; +use std::slice::from_raw_parts_mut; + +use super::{Arena, ArenaString}; + +pub fn read_to_vec>(arena: &Arena, path: P) -> io::Result> { + fn inner<'a>(arena: &'a Arena, path: &Path) -> io::Result> { + let mut file = File::open(path)?; + let mut vec = Vec::new_in(arena); + + const MIN_SIZE: usize = 1024; + const MAX_SIZE: usize = 128 * 1024; + let mut buf_size = MIN_SIZE; + + loop { + vec.reserve(buf_size); + let spare = vec.spare_capacity_mut(); + let to_read = spare.len().min(buf_size); + + match file_read_uninit(&mut file, &mut spare[..to_read]) { + Ok(0) => break, + Ok(n) => { + unsafe { vec.set_len(vec.len() + n) }; + buf_size = (buf_size * 2).min(MAX_SIZE); + } + Err(e) if e.kind() == io::ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + + Ok(vec) + } + inner(arena, path.as_ref()) +} + +pub fn read_to_string>(arena: &Arena, path: P) -> io::Result> { + fn inner<'a>(arena: &'a Arena, path: &Path) -> io::Result> { + let vec = read_to_vec(arena, path)?; + ArenaString::from_utf8(vec).map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "stream did not contain valid UTF-8") + }) + } + inner(arena, path.as_ref()) +} + +fn file_read_uninit(file: &mut T, buf: &mut [MaybeUninit]) -> io::Result { + unsafe { + let buf_slice = from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()); + let n = file.read(buf_slice)?; + Ok(n) + } +} diff --git a/crates/stdext/src/arena/mod.rs b/crates/stdext/src/arena/mod.rs index a7099c7b8ac..2a76e210b4e 100644 --- a/crates/stdext/src/arena/mod.rs +++ b/crates/stdext/src/arena/mod.rs @@ -5,13 +5,15 @@ #[cfg(debug_assertions)] mod debug; +mod fs; mod release; mod scratch; mod string; #[cfg(all(not(doc), debug_assertions))] -pub use self::debug::Arena; +pub use self::debug::*; +pub use self::fs::*; #[cfg(any(doc, not(debug_assertions)))] -pub use self::release::Arena; -pub use self::scratch::{ScratchArena, init, scratch_arena}; -pub use self::string::ArenaString; +pub use self::release::*; +pub use self::scratch::*; +pub use self::string::*; diff --git a/crates/stdext/src/arena/release.rs b/crates/stdext/src/arena/release.rs index c1441c2e8c6..53fea9505ad 100644 --- a/crates/stdext/src/arena/release.rs +++ b/crates/stdext/src/arena/release.rs @@ -7,10 +7,13 @@ use std::alloc::{AllocError, Allocator, Layout}; use std::cell::Cell; use std::mem::MaybeUninit; use std::ptr::{self, NonNull}; -use std::{mem, slice}; +use std::{io, mem, slice}; use crate::{cold_path, sys}; +#[cfg(target_pointer_width = "32")] +const ALLOC_CHUNK_SIZE: usize = 32 * 1024; +#[cfg(target_pointer_width = "64")] const ALLOC_CHUNK_SIZE: usize = 64 * 1024; /// An arena allocator. @@ -62,7 +65,7 @@ impl Arena { } } - pub fn new(capacity: usize) -> Result { + pub fn new(capacity: usize) -> io::Result { let capacity = (capacity.max(1) + ALLOC_CHUNK_SIZE - 1) & !(ALLOC_CHUNK_SIZE - 1); let base = unsafe { sys::virtual_reserve(capacity)? }; @@ -103,11 +106,7 @@ impl Arena { } #[inline] - pub(super) fn alloc_raw( - &self, - bytes: usize, - alignment: usize, - ) -> Result, AllocError> { + pub(super) fn alloc_raw(&self, bytes: usize, alignment: usize) -> NonNull<[u8]> { let commit = self.commit.get(); let offset = self.offset.get(); @@ -125,12 +124,12 @@ impl Arena { } self.offset.replace(end); - Ok(unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), bytes) }) + unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), bytes) } } // With the code in `alloc_raw_bump()` out of the way, `alloc_raw()` compiles down to some super tight assembly. #[cold] - fn alloc_raw_bump(&self, beg: usize, end: usize) -> Result, AllocError> { + fn alloc_raw_bump(&self, beg: usize, end: usize) -> NonNull<[u8]> { let offset = self.offset.get(); let commit_old = self.commit.get(); let commit_new = (end + ALLOC_CHUNK_SIZE - 1) & !(ALLOC_CHUNK_SIZE - 1); @@ -140,7 +139,10 @@ impl Arena { sys::virtual_commit(self.base.add(commit_old), commit_new - commit_old).is_err() } { - return Err(AllocError); + // Panicking inside this [cold] function has the benefit of removing duplicated panic code from any + // inlined alloc() function. If we ever add fallible allocations, we should probably duplicate alloc_raw() + // and alloc_raw_bump() instead of returning a Result here and calling unwrap() in the common path. + panic!("out of memory"); } if cfg!(debug_assertions) { @@ -151,22 +153,24 @@ impl Arena { self.commit.replace(commit_new); self.offset.replace(end); - Ok(unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), end - beg) }) + unsafe { NonNull::slice_from_raw_parts(self.base.add(beg), end - beg) } } + #[inline] #[allow(clippy::mut_from_ref)] pub fn alloc_uninit(&self) -> &mut MaybeUninit { let bytes = mem::size_of::(); let alignment = mem::align_of::(); - let ptr = self.alloc_raw(bytes, alignment).unwrap(); + let ptr = self.alloc_raw(bytes, alignment); unsafe { ptr.cast().as_mut() } } + #[inline] #[allow(clippy::mut_from_ref)] pub fn alloc_uninit_slice(&self, count: usize) -> &mut [MaybeUninit] { let bytes = mem::size_of::() * count; let alignment = mem::align_of::(); - let ptr = self.alloc_raw(bytes, alignment).unwrap(); + let ptr = self.alloc_raw(bytes, alignment); unsafe { slice::from_raw_parts_mut(ptr.cast().as_ptr(), count) } } } @@ -187,11 +191,11 @@ impl Default for Arena { unsafe impl Allocator for Arena { fn allocate(&self, layout: Layout) -> Result, AllocError> { - self.alloc_raw(layout.size(), layout.align()) + Ok(self.alloc_raw(layout.size(), layout.align())) } fn allocate_zeroed(&self, layout: Layout) -> Result, AllocError> { - let p = self.alloc_raw(layout.size(), layout.align())?; + let p = self.alloc_raw(layout.size(), layout.align()); unsafe { p.cast::().as_ptr().write_bytes(0, p.len()) } Ok(p) } @@ -217,7 +221,7 @@ unsafe impl Allocator for Arena { let delta = new_layout.size() - old_layout.size(); // Assuming that the given ptr/length area is at the end of the arena, // we can just push more memory to the end of the arena to grow it. - self.alloc_raw(delta, 1)?; + self.alloc_raw(delta, 1); } else { cold_path(); diff --git a/crates/stdext/src/arena/scratch.rs b/crates/stdext/src/arena/scratch.rs index 7c519dfad10..036a8ce2b34 100644 --- a/crates/stdext/src/arena/scratch.rs +++ b/crates/stdext/src/arena/scratch.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::alloc::AllocError; +use std::io; use std::ops::Deref; #[cfg(debug_assertions)] @@ -74,7 +74,7 @@ mod single_threaded { /// Initialize the scratch arenas with a given capacity. /// Call this before using [`scratch_arena`]. #[allow(dead_code)] - pub fn init(capacity: usize) -> Result<(), AllocError> { + pub fn init(capacity: usize) -> io::Result<()> { unsafe { for s in &mut S_SCRATCH[..] { *s = release::Arena::new(capacity)?; @@ -120,6 +120,7 @@ mod single_threaded { mod multi_threaded { use std::cell::Cell; use std::ptr; + use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; @@ -128,9 +129,13 @@ mod multi_threaded { const { [Cell::new(release::Arena::empty()), Cell::new(release::Arena::empty())] }; } - /// Does nothing. - #[allow(dead_code)] - pub fn init(_: usize) -> Result<(), AllocError> { + static INIT_SIZE: AtomicUsize = AtomicUsize::new(128 * MEBI); + + /// Sets the default scratch arena size. + pub fn init(capacity: usize) -> io::Result<()> { + if capacity != 0 { + INIT_SIZE.store(capacity, Ordering::Relaxed); + } Ok(()) } @@ -142,23 +147,24 @@ mod multi_threaded { #[cold] fn init(s: &[Cell; 2]) { + let capacity = INIT_SIZE.load(Ordering::Relaxed); for s in s { - s.set(release::Arena::new(128 * 1024 * 1024).unwrap()); + s.set(release::Arena::new(capacity).unwrap()); } } - S_SCRATCH.with(|s| { - let index = ptr::eq(opt_ptr(conflict), s[0].as_ptr()) as usize; - let arena = unsafe { &*s[index].as_ptr() }; + S_SCRATCH.with(|arenas| { + let index = ptr::eq(opt_ptr(conflict), arenas[0].as_ptr()) as usize; + let arena = unsafe { &*arenas[index].as_ptr() }; if arena.is_empty() { - init(s); + init(arenas); } ScratchArena::new(arena) }) } } -#[cfg(test)] +#[cfg(not(feature = "single-threaded"))] pub use multi_threaded::*; -#[cfg(not(test))] +#[cfg(feature = "single-threaded")] pub use single_threaded::*; diff --git a/crates/stdext/src/arena/string.rs b/crates/stdext/src/arena/string.rs index 9c24b2b7e60..1322fb15417 100644 --- a/crates/stdext/src/arena/string.rs +++ b/crates/stdext/src/arena/string.rs @@ -36,8 +36,9 @@ impl<'a> ArenaString<'a> { res } - pub fn from_utf8(arena: &'a Arena, vec: &[u8]) -> Result { - Ok(Self::from_str(arena, str::from_utf8(vec)?)) + pub fn from_utf8(vec: Vec) -> Result { + str::from_utf8(&vec)?; + Ok(Self { vec }) } /// It says right here that you checked if `bytes` is valid UTF-8 @@ -273,7 +274,7 @@ impl DerefMut for ArenaString<'_> { impl fmt::Display for ArenaString<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) + fmt::Display::fmt(&**self, f) } } diff --git a/crates/stdext/src/sys/unix.rs b/crates/stdext/src/sys/unix.rs index dd634ca8c72..2d0575b1dc5 100644 --- a/crates/stdext/src/sys/unix.rs +++ b/crates/stdext/src/sys/unix.rs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::alloc::AllocError; use std::ffi::c_int; +use std::io; use std::ptr::{self, NonNull, null_mut}; /// Reserves a virtual memory region of the given size. @@ -13,7 +13,7 @@ use std::ptr::{self, NonNull, null_mut}; /// /// This function is unsafe because it uses raw pointers. /// Don't forget to release the memory when you're done with it or you'll leak it. -pub unsafe fn virtual_reserve(size: usize) -> Result, AllocError> { +pub unsafe fn virtual_reserve(size: usize) -> io::Result> { unsafe { let ptr = libc::mmap( null_mut(), @@ -24,7 +24,7 @@ pub unsafe fn virtual_reserve(size: usize) -> Result, AllocError> { 0, ); if ptr.is_null() || ptr::eq(ptr, libc::MAP_FAILED) { - Err(AllocError) + Err(io::Error::last_os_error()) } else { Ok(NonNull::new_unchecked(ptr as *mut u8)) } @@ -65,9 +65,9 @@ pub unsafe fn virtual_release(base: NonNull, size: usize) { /// This function is unsafe because it uses raw pointers. /// Make sure to only pass pointers acquired from `virtual_reserve` /// and to pass a size less than or equal to the size passed to `virtual_reserve`. -pub unsafe fn virtual_commit(base: NonNull, size: usize) -> Result<(), AllocError> { +pub unsafe fn virtual_commit(base: NonNull, size: usize) -> io::Result<()> { unsafe { let status = libc::mprotect(base.cast().as_ptr(), size, libc::PROT_READ | libc::PROT_WRITE); - if status != 0 { Err(AllocError) } else { Ok(()) } + if status != 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } } diff --git a/crates/stdext/src/sys/windows.rs b/crates/stdext/src/sys/windows.rs index e008814efcb..59282bec9ff 100644 --- a/crates/stdext/src/sys/windows.rs +++ b/crates/stdext/src/sys/windows.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::alloc::AllocError; +use std::io; use std::ptr::{NonNull, null_mut}; const MEM_COMMIT: u32 = 0x00001000; @@ -27,10 +27,14 @@ unsafe extern "system" { /// /// This function is unsafe because it uses raw pointers. /// Don't forget to release the memory when you're done with it or you'll leak it. -pub unsafe fn virtual_reserve(size: usize) -> Result, AllocError> { +pub unsafe fn virtual_reserve(size: usize) -> io::Result> { unsafe { let res = VirtualAlloc(null_mut(), size, MEM_RESERVE, PAGE_READWRITE); - if res.is_null() { Err(AllocError) } else { Ok(NonNull::new_unchecked(res)) } + if res.is_null() { + Err(io::Error::last_os_error()) + } else { + Ok(NonNull::new_unchecked(res)) + } } } @@ -55,9 +59,9 @@ pub unsafe fn virtual_release(base: NonNull, _size: usize) { /// This function is unsafe because it uses raw pointers. /// Make sure to only pass pointers acquired from [`virtual_reserve`] /// and to pass a size less than or equal to the size passed to [`virtual_reserve`]. -pub unsafe fn virtual_commit(base: NonNull, size: usize) -> Result<(), AllocError> { +pub unsafe fn virtual_commit(base: NonNull, size: usize) -> io::Result<()> { unsafe { let res = VirtualAlloc(base.as_ptr() as *mut _, size, MEM_COMMIT, PAGE_READWRITE); - if res.is_null() { Err(AllocError) } else { Ok(()) } + if res.is_null() { Err(io::Error::last_os_error()) } else { Ok(()) } } } From cb14214c6ffa798ca61279bd8dbd82a7df4c0769 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Jan 2026 00:05:09 +0100 Subject: [PATCH 23/27] Add a JSON parser (#742) This is a bog-standard JSONC parser. Not much to be said. Its performance is quite alright. Depends on #741 --- Cargo.lock | 167 ++++--- assets/highlighting-tests/json.json | 26 ++ crates/edit/Cargo.toml | 4 +- crates/edit/benches/lib.rs | 83 +++- crates/edit/src/json.rs | 645 ++++++++++++++++++++++++++++ crates/edit/src/lib.rs | 1 + 6 files changed, 841 insertions(+), 85 deletions(-) create mode 100644 assets/highlighting-tests/json.json create mode 100644 crates/edit/src/json.rs diff --git a/Cargo.lock b/Cargo.lock index da1473dc64e..8375271b12f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -46,9 +55,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cast" @@ -58,9 +67,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.44" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -76,9 +85,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -116,18 +125,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstyle", "clap_lex", @@ -135,9 +144,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "core-foundation-sys" @@ -147,10 +156,11 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -159,6 +169,7 @@ dependencies = [ "itertools", "num-traits", "oorandom", + "page_size", "plotters", "rayon", "regex", @@ -170,9 +181,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", "itertools", @@ -215,8 +226,6 @@ version = "1.2.1" dependencies = [ "criterion", "libc", - "serde", - "serde_json", "stdext", "toml-span", "windows-sys", @@ -232,9 +241,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "getrandom" @@ -303,9 +312,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -319,9 +328,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -329,15 +338,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" @@ -366,6 +375,16 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "pico-args" version = "0.5.0" @@ -408,18 +427,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -494,12 +513,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -541,15 +554,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -573,9 +586,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.108" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -637,18 +650,18 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -659,9 +672,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -669,9 +682,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -682,23 +695,39 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -708,6 +737,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -778,39 +813,45 @@ dependencies = [ [[package]] name = "winresource" -version = "0.1.26" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6d1a5aac12a0cccc4dea310c464cae005a78eadfa72fc3bc45fe696ebfbb9d" +checksum = "17cdfa8da4b111045a5e47c7c839e6c5e11c942de1309bc624393ed5d87f89c6" dependencies = [ "version_check", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" + [[package]] name = "zstd" version = "0.13.3" diff --git a/assets/highlighting-tests/json.json b/assets/highlighting-tests/json.json new file mode 100644 index 00000000000..b4aae26ea7c --- /dev/null +++ b/assets/highlighting-tests/json.json @@ -0,0 +1,26 @@ +{ + // Object with various value types + "string": "Hello, world!", // string literal + "numberInt": 42, // integer number + "numberFloat": -3.14e+2, // floating point with exponent + "booleanTrue": true, // boolean true + "booleanFalse": false, // boolean false + "nullValue": null, // null literal + "array": [ + "item1", // string in array + 2, // number in array + false, // boolean in array + null, // null in array + { + "nested": "object" + } // object in array + ], + "emptyObject": {}, // empty object + "emptyArray": [], // empty array + /* Multi-line comment: + This is a block comment + inside JSONC. + */ + "unicodeString": "Emoji: \uD83D\uDE03", // Unicode escape + "escapedChars": "Line1\nLine2\tTabbed\\Backslash\"Quote" // Escaped characters +} diff --git a/crates/edit/Cargo.toml b/crates/edit/Cargo.toml index f96ceb7d531..c7bc03f0afb 100644 --- a/crates/edit/Cargo.toml +++ b/crates/edit/Cargo.toml @@ -47,7 +47,5 @@ features = [ ] [dev-dependencies] -criterion = { version = "0.7", features = ["html_reports"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } +criterion = { version = "0.8", features = ["html_reports"] } zstd = { version = "0.13", default-features = false } diff --git a/crates/edit/benches/lib.rs b/crates/edit/benches/lib.rs index 4c8fcc37df3..57ccc73ef32 100644 --- a/crates/edit/benches/lib.rs +++ b/crates/edit/benches/lib.rs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#![feature(allocator_api)] + use std::hint::black_box; use std::io::Cursor; use std::{mem, vec}; @@ -8,33 +10,60 @@ use std::{mem, vec}; use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use edit::helpers::*; use edit::simd::MemsetSafe; -use edit::{buffer, hash, oklab, simd, unicode}; -use serde::Deserialize; +use edit::{buffer, hash, json, oklab, simd, unicode}; use stdext::arena; +use stdext::arena::{Arena, scratch_arena}; -#[derive(Deserialize)] -pub struct EditingTracePatch(pub usize, pub usize, pub String); +struct EditingTracePatch<'a>(usize, usize, &'a str); -#[derive(Deserialize)] -pub struct EditingTraceTransaction { - pub patches: Vec, +struct EditingTraceTransaction<'a> { + patches: Vec, &'a Arena>, } -#[derive(Deserialize)] -pub struct EditingTraceData { - #[serde(rename = "startContent")] - pub start_content: String, - #[serde(rename = "endContent")] - pub end_content: String, - pub txns: Vec, +struct EditingTraceData<'a> { + start_content: &'a str, + end_content: &'a str, + txns: Vec, &'a Arena>, } fn bench_buffer(c: &mut Criterion) { - let data = include_bytes!("../../../assets/editing-traces/rustcode.json.zst"); - let data = zstd::decode_all(Cursor::new(data)).unwrap(); - let data: EditingTraceData = serde_json::from_slice(&data).unwrap(); - let mut patches_with_coords = Vec::new(); + let scratch = scratch_arena(None); + let data = { + let data = include_bytes!("../../../assets/editing-traces/rustcode.json.zst"); + let data = zstd::decode_all(Cursor::new(data)).unwrap(); + let data = str::from_utf8(&data).unwrap(); + + let data = json::parse(&scratch, data).unwrap(); + let root = data.as_object().unwrap(); + let txns = root.get_array("txns").unwrap(); + + let mut res = EditingTraceData { + start_content: root.get_str("startContent").unwrap(), + end_content: root.get_str("endContent").unwrap(), + txns: Vec::with_capacity_in(txns.len(), &scratch), + }; + + for txn in txns { + let txn = txn.as_object().unwrap(); + let patches = txn.get_array("patches").unwrap(); + let mut txn = + EditingTraceTransaction { patches: Vec::with_capacity_in(patches.len(), &scratch) }; + + for patch in patches { + let patch = patch.as_array().unwrap(); + let offset = patch[0].as_number().unwrap() as usize; + let del_len = patch[1].as_number().unwrap() as usize; + let ins_str = patch[2].as_str().unwrap(); + txn.patches.push(EditingTracePatch(offset, del_len, ins_str)); + } + + res.txns.push(txn); + } + + res + }; + let mut patches_with_coords = Vec::new(); { let mut tb = buffer::TextBuffer::new(false).unwrap(); tb.set_crlf(false); @@ -48,7 +77,7 @@ fn bench_buffer(c: &mut Criterion) { tb.delete(buffer::CursorMovement::Grapheme, p.1 as CoordType); tb.write_raw(p.2.as_bytes()); - patches_with_coords.push((beg, p.1 as CoordType, p.2.clone())); + patches_with_coords.push((beg, p.1 as CoordType, p.2)); } } @@ -126,6 +155,21 @@ fn bench_hash(c: &mut Criterion) { }); } +fn bench_json(c: &mut Criterion) { + let str = include_str!("../../../assets/highlighting-tests/json.json"); + + c.benchmark_group("json").throughput(Throughput::Bytes(str.len() as u64)).bench_function( + "parse", + |b| { + b.iter(|| { + let scratch = scratch_arena(None); + let obj = json::parse(&scratch, black_box(str)).unwrap(); + black_box(obj); + }) + }, + ); +} + fn bench_oklab(c: &mut Criterion) { c.benchmark_group("oklab") .bench_function("StraightRgba::as_oklab", |b| { @@ -232,6 +276,7 @@ fn bench(c: &mut Criterion) { bench_buffer(c); bench_hash(c); + bench_json(c); bench_oklab(c); bench_simd_lines_fwd(c); bench_simd_memchr2(c); diff --git a/crates/edit/src/json.rs b/crates/edit/src/json.rs new file mode 100644 index 00000000000..368de37c8c7 --- /dev/null +++ b/crates/edit/src/json.rs @@ -0,0 +1,645 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! A simple JSONC parser with trailing comma support. +//! +//! It's designed for parsing our small settings files, +//! but its performance is rather competitive in general. + +use std::fmt; +use std::hint::unreachable_unchecked; + +use stdext::arena::{Arena, ArenaString}; + +use crate::unicode::MeasurementConfig; + +/// Maximum nesting depth to prevent stack overflow. +const MAX_DEPTH: usize = 64; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ParseErrorKind { + /// Invalid JSON syntax + Syntax, + /// Maximum nesting depth exceeded + MaxDepth, +} + +#[derive(Debug, Clone)] +pub struct ParseError { + kind: ParseErrorKind, + line: usize, + column: usize, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let message = match self.kind { + ParseErrorKind::Syntax => "Invalid JSON", + ParseErrorKind::MaxDepth => "JSON too deeply nested", + }; + write!(f, "{}:{}: {}", self.line, self.column, message) + } +} + +impl std::error::Error for ParseError {} + +#[derive(Debug, Clone)] +pub enum Value<'a> { + Null, + Bool(bool), + Number(f64), + String(&'a str), + Array(&'a [Value<'a>]), + Object(&'a [(&'a str, Value<'a>)]), +} + +impl<'a> Value<'a> { + pub fn is_null(&self) -> bool { + matches!(self, Value::Null) + } + + pub fn as_bool(&self) -> Option { + match self { + Value::Bool(b) => Some(*b), + _ => None, + } + } + + pub fn as_number(&self) -> Option { + match self { + Value::Number(n) => Some(*n), + _ => None, + } + } + + pub fn as_str(&self) -> Option<&'a str> { + match self { + Value::String(s) => Some(s), + _ => None, + } + } + + pub fn as_array(&self) -> Option<&'a [Value<'a>]> { + match self { + Value::Array(arr) => Some(arr), + _ => None, + } + } + + pub fn as_object(&self) -> Option> { + match self { + Value::Object(entries) => Some(Object { entries }), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Object<'a> { + entries: &'a [(&'a str, Value<'a>)], +} + +impl<'a> Object<'a> { + pub fn get(&self, key: &str) -> Option<&'a Value<'a>> { + self.entries.iter().find(|e| e.0 == key).map(|e| &e.1) + } + + pub fn get_bool(&self, key: &str) -> Option { + self.get(key).and_then(Value::as_bool) + } + + pub fn get_number(&self, key: &str) -> Option { + self.get(key).and_then(Value::as_number) + } + + pub fn get_str(&self, key: &str) -> Option<&'a str> { + self.get(key).and_then(Value::as_str) + } + + pub fn get_array(&self, key: &str) -> Option<&'a [Value<'a>]> { + self.get(key).and_then(Value::as_array) + } + + pub fn get_object(&self, key: &str) -> Option> { + self.get(key).and_then(Value::as_object) + } + + pub fn iter(&self) -> impl Iterator)> { + self.entries.iter() + } + + pub fn len(&self) -> usize { + self.entries.len() + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } +} + +pub fn parse<'a>(arena: &'a Arena, input: &str) -> Result, ParseError> { + let mut parser = Parser::new(arena, input); + parser.skip_bom(); + let value = parser.parse_value(0)?; + parser.skip_whitespace_and_comments()?; + if parser.pos == parser.input.len() { + Ok(value) + } else { + // Unexpected data after JSON value + Err(parser.fail(parser.pos, ParseErrorKind::Syntax)) + } +} + +struct Parser<'a, 'i> { + arena: &'a Arena, + input: &'i str, + bytes: &'i [u8], + pos: usize, +} + +impl<'a, 'i> Parser<'a, 'i> { + fn new(arena: &'a Arena, input: &'i str) -> Self { + Self { arena, input, bytes: input.as_bytes(), pos: 0 } + } + + fn parse_value(&mut self, depth: usize) -> Result, ParseError> { + // Prevent stack overflow from deeply nested structures + if depth >= MAX_DEPTH { + return Err(self.fail(self.pos, ParseErrorKind::MaxDepth)); + } + + self.skip_whitespace_and_comments()?; + + let ch = match self.peek() { + Some(ch) => ch, + // Unexpected end of input + None => return Err(self.fail(self.pos, ParseErrorKind::Syntax)), + }; + + match ch { + 'n' => self.parse_null(), + 't' => self.parse_true(), + 'f' => self.parse_false(), + '-' | '0'..='9' => self.parse_number(), + '"' => self.parse_string(), + '[' => self.parse_array(depth), + '{' => self.parse_object(depth), + _ => Err(self.fail(self.pos, ParseErrorKind::Syntax)), + } + } + + fn parse_null(&mut self) -> Result, ParseError> { + self.expect_str("null")?; + Ok(Value::Null) + } + + fn parse_true(&mut self) -> Result, ParseError> { + self.expect_str("true")?; + Ok(Value::Bool(true)) + } + + fn parse_false(&mut self) -> Result, ParseError> { + self.expect_str("false")?; + Ok(Value::Bool(false)) + } + + fn parse_number(&mut self) -> Result, ParseError> { + let start = self.pos; + + while self.pos < self.bytes.len() + && matches!(self.bytes[self.pos], b'0'..=b'9' | b'.' | b'-' | b'+' | b'e' | b'E') + { + self.pos += 1; + } + + if let Ok(num) = self.input[start..self.pos].parse::() + && num.is_finite() + { + Ok(Value::Number(num)) + } else { + Err(self.fail(self.pos, ParseErrorKind::Syntax)) + } + } + + fn parse_string(&mut self) -> Result, ParseError> { + self.expect(b'"')?; + + let mut result = ArenaString::new_in(self.arena); + + loop { + if self.pos >= self.bytes.len() { + // Unterminated string + return Err(self.fail(self.pos, ParseErrorKind::Syntax)); + } + + let b = self.bytes[self.pos]; + self.pos += 1; + + match b { + b'"' => break, + b'\\' => self.parse_escape(&mut result)?, + ..=0x1f => { + // Control characters must be escaped + return Err(self.fail(self.pos - 1, ParseErrorKind::Syntax)); + } + _ => { + let beg = self.pos - 1; + + while self.pos < self.bytes.len() + && !matches!(self.bytes[self.pos], b'"' | b'\\' | ..=0x1f) + { + self.pos += 1; + } + + result.push_str(&self.input[beg..self.pos]); + } + } + } + + Ok(Value::String(result.leak())) + } + + #[cold] + fn parse_escape(&mut self, result: &mut ArenaString) -> Result<(), ParseError> { + if self.pos >= self.bytes.len() { + // Unterminated escape sequence + return Err(self.fail(self.pos, ParseErrorKind::Syntax)); + } + + let b = self.bytes[self.pos]; + self.pos += 1; + + let ch = match b { + b'"' => b'"', + b'\\' => b'\\', + b'/' => b'/', + b'b' => b'\x08', + b'f' => b'\x0C', + b'n' => b'\n', + b'r' => b'\r', + b't' => b'\t', + b'u' => return self.parse_unicode_escape(result), + _ => { + // Invalid escape sequence + return Err(self.fail(self.pos - 2, ParseErrorKind::Syntax)); + } + }; + + result.push(ch as char); + Ok(()) + } + + #[cold] + fn parse_unicode_escape(&mut self, result: &mut ArenaString) -> Result<(), ParseError> { + let start = self.pos - 2; // parse_escape() already advanced past "\u" + let mut code = self.parse_hex4()?; + + if (0xd800..=0xdbff).contains(&code) { + if self.is_str("\\u") + && let _ = self.advance(2) + && let Ok(low) = self.parse_hex4() + && (0xdc00..=0xdfff).contains(&low) + { + code = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00); + } else { + code = u32::MAX; + }; + } + + match char::from_u32(code) { + Some(c) => { + result.push(c); + Ok(()) + } + None => Err(self.fail(start, ParseErrorKind::Syntax)), + } + } + + fn parse_hex4(&mut self) -> Result { + let start = self.pos - 2; // parse_unicode_escape() already advanced past "\u" + + self.bytes + .get(self.pos..self.pos + 4) + .and_then(|b| { + self.pos += 4; + b.iter().try_fold(0u32, |acc, &b| { + let d = (b as char).to_digit(16)?; + Some((acc << 4) | d) + }) + }) + .ok_or_else(|| self.fail(start, ParseErrorKind::Syntax)) + } + + fn parse_array(&mut self, depth: usize) -> Result, ParseError> { + let mut values = Vec::new_in(self.arena); + let mut expects_comma = false; + + self.expect(b'[')?; + + loop { + self.skip_whitespace_and_comments()?; + + match self.peek() { + // Unexpected end of input + None => return Err(self.fail(self.pos, ParseErrorKind::Syntax)), + Some(']') => break, + Some(',') => { + if !expects_comma { + // Unexpected comma + return Err(self.fail(self.pos, ParseErrorKind::Syntax)); + } + + self.advance(1); + self.skip_whitespace_and_comments()?; + expects_comma = false; + } + Some(_) => { + if expects_comma { + // Missing comma + return Err(self.fail(self.pos, ParseErrorKind::Syntax)); + } + + values.push(self.parse_value(depth + 1)?); + expects_comma = true; + } + } + } + + self.expect(b']')?; + Ok(Value::Array(values.leak())) + } + + fn parse_object(&mut self, depth: usize) -> Result, ParseError> { + let mut entries = Vec::new_in(self.arena); + let mut expects_comma = false; + + self.expect(b'{')?; + + loop { + self.skip_whitespace_and_comments()?; + + match self.peek() { + // Unexpected end of input + None => return Err(self.fail(self.pos, ParseErrorKind::Syntax)), + Some(',') => { + if !expects_comma { + // Unexpected comma + return Err(self.fail(self.pos, ParseErrorKind::Syntax)); + } + + self.advance(1); + self.skip_whitespace_and_comments()?; + expects_comma = false; + } + Some('}') => break, + Some(_) => { + if expects_comma { + // Missing comma + return Err(self.fail(self.pos, ParseErrorKind::Syntax)); + } + + let key = match self.parse_string()? { + Value::String(s) => s, + // The entire point of parse_string is to return a string. + // If that fails, we all should start farming potatoes. + // This is essentially an unwrap_unchecked(). + _ => unsafe { unreachable_unchecked() }, + }; + self.skip_whitespace_and_comments()?; + self.expect(b':')?; + + let value = self.parse_value(depth + 1)?; + entries.push((key, value)); + expects_comma = true; + } + } + } + + self.expect(b'}')?; + Ok(Value::Object(entries.leak())) + } + + fn skip_bom(&mut self) { + if self.is_str("\u{feff}") { + self.advance(3); + } + } + + fn skip_whitespace_and_comments(&mut self) -> Result<(), ParseError> { + loop { + loop { + if self.pos >= self.bytes.len() { + return Ok(()); + } + match self.bytes[self.pos] { + b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1, + _ => break, + } + } + + if self.is_str("//") { + self.pos += 2; + while self.pos < self.bytes.len() && self.bytes[self.pos] != b'\n' { + self.pos += 1; + } + } else if self.is_str("/*") { + let start = self.pos; + self.pos += 2; + loop { + while self.pos < self.bytes.len() && self.bytes[self.pos] != b'*' { + self.pos += 1; + } + if self.pos >= self.bytes.len() { + return Err(self.fail(start, ParseErrorKind::Syntax)); + } + if self.is_str("*/") { + self.pos += 2; + break; + } + self.pos += 1; + } + } else { + return Ok(()); + } + } + } + + fn expect(&mut self, expected: u8) -> Result<(), ParseError> { + if self.bytes.get(self.pos) == Some(&expected) { + self.pos += 1; + Ok(()) + } else { + Err(self.fail(self.pos, ParseErrorKind::Syntax)) + } + } + + fn expect_str(&mut self, expected: &str) -> Result<(), ParseError> { + if self.is_str(expected) { + self.pos += expected.len(); + Ok(()) + } else { + Err(self.fail(self.pos, ParseErrorKind::Syntax)) + } + } + + fn is_str(&self, expected: &str) -> bool { + self.bytes.get(self.pos..self.pos + expected.len()) == Some(expected.as_bytes()) + } + + fn peek(&self) -> Option { + if self.pos < self.bytes.len() { Some(self.bytes[self.pos] as char) } else { None } + } + + fn advance(&mut self, num: usize) { + self.pos += num; + } + + #[cold] + fn fail(&self, pos: usize, kind: ParseErrorKind) -> ParseError { + let mut cfg = MeasurementConfig::new(&self.bytes); + let pos = cfg.goto_offset(pos); + let line = pos.logical_pos.y.max(0) as usize + 1; + let column = pos.logical_pos.x.max(0) as usize + 1; + ParseError { kind, line, column } + } +} + +#[allow(non_snake_case)] +#[allow(clippy::invisible_characters)] +#[cfg(test)] +mod tests { + use stdext::arena::scratch_arena; + + use super::*; + + #[test] + fn test_null() { + let scratch = scratch_arena(None); + assert!(parse(&scratch, "null").unwrap().is_null()); + } + + #[test] + fn test_bool() { + let scratch = scratch_arena(None); + assert_eq!(parse(&scratch, "true").unwrap().as_bool(), Some(true)); + assert_eq!(parse(&scratch, "false").unwrap().as_bool(), Some(false)); + } + + #[test] + fn test_number() { + let scratch = scratch_arena(None); + assert_eq!(parse(&scratch, "0").unwrap().as_number(), Some(0.0)); + assert_eq!(parse(&scratch, "123").unwrap().as_number(), Some(123.0)); + assert_eq!(parse(&scratch, "-456").unwrap().as_number(), Some(-456.0)); + assert_eq!(parse(&scratch, "3.15").unwrap().as_number(), Some(3.15)); + assert_eq!(parse(&scratch, "1e10").unwrap().as_number(), Some(1e10)); + assert_eq!(parse(&scratch, "1.5e-3").unwrap().as_number(), Some(0.0015)); + } + + #[test] + fn test_string() { + let scratch = scratch_arena(None); + assert_eq!(parse(&scratch, r#""hello""#).unwrap().as_str(), Some("hello")); + assert_eq!(parse(&scratch, r#""hello\nworld""#).unwrap().as_str(), Some("hello\nworld")); + assert_eq!(parse(&scratch, r#""\u0041\u0042\u0043""#).unwrap().as_str(), Some("ABC")); + } + + #[test] + fn test_array() { + let scratch = scratch_arena(None); + let value = parse(&scratch, "[1, 2, 3]").unwrap(); + let arr = value.as_array().unwrap(); + assert_eq!(arr.len(), 3); + assert_eq!(arr[0].as_number(), Some(1.0)); + assert_eq!(arr[1].as_number(), Some(2.0)); + assert_eq!(arr[2].as_number(), Some(3.0)); + } + + #[test] + fn test_object() { + let scratch = scratch_arena(None); + let value = parse(&scratch, r#"{"a": 1, "b": true}"#).unwrap(); + let obj = value.as_object().unwrap(); + assert_eq!(obj.get_number("a"), Some(1.0)); + assert_eq!(obj.get_bool("b"), Some(true)); + } + + #[test] + fn test_comments() { + let scratch = scratch_arena(None); + let input = r#"{ + // Line comment + "a": 1, + /* Block comment */ + "b": 2 + }"#; + let value = parse(&scratch, input).unwrap(); + let obj = value.as_object().unwrap(); + assert_eq!(obj.get_number("a"), Some(1.0)); + assert_eq!(obj.get_number("b"), Some(2.0)); + } + + #[test] + fn test_trailing_comma() { + let scratch = scratch_arena(None); + assert!(parse(&scratch, "[1, 2, 3,]").is_ok()); + assert!(parse(&scratch, r#"{"a": 1,}"#).is_ok()); + } + + #[test] + fn test_nested() { + let scratch = scratch_arena(None); + let input = r#"{ + "nested": { + "array": [1, 2, {"deep": true}] + } + }"#; + let value = parse(&scratch, input).unwrap(); + let obj = value.as_object().unwrap(); + let nested = obj.get_object("nested").unwrap(); + let array = nested.get_array("array").unwrap(); + assert_eq!(array.len(), 3); + let deep_obj = array[2].as_object().unwrap(); + assert_eq!(deep_obj.get_bool("deep"), Some(true)); + } + + #[test] + fn test_max_depth() { + let scratch = scratch_arena(None); + let mut input = String::new(); + for _ in 0..100 { + input.push('['); + } + for _ in 0..100 { + input.push(']'); + } + assert!(parse(&scratch, &input).is_err()); + } + + #[test] + fn test_invalid_json() { + let scratch = scratch_arena(None); + assert!(parse(&scratch, "").is_err()); + assert!(parse(&scratch, "{").is_err()); + assert!(parse(&scratch, r#"{"a":}"#).is_err()); + assert!(parse(&scratch, r#"{5:1}"#).is_err()); + assert!(parse(&scratch, "[1, 2,").is_err()); + assert!(parse(&scratch, r#""unterminated"#).is_err()); + } + + #[test] + fn test_control_chars() { + let scratch = scratch_arena(None); + // Control characters must be escaped + assert!(parse(&scratch, "\"\x01\"").is_err()); + } + + #[test] + fn test_unicode() { + let scratch = scratch_arena(None); + // Test emoji (surrogate pair) + assert_eq!(parse(&scratch, r#""\uD83D\uDE00""#).unwrap().as_str(), Some("😀")); + // Test regular unicode + assert_eq!(parse(&scratch, r#""\u2764""#).unwrap().as_str(), Some("❤")); + } +} diff --git a/crates/edit/src/lib.rs b/crates/edit/src/lib.rs index 59e3df0ed02..126cbce525f 100644 --- a/crates/edit/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -29,6 +29,7 @@ pub mod hash; pub mod helpers; pub mod icu; pub mod input; +pub mod json; pub mod oklab; pub mod path; pub mod simd; From f67627f6160085ccdb799dcec3d43b31924e8b2e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 22 Jan 2026 19:49:04 +0100 Subject: [PATCH 24/27] Add a glob matcher (#743) Supports `*` and `**` patterns. That's enough for our upcoming purposes. --- crates/edit/benches/lib.rs | 15 +- crates/edit/src/glob.rs | 273 +++++++++++++++++++++++++++++++++++++ crates/edit/src/lib.rs | 1 + 3 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 crates/edit/src/glob.rs diff --git a/crates/edit/benches/lib.rs b/crates/edit/benches/lib.rs index 57ccc73ef32..56b92c067ee 100644 --- a/crates/edit/benches/lib.rs +++ b/crates/edit/benches/lib.rs @@ -10,9 +10,8 @@ use std::{mem, vec}; use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use edit::helpers::*; use edit::simd::MemsetSafe; -use edit::{buffer, hash, json, oklab, simd, unicode}; -use stdext::arena; -use stdext::arena::{Arena, scratch_arena}; +use edit::{buffer, glob, hash, json, oklab, simd, unicode}; +use stdext::arena::{self, Arena, scratch_arena}; struct EditingTracePatch<'a>(usize, usize, &'a str); @@ -136,6 +135,15 @@ fn bench_buffer(c: &mut Criterion) { }); } +fn bench_glob(c: &mut Criterion) { + // Same benchmark as in glob-match + const PATH: &str = "foo/bar/foo/bar/foo/bar/foo/bar/foo/bar.txt"; + const GLOB: &str = "foo/**/bar.txt"; + + c.benchmark_group("glob") + .bench_function("glob_match", |b| b.iter(|| assert!(glob::glob_match(GLOB, PATH)))); +} + fn bench_hash(c: &mut Criterion) { c.benchmark_group("hash") .throughput(Throughput::Bytes(8)) @@ -275,6 +283,7 @@ fn bench(c: &mut Criterion) { arena::init(128 * MEBI).unwrap(); bench_buffer(c); + bench_glob(c); bench_hash(c); bench_json(c); bench_oklab(c); diff --git a/crates/edit/src/glob.rs b/crates/edit/src/glob.rs new file mode 100644 index 00000000000..2a9a75adb4d --- /dev/null +++ b/crates/edit/src/glob.rs @@ -0,0 +1,273 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Simple glob matching. +//! +//! Supported patterns: +//! - `*` matches any characters except for path separators, including an empty string. +//! - `**` matches any characters, including an empty string. +//! For convenience, `/**/` also matches `/`. + +use std::path::is_separator; + +#[inline] +pub fn glob_match, N: AsRef<[u8]>>(pattern: P, name: N) -> bool { + glob(pattern.as_ref(), name.as_ref()) +} + +fn glob(pattern: &[u8], name: &[u8]) -> bool { + fast_path(pattern, name).unwrap_or_else(|| slow_path(pattern, name)) +} + +// Fast-pass for the most common patterns: +// * Matching files by extension (e.g., **/*.rs) +// * Matching files by name (e.g., **/Cargo.toml) +fn fast_path(pattern: &[u8], name: &[u8]) -> Option { + // In either case, the glob must start with "**/". + let mut suffix = pattern.strip_prefix(b"**/")?; + if suffix.is_empty() { + return None; + } + + // Determine whether it's "**/" or "**/*". + let mut needs_dir_anchor = true; + if let Some(s) = suffix.strip_prefix(b"*") { + suffix = s; + needs_dir_anchor = false; + } + + // Restrict down to anything we can handle with a suffix check. + if suffix.is_empty() || contains_magic(suffix) { + return None; + } + + Some( + match_path_suffix(name, suffix) + && ( + // In case of "**/*extension" a simple suffix match is sufficient. + !needs_dir_anchor + // But for "**/filename" we need to ensure that path is either "filename"... + || name.len() == suffix.len() + // ...or that it is ".../filename". + || is_separator(name[name.len() - suffix.len() - 1] as char) + ), + ) +} + +fn contains_magic(pattern: &[u8]) -> bool { + pattern.contains(&b'*') +} + +fn match_path_suffix(path: &[u8], suffix: &[u8]) -> bool { + if path.len() < suffix.len() { + return false; + } + + let path = &path[path.len() - suffix.len()..]; + + #[cfg(windows)] + { + path.iter().zip(suffix.iter()).all(|(a, b)| { + let a = if *a == b'\\' { b'/' } else { *a }; + let b = if *b == b'\\' { b'/' } else { *b }; + a.eq_ignore_ascii_case(&b) + }) + } + + #[cfg(not(windows))] + path.eq_ignore_ascii_case(suffix) +} + +// This code is based on https://research.swtch.com/glob.go +// It's not particularly fast, but it doesn't need to be. It doesn't run often. +#[cold] +fn slow_path(pattern: &[u8], name: &[u8]) -> bool { + let mut px = 0; + let mut nx = 0; + let mut next_px = 0; + let mut next_nx = 0; + let mut is_double_star = false; + + while px < pattern.len() || nx < name.len() { + if px < pattern.len() { + match pattern[px] { + b'*' => { + // Try to match at nx. If that doesn't work out, restart at nx+1 next. + next_px = px; + next_nx = nx + 1; + px += 1; + is_double_star = false; + + if px < pattern.len() && pattern[px] == b'*' { + px += 1; + is_double_star = true; + + // For convenience, /**/ also matches / + if px >= 3 + && px < pattern.len() + && pattern[px] == b'/' + && pattern[px - 3] == b'/' + { + px += 1; + } + } + continue; + } + c => { + if nx < name.len() && name[nx].eq_ignore_ascii_case(&c) { + px += 1; + nx += 1; + continue; + } + } + } + } + + // Mismatch. Maybe restart. + if next_nx > 0 + && next_nx <= name.len() + && (is_double_star || !is_separator(name[next_nx - 1] as char)) + { + px = next_px; + nx = next_nx; + continue; + } + + return false; + } + + true +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_glob_match() { + let tests = [ + // Test cases from https://research.swtch.com/glob.go + ("", "", true), + ("x", "", false), + ("", "x", false), + ("abc", "abc", true), + ("*", "abc", true), + ("*c", "abc", true), + ("*b", "abc", false), + ("a*", "abc", true), + ("b*", "abc", false), + ("a*", "a", true), + ("*a", "a", true), + ("a*b*c*d*e*", "axbxcxdxe", true), + ("a*b*c*d*e*", "axbxcxdxexxx", true), + ("*x", "xxx", true), + // Test cases from https://github.com/golang/go/blob/master/src/path/filepath/match_test.go + ("a*", "ab/c", false), + ("a*b", "a/b", false), + ("a*/b", "abc/b", true), + ("a*/b", "a/c/b", false), + ("a*b*c*d*e*/f", "axbxcxdxe/f", true), + ("a*b*c*d*e*/f", "axbxcxdxexxx/f", true), + ("a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false), + ("a*b*c*d*e*/f", "axbxcxdxexxx/fff", false), + // Single star (*) + // - Empty string + ("*", "", true), + // - Anything else is covered above + // Double star (**) + // - Empty string + ("**", "", true), + ("a**", "a", true), + ("**a", "a", true), + // - Prefix + ("**", "abc", true), + ("**", "foo/baz/bar", true), + ("**c", "abc", true), + ("**b", "abc", false), + // - Infix + ("a**c", "ac", true), + ("a**c", "abc", true), + ("a**c", "abd", false), + ("a**d", "abc", false), + ("a**c", "a/bc", true), + ("a**c", "ab/c", true), + ("a**c", "a/b/c", true), + // -- Infix with left separator + ("a/**c", "ac", false), + ("a/**c", "a/c", true), + ("a/**c", "b/c", false), + ("a/**c", "a/d", false), + ("a/**c", "a/b/c", true), + ("a/**c", "a/b/d", false), + ("a/**c", "d/b/c", false), + // -- Infix with right separator + ("a**/c", "ac", false), + ("a**/c", "a/c", true), + ("a**/c", "b/c", false), + ("a**/c", "a/d", false), + ("a**/c", "a/b/c", true), + ("a**/c", "a/b/d", false), + ("a**/c", "d/b/c", false), + // - Infix with two separators + ("a/**/c", "ac", false), + ("a/**/c", "a/c", true), + ("a/**/c", "b/c", false), + ("a/**/c", "a/d", false), + ("a/**/c", "a/b/c", true), + ("a/**/c", "a/b/d", false), + ("a/**/c", "d/b/c", false), + // - * + * is covered above + // - * + ** + ("a*b**c", "abc", true), + ("a*b**c", "aXbYc", true), + ("a*b**c", "aXb/Yc", true), + ("a*b**c", "aXbY/Yc", true), + ("a*b**c", "aXb/Y/c", true), + ("a*b**c", "a/XbYc", false), + ("a*b**c", "aX/XbYc", false), + ("a*b**c", "a/X/bYc", false), + // - ** + * + ("a**b*c", "abc", true), + ("a**b*c", "aXbYc", true), + ("a**b*c", "aXb/Yc", false), + ("a**b*c", "aXbY/Yc", false), + ("a**b*c", "aXb/Y/c", false), + ("a**b*c", "a/XbYc", true), + ("a**b*c", "aX/XbYc", true), + ("a**b*c", "a/X/bYc", true), + // - ** + ** + ("a**b**c", "abc", true), + ("a**b**c", "aXbYc", true), + ("a**b**c", "aXb/Yc", true), + ("a**b**c", "aXbY/Yc", true), + ("a**b**c", "aXb/Y/c", true), + ("a**b**c", "aXbYc", true), + ("a**b**c", "a/XbYc", true), + ("a**b**c", "aX/XbYc", true), + ("a**b**c", "a/X/bYc", true), + // Case insensitivity + ("*.txt", "file.TXT", true), + ("**/*.rs", "dir/file.RS", true), + // Optimized patterns: **/*.ext and **/name + ("**/*.rs", "foo.rs", true), + ("**/*.rs", "dir/foo.rs", true), + ("**/*.rs", "dir/sub/foo.rs", true), + ("**/*.rs", "foo.txt", false), + ("**/*.rs", "dir/foo.txt", false), + ("**/Cargo.toml", "Cargo.toml", true), + ("**/Cargo.toml", "dir/Cargo.toml", true), + ("**/Cargo.toml", "dir/sub/Cargo.toml", true), + ("**/Cargo.toml", "Cargo.lock", false), + ("**/Cargo.toml", "dir/Cargo.lock", false), + ]; + + for (pattern, name, expected) in tests { + let result = glob_match(pattern, name); + assert_eq!( + result, expected, + "test case ({:?}, {:?}, {}) failed, got {}", + pattern, name, expected, result + ); + } + } +} diff --git a/crates/edit/src/lib.rs b/crates/edit/src/lib.rs index 126cbce525f..ca19efca1a1 100644 --- a/crates/edit/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -25,6 +25,7 @@ pub mod clipboard; pub mod document; pub mod framebuffer; pub mod fuzzy; +pub mod glob; pub mod hash; pub mod helpers; pub mod icu; From 5787514d0d92343489a0c28c1a277a520e36406d Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 23 Jan 2026 13:57:42 +0100 Subject: [PATCH 25/27] Clean up error handling (#745) * Use `io::Error` for OS error handling (This results in some added bloat, but is more convenient for now.) * Add an `icu` error type * Move `apperr` from the library into the binary This also sneaks in a minor improvement to sorting. The only two places we need to sort, don't need stable sorting. (Unstable sorting is a lot faster and simpler.) --- crates/edit/Cargo.toml | 1 - crates/edit/src/apperr.rs | 51 -------- crates/edit/src/bin/edit/documents.rs | 11 +- crates/edit/src/bin/edit/draw_editor.rs | 2 +- crates/edit/src/bin/edit/draw_filepicker.rs | 2 +- crates/edit/src/bin/edit/draw_statusbar.rs | 2 +- crates/edit/src/bin/edit/localization.rs | 2 +- crates/edit/src/bin/edit/main.rs | 6 +- crates/edit/src/bin/edit/state.rs | 10 +- crates/edit/src/buffer/gap_buffer.rs | 6 +- crates/edit/src/buffer/mod.rs | 68 ++++++---- crates/edit/src/clipboard.rs | 3 + crates/edit/src/document.rs | 3 +- crates/edit/src/helpers.rs | 137 +------------------- crates/edit/src/icu.rs | 86 ++++++------ crates/edit/src/lib.rs | 1 - crates/edit/src/sys/unix.rs | 64 ++++----- crates/edit/src/sys/windows.rs | 101 +++------------ crates/edit/src/tui.rs | 17 +-- 19 files changed, 176 insertions(+), 397 deletions(-) delete mode 100644 crates/edit/src/apperr.rs diff --git a/crates/edit/Cargo.toml b/crates/edit/Cargo.toml index c7bc03f0afb..fcf8edeb79d 100644 --- a/crates/edit/Cargo.toml +++ b/crates/edit/Cargo.toml @@ -40,7 +40,6 @@ features = [ "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", - "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_LibraryLoader", "Win32_System_Threading", diff --git a/crates/edit/src/apperr.rs b/crates/edit/src/apperr.rs deleted file mode 100644 index 8427f2f19e9..00000000000 --- a/crates/edit/src/apperr.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Provides a transparent error type for edit. - -use std::alloc::AllocError; -use std::{io, result}; - -use crate::sys; - -pub const APP_ICU_MISSING: Error = Error::new_app(0); - -/// Edit's transparent `Result` type. -pub type Result = result::Result; - -/// Edit's transparent `Error` type. -/// Abstracts over system and application errors. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Error { - App(u32), - Icu(u32), - Sys(u32), -} - -impl Error { - pub const fn new_app(code: u32) -> Self { - Self::App(code) - } - - pub const fn new_icu(code: u32) -> Self { - Self::Icu(code) - } - - pub const fn new_sys(code: u32) -> Self { - Self::Sys(code) - } -} - -impl From for Error { - fn from(err: io::Error) -> Self { - sys::io_error_to_apperr(err) - } -} - -impl From for Error { - fn from(_: AllocError) -> Self { - // TODO: Technically this breaks if the AllocError isn't recent. By then, the errno may - // have been tained. But the stdlib AllocError is a bad type with no way to carry info. - sys::get_last_error() - } -} diff --git a/crates/edit/src/bin/edit/documents.rs b/crates/edit/src/bin/edit/documents.rs index 4366417af62..eabf798678f 100644 --- a/crates/edit/src/bin/edit/documents.rs +++ b/crates/edit/src/bin/edit/documents.rs @@ -3,14 +3,15 @@ use std::collections::LinkedList; use std::ffi::OsStr; -use std::fs; use std::fs::File; use std::path::{Path, PathBuf}; +use std::{fs, io}; use edit::buffer::{RcTextBuffer, TextBuffer}; use edit::helpers::{CoordType, Point}; -use edit::{apperr, path, sys}; +use edit::{path, sys}; +use crate::apperr; use crate::state::DisplayablePathBuf; pub struct Document { @@ -144,10 +145,10 @@ impl DocumentManager { let (path, goto) = Self::parse_filename_goto(path); let path = path::normalize(path); - let mut file = match Self::open_for_reading(&path) { + let mut file = match File::open(&path) { Ok(file) => Some(file), - Err(err) if sys::apperr_is_not_found(err) => None, - Err(err) => return Err(err), + Err(err) if err.kind() == io::ErrorKind::NotFound => None, + Err(err) => return Err(err.into()), }; let file_id = if file.is_some() { Some(sys::file_id(file.as_ref(), &path)?) } else { None }; diff --git a/crates/edit/src/bin/edit/draw_editor.rs b/crates/edit/src/bin/edit/draw_editor.rs index 94f7dbfc50f..479c053ac4d 100644 --- a/crates/edit/src/bin/edit/draw_editor.rs +++ b/crates/edit/src/bin/edit/draw_editor.rs @@ -38,7 +38,7 @@ pub fn draw_editor(ctx: &mut Context, state: &mut State) { fn draw_search(ctx: &mut Context, state: &mut State) { if let Err(err) = icu::init() { - error_log_add(ctx, state, err); + error_log_add(ctx, state, err.into()); state.wants_search.kind = StateSearchKind::Disabled; return; } diff --git a/crates/edit/src/bin/edit/draw_filepicker.rs b/crates/edit/src/bin/edit/draw_filepicker.rs index 94adb033129..d6e9d3b73f3 100644 --- a/crates/edit/src/bin/edit/draw_filepicker.rs +++ b/crates/edit/src/bin/edit/draw_filepicker.rs @@ -341,7 +341,7 @@ fn draw_dialog_saveas_refresh_files(state: &mut State) { } for entries in &mut dirs_files[1..] { - entries.sort_by(|a, b| { + entries.sort_unstable_by(|a, b| { let a = a.as_bytes(); let b = b.as_bytes(); diff --git a/crates/edit/src/bin/edit/draw_statusbar.rs b/crates/edit/src/bin/edit/draw_statusbar.rs index 8250e44daed..935e158e176 100644 --- a/crates/edit/src/bin/edit/draw_statusbar.rs +++ b/crates/edit/src/bin/edit/draw_statusbar.rs @@ -302,7 +302,7 @@ fn encoding_picker_update_list(state: &mut State) { } } - matches.sort_by(|a, b| b.0.cmp(&a.0)); + matches.sort_unstable_by(|a, b| b.0.cmp(&a.0)); state.encoding_picker_results = Some(Vec::from_iter(matches.iter().map(|(_, enc)| *enc))); } diff --git a/crates/edit/src/bin/edit/localization.rs b/crates/edit/src/bin/edit/localization.rs index 2e3eed8943e..fa22dc89c45 100644 --- a/crates/edit/src/bin/edit/localization.rs +++ b/crates/edit/src/bin/edit/localization.rs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use edit::helpers::AsciiStringHelpers; use edit::sys; +use stdext::AsciiStringHelpers as _; use stdext::arena::scratch_arena; include!(concat!(env!("OUT_DIR"), "/i18n_edit.rs")); diff --git a/crates/edit/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs index 7e02ec82b41..a05756a009c 100644 --- a/crates/edit/src/bin/edit/main.rs +++ b/crates/edit/src/bin/edit/main.rs @@ -3,6 +3,7 @@ #![feature(allocator_api, linked_list_cursors, string_from_utf8_lossy_owned)] +mod apperr; mod documents; mod draw_editor; mod draw_filepicker; @@ -26,7 +27,7 @@ use edit::input::{self, kbmod, vk}; use edit::oklab::StraightRgba; use edit::tui::*; use edit::vt::{self, Token}; -use edit::{apperr, base64, path, sys, unicode}; +use edit::{base64, path, sys, unicode}; use localization::*; use state::*; use stdext::arena::{self, Arena, ArenaString, scratch_arena}; @@ -37,6 +38,9 @@ const SCRATCH_ARENA_CAPACITY: usize = 128 * MEBI; #[cfg(target_pointer_width = "64")] const SCRATCH_ARENA_CAPACITY: usize = 512 * MEBI; +// NOTE: Before our main() gets called, Rust initializes its stdlib. This pulls in the entire +// std::io::{stdin, stdout, stderr} machinery, and probably some more, which amounts to about 20KB. +// It can technically be avoided nowadays with `#![no_main]`. Maybe a fun project for later? :) fn main() -> process::ExitCode { if cfg!(debug_assertions) { let hook = std::panic::take_hook(); diff --git a/crates/edit/src/bin/edit/state.rs b/crates/edit/src/bin/edit/state.rs index 451060bf6db..c8d45bd8ca6 100644 --- a/crates/edit/src/bin/edit/state.rs +++ b/crates/edit/src/bin/edit/state.rs @@ -10,8 +10,9 @@ use edit::framebuffer::IndexedColor; use edit::helpers::*; use edit::oklab::StraightRgba; use edit::tui::*; -use edit::{apperr, buffer, icu, sys}; +use edit::{buffer, icu}; +use crate::apperr; use crate::documents::DocumentManager; use crate::localization::*; @@ -27,10 +28,9 @@ impl From for FormatApperr { impl std::fmt::Display for FormatApperr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.0 { - apperr::APP_ICU_MISSING => f.write_str(loc(LocId::ErrorIcuMissing)), - apperr::Error::App(code) => write!(f, "Unknown app error code: {code}"), - apperr::Error::Icu(code) => icu::apperr_format(f, code), - apperr::Error::Sys(code) => sys::apperr_format(f, code), + apperr::Error::Icu(icu::ICU_MISSING_ERROR) => f.write_str(loc(LocId::ErrorIcuMissing)), + apperr::Error::Icu(ref err) => err.fmt(f), + apperr::Error::Io(ref err) => err.fmt(f), } } } diff --git a/crates/edit/src/buffer/gap_buffer.rs b/crates/edit/src/buffer/gap_buffer.rs index 4f84d712638..401d0f08442 100644 --- a/crates/edit/src/buffer/gap_buffer.rs +++ b/crates/edit/src/buffer/gap_buffer.rs @@ -3,11 +3,11 @@ use std::ops::Range; use std::ptr::{self, NonNull}; -use std::slice; +use std::{io, slice}; use stdext::sys::{virtual_commit, virtual_release, virtual_reserve}; +use stdext::{ReplaceRange as _, slice_copy_safe}; -use crate::apperr; use crate::document::{ReadableDocument, WriteableDocument}; use crate::helpers::*; @@ -64,7 +64,7 @@ pub struct GapBuffer { } impl GapBuffer { - pub fn new(small: bool) -> apperr::Result { + pub fn new(small: bool) -> io::Result { let reserve; let buffer; let text; diff --git a/crates/edit/src/buffer/mod.rs b/crates/edit/src/buffer/mod.rs index 5f4ca052f24..251fa401d14 100644 --- a/crates/edit/src/buffer/mod.rs +++ b/crates/edit/src/buffer/mod.rs @@ -28,7 +28,7 @@ use std::cell::UnsafeCell; use std::collections::LinkedList; use std::fmt::Write as _; use std::fs::File; -use std::io::{Read as _, Write as _}; +use std::io::{self, Read as _, Write as _}; use std::mem::{self, MaybeUninit}; use std::ops::Range; use std::rc::Rc; @@ -36,6 +36,7 @@ use std::str; pub use gap_buffer::GapBuffer; use stdext::arena::{Arena, ArenaString, scratch_arena}; +use stdext::{ReplaceRange as _, minmax, slice_as_uninit_mut, slice_copy_safe}; use crate::cell::SemiRefCell; use crate::clipboard::Clipboard; @@ -45,7 +46,7 @@ use crate::helpers::*; use crate::oklab::StraightRgba; use crate::simd::memchr2; use crate::unicode::{self, Cursor, MeasurementConfig, Utf8Chars}; -use crate::{apperr, icu, simd}; +use crate::{icu, simd}; /// The margin template is used for line numbers. /// The max. line number we should ever expect is probably 64-bit, @@ -59,6 +60,25 @@ const VISUAL_SPACE_PREFIX_ADD: usize = '・'.len_utf8() - 1; const VISUAL_TAB: &str = "→ "; const VISUAL_TAB_PREFIX_ADD: usize = '→'.len_utf8() - 1; +pub enum IoError { + Io(io::Error), + Icu(icu::Error), +} + +pub type IoResult = std::result::Result; + +impl From for IoError { + fn from(err: io::Error) -> Self { + Self::Io(err) + } +} + +impl From for IoError { + fn from(err: icu::Error) -> Self { + Self::Icu(err) + } +} + /// Stores statistics about the whole document. #[derive(Copy, Clone)] pub struct TextBufferStatistics { @@ -250,14 +270,14 @@ pub struct TextBuffer { impl TextBuffer { /// Creates a new text buffer inside an [`Rc`]. /// See [`TextBuffer::new()`]. - pub fn new_rc(small: bool) -> apperr::Result { + pub fn new_rc(small: bool) -> io::Result { let buffer = Self::new(small)?; Ok(Rc::new(SemiRefCell::new(buffer))) } /// Creates a new text buffer. With `small` you can control /// if the buffer is optimized for <1MiB contents. - pub fn new(small: bool) -> apperr::Result { + pub fn new(small: bool) -> io::Result { Ok(Self { buffer: GapBuffer::new(small)?, @@ -665,11 +685,7 @@ impl TextBuffer { } /// Reads a file from disk into the text buffer, detecting encoding and BOM. - pub fn read_file( - &mut self, - file: &mut File, - encoding: Option<&'static str>, - ) -> apperr::Result<()> { + pub fn read_file(&mut self, file: &mut File, encoding: Option<&'static str>) -> IoResult<()> { let scratch = scratch_arena(None); let mut buf = scratch.alloc_uninit().transpose(); let mut first_chunk_len = 0; @@ -818,7 +834,7 @@ impl TextBuffer { buf: &mut [MaybeUninit; 4 * KIBI], first_chunk_len: usize, done: bool, - ) -> apperr::Result<()> { + ) -> io::Result<()> { { let mut first_chunk = unsafe { buf[..first_chunk_len].assume_init_ref() }; if first_chunk.starts_with(b"\xEF\xBB\xBF") { @@ -872,7 +888,7 @@ impl TextBuffer { buf: &mut [MaybeUninit; 4 * KIBI], first_chunk_len: usize, mut done: bool, - ) -> apperr::Result<()> { + ) -> IoResult<()> { let scratch = scratch_arena(None); let pivot_buffer = scratch.alloc_uninit_slice(4 * KIBI); let mut c = icu::Converter::new(pivot_buffer, self.encoding, "UTF-8")?; @@ -931,7 +947,7 @@ impl TextBuffer { } /// Writes the text buffer contents to a file, handling BOM and encoding. - pub fn write_file(&mut self, file: &mut File) -> apperr::Result<()> { + pub fn write_file(&mut self, file: &mut File) -> IoResult<()> { let mut offset = 0; if self.encoding.starts_with("UTF-8") { @@ -954,7 +970,7 @@ impl TextBuffer { Ok(()) } - fn write_file_with_icu(&mut self, file: &mut File) -> apperr::Result<()> { + fn write_file_with_icu(&mut self, file: &mut File) -> IoResult<()> { let scratch = scratch_arena(None); let pivot_buffer = scratch.alloc_uninit_slice(4 * KIBI); let buf = scratch.alloc_uninit_slice(4 * KIBI); @@ -1080,7 +1096,7 @@ impl TextBuffer { } /// Find the next occurrence of the given `pattern` and select it. - pub fn find_and_select(&mut self, pattern: &str, options: SearchOptions) -> apperr::Result<()> { + pub fn find_and_select(&mut self, pattern: &str, options: SearchOptions) -> icu::Result<()> { if let Some(search) = &mut self.search { let search = search.get_mut(); // When the search input changes we must reset the search. @@ -1138,7 +1154,7 @@ impl TextBuffer { pattern: &str, options: SearchOptions, replacement: &[u8], - ) -> apperr::Result<()> { + ) -> icu::Result<()> { // Editors traditionally replace the previous search hit, not the next possible one. if let (Some(search), Some(..)) = (&self.search, &self.selection) { let search = unsafe { &mut *search.get() }; @@ -1161,7 +1177,7 @@ impl TextBuffer { pattern: &str, options: SearchOptions, replacement: &[u8], - ) -> apperr::Result<()> { + ) -> icu::Result<()> { let scratch = scratch_arena(None); let mut search = self.find_construct_search(pattern, options)?; let mut offset = 0; @@ -1186,9 +1202,9 @@ impl TextBuffer { &self, pattern: &str, options: SearchOptions, - ) -> apperr::Result { + ) -> icu::Result { if pattern.is_empty() { - return Err(apperr::Error::Icu(1)); // U_ILLEGAL_ARGUMENT_ERROR + return Err(icu::ILLEGAL_ARGUMENT_ERROR); } let sanitized_pattern = if options.whole_word && options.use_regex { @@ -1714,13 +1730,11 @@ impl TextBuffer { return None; } - let scratch = scratch_arena(None); let width = destination.width(); let height = destination.height(); let line_number_width = self.margin_width.max(3) as usize - 3; let text_width = width - self.margin_width; let mut visualizer_buf = [0xE2, 0x90, 0x80]; // U+2400 in UTF8 - let mut line = ArenaString::new_in(&scratch); let mut visual_pos_x_max = 0; // Pick the cursor closer to the `origin.y`. @@ -1737,10 +1751,10 @@ impl TextBuffer { Some(TextBufferSelection { beg, end }) => minmax(beg, end), }; - line.reserve(width as usize * 2); - for y in 0..height { - line.clear(); + let scratch = scratch_arena(None); + let mut line = ArenaString::new_in(&scratch); + line.reserve(width as usize * 2); let visual_line = origin.y + y; let mut cursor_beg = @@ -2717,6 +2731,7 @@ impl TextBuffer { fn undo_redo(&mut self, undo: bool) { let buffer_generation = self.buffer.generation(); let mut entry_buffer_generation = None; + let mut damage_start = CoordType::MAX; loop { // Transfer the last entry from the undo stack to the redo stack or vice versa. @@ -2761,6 +2776,8 @@ impl TextBuffer { cursor }; + damage_start = damage_start.min(cursor.logical_pos.y); + { let mut change = change.borrow_mut(); let change = &mut *change; @@ -2830,6 +2847,11 @@ impl TextBuffer { } } + if damage_start == CoordType::MAX { + // There weren't any undo/redo entries. + return; + } + if entry_buffer_generation.is_some() { self.recalc_after_content_changed(); } diff --git a/crates/edit/src/clipboard.rs b/crates/edit/src/clipboard.rs index 413de71fbd9..741931e25bb 100644 --- a/crates/edit/src/clipboard.rs +++ b/crates/edit/src/clipboard.rs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + //! Clipboard facilities for the editor. /// The builtin, internal clipboard of the editor. diff --git a/crates/edit/src/document.rs b/crates/edit/src/document.rs index c7def366d30..0059a959772 100644 --- a/crates/edit/src/document.rs +++ b/crates/edit/src/document.rs @@ -8,10 +8,9 @@ use std::mem; use std::ops::Range; use std::path::PathBuf; +use stdext::ReplaceRange as _; use stdext::arena::{ArenaString, scratch_arena}; -use crate::helpers::ReplaceRange as _; - /// An abstraction over reading from text containers. pub trait ReadableDocument { /// Read some bytes starting at (including) the given absolute offset. diff --git a/crates/edit/src/helpers.rs b/crates/edit/src/helpers.rs index 40590733da9..756292b8575 100644 --- a/crates/edit/src/helpers.rs +++ b/crates/edit/src/helpers.rs @@ -3,14 +3,10 @@ //! Random assortment of helpers I didn't know where to put. -use std::alloc::Allocator; use std::cmp::Ordering; -use std::io::Read; -use std::mem::{self, MaybeUninit}; -use std::ops::{Bound, Range, RangeBounds}; -use std::{fmt, ptr, slice, str}; - -use crate::apperr; +use std::io::{self, Read}; +use std::mem::MaybeUninit; +use std::{fmt, slice}; pub const KILO: usize = 1000; pub const MEGA: usize = 1000 * 1000; @@ -161,136 +157,11 @@ where if v2 < v1 { [v2, v1] } else { [v1, v2] } } -#[inline(always)] -#[allow(clippy::ptr_eq)] -pub fn opt_ptr(a: Option<&T>) -> *const T { - unsafe { mem::transmute(a) } -} - -/// Surprisingly, there's no way in Rust to do a `ptr::eq` on `Option<&T>`. -/// Uses `unsafe` so that the debug performance isn't too bad. -#[inline(always)] -#[allow(clippy::ptr_eq)] -pub fn opt_ptr_eq(a: Option<&T>, b: Option<&T>) -> bool { - opt_ptr(a) == opt_ptr(b) -} - -/// Creates a `&str` from a pointer and a length. -/// Exists, because `std::str::from_raw_parts` is unstable, par for the course. -/// -/// # Safety -/// -/// The given data must be valid UTF-8. -/// The given data must outlive the returned reference. -#[inline] -#[must_use] -pub const unsafe fn str_from_raw_parts<'a>(ptr: *const u8, len: usize) -> &'a str { - unsafe { str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)) } -} - -/// [`<[T]>::copy_from_slice`] panics if the two slices have different lengths. -/// This one just returns the copied amount. -pub fn slice_copy_safe(dst: &mut [T], src: &[T]) -> usize { - let len = src.len().min(dst.len()); - unsafe { ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), len) }; - len -} - -/// [`Vec::splice`] results in really bad assembly. -/// This doesn't. Don't use [`Vec::splice`]. -pub trait ReplaceRange { - fn replace_range>(&mut self, range: R, src: &[T]); -} - -impl ReplaceRange for Vec { - fn replace_range>(&mut self, range: R, src: &[T]) { - let start = match range.start_bound() { - Bound::Included(&start) => start, - Bound::Excluded(start) => start + 1, - Bound::Unbounded => 0, - }; - let end = match range.end_bound() { - Bound::Included(end) => end + 1, - Bound::Excluded(&end) => end, - Bound::Unbounded => usize::MAX, - }; - vec_replace_impl(self, start..end, src); - } -} - -fn vec_replace_impl(dst: &mut Vec, range: Range, src: &[T]) { - unsafe { - let dst_len = dst.len(); - let src_len = src.len(); - let off = range.start.min(dst_len); - let del_len = range.end.saturating_sub(off).min(dst_len - off); - - if del_len == 0 && src_len == 0 { - return; // nothing to do - } - - let tail_len = dst_len - off - del_len; - let new_len = dst_len - del_len + src_len; - - if src_len > del_len { - dst.reserve(src_len - del_len); - } - - // NOTE: drop_in_place() is not needed here, because T is constrained to Copy. - - // SAFETY: as_mut_ptr() must called after reserve() to ensure that the pointer is valid. - let ptr = dst.as_mut_ptr().add(off); - - // Shift the tail. - if tail_len > 0 && src_len != del_len { - ptr::copy(ptr.add(del_len), ptr.add(src_len), tail_len); - } - - // Copy in the replacement. - ptr::copy_nonoverlapping(src.as_ptr(), ptr, src_len); - dst.set_len(new_len); - } -} - /// [`Read`] but with [`MaybeUninit`] buffers. -pub fn file_read_uninit( - file: &mut T, - buf: &mut [MaybeUninit], -) -> apperr::Result { +pub fn file_read_uninit(file: &mut T, buf: &mut [MaybeUninit]) -> io::Result { unsafe { let buf_slice = slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()); let n = file.read(buf_slice)?; Ok(n) } } - -/// Turns a [`&[u8]`] into a [`&[MaybeUninit]`]. -#[inline(always)] -pub const fn slice_as_uninit_ref(slice: &[T]) -> &[MaybeUninit] { - unsafe { slice::from_raw_parts(slice.as_ptr() as *const MaybeUninit, slice.len()) } -} - -/// Turns a [`&mut [T]`] into a [`&mut [MaybeUninit]`]. -#[inline(always)] -pub const fn slice_as_uninit_mut(slice: &mut [T]) -> &mut [MaybeUninit] { - unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut MaybeUninit, slice.len()) } -} - -/// Helpers for ASCII string comparisons. -pub trait AsciiStringHelpers { - /// Tests if a string starts with a given ASCII prefix. - /// - /// This function name really is a mouthful, but it's a combination - /// of [`str::starts_with`] and [`str::eq_ignore_ascii_case`]. - fn starts_with_ignore_ascii_case(&self, prefix: &str) -> bool; -} - -impl AsciiStringHelpers for str { - fn starts_with_ignore_ascii_case(&self, prefix: &str) -> bool { - // Casting to bytes first ensures we skip any UTF8 boundary checks. - // Since the comparison is ASCII, we don't need to worry about that. - let s = self.as_bytes(); - let p = prefix.as_bytes(); - p.len() <= s.len() && s[..p.len()].eq_ignore_ascii_case(p) - } -} diff --git a/crates/edit/src/icu.rs b/crates/edit/src/icu.rs index 1be796019f7..8b8f783eeee 100644 --- a/crates/edit/src/icu.rs +++ b/crates/edit/src/icu.rs @@ -5,17 +5,54 @@ use std::cmp::Ordering; use std::ffi::{CStr, c_char}; -use std::mem; use std::mem::MaybeUninit; use std::ops::Range; use std::ptr::{null, null_mut}; +use std::{fmt, mem}; use stdext::arena::{Arena, ArenaString, scratch_arena}; use stdext::arena_format; use crate::buffer::TextBuffer; +use crate::sys; use crate::unicode::Utf8Chars; -use crate::{apperr, sys}; + +pub(crate) const ILLEGAL_ARGUMENT_ERROR: Error = Error(1); // U_ILLEGAL_ARGUMENT_ERROR +pub const ICU_MISSING_ERROR: Error = Error(0); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Error(u32); + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn format(code: u32) -> &'static str { + let Ok(f) = init_if_needed() else { + return ""; + }; + + let status = icu_ffi::UErrorCode::new(code); + let ptr = unsafe { (f.u_errorName)(status) }; + if ptr.is_null() { + return ""; + } + + let str = unsafe { CStr::from_ptr(ptr) }; + str.to_str().unwrap_or("") + } + + let code = self.0; + if code != 0 + && let msg = format(code) + && !msg.is_empty() + { + write!(f, "ICU Error: {msg}") + } else { + write!(f, "ICU Error: {code:#08x}") + } + } +} + +pub type Result = std::result::Result; #[derive(Clone, Copy)] pub struct Encoding { @@ -93,31 +130,6 @@ pub fn get_available_encodings() -> &'static Encodings { } } -/// Formats the given ICU error code into a human-readable string. -pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result { - fn format(code: u32) -> &'static str { - let Ok(f) = init_if_needed() else { - return ""; - }; - - let status = icu_ffi::UErrorCode::new(code); - let ptr = unsafe { (f.u_errorName)(status) }; - if ptr.is_null() { - return ""; - } - - let str = unsafe { CStr::from_ptr(ptr) }; - str.to_str().unwrap_or("") - } - - let msg = format(code); - if !msg.is_empty() { - write!(f, "ICU Error: {msg}") - } else { - write!(f, "ICU Error: {code:#08x}") - } -} - /// Converts between two encodings using ICU. pub struct Converter<'pivot> { source: *mut icu_ffi::UConverter, @@ -149,7 +161,7 @@ impl<'pivot> Converter<'pivot> { pivot_buffer: &'pivot mut [MaybeUninit], source_encoding: &str, target_encoding: &str, - ) -> apperr::Result { + ) -> Result { let f = init_if_needed()?; let arena = scratch_arena(None); @@ -197,7 +209,7 @@ impl<'pivot> Converter<'pivot> { &mut self, input: &[u8], output: &mut [MaybeUninit], - ) -> apperr::Result<(usize, usize)> { + ) -> Result<(usize, usize)> { let f = assume_loaded(); let input_beg = input.as_ptr(); @@ -303,7 +315,7 @@ impl Text { /// /// The caller must ensure that the given [`TextBuffer`] /// outlives the returned `Text` instance. - pub unsafe fn new(tb: &TextBuffer) -> apperr::Result { + pub unsafe fn new(tb: &TextBuffer) -> Result { let f = init_if_needed()?; let mut status = icu_ffi::U_ZERO_ERROR; @@ -623,7 +635,7 @@ impl Regex { /// # Safety /// /// The caller must ensure that the given `Text` outlives the returned `Regex` instance. - pub unsafe fn new(pattern: &str, flags: i32, text: &Text) -> apperr::Result { + pub unsafe fn new(pattern: &str, flags: i32, text: &Text) -> Result { let f = init_if_needed()?; unsafe { let scratch = scratch_arena(None); @@ -968,13 +980,13 @@ enum LibraryFunctionsState { static mut LIBRARY_FUNCTIONS: LibraryFunctionsState = LibraryFunctionsState::Uninitialized; -pub fn init() -> apperr::Result<()> { +pub fn init() -> Result<()> { init_if_needed()?; Ok(()) } #[allow(static_mut_refs)] -fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> { +fn init_if_needed() -> Result<&'static LibraryFunctions> { #[cold] fn load() { unsafe { @@ -1045,7 +1057,7 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> { match unsafe { &LIBRARY_FUNCTIONS } { LibraryFunctionsState::Loaded(f) => Ok(f), - _ => Err(apperr::APP_ICU_MISSING), + _ => Err(ICU_MISSING_ERROR), } } @@ -1062,7 +1074,7 @@ mod icu_ffi { use std::ffi::{c_char, c_int, c_void}; - use crate::apperr; + use super::Error; #[derive(Copy, Clone, Eq, PartialEq)] #[repr(transparent)] @@ -1081,9 +1093,9 @@ mod icu_ffi { self.0 > 0 } - pub fn as_error(&self) -> apperr::Error { + pub fn as_error(&self) -> Error { debug_assert!(self.0 > 0); - apperr::Error::new_icu(self.0 as u32) + Error(self.0 as u32) } } diff --git a/crates/edit/src/lib.rs b/crates/edit/src/lib.rs index ca19efca1a1..6dea3a7380b 100644 --- a/crates/edit/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -17,7 +17,6 @@ )] #![allow(clippy::missing_transmute_annotations, clippy::new_without_default, stable_features)] -pub mod apperr; pub mod base64; pub mod buffer; pub mod cell; diff --git a/crates/edit/src/sys/unix.rs b/crates/edit/src/sys/unix.rs index 07514325d5c..ba9d06fde64 100644 --- a/crates/edit/src/sys/unix.rs +++ b/crates/edit/src/sys/unix.rs @@ -12,12 +12,11 @@ use std::mem::{self, ManuallyDrop, MaybeUninit}; use std::os::fd::{AsRawFd as _, FromRawFd as _}; use std::path::Path; use std::ptr::{NonNull, null_mut}; -use std::{thread, time}; +use std::{io, thread, time}; use stdext::arena::{Arena, ArenaString, scratch_arena}; use stdext::arena_format; -use crate::apperr; use crate::helpers::*; struct State { @@ -51,7 +50,7 @@ pub fn init() -> Deinit { Deinit } -pub fn switch_modes() -> apperr::Result<()> { +pub fn switch_modes() -> io::Result<()> { unsafe { // Reopen stdin if it's redirected (= piped input). if libc::isatty(STATE.stdin) == 0 { @@ -352,7 +351,7 @@ pub struct FileId { } /// Returns a unique identifier for the given file by handle or path. -pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result { +pub fn file_id(file: Option<&File>, path: &Path) -> io::Result { let file = match file { Some(f) => f, None => &File::open(path)?, @@ -366,10 +365,10 @@ pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result { } } -unsafe fn load_library(name: *const c_char) -> apperr::Result> { +unsafe fn load_library(name: *const c_char) -> io::Result> { unsafe { NonNull::new(libc::dlopen(name, libc::RTLD_LAZY)) - .ok_or_else(|| errno_to_apperr(libc::ENOENT)) + .ok_or_else(|| from_raw_os_error(libc::ENOENT)) } } @@ -381,14 +380,11 @@ unsafe fn load_library(name: *const c_char) -> apperr::Result> { /// of the function you're loading. No type checks whatsoever are performed. // // It'd be nice to constrain T to std::marker::FnPtr, but that's unstable. -pub unsafe fn get_proc_address( - handle: NonNull, - name: *const c_char, -) -> apperr::Result { +pub unsafe fn get_proc_address(handle: NonNull, name: *const c_char) -> io::Result { unsafe { let sym = libc::dlsym(handle.as_ptr(), name); if sym.is_null() { - Err(errno_to_apperr(libc::ENOENT)) + Err(from_raw_os_error(libc::ENOENT)) } else { Ok(mem::transmute_copy(&sym)) } @@ -400,7 +396,7 @@ pub struct LibIcu { pub libicui18n: NonNull, } -pub fn load_icu() -> apperr::Result { +pub fn load_icu() -> io::Result { const fn const_str_eq(a: &str, b: &str) -> bool { let a = a.as_bytes(); let b = b.as_bytes(); @@ -540,45 +536,29 @@ pub fn preferred_languages(arena: &Arena) -> Vec, &Arena> { } #[inline] -fn errno() -> i32 { +#[cold] +fn errno() -> c_int { + // libc unfortunately doesn't export an alias for `errno` (WHY?). + // As such we (ab)use the stdlib and use its internal errno implementation. + // // Under `-O -Copt-level=s` the 1.87 compiler fails to fully inline and // remove the raw_os_error() call. This leaves us with the drop() call. // ManuallyDrop fixes that and results in a direct `std::sys::os::errno` call. - ManuallyDrop::new(std::io::Error::last_os_error()).raw_os_error().unwrap_or(0) + ManuallyDrop::new(io::Error::last_os_error()).raw_os_error().unwrap_or(0) } +#[inline] #[cold] -pub fn get_last_error() -> apperr::Error { - errno_to_apperr(errno()) +fn last_os_error() -> io::Error { + io::Error::last_os_error() } #[inline] -pub(crate) fn io_error_to_apperr(err: std::io::Error) -> apperr::Error { - errno_to_apperr(err.raw_os_error().unwrap_or(0)) -} - -pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result { - write!(f, "Error {code}")?; - - unsafe { - let ptr = libc::strerror(code as i32); - if !ptr.is_null() { - let msg = CStr::from_ptr(ptr).to_string_lossy(); - write!(f, ": {msg}")?; - } - } - - Ok(()) -} - -pub fn apperr_is_not_found(err: apperr::Error) -> bool { - err == errno_to_apperr(libc::ENOENT) -} - -const fn errno_to_apperr(no: c_int) -> apperr::Error { - apperr::Error::new_sys(if no < 0 { 0 } else { no as u32 }) +#[cold] +fn from_raw_os_error(code: c_int) -> io::Error { + io::Error::from_raw_os_error(code) } -fn check_int_return(ret: libc::c_int) -> apperr::Result { - if ret < 0 { Err(get_last_error()) } else { Ok(ret) } +fn check_int_return(ret: libc::c_int) -> io::Result { + if ret < 0 { Err(last_os_error()) } else { Ok(ret) } } diff --git a/crates/edit/src/sys/windows.rs b/crates/edit/src/sys/windows.rs index 380153b407e..b5475486280 100644 --- a/crates/edit/src/sys/windows.rs +++ b/crates/edit/src/sys/windows.rs @@ -8,17 +8,14 @@ use std::mem::MaybeUninit; use std::os::windows::io::{AsRawHandle as _, FromRawHandle}; use std::path::{Path, PathBuf}; use std::ptr::{self, NonNull, null, null_mut}; -use std::{mem, time}; +use std::{io, mem, time}; use stdext::arena::{Arena, ArenaString, scratch_arena}; -use windows_sys::Win32::Foundation::ERROR_INVALID_PARAMETER; use windows_sys::Win32::Storage::FileSystem; -use windows_sys::Win32::System::Diagnostics::Debug; use windows_sys::Win32::System::{Console, IO, LibraryLoader, Threading}; use windows_sys::Win32::{Foundation, Globalization}; use windows_sys::core::*; -use crate::apperr; use crate::helpers::*; macro_rules! w_env { @@ -69,12 +66,8 @@ unsafe extern "system" fn read_console_input_ex_placeholder( } const CONSOLE_READ_NOWAIT: u16 = 0x0002; - const INVALID_CONSOLE_MODE: u32 = u32::MAX; -// Locally-defined error codes follow the HRESULT format, but they have bit 29 set to indicate that they are Customer error codes. -const ERROR_UNSUPPORTED_LEGACY_CONSOLE: u32 = 0xE0010001; - struct State { read_console_input_ex: ReadConsoleInputExW, stdin: Foundation::HANDLE, @@ -122,7 +115,7 @@ pub fn init() -> Deinit { } /// Switches the terminal into raw mode, etc. -pub fn switch_modes() -> apperr::Result<()> { +pub fn switch_modes() -> io::Result<()> { unsafe { // `kernel32.dll` doesn't exist on OneCore variants of Windows. // NOTE: `kernelbase.dll` is NOT a stable API to rely on. In our case it's the best option though. @@ -130,7 +123,7 @@ pub fn switch_modes() -> apperr::Result<()> { // This is written as two nested `match` statements so that we can return the error from the first // `load_read_func` call if it fails. The kernel32.dll lookup may contain some valid information, // while the kernelbase.dll lookup may not, since it's not a stable API. - unsafe fn load_read_func(module: *const u16) -> apperr::Result { + unsafe fn load_read_func(module: *const u16) -> io::Result { unsafe { get_module(module) .and_then(|m| get_proc_address(m, c"ReadConsoleInputExW".as_ptr())) @@ -161,7 +154,7 @@ pub fn switch_modes() -> apperr::Result<()> { if ptr::eq(STATE.stdin, Foundation::INVALID_HANDLE_VALUE) || ptr::eq(STATE.stdout, Foundation::INVALID_HANDLE_VALUE) { - return Err(get_last_error()); + return Err(last_os_error()); } check_bool_return(Console::GetConsoleMode(STATE.stdin, &raw mut STATE.stdin_mode_old))?; @@ -173,8 +166,8 @@ pub fn switch_modes() -> apperr::Result<()> { | Console::ENABLE_EXTENDED_FLAGS | Console::ENABLE_VIRTUAL_TERMINAL_INPUT, )) { - Err(e) if e == gle_to_apperr(ERROR_INVALID_PARAMETER) => { - Err(apperr::Error::Sys(ERROR_UNSUPPORTED_LEGACY_CONSOLE)) + Err(e) if e.kind() == io::ErrorKind::InvalidInput => { + Err(io::Error::other("This application does not support the legacy console.")) } other => other, }?; @@ -475,7 +468,7 @@ impl PartialEq for FileId { impl Eq for FileId {} /// Returns a unique identifier for the given file by handle or path. -pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result { +pub fn file_id(file: Option<&File>, path: &Path) -> io::Result { let file = match file { Some(f) => f, None => &File::open(path)?, @@ -484,7 +477,7 @@ pub fn file_id(file: Option<&File>, path: &Path) -> apperr::Result { file_id_from_handle(file).or_else(|_| Ok(FileId::Path(std::fs::canonicalize(path)?))) } -fn file_id_from_handle(file: &File) -> apperr::Result { +fn file_id_from_handle(file: &File) -> io::Result { unsafe { let mut info = MaybeUninit::::uninit(); check_bool_return(FileSystem::GetFileInformationByHandleEx( @@ -516,11 +509,11 @@ pub fn canonicalize(path: &Path) -> std::io::Result { Ok(path) } -unsafe fn get_module(name: *const u16) -> apperr::Result> { +unsafe fn get_module(name: *const u16) -> io::Result> { unsafe { check_ptr_return(LibraryLoader::GetModuleHandleW(name)) } } -unsafe fn load_library(name: *const u16) -> apperr::Result> { +unsafe fn load_library(name: *const u16) -> io::Result> { unsafe { check_ptr_return(LibraryLoader::LoadLibraryExW( name, @@ -538,13 +531,10 @@ unsafe fn load_library(name: *const u16) -> apperr::Result> { /// of the function you're loading. No type checks whatsoever are performed. // // It'd be nice to constrain T to std::marker::FnPtr, but that's unstable. -pub unsafe fn get_proc_address( - handle: NonNull, - name: *const c_char, -) -> apperr::Result { +pub unsafe fn get_proc_address(handle: NonNull, name: *const c_char) -> io::Result { unsafe { let ptr = LibraryLoader::GetProcAddress(handle.as_ptr(), name as *const u8); - if let Some(ptr) = ptr { Ok(mem::transmute_copy(&ptr)) } else { Err(get_last_error()) } + if let Some(ptr) = ptr { Ok(mem::transmute_copy(&ptr)) } else { Err(last_os_error()) } } } @@ -553,7 +543,7 @@ pub struct LibIcu { pub libicui18n: NonNull, } -pub fn load_icu() -> apperr::Result { +pub fn load_icu() -> io::Result { const fn const_ptr_u16_eq(a: *const u16, b: *const u16) -> bool { unsafe { let mut a = a; @@ -654,67 +644,16 @@ fn wide_to_utf8<'a>(arena: &'a Arena, wide: &[u16]) -> ArenaString<'a> { res } -#[cold] -pub fn get_last_error() -> apperr::Error { - unsafe { gle_to_apperr(Foundation::GetLastError()) } -} - #[inline] -const fn gle_to_apperr(gle: u32) -> apperr::Error { - apperr::Error::new_sys(if gle == 0 { 0x8000FFFF } else { 0x80070000 | gle }) -} - -#[inline] -pub(crate) fn io_error_to_apperr(err: std::io::Error) -> apperr::Error { - gle_to_apperr(err.raw_os_error().unwrap_or(0) as u32) -} - -/// Formats a platform error code into a human-readable string. -pub fn apperr_format(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result { - match code { - ERROR_UNSUPPORTED_LEGACY_CONSOLE => { - write!(f, "This application does not support the legacy console.") - } - _ => unsafe { - let mut ptr: *mut u8 = null_mut(); - let len = Debug::FormatMessageA( - Debug::FORMAT_MESSAGE_ALLOCATE_BUFFER - | Debug::FORMAT_MESSAGE_FROM_SYSTEM - | Debug::FORMAT_MESSAGE_IGNORE_INSERTS, - null(), - code, - 0, - &mut ptr as *mut *mut _ as *mut _, - 0, - null_mut(), - ); - - write!(f, "Error {code:#08x}")?; - - if len > 0 { - let msg = str_from_raw_parts(ptr, len as usize); - let msg = msg.trim_ascii(); - let msg = msg.replace(['\r', '\n'], " "); - write!(f, ": {msg}")?; - Foundation::LocalFree(ptr as *mut _); - } - - Ok(()) - }, - } -} - -/// Checks if the given error is a "file not found" error. -pub fn apperr_is_not_found(err: apperr::Error) -> bool { - const FNF: apperr::Error = gle_to_apperr(Foundation::ERROR_FILE_NOT_FOUND); - const PNF: apperr::Error = gle_to_apperr(Foundation::ERROR_PATH_NOT_FOUND); - err == FNF || err == PNF +#[cold] +fn last_os_error() -> io::Error { + io::Error::last_os_error() } -fn check_bool_return(ret: BOOL) -> apperr::Result<()> { - if ret == 0 { Err(get_last_error()) } else { Ok(()) } +fn check_bool_return(ret: BOOL) -> io::Result<()> { + if ret == 0 { Err(last_os_error()) } else { Ok(()) } } -fn check_ptr_return(ret: *mut T) -> apperr::Result> { - NonNull::new(ret).ok_or_else(get_last_error) +fn check_ptr_return(ret: *mut T) -> io::Result> { + NonNull::new(ret).ok_or_else(last_os_error) } diff --git a/crates/edit/src/tui.rs b/crates/edit/src/tui.rs index 356e4e9a851..01fa7bcb71d 100644 --- a/crates/edit/src/tui.rs +++ b/crates/edit/src/tui.rs @@ -147,10 +147,10 @@ use std::arch::breakpoint; #[cfg(debug_assertions)] use std::collections::HashSet; use std::fmt::Write as _; -use std::{iter, mem, ptr, time}; +use std::{io, iter, mem, ptr, time}; use stdext::arena::{Arena, ArenaString, scratch_arena}; -use stdext::arena_format; +use stdext::{arena_format, opt_ptr_eq, str_from_raw_parts}; use crate::buffer::{CursorMovement, MoveLineDirection, RcTextBuffer, TextBuffer, TextBufferCell}; use crate::cell::*; @@ -161,12 +161,12 @@ use crate::hash::*; use crate::helpers::*; use crate::input::{InputKeyMod, kbmod, vk}; use crate::oklab::StraightRgba; -use crate::{apperr, input, simd, unicode}; +use crate::{input, simd, unicode}; const ROOT_ID: u64 = 0x14057B7EF767814F; // Knuth's MMIX constant const SHIFT_TAB: InputKey = vk::TAB.with_modifiers(kbmod::SHIFT); const KBMOD_FOR_WORD_NAV: InputKeyMod = - if cfg!(target_os = "macos") { kbmod::ALT } else { kbmod::CTRL }; + if cfg!(any(target_os = "macos", target_os = "ios")) { kbmod::ALT } else { kbmod::CTRL }; type Input<'input> = input::Input<'input>; type InputKey = input::InputKey; @@ -375,7 +375,7 @@ pub struct Tui { impl Tui { /// Creates a new [`Tui`] instance for storing state across frames. - pub fn new() -> apperr::Result { + pub fn new() -> io::Result { let arena_prev = Arena::new(128 * MEBI)?; let arena_next = Arena::new(128 * MEBI)?; // SAFETY: Since `prev_tree` refers to `arena_prev`/`arena_next`, from its POV the lifetime @@ -2687,7 +2687,7 @@ impl<'a> Context<'a, '_> { _ => return false, }, vk::B => match modifiers { - kbmod::ALT if cfg!(target_os = "macos") => { + kbmod::ALT if cfg!(any(target_os = "macos", target_os = "ios")) => { // On macOS, terminals commonly emit the Emacs style // Alt+B (ESC b) sequence for Alt+Left. tb.cursor_move_delta(CursorMovement::Word, -1); @@ -2695,7 +2695,7 @@ impl<'a> Context<'a, '_> { _ => return false, }, vk::F => match modifiers { - kbmod::ALT if cfg!(target_os = "macos") => { + kbmod::ALT if cfg!(any(target_os = "macos", target_os = "ios")) => { // On macOS, terminals commonly emit the Emacs style // Alt+F (ESC f) sequence for Alt+Right. tb.cursor_move_delta(CursorMovement::Word, 1); @@ -3155,7 +3155,8 @@ impl<'a> Context<'a, '_> { /// /// Returns true if the menu is open. Continue appending items to it in that case. pub fn menubar_menu_begin(&mut self, text: &str, accelerator: char) -> bool { - let accelerator = if cfg!(target_os = "macos") { '\0' } else { accelerator }; + let accelerator = + if cfg!(any(target_os = "macos", target_os = "ios")) { '\0' } else { accelerator }; let mixin = self.tree.current_node.borrow().child_count as u64; self.next_block_id_mixin(mixin); From 1d2043729232aceab3471e4929bd5844ec0f067e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 26 Jan 2026 23:30:31 +0100 Subject: [PATCH 26/27] Fix the latest clippy warning (#751) --- crates/edit/src/bin/edit/draw_statusbar.rs | 2 +- crates/edit/src/icu.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/edit/src/bin/edit/draw_statusbar.rs b/crates/edit/src/bin/edit/draw_statusbar.rs index 935e158e176..10e7ddbcfe1 100644 --- a/crates/edit/src/bin/edit/draw_statusbar.rs +++ b/crates/edit/src/bin/edit/draw_statusbar.rs @@ -302,7 +302,7 @@ fn encoding_picker_update_list(state: &mut State) { } } - matches.sort_unstable_by(|a, b| b.0.cmp(&a.0)); + matches.sort_unstable_by_key(|b| std::cmp::Reverse(b.0)); state.encoding_picker_results = Some(Vec::from_iter(matches.iter().map(|(_, enc)| *enc))); } diff --git a/crates/edit/src/icu.rs b/crates/edit/src/icu.rs index 8b8f783eeee..a99acac7cc6 100644 --- a/crates/edit/src/icu.rs +++ b/crates/edit/src/icu.rs @@ -534,11 +534,7 @@ fn utext_access_impl<'a>( } } - loop { - let Some(c) = it.next() else { - break; - }; - + while let Some(c) = it.next() { // Thanks to our `if utf16_len >= UTF16_LEN_LIMIT` check, // we can safely assume that this will fit. unsafe { From 8286024ea7fbbd02cbbb44ddaf7b6561c2cc6077 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 28 Jan 2026 00:12:20 +0100 Subject: [PATCH 27/27] Fix warnings when building on macOS (#754) --- crates/edit/src/helpers.rs | 11 ----------- crates/edit/src/sys/unix.rs | 7 ++++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/edit/src/helpers.rs b/crates/edit/src/helpers.rs index 756292b8575..f8539f8155f 100644 --- a/crates/edit/src/helpers.rs +++ b/crates/edit/src/helpers.rs @@ -78,9 +78,6 @@ pub struct Size { } impl Size { - pub const MIN: Self = Self { width: 0, height: 0 }; - pub const MAX: Self = Self { width: CoordType::MAX, height: CoordType::MAX }; - pub fn as_rect(&self) -> Rect { Rect { left: 0, top: 0, right: self.width, bottom: self.height } } @@ -149,14 +146,6 @@ impl Rect { } } -/// [`std::cmp::minmax`] is unstable, as per usual. -pub fn minmax(v1: T, v2: T) -> [T; 2] -where - T: Ord, -{ - if v2 < v1 { [v2, v1] } else { [v1, v2] } -} - /// [`Read`] but with [`MaybeUninit`] buffers. pub fn file_read_uninit(file: &mut T, buf: &mut [MaybeUninit]) -> io::Result { unsafe { diff --git a/crates/edit/src/sys/unix.rs b/crates/edit/src/sys/unix.rs index ba9d06fde64..b0e275cfc55 100644 --- a/crates/edit/src/sys/unix.rs +++ b/crates/edit/src/sys/unix.rs @@ -6,7 +6,7 @@ //! Read the `windows` module for reference. //! TODO: This reminds me that the sys API should probably be a trait. -use std::ffi::{CStr, c_char, c_int, c_void}; +use std::ffi::{c_char, c_int, c_void}; use std::fs::File; use std::mem::{self, ManuallyDrop, MaybeUninit}; use std::os::fd::{AsRawFd as _, FromRawFd as _}; @@ -425,6 +425,7 @@ pub fn load_icu() -> io::Result { Ok(LibIcu { libicuuc, libicui18n }) } } + /// ICU, by default, adds the major version as a suffix to each exported symbol. /// They also recommend to disable this for system-level installations (`runConfigureICU Linux --disable-renaming`), /// but I found that many (most?) Linux distributions don't do this for some reason. @@ -459,7 +460,7 @@ pub fn icu_detect_renaming_suffix(arena: &Arena, handle: NonNull) -> Are } // The library path is in `info.dli_fname`. - let path = match CStr::from_ptr(info.dli_fname).to_str() { + let path = match std::ffi::CStr::from_ptr(info.dli_fname).to_str() { Ok(name) => name, Err(_) => return res, }; @@ -502,7 +503,7 @@ where } else { // SAFETY: In this particular case we know that the string // is valid UTF-8, because it comes from icu.rs. - let name = unsafe { CStr::from_ptr(name) }; + let name = unsafe { std::ffi::CStr::from_ptr(name) }; let name = unsafe { name.to_str().unwrap_unchecked() }; let mut res = ManuallyDrop::new(ArenaString::new_in(arena));