use std::{
collections::HashMap,
fs,
ops::Range,
path::{Path, PathBuf},
sync::{LazyLock, Mutex, MutexGuard},
};
use crossterm::event::{MouseButton, MouseEventKind};
pub use crate::buffer::{
buffer_id::BufferId,
history::{Change, Changes, History, Moment, RangesToUpdate},
opts::BufferOpts,
};
use crate::{
context::{self, Handle, cache},
data::{Pass, RwData, WriteableTuple},
hook::{self, BufferSaved, BufferUpdated, OnMouseEvent},
mode::{Cursor, Selections, TwoPointsPlace},
opts::PrintOpts,
text::{Point, Strs, StrsBuf, Text, TextMut, TextParts, TextVersion, txt},
ui::{Area, Coord, PrintInfo, PrintedLine, Widget},
};
mod history;
mod opts;
pub(crate) fn add_buffer_hooks() {
hook::add::<OnMouseEvent<Buffer>>(|pa, event| match event.kind {
MouseEventKind::Down(MouseButton::Left) => {
let point = match event.points {
Some(TwoPointsPlace::Within(points) | TwoPointsPlace::AheadOf(points)) => {
points.real
}
_ => event.handle.text(pa).last_point(),
};
event.handle.selections_mut(pa).remove_extras();
event.handle.edit_main(pa, |mut c| {
c.unset_anchor();
c.move_to(point)
})
}
MouseEventKind::Down(_) => {}
MouseEventKind::Up(_) => {}
MouseEventKind::Drag(MouseButton::Left) => {
let point = match event.points {
Some(TwoPointsPlace::Within(points) | TwoPointsPlace::AheadOf(points)) => {
points.real
}
_ => event.handle.text(pa).last_point(),
};
event.handle.selections_mut(pa).remove_extras();
event.handle.edit_main(pa, |mut c| {
c.set_anchor_if_needed();
c.move_to(point);
})
}
MouseEventKind::Drag(_) => {}
MouseEventKind::Moved => {}
MouseEventKind::ScrollDown => {
let opts = event.handle.opts(pa);
let (widget, area) = event.handle.write_with_area(pa);
area.scroll_ver(widget.text(), 3, opts);
}
MouseEventKind::ScrollUp => {
let opts = event.handle.opts(pa);
let (widget, area) = event.handle.write_with_area(pa);
area.scroll_ver(widget.text(), -3, opts);
}
MouseEventKind::ScrollLeft => {}
MouseEventKind::ScrollRight => {}
});
}
pub struct Buffer {
id: BufferId,
path: PathKind,
text: Text,
pub(crate) layout_order: usize,
history: History,
cached_print_info: Mutex<Option<CachedPrintInfo>>,
pub opts: BufferOpts,
prev_opts: Mutex<PrintOpts>,
was_reloaded: bool,
}
impl Buffer {
pub(crate) fn new(path: Option<PathBuf>, opts: BufferOpts) -> Self {
let (text, path) = match path {
Some(path) => {
let canon_path = path.canonicalize();
if let Ok(path) = &canon_path
&& let Ok(buffer) = std::fs::read_to_string(path)
{
let selections = {
let selection = cache::load(path).unwrap_or_default();
Selections::new(selection)
};
let text = Text::from_parts(StrsBuf::new(buffer), selections);
(text, PathKind::SetExists(path.clone()))
} else if canon_path.is_err()
&& let Ok(mut canon_path) = path.with_file_name(".").canonicalize()
{
canon_path.push(path.file_name().unwrap());
(
Text::with_default_main_selection(),
PathKind::SetAbsent(canon_path),
)
} else {
(Text::with_default_main_selection(), PathKind::new_unset())
}
}
None => (Text::with_default_main_selection(), PathKind::new_unset()),
};
let history = History::new(&text);
Self {
id: BufferId::new(),
path,
text,
layout_order: 0,
history,
cached_print_info: Mutex::new(None),
opts,
prev_opts: Mutex::new(opts.to_print_opts()),
was_reloaded: false,
}
}
pub(crate) fn from_raw_parts(
buf: StrsBuf,
selections: Selections,
history: History,
path: PathKind,
opts: BufferOpts,
layout_order: usize,
was_reloaded: bool,
) -> Buffer {
Self {
id: BufferId::new(),
path,
text: Text::from_parts(buf, selections),
layout_order,
history,
cached_print_info: Mutex::new(None),
opts,
prev_opts: Mutex::new(opts.to_print_opts()),
was_reloaded,
}
}
pub fn path(&self) -> PathBuf {
self.path.path()
}
pub fn path_set(&self) -> Option<PathBuf> {
self.path.path_set()
}
pub fn path_txt(&self) -> Text {
self.path_kind().path_txt()
}
pub fn name(&self) -> String {
self.path.name()
}
pub fn name_set(&self) -> Option<String> {
self.path.name_set()
}
pub fn name_txt(&self) -> Text {
self.path.name_txt()
}
pub fn path_kind(&self) -> PathKind {
self.path.clone()
}
fn reset_print_info_if_needed<'b>(
&'b self,
area: &Area,
) -> MutexGuard<'b, Option<CachedPrintInfo>> {
let opts_changed = {
let mut prev_opts = self.prev_opts.lock().unwrap();
let cur_opts = self.opts.to_print_opts();
let opts_changed = *prev_opts != cur_opts;
*prev_opts = cur_opts;
opts_changed
};
let mut cached_print_info = self.cached_print_info.lock().unwrap();
if opts_changed
|| cached_print_info.as_ref().is_none_or(|cpi| {
self.text
.version()
.has_structurally_changed_since(cpi.text_state)
|| area.get_print_info() != cpi.area_print_info
|| area.top_left() != cpi.coords.0
|| area.bottom_right() != cpi.coords.1
})
{
let opts = self.opts.to_print_opts();
let start = area.start_points(&self.text, opts).real;
let end = area.end_points(&self.text, opts).real;
let printed_line_numbers = area.get_printed_lines(&self.text, opts).unwrap();
*cached_print_info = Some(CachedPrintInfo {
range: start..end,
printed_line_numbers,
printed_line_ranges: None,
_visible_line_ranges: None,
text_state: self.text.version(),
area_print_info: area.get_print_info(),
coords: (area.top_left(), area.bottom_right()),
});
} else {
cached_print_info.as_mut().unwrap().text_state = self.text.version();
};
cached_print_info
}
pub fn buffer_id(&self) -> BufferId {
self.id
}
pub fn text(&self) -> &Text {
&self.text
}
pub fn text_mut(&mut self) -> TextMut<'_> {
self.text.as_mut()
}
pub fn text_parts(&mut self) -> TextParts<'_> {
self.text.parts()
}
pub fn len_bytes(&self) -> usize {
self.text.len()
}
pub fn len_chars(&self) -> usize {
self.text.end_point().char()
}
pub fn len_lines(&self) -> usize {
self.text.end_point().line()
}
pub fn selections(&self) -> &Selections {
self.text.selections()
}
pub fn selections_mut(&mut self) -> &mut Selections {
self.text.selections_mut()
}
pub fn exists(&self) -> bool {
self.path_set()
.is_some_and(|p| std::fs::exists(PathBuf::from(&p)).is_ok_and(|e| e))
}
pub fn was_reloaded(&self) -> bool {
self.was_reloaded
}
pub(crate) fn take_reload_parts(&mut self) -> (StrsBuf, Selections, History) {
self.text.prepare_for_reloading();
let (strs_buf, selections) = self.text.take_reload_parts();
(
strs_buf,
selections,
std::mem::replace(&mut self.history, History::new(&self.text)),
)
}
pub(crate) fn update(pa: &mut Pass, handle: &Handle<Self>) {
let (buffer, area) = handle.write_with_area(pa);
if let Some(main) = buffer.text().get_main_sel() {
area.scroll_around_points(
buffer.text(),
main.caret().to_two_points_after(),
buffer.print_opts(),
);
}
drop(buffer.reset_print_info_if_needed(area));
hook::trigger(pa, BufferUpdated(handle.clone()));
handle.text_mut(pa).update_bounds();
}
}
impl Widget for Buffer {
fn text(&self) -> &Text {
&self.text
}
fn text_mut(&mut self) -> TextMut<'_> {
let mut text_mut = self.text.as_mut();
text_mut.attach_history(&mut self.history);
text_mut
}
fn print_opts(&self) -> PrintOpts {
self.opts.to_print_opts()
}
}
impl Handle {
pub fn save(&self, pa: &mut Pass) -> Result<bool, Text> {
self.save_quit(pa, false)
}
pub(crate) fn save_quit(&self, pa: &mut Pass, quit: bool) -> Result<bool, Text> {
let buf = self.write(pa);
if let PathKind::SetExists(path) | PathKind::SetAbsent(path) = &buf.path {
let path = path.clone();
if buf.text.has_unsaved_changes() {
crate::notify::set_next_write_as_from_duat(path.clone());
let file = match std::fs::File::create(&path) {
Ok(file) => file,
Err(err) => {
crate::notify::unset_next_write_as_from_duat(path.clone());
return Err(err.into());
}
};
if let Err(err) = buf
.text
.save_on(std::io::BufWriter::new(file))
.inspect(|_| buf.path = PathKind::SetExists(path.clone()))
{
crate::notify::unset_next_write_as_from_duat(path.clone());
return Err(err.into());
}
hook::trigger(pa, BufferSaved((self.clone(), quit)));
Ok(true)
} else {
Ok(false)
}
} else {
Err(txt!("No buffer was set"))
}
}
pub fn save_to(
&self,
pa: &mut Pass,
path: impl AsRef<std::path::Path>,
) -> std::io::Result<bool> {
self.save_quit_to(pa, path, false)
}
pub(crate) fn save_quit_to(
&self,
pa: &mut Pass,
path: impl AsRef<std::path::Path>,
quit: bool,
) -> std::io::Result<bool> {
let buf = self.write(pa);
if buf.text.has_unsaved_changes() {
let path = path.as_ref();
let res = buf
.text
.save_on(std::io::BufWriter::new(fs::File::create(path)?));
buf.history.declare_saved();
if res.as_ref().is_ok() {
hook::trigger(pa, BufferSaved((self.clone(), quit)));
}
res.and(Ok(true))
} else {
Ok(false)
}
}
#[track_caller]
pub fn printed_line_numbers(&self, pa: &Pass) -> Vec<PrintedLine> {
let buffer = self.read(pa);
let cpi = buffer.reset_print_info_if_needed(self.area().read(pa));
cpi.as_ref().unwrap().printed_line_numbers.clone()
}
pub fn full_printed_range(&self, pa: &Pass) -> Range<Point> {
let buffer = self.read(pa);
let cpi = buffer.reset_print_info_if_needed(self.area().read(pa));
cpi.as_ref().unwrap().range.clone()
}
pub fn printed_lines<'b>(&'b self, pa: &'b Pass) -> Vec<&'b Strs> {
let buffer = self.read(pa);
let mut cpi = buffer.reset_print_info_if_needed(self.area().read(pa));
let cpi = cpi.as_mut().unwrap();
let lines = &cpi.printed_line_numbers;
let printed_lines = if let Some(printed_lines) = &cpi.printed_line_ranges {
printed_lines
} else {
let mut last = None;
cpi.printed_line_ranges.insert(
lines
.iter()
.filter(|line| {
last.as_mut()
.is_none_or(|num| std::mem::replace(num, line.number) < line.number)
})
.map(|line| buffer.text.line(line.number).range())
.collect(),
)
};
printed_lines
.iter()
.map(|range| &buffer.text[range.clone()])
.collect()
}
pub fn printed_line_ranges(&self, pa: &Pass) -> Vec<Range<usize>> {
let lines = self.printed_lines(pa);
lines.into_iter().map(|line| line.byte_range()).collect()
}
pub fn visible_lines<'b>(&'b self, _: &'b Pass) -> Vec<&'b Strs> {
todo!();
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, bincode::Decode, bincode::Encode)]
pub enum PathKind {
SetExists(PathBuf),
SetAbsent(PathBuf),
NotSet(usize),
}
impl PathKind {
pub(crate) fn new_unset() -> PathKind {
use std::sync::atomic::{AtomicUsize, Ordering};
static UNSET_COUNT: AtomicUsize = AtomicUsize::new(1);
PathKind::NotSet(UNSET_COUNT.fetch_add(1, Ordering::Relaxed))
}
pub fn as_path(&self) -> Option<PathBuf> {
match self {
PathKind::SetExists(path) | PathKind::SetAbsent(path) => Some(path.clone()),
PathKind::NotSet(_) => None,
}
}
pub fn path(&self) -> PathBuf {
match self {
PathKind::SetExists(path) | PathKind::SetAbsent(path) => path.clone(),
PathKind::NotSet(id) => PathBuf::from(format!("*scratch buffer*#{id}")),
}
}
pub fn path_set(&self) -> Option<PathBuf> {
match self {
PathKind::SetExists(path) | PathKind::SetAbsent(path) => Some(path.clone()),
PathKind::NotSet(_) => None,
}
}
pub fn name(&self) -> String {
match self {
PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
let cur_dir = context::current_dir();
if let Ok(path) = path.strip_prefix(cur_dir) {
path.to_string_lossy().to_string()
} else if let Some(home_dir) = dirs_next::home_dir()
&& let Ok(path) = path.strip_prefix(home_dir)
{
Path::new("~").join(path).to_string_lossy().to_string()
} else {
path.to_string_lossy().to_string()
}
}
PathKind::NotSet(id) => format!("*scratch buffer #{id}*"),
}
}
pub fn name_set(&self) -> Option<String> {
match self {
PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
let cur_dir = context::current_dir();
Some(if let Ok(path) = path.strip_prefix(cur_dir) {
path.to_string_lossy().to_string()
} else if let Some(home_dir) = dirs_next::home_dir()
&& let Ok(path) = path.strip_prefix(home_dir)
{
Path::new("~").join(path).to_string_lossy().to_string()
} else {
path.to_string_lossy().to_string()
})
}
PathKind::NotSet(_) => None,
}
}
pub fn path_txt(&self) -> Text {
match self {
PathKind::SetExists(path) | PathKind::SetAbsent(path) => txt!("[buffer]{path}"),
PathKind::NotSet(id) => txt!("[buffer.new.scratch]*scratch buffer #{id}*"),
}
}
pub fn name_txt(&self) -> Text {
match self {
PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
let cur_dir = context::current_dir();
if let Ok(path) = path.strip_prefix(cur_dir) {
txt!("[buffer]{path}")
} else if let Some(home_dir) = dirs_next::home_dir()
&& let Ok(path) = path.strip_prefix(home_dir)
{
txt!("[buffer]{}", Path::new("~").join(path))
} else {
txt!("[buffer]{path}")
}
}
PathKind::NotSet(id) => txt!("[buffer.new.scratch]*scratch buffer #{id}*"),
}
}
}
impl<P: AsRef<Path>> From<P> for PathKind {
fn from(value: P) -> Self {
let path = value.as_ref();
if let Ok(true) = path.try_exists() {
PathKind::SetExists(path.into())
} else {
PathKind::SetAbsent(path.into())
}
}
}
struct CachedPrintInfo {
range: Range<Point>,
printed_line_numbers: Vec<PrintedLine>,
printed_line_ranges: Option<Vec<Range<Point>>>,
_visible_line_ranges: Option<Vec<Range<Point>>>,
text_state: TextVersion,
area_print_info: PrintInfo,
coords: (Coord, Coord),
}
mod buffer_id {
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BufferId(usize);
impl BufferId {
pub(super) fn new() -> Self {
Self(COUNT.fetch_add(1, Ordering::Relaxed))
}
}
}
pub struct PerBuffer<T: 'static>(LazyLock<RwData<HashMap<BufferId, T>>>);
impl<T: 'static> PerBuffer<T> {
pub const fn new() -> Self {
Self(LazyLock::new(RwData::default))
}
pub fn register<'p>(
&'p self,
pa: &'p mut Pass,
handle: &'p Handle,
new_value: T,
) -> (&'p mut T, &'p mut Buffer) {
let (list, buf) = pa.write_many((&*self.0, handle));
let entry = list.entry(buf.buffer_id()).insert_entry(new_value);
(entry.into_mut(), buf)
}
pub fn unregister(&self, pa: &mut Pass, handle: &Handle) -> Option<T> {
let buf_id = handle.read(pa).buffer_id();
self.0.write(pa).remove(&buf_id)
}
pub fn get<'b>(&'b self, buffer_pass: &'b impl BufferPass) -> Option<&'b T> {
static PASS: Pass = unsafe { Pass::new() };
let list = self.0.read(&PASS);
list.get(&buffer_pass.buffer_id())
}
pub fn get_mut<'b>(&'b self, buffer: &'b mut impl BufferPass) -> Option<&'b mut T> {
static PASS: Pass = unsafe { Pass::new() };
let list = self
.0
.write(unsafe { (&raw const PASS as *mut Pass).as_mut() }.unwrap());
list.get_mut(&buffer.buffer_id())
}
pub fn write<'p>(
&'p self,
pa: &'p mut Pass,
handle: &'p Handle,
) -> Option<(&'p mut T, &'p mut Buffer)> {
let (list, buffer) = pa.write_many((&*self.0, handle));
Some((list.get_mut(&buffer.buffer_id())?, buffer))
}
pub fn write_with<'p, Tup: WriteableTuple<'p, impl std::any::Any>>(
&'p self,
pa: &'p mut Pass,
handle: &'p Handle,
tup: Tup,
) -> Option<(&'p mut T, &'p mut Buffer, Tup::Return)> {
let (list, buffer, ret) = pa.try_write_many((&*self.0, handle, tup))?;
Some((list.get_mut(&buffer.buffer_id())?, buffer, ret))
}
pub fn write_many<'p, const N: usize>(
&'p self,
pa: &'p mut Pass,
handles: [&'p Handle; N],
) -> Option<[(&'p mut T, &'p mut Buffer); N]> {
let (list, buffers) = pa.try_write_many((&*self.0, handles))?;
let buf_ids = buffers.each_ref().map(|buf| buf.buffer_id());
let values = list.get_disjoint_mut(buf_ids.each_ref());
let list = values
.into_iter()
.zip(buffers)
.map(|(value, buf)| value.zip(Some(buf)))
.collect::<Option<Vec<_>>>()?;
list.try_into().ok()
}
pub fn write_many_with<'p, const N: usize, Tup: WriteableTuple<'p, impl std::any::Any>>(
&'p self,
pa: &'p mut Pass,
handles: [&'p Handle; N],
tup: Tup,
) -> Option<([(&'p mut T, &'p mut Buffer); N], Tup::Return)> {
let (list, buffers, ret) = pa.try_write_many((&*self.0, handles, tup))?;
let buf_ids = buffers.each_ref().map(|buf| buf.buffer_id());
let values = list.get_disjoint_mut(buf_ids.each_ref());
let list = values
.into_iter()
.zip(buffers)
.map(|(value, buf)| value.zip(Some(buf)))
.collect::<Option<Vec<_>>>()?;
Some((list.try_into().ok()?, ret))
}
}
impl<T: 'static> Default for PerBuffer<T> {
fn default() -> Self {
Self::new()
}
}
#[doc(hidden)]
pub trait BufferPass: InnerBufferPass {
#[doc(hidden)]
fn buffer_id(&self) -> BufferId;
}
impl BufferPass for Buffer {
fn buffer_id(&self) -> BufferId {
Buffer::buffer_id(self)
}
}
impl<'b> BufferPass for Cursor<'b, Buffer> {
fn buffer_id(&self) -> BufferId {
Cursor::buffer_id(self)
}
}
trait InnerBufferPass {}
impl InnerBufferPass for Buffer {}
impl<'b> InnerBufferPass for Cursor<'b, Buffer> {}