use vek::*;
use crate::{
draw::*,
BufferId,
BufferHandle,
Line,
Event,
Dir,
buffer::{highlight::Highlights, shared::SharedBuffer},
};
use super::{
Context,
Element,
};
const MARGIN_WIDTH: usize = 5;
const CURSOR_SPACE: Vec2<usize> = Vec2 { x: 4, y: 4 };
const PAGE_HEIGHT: usize = 24;
pub struct Editor {
loc: Vec2<usize>,
buffer: BufferHandle,
page_height: usize,
}
impl From<BufferHandle> for Editor {
fn from(buffer: BufferHandle) -> Self {
Self {
loc: Vec2::zero(),
buffer,
page_height: PAGE_HEIGHT,
}
}
}
impl Editor {
pub fn empty(ctx: &mut Context) -> Self {
let buffer = ctx.state.new_empty_buffer();
Self::from(ctx.state.new_handle(buffer).unwrap())
}
pub fn recent(ctx: &mut Context) -> Self {
let recent_buf = ctx.state
.recent_buffers()
.next()
.cloned();
recent_buf
.map(|b| Self::from(ctx.state.duplicate_handle(&b).unwrap()))
.unwrap_or_else(|| Self::empty(ctx))
}
pub fn buffer(&self) -> &BufferHandle {
&self.buffer
}
}
impl Element for Editor {
type Response = Result<(), Event>;
fn handle(&mut self, ctx: &mut Context, event: Event) -> Self::Response {
let mut buf = ctx.state
.get_buffer_mut(&self.buffer)
.unwrap();
match event {
Event::CloseBuffer => {
let buf = ctx.state
.recent_buffers()
.find(|buf| buf.buffer_id != self.buffer.buffer_id)
.cloned();
if let Some(buf) = buf {
let old_buffer = self.buffer.buffer_id;
self.buffer = buf;
ctx.state.close_buffer(old_buffer);
}
},
Event::NewBuffer => {
let buf = ctx.state.insert_buffer(SharedBuffer::default());
self.buffer = ctx.state.new_handle(buf).unwrap();
},
Event::SaveBuffer => buf.try_save().unwrap(),
Event::SwitchBuffer(buffer) => self.buffer = buffer,
Event::PageMove(dir, reach) => buf.do_cursor_movement(dir, reach, |b| { b.cursor_move(dir, self.page_height); }),
Event::Undo => buf.undo(),
Event::Redo => buf.redo(),
Event::NewFile(path) | Event::OpenFile(path) => match ctx
.state
.open_or_create_file(path, self.buffer.clone())
{
Ok(buf) => self.buffer = buf,
Err(err) => log::warn!("When opening file: {:?}", err),
},
event => buf.handle(event)?,
}
Ok(())
}
fn update(&mut self, ctx: &mut Context, canvas: &mut impl Canvas, active: bool) {
let canvas = canvas.window(Rect::new(1, 1, canvas.size().w.saturating_sub(2), canvas.size().h.saturating_sub(2)));
self.page_height = canvas.size().h;
if active {
ctx.state.set_recent_buffer(self.buffer.clone());
ctx.active_buffer = self.buffer.buffer_id;
}
let buf = ctx.state
.get_buffer_mut(&self.buffer)
.unwrap();
let cursor_loc = buf.pos_loc(buf.cursor().pos);
self.loc.x = self.loc.x
.min(cursor_loc.x.saturating_sub(CURSOR_SPACE.x))
.max(cursor_loc.x.saturating_sub(canvas.size().w.saturating_sub(MARGIN_WIDTH + CURSOR_SPACE.x)));
self.loc.y = self.loc.y
.min(cursor_loc.y.saturating_sub(CURSOR_SPACE.y))
.max(cursor_loc.y.saturating_sub(canvas.size().h.saturating_sub(CURSOR_SPACE.y)));
}
fn render(&self, ctx: &mut Context, canvas: &mut impl Canvas, active: bool) {
let sz = canvas.size();
let buf = ctx.state
.get_buffer_mut(&self.buffer)
.unwrap();
canvas
.with_fg(Color::Rgb(if active {
Rgb::broadcast(255)
} else {
Rgb::broadcast(100)
}))
.frame();
let title = format!("[ {}{} ]", if buf.is_unsaved() { "*" } else { "" }, buf.title());
canvas.write_str(Vec2::new((sz.w.saturating_sub(title.len())) / 2, 0), &title);
let mut canvas = canvas.window(Rect::new(1, 1, canvas.size().w.saturating_sub(2), canvas.size().h.saturating_sub(2)));
let highlights = Highlights::from_file(buf.path(), &buf.get_string());
let cursor_loc = buf.pos_loc(buf.cursor().pos);
for row in 0..canvas.size().h {
let buf_row = row + self.loc.y;
let buf_row_pos = buf.loc_pos(Vec2::new(0, buf_row));
let (line, margin) = match buf.line(buf_row) {
Some(line) => (line, format!("{:>4} ", buf_row + 1)),
None => (Line::empty(), " ".to_string()),
};
for (col, c) in margin
.chars()
.enumerate()
{
let line_selected = buf.cursor().encloses(buf_row_pos) && row < buf.content().lines().len();
canvas
.with_fg(if line_selected {
Color::Rgb(Rgb::broadcast(255))
} else {
ctx.theme.line_number_color
})
.with_bg(if line_selected {
ctx.theme.selection_color
} else {
ctx.theme.margin_color
})
.write_char(Vec2::new(col, row), c);
}
for (col, (line_pos, c)) in line
.glyphs(&buf.config())
.skip(self.loc.x)
.enumerate()
.take(canvas.size().w.saturating_sub(MARGIN_WIDTH))
{
let buf_col = col + self.loc.x;
let buf_pos = buf_row_pos + line_pos.unwrap_or(line.len().saturating_sub(1));
let bg_color = if buf.cursor().inside_reach(buf_pos) {
ctx.theme.selection_color
} else if buf_row == cursor_loc.y {
ctx.theme.subtle_bg_color
} else {
Color::Reset
};
canvas
.with_fg(ctx.theme.get_highlight_color(highlights.get_at(buf_pos)))
.with_bg(bg_color)
.write_char(Vec2::new(MARGIN_WIDTH + col, row), c);
}
}
if active {
let cursor_screen_loc = cursor_loc.map2(self.loc, |e, loc| e.saturating_sub(loc)) + Vec2::unit_x() * MARGIN_WIDTH;
canvas.set_cursor(Some(cursor_screen_loc).filter(|loc| loc.x < canvas.size().w && loc.y < canvas.size().h));
}
let pad_h = (sz.h.saturating_sub(2).pow(2) / buf.content().lines().len().max(1)).max(1);
if pad_h < sz.h.saturating_sub(2) {
canvas
.with_fg(ctx.theme.scrollbar_color)
.with_bg(ctx.theme.margin_color)
.rectangle((sz.w - 2, 0), (1, sz.h - 2), '│');
canvas
.with_fg(ctx.theme.scrollbar_color)
.with_bg(ctx.theme.scrollpad_color)
.rectangle((sz.w - 2, ((self.loc.y * sz.h.saturating_sub(1)) as f32 / buf.content().lines().len().max(1) as f32) as usize), (1, pad_h), '.');
}
}
}