mirror of
https://github.com/microsoft/edit.git
synced 2025-07-03 22:43:22 +00:00
Adopt rustfmt.toml from rust-lang/rust
This commit is contained in:
parent
d82a1659b6
commit
b7024f1bf9
27 changed files with 326 additions and 917 deletions
|
@ -1,7 +1,6 @@
|
|||
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use edit::helpers::*;
|
||||
use edit::simd;
|
||||
use edit::ucd;
|
||||
use edit::{simd, ucd};
|
||||
|
||||
fn bench(c: &mut Criterion) {
|
||||
let mut buffer1 = [0u8; 2048];
|
||||
|
@ -23,9 +22,7 @@ fn bench(c: &mut Criterion) {
|
|||
});
|
||||
group.bench_function("word_wrap", |b| {
|
||||
b.iter(|| {
|
||||
ucd::MeasurementConfig::new(&bytes)
|
||||
.with_word_wrap_column(50)
|
||||
.goto_logical(Point::MAX)
|
||||
ucd::MeasurementConfig::new(&bytes).with_word_wrap_column(50).goto_logical(Point::MAX)
|
||||
})
|
||||
});
|
||||
group.finish();
|
||||
|
|
5
build.rs
5
build.rs
|
@ -4,10 +4,7 @@ fn main() {
|
|||
winres::WindowsResource::new()
|
||||
.set_manifest_file("src/bin/edit/edit.exe.manifest")
|
||||
.set("FileDescription", "Microsoft Edit")
|
||||
.set(
|
||||
"LegalCopyright",
|
||||
"© Microsoft Corporation. All rights reserved.",
|
||||
)
|
||||
.set("LegalCopyright", "© Microsoft Corporation. All rights reserved.")
|
||||
.compile()
|
||||
.unwrap();
|
||||
}
|
||||
|
|
5
rustfmt.toml
Normal file
5
rustfmt.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
style_edition = "2024"
|
||||
use_small_heuristics = "Max"
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
use_field_init_shorthand = true
|
|
@ -1,6 +1,6 @@
|
|||
use std::{io, result};
|
||||
|
||||
use crate::sys;
|
||||
use std::io;
|
||||
use std::result;
|
||||
|
||||
// Remember to add an entry to `Error::message()` for each new error.
|
||||
pub const APP_ICU_MISSING: Error = Error::new_app(0);
|
||||
|
|
38
src/arena.rs
38
src/arena.rs
|
@ -1,17 +1,11 @@
|
|||
use crate::apperr;
|
||||
use crate::helpers;
|
||||
use crate::sys;
|
||||
use std::alloc::AllocError;
|
||||
use std::alloc::Allocator;
|
||||
use std::alloc::Layout;
|
||||
use std::alloc::{AllocError, Allocator, Layout};
|
||||
use std::cell::Cell;
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::slice;
|
||||
use std::{fmt, mem, slice};
|
||||
|
||||
use crate::{apperr, helpers, sys};
|
||||
|
||||
const ALLOC_CHUNK_SIZE: usize = 64 * 1024;
|
||||
|
||||
|
@ -24,23 +18,13 @@ pub struct Arena {
|
|||
|
||||
impl Arena {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
base: NonNull::dangling(),
|
||||
capacity: 0,
|
||||
commit: Cell::new(0),
|
||||
offset: Cell::new(0),
|
||||
}
|
||||
Self { base: NonNull::dangling(), capacity: 0, commit: Cell::new(0), offset: Cell::new(0) }
|
||||
}
|
||||
|
||||
pub fn new(capacity: usize) -> apperr::Result<Arena> {
|
||||
let capacity = (capacity + ALLOC_CHUNK_SIZE - 1) & !(ALLOC_CHUNK_SIZE - 1);
|
||||
let base = unsafe { sys::virtual_reserve(capacity)? };
|
||||
Ok(Arena {
|
||||
base,
|
||||
capacity,
|
||||
commit: Cell::new(0),
|
||||
offset: Cell::new(0),
|
||||
})
|
||||
Ok(Arena { base, capacity, commit: Cell::new(0), offset: Cell::new(0) })
|
||||
}
|
||||
|
||||
/// "Deallocates" the memory in the arena down to the given offset.
|
||||
|
@ -340,9 +324,7 @@ pub struct ArenaString<'a> {
|
|||
impl<'a> ArenaString<'a> {
|
||||
#[must_use]
|
||||
pub const fn new_in(arena: &'a Arena) -> Self {
|
||||
Self {
|
||||
vec: Vec::new_in(arena),
|
||||
}
|
||||
Self { vec: Vec::new_in(arena) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -448,9 +430,7 @@ impl<'a> ArenaString<'a> {
|
|||
pub fn push(&mut self, ch: char) {
|
||||
match ch.len_utf8() {
|
||||
1 => self.vec.push(ch as u8),
|
||||
_ => self
|
||||
.vec
|
||||
.extend_from_slice(ch.encode_utf8(&mut [0; 4]).as_bytes()),
|
||||
_ => self.vec.extend_from_slice(ch.encode_utf8(&mut [0; 4]).as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use edit::apperr;
|
||||
use edit::buffer::{RcTextBuffer, TextBuffer};
|
||||
use edit::helpers::{CoordType, Point};
|
||||
use edit::simd::memrchr2;
|
||||
use edit::sys;
|
||||
use std::collections::LinkedList;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use edit::buffer::{RcTextBuffer, TextBuffer};
|
||||
use edit::helpers::{CoordType, Point};
|
||||
use edit::simd::memrchr2;
|
||||
use edit::{apperr, sys};
|
||||
|
||||
pub enum DocumentPath {
|
||||
None,
|
||||
Preliminary(PathBuf),
|
||||
|
@ -39,11 +39,7 @@ pub struct Document {
|
|||
impl Document {
|
||||
fn update_file_mode(&mut self) {
|
||||
let mut tb = self.buffer.borrow_mut();
|
||||
tb.set_ruler(if self.filename == "COMMIT_EDITMSG" {
|
||||
72
|
||||
} else {
|
||||
0
|
||||
});
|
||||
tb.set_ruler(if self.filename == "COMMIT_EDITMSG" { 72 } else { 0 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,15 +160,8 @@ impl DocumentManager {
|
|||
// Path exists but is not a file (a directory?).
|
||||
_ => DocumentPath::None,
|
||||
};
|
||||
let filename = path
|
||||
.as_path()
|
||||
.map_or(Default::default(), Self::get_filename_from_path);
|
||||
let mut doc = Document {
|
||||
buffer,
|
||||
path,
|
||||
filename,
|
||||
new_file_counter: 0,
|
||||
};
|
||||
let filename = path.as_path().map_or(Default::default(), Self::get_filename_from_path);
|
||||
let mut doc = Document { buffer, path, filename, new_file_counter: 0 };
|
||||
|
||||
if doc.filename.is_empty() {
|
||||
self.gen_untitled_name(&mut doc);
|
||||
|
@ -210,10 +199,7 @@ impl DocumentManager {
|
|||
}
|
||||
|
||||
pub fn get_filename_from_path(path: &Path) -> String {
|
||||
path.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
path.file_name().unwrap_or_default().to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
// Parse a filename in the form of "filename:line:char".
|
||||
|
@ -288,10 +274,7 @@ mod tests {
|
|||
assert_eq!(parse("45:123"), ("45", Some(Point { x: 0, y: 122 })));
|
||||
assert_eq!(parse(":45:123"), (":45", Some(Point { x: 0, y: 122 })));
|
||||
assert_eq!(parse("abc:45:123"), ("abc", Some(Point { x: 122, y: 44 })));
|
||||
assert_eq!(
|
||||
parse("abc:def:123"),
|
||||
("abc:def", Some(Point { x: 0, y: 122 }))
|
||||
);
|
||||
assert_eq!(parse("abc:def:123"), ("abc:def", Some(Point { x: 0, y: 122 })));
|
||||
assert_eq!(parse("1:2:3"), ("1", Some(Point { x: 2, y: 1 })));
|
||||
assert_eq!(parse("::3"), (":", Some(Point { x: 0, y: 2 })));
|
||||
assert_eq!(parse("1::3"), ("1:", Some(Point { x: 0, y: 2 })));
|
||||
|
@ -300,13 +283,7 @@ mod tests {
|
|||
assert_eq!(parse("::"), ("::", None));
|
||||
assert_eq!(parse("a:1"), ("a", Some(Point { x: 0, y: 0 })));
|
||||
assert_eq!(parse("1:a"), ("1:a", None));
|
||||
assert_eq!(
|
||||
parse("file.txt:10"),
|
||||
("file.txt", Some(Point { x: 0, y: 9 }))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("file.txt:10:5"),
|
||||
("file.txt", Some(Point { x: 4, y: 9 }))
|
||||
);
|
||||
assert_eq!(parse("file.txt:10"), ("file.txt", Some(Point { x: 0, y: 9 })));
|
||||
assert_eq!(parse("file.txt:10:5"), ("file.txt", Some(Point { x: 4, y: 9 })));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
use crate::loc::*;
|
||||
use crate::state::*;
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::icu;
|
||||
use edit::input::kbmod;
|
||||
use edit::input::vk;
|
||||
use edit::input::{kbmod, vk};
|
||||
use edit::tui::*;
|
||||
|
||||
use crate::loc::*;
|
||||
use crate::state::*;
|
||||
|
||||
pub fn draw_editor(ctx: &mut Context, state: &mut State) {
|
||||
if !matches!(
|
||||
state.wants_search.kind,
|
||||
StateSearchKind::Hidden | StateSearchKind::Disabled
|
||||
) {
|
||||
if !matches!(state.wants_search.kind, StateSearchKind::Hidden | StateSearchKind::Disabled) {
|
||||
draw_search(ctx, state);
|
||||
}
|
||||
|
||||
|
@ -31,10 +28,7 @@ pub fn draw_editor(ctx: &mut Context, state: &mut State) {
|
|||
ctx.block_end();
|
||||
}
|
||||
|
||||
ctx.attr_intrinsic_size(Size {
|
||||
width: 0,
|
||||
height: size.height - height_reduction,
|
||||
});
|
||||
ctx.attr_intrinsic_size(Size { width: 0, height: size.height - height_reduction });
|
||||
}
|
||||
|
||||
fn draw_search(ctx: &mut Context, state: &mut State) {
|
||||
|
@ -81,10 +75,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
|
|||
}
|
||||
|
||||
ctx.table_begin("needle");
|
||||
ctx.table_set_cell_gap(Size {
|
||||
width: 1,
|
||||
height: 0,
|
||||
});
|
||||
ctx.table_set_cell_gap(Size { width: 1, height: 0 });
|
||||
{
|
||||
{
|
||||
ctx.table_next_row();
|
||||
|
@ -97,10 +88,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
|
|||
ctx.attr_background_rgba(ctx.indexed(IndexedColor::Red));
|
||||
ctx.attr_foreground_rgba(ctx.indexed(IndexedColor::BrightWhite));
|
||||
}
|
||||
ctx.attr_intrinsic_size(Size {
|
||||
width: COORD_TYPE_SAFE_MAX,
|
||||
height: 1,
|
||||
});
|
||||
ctx.attr_intrinsic_size(Size { width: COORD_TYPE_SAFE_MAX, height: 1 });
|
||||
if focus == StateSearchKind::Search {
|
||||
ctx.steal_focus();
|
||||
}
|
||||
|
@ -114,10 +102,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
|
|||
ctx.label("label", Overflow::Clip, loc(LocId::SearchReplacementLabel));
|
||||
|
||||
ctx.editline("replacement", &mut state.search_replacement);
|
||||
ctx.attr_intrinsic_size(Size {
|
||||
width: COORD_TYPE_SAFE_MAX,
|
||||
height: 1,
|
||||
});
|
||||
ctx.attr_intrinsic_size(Size { width: COORD_TYPE_SAFE_MAX, height: 1 });
|
||||
if focus == StateSearchKind::Replace {
|
||||
ctx.steal_focus();
|
||||
}
|
||||
|
@ -133,10 +118,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
|
|||
ctx.table_end();
|
||||
|
||||
ctx.table_begin("options");
|
||||
ctx.table_set_cell_gap(Size {
|
||||
width: 2,
|
||||
height: 0,
|
||||
});
|
||||
ctx.table_set_cell_gap(Size { width: 2, height: 0 });
|
||||
{
|
||||
ctx.table_next_row();
|
||||
|
||||
|
@ -181,10 +163,9 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
|
|||
|
||||
state.search_success = match action {
|
||||
SearchAction::None => return,
|
||||
SearchAction::Search => doc
|
||||
.buffer
|
||||
.borrow_mut()
|
||||
.find_and_select(&state.search_needle, state.search_options),
|
||||
SearchAction::Search => {
|
||||
doc.buffer.borrow_mut().find_and_select(&state.search_needle, state.search_options)
|
||||
}
|
||||
SearchAction::Replace => doc.buffer.borrow_mut().find_and_replace(
|
||||
&state.search_needle,
|
||||
state.search_options,
|
||||
|
@ -243,21 +224,14 @@ pub fn draw_handle_wants_close(ctx: &mut Context, state: &mut State) {
|
|||
ctx.attr_background_rgba(ctx.indexed(IndexedColor::Red));
|
||||
ctx.attr_foreground_rgba(ctx.indexed(IndexedColor::BrightWhite));
|
||||
{
|
||||
ctx.label(
|
||||
"description",
|
||||
Overflow::Clip,
|
||||
loc(LocId::UnsavedChangesDialogDescription),
|
||||
);
|
||||
ctx.label("description", Overflow::Clip, loc(LocId::UnsavedChangesDialogDescription));
|
||||
ctx.attr_padding(Rect::three(1, 2, 1));
|
||||
|
||||
ctx.table_begin("choices");
|
||||
ctx.inherit_focus();
|
||||
ctx.attr_padding(Rect::three(0, 2, 1));
|
||||
ctx.attr_position(Position::Center);
|
||||
ctx.table_set_cell_gap(Size {
|
||||
width: 2,
|
||||
height: 0,
|
||||
});
|
||||
ctx.table_set_cell_gap(Size { width: 2, height: 0 });
|
||||
{
|
||||
ctx.table_next_row();
|
||||
ctx.inherit_focus();
|
||||
|
@ -269,11 +243,7 @@ pub fn draw_handle_wants_close(ctx: &mut Context, state: &mut State) {
|
|||
if ctx.button("no", Overflow::Clip, loc(LocId::UnsavedChangesDialogNo)) {
|
||||
action = Action::Discard;
|
||||
}
|
||||
if ctx.button(
|
||||
"cancel",
|
||||
Overflow::Clip,
|
||||
loc(LocId::UnsavedChangesDialogCancel),
|
||||
) {
|
||||
if ctx.button("cancel", Overflow::Clip, loc(LocId::UnsavedChangesDialogCancel)) {
|
||||
action = Action::Cancel;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use crate::documents::*;
|
||||
use crate::loc::*;
|
||||
use crate::state::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::path::{Component, PathBuf};
|
||||
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::icu;
|
||||
use edit::input::vk;
|
||||
use edit::tui::*;
|
||||
use std::cmp::Ordering;
|
||||
use std::path::Component;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::documents::*;
|
||||
use crate::loc::*;
|
||||
use crate::state::*;
|
||||
|
||||
pub fn draw_file_picker(ctx: &mut Context, state: &mut State) {
|
||||
let width = (ctx.size().width - 20).max(10);
|
||||
|
@ -29,34 +30,19 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) {
|
|||
|
||||
ctx.table_begin("path");
|
||||
ctx.table_set_columns(&[0, COORD_TYPE_SAFE_MAX]);
|
||||
ctx.table_set_cell_gap(Size {
|
||||
width: 1,
|
||||
height: 0,
|
||||
});
|
||||
ctx.table_set_cell_gap(Size { width: 1, height: 0 });
|
||||
ctx.attr_padding(Rect::two(1, 1));
|
||||
ctx.inherit_focus();
|
||||
{
|
||||
ctx.table_next_row();
|
||||
|
||||
ctx.label(
|
||||
"dir-label",
|
||||
Overflow::Clip,
|
||||
loc(LocId::SaveAsDialogPathLabel),
|
||||
);
|
||||
ctx.label(
|
||||
"dir",
|
||||
Overflow::TruncateMiddle,
|
||||
state.file_picker_pending_dir.as_str(),
|
||||
);
|
||||
ctx.label("dir-label", Overflow::Clip, loc(LocId::SaveAsDialogPathLabel));
|
||||
ctx.label("dir", Overflow::TruncateMiddle, state.file_picker_pending_dir.as_str());
|
||||
|
||||
ctx.table_next_row();
|
||||
ctx.inherit_focus();
|
||||
|
||||
ctx.label(
|
||||
"name-label",
|
||||
Overflow::Clip,
|
||||
loc(LocId::SaveAsDialogNameLabel),
|
||||
);
|
||||
ctx.label("name-label", Overflow::Clip, loc(LocId::SaveAsDialogNameLabel));
|
||||
ctx.editline("name", &mut state.file_picker_pending_name);
|
||||
ctx.inherit_focus();
|
||||
if ctx.is_focused() && ctx.consume_shortcut(vk::RETURN) {
|
||||
|
@ -139,10 +125,7 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) {
|
|||
ctx.inherit_focus();
|
||||
ctx.attr_padding(Rect::three(0, 2, 1));
|
||||
ctx.attr_position(Position::Center);
|
||||
ctx.table_set_cell_gap(Size {
|
||||
width: 2,
|
||||
height: 0,
|
||||
});
|
||||
ctx.table_set_cell_gap(Size { width: 2, height: 0 });
|
||||
{
|
||||
ctx.table_next_row();
|
||||
ctx.inherit_focus();
|
||||
|
@ -213,11 +196,7 @@ fn draw_file_picker_update_path(state: &mut State) -> Option<PathBuf> {
|
|||
}
|
||||
|
||||
state.file_picker_pending_name = name;
|
||||
if state.file_picker_pending_name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(normalized)
|
||||
}
|
||||
if state.file_picker_pending_name.is_empty() { None } else { Some(normalized) }
|
||||
}
|
||||
|
||||
fn draw_dialog_saveas_refresh_files(state: &mut State) {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::loc::*;
|
||||
use crate::state::*;
|
||||
use edit::arena_format;
|
||||
use edit::helpers::*;
|
||||
use edit::input::{kbmod, vk};
|
||||
use edit::tui::*;
|
||||
|
||||
use crate::loc::*;
|
||||
use crate::state::*;
|
||||
|
||||
pub fn draw_menubar(ctx: &mut Context, state: &mut State) {
|
||||
ctx.menubar_begin();
|
||||
ctx.attr_background_rgba(state.menubar_color_bg);
|
||||
|
@ -132,11 +133,7 @@ pub fn draw_dialog_about(ctx: &mut Context, state: &mut State) {
|
|||
);
|
||||
ctx.attr_position(Position::Center);
|
||||
|
||||
ctx.label(
|
||||
"copyright",
|
||||
Overflow::TruncateTail,
|
||||
"Copyright (c) Microsoft Corp 2025",
|
||||
);
|
||||
ctx.label("copyright", Overflow::TruncateTail, "Copyright (c) Microsoft Corp 2025");
|
||||
ctx.attr_position(Position::Center);
|
||||
|
||||
ctx.block_begin("choices");
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
use std::ptr;
|
||||
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::input::vk;
|
||||
use edit::tui::*;
|
||||
use edit::{arena_format, icu};
|
||||
|
||||
use crate::documents::*;
|
||||
use crate::loc::*;
|
||||
use crate::state::*;
|
||||
use edit::arena_format;
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::icu;
|
||||
use edit::input::vk;
|
||||
use edit::tui::*;
|
||||
|
||||
pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
|
||||
ctx.table_begin("statusbar");
|
||||
ctx.attr_background_rgba(state.menubar_color_bg);
|
||||
ctx.attr_foreground_rgba(state.menubar_color_fg);
|
||||
ctx.table_set_cell_gap(Size {
|
||||
width: 2,
|
||||
height: 0,
|
||||
});
|
||||
ctx.attr_intrinsic_size(Size {
|
||||
width: COORD_TYPE_SAFE_MAX,
|
||||
height: 1,
|
||||
});
|
||||
ctx.table_set_cell_gap(Size { width: 2, height: 0 });
|
||||
ctx.attr_intrinsic_size(Size { width: COORD_TYPE_SAFE_MAX, height: 1 });
|
||||
ctx.attr_padding(Rect::two(0, 1));
|
||||
|
||||
if let Some(doc) = state.documents.active() {
|
||||
|
@ -29,11 +23,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
|
|||
|
||||
ctx.table_next_row();
|
||||
|
||||
if ctx.button(
|
||||
"newline",
|
||||
Overflow::Clip,
|
||||
if tb.is_crlf() { "CRLF" } else { "LF" },
|
||||
) {
|
||||
if ctx.button("newline", Overflow::Clip, if tb.is_crlf() { "CRLF" } else { "LF" }) {
|
||||
let is_crlf = tb.is_crlf();
|
||||
tb.normalize_newlines(!is_crlf);
|
||||
}
|
||||
|
@ -109,10 +99,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
|
|||
});
|
||||
ctx.attr_border();
|
||||
ctx.attr_padding(Rect::two(0, 1));
|
||||
ctx.table_set_cell_gap(Size {
|
||||
width: 1,
|
||||
height: 0,
|
||||
});
|
||||
ctx.table_set_cell_gap(Size { width: 1, height: 0 });
|
||||
{
|
||||
if ctx.consume_shortcut(vk::RETURN) {
|
||||
ctx.toss_focus_up();
|
||||
|
@ -203,10 +190,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
|
|||
}
|
||||
|
||||
ctx.block_begin("filename-container");
|
||||
ctx.attr_intrinsic_size(Size {
|
||||
width: COORD_TYPE_SAFE_MAX,
|
||||
height: 1,
|
||||
});
|
||||
ctx.attr_intrinsic_size(Size { width: COORD_TYPE_SAFE_MAX, height: 1 });
|
||||
{
|
||||
let total = state.documents.len();
|
||||
let mut filename = doc.filename.as_str();
|
||||
|
@ -236,11 +220,7 @@ pub fn draw_dialog_encoding_change(ctx: &mut Context, state: &mut State) {
|
|||
|
||||
ctx.modal_begin(
|
||||
"encode",
|
||||
if reopen {
|
||||
loc(LocId::EncodingReopen)
|
||||
} else {
|
||||
loc(LocId::EncodingConvert)
|
||||
},
|
||||
if reopen { loc(LocId::EncodingReopen) } else { loc(LocId::EncodingConvert) },
|
||||
);
|
||||
{
|
||||
ctx.scrollarea_begin("scrollarea", Size { width, height });
|
||||
|
|
|
@ -8,30 +8,28 @@ mod draw_statusbar;
|
|||
mod loc;
|
||||
mod state;
|
||||
|
||||
use std::borrow::Cow;
|
||||
#[cfg(feature = "debug-latency")]
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
||||
use documents::DocumentPath;
|
||||
use draw_editor::*;
|
||||
use draw_filepicker::*;
|
||||
use draw_menubar::*;
|
||||
use draw_statusbar::*;
|
||||
use edit::apperr;
|
||||
use edit::arena::{self, ArenaString, scratch_arena};
|
||||
use edit::base64;
|
||||
#[cfg(feature = "debug-latency")]
|
||||
use edit::arena_format;
|
||||
use edit::buffer::TextBuffer;
|
||||
use edit::framebuffer::{self, IndexedColor, alpha_blend};
|
||||
use edit::input::{self, kbmod, vk};
|
||||
use edit::sys;
|
||||
use edit::tui::*;
|
||||
use edit::vt::{self, Token};
|
||||
use edit::{apperr, base64, sys};
|
||||
use loc::*;
|
||||
use state::*;
|
||||
use std::borrow::Cow;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
|
||||
#[cfg(feature = "debug-latency")]
|
||||
use edit::arena_format;
|
||||
#[cfg(feature = "debug-latency")]
|
||||
use std::fmt::Write;
|
||||
|
||||
impl State {
|
||||
fn new() -> apperr::Result<Self> {
|
||||
|
@ -58,10 +56,7 @@ impl State {
|
|||
file_picker_entries: None,
|
||||
file_picker_overwrite_warning: None,
|
||||
|
||||
wants_search: StateSearch {
|
||||
kind: StateSearchKind::Hidden,
|
||||
focus: false,
|
||||
},
|
||||
wants_search: StateSearch { kind: StateSearchKind::Hidden, focus: false },
|
||||
search_needle: Default::default(),
|
||||
search_replacement: Default::default(),
|
||||
search_options: Default::default(),
|
||||
|
@ -200,10 +195,7 @@ fn run() -> apperr::Result<()> {
|
|||
#[cfg(feature = "debug-layout")]
|
||||
{
|
||||
drop(ctx);
|
||||
state
|
||||
.buffer
|
||||
.buffer
|
||||
.debug_replace_everything(&tui.debug_layout());
|
||||
state.buffer.buffer.debug_replace_everything(&tui.debug_layout());
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug-latency")]
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use crate::documents::DocumentManager;
|
||||
use crate::loc::*;
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::icu;
|
||||
use edit::sys;
|
||||
use edit::tui::*;
|
||||
use edit::{apperr, buffer};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use edit::framebuffer::IndexedColor;
|
||||
use edit::helpers::*;
|
||||
use edit::tui::*;
|
||||
use edit::{apperr, buffer, icu, sys};
|
||||
|
||||
use crate::documents::DocumentManager;
|
||||
use crate::loc::*;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct FormatApperr(apperr::Error);
|
||||
|
@ -59,10 +58,7 @@ impl DisplayablePathBuf {
|
|||
|
||||
impl Default for DisplayablePathBuf {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
value: PathBuf::default(),
|
||||
str: Cow::Borrowed(""),
|
||||
}
|
||||
Self { value: PathBuf::default(), str: Cow::Borrowed("") }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
244
src/buffer.rs
244
src/buffer.rs
|
@ -13,29 +13,26 @@
|
|||
//! The solution to the former is to keep line caches, which further complicates the architecture.
|
||||
//! There's no solution for the latter. However, there's a chance that the performance will still be sufficient.
|
||||
|
||||
use crate::apperr;
|
||||
use crate::arena::scratch_arena;
|
||||
use crate::cell::SemiRefCell;
|
||||
use crate::framebuffer::{Framebuffer, IndexedColor, alpha_blend};
|
||||
use crate::helpers::{self, COORD_TYPE_SAFE_MAX, CoordType, Point, Rect};
|
||||
use crate::icu;
|
||||
use crate::simd::memchr2;
|
||||
use crate::sys;
|
||||
use crate::ucd::{self, Document};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::LinkedList;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::File;
|
||||
use std::io::Read as _;
|
||||
use std::io::Write as _;
|
||||
use std::io::{Read as _, Write as _};
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
use std::{slice, str};
|
||||
|
||||
use crate::arena::scratch_arena;
|
||||
use crate::cell::SemiRefCell;
|
||||
use crate::framebuffer::{Framebuffer, IndexedColor, alpha_blend};
|
||||
use crate::helpers::{self, COORD_TYPE_SAFE_MAX, CoordType, Point, Rect};
|
||||
use crate::simd::memchr2;
|
||||
use crate::ucd::{self, Document};
|
||||
use crate::{apperr, icu, sys};
|
||||
|
||||
/// The margin template is used for line numbers.
|
||||
/// The max. line number we should ever expect is probably 64-bit,
|
||||
|
@ -183,10 +180,7 @@ impl TextBuffer {
|
|||
active_edit_depth: 0,
|
||||
active_edit_off: 0,
|
||||
|
||||
stats: TextBufferStatistics {
|
||||
logical_lines: 1,
|
||||
visual_lines: 1,
|
||||
},
|
||||
stats: TextBufferStatistics { logical_lines: 1, visual_lines: 1 },
|
||||
cursor: ucd::UcdCursor::default(),
|
||||
cursor_for_rendering: None,
|
||||
selection: TextBufferSelection::None,
|
||||
|
@ -243,9 +237,8 @@ impl TextBuffer {
|
|||
let mut off = 0;
|
||||
|
||||
let mut cursor_offset = self.cursor.offset;
|
||||
let mut cursor_for_rendering_offset = self
|
||||
.cursor_for_rendering
|
||||
.map_or(cursor_offset, |c| c.offset);
|
||||
let mut cursor_for_rendering_offset =
|
||||
self.cursor_for_rendering.map_or(cursor_offset, |c| c.offset);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let mut adjusted_newlines = 0;
|
||||
|
@ -288,9 +281,7 @@ impl TextBuffer {
|
|||
|
||||
// Replace the newline.
|
||||
off -= chunk_newline_len;
|
||||
let gap = self
|
||||
.buffer
|
||||
.allocate_gap(off, newline.len(), chunk_newline_len);
|
||||
let gap = self.buffer.allocate_gap(off, newline.len(), chunk_newline_len);
|
||||
gap.copy_from_slice(newline);
|
||||
self.buffer.commit_gap(newline.len());
|
||||
off += newline.len();
|
||||
|
@ -417,11 +408,8 @@ impl TextBuffer {
|
|||
|
||||
let text_width = self.get_text_width();
|
||||
// 2 columns are required, because otherwise wide glyphs wouldn't ever fit.
|
||||
let word_wrap_column = if self.word_wrap_enabled && text_width >= 2 {
|
||||
text_width
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let word_wrap_column =
|
||||
if self.word_wrap_enabled && text_width >= 2 { text_width } else { 0 };
|
||||
|
||||
if force || self.word_wrap_column > word_wrap_column {
|
||||
self.word_wrap_column = word_wrap_column;
|
||||
|
@ -463,10 +451,7 @@ impl TextBuffer {
|
|||
pub fn copy_from_str(&mut self, text: &str) {
|
||||
if self.buffer.copy_from_str(text) {
|
||||
self.recalc_after_content_swap();
|
||||
self.cursor_move_to_logical(Point {
|
||||
x: CoordType::MAX,
|
||||
y: 0,
|
||||
});
|
||||
self.cursor_move_to_logical(Point { x: CoordType::MAX, y: 0 });
|
||||
|
||||
let delete = self.buffer.len() - self.cursor.offset;
|
||||
if delete != 0 {
|
||||
|
@ -481,10 +466,7 @@ impl TextBuffer {
|
|||
let before = self.cursor.logical_pos;
|
||||
let end = self.cursor_move_to_logical_internal(
|
||||
ucd::UcdCursor::default(),
|
||||
Point {
|
||||
x: 0,
|
||||
y: CoordType::MAX,
|
||||
},
|
||||
Point { x: 0, y: CoordType::MAX },
|
||||
);
|
||||
self.stats.logical_lines = end.logical_pos.y + 1;
|
||||
self.stats.visual_lines = self.stats.logical_lines;
|
||||
|
@ -586,11 +568,8 @@ impl TextBuffer {
|
|||
} else {
|
||||
// Otherwise, check how many spaces the line starts with. Searching for >8 spaces
|
||||
// allows us to reject lines that have more than 1 level of indentation.
|
||||
let space_indentation = chunk[offset..]
|
||||
.iter()
|
||||
.take(9)
|
||||
.take_while(|&&c| c == b' ')
|
||||
.count();
|
||||
let space_indentation =
|
||||
chunk[offset..].iter().take(9).take_while(|&&c| c == b' ').count();
|
||||
|
||||
// We'll also reject lines starting with 1 space, because that's too fickle as a heuristic.
|
||||
if (2..=8).contains(&space_indentation) {
|
||||
|
@ -917,18 +896,10 @@ impl TextBuffer {
|
|||
pub fn select_line(&mut self) {
|
||||
let beg = self.cursor_move_to_logical_internal(
|
||||
self.cursor,
|
||||
Point {
|
||||
x: 0,
|
||||
y: self.cursor.logical_pos.y,
|
||||
},
|
||||
);
|
||||
let end = self.cursor_move_to_logical_internal(
|
||||
beg,
|
||||
Point {
|
||||
x: 0,
|
||||
y: self.cursor.logical_pos.y + 1,
|
||||
},
|
||||
Point { x: 0, y: self.cursor.logical_pos.y },
|
||||
);
|
||||
let end = self
|
||||
.cursor_move_to_logical_internal(beg, Point { x: 0, y: self.cursor.logical_pos.y + 1 });
|
||||
self.set_cursor_for_selection(end);
|
||||
self.set_selection(TextBufferSelection::Done {
|
||||
beg: beg.logical_pos,
|
||||
|
@ -1000,8 +971,7 @@ impl TextBuffer {
|
|||
if self.selection_generation == search.selection_generation {
|
||||
search.next_search_offset
|
||||
} else {
|
||||
self.cursor_move_to_logical_internal(self.cursor, beg.min(end))
|
||||
.offset
|
||||
self.cursor_move_to_logical_internal(self.cursor, beg.min(end)).offset
|
||||
}
|
||||
}
|
||||
_ => self.cursor.offset,
|
||||
|
@ -1209,16 +1179,10 @@ impl TextBuffer {
|
|||
|
||||
if self.word_wrap_column > 0 {
|
||||
let upward = result.offset < cursor.offset;
|
||||
let (top, bottom) = if upward {
|
||||
(result, cursor)
|
||||
} else {
|
||||
(cursor, result)
|
||||
};
|
||||
let (top, bottom) = if upward { (result, cursor) } else { (cursor, result) };
|
||||
|
||||
let mut bottom_remeasured = self
|
||||
.measurement_config()
|
||||
.with_cursor(top)
|
||||
.goto_logical(bottom.logical_pos);
|
||||
let mut bottom_remeasured =
|
||||
self.measurement_config().with_cursor(top).goto_logical(bottom.logical_pos);
|
||||
|
||||
// The second problem is that visual positions can be ambiguous. A single logical position
|
||||
// can map to two visual positions: One at the end of the preceeding line in front of
|
||||
|
@ -1274,9 +1238,7 @@ impl TextBuffer {
|
|||
cursor = self.goto_line_start(cursor, cursor.logical_pos.y - 1);
|
||||
}
|
||||
|
||||
self.measurement_config()
|
||||
.with_cursor(cursor)
|
||||
.goto_offset(offset)
|
||||
self.measurement_config().with_cursor(cursor).goto_offset(offset)
|
||||
}
|
||||
|
||||
fn cursor_move_to_logical_internal(
|
||||
|
@ -1284,10 +1246,7 @@ impl TextBuffer {
|
|||
mut cursor: ucd::UcdCursor,
|
||||
pos: Point,
|
||||
) -> ucd::UcdCursor {
|
||||
let pos = Point {
|
||||
x: pos.x.max(0),
|
||||
y: pos.y.max(0),
|
||||
};
|
||||
let pos = Point { x: pos.x.max(0), y: pos.y.max(0) };
|
||||
|
||||
if pos == cursor.logical_pos {
|
||||
return cursor;
|
||||
|
@ -1300,9 +1259,7 @@ impl TextBuffer {
|
|||
cursor = self.goto_line_start(cursor, pos.y);
|
||||
}
|
||||
|
||||
self.measurement_config()
|
||||
.with_cursor(cursor)
|
||||
.goto_logical(pos)
|
||||
self.measurement_config().with_cursor(cursor).goto_logical(pos)
|
||||
}
|
||||
|
||||
fn cursor_move_to_visual_internal(
|
||||
|
@ -1310,10 +1267,7 @@ impl TextBuffer {
|
|||
mut cursor: ucd::UcdCursor,
|
||||
pos: Point,
|
||||
) -> ucd::UcdCursor {
|
||||
let pos = Point {
|
||||
x: pos.x.max(0),
|
||||
y: pos.y.max(0),
|
||||
};
|
||||
let pos = Point { x: pos.x.max(0), y: pos.y.max(0) };
|
||||
|
||||
if pos == cursor.visual_pos {
|
||||
return cursor;
|
||||
|
@ -1336,9 +1290,7 @@ impl TextBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
self.measurement_config()
|
||||
.with_cursor(cursor)
|
||||
.goto_visual(pos)
|
||||
self.measurement_config().with_cursor(cursor).goto_visual(pos)
|
||||
}
|
||||
|
||||
fn cursor_move_delta_internal(
|
||||
|
@ -1362,10 +1314,7 @@ impl TextBuffer {
|
|||
|
||||
cursor = self.cursor_move_to_logical_internal(
|
||||
cursor,
|
||||
Point {
|
||||
x: target_x,
|
||||
y: cursor.logical_pos.y,
|
||||
},
|
||||
Point { x: target_x, y: cursor.logical_pos.y },
|
||||
);
|
||||
|
||||
// We can stop if we ran out of remaining delta
|
||||
|
@ -1381,10 +1330,7 @@ impl TextBuffer {
|
|||
|
||||
cursor = self.cursor_move_to_logical_internal(
|
||||
cursor,
|
||||
Point {
|
||||
x: start_x,
|
||||
y: cursor.logical_pos.y + sign,
|
||||
},
|
||||
Point { x: start_x, y: cursor.logical_pos.y + sign },
|
||||
);
|
||||
|
||||
// We crossed a newline which counts for 1 grapheme cluster.
|
||||
|
@ -1511,19 +1457,11 @@ impl TextBuffer {
|
|||
line.clear();
|
||||
|
||||
let visual_line = origin.y + y;
|
||||
let mut cursor_beg = self.cursor_move_to_visual_internal(
|
||||
cursor,
|
||||
Point {
|
||||
x: origin.x,
|
||||
y: visual_line,
|
||||
},
|
||||
);
|
||||
let mut cursor_beg =
|
||||
self.cursor_move_to_visual_internal(cursor, Point { x: origin.x, y: visual_line });
|
||||
let cursor_end = self.cursor_move_to_visual_internal(
|
||||
cursor_beg,
|
||||
Point {
|
||||
x: origin.x + text_width,
|
||||
y: visual_line,
|
||||
},
|
||||
Point { x: origin.x + text_width, y: visual_line },
|
||||
);
|
||||
|
||||
// Accelerate the next render pass by remembering where we started off.
|
||||
|
@ -1542,12 +1480,7 @@ impl TextBuffer {
|
|||
line.push_str(&MARGIN_TEMPLATE[off..]);
|
||||
} else if self.word_wrap_column <= 0 || cursor_beg.logical_pos.x == 0 {
|
||||
// Regular line? Place "123 | " in the margin.
|
||||
_ = write!(
|
||||
line,
|
||||
"{:1$} │ ",
|
||||
cursor_beg.logical_pos.y + 1,
|
||||
line_number_width
|
||||
);
|
||||
_ = write!(line, "{:1$} │ ", cursor_beg.logical_pos.y + 1, line_number_width);
|
||||
} else {
|
||||
// Wrapped line? Place " ... | " in the margin.
|
||||
let number_width = (cursor_beg.logical_pos.y + 1).ilog10() as usize + 1;
|
||||
|
@ -1562,12 +1495,7 @@ impl TextBuffer {
|
|||
let left = destination.left;
|
||||
let top = destination.top + y;
|
||||
fb.blend_fg(
|
||||
Rect {
|
||||
left,
|
||||
top,
|
||||
right: left + line_number_width as i32,
|
||||
bottom: top + 1,
|
||||
},
|
||||
Rect { left, top, right: left + line_number_width as i32, bottom: top + 1 },
|
||||
fb.indexed(IndexedColor::Background),
|
||||
);
|
||||
}
|
||||
|
@ -1581,10 +1509,7 @@ impl TextBuffer {
|
|||
if cursor_beg.visual_pos.x < origin.x {
|
||||
let cursor_next = self.cursor_move_to_logical_internal(
|
||||
cursor_beg,
|
||||
Point {
|
||||
x: cursor_beg.logical_pos.x + 1,
|
||||
y: cursor_beg.logical_pos.y,
|
||||
},
|
||||
Point { x: cursor_beg.logical_pos.x + 1, y: cursor_beg.logical_pos.y },
|
||||
);
|
||||
|
||||
if cursor_next.visual_pos.x > origin.x {
|
||||
|
@ -1662,12 +1587,7 @@ impl TextBuffer {
|
|||
visual_pos_x_max = visual_pos_x_max.max(cursor_end.visual_pos.x);
|
||||
}
|
||||
|
||||
fb.replace_text(
|
||||
destination.top + y,
|
||||
destination.left,
|
||||
destination.right,
|
||||
&line,
|
||||
);
|
||||
fb.replace_text(destination.top + y, destination.left, destination.right, &line);
|
||||
|
||||
// Draw the selection on this line, if any.
|
||||
// FYI: `cursor_beg.visual_pos.y == visual_line` is necessary as the `visual_line`
|
||||
|
@ -1702,12 +1622,7 @@ impl TextBuffer {
|
|||
|
||||
let left = destination.left + self.margin_width - origin.x;
|
||||
let top = destination.top + y;
|
||||
let rect = Rect {
|
||||
left: left + beg,
|
||||
top,
|
||||
right: left + end,
|
||||
bottom: top + 1,
|
||||
};
|
||||
let rect = Rect { left: left + beg, top, right: left + end, bottom: top + 1 };
|
||||
|
||||
let mut bg = alpha_blend(
|
||||
fb.indexed(IndexedColor::Foreground),
|
||||
|
@ -1740,12 +1655,7 @@ impl TextBuffer {
|
|||
let right = destination.right;
|
||||
if left < right {
|
||||
fb.blend_bg(
|
||||
Rect {
|
||||
left,
|
||||
top: destination.top,
|
||||
right,
|
||||
bottom: destination.bottom,
|
||||
},
|
||||
Rect { left, top: destination.top, right, bottom: destination.bottom },
|
||||
fb.indexed_alpha(IndexedColor::BrightRed, 0x1f),
|
||||
);
|
||||
}
|
||||
|
@ -1849,10 +1759,7 @@ impl TextBuffer {
|
|||
let delete = self.cursor.logical_pos.x - column_before;
|
||||
let end = self.cursor_move_to_logical_internal(
|
||||
self.cursor,
|
||||
Point {
|
||||
x: self.cursor.logical_pos.x + delete,
|
||||
y: self.cursor.logical_pos.y,
|
||||
},
|
||||
Point { x: self.cursor.logical_pos.x + delete, y: self.cursor.logical_pos.y },
|
||||
);
|
||||
self.edit_delete(end);
|
||||
}
|
||||
|
@ -1986,17 +1893,10 @@ impl TextBuffer {
|
|||
|
||||
let [beg, end] = helpers::minmax(selection_beg, selection_end);
|
||||
let beg = self.cursor_move_to_logical_internal(self.cursor, Point { x: 0, y: beg.y });
|
||||
let end = self.cursor_move_to_logical_internal(
|
||||
beg,
|
||||
Point {
|
||||
x: CoordType::MAX,
|
||||
y: end.y,
|
||||
},
|
||||
);
|
||||
let end = self.cursor_move_to_logical_internal(beg, Point { x: CoordType::MAX, y: end.y });
|
||||
|
||||
let mut replacement = Vec::new();
|
||||
self.buffer
|
||||
.extract_raw(beg.offset, end.offset, &mut replacement, 0);
|
||||
self.buffer.extract_raw(beg.offset, end.offset, &mut replacement, 0);
|
||||
|
||||
let initial_len = replacement.len();
|
||||
let mut offset = 0;
|
||||
|
@ -2099,14 +1999,8 @@ impl TextBuffer {
|
|||
let [beg, end] = match self.selection {
|
||||
TextBufferSelection::None if !line_fallback => return None,
|
||||
TextBufferSelection::None => [
|
||||
Point {
|
||||
x: 0,
|
||||
y: self.cursor.logical_pos.y,
|
||||
},
|
||||
Point {
|
||||
x: 0,
|
||||
y: self.cursor.logical_pos.y + 1,
|
||||
},
|
||||
Point { x: 0, y: self.cursor.logical_pos.y },
|
||||
Point { x: 0, y: self.cursor.logical_pos.y + 1 },
|
||||
],
|
||||
TextBufferSelection::Active { beg, end } | TextBufferSelection::Done { beg, end } => {
|
||||
helpers::minmax(beg, end)
|
||||
|
@ -2116,11 +2010,7 @@ impl TextBuffer {
|
|||
let beg = self.cursor_move_to_logical_internal(self.cursor, beg);
|
||||
let end = self.cursor_move_to_logical_internal(beg, end);
|
||||
|
||||
if beg.offset < end.offset {
|
||||
Some((beg, end))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if beg.offset < end.offset { Some((beg, end)) } else { None }
|
||||
}
|
||||
|
||||
fn edit_begin(&mut self, history_type: HistoryType, cursor: ucd::UcdCursor) {
|
||||
|
@ -2163,10 +2053,7 @@ impl TextBuffer {
|
|||
let safe_start = self.goto_line_start(cursor, cursor.logical_pos.y);
|
||||
let next_line = self.cursor_move_to_logical_internal(
|
||||
cursor,
|
||||
Point {
|
||||
x: 0,
|
||||
y: cursor.logical_pos.y + 1,
|
||||
},
|
||||
Point { x: 0, y: cursor.logical_pos.y + 1 },
|
||||
);
|
||||
self.active_edit_line_info = Some(ActiveEditLineInfo {
|
||||
safe_start,
|
||||
|
@ -2187,9 +2074,7 @@ impl TextBuffer {
|
|||
|
||||
// Write!
|
||||
{
|
||||
let gap = self
|
||||
.buffer
|
||||
.allocate_gap(self.active_edit_off, text.len(), 0);
|
||||
let gap = self.buffer.allocate_gap(self.active_edit_off, text.len(), 0);
|
||||
gap.copy_from_slice(text);
|
||||
self.buffer.commit_gap(text.len());
|
||||
}
|
||||
|
@ -2240,13 +2125,7 @@ impl TextBuffer {
|
|||
}
|
||||
|
||||
if let Some(info) = self.active_edit_line_info.take() {
|
||||
let deleted_count = self
|
||||
.undo_stack
|
||||
.back_mut()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.deleted
|
||||
.len();
|
||||
let deleted_count = self.undo_stack.back_mut().unwrap().borrow_mut().deleted.len();
|
||||
let target = self.cursor.logical_pos;
|
||||
|
||||
// From our safe position we can measure the actual visual position of the cursor.
|
||||
|
@ -2261,13 +2140,8 @@ impl TextBuffer {
|
|||
// the entire buffer contents until the end to compute `self.stats.visual_lines`.
|
||||
if deleted_count < info.distance_next_line_start {
|
||||
// Now we can measure how many more visual rows this logical line spans.
|
||||
let next_line = self.cursor_move_to_logical_internal(
|
||||
self.cursor,
|
||||
Point {
|
||||
x: 0,
|
||||
y: target.y + 1,
|
||||
},
|
||||
);
|
||||
let next_line = self
|
||||
.cursor_move_to_logical_internal(self.cursor, Point { x: 0, y: target.y + 1 });
|
||||
let lines_before = info.line_height_in_rows;
|
||||
let lines_after = next_line.visual_pos.y - info.safe_start.visual_pos.y;
|
||||
self.stats.visual_lines += lines_after - lines_before;
|
||||
|
@ -2311,11 +2185,7 @@ impl TextBuffer {
|
|||
}
|
||||
|
||||
let change = {
|
||||
let to = if undo {
|
||||
&self.redo_stack
|
||||
} else {
|
||||
&self.undo_stack
|
||||
};
|
||||
let to = if undo { &self.redo_stack } else { &self.undo_stack };
|
||||
to.back().unwrap()
|
||||
};
|
||||
|
||||
|
@ -2340,9 +2210,7 @@ impl TextBuffer {
|
|||
// Delete the inserted portion and reinsert the deleted portion.
|
||||
let deleted = change.deleted.len();
|
||||
let added = &change.added[..];
|
||||
let gap = self
|
||||
.buffer
|
||||
.allocate_gap(cursor.offset, added.len(), deleted);
|
||||
let gap = self.buffer.allocate_gap(cursor.offset, added.len(), deleted);
|
||||
gap.copy_from_slice(added);
|
||||
self.buffer.commit_gap(added.len());
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use crate::arena::{Arena, ArenaString};
|
||||
use crate::helpers::{CoordType, Point, Rect, Size};
|
||||
use crate::simd::{MemsetSafe, memset};
|
||||
use crate::ucd;
|
||||
use std::fmt::Write;
|
||||
use std::ops::{BitOr, BitXor};
|
||||
use std::ptr;
|
||||
use std::slice::ChunksExact;
|
||||
|
||||
use crate::arena::{Arena, ArenaString};
|
||||
use crate::helpers::{CoordType, Point, Rect, Size};
|
||||
use crate::simd::{MemsetSafe, memset};
|
||||
use crate::ucd;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum IndexedColor {
|
||||
Black,
|
||||
|
@ -201,21 +202,16 @@ impl Framebuffer {
|
|||
let mut fract_buf = [0xE2, 0x96, 0x88];
|
||||
if top_fract != 0 {
|
||||
fract_buf[2] = (0x88 - top_fract) as u8;
|
||||
self.replace_text(
|
||||
thumb_top - 1,
|
||||
track_clipped.left,
|
||||
track_clipped.right,
|
||||
unsafe { std::str::from_utf8_unchecked(&fract_buf) },
|
||||
);
|
||||
self.replace_text(thumb_top - 1, track_clipped.left, track_clipped.right, unsafe {
|
||||
std::str::from_utf8_unchecked(&fract_buf)
|
||||
});
|
||||
}
|
||||
if bottom_fract != 0 {
|
||||
fract_buf[2] = (0x88 - bottom_fract) as u8;
|
||||
let rect = self.replace_text(
|
||||
thumb_bottom,
|
||||
track_clipped.left,
|
||||
track_clipped.right,
|
||||
unsafe { std::str::from_utf8_unchecked(&fract_buf) },
|
||||
);
|
||||
let rect =
|
||||
self.replace_text(thumb_bottom, track_clipped.left, track_clipped.right, unsafe {
|
||||
std::str::from_utf8_unchecked(&fract_buf)
|
||||
});
|
||||
self.blend_bg(rect, self.indexed(IndexedColor::BrightWhite));
|
||||
self.blend_fg(rect, self.indexed(IndexedColor::BrightBlack));
|
||||
}
|
||||
|
@ -400,12 +396,7 @@ impl Framebuffer {
|
|||
}
|
||||
|
||||
let beg = cfg.cursor().offset;
|
||||
let end = cfg
|
||||
.goto_visual(Point {
|
||||
x: chunk_end as CoordType,
|
||||
y: 0,
|
||||
})
|
||||
.offset;
|
||||
let end = cfg.goto_visual(Point { x: chunk_end as CoordType, y: 0 }).offset;
|
||||
result.push_str(&back_line[beg..end]);
|
||||
|
||||
chunk_end < back_bg.len()
|
||||
|
@ -458,10 +449,7 @@ struct LineBuffer {
|
|||
|
||||
impl LineBuffer {
|
||||
fn new(size: Size) -> Self {
|
||||
Self {
|
||||
lines: vec![String::new(); size.height as usize],
|
||||
size,
|
||||
}
|
||||
Self { lines: vec![String::new(); size.height as usize], size }
|
||||
}
|
||||
|
||||
fn fill_whitespace(&mut self) {
|
||||
|
@ -513,10 +501,7 @@ impl LineBuffer {
|
|||
if left < 0 && cursor.offset < text.len() {
|
||||
// `-left` must've intersected a wide glyph and since goto_visual stops _before_ reaching the target,
|
||||
// we stoped before the wide glyph and thus must step forward to the next glyph.
|
||||
let cursor = cfg.goto_logical(Point {
|
||||
x: cursor.logical_pos.x + 1,
|
||||
y: 0,
|
||||
});
|
||||
let cursor = cfg.goto_logical(Point { x: cursor.logical_pos.x + 1, y: 0 });
|
||||
left += cursor.visual_pos.x;
|
||||
}
|
||||
}
|
||||
|
@ -528,10 +513,7 @@ impl LineBuffer {
|
|||
}
|
||||
|
||||
// Measure the width of the new text (= `res_new.visual_target.x`).
|
||||
let res_new = cfg.goto_visual(Point {
|
||||
x: layout_width,
|
||||
y: 0,
|
||||
});
|
||||
let res_new = cfg.goto_visual(Point { x: layout_width, y: 0 });
|
||||
|
||||
// Figure out at which byte offset the new text gets inserted.
|
||||
let right = left + res_new.visual_pos.x;
|
||||
|
@ -543,10 +525,7 @@ impl LineBuffer {
|
|||
// Since the goto functions will always stop short of the target position,
|
||||
// we need to manually step beyond it if we intersect with a wide glyph.
|
||||
if res_old_end.visual_pos.x < right {
|
||||
res_old_end = cfg_old.goto_logical(Point {
|
||||
x: res_old_end.logical_pos.x + 1,
|
||||
y: 0,
|
||||
});
|
||||
res_old_end = cfg_old.goto_logical(Point { x: res_old_end.logical_pos.x + 1, y: 0 });
|
||||
}
|
||||
|
||||
// If we intersect a wide glyph, we need to pad the new text with spaces.
|
||||
|
@ -607,12 +586,7 @@ impl LineBuffer {
|
|||
dst.set_len(dst_len - total_del + total_add);
|
||||
}
|
||||
|
||||
Rect {
|
||||
left,
|
||||
top: y,
|
||||
right,
|
||||
bottom: y + 1,
|
||||
}
|
||||
Rect { left, top: y, right, bottom: y + 1 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -624,10 +598,7 @@ struct Bitmap {
|
|||
|
||||
impl Bitmap {
|
||||
fn new(size: Size) -> Self {
|
||||
Self {
|
||||
data: vec![0; (size.width * size.height) as usize],
|
||||
size,
|
||||
}
|
||||
Self { data: vec![0; (size.width * size.height) as usize], size }
|
||||
}
|
||||
|
||||
fn fill(&mut self, color: u32) {
|
||||
|
@ -809,10 +780,7 @@ struct AttributeBuffer {
|
|||
|
||||
impl AttributeBuffer {
|
||||
fn new(size: Size) -> Self {
|
||||
Self {
|
||||
data: vec![Default::default(); (size.width * size.height) as usize],
|
||||
size,
|
||||
}
|
||||
Self { data: vec![Default::default(); (size.width * size.height) as usize], size }
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
|
@ -881,16 +849,10 @@ struct Cursor {
|
|||
|
||||
impl Cursor {
|
||||
const fn new_invalid() -> Self {
|
||||
Self {
|
||||
pos: Point::MIN,
|
||||
overtype: false,
|
||||
}
|
||||
Self { pos: Point::MIN, overtype: false }
|
||||
}
|
||||
|
||||
const fn new_disabled() -> Self {
|
||||
Self {
|
||||
pos: Point { x: -1, y: -1 },
|
||||
overtype: false,
|
||||
}
|
||||
Self { pos: Point { x: -1, y: -1 }, overtype: false }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use crate::apperr;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
use std::io::Read;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
use std::{ptr, slice, str};
|
||||
|
||||
use crate::apperr;
|
||||
|
||||
pub type CoordType = i32;
|
||||
|
||||
|
@ -19,14 +18,8 @@ pub struct Point {
|
|||
}
|
||||
|
||||
impl Point {
|
||||
pub const MIN: Point = Point {
|
||||
x: CoordType::MIN,
|
||||
y: CoordType::MIN,
|
||||
};
|
||||
pub const MAX: Point = Point {
|
||||
x: CoordType::MAX,
|
||||
y: CoordType::MAX,
|
||||
};
|
||||
pub const MIN: Point = Point { x: CoordType::MIN, y: CoordType::MIN };
|
||||
pub const MAX: Point = Point { x: CoordType::MAX, y: CoordType::MAX };
|
||||
}
|
||||
|
||||
impl PartialOrd<Point> for Point {
|
||||
|
@ -52,12 +45,7 @@ pub struct Size {
|
|||
|
||||
impl Size {
|
||||
pub fn as_rect(&self) -> Rect {
|
||||
Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: self.width,
|
||||
bottom: self.height,
|
||||
}
|
||||
Rect { left: 0, top: 0, right: self.width, bottom: self.height }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,30 +59,15 @@ pub struct Rect {
|
|||
|
||||
impl Rect {
|
||||
pub fn one(value: CoordType) -> Self {
|
||||
Self {
|
||||
left: value,
|
||||
top: value,
|
||||
right: value,
|
||||
bottom: value,
|
||||
}
|
||||
Self { left: value, top: value, right: value, bottom: value }
|
||||
}
|
||||
|
||||
pub fn two(top_bottom: CoordType, left_right: CoordType) -> Self {
|
||||
Self {
|
||||
left: left_right,
|
||||
top: top_bottom,
|
||||
right: left_right,
|
||||
bottom: top_bottom,
|
||||
}
|
||||
Self { left: left_right, top: top_bottom, right: left_right, bottom: top_bottom }
|
||||
}
|
||||
|
||||
pub fn three(top: CoordType, left_right: CoordType, bottom: CoordType) -> Self {
|
||||
Self {
|
||||
left: left_right,
|
||||
top,
|
||||
right: left_right,
|
||||
bottom,
|
||||
}
|
||||
Self { left: left_right, top, right: left_right, bottom }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
|
@ -124,12 +97,7 @@ impl Rect {
|
|||
let r = l.max(r);
|
||||
let b = t.max(b);
|
||||
|
||||
Rect {
|
||||
left: l,
|
||||
top: t,
|
||||
right: r,
|
||||
bottom: b,
|
||||
}
|
||||
Rect { left: l, top: t, right: r, bottom: b }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
33
src/icu.rs
33
src/icu.rs
|
@ -1,13 +1,14 @@
|
|||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::buffer::TextBuffer;
|
||||
use crate::utf8::Utf8Chars;
|
||||
use crate::{apperr, sys};
|
||||
use std::ffi::CStr;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::Range;
|
||||
use std::ptr::{null, null_mut};
|
||||
use std::{cmp, mem};
|
||||
|
||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::buffer::TextBuffer;
|
||||
use crate::utf8::Utf8Chars;
|
||||
use crate::{apperr, sys};
|
||||
|
||||
static mut ENCODINGS: Vec<&'static str> = Vec::new();
|
||||
|
||||
pub fn get_available_encodings() -> &'static [&'static str] {
|
||||
|
@ -104,14 +105,7 @@ impl<'pivot> Converter<'pivot> {
|
|||
let pivot_source = pivot_buffer.as_mut_ptr() as *mut u16;
|
||||
let pivot_target = unsafe { pivot_source.add(pivot_buffer.len()) };
|
||||
|
||||
Ok(Self {
|
||||
source,
|
||||
target,
|
||||
pivot_buffer,
|
||||
pivot_source,
|
||||
pivot_target,
|
||||
reset: true,
|
||||
})
|
||||
Ok(Self { source, target, pivot_buffer, pivot_source, pivot_target, reset: true })
|
||||
}
|
||||
|
||||
fn append_nul<'a>(arena: &'a Arena, input: &str) -> ArenaString<'a> {
|
||||
|
@ -425,10 +419,7 @@ fn utext_access_impl<'a>(
|
|||
|
||||
// When it comes to performance, and the search space is small (which it is here),
|
||||
// it's always a good idea to keep the loops small and tight...
|
||||
let len = haystack
|
||||
.iter()
|
||||
.position(|&c| c >= 0x80)
|
||||
.unwrap_or(haystack.len());
|
||||
let len = haystack.iter().position(|&c| c >= 0x80).unwrap_or(haystack.len());
|
||||
|
||||
// ...In this case it allows the compiler to vectorize this loop and double
|
||||
// the performance. Luckily, llvm doesn't unroll the loop, which is great,
|
||||
|
@ -844,10 +835,9 @@ fn init_if_needed() -> apperr::Result<&'static LibraryFunctions> {
|
|||
#[cfg(unix)]
|
||||
let suffix = sys::icu_proc_suffix(&scratch_outer, libicuuc);
|
||||
|
||||
for (handle, names) in [
|
||||
(libicuuc, &LIBICUUC_PROC_NAMES[..]),
|
||||
(libicui18n, &LIBICUI18N_PROC_NAMES[..]),
|
||||
] {
|
||||
for (handle, names) in
|
||||
[(libicuuc, &LIBICUUC_PROC_NAMES[..]), (libicui18n, &LIBICUI18N_PROC_NAMES[..])]
|
||||
{
|
||||
for name in names {
|
||||
#[cfg(unix)]
|
||||
let scratch = scratch_arena(Some(&scratch_outer));
|
||||
|
@ -895,9 +885,10 @@ fn assume_loaded() -> &'static LibraryFunctions {
|
|||
mod icu_ffi {
|
||||
#![allow(dead_code, non_camel_case_types)]
|
||||
|
||||
use crate::apperr;
|
||||
use std::ffi::{c_char, c_int, c_void};
|
||||
|
||||
use crate::apperr;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct UErrorCode(c_int);
|
||||
|
|
48
src/input.rs
48
src/input.rs
|
@ -260,10 +260,7 @@ impl Parser {
|
|||
&'parser mut self,
|
||||
stream: vt::Stream<'vt, 'input>,
|
||||
) -> Stream<'parser, 'vt, 'input> {
|
||||
Stream {
|
||||
parser: self,
|
||||
stream,
|
||||
}
|
||||
Stream { parser: self, stream }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,10 +286,7 @@ impl<'input> Stream<'_, '_, 'input> {
|
|||
|
||||
match self.stream.next()? {
|
||||
vt::Token::Text(text) => {
|
||||
return Some(Input::Text(InputText {
|
||||
text,
|
||||
bracketed: false,
|
||||
}));
|
||||
return Some(Input::Text(InputText { text, bracketed: false }));
|
||||
}
|
||||
vt::Token::Ctrl(ch) => match ch {
|
||||
'\0' | '\t' | '\r' => return Some(Input::Keyboard(InputKey::new(ch as u32))),
|
||||
|
@ -312,11 +306,8 @@ impl<'input> Stream<'_, '_, 'input> {
|
|||
' '..='~' => {
|
||||
let ch = ch as u32;
|
||||
let key = ch & !0x20; // Shift a-z to A-Z
|
||||
let modifiers = if (ch & 0x20) != 0 {
|
||||
kbmod::ALT
|
||||
} else {
|
||||
kbmod::ALT_SHIFT
|
||||
};
|
||||
let modifiers =
|
||||
if (ch & 0x20) != 0 { kbmod::ALT } else { kbmod::ALT_SHIFT };
|
||||
return Some(Input::Keyboard(modifiers | InputKey::new(key)));
|
||||
}
|
||||
_ => {}
|
||||
|
@ -426,21 +417,12 @@ impl<'input> Stream<'_, '_, 'input> {
|
|||
}
|
||||
|
||||
mouse.modifiers = kbmod::NONE;
|
||||
mouse.modifiers |= if (btn & 0x04) != 0 {
|
||||
kbmod::SHIFT
|
||||
} else {
|
||||
kbmod::NONE
|
||||
};
|
||||
mouse.modifiers |= if (btn & 0x08) != 0 {
|
||||
kbmod::ALT
|
||||
} else {
|
||||
kbmod::NONE
|
||||
};
|
||||
mouse.modifiers |= if (btn & 0x10f) != 0 {
|
||||
kbmod::CTRL
|
||||
} else {
|
||||
kbmod::NONE
|
||||
};
|
||||
mouse.modifiers |=
|
||||
if (btn & 0x04) != 0 { kbmod::SHIFT } else { kbmod::NONE };
|
||||
mouse.modifiers |=
|
||||
if (btn & 0x08) != 0 { kbmod::ALT } else { kbmod::NONE };
|
||||
mouse.modifiers |=
|
||||
if (btn & 0x10f) != 0 { kbmod::CTRL } else { kbmod::NONE };
|
||||
|
||||
mouse.position.x = csi.params[1] - 1;
|
||||
mouse.position.y = csi.params[2] - 1;
|
||||
|
@ -480,10 +462,7 @@ impl<'input> Stream<'_, '_, 'input> {
|
|||
|
||||
if end != beg {
|
||||
let input = self.stream.input();
|
||||
Some(Input::Text(InputText {
|
||||
text: &input[beg..end],
|
||||
bracketed: true,
|
||||
}))
|
||||
Some(Input::Text(InputText { text: &input[beg..end], bracketed: true }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -498,9 +477,8 @@ 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<Input<'input>> {
|
||||
self.parser.x10_mouse_len += self
|
||||
.stream
|
||||
.read(&mut self.parser.x10_mouse_buf[self.parser.x10_mouse_len..]);
|
||||
self.parser.x10_mouse_len +=
|
||||
self.stream.read(&mut self.parser.x10_mouse_buf[self.parser.x10_mouse_len..]);
|
||||
if self.parser.x10_mouse_len < 3 {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
//! Rust has a very popular `memchr` crate. It's quite fast, so you may ask yourself
|
||||
//! why we don't just use it: Simply put, this is optimized for short inputs.
|
||||
|
||||
use super::distance;
|
||||
use std::ptr;
|
||||
|
||||
use super::distance;
|
||||
|
||||
/// memchr(), but with two needles.
|
||||
/// Returns the index of the first occurrence of either needle in the `haystack`.
|
||||
/// If no needle is found, `haystack.len()` is returned.
|
||||
|
@ -58,11 +59,7 @@ static mut MEMCHR2_DISPATCH: unsafe fn(
|
|||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe fn memchr2_dispatch(needle1: u8, needle2: u8, beg: *const u8, end: *const u8) -> *const u8 {
|
||||
let func = if is_x86_feature_detected!("avx2") {
|
||||
memchr2_avx2
|
||||
} else {
|
||||
memchr2_fallback
|
||||
};
|
||||
let func = if is_x86_feature_detected!("avx2") { memchr2_avx2 } else { memchr2_fallback };
|
||||
unsafe { MEMCHR2_DISPATCH = func };
|
||||
unsafe { func(needle1, needle2, beg, end) }
|
||||
}
|
||||
|
@ -136,9 +133,10 @@ unsafe fn memchr2_neon(needle1: u8, needle2: u8, mut beg: *const u8, end: *const
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::slice;
|
||||
|
||||
use super::*;
|
||||
use crate::sys;
|
||||
use std::slice;
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
//! Rust has a very popular `memchr` crate. It's quite fast, so you may ask yourself
|
||||
//! why we don't just use it: Simply put, this is optimized for short inputs.
|
||||
|
||||
use super::distance;
|
||||
use std::ptr;
|
||||
|
||||
use super::distance;
|
||||
|
||||
/// Same as `memchr2`, but searches from the end of the haystack.
|
||||
/// If no needle is found, 0 is returned.
|
||||
///
|
||||
|
@ -15,11 +16,7 @@ pub fn memrchr2(needle1: u8, needle2: u8, haystack: &[u8], offset: usize) -> Opt
|
|||
let beg = haystack.as_ptr();
|
||||
let it = beg.add(offset.min(haystack.len()));
|
||||
let it = memrchr2_raw(needle1, needle2, beg, it);
|
||||
if it.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(distance(it, beg))
|
||||
}
|
||||
if it.is_null() { None } else { Some(distance(it, beg)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,11 +59,7 @@ static mut MEMRCHR2_DISPATCH: unsafe fn(
|
|||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe fn memrchr2_dispatch(needle1: u8, needle2: u8, beg: *const u8, end: *const u8) -> *const u8 {
|
||||
let func = if is_x86_feature_detected!("avx2") {
|
||||
memrchr2_avx2
|
||||
} else {
|
||||
memrchr2_fallback
|
||||
};
|
||||
let func = if is_x86_feature_detected!("avx2") { memrchr2_avx2 } else { memrchr2_fallback };
|
||||
unsafe { MEMRCHR2_DISPATCH = func };
|
||||
unsafe { func(needle1, needle2, beg, end) }
|
||||
}
|
||||
|
@ -143,9 +136,10 @@ unsafe fn memrchr2_neon(needle1: u8, needle2: u8, beg: *const u8, mut end: *cons
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::slice;
|
||||
|
||||
use super::*;
|
||||
use crate::sys;
|
||||
use std::slice;
|
||||
|
||||
#[test]
|
||||
fn test_empty() {
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
//! types into a larger `u64` register we can treat all sizes as if they were `u64`. The only thing we need
|
||||
//! to take care of then, is the tail end of the array, where we need to write 0-7 additional bytes.
|
||||
|
||||
use super::distance;
|
||||
use std::mem;
|
||||
|
||||
use super::distance;
|
||||
|
||||
/// A trait to mark types that are safe to use with `memset`.
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -44,21 +45,13 @@ pub fn memset<T: MemsetSafe>(dst: &mut [T], val: T) {
|
|||
let beg = dst.as_mut_ptr();
|
||||
let end = beg.add(dst.len());
|
||||
let val = mem::transmute_copy::<_, u16>(&val);
|
||||
memset_raw(
|
||||
beg as *mut u8,
|
||||
end as *mut u8,
|
||||
val as u64 * 0x0001000100010001,
|
||||
);
|
||||
memset_raw(beg as *mut u8, end as *mut u8, val as u64 * 0x0001000100010001);
|
||||
}
|
||||
4 => {
|
||||
let beg = dst.as_mut_ptr();
|
||||
let end = beg.add(dst.len());
|
||||
let val = mem::transmute_copy::<_, u32>(&val);
|
||||
memset_raw(
|
||||
beg as *mut u8,
|
||||
end as *mut u8,
|
||||
val as u64 * 0x0000000100000001,
|
||||
);
|
||||
memset_raw(beg as *mut u8, end as *mut u8, val as u64 * 0x0000000100000001);
|
||||
}
|
||||
8 => {
|
||||
let beg = dst.as_mut_ptr();
|
||||
|
@ -85,11 +78,7 @@ static mut MEMSET_DISPATCH: unsafe fn(beg: *mut u8, end: *mut u8, val: u64) = me
|
|||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn memset_dispatch(beg: *mut u8, end: *mut u8, val: u64) {
|
||||
let func = if is_x86_feature_detected!("avx2") {
|
||||
memset_avx2
|
||||
} else {
|
||||
memset_sse2
|
||||
};
|
||||
let func = if is_x86_feature_detected!("avx2") { memset_avx2 } else { memset_sse2 };
|
||||
unsafe { MEMSET_DISPATCH = func };
|
||||
unsafe { func(beg, end, val) }
|
||||
}
|
||||
|
@ -253,10 +242,11 @@ unsafe fn memset_neon(mut beg: *mut u8, end: *mut u8, val: u64) {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fmt;
|
||||
use std::ops::Not;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn check_memset<T>(val: T, len: usize)
|
||||
where
|
||||
T: MemsetSafe + Not<Output = T> + PartialEq + fmt::Debug,
|
||||
|
|
|
@ -3,10 +3,10 @@ mod unix;
|
|||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub use std::fs::canonicalize;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use unix::*;
|
||||
#[cfg(windows)]
|
||||
pub use windows::*;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub use std::fs::canonicalize;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::apperr;
|
||||
use crate::arena::{Arena, ArenaString};
|
||||
use std::ffi::{CStr, c_int, c_void};
|
||||
use std::fs::{self, File};
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::os::fd::FromRawFd;
|
||||
use std::ptr::{self, NonNull, null, null_mut};
|
||||
use std::thread;
|
||||
use std::time;
|
||||
use std::{thread, time};
|
||||
|
||||
use crate::apperr;
|
||||
use crate::arena::{Arena, ArenaString};
|
||||
|
||||
struct State {
|
||||
stdin: libc::c_int,
|
||||
|
@ -68,11 +68,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;
|
||||
check_int_return(libc::sigaction(
|
||||
libc::SIGWINCH,
|
||||
&sigwinch_action,
|
||||
null_mut(),
|
||||
))?;
|
||||
check_int_return(libc::sigaction(libc::SIGWINCH, &sigwinch_action, null_mut()))?;
|
||||
|
||||
// Get the original terminal modes so we can disable raw mode on exit.
|
||||
let mut termios = MaybeUninit::<libc::termios>::uninit();
|
||||
|
@ -189,11 +185,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaStr
|
|||
if timeout != time::Duration::MAX {
|
||||
let beg = time::Instant::now();
|
||||
|
||||
let mut pollfd = libc::pollfd {
|
||||
fd: STATE.stdin,
|
||||
events: libc::POLLIN,
|
||||
revents: 0,
|
||||
};
|
||||
let mut pollfd = libc::pollfd { fd: STATE.stdin, events: libc::POLLIN, revents: 0 };
|
||||
let ts = libc::timespec {
|
||||
tv_sec: timeout.as_secs() as libc::time_t,
|
||||
tv_nsec: timeout.subsec_nanos() as libc::c_long,
|
||||
|
@ -361,16 +353,8 @@ pub unsafe fn virtual_release(base: NonNull<u8>, size: usize) {
|
|||
/// and to pass a size less than or equal to the size passed to `virtual_reserve`.
|
||||
pub unsafe fn virtual_commit(base: NonNull<u8>, 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(())
|
||||
}
|
||||
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(()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,9 +482,7 @@ pub fn preferred_languages(arena: &Arena) -> Vec<ArenaString<'_>, &Arena> {
|
|||
for key in ["LANGUAGE", "LC_ALL", "LANG"] {
|
||||
if let Ok(val) = std::env::var(key) {
|
||||
locales.extend(
|
||||
val.split(':')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| ArenaString::from_str(arena, s)),
|
||||
val.split(':').filter(|s| !s.is_empty()).map(|s| ArenaString::from_str(arena, s)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -536,9 +518,5 @@ const fn errno_to_apperr(no: c_int) -> apperr::Error {
|
|||
}
|
||||
|
||||
fn check_int_return(ret: libc::c_int) -> apperr::Result<libc::c_int> {
|
||||
if ret < 0 {
|
||||
Err(errno_to_apperr(unsafe { *libc::__errno_location() }))
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
if ret < 0 { Err(errno_to_apperr(unsafe { *libc::__errno_location() })) } else { Ok(ret) }
|
||||
}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::helpers::{CoordType, Size};
|
||||
use crate::{apperr, helpers};
|
||||
use std::ffi::{CStr, OsString, c_void};
|
||||
use std::fmt::Write as _;
|
||||
use std::fs::{self, File};
|
||||
|
@ -9,17 +6,17 @@ use std::os::windows::io::FromRawHandle;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::ptr::{self, NonNull, null, null_mut};
|
||||
use std::{mem, time};
|
||||
use windows_sys::Win32::Foundation;
|
||||
use windows_sys::Win32::Globalization;
|
||||
|
||||
use windows_sys::Win32::Storage::FileSystem;
|
||||
use windows_sys::Win32::System::Console;
|
||||
use windows_sys::Win32::System::Diagnostics::Debug;
|
||||
use windows_sys::Win32::System::IO;
|
||||
use windows_sys::Win32::System::LibraryLoader;
|
||||
use windows_sys::Win32::System::Memory;
|
||||
use windows_sys::Win32::System::Threading;
|
||||
use windows_sys::Win32::System::{Console, IO, LibraryLoader, Memory, Threading};
|
||||
use windows_sys::Win32::{Foundation, Globalization};
|
||||
use windows_sys::w;
|
||||
|
||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::helpers::{CoordType, Size};
|
||||
use crate::{apperr, helpers};
|
||||
|
||||
type ReadConsoleInputExW = unsafe extern "system" fn(
|
||||
h_console_input: Foundation::HANDLE,
|
||||
lp_buffer: *mut Console::INPUT_RECORD,
|
||||
|
@ -156,21 +153,12 @@ impl Drop for Deinit {
|
|||
|
||||
pub fn switch_modes() -> apperr::Result<()> {
|
||||
unsafe {
|
||||
check_bool_return(Console::SetConsoleCtrlHandler(
|
||||
Some(console_ctrl_handler),
|
||||
1,
|
||||
))?;
|
||||
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::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))?;
|
||||
|
@ -208,10 +196,7 @@ fn get_console_size() -> Option<Size> {
|
|||
|
||||
let w = (info.srWindow.Right - info.srWindow.Left + 1).max(1) as CoordType;
|
||||
let h = (info.srWindow.Bottom - info.srWindow.Top + 1).max(1) as CoordType;
|
||||
Some(Size {
|
||||
width: w,
|
||||
height: h,
|
||||
})
|
||||
Some(Size { width: w, height: h })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,10 +286,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaStr
|
|||
// Windows is prone to sending broken/useless `WINDOW_BUFFER_SIZE_EVENT`s.
|
||||
// E.g. starting conhost will emit 3 in a row. Skip rendering in that case.
|
||||
if w > 0 && h > 0 {
|
||||
resize_event = Some(Size {
|
||||
width: w,
|
||||
height: h,
|
||||
});
|
||||
resize_event = Some(Size { width: w, height: h });
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -317,11 +299,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaStr
|
|||
}
|
||||
|
||||
const RESIZE_EVENT_FMT_MAX_LEN: usize = 16; // "\x1b[8;65535;65535t"
|
||||
let resize_event_len = if resize_event.is_some() {
|
||||
RESIZE_EVENT_FMT_MAX_LEN
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let resize_event_len = if resize_event.is_some() { RESIZE_EVENT_FMT_MAX_LEN } else { 0 };
|
||||
// +1 to account for a potential `STATE.leading_surrogate`.
|
||||
let utf8_max_len = (utf16_buf_len + 1) * 3;
|
||||
let mut text = arena.new_string();
|
||||
|
@ -332,11 +310,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaStr
|
|||
// If I read xterm's documentation correctly, CSI 18 t reports the window size in characters.
|
||||
// CSI 8 ; height ; width t is the response. Of course, we didn't send the request,
|
||||
// but we can use this fake response to trigger the editor to resize itself.
|
||||
_ = write!(
|
||||
text,
|
||||
"\x1b[8;{};{}t",
|
||||
resize_event.height, resize_event.width
|
||||
);
|
||||
_ = write!(text, "\x1b[8;{};{}t", resize_event.height, resize_event.width);
|
||||
}
|
||||
|
||||
// If the input ends with a lone lead surrogate, we need to remember it for the next read.
|
||||
|
@ -398,11 +372,7 @@ pub fn open_stdin_if_redirected() -> Option<File> {
|
|||
unsafe {
|
||||
let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE);
|
||||
// Did we reopen stdin during `init()`?
|
||||
if !std::ptr::eq(STATE.stdin, handle) {
|
||||
Some(File::from_raw_handle(handle))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if !std::ptr::eq(STATE.stdin, handle) { Some(File::from_raw_handle(handle)) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -506,11 +476,7 @@ unsafe fn load_library(name: *const u16) -> apperr::Result<NonNull<c_void>> {
|
|||
pub unsafe fn get_proc_address<T>(handle: NonNull<c_void>, name: &CStr) -> apperr::Result<T> {
|
||||
unsafe {
|
||||
let ptr = LibraryLoader::GetProcAddress(handle.as_ptr(), name.as_ptr() 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(get_last_error()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -601,11 +567,7 @@ fn get_last_error() -> apperr::Error {
|
|||
|
||||
#[inline]
|
||||
const fn gle_to_apperr(gle: u32) -> apperr::Error {
|
||||
apperr::Error::new_sys(if gle == 0 {
|
||||
0x8000FFFF
|
||||
} else {
|
||||
0x80070000 | gle
|
||||
})
|
||||
apperr::Error::new_sys(if gle == 0 { 0x8000FFFF } else { 0x80070000 | gle })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -647,11 +609,7 @@ pub fn apperr_is_not_found(err: apperr::Error) -> bool {
|
|||
}
|
||||
|
||||
fn check_bool_return(ret: Foundation::BOOL) -> apperr::Result<()> {
|
||||
if ret == 0 {
|
||||
Err(get_last_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
if ret == 0 { Err(get_last_error()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
fn check_ptr_return<T>(ret: *mut T) -> apperr::Result<NonNull<T>> {
|
||||
|
|
161
src/tui.rs
161
src/tui.rs
|
@ -1,3 +1,9 @@
|
|||
use std::arch::breakpoint;
|
||||
#[cfg(debug_assertions)]
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write as _;
|
||||
use std::{iter, mem, ptr, time};
|
||||
|
||||
use crate::arena::{Arena, ArenaString, scratch_arena};
|
||||
use crate::buffer::{CursorMovement, RcTextBuffer, TextBuffer, TextBufferCell};
|
||||
use crate::cell::*;
|
||||
|
@ -6,15 +12,6 @@ use crate::helpers::{CoordType, Point, Rect, Size, hash, hash_str, opt_ptr_eq, w
|
|||
use crate::input::{InputKeyMod, kbmod, vk};
|
||||
use crate::ucd::Document;
|
||||
use crate::{apperr, helpers, input, ucd};
|
||||
use std::arch::breakpoint;
|
||||
use std::fmt::Write as _;
|
||||
use std::iter;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
use std::time;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use std::collections::HashSet;
|
||||
|
||||
const ROOT_ID: u64 = 0x14057B7EF767814F; // Knuth's MMIX constant
|
||||
const SHIFT_TAB: InputKey = vk::TAB.with_modifiers(kbmod::SHIFT);
|
||||
|
@ -105,10 +102,7 @@ impl Tui {
|
|||
modal_default_bg: 0,
|
||||
modal_default_fg: 0,
|
||||
|
||||
size: Size {
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
size: Size { width: 0, height: 0 },
|
||||
mouse_position: Point::MIN,
|
||||
mouse_down_position: Point::MIN,
|
||||
left_mouse_down_target: 0,
|
||||
|
@ -593,8 +587,7 @@ impl Tui {
|
|||
}
|
||||
}
|
||||
|
||||
self.framebuffer
|
||||
.replace_attr(outer_clipped, Attributes::All, Attributes::None);
|
||||
self.framebuffer.replace_attr(outer_clipped, Attributes::All, Attributes::None);
|
||||
}
|
||||
|
||||
self.framebuffer.blend_bg(outer_clipped, node.attributes.bg);
|
||||
|
@ -655,20 +648,15 @@ impl Tui {
|
|||
let mut end = cfg.goto_visual(Point { x: mid_end_x, y: 0 });
|
||||
if end.visual_pos.x < mid_end_x {
|
||||
// If we intersected a wide glyph, we need to move past that.
|
||||
end = cfg.goto_logical(Point {
|
||||
x: end.logical_pos.x + 1,
|
||||
y: 0,
|
||||
});
|
||||
end =
|
||||
cfg.goto_logical(Point { x: end.logical_pos.x + 1, y: 0 });
|
||||
}
|
||||
modified.push_str(&text[..beg.offset]);
|
||||
modified.push('…');
|
||||
modified.push_str(&text[end.offset..]);
|
||||
}
|
||||
Overflow::TruncateTail => {
|
||||
let end = cfg.goto_visual(Point {
|
||||
x: restricted_width - 1,
|
||||
y: 0,
|
||||
});
|
||||
let end = cfg.goto_visual(Point { x: restricted_width - 1, y: 0 });
|
||||
modified.push_str(&text[..end.offset]);
|
||||
modified.push('…');
|
||||
}
|
||||
|
@ -712,12 +700,9 @@ impl Tui {
|
|||
destination.right -= 1;
|
||||
}
|
||||
|
||||
if let Some(res) = tb.render(
|
||||
tc.scroll_offset,
|
||||
destination,
|
||||
tc.has_focus,
|
||||
&mut self.framebuffer,
|
||||
) {
|
||||
if let Some(res) =
|
||||
tb.render(tc.scroll_offset, destination, tc.has_focus, &mut self.framebuffer)
|
||||
{
|
||||
tc.scroll_offset_x_max = res.visual_pos_x_max;
|
||||
}
|
||||
|
||||
|
@ -836,11 +821,7 @@ impl Tui {
|
|||
_ = write!(
|
||||
result,
|
||||
" text: \"{}\"\r\n",
|
||||
content
|
||||
.chunks
|
||||
.iter()
|
||||
.map(|c| c.text.as_str())
|
||||
.collect::<String>()
|
||||
content.chunks.iter().map(|c| c.text.as_str()).collect::<String>()
|
||||
);
|
||||
}
|
||||
NodeContent::Textarea(content) => {
|
||||
|
@ -894,9 +875,7 @@ impl Tui {
|
|||
.iter()
|
||||
.skip(pop_minimum)
|
||||
.position(|&id| {
|
||||
self.prev_node_map
|
||||
.get(id)
|
||||
.is_some_and(|node| node.borrow().attributes.focusable)
|
||||
self.prev_node_map.get(id).is_some_and(|node| node.borrow().attributes.focusable)
|
||||
})
|
||||
.map(|idx| idx + pop_minimum)
|
||||
.unwrap_or(path.len());
|
||||
|
@ -983,17 +962,9 @@ impl Tui {
|
|||
|
||||
let row = row.borrow();
|
||||
let cell = cell.borrow();
|
||||
let mut next = if input == vk::LEFT {
|
||||
cell.siblings.prev
|
||||
} else {
|
||||
cell.siblings.next
|
||||
};
|
||||
let mut next = if input == vk::LEFT { cell.siblings.prev } else { cell.siblings.next };
|
||||
if next.is_none() {
|
||||
next = if input == vk::LEFT {
|
||||
row.children.last
|
||||
} else {
|
||||
row.children.first
|
||||
};
|
||||
next = if input == vk::LEFT { row.children.last } else { row.children.first };
|
||||
}
|
||||
|
||||
if let Some(next) = next {
|
||||
|
@ -1283,10 +1254,7 @@ impl<'a> Context<'a, '_> {
|
|||
ln.attributes.float = Some(FloatAttributes {
|
||||
gravity_x: spec.gravity_x.clamp(0.0, 1.0),
|
||||
gravity_y: spec.gravity_y.clamp(0.0, 1.0),
|
||||
offset: Point {
|
||||
x: spec.offset_x,
|
||||
y: spec.offset_y,
|
||||
},
|
||||
offset: Point { x: spec.offset_x, y: spec.offset_y },
|
||||
});
|
||||
ln.attributes.bg = self.tui.floater_default_bg;
|
||||
ln.attributes.fg = self.tui.floater_default_fg;
|
||||
|
@ -1367,14 +1335,8 @@ impl<'a> Context<'a, '_> {
|
|||
|
||||
pub fn modal_begin(&mut self, classname: &'static str, title: &str) {
|
||||
self.block_begin(classname);
|
||||
self.attr_float(FloatSpec {
|
||||
anchor: Anchor::Root,
|
||||
..Default::default()
|
||||
});
|
||||
self.attr_intrinsic_size(Size {
|
||||
width: self.tui.size.width,
|
||||
height: self.tui.size.height,
|
||||
});
|
||||
self.attr_float(FloatSpec { anchor: Anchor::Root, ..Default::default() });
|
||||
self.attr_intrinsic_size(Size { width: self.tui.size.width, height: self.tui.size.height });
|
||||
self.attr_background_rgba(self.indexed_alpha(IndexedColor::Background, 0xd6));
|
||||
self.attr_foreground_rgba(self.indexed_alpha(IndexedColor::Background, 0xd6));
|
||||
self.attr_focus_well();
|
||||
|
@ -1482,10 +1444,8 @@ impl<'a> Context<'a, '_> {
|
|||
|
||||
pub fn styled_label_begin(&mut self, classname: &'static str, overflow: Overflow) {
|
||||
self.block_begin(classname);
|
||||
self.tree.last_node.borrow_mut().content = NodeContent::Text(TextContent {
|
||||
chunks: Vec::new_in(self.arena()),
|
||||
overflow,
|
||||
});
|
||||
self.tree.last_node.borrow_mut().content =
|
||||
NodeContent::Text(TextContent { chunks: Vec::new_in(self.arena()), overflow });
|
||||
}
|
||||
|
||||
pub fn styled_label_set_foreground(&mut self, color: u32) {
|
||||
|
@ -1537,10 +1497,8 @@ impl<'a> Context<'a, '_> {
|
|||
return;
|
||||
};
|
||||
|
||||
let cursor = ucd::MeasurementConfig::new(&content.chunks).goto_visual(Point {
|
||||
x: CoordType::MAX,
|
||||
y: 0,
|
||||
});
|
||||
let cursor = ucd::MeasurementConfig::new(&content.chunks)
|
||||
.goto_visual(Point { x: CoordType::MAX, y: 0 });
|
||||
last_node.intrinsic_size.width = cursor.visual_pos.x;
|
||||
last_node.intrinsic_size.height = 1;
|
||||
last_node.intrinsic_size_set = true;
|
||||
|
@ -1995,14 +1953,10 @@ impl<'a> Context<'a, '_> {
|
|||
},
|
||||
vk::HOME => match modifiers {
|
||||
kbmod::CTRL => tb.cursor_move_to_logical(Point::default()),
|
||||
kbmod::SHIFT => tb.selection_update_visual(Point {
|
||||
x: 0,
|
||||
y: tb.get_cursor_visual_pos().y,
|
||||
}),
|
||||
_ => tb.cursor_move_to_visual(Point {
|
||||
x: 0,
|
||||
y: tb.get_cursor_visual_pos().y,
|
||||
}),
|
||||
kbmod::SHIFT => {
|
||||
tb.selection_update_visual(Point { x: 0, y: tb.get_cursor_visual_pos().y })
|
||||
}
|
||||
_ => tb.cursor_move_to_visual(Point { x: 0, y: tb.get_cursor_visual_pos().y }),
|
||||
},
|
||||
vk::LEFT => {
|
||||
let granularity = if modifiers.contains(kbmod::CTRL) {
|
||||
|
@ -2334,16 +2288,12 @@ impl<'a> Context<'a, '_> {
|
|||
.prev_node_map
|
||||
.get(last_node.id)
|
||||
.and_then(|node| match &node.borrow().content {
|
||||
NodeContent::List(content) => Some(ListContent {
|
||||
selected: content.selected,
|
||||
selected_node: None,
|
||||
}),
|
||||
NodeContent::List(content) => {
|
||||
Some(ListContent { selected: content.selected, selected_node: None })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(ListContent {
|
||||
selected: 0,
|
||||
selected_node: None,
|
||||
});
|
||||
.unwrap_or(ListContent { selected: 0, selected_node: None });
|
||||
|
||||
last_node.attributes.focus_void = true;
|
||||
last_node.content = NodeContent::List(content);
|
||||
|
@ -2432,17 +2382,10 @@ impl<'a> Context<'a, '_> {
|
|||
{
|
||||
let selected = selected_node.unwrap().borrow();
|
||||
let forward = self.input_keyboard == Some(vk::DOWN);
|
||||
let mut node = if forward {
|
||||
selected.siblings.next
|
||||
} else {
|
||||
selected.siblings.prev
|
||||
};
|
||||
let mut node =
|
||||
if forward { selected.siblings.next } else { selected.siblings.prev };
|
||||
if node.is_none() {
|
||||
node = if forward {
|
||||
list.children.first
|
||||
} else {
|
||||
list.children.last
|
||||
};
|
||||
node = if forward { list.children.first } else { list.children.last };
|
||||
}
|
||||
selected_next = node;
|
||||
} else {
|
||||
|
@ -2656,12 +2599,7 @@ impl<'a> Context<'a, '_> {
|
|||
}
|
||||
|
||||
self.styled_label_end();
|
||||
self.attr_padding(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 2,
|
||||
bottom: 0,
|
||||
});
|
||||
self.attr_padding(Rect { left: 0, top: 0, right: 2, bottom: 0 });
|
||||
}
|
||||
|
||||
fn menubar_shortcut(&mut self, shortcut: InputKey) {
|
||||
|
@ -2687,12 +2625,7 @@ impl<'a> Context<'a, '_> {
|
|||
self.block_begin("shortcut");
|
||||
self.block_end();
|
||||
}
|
||||
self.attr_padding(Rect {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 2,
|
||||
bottom: 0,
|
||||
});
|
||||
self.attr_padding(Rect { left: 0, top: 0, right: 2, bottom: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2828,16 +2761,8 @@ impl<'a> Tree<'a> {
|
|||
mut cb: T,
|
||||
) {
|
||||
let mut node = start;
|
||||
let children_idx = if forward {
|
||||
NodeChildren::FIRST
|
||||
} else {
|
||||
NodeChildren::LAST
|
||||
};
|
||||
let siblings_idx = if forward {
|
||||
NodeSiblings::NEXT
|
||||
} else {
|
||||
NodeSiblings::PREV
|
||||
};
|
||||
let children_idx = if forward { NodeChildren::FIRST } else { NodeChildren::LAST };
|
||||
let siblings_idx = if forward { NodeSiblings::NEXT } else { NodeSiblings::PREV };
|
||||
|
||||
while {
|
||||
'traverse: {
|
||||
|
@ -2888,11 +2813,7 @@ struct NodeMap<'a> {
|
|||
|
||||
impl Default for NodeMap<'_> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
slots: vec![None; 1],
|
||||
shift: 0,
|
||||
mask: 0,
|
||||
}
|
||||
Self { slots: vec![None; 1], shift: 0, mask: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
80
src/ucd.rs
80
src/ucd.rs
|
@ -1,8 +1,9 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use crate::helpers::{self, CoordType, Point};
|
||||
use crate::simd::{memchr2, memrchr2};
|
||||
use crate::ucd_gen::*;
|
||||
use crate::utf8::Utf8Chars;
|
||||
use std::ops::Range;
|
||||
|
||||
/// An abstraction over potentially chunked text containers.
|
||||
pub trait Document {
|
||||
|
@ -74,12 +75,7 @@ pub struct MeasurementConfig<'doc> {
|
|||
|
||||
impl<'doc> MeasurementConfig<'doc> {
|
||||
pub fn new(buffer: &'doc dyn Document) -> Self {
|
||||
Self {
|
||||
buffer,
|
||||
tab_size: 8,
|
||||
word_wrap_column: 0,
|
||||
cursor: UcdCursor::default(),
|
||||
}
|
||||
Self { buffer, tab_size: 8, word_wrap_column: 0, cursor: UcdCursor::default() }
|
||||
}
|
||||
|
||||
pub fn with_tab_size(mut self, tab_size: CoordType) -> Self {
|
||||
|
@ -472,14 +468,8 @@ impl<'doc> MeasurementConfig<'doc> {
|
|||
|
||||
UcdCursor {
|
||||
offset,
|
||||
logical_pos: Point {
|
||||
x: logical_pos_x,
|
||||
y: logical_pos_y,
|
||||
},
|
||||
visual_pos: Point {
|
||||
x: visual_pos_x,
|
||||
y: visual_pos_y,
|
||||
},
|
||||
logical_pos: Point { x: logical_pos_x, y: logical_pos_y },
|
||||
visual_pos: Point { x: visual_pos_x, y: visual_pos_y },
|
||||
column,
|
||||
wrap_opp,
|
||||
}
|
||||
|
@ -529,22 +519,12 @@ const WORD_CLASSIFIER: [CharClass; 256] =
|
|||
/// Finds the next word boundary given a document cursor offset.
|
||||
/// Returns the offset of the next word boundary.
|
||||
pub fn word_forward(doc: &dyn Document, offset: usize) -> usize {
|
||||
word_navigation(WordForward {
|
||||
doc,
|
||||
offset,
|
||||
chunk: &[],
|
||||
chunk_off: 0,
|
||||
})
|
||||
word_navigation(WordForward { doc, offset, chunk: &[], chunk_off: 0 })
|
||||
}
|
||||
|
||||
/// The backward version of `word_forward`.
|
||||
pub fn word_backward(doc: &dyn Document, offset: usize) -> usize {
|
||||
word_navigation(WordBackward {
|
||||
doc,
|
||||
offset,
|
||||
chunk: &[],
|
||||
chunk_off: 0,
|
||||
})
|
||||
word_navigation(WordBackward { doc, offset, chunk: &[], chunk_off: 0 })
|
||||
}
|
||||
|
||||
/// Word navigation implementation. Matches the behavior of VS Code.
|
||||
|
@ -923,10 +903,7 @@ mod test {
|
|||
|
||||
// Does hitting the visual target within a word reset the hit back to the end of the visual line?
|
||||
let mut cfg = MeasurementConfig::new(&text).with_word_wrap_column(6);
|
||||
let cursor = cfg.goto_visual(Point {
|
||||
x: CoordType::MAX,
|
||||
y: 0,
|
||||
});
|
||||
let cursor = cfg.goto_visual(Point { x: CoordType::MAX, y: 0 });
|
||||
assert_eq!(
|
||||
cursor,
|
||||
UcdCursor {
|
||||
|
@ -939,9 +916,8 @@ mod test {
|
|||
);
|
||||
|
||||
// Does hitting the same target but with a non-zero starting position result in the same outcome?
|
||||
let mut cfg = MeasurementConfig::new(&text)
|
||||
.with_word_wrap_column(6)
|
||||
.with_cursor(UcdCursor {
|
||||
let mut cfg =
|
||||
MeasurementConfig::new(&text).with_word_wrap_column(6).with_cursor(UcdCursor {
|
||||
offset: 1,
|
||||
logical_pos: Point { x: 1, y: 0 },
|
||||
visual_pos: Point { x: 1, y: 0 },
|
||||
|
@ -1012,9 +988,8 @@ mod test {
|
|||
#[test]
|
||||
fn test_measure_forward_tabs() {
|
||||
let text = "a\tb\tc".as_bytes();
|
||||
let cursor = MeasurementConfig::new(&text)
|
||||
.with_tab_size(4)
|
||||
.goto_visual(Point { x: 4, y: 0 });
|
||||
let cursor =
|
||||
MeasurementConfig::new(&text).with_tab_size(4).goto_visual(Point { x: 4, y: 0 });
|
||||
assert_eq!(
|
||||
cursor,
|
||||
UcdCursor {
|
||||
|
@ -1045,12 +1020,7 @@ mod test {
|
|||
// |foo_ |
|
||||
// |bar. |
|
||||
// |abc |
|
||||
let chunks = [
|
||||
"foo ".as_bytes(),
|
||||
"bar".as_bytes(),
|
||||
".\n".as_bytes(),
|
||||
"abc".as_bytes(),
|
||||
];
|
||||
let chunks = ["foo ".as_bytes(), "bar".as_bytes(), ".\n".as_bytes(), "abc".as_bytes()];
|
||||
let doc = ChunkedDoc(&chunks);
|
||||
let mut cfg = MeasurementConfig::new(&doc).with_word_wrap_column(7);
|
||||
let max = CoordType::MAX;
|
||||
|
@ -1225,10 +1195,7 @@ mod test {
|
|||
let mut cfg = MeasurementConfig::new(&bytes).with_word_wrap_column(8);
|
||||
|
||||
// At the end of "// " there should be a wrap.
|
||||
let end0 = cfg.goto_visual(Point {
|
||||
x: CoordType::MAX,
|
||||
y: 0,
|
||||
});
|
||||
let end0 = cfg.goto_visual(Point { x: CoordType::MAX, y: 0 });
|
||||
assert_eq!(
|
||||
end0,
|
||||
UcdCursor {
|
||||
|
@ -1240,10 +1207,7 @@ mod test {
|
|||
}
|
||||
);
|
||||
|
||||
let mid1 = cfg.goto_visual(Point {
|
||||
x: end0.visual_pos.x,
|
||||
y: 1,
|
||||
});
|
||||
let mid1 = cfg.goto_visual(Point { x: end0.visual_pos.x, y: 1 });
|
||||
assert_eq!(
|
||||
mid1,
|
||||
UcdCursor {
|
||||
|
@ -1255,10 +1219,7 @@ mod test {
|
|||
}
|
||||
);
|
||||
|
||||
let mid2 = cfg.goto_visual(Point {
|
||||
x: end0.visual_pos.x,
|
||||
y: 2,
|
||||
});
|
||||
let mid2 = cfg.goto_visual(Point { x: end0.visual_pos.x, y: 2 });
|
||||
assert_eq!(
|
||||
mid2,
|
||||
UcdCursor {
|
||||
|
@ -1329,9 +1290,7 @@ mod test {
|
|||
// |____b | <- 1 tab, 1 space
|
||||
let text = "foo \t b";
|
||||
let bytes = text.as_bytes();
|
||||
let mut cfg = MeasurementConfig::new(&bytes)
|
||||
.with_word_wrap_column(8)
|
||||
.with_tab_size(4);
|
||||
let mut cfg = MeasurementConfig::new(&bytes).with_word_wrap_column(8).with_tab_size(4);
|
||||
let max = CoordType::MAX;
|
||||
|
||||
let end0 = cfg.goto_visual(Point { x: max, y: 0 });
|
||||
|
@ -1374,10 +1333,7 @@ mod test {
|
|||
#[test]
|
||||
fn test_crlf() {
|
||||
let text = "a\r\nbcd\r\ne".as_bytes();
|
||||
let cursor = MeasurementConfig::new(&text).goto_visual(Point {
|
||||
x: CoordType::MAX,
|
||||
y: 1,
|
||||
});
|
||||
let cursor = MeasurementConfig::new(&text).goto_visual(Point { x: CoordType::MAX, y: 1 });
|
||||
assert_eq!(
|
||||
cursor,
|
||||
UcdCursor {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use std::hint;
|
||||
use std::iter;
|
||||
use std::{hint, iter};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Utf8Chars<'a> {
|
||||
|
|
36
src/vt.rs
36
src/vt.rs
|
@ -1,5 +1,4 @@
|
|||
use std::mem;
|
||||
use std::time;
|
||||
use std::{mem, time};
|
||||
|
||||
use crate::simd::memchr2;
|
||||
|
||||
|
@ -43,12 +42,7 @@ impl Parser {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: State::Ground,
|
||||
csi: Csi {
|
||||
params: [0; 32],
|
||||
param_count: 0,
|
||||
private_byte: '\0',
|
||||
final_byte: '\0',
|
||||
},
|
||||
csi: Csi { params: [0; 32], param_count: 0, private_byte: '\0', final_byte: '\0' },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,11 +69,7 @@ impl Parser {
|
|||
&'parser mut self,
|
||||
input: &'input str,
|
||||
) -> Stream<'parser, 'input> {
|
||||
Stream {
|
||||
parser: self,
|
||||
input,
|
||||
off: 0,
|
||||
}
|
||||
Stream { parser: self, input, off: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,14 +284,8 @@ impl<'parser, 'input> Stream<'parser, 'input> {
|
|||
self.off += 1;
|
||||
|
||||
return match state {
|
||||
State::OscEsc => Some(Token::Osc {
|
||||
data: "",
|
||||
partial: false,
|
||||
}),
|
||||
_ => Some(Token::Dcs {
|
||||
data: "",
|
||||
partial: false,
|
||||
}),
|
||||
State::OscEsc => Some(Token::Osc { data: "", partial: false }),
|
||||
_ => Some(Token::Dcs { data: "", partial: false }),
|
||||
};
|
||||
} else {
|
||||
// False alarm: Not a string terminator.
|
||||
|
@ -312,14 +296,8 @@ impl<'parser, 'input> Stream<'parser, 'input> {
|
|||
_ => State::Dcs,
|
||||
};
|
||||
return match parser.state {
|
||||
State::Osc => Some(Token::Osc {
|
||||
data: "\x1b",
|
||||
partial: true,
|
||||
}),
|
||||
_ => Some(Token::Dcs {
|
||||
data: "\x1b",
|
||||
partial: true,
|
||||
}),
|
||||
State::Osc => Some(Token::Osc { data: "\x1b", partial: true }),
|
||||
_ => Some(Token::Dcs { data: "\x1b", partial: true }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue