From afb27f63641e7bbbe786f779e30f69dcbdfdbb85 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 6 Feb 2026 22:36:50 +0100 Subject: [PATCH] Remove the majority of nightly features --- Cargo.toml | 2 +- crates/edit/src/bin/edit/documents.rs | 56 ++++++++++++++++--------- crates/edit/src/bin/edit/draw_editor.rs | 3 +- crates/edit/src/bin/edit/main.rs | 2 +- crates/edit/src/buffer/mod.rs | 29 ++++++------- crates/edit/src/lib.rs | 10 +---- crates/edit/src/tui.rs | 7 +--- crates/edit/src/unicode/measurement.rs | 2 +- crates/stdext/src/arena/debug.rs | 21 ++++------ crates/stdext/src/arena/release.rs | 18 ++++++++ crates/stdext/src/helpers.rs | 10 +++++ crates/stdext/src/lib.rs | 2 +- 12 files changed, 94 insertions(+), 68 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fb53ab464f5f..1cab5da276be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ resolver = "2" edition = "2024" license = "MIT" repository = "https://github.com/microsoft/edit" -rust-version = "1.88" +rust-version = "1.93" # We use `opt-level = "s"` as it significantly reduces binary size. # We could then use the `#[optimize(speed)]` attribute for spot optimizations. diff --git a/crates/edit/src/bin/edit/documents.rs b/crates/edit/src/bin/edit/documents.rs index eabf798678f1..23de9a77c33a 100644 --- a/crates/edit/src/bin/edit/documents.rs +++ b/crates/edit/src/bin/edit/documents.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::collections::LinkedList; use std::ffi::OsStr; use std::fs::File; use std::path::{Path, PathBuf}; @@ -77,7 +76,7 @@ impl Document { #[derive(Default)] pub struct DocumentManager { - list: LinkedList, + list: Vec, } impl DocumentManager { @@ -88,30 +87,48 @@ impl DocumentManager { #[inline] pub fn active(&self) -> Option<&Document> { - self.list.front() + self.list.last() } #[inline] pub fn active_mut(&mut self) -> Option<&mut Document> { - self.list.front_mut() + self.list.last_mut() } - #[inline] pub fn update_active bool>(&mut self, mut func: F) -> bool { - let mut cursor = self.list.cursor_front_mut(); - while let Some(doc) = cursor.current() { - if func(doc) { - let list = cursor.remove_current_as_list().unwrap(); - self.list.cursor_front_mut().splice_before(list); - return true; - } - cursor.move_next(); + let Some(idx) = self.list.iter().rposition(&mut func) else { + return false; + }; + + // Already active (= last) document matched? Nothing to do. + if idx == self.list.len() - 1 { + return false; } - false + + // Otherwise, move the matched document to the end of the list so it becomes active. + // Uses unsafe, because `rotate_left()` is horrendously bad with -Copt-level=s + // (it's really almost comical) and I just don't tolerate that. + // If I'm dead and you're looking to rewrite this use `list.push(list.remove(idx))`. + unsafe { + let beg = self.list.as_mut_ptr(); + let doc = beg.add(idx); + let last = beg.add(self.list.len() - 1); + let amount = self.list.len() - idx - 1; + let mut temp = std::mem::MaybeUninit::::uninit(); + + // Make a backup of the document + std::ptr::copy_nonoverlapping(doc, temp.as_mut_ptr(), 1); + // Shift the rest to the front + std::ptr::copy(doc.add(1), doc, amount); + // Move the backup to the end + std::ptr::copy_nonoverlapping(temp.as_ptr(), last, 1); + } + + true } pub fn remove_active(&mut self) { - self.list.pop_front(); + self.list.pop(); } pub fn add_untitled(&mut self) -> apperr::Result<&mut Document> { @@ -126,8 +143,9 @@ impl DocumentManager { }; self.gen_untitled_name(&mut doc); - self.list.push_front(doc); - Ok(self.list.front_mut().unwrap()) + // In the future this could use push_mut, but it's unstable right now. As usual. + self.list.push(doc); + Ok(self.list.last_mut().unwrap()) } pub fn gen_untitled_name(&self, doc: &mut Document) { @@ -196,8 +214,8 @@ impl DocumentManager { self.remove_active(); } - self.list.push_front(doc); - Ok(self.list.front_mut().unwrap()) + self.list.push(doc); + Ok(self.list.last_mut().unwrap()) } pub fn reflow_all(&self) { diff --git a/crates/edit/src/bin/edit/draw_editor.rs b/crates/edit/src/bin/edit/draw_editor.rs index 479c053ac4dc..7638d33ccd6d 100644 --- a/crates/edit/src/bin/edit/draw_editor.rs +++ b/crates/edit/src/bin/edit/draw_editor.rs @@ -8,6 +8,7 @@ use edit::helpers::*; use edit::icu; use edit::input::{kbmod, vk}; use edit::tui::*; +use stdext::string_from_utf8_lossy_owned; use crate::localization::*; use crate::state::*; @@ -58,7 +59,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) { // If the selection is empty, focus the search input field. // Otherwise, focus the replace input field, if it exists. if let Some(selection) = doc.buffer.borrow_mut().extract_user_selection(false) { - state.search_needle = String::from_utf8_lossy_owned(selection); + state.search_needle = string_from_utf8_lossy_owned(selection); focus = state.wants_search.kind; } } diff --git a/crates/edit/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs index a05756a009cd..6280afabd45a 100644 --- a/crates/edit/src/bin/edit/main.rs +++ b/crates/edit/src/bin/edit/main.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#![feature(allocator_api, linked_list_cursors, string_from_utf8_lossy_owned)] +#![feature(allocator_api)] mod apperr; mod documents; diff --git a/crates/edit/src/buffer/mod.rs b/crates/edit/src/buffer/mod.rs index 251fa401d141..8aa4d498a2d2 100644 --- a/crates/edit/src/buffer/mod.rs +++ b/crates/edit/src/buffer/mod.rs @@ -25,7 +25,7 @@ mod navigation; use std::borrow::Cow; use std::cell::UnsafeCell; -use std::collections::LinkedList; +use std::collections::VecDeque; use std::fmt::Write as _; use std::fs::File; use std::io::{self, Read as _, Write as _}; @@ -229,8 +229,8 @@ pub type RcTextBuffer = Rc; pub struct TextBuffer { buffer: GapBuffer, - undo_stack: LinkedList>, - redo_stack: LinkedList>, + undo_stack: VecDeque>, + redo_stack: VecDeque>, last_history_type: HistoryType, last_save_generation: u32, @@ -281,8 +281,8 @@ impl TextBuffer { Ok(Self { buffer: GapBuffer::new(small)?, - undo_stack: LinkedList::new(), - redo_stack: LinkedList::new(), + undo_stack: Default::default(), + redo_stack: Default::default(), last_history_type: HistoryType::Other, last_save_generation: 0, @@ -687,7 +687,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>) -> IoResult<()> { let scratch = scratch_arena(None); - let mut buf = scratch.alloc_uninit().transpose(); + let buf = scratch.alloc_uninit_array(); let mut first_chunk_len = 0; let mut read = 0; @@ -713,9 +713,9 @@ impl TextBuffer { let done = read == 0; if self.encoding == "UTF-8" { - self.read_file_as_utf8(file, &mut buf, first_chunk_len, done)?; + self.read_file_as_utf8(file, buf, first_chunk_len, done)?; } else { - self.read_file_with_icu(file, &mut buf, first_chunk_len, done)?; + self.read_file_with_icu(file, buf, first_chunk_len, done)?; } // Figure out @@ -2742,17 +2742,14 @@ impl TextBuffer { (&mut self.redo_stack, &mut self.undo_stack) }; - if let Some(g) = entry_buffer_generation - && from.back().is_none_or(|c| c.borrow().generation_before != g) - { - break; - } - - let Some(list) = from.cursor_back_mut().remove_current_as_list() else { + // Only pop the entry if its buffer generation matches the previous one + let Some(g) = from.pop_back_if(|c| { + entry_buffer_generation.is_none_or(|g| g == c.borrow().generation_before) + }) else { break; }; - to.cursor_back_mut().splice_after(list); + to.push_back(g); } let change = { diff --git a/crates/edit/src/lib.rs b/crates/edit/src/lib.rs index 6dea3a7380bd..3162efa37734 100644 --- a/crates/edit/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -1,15 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#![feature( - allocator_api, - breakpoint, - cold_path, - linked_list_cursors, - maybe_uninit_fill, - maybe_uninit_slice, - maybe_uninit_uninit_array_transpose -)] +#![feature(allocator_api)] #![cfg_attr( target_arch = "loongarch64", feature(stdarch_loongarch, stdarch_loongarch_feature_detection, loongarch_target_feature), diff --git a/crates/edit/src/tui.rs b/crates/edit/src/tui.rs index 247505a60419..a6a9f57379bd 100644 --- a/crates/edit/src/tui.rs +++ b/crates/edit/src/tui.rs @@ -143,7 +143,6 @@ //! } //! ``` -use std::arch::breakpoint; #[cfg(debug_assertions)] use std::collections::HashSet; use std::fmt::Write as _; @@ -845,9 +844,7 @@ impl Tui { // If the focus has changed, the new node may need to be re-rendered. // Same, every time we encounter a previously unknown node via `get_prev_node`, // because that means it likely failed to get crucial information such as the layout size. - if cfg!(debug_assertions) && self.settling_have == 15 { - breakpoint(); - } + debug_assert!(self.settling_have <= 15); self.settling_want = (self.settling_have + 1).min(20); } @@ -3577,7 +3574,7 @@ impl<'a> NodeMap<'a> { let shift = 64 - width; let mask = (slots - 1) as u64; - let slots = arena.alloc_uninit_slice(slots).write_filled(None); + let slots = arena.alloc_slice(slots, None); let mut node = tree.root_first; loop { diff --git a/crates/edit/src/unicode/measurement.rs b/crates/edit/src/unicode/measurement.rs index 38e22adf6d45..806f8494901f 100644 --- a/crates/edit/src/unicode/measurement.rs +++ b/crates/edit/src/unicode/measurement.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use std::hint::cold_path; +use stdext::cold_path; use super::Utf8Chars; use super::tables::*; diff --git a/crates/stdext/src/arena/debug.rs b/crates/stdext/src/arena/debug.rs index 851474e546f6..f0663d15a27d 100644 --- a/crates/stdext/src/arena/debug.rs +++ b/crates/stdext/src/arena/debug.rs @@ -5,7 +5,7 @@ use std::alloc::{AllocError, Allocator, Layout}; use std::io; -use std::mem::MaybeUninit; +use std::ops::Deref; use std::ptr::NonNull; use super::release; @@ -94,21 +94,14 @@ impl Arena { Self::Owned { arena } => arena, } } +} - pub fn offset(&self) -> usize { - self.delegate_target().offset() - } - - pub unsafe fn reset(&self, to: usize) { - unsafe { self.delegate_target().reset(to) } - } - - pub fn alloc_uninit(&self) -> &mut MaybeUninit { - self.delegate_target().alloc_uninit() - } +impl Deref for Arena { + type Target = release::Arena; - pub fn alloc_uninit_slice(&self, count: usize) -> &mut [MaybeUninit] { - self.delegate_target().alloc_uninit_slice(count) + #[inline] + fn deref(&self) -> &Self::Target { + self.delegate_target() } } diff --git a/crates/stdext/src/arena/release.rs b/crates/stdext/src/arena/release.rs index 53fea9505adb..e04deb121092 100644 --- a/crates/stdext/src/arena/release.rs +++ b/crates/stdext/src/arena/release.rs @@ -165,6 +165,15 @@ impl Arena { unsafe { ptr.cast().as_mut() } } + #[inline] + #[allow(clippy::mut_from_ref)] + pub fn alloc_uninit_array(&self) -> &mut [MaybeUninit; N] { + let bytes = mem::size_of::<[MaybeUninit; N]>(); + let alignment = mem::align_of::<[MaybeUninit; N]>(); + 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] { @@ -173,6 +182,15 @@ impl Arena { let ptr = self.alloc_raw(bytes, alignment); unsafe { slice::from_raw_parts_mut(ptr.cast().as_ptr(), count) } } + + /// A workaround for `alloc_uninit_slice(count).write_filled()` being unstable (`maybe_uninit_fill`). + #[inline] + #[allow(clippy::mut_from_ref)] + pub fn alloc_slice(&self, count: usize, value: T) -> &mut [T] { + let slice = self.alloc_uninit_slice(count); + slice.fill(MaybeUninit::new(value)); + unsafe { slice.assume_init_mut() } + } } impl Drop for Arena { diff --git a/crates/stdext/src/helpers.rs b/crates/stdext/src/helpers.rs index 556206ae6ed7..6bd10ca3c72d 100644 --- a/crates/stdext/src/helpers.rs +++ b/crates/stdext/src/helpers.rs @@ -4,6 +4,7 @@ //! Random assortment of helpers I didn't know where to put. use std::alloc::Allocator; +use std::borrow::Cow; use std::mem::{self, MaybeUninit}; use std::ops::{Bound, Range, RangeBounds}; use std::{fmt, ptr, slice, str}; @@ -151,6 +152,15 @@ 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()) } } +/// A stable clone of [`String::from_utf8_lossy_owned`] (`string_from_utf8_lossy_owned`). +pub fn string_from_utf8_lossy_owned(v: Vec) -> String { + if let Cow::Owned(string) = String::from_utf8_lossy(&v) { + string + } else { + unsafe { String::from_utf8_unchecked(v) } + } +} + /// Helpers for ASCII string comparisons. pub trait AsciiStringHelpers { /// Tests if a string starts with a given ASCII prefix. diff --git a/crates/stdext/src/lib.rs b/crates/stdext/src/lib.rs index d7226d7e19c2..b0ee3509cb48 100644 --- a/crates/stdext/src/lib.rs +++ b/crates/stdext/src/lib.rs @@ -6,7 +6,7 @@ #![feature(allocator_api)] pub mod arena; +mod helpers; pub mod sys; -mod helpers; pub use helpers::*;