Added file saving
This commit is contained in:
parent
e9e938ca4a
commit
bc2cff34d4
6 changed files with 80 additions and 16 deletions
|
|
@ -34,6 +34,7 @@ pub enum Action {
|
|||
CommandStart(&'static str), // Start a new command
|
||||
GotoLine(isize), // Go to the specified file line
|
||||
SelectToken, // Fully select the token under the cursor
|
||||
Save, // Save the current buffer
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -349,4 +350,20 @@ impl RawEvent {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_save(&self) -> Option<Action> {
|
||||
if matches!(
|
||||
&self.0,
|
||||
TerminalEvent::Key(KeyEvent {
|
||||
code: KeyCode::Char('s'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
})
|
||||
) {
|
||||
Some(Action::Save)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
src/state.rs
42
src/state.rs
|
|
@ -128,6 +128,7 @@ impl Text {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Buffer {
|
||||
pub unsaved: bool,
|
||||
pub text: Text,
|
||||
pub highlights: Option<Highlights>,
|
||||
pub cursors: HopSlotMap<CursorId, Cursor>,
|
||||
|
|
@ -137,11 +138,11 @@ pub struct Buffer {
|
|||
|
||||
impl Buffer {
|
||||
pub fn from_file(path: PathBuf) -> Result<Self, Error> {
|
||||
let (dir, chars, s) = match std::fs::read_to_string(&path) {
|
||||
let (unsaved, dir, chars, s) = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => {
|
||||
let mut path = path.canonicalize()?;
|
||||
path.pop();
|
||||
(Some(path), s.chars().collect(), s)
|
||||
(false, Some(path), s.chars().collect(), s)
|
||||
}
|
||||
// If the file doesn't exist, create a new file
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
|
|
@ -150,11 +151,12 @@ impl Buffer {
|
|||
.filter(|p| p.to_str() != Some(""))
|
||||
.map(Path::to_owned)
|
||||
.or_else(|| std::env::current_dir().ok());
|
||||
(dir, Vec::new(), String::new())
|
||||
(true, dir, Vec::new(), String::new())
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
Ok(Self {
|
||||
unsaved,
|
||||
highlights: Highlighter::from_file_name(&path).map(|h| h.highlight(&chars)),
|
||||
text: Text { chars },
|
||||
cursors: HopSlotMap::default(),
|
||||
|
|
@ -163,11 +165,22 @@ impl Buffer {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
pub fn save(&mut self) -> Result<(), Error> {
|
||||
if self.unsaved {
|
||||
std::fs::write(
|
||||
self.path.as_ref().expect("buffer must have path to save"),
|
||||
self.text.to_string(),
|
||||
)?;
|
||||
self.unsaved = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<String> {
|
||||
Some(
|
||||
match self.path.as_ref()?.file_name().and_then(|n| n.to_str()) {
|
||||
Some(name) => name,
|
||||
None => "<error>",
|
||||
Some(name) => format!("{}{name}", if self.unsaved { "* " } else { "" }),
|
||||
None => "<error>".to_string(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -180,6 +193,8 @@ impl Buffer {
|
|||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.unsaved = true;
|
||||
|
||||
self.text.chars.clear();
|
||||
self.update_highlights();
|
||||
// Reset cursors
|
||||
|
|
@ -299,6 +314,8 @@ impl Buffer {
|
|||
}
|
||||
|
||||
pub fn insert(&mut self, pos: usize, chars: impl IntoIterator<Item = char>) {
|
||||
self.unsaved = true;
|
||||
|
||||
let mut n = 0;
|
||||
for c in chars {
|
||||
self.text
|
||||
|
|
@ -332,6 +349,8 @@ impl Buffer {
|
|||
|
||||
// Assumes range is well-formed
|
||||
pub fn remove(&mut self, range: Range<usize>) {
|
||||
self.unsaved = true;
|
||||
|
||||
// TODO: Bell if false?
|
||||
self.text.chars.drain(range.clone());
|
||||
self.update_highlights();
|
||||
|
|
@ -409,6 +428,17 @@ impl TryFrom<Args> for State {
|
|||
}
|
||||
|
||||
impl State {
|
||||
pub fn open_or_get(&mut self, path: PathBuf) -> Result<BufferId, Error> {
|
||||
let true_path = path.canonicalize()?;
|
||||
if let Some((buffer_id, _)) = self.buffers.iter().find(|(_, b)| {
|
||||
b.path.as_ref().and_then(|p| p.canonicalize().ok()).as_ref() == Some(&true_path)
|
||||
}) {
|
||||
Ok(buffer_id)
|
||||
} else {
|
||||
Ok(self.buffers.insert(Buffer::from_file(path)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
self.tick += 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ pub struct Theme {
|
|||
pub ui_bg: Color,
|
||||
pub select_bg: Color,
|
||||
pub unfocus_select_bg: Color,
|
||||
pub search_result_bg: Color,
|
||||
pub margin_bg: Color,
|
||||
pub margin_line_num: Color,
|
||||
pub border: BorderTheme,
|
||||
|
|
@ -69,6 +70,7 @@ impl Default for Theme {
|
|||
ui_bg: Color::AnsiValue(235),
|
||||
select_bg: Color::AnsiValue(23),
|
||||
unfocus_select_bg: Color::AnsiValue(240),
|
||||
search_result_bg: Color::AnsiValue(124),
|
||||
margin_bg: Color::Reset,
|
||||
margin_line_num: Color::AnsiValue(245),
|
||||
border: BorderTheme::default(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
use crate::{
|
||||
state::{Buffer, BufferId, CursorId},
|
||||
state::{Buffer, BufferId, Cursor, CursorId},
|
||||
terminal::CursorStyle,
|
||||
};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
|
@ -72,20 +72,20 @@ impl Element for Doc {
|
|||
.or_else(|| e.to_open_opener(open_path))
|
||||
.or_else(|| e.to_open_finder())
|
||||
.or_else(|| e.to_move())
|
||||
.or_else(|| e.to_save())
|
||||
}) {
|
||||
action @ Some(Action::OpenSwitcher) => Ok(Resp::handled(action.map(Into::into))),
|
||||
action @ Some(Action::OpenOpener(_)) => Ok(Resp::handled(action.map(Into::into))),
|
||||
action @ Some(Action::OpenFinder) => {
|
||||
self.search = Some(Search::new());
|
||||
self.search = Some(Search::new(buffer.cursors[cursor_id]));
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
Some(Action::SwitchBuffer(new_buffer)) => {
|
||||
self.switch_buffer(state, new_buffer);
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
Some(Action::OpenFile(path)) => match Buffer::from_file(path) {
|
||||
Ok(buffer) => {
|
||||
let buffer_id = state.buffers.insert(buffer);
|
||||
Some(Action::OpenFile(path)) => match state.open_or_get(path) {
|
||||
Ok(buffer_id) => {
|
||||
self.switch_buffer(state, buffer_id);
|
||||
Ok(Resp::handled(None))
|
||||
}
|
||||
|
|
@ -93,6 +93,12 @@ impl Element for Doc {
|
|||
Action::Show(Some(format!("Could not open file")), format!("{err}")).into(),
|
||||
))),
|
||||
},
|
||||
Some(Action::Save) => {
|
||||
let event = buffer.save().err().map(|err| {
|
||||
Action::Show(Some("Could not save file".to_string()), err.to_string()).into()
|
||||
});
|
||||
Ok(Resp::handled(event))
|
||||
}
|
||||
_ => {
|
||||
let Some(buffer) = state.buffers.get_mut(self.buffer) else {
|
||||
return Err(event);
|
||||
|
|
@ -122,7 +128,7 @@ impl Visual for Doc {
|
|||
.with(|f| {
|
||||
self.input.render(
|
||||
state,
|
||||
buffer.name(),
|
||||
buffer.name().as_deref(),
|
||||
buffer,
|
||||
cursor_id,
|
||||
self.search.as_ref(),
|
||||
|
|
@ -144,6 +150,8 @@ impl Visual for Doc {
|
|||
}
|
||||
|
||||
pub struct Search {
|
||||
old_cursor: Cursor,
|
||||
|
||||
buffer: Buffer,
|
||||
cursor_id: CursorId,
|
||||
input: Input,
|
||||
|
|
@ -154,9 +162,11 @@ pub struct Search {
|
|||
}
|
||||
|
||||
impl Search {
|
||||
fn new() -> Self {
|
||||
fn new(old_cursor: Cursor) -> Self {
|
||||
let mut buffer = Buffer::default();
|
||||
Self {
|
||||
old_cursor,
|
||||
|
||||
cursor_id: buffer.start_session(),
|
||||
buffer,
|
||||
input: Input::filter(),
|
||||
|
|
@ -192,7 +202,12 @@ impl Search {
|
|||
let res = match event
|
||||
.to_action(|e| e.to_cancel().or_else(|| e.to_go()).or_else(|| e.to_move()))
|
||||
{
|
||||
Some(Action::Cancel | Action::Go) => return Ok(Resp::end(None)),
|
||||
Some(Action::Cancel) => {
|
||||
buffer.cursors[cursor_id] = self.old_cursor;
|
||||
input.refocus(buffer, cursor_id);
|
||||
return Ok(Resp::end(None));
|
||||
}
|
||||
Some(Action::Go) => return Ok(Resp::end(None)),
|
||||
Some(Action::Move(dir, false, _)) => {
|
||||
match dir {
|
||||
Dir::Up => {
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ impl Input {
|
|||
let bg = if let Some(s) = search {
|
||||
match s.contains(pos) {
|
||||
Some(true) => state.theme.select_bg,
|
||||
Some(false) => state.theme.unfocus_select_bg,
|
||||
Some(false) => state.theme.search_result_bg,
|
||||
None => Color::Reset,
|
||||
}
|
||||
} else if !selected {
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ impl Visual for BufferId {
|
|||
let Some(buffer) = state.buffers.get(*self) else {
|
||||
return;
|
||||
};
|
||||
frame.text([0, 0], buffer.name().unwrap_or("<unknown>"));
|
||||
frame.text([0, 0], buffer.name().as_deref().unwrap_or("<unknown>"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue