Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ resolver = "2"
edition = "2024"
license = "MIT"
repository = "https://github.com/microsoft/edit"
rust-version = "1.88"
rust-version = "1.93"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this is the latest stable version which was released earlier this month. (We could support older versions if we add more shims.)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this is the latest stable version which was released earlier this month. (We could support older versions if we add more shims.)

@lhecker Just curious, why support older versions of Rust? Why shims? Rust always works on all platforms. I think it's more correct to always prefer the Rust std and keep the code concise.


# We use `opt-level = "s"` as it significantly reduces binary size.
# We could then use the `#[optimize(speed)]` attribute for spot optimizations.
Expand Down
56 changes: 37 additions & 19 deletions crates/edit/src/bin/edit/documents.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -77,7 +76,7 @@ impl Document {

#[derive(Default)]
pub struct DocumentManager {
list: LinkedList<Document>,
list: Vec<Document>,
}

impl DocumentManager {
Expand All @@ -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<F: FnMut(&Document) -> 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::<Document>::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> {
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion crates/edit/src/bin/edit/draw_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/edit/src/bin/edit/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
29 changes: 13 additions & 16 deletions crates/edit/src/buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _};
Expand Down Expand Up @@ -229,8 +229,8 @@ pub type RcTextBuffer = Rc<TextBufferCell>;
pub struct TextBuffer {
buffer: GapBuffer,

undo_stack: LinkedList<SemiRefCell<HistoryEntry>>,
redo_stack: LinkedList<SemiRefCell<HistoryEntry>>,
undo_stack: VecDeque<SemiRefCell<HistoryEntry>>,
redo_stack: VecDeque<SemiRefCell<HistoryEntry>>,
last_history_type: HistoryType,
last_save_generation: u32,

Expand Down Expand Up @@ -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,

Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down
10 changes: 1 addition & 9 deletions crates/edit/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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),
Expand Down
7 changes: 2 additions & 5 deletions crates/edit/src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@
//! }
//! ```

use std::arch::breakpoint;
#[cfg(debug_assertions)]
use std::collections::HashSet;
use std::fmt::Write as _;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/edit/src/unicode/measurement.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::hint::cold_path;
use stdext::cold_path;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR for stabilizing cold_path was just merged by the Rust team hours ago:
rust-lang/rust#151576
Another PR for the cold_path release notes was just opened with the 1.95.0 milestone:
rust-lang/rust#152251
Rust version 1.95.0 is planned to be stable on April 16, 2026:
https://releases.rs/docs/1.95.0/


use super::Utf8Chars;
use super::tables::*;
Expand Down
21 changes: 7 additions & 14 deletions crates/stdext/src/arena/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<T>(&self) -> &mut MaybeUninit<T> {
self.delegate_target().alloc_uninit()
}
impl Deref for Arena {
type Target = release::Arena;

pub fn alloc_uninit_slice<T>(&self, count: usize) -> &mut [MaybeUninit<T>] {
self.delegate_target().alloc_uninit_slice(count)
#[inline]
fn deref(&self) -> &Self::Target {
self.delegate_target()
}
}

Expand Down
18 changes: 18 additions & 0 deletions crates/stdext/src/arena/release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ impl Arena {
unsafe { ptr.cast().as_mut() }
}

#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_uninit_array<const N: usize, T>(&self) -> &mut [MaybeUninit<T>; N] {
let bytes = mem::size_of::<[MaybeUninit<T>; N]>();
let alignment = mem::align_of::<[MaybeUninit<T>; N]>();
let ptr = self.alloc_raw(bytes, alignment);
unsafe { ptr.cast().as_mut() }
}

#[inline]
#[allow(clippy::mut_from_ref)]
pub fn alloc_uninit_slice<T>(&self, count: usize) -> &mut [MaybeUninit<T>] {
Expand All @@ -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<T: Copy>(&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 {
Expand Down
10 changes: 10 additions & 0 deletions crates/stdext/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -151,6 +152,15 @@ pub const fn slice_as_uninit_mut<T>(slice: &mut [T]) -> &mut [MaybeUninit<T>] {
unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut MaybeUninit<T>, 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<u8>) -> 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.
Expand Down
2 changes: 1 addition & 1 deletion crates/stdext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#![feature(allocator_api)]

pub mod arena;
mod helpers;
pub mod sys;

mod helpers;
pub use helpers::*;