Compare commits

..

No commits in common. "4b5579c7269077c7098027caa9980243b3f0b257" and "18751bb6c33d7ec60c62388db15a709aa71c266d" have entirely different histories.

8 changed files with 73 additions and 201 deletions

View file

@ -25,4 +25,4 @@
## Issues to fix ## Issues to fix
- Undo history changes should not join so easily - New file creation should work with non-existent directories

View file

@ -198,7 +198,7 @@ impl Highlighter {
pub fn glsl(self) -> Self { pub fn glsl(self) -> Self {
self self
// Keywords // Keywords
.with(TokenKind::Keyword, r"\b[(struct)(if)(while)(for)(else)(break)(continue)(const)(return)(layout)(uniform)(set)(binding)(location)(inout)(in)(case)(switch)(default)]\b") .with(TokenKind::Keyword, r"\b[(struct)(if)(while)(for)(else)(break)(continue)(const)(return)(layout)(uniform)(set)(binding)(location)(in)]\b")
// Primitives // Primitives
.with(TokenKind::Type, r"\b[(u?int)(float)(double)(bool)(void)([ui]?vec[1-4]*)([ui]?mat[1-4]*)(texture[(2D)(3D)]?(Cube)?)([ui]?sampler[(2D)(3D)]?(Shadow)?)]\b") .with(TokenKind::Type, r"\b[(u?int)(float)(double)(bool)(void)([ui]?vec[1-4]*)([ui]?mat[1-4]*)(texture[(2D)(3D)]?(Cube)?)([ui]?sampler[(2D)(3D)]?(Shadow)?)]\b")
// Builtins // Builtins

View file

@ -223,11 +223,10 @@ impl Buffer {
} }
pub fn save(&mut self) -> Result<(), Error> { pub fn save(&mut self) -> Result<(), Error> {
let path = self.path.as_ref().expect("buffer must have path to save"); std::fs::write(
if let Some(parent) = path.parent() { self.path.as_ref().expect("buffer must have path to save"),
std::fs::create_dir_all(parent)?; self.text.to_string(),
} )?;
std::fs::write(path, self.text.to_string())?;
self.diverged = false; self.diverged = false;
self.opened_at = Some(SystemTime::now()); self.opened_at = Some(SystemTime::now());
self.unsaved = false; self.unsaved = false;

View file

@ -60,8 +60,8 @@ impl Area {
} }
pub struct Rect<'a> { pub struct Rect<'a> {
pub fg: Color, fg: Color,
pub bg: Color, bg: Color,
area: Area, area: Area,
fb: &'a mut Framebuffer, fb: &'a mut Framebuffer,
has_focus: bool, has_focus: bool,
@ -177,11 +177,6 @@ impl<'a> Rect<'a> {
} }
} }
/// `with_bg`, but only if background color is not already set.
pub fn with_bg_preference(&mut self, bg: Color) -> Rect<'_> {
self.with_bg(if self.bg == Color::Reset { bg } else { self.bg })
}
pub fn with_focus(&mut self, focus: bool) -> Rect<'_> { pub fn with_focus(&mut self, focus: bool) -> Rect<'_> {
Rect { Rect {
fg: self.fg, fg: self.fg,

View file

@ -10,13 +10,11 @@ enum Mode {
Doc, Doc,
Prompt, Prompt,
Filter, Filter,
SearchResult,
} }
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Input { pub struct Input {
pub mode: Mode, pub mode: Mode,
line_offset: usize,
// x/y location in the buffer that the pane is trying to focus on // x/y location in the buffer that the pane is trying to focus on
pub focus: [isize; 2], pub focus: [isize; 2],
// Remember the last area for things like scrolling // Remember the last area for things like scrolling
@ -40,14 +38,6 @@ impl Input {
} }
} }
pub fn search_result(line_offset: usize) -> Self {
Self {
mode: Mode::SearchResult,
line_offset,
..Self::default()
}
}
pub fn focus(&mut self, coord: [isize; 2]) { pub fn focus(&mut self, coord: [isize; 2]) {
for i in 0..2 { for i in 0..2 {
self.focus[i] = self.focus[i] self.focus[i] = self.focus[i]
@ -128,11 +118,7 @@ impl Input {
Ok(Resp::handled(None)) Ok(Resp::handled(None))
} }
Some(Action::GotoLine(line)) => { Some(Action::GotoLine(line)) => {
buffer.goto_cursor( buffer.goto_cursor(cursor_id, [0, line], true);
cursor_id,
[0, (line - self.line_offset as isize).max(0)],
true,
);
self.refocus(buffer, cursor_id); self.refocus(buffer, cursor_id);
Ok(Resp::handled(None)) Ok(Resp::handled(None))
} }
@ -251,30 +237,20 @@ impl Input {
outer_frame: &mut Rect, outer_frame: &mut Rect,
) { ) {
// Add frame // Add frame
let mut frame = if matches!(self.mode, Mode::SearchResult) { let mut frame = outer_frame.with_border(
outer_frame.rect([0; 2], [!0; 2]) if outer_frame.has_focus() {
} else { &state.theme.focus_border
outer_frame.with_border( } else {
if outer_frame.has_focus() { &state.theme.border
&state.theme.focus_border },
} else { title.as_deref(),
&state.theme.border );
},
title.as_deref(),
)
};
let (line_num_w, margin_w) = match self.mode { let line_num_w = buffer.text.lines().count().max(1).ilog10() as usize + 1;
Mode::Prompt => (2, 0), let margin_w = match self.mode {
Mode::Filter => (0, 0), Mode::Prompt => 2,
Mode::Doc => { Mode::Filter => 0,
let line_num_w = (self.line_offset + buffer.text.lines().count()) Mode::Doc => line_num_w + 2,
.max(1)
.ilog10() as usize
+ 1;
(line_num_w, line_num_w + 2)
}
Mode::SearchResult => (4, 6),
}; };
self.last_area = frame.rect([margin_w, 0], [!0, !0]).area(); self.last_area = frame.rect([margin_w, 0], [!0, !0]).area();
@ -303,19 +279,16 @@ impl Input {
Mode::Filter => frame.rect([0, 0], frame.size()), Mode::Filter => frame.rect([0, 0], frame.size()),
Mode::Prompt => frame Mode::Prompt => frame
.rect([0, i], [1, 1]) .rect([0, i], [1, 1])
.with_bg_preference(state.theme.margin_bg) .with_bg(state.theme.margin_bg)
.with_fg(state.theme.margin_line_num) .with_fg(state.theme.margin_line_num)
.fill(' ') .fill(' ')
.text([0, 0], ">"), .text([0, 0], ">"),
Mode::Doc | Mode::SearchResult => frame Mode::Doc => frame
.rect([0, i], [margin_w, 1]) .rect([0, i], [margin_w, 1])
.with_bg_preference(state.theme.margin_bg) .with_bg(state.theme.margin_bg)
.with_fg(state.theme.margin_line_num) .with_fg(state.theme.margin_line_num)
.fill(' ') .fill(' ')
.text( .text([1, 0], &format!("{:>line_num_w$}", line_num + 1)),
[1, 0],
&format!("{:>line_num_w$}", self.line_offset + line_num + 1),
),
}; };
let line_highlight_selected = matches!(self.mode, Mode::Doc) let line_highlight_selected = matches!(self.mode, Mode::Doc)
@ -364,7 +337,7 @@ impl Input {
} else if line_highlight_selected && frame.has_focus() { } else if line_highlight_selected && frame.has_focus() {
state.theme.line_select_bg state.theme.line_select_bg
} else { } else {
frame.bg Color::Reset
} }
} }
}; };

View file

@ -184,7 +184,7 @@ impl<T> Options<T> {
} }
} }
impl<T> Element<T> for Options<T> { impl<T: Clone> Element<T> for Options<T> {
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<T>, Event> { fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<T>, Event> {
match event.to_action(|e| e.to_go().or_else(|| e.to_move())) { match event.to_action(|e| e.to_go().or_else(|| e.to_move())) {
Some(Action::Move( Some(Action::Move(
@ -216,7 +216,7 @@ impl<T> Element<T> for Options<T> {
Some(Action::Go) => { Some(Action::Go) => {
if self.selected < self.ranking.len() { if self.selected < self.ranking.len() {
Ok(Resp::end_with( Ok(Resp::end_with(
self.options.remove(self.ranking[self.selected]), self.options[self.ranking[self.selected]].clone(),
None, None,
)) ))
} else { } else {

View file

@ -244,24 +244,16 @@ impl Element<()> for Switcher {
) )
.map(Resp::into_can_end); .map(Resp::into_can_end);
// Score entries // Score entries
let filter = self.buffer.text.to_string().to_lowercase(); let filter = self.buffer.text.to_string();
self.options.apply_scoring(|b| { self.options.apply_scoring(|b| {
let Some(buffer) = state.buffers.get(*b) else { let Some(buffer) = state.buffers.get(*b) else {
return None; return None;
}; };
let name = buffer.name().as_deref().unwrap_or("").to_lowercase(); let name = buffer.name()?;
let parent = buffer
.path
.as_ref()
.and_then(|p| Some(p.parent()?.to_str()?.to_lowercase()));
if name.starts_with(&filter) { if name.starts_with(&filter) {
Some(1) Some(1)
} else if name.contains(&filter) { } else if name.contains(&filter) {
Some(2) Some(2)
} else if let Some(parent) = parent
&& parent.contains(&filter)
{
Some(3)
} else { } else {
None None
} }
@ -292,18 +284,7 @@ impl Visual for BufferId {
let Some(buffer) = state.buffers.get(*self) else { let Some(buffer) = state.buffers.get(*self) else {
return; return;
}; };
frame frame.text([0, 0], buffer.name().as_deref().unwrap_or("<unknown>"));
.with_fg(state.theme.option_file)
.text([0, 0], buffer.name().as_deref().unwrap_or("<unknown>"));
let path_x = (frame.size()[0] as isize / 3).max(32);
frame.with_fg(state.theme.option_dir).text(
[path_x, 0],
buffer
.path
.as_ref()
.and_then(|p| p.parent()?.to_str())
.unwrap_or("<unknown>"),
);
} }
} }
@ -407,18 +388,7 @@ impl Opener {
} }
}) })
} }
Err(err) => self.options.set_options( Err(err) => self.options.set_options::<_, ()>(Vec::new(), |_| None),
if filter != "" {
vec![FileOption {
path: [dir, &file_name].into_iter().collect(),
kind: FileKind::New,
is_link: false,
}]
} else {
Vec::new()
},
|_| Some(()),
),
} }
} }
} }

View file

@ -1,42 +1,35 @@
use super::*; use super::*;
use crate::state::{Buffer, CursorId}; use crate::state::{Buffer, CursorId};
use std::{fs, path::Path}; use std::fs;
pub struct Searcher { pub struct Searcher {
options: Options<SearchResult>, options: Options<SearchResult>,
path: PathBuf, path: PathBuf,
search_path: PathBuf,
needle: String, needle: String,
// Filter // Filter
buffer: Buffer, buffer: Buffer,
cursor_id: CursorId, cursor_id: CursorId,
input: Input, input: Input,
preview: Option<(Buffer, CursorId, Input, SearchLoc)>, preview: Option<(Buffer, CursorId, Input, SearchResult)>,
} }
impl Searcher { impl Searcher {
pub fn new(path: PathBuf, needle: String) -> Self { pub fn new(mut path: PathBuf, needle: String) -> Self {
let mut search_path = path.clone(); let path = loop {
let search_path = loop { if let Ok(mut entries) = fs::read_dir(&path)
if let Ok(mut entries) = fs::read_dir(&search_path)
&& entries.any(|e| { && entries.any(|e| {
e.map_or(false, |e| { e.map_or(false, |e| {
e.file_name() == ".git" && e.file_type().map_or(false, |t| t.is_dir()) e.file_name() == ".git" && e.file_type().map_or(false, |t| t.is_dir())
}) })
}) })
{ {
break search_path; break path;
} else if !search_path.pop() { } else if !path.pop() {
break std::env::current_dir().expect("No cwd"); break std::env::current_dir().expect("No cwd");
} }
}; };
fn search_in( fn search_in(path: &PathBuf, needle: &str, results: &mut Vec<SearchResult>) {
search_path: &Path,
path: &Path,
needle: &str,
results: &mut Vec<SearchResult>,
) {
// Cap reached! // Cap reached!
if results.len() < 500 { if results.len() < 500 {
// Skip hidden files // Skip hidden files
@ -58,31 +51,16 @@ impl Searcher {
for (line_idx, line_text) in for (line_idx, line_text) in
s.lines().enumerate().filter(|(_, l)| l.contains(needle)) s.lines().enumerate().filter(|(_, l)| l.contains(needle))
{ {
let mut line_buffer = Buffer::new(
false,
line_text.trim().chars().collect(),
path.to_path_buf(),
);
results.push(SearchResult { results.push(SearchResult {
loc: SearchLoc { path: path.clone(),
path: path.to_path_buf(), line_idx,
line_idx, line_text: line_text.trim().to_string(),
},
rdir: format!(
"./{}",
path.parent()
.and_then(|p| p.strip_prefix(search_path).ok()?.to_str())
.unwrap_or("unknown")
),
line_input: Input::search_result(line_idx),
line_cursor: line_buffer.start_session(),
line_buffer,
}); });
} }
} else if let Ok(entries) = fs::read_dir(path) { } else if let Ok(entries) = fs::read_dir(path) {
// Special case, ignore Rust target dir to prevent searching too many places // Special case, ignore Rust target dir to prevent searching too many places
{ {
let mut path = path.to_path_buf(); let mut path = path.clone();
path.push("CACHEDIR.TAG"); path.push("CACHEDIR.TAG");
if path.exists() { if path.exists() {
return; return;
@ -91,30 +69,27 @@ impl Searcher {
for entry in entries { for entry in entries {
let Ok(entry) = entry else { continue }; let Ok(entry) = entry else { continue };
search_in(search_path, &entry.path(), needle, results); search_in(&entry.path(), needle, results);
} }
} }
} }
} }
let mut results = Vec::new(); let mut results = Vec::new();
search_in(&search_path, &search_path, &needle, &mut results); search_in(&path, &needle, &mut results);
let mut buffer = Buffer::default(); let mut buffer = Buffer::default();
let cursor_id = buffer.start_session(); let cursor_id = buffer.start_session();
let mut this = Self { Self {
options: Options::new(results), options: Options::new(results),
path, path,
search_path,
needle, needle,
cursor_id, cursor_id,
buffer, buffer,
input: Input::filter(), input: Input::filter(),
preview: None, preview: None,
}; }
this.update_completions();
this
} }
pub fn requested_height(&self) -> usize { pub fn requested_height(&self) -> usize {
@ -125,34 +100,13 @@ impl Searcher {
fn update_completions(&mut self) { fn update_completions(&mut self) {
let filter = self.buffer.text.to_string().to_lowercase(); let filter = self.buffer.text.to_string().to_lowercase();
self.options.apply_scoring(|e| { self.options.apply_scoring(|e| {
let name = e let name = format!("{}", e.path.display()).to_lowercase();
.loc
.path
.file_name()
.and_then(|f| f.to_str())
.unwrap_or("<unknown>")
.to_lowercase();
let parent = e
.loc
.path
.parent()
.and_then(|f| Some(f.to_str()?.to_lowercase()));
let unshared_components = e
.loc
.path
.ancestors()
.map(|a| if self.path.starts_with(a) { -1 } else { 1 })
.sum::<i32>();
if name == filter { if name == filter {
Some((0, unshared_components)) Some((0, name.chars().count()))
} else if name.starts_with(&filter) { } else if name.starts_with(&filter) {
Some((1, unshared_components)) Some((1, name.chars().count()))
} else if name.contains(&filter) { } else if name.contains(&filter) {
Some((2, unshared_components)) Some((2, name.chars().count()))
} else if let Some(parent) = parent
&& parent.contains(&filter)
{
Some((3, unshared_components))
} else { } else {
None None
} }
@ -169,8 +123,8 @@ impl Element<()> for Searcher {
_ => match self.options.handle(state, event).map(Resp::into_ended) { _ => match self.options.handle(state, event).map(Resp::into_ended) {
// Selecting a directory enters the directory // Selecting a directory enters the directory
Ok(Some(result)) => Ok(Resp::end(Some(Event::Action(Action::OpenFile( Ok(Some(result)) => Ok(Resp::end(Some(Event::Action(Action::OpenFile(
result.loc.path, result.path,
result.loc.line_idx, result.line_idx,
))))), ))))),
Ok(None) => Ok(Resp::handled(None)), Ok(None) => Ok(Resp::handled(None)),
Err(event) => { Err(event) => {
@ -208,46 +162,27 @@ impl Element<()> for Searcher {
} }
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
struct SearchLoc { pub struct SearchResult {
path: PathBuf, pub path: PathBuf,
line_idx: usize, pub line_idx: usize,
} pub line_text: String,
struct SearchResult {
loc: SearchLoc,
rdir: String,
line_input: Input,
line_cursor: CursorId,
line_buffer: Buffer,
} }
impl Visual for SearchResult { impl Visual for SearchResult {
fn render(&mut self, state: &State, frame: &mut Rect) { fn render(&mut self, state: &State, frame: &mut Rect) {
let name = match self.loc.path.file_name().and_then(|n| n.to_str()) { let name = match self.path.file_name().and_then(|n| n.to_str()) {
Some(name) => format!("{name}"), Some(name) => format!("{name}"),
None => format!("Unknown"), None => format!("Unknown"),
}; };
let col_a = (frame.size()[0] / 5).max(20);
let col_b = frame.size()[0] / 3;
// Filename
frame frame
.rect([0, 0], [col_a, !0])
.with_fg(state.theme.option_file) .with_fg(state.theme.option_file)
.text([0, 0], &name); .text([0, 0], &format!("{name}:{}", self.line_idx + 1));
// Path frame.with_fg(state.theme.margin_line_num).with(|f| {
frame f.text(
.rect([col_a, 0], [col_b, !0]) [f.size()[0] as isize / 3, 0],
.with_fg(state.theme.option_dir) &format!("{}", self.line_text),
.text([0, 0], &self.rdir); );
// Code snippet });
self.line_input.render(
state,
None,
&self.line_buffer,
self.line_cursor,
None,
&mut frame.rect([col_a + col_b, 0], [!0, !0]),
);
} }
} }
@ -265,18 +200,18 @@ impl Visual for Searcher {
self.preview = self.options.selected().and_then(|result| { self.preview = self.options.selected().and_then(|result| {
self.preview self.preview
.take() .take()
.filter(|(_, _, _, loc)| loc == &result.loc) .filter(|(_, _, _, r)| r == result)
.or_else(|| { .or_else(|| {
let mut buffer = Buffer::open(result.loc.path.clone()).ok()?; let mut buffer = Buffer::open(result.path.clone()).ok()?;
let cursor_id = buffer.start_session(); let cursor_id = buffer.start_session();
let mut input = Input::default(); let mut input = Input::default();
buffer.goto_cursor(cursor_id, [0, result.loc.line_idx as isize], true); buffer.goto_cursor(cursor_id, [0, result.line_idx as isize], true);
input.focus([0, result.loc.line_idx as isize - preview_sz as isize / 2]); input.focus([0, result.line_idx as isize - preview_sz as isize / 2]);
Some((buffer, cursor_id, input, result.loc.clone())) Some((buffer, cursor_id, input, result.clone()))
}) })
}); });
if let Some((buffer, cursor_id, input, _)) = &mut self.preview { if let Some((buffer, cursor_id, input, result)) = &mut self.preview {
frame.rect([0, 0], [frame.size()[0], preview_sz]).with(|f| { frame.rect([0, 0], [frame.size()[0], preview_sz]).with(|f| {
input.render(state, buffer.name().as_deref(), buffer, *cursor_id, None, f) input.render(state, buffer.name().as_deref(), buffer, *cursor_id, None, f)
}); });