Added previews to opener and searcher
This commit is contained in:
parent
3e8dcdfb11
commit
0337006986
7 changed files with 141 additions and 30 deletions
|
|
@ -21,7 +21,7 @@ impl LangPack {
|
||||||
highlighter: Highlighter::default().markdown().git(),
|
highlighter: Highlighter::default().markdown().git(),
|
||||||
comment_syntax: None,
|
comment_syntax: None,
|
||||||
},
|
},
|
||||||
(_, "toml") => Self {
|
("Cargo.lock", _) | (_, "toml") => Self {
|
||||||
highlighter: Highlighter::default().toml().git(),
|
highlighter: Highlighter::default().toml().git(),
|
||||||
comment_syntax: Some(vec!['#', ' ']),
|
comment_syntax: Some(vec!['#', ' ']),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
15
src/state.rs
15
src/state.rs
|
|
@ -834,6 +834,16 @@ impl Buffer {
|
||||||
pub fn end_session(&mut self, cursor_id: CursorId) {
|
pub fn end_session(&mut self, cursor_id: CursorId) {
|
||||||
self.cursors.remove(cursor_id);
|
self.cursors.remove(cursor_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_same_path(&self, path: &Path) -> bool {
|
||||||
|
self.path
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|p| p.canonicalize().ok())
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |p| {
|
||||||
|
path.canonicalize().ok().map_or(false, |path| *p == path)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLassify the character by property
|
// CLassify the character by property
|
||||||
|
|
@ -882,10 +892,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_or_get(&mut self, path: PathBuf) -> Result<BufferId, Error> {
|
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.is_same_path(&path)) {
|
||||||
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)
|
Ok(buffer_id)
|
||||||
} else {
|
} else {
|
||||||
Ok(self.buffers.insert(Buffer::from_file(path)?))
|
Ok(self.buffers.insert(Buffer::from_file(path)?))
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ impl Default for Theme {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ui_bg: Color::AnsiValue(235),
|
ui_bg: Color::AnsiValue(235),
|
||||||
select_bg: Color::AnsiValue(23),
|
select_bg: Color::AnsiValue(8),
|
||||||
line_select_bg: Color::AnsiValue(238),
|
line_select_bg: Color::AnsiValue(238),
|
||||||
unfocus_select_bg: Color::AnsiValue(240),
|
unfocus_select_bg: Color::AnsiValue(240),
|
||||||
search_result_bg: Color::AnsiValue(60),
|
search_result_bg: Color::AnsiValue(60),
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ impl Input {
|
||||||
event: Event,
|
event: Event,
|
||||||
) -> Result<Resp, Event> {
|
) -> Result<Resp, Event> {
|
||||||
buffer.begin_action();
|
buffer.begin_action();
|
||||||
|
let is_doc = matches!(self.mode, Mode::Doc);
|
||||||
match event.to_action(|e| {
|
match event.to_action(|e| {
|
||||||
e.to_char()
|
e.to_char()
|
||||||
.map(Action::Char)
|
.map(Action::Char)
|
||||||
|
|
@ -84,7 +85,9 @@ impl Input {
|
||||||
self.refocus(buffer, cursor_id);
|
self.refocus(buffer, cursor_id);
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
Some(Action::Move(dir, dist, retain_base, word)) => {
|
Some(Action::Move(dir, dist, retain_base, word))
|
||||||
|
if matches!(dir, Dir::Left | Dir::Right) || is_doc =>
|
||||||
|
{
|
||||||
let dist = match dist {
|
let dist = match dist {
|
||||||
Dist::Char => [1, 1],
|
Dist::Char => [1, 1],
|
||||||
Dist::Page => self.last_area.size().map(|s| s.saturating_sub(3).max(1)),
|
Dist::Page => self.last_area.size().map(|s| s.saturating_sub(3).max(1)),
|
||||||
|
|
@ -95,7 +98,7 @@ impl Input {
|
||||||
self.refocus(buffer, cursor_id);
|
self.refocus(buffer, cursor_id);
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
Some(Action::Pan(dir, dist)) => {
|
Some(Action::Pan(dir, dist)) if is_doc => {
|
||||||
let dist = match dist {
|
let dist = match dist {
|
||||||
Dist::Char => [1, 1],
|
Dist::Char => [1, 1],
|
||||||
Dist::Page => self.last_area.size().map(|s| s.saturating_sub(3).max(1)),
|
Dist::Page => self.last_area.size().map(|s| s.saturating_sub(3).max(1)),
|
||||||
|
|
@ -247,11 +250,6 @@ impl Input {
|
||||||
title.as_deref(),
|
title.as_deref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let Some(cursor) = buffer.cursors.get(cursor_id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let cursor_coord = buffer.text.to_coord(cursor.pos);
|
|
||||||
|
|
||||||
let line_num_w = buffer.text.lines().count().max(1).ilog10() as usize + 1;
|
let line_num_w = buffer.text.lines().count().max(1).ilog10() as usize + 1;
|
||||||
let margin_w = match self.mode {
|
let margin_w = match self.mode {
|
||||||
Mode::Prompt => 2,
|
Mode::Prompt => 2,
|
||||||
|
|
@ -261,6 +259,11 @@ impl Input {
|
||||||
|
|
||||||
self.last_area = frame.rect([margin_w, 0], [!0, !0]).area();
|
self.last_area = frame.rect([margin_w, 0], [!0, !0]).area();
|
||||||
|
|
||||||
|
let Some(cursor) = buffer.cursors.get(cursor_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let cursor_coord = buffer.text.to_coord(cursor.pos);
|
||||||
|
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
for (i, (line_num, (line_pos, line))) in buffer
|
for (i, (line_num, (line_pos, line))) in buffer
|
||||||
.text
|
.text
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,10 @@ impl<T> Options<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected(&self) -> Option<&T> {
|
||||||
|
self.options.get(*self.ranking.get(self.selected)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_options<F: FnMut(&T) -> Option<S>, S: Ord + Copy>(
|
pub fn set_options<F: FnMut(&T) -> Option<S>, S: Ord + Copy>(
|
||||||
&mut self,
|
&mut self,
|
||||||
options: impl IntoIterator<Item = T>,
|
options: impl IntoIterator<Item = T>,
|
||||||
|
|
@ -157,10 +161,10 @@ impl<T> Options<T> {
|
||||||
impl<T: Clone> 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(dir, dist, false, false)) => {
|
Some(Action::Move(dir, dist @ (Dist::Char | Dist::Doc), false, false)) => {
|
||||||
let dist = match dist {
|
let dist = match dist {
|
||||||
Dist::Char => 1,
|
Dist::Char => 1,
|
||||||
Dist::Page => self.last_height.saturating_sub(1).min(self.ranking.len()),
|
Dist::Page => unimplemented!(),
|
||||||
Dist::Doc => self.ranking.len(),
|
Dist::Doc => self.ranking.len(),
|
||||||
};
|
};
|
||||||
match dir {
|
match dir {
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,7 @@ pub struct Opener {
|
||||||
pub buffer: Buffer,
|
pub buffer: Buffer,
|
||||||
pub cursor_id: CursorId,
|
pub cursor_id: CursorId,
|
||||||
pub input: Input,
|
pub input: Input,
|
||||||
|
preview: Option<(Buffer, CursorId, Input)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Opener {
|
impl Opener {
|
||||||
|
|
@ -297,13 +298,15 @@ impl Opener {
|
||||||
cursor_id,
|
cursor_id,
|
||||||
buffer,
|
buffer,
|
||||||
input: Input::filter(),
|
input: Input::filter(),
|
||||||
|
preview: None,
|
||||||
};
|
};
|
||||||
this.update_completions();
|
this.update_completions();
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requested_height(&self) -> usize {
|
pub fn requested_height(&self) -> usize {
|
||||||
self.options.requested_height() + 3
|
!0
|
||||||
|
// self.options.requested_height() * 2 + 3
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_string(&mut self, s: &str) {
|
fn set_string(&mut self, s: &str) {
|
||||||
|
|
@ -378,7 +381,7 @@ impl Opener {
|
||||||
impl Element<()> for Opener {
|
impl Element<()> for Opener {
|
||||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<()>, Event> {
|
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<()>, Event> {
|
||||||
let path_str = self.buffer.text.to_string();
|
let path_str = self.buffer.text.to_string();
|
||||||
match event.to_action(|e| e.to_cancel().or_else(|| e.to_char().map(Action::Char))) {
|
let res = match event.to_action(|e| e.to_cancel().or_else(|| e.to_char().map(Action::Char))) {
|
||||||
Some(Action::Cancel) => Ok(Resp::end(None)),
|
Some(Action::Cancel) => Ok(Resp::end(None)),
|
||||||
// Backspace removes the entire path segment!
|
// Backspace removes the entire path segment!
|
||||||
// Only works if we're at the end of the string
|
// Only works if we're at the end of the string
|
||||||
|
|
@ -409,15 +412,27 @@ impl Element<()> for Opener {
|
||||||
}
|
}
|
||||||
Ok(None) => Ok(Resp::handled(None)),
|
Ok(None) => Ok(Resp::handled(None)),
|
||||||
Err(event) => {
|
Err(event) => {
|
||||||
let res = self
|
let res = match self
|
||||||
.input
|
.input
|
||||||
.handle(&mut self.buffer, self.cursor_id, event)
|
.handle(&mut self.buffer, self.cursor_id, event)
|
||||||
.map(Resp::into_can_end);
|
.map(Resp::into_can_end)
|
||||||
self.update_completions();
|
{
|
||||||
|
Ok(x) => Ok(x),
|
||||||
|
Err(event) => if let Some((buffer, cursor_id, input)) = &mut self.preview {
|
||||||
|
input.handle(buffer, *cursor_id, event).map(Resp::into_can_end)
|
||||||
|
} else {
|
||||||
|
Err(event)
|
||||||
|
},
|
||||||
|
};
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.buffer.text.to_string() != path_str {
|
||||||
|
self.update_completions();
|
||||||
}
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -465,11 +480,40 @@ impl Visual for FileOption {
|
||||||
|
|
||||||
impl Visual for Opener {
|
impl Visual for Opener {
|
||||||
fn render(&mut self, state: &State, frame: &mut Rect) {
|
fn render(&mut self, state: &State, frame: &mut Rect) {
|
||||||
|
self.preview = self.options.selected().and_then(|f| {
|
||||||
|
self.preview
|
||||||
|
.take()
|
||||||
|
.filter(|(b, _, _)| b.is_same_path(&f.path))
|
||||||
|
.or_else(|| {
|
||||||
|
let mut buffer = Buffer::from_file(f.path.clone()).ok()?;
|
||||||
|
let cursor_id = buffer.start_session();
|
||||||
|
Some((buffer, cursor_id, Input::default()))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let path_input_sz = 3;
|
||||||
|
let remaining_sz = frame.size()[1].saturating_sub(path_input_sz);
|
||||||
|
let (preview_sz, options_sz) = if remaining_sz > 12 {
|
||||||
|
let preview_sz = remaining_sz / 2;
|
||||||
|
(preview_sz, remaining_sz - preview_sz)
|
||||||
|
} else {
|
||||||
|
(0, remaining_sz)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((buffer, cursor_id, input)) = &mut self.preview {
|
||||||
|
frame.rect([0, 0], [frame.size()[0], preview_sz]).with(|f| {
|
||||||
|
input.render(state, buffer.name().as_deref(), buffer, *cursor_id, None, f)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
frame
|
frame
|
||||||
.rect([0, 0], [frame.size()[0], frame.size()[1].saturating_sub(3)])
|
.rect([0, preview_sz], [frame.size()[0], options_sz])
|
||||||
.with(|f| self.options.render(state, f));
|
.with(|f| self.options.render(state, f));
|
||||||
frame
|
frame
|
||||||
.rect([0, frame.size()[1].saturating_sub(3)], [frame.size()[0], 3])
|
.rect(
|
||||||
|
[0, preview_sz + options_sz],
|
||||||
|
[frame.size()[0], path_input_sz],
|
||||||
|
)
|
||||||
.with(|f| {
|
.with(|f| {
|
||||||
self.input
|
self.input
|
||||||
.render(state, None, &self.buffer, self.cursor_id, None, f)
|
.render(state, None, &self.buffer, self.cursor_id, None, f)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ pub struct Searcher {
|
||||||
buffer: Buffer,
|
buffer: Buffer,
|
||||||
cursor_id: CursorId,
|
cursor_id: CursorId,
|
||||||
input: Input,
|
input: Input,
|
||||||
|
preview: Option<(Buffer, CursorId, Input, SearchResult)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Searcher {
|
impl Searcher {
|
||||||
|
|
@ -87,11 +88,13 @@ impl Searcher {
|
||||||
cursor_id,
|
cursor_id,
|
||||||
buffer,
|
buffer,
|
||||||
input: Input::filter(),
|
input: Input::filter(),
|
||||||
|
preview: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requested_height(&self) -> usize {
|
pub fn requested_height(&self) -> usize {
|
||||||
self.options.requested_height() + 3
|
!0
|
||||||
|
// self.options.requested_height() + 3
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_completions(&mut self) {
|
fn update_completions(&mut self) {
|
||||||
|
|
@ -113,7 +116,9 @@ impl Searcher {
|
||||||
|
|
||||||
impl Element<()> for Searcher {
|
impl Element<()> for Searcher {
|
||||||
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<()>, Event> {
|
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<()>, Event> {
|
||||||
match event.to_action(|e| e.to_cancel().or_else(|| e.to_char().map(Action::Char))) {
|
let filter_str = self.buffer.text.to_string();
|
||||||
|
let res = match event.to_action(|e| e.to_cancel().or_else(|| e.to_char().map(Action::Char)))
|
||||||
|
{
|
||||||
Some(Action::Cancel) => Ok(Resp::end(None)),
|
Some(Action::Cancel) => Ok(Resp::end(None)),
|
||||||
_ => 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
|
||||||
|
|
@ -123,19 +128,35 @@ impl Element<()> for Searcher {
|
||||||
))))),
|
))))),
|
||||||
Ok(None) => Ok(Resp::handled(None)),
|
Ok(None) => Ok(Resp::handled(None)),
|
||||||
Err(event) => {
|
Err(event) => {
|
||||||
let res = self
|
let res = match self
|
||||||
.input
|
.input
|
||||||
.handle(&mut self.buffer, self.cursor_id, event)
|
.handle(&mut self.buffer, self.cursor_id, event)
|
||||||
.map(Resp::into_can_end);
|
.map(Resp::into_can_end)
|
||||||
self.update_completions();
|
{
|
||||||
|
Ok(x) => Ok(x),
|
||||||
|
Err(event) => {
|
||||||
|
if let Some((buffer, cursor_id, input, _)) = &mut self.preview {
|
||||||
|
input
|
||||||
|
.handle(buffer, *cursor_id, event)
|
||||||
|
.map(Resp::into_can_end)
|
||||||
|
} else {
|
||||||
|
Err(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.buffer.text.to_string() != filter_str {
|
||||||
|
self.update_completions();
|
||||||
}
|
}
|
||||||
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct SearchResult {
|
pub struct SearchResult {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub line_idx: usize,
|
pub line_idx: usize,
|
||||||
|
|
@ -162,11 +183,43 @@ impl Visual for SearchResult {
|
||||||
|
|
||||||
impl Visual for Searcher {
|
impl Visual for Searcher {
|
||||||
fn render(&mut self, state: &State, frame: &mut Rect) {
|
fn render(&mut self, state: &State, frame: &mut Rect) {
|
||||||
|
let path_input_sz = 3;
|
||||||
|
let remaining_sz = frame.size()[1].saturating_sub(path_input_sz);
|
||||||
|
let (preview_sz, options_sz) = if remaining_sz > 12 {
|
||||||
|
let preview_sz = remaining_sz / 2;
|
||||||
|
(preview_sz, remaining_sz - preview_sz)
|
||||||
|
} else {
|
||||||
|
(0, remaining_sz)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.preview = self.options.selected().and_then(|result| {
|
||||||
|
self.preview
|
||||||
|
.take()
|
||||||
|
.filter(|(_, _, _, r)| r == result)
|
||||||
|
.or_else(|| {
|
||||||
|
let mut buffer = Buffer::from_file(result.path.clone()).ok()?;
|
||||||
|
let cursor_id = buffer.start_session();
|
||||||
|
let mut input = Input::default();
|
||||||
|
buffer.goto_cursor(cursor_id, [0, result.line_idx as isize], true);
|
||||||
|
input.focus([0, result.line_idx as isize - preview_sz as isize / 2]);
|
||||||
|
Some((buffer, cursor_id, input, result.clone()))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((buffer, cursor_id, input, result)) = &mut self.preview {
|
||||||
|
frame.rect([0, 0], [frame.size()[0], preview_sz]).with(|f| {
|
||||||
|
input.render(state, buffer.name().as_deref(), buffer, *cursor_id, None, f)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
frame
|
frame
|
||||||
.rect([0, 0], [frame.size()[0], frame.size()[1].saturating_sub(3)])
|
.rect([0, preview_sz], [frame.size()[0], options_sz])
|
||||||
.with(|f| self.options.render(state, f));
|
.with(|f| self.options.render(state, f));
|
||||||
frame
|
frame
|
||||||
.rect([0, frame.size()[1].saturating_sub(3)], [frame.size()[0], 3])
|
.rect(
|
||||||
|
[0, preview_sz + options_sz],
|
||||||
|
[frame.size()[0], path_input_sz],
|
||||||
|
)
|
||||||
.with(|f| {
|
.with(|f| {
|
||||||
let title = format!(
|
let title = format!(
|
||||||
"{} of {} results for '{}' in {}/",
|
"{} of {} results for '{}' in {}/",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue