Compare commits
6 commits
3862c66538
...
0337006986
| Author | SHA1 | Date | |
|---|---|---|---|
| 0337006986 | |||
| 3e8dcdfb11 | |||
| 6a6bcec4ce | |||
| 07837f7761 | |||
| df378b46de | |||
| 4d50159122 |
12 changed files with 265 additions and 110 deletions
|
|
@ -17,20 +17,20 @@ pub enum Dir {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Char(char), // Insert a character
|
Char(char), // Insert a character
|
||||||
Indent(bool), // Indent (indent vs deindent)
|
Indent(bool), // Indent (indent vs deindent)
|
||||||
Move(Dir, Dist, bool, bool), // Move the cursor (dir, dist, retain_base, word)
|
Move(Dir, Dist, bool, bool), // Move the cursor (dir, dist, retain_base, word)
|
||||||
Pan(Dir, Dist), // Pan the view window
|
Pan(Dir, Dist), // Pan the view window
|
||||||
PaneMove(Dir), // Move panes
|
PaneMove(Dir), // Move panes
|
||||||
PaneOpen(Dir), // Create a new pane
|
PaneOpen(Dir), // Create a new pane
|
||||||
PaneClose, // Close the current pane
|
PaneClose, // Close the current pane
|
||||||
Cancel, // Cancels the current action
|
Cancel, // Cancels the current action
|
||||||
Continue, // Continue past an info-only element (like a help screen)
|
Continue, // Continue past an info-only element (like a help screen)
|
||||||
Go, // Search, accept, or select the current option
|
Go, // Search, accept, or select the current option
|
||||||
Yes, // A binary confirmation is answered 'yes'
|
Yes, // A binary confirmation is answered 'yes'
|
||||||
No, // A binary confirmation is answered 'no'
|
No, // A binary confirmation is answered 'no'
|
||||||
Quit, // Quit the application
|
Quit, // Quit the application
|
||||||
OpenPrompt, // Open the command prompt
|
OpenPrompt, // Open the command prompt
|
||||||
Show(Option<String>, String), // Display an optionally titled informational text box to the user
|
Show(Option<String>, String), // Display an optionally titled informational text box to the user
|
||||||
OpenSwitcher, // Open the buffer switcher
|
OpenSwitcher, // Open the buffer switcher
|
||||||
OpenOpener(PathBuf), // Open the file opener
|
OpenOpener(PathBuf), // Open the file opener
|
||||||
|
|
@ -45,7 +45,7 @@ pub enum Action {
|
||||||
SelectToken, // Fully select the token under the cursor
|
SelectToken, // Fully select the token under the cursor
|
||||||
SelectAll, // Fully select the entire input
|
SelectAll, // Fully select the entire input
|
||||||
Save, // Save the current buffer
|
Save, // Save the current buffer
|
||||||
Mouse(MouseAction, [isize; 2], bool), // (action, pos, is_ctrl)
|
Mouse(MouseAction, [isize; 2], bool, usize), // (action, pos, is_ctrl, drag_id)
|
||||||
Undo,
|
Undo,
|
||||||
Redo,
|
Redo,
|
||||||
Copy,
|
Copy,
|
||||||
|
|
@ -495,7 +495,7 @@ impl RawEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_mouse(&self, area: Area) -> Option<Action> {
|
pub fn to_mouse(&self, area: Area, drag_id_counter: &mut usize) -> Option<Action> {
|
||||||
let TerminalEvent::Mouse(ev) = self.0 else {
|
let TerminalEvent::Mouse(ev) = self.0 else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
@ -504,12 +504,15 @@ impl RawEvent {
|
||||||
let action = match ev.kind {
|
let action = match ev.kind {
|
||||||
MouseEventKind::ScrollUp => MouseAction::ScrollUp,
|
MouseEventKind::ScrollUp => MouseAction::ScrollUp,
|
||||||
MouseEventKind::ScrollDown => MouseAction::ScrollDown,
|
MouseEventKind::ScrollDown => MouseAction::ScrollDown,
|
||||||
MouseEventKind::Down(MouseButton::Left) => MouseAction::Click,
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
|
*drag_id_counter += 1;
|
||||||
|
MouseAction::Click
|
||||||
|
}
|
||||||
MouseEventKind::Drag(MouseButton::Left) => MouseAction::Drag,
|
MouseEventKind::Drag(MouseButton::Left) => MouseAction::Drag,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
let is_ctrl = ev.modifiers == KeyModifiers::CONTROL;
|
let is_ctrl = ev.modifiers == KeyModifiers::CONTROL;
|
||||||
Some(Action::Mouse(action, pos, is_ctrl))
|
Some(Action::Mouse(action, pos, is_ctrl, *drag_id_counter))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{ops::Range, path::Path};
|
use std::ops::Range;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum TokenKind {
|
pub enum TokenKind {
|
||||||
|
|
@ -58,7 +58,7 @@ impl Highlighter {
|
||||||
Self { entries, matchers }
|
Self { entries, matchers }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with(mut self, token: TokenKind, p: impl AsRef<str>) -> Self {
|
pub fn with(self, token: TokenKind, p: impl AsRef<str>) -> Self {
|
||||||
self.with_many([(token, p)])
|
self.with_many([(token, p)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl Highlighter {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highlight_str(&self, mut s: &[char]) -> Vec<Token> {
|
fn highlight_str(&self, s: &[char]) -> Vec<Token> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -325,6 +325,8 @@ impl Regex {
|
||||||
postfix(1, just('*'), |r, _, _| Self::Many(0, !0, Box::new(r))),
|
postfix(1, just('*'), |r, _, _| Self::Many(0, !0, Box::new(r))),
|
||||||
postfix(1, just('+'), |r, _, _| Self::Many(1, !0, Box::new(r))),
|
postfix(1, just('+'), |r, _, _| Self::Many(1, !0, Box::new(r))),
|
||||||
postfix(1, just('?'), |r, _, _| Self::Many(0, 1, Box::new(r))),
|
postfix(1, just('?'), |r, _, _| Self::Many(0, 1, Box::new(r))),
|
||||||
|
// Non-standard: match the lhs, then rewind the input (i.e: as if it had never been parsed).
|
||||||
|
// Most useful at the end of tokens for context-sensitivie behaviour. For example, differentiating idents and function calls
|
||||||
postfix(1, just('%'), |r, _, _| Self::Rewind(Box::new(r))),
|
postfix(1, just('%'), |r, _, _| Self::Rewind(Box::new(r))),
|
||||||
// Non-standard: `x@y` parses `x` and then `y`. `y` can use `~` to refer to the extra string that was
|
// Non-standard: `x@y` parses `x` and then `y`. `y` can use `~` to refer to the extra string that was
|
||||||
// parsed by `x`. This supports nesting and is intended for context-sensitive patterns like Rust raw
|
// parsed by `x`. This supports nesting and is intended for context-sensitive patterns like Rust raw
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use super::*;
|
|
||||||
use crate::highlight::{Highlighter, TokenKind};
|
use crate::highlight::{Highlighter, TokenKind};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
@ -22,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!['#', ' ']),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
27
src/state.rs
27
src/state.rs
|
|
@ -212,13 +212,11 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&mut self) -> Result<(), Error> {
|
pub fn save(&mut self) -> Result<(), Error> {
|
||||||
if self.unsaved {
|
std::fs::write(
|
||||||
std::fs::write(
|
self.path.as_ref().expect("buffer must have path to save"),
|
||||||
self.path.as_ref().expect("buffer must have path to save"),
|
self.text.to_string(),
|
||||||
self.text.to_string(),
|
)?;
|
||||||
)?;
|
self.unsaved = false;
|
||||||
self.unsaved = false;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -836,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
|
||||||
|
|
@ -884,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)?))
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,11 @@ impl<'a> Rect<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with<R>(&mut self, f: impl FnOnce(&mut Rect) -> R) -> R {
|
pub fn with<R>(&mut self, f: impl FnOnce(&mut Rect<'_>) -> R) -> R {
|
||||||
f(self)
|
f(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rect(&mut self, origin: [usize; 2], size: [usize; 2]) -> Rect {
|
pub fn rect(&mut self, origin: [usize; 2], size: [usize; 2]) -> Rect<'_> {
|
||||||
Rect {
|
Rect {
|
||||||
area: Area {
|
area: Area {
|
||||||
origin: [
|
origin: [
|
||||||
|
|
@ -103,7 +103,7 @@ impl<'a> Rect<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_border(&mut self, theme: &theme::BorderTheme, title: Option<&str>) -> Rect {
|
pub fn with_border(&mut self, theme: &theme::BorderTheme, title: Option<&str>) -> Rect<'_> {
|
||||||
let edge = self.size().map(|e| e.saturating_sub(1));
|
let edge = self.size().map(|e| e.saturating_sub(1));
|
||||||
for col in 0..edge[0] {
|
for col in 0..edge[0] {
|
||||||
self.get_mut([col, 0]).map(|c| {
|
self.get_mut([col, 0]).map(|c| {
|
||||||
|
|
@ -157,7 +157,7 @@ impl<'a> Rect<'a> {
|
||||||
self.rect([1, 1], self.size().map(|e| e.saturating_sub(2)))
|
self.rect([1, 1], self.size().map(|e| e.saturating_sub(2)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_fg(&mut self, fg: Color) -> Rect {
|
pub fn with_fg(&mut self, fg: Color) -> Rect<'_> {
|
||||||
Rect {
|
Rect {
|
||||||
fg,
|
fg,
|
||||||
bg: self.bg,
|
bg: self.bg,
|
||||||
|
|
@ -167,7 +167,7 @@ impl<'a> Rect<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_bg(&mut self, bg: Color) -> Rect {
|
pub fn with_bg(&mut self, bg: Color) -> Rect<'_> {
|
||||||
Rect {
|
Rect {
|
||||||
fg: self.fg,
|
fg: self.fg,
|
||||||
bg,
|
bg,
|
||||||
|
|
@ -177,7 +177,7 @@ impl<'a> Rect<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
bg: self.bg,
|
bg: self.bg,
|
||||||
|
|
@ -199,7 +199,7 @@ impl<'a> Rect<'a> {
|
||||||
self.area.size.map(|e| e as usize)
|
self.area.size.map(|e| e as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill(&mut self, c: char) -> Rect {
|
pub fn fill(&mut self, c: char) -> Rect<'_> {
|
||||||
for row in 0..self.size()[1] {
|
for row in 0..self.size()[1] {
|
||||||
for col in 0..self.size()[0] {
|
for col in 0..self.size()[0] {
|
||||||
let cell = Cell {
|
let cell = Cell {
|
||||||
|
|
@ -215,7 +215,7 @@ impl<'a> Rect<'a> {
|
||||||
self.rect([0, 0], self.size())
|
self.rect([0, 0], self.size())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(&mut self, origin: [isize; 2], text: &str) -> Rect {
|
pub fn text(&mut self, origin: [isize; 2], text: &str) -> Rect<'_> {
|
||||||
for (idx, c) in text.chars().enumerate() {
|
for (idx, c) in text.chars().enumerate() {
|
||||||
if (0..self.size()[0] as isize).contains(&(origin[0] + idx as isize)) && origin[1] >= 0
|
if (0..self.size()[0] as isize).contains(&(origin[0] + idx as isize)) && origin[1] >= 0
|
||||||
{
|
{
|
||||||
|
|
@ -234,7 +234,7 @@ impl<'a> Rect<'a> {
|
||||||
self.rect([0, 0], self.size())
|
self.rect([0, 0], self.size())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_cursor(&mut self, cursor: [isize; 2], style: CursorStyle) -> Rect {
|
pub fn set_cursor(&mut self, cursor: [isize; 2], style: CursorStyle) -> Rect<'_> {
|
||||||
if self.has_focus
|
if self.has_focus
|
||||||
&& (0..=self.size()[0] as isize).contains(&cursor[0])
|
&& (0..=self.size()[0] as isize).contains(&cursor[0])
|
||||||
&& (0..self.size()[1] as isize).contains(&cursor[1])
|
&& (0..self.size()[1] as isize).contains(&cursor[1])
|
||||||
|
|
@ -264,7 +264,7 @@ pub struct Framebuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Framebuffer {
|
impl Framebuffer {
|
||||||
pub fn rect(&mut self) -> Rect {
|
pub fn rect(&mut self) -> Rect<'_> {
|
||||||
Rect {
|
Rect {
|
||||||
fg: Color::Reset,
|
fg: Color::Reset,
|
||||||
bg: Color::Reset,
|
bg: Color::Reset,
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::state::{Buffer, BufferId, Cursor, CursorId};
|
||||||
state::{Buffer, BufferId, Cursor, CursorId},
|
use std::collections::HashMap;
|
||||||
terminal::CursorStyle,
|
|
||||||
};
|
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
|
||||||
|
|
||||||
pub struct Doc {
|
pub struct Doc {
|
||||||
buffer: BufferId,
|
buffer: BufferId,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ pub struct Input {
|
||||||
pub focus: [isize; 2],
|
pub focus: [isize; 2],
|
||||||
// Remember the last area for things like scrolling
|
// Remember the last area for things like scrolling
|
||||||
pub last_area: Area,
|
pub last_area: Area,
|
||||||
|
pub last_scroll_pos: Option<([isize; 2], usize, usize)>,
|
||||||
|
pub scroll_grab: Option<(usize, isize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
|
|
@ -59,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)
|
||||||
|
|
@ -67,7 +70,6 @@ impl Input {
|
||||||
.or_else(|| e.to_select_token())
|
.or_else(|| e.to_select_token())
|
||||||
.or_else(|| e.to_select_all())
|
.or_else(|| e.to_select_all())
|
||||||
.or_else(|| e.to_indent())
|
.or_else(|| e.to_indent())
|
||||||
.or_else(|| e.to_mouse(self.last_area))
|
|
||||||
.or_else(|| e.to_edit())
|
.or_else(|| e.to_edit())
|
||||||
}) {
|
}) {
|
||||||
Some(Action::Char(c)) => {
|
Some(Action::Char(c)) => {
|
||||||
|
|
@ -83,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)),
|
||||||
|
|
@ -94,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)),
|
||||||
|
|
@ -130,22 +134,42 @@ impl Input {
|
||||||
buffer.select_all_cursor(cursor_id);
|
buffer.select_all_cursor(cursor_id);
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
Some(Action::Mouse(MouseAction::Click, pos, false)) => {
|
Some(Action::Mouse(MouseAction::Click, pos, false, drag_id)) => {
|
||||||
let pos = [self.focus[0] + pos[0], self.focus[1] + pos[1]];
|
if let Some((scroll_pos, h, _)) = self.last_scroll_pos
|
||||||
// If we're already in the right place, select the token instead
|
&& scroll_pos[0] == pos[0]
|
||||||
if let Some(cursor) = buffer.cursors.get(cursor_id)
|
&& (scroll_pos[1]..=scroll_pos[1] + h as isize).contains(&pos[1])
|
||||||
&& cursor.selection().is_none()
|
|
||||||
&& buffer.text.to_coord(cursor.pos) == pos
|
|
||||||
{
|
{
|
||||||
buffer.select_token_cursor(cursor_id);
|
self.scroll_grab = Some((drag_id, pos[1] - scroll_pos[1]));
|
||||||
} else {
|
} else if let Some(pos) = self.last_area.contains(pos) {
|
||||||
buffer.goto_cursor(cursor_id, pos, true);
|
let pos = [self.focus[0] + pos[0], self.focus[1] + pos[1]];
|
||||||
|
// If we're already in the right place, select the token instead
|
||||||
|
if let Some(cursor) = buffer.cursors.get(cursor_id)
|
||||||
|
&& cursor.selection().is_none()
|
||||||
|
&& buffer.text.to_coord(cursor.pos) == pos
|
||||||
|
{
|
||||||
|
buffer.select_token_cursor(cursor_id);
|
||||||
|
} else {
|
||||||
|
buffer.goto_cursor(cursor_id, pos, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Resp::handled(None))
|
||||||
|
}
|
||||||
|
Some(Action::Mouse(MouseAction::Drag, pos, false, drag_id))
|
||||||
|
if self.scroll_grab.map_or(false, |(di, _)| di == drag_id) =>
|
||||||
|
{
|
||||||
|
if let Some(pos) = self.last_area.contains(pos)
|
||||||
|
&& let Some((_, offset)) = self.scroll_grab
|
||||||
|
&& let Some((_, scroll_sz, frame_sz)) = self.last_scroll_pos
|
||||||
|
{
|
||||||
|
self.focus[1] = ((pos[1] - offset).max(0) as usize
|
||||||
|
* buffer.text.lines().count()
|
||||||
|
/ frame_sz) as isize;
|
||||||
}
|
}
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
Some(
|
Some(
|
||||||
Action::Mouse(MouseAction::Drag, pos, false)
|
Action::Mouse(MouseAction::Drag, pos, false, _)
|
||||||
| Action::Mouse(MouseAction::Click, pos, true),
|
| Action::Mouse(MouseAction::Click, pos, true, _),
|
||||||
) => {
|
) => {
|
||||||
buffer.goto_cursor(
|
buffer.goto_cursor(
|
||||||
cursor_id,
|
cursor_id,
|
||||||
|
|
@ -226,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,
|
||||||
|
|
@ -240,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
|
||||||
|
|
@ -342,18 +366,19 @@ impl Input {
|
||||||
let line_count = buffer.text.lines().count();
|
let line_count = buffer.text.lines().count();
|
||||||
let frame_sz = outer_frame.size()[1].saturating_sub(2).max(1);
|
let frame_sz = outer_frame.size()[1].saturating_sub(2).max(1);
|
||||||
let scroll_sz = (frame_sz * frame_sz / line_count).max(1).min(frame_sz);
|
let scroll_sz = (frame_sz * frame_sz / line_count).max(1).min(frame_sz);
|
||||||
if scroll_sz != frame_sz {
|
self.last_scroll_pos = if scroll_sz != frame_sz {
|
||||||
let lines2 = line_count.saturating_sub(frame_sz).max(1);
|
let lines2 = line_count.saturating_sub(frame_sz).max(1);
|
||||||
let offset = frame_sz.saturating_sub(scroll_sz)
|
let offset = frame_sz.saturating_sub(scroll_sz)
|
||||||
* (self.focus[1].max(0) as usize).min(lines2)
|
* (self.focus[1].max(0) as usize).min(lines2)
|
||||||
/ lines2;
|
/ lines2;
|
||||||
|
let pos = [outer_frame.size()[0].saturating_sub(1), 1 + offset];
|
||||||
outer_frame
|
outer_frame
|
||||||
.rect(
|
.rect(pos, [1, scroll_sz])
|
||||||
[outer_frame.size()[0].saturating_sub(1), 1 + offset],
|
|
||||||
[1, scroll_sz],
|
|
||||||
)
|
|
||||||
.with_bg(Color::White)
|
.with_bg(Color::White)
|
||||||
.fill(' ');
|
.fill(' ');
|
||||||
}
|
Some((pos.map(|e| e as isize), scroll_sz, frame_sz))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ mod status;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
doc::{Doc, Finder},
|
doc::{Doc, Finder},
|
||||||
input::Input,
|
input::Input,
|
||||||
panes::{Pane, Panes},
|
panes::Panes,
|
||||||
prompt::{Confirm, Opener, Prompt, Show, Switcher},
|
prompt::{Confirm, Opener, Prompt, Show, Switcher},
|
||||||
root::Root,
|
root::Root,
|
||||||
search::Searcher,
|
search::Searcher,
|
||||||
|
|
@ -64,7 +64,7 @@ impl<End> Resp<End> {
|
||||||
pub fn is_end(&self) -> bool {
|
pub fn is_end(&self) -> bool {
|
||||||
self.ended.is_some()
|
self.ended.is_some()
|
||||||
}
|
}
|
||||||
pub fn into_ended(mut self) -> Option<End> {
|
pub fn into_ended(self) -> Option<End> {
|
||||||
self.ended
|
self.ended
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +113,7 @@ pub struct Options<T> {
|
||||||
// (score, option)
|
// (score, option)
|
||||||
pub options: Vec<T>,
|
pub options: Vec<T>,
|
||||||
pub ranking: Vec<usize>,
|
pub ranking: Vec<usize>,
|
||||||
|
pub last_height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Options<T> {
|
impl<T> Options<T> {
|
||||||
|
|
@ -123,13 +124,18 @@ impl<T> Options<T> {
|
||||||
selected: 0,
|
selected: 0,
|
||||||
options,
|
options,
|
||||||
ranking,
|
ranking,
|
||||||
|
last_height: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
mut f: F,
|
f: F,
|
||||||
) {
|
) {
|
||||||
self.options = options.into_iter().collect();
|
self.options = options.into_iter().collect();
|
||||||
self.apply_scoring(f);
|
self.apply_scoring(f);
|
||||||
|
|
@ -155,13 +161,28 @@ 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::Char, false, false)) => {
|
Some(Action::Move(dir, dist @ (Dist::Char | Dist::Doc), false, false)) => {
|
||||||
|
let dist = match dist {
|
||||||
|
Dist::Char => 1,
|
||||||
|
Dist::Page => unimplemented!(),
|
||||||
|
Dist::Doc => self.ranking.len(),
|
||||||
|
};
|
||||||
match dir {
|
match dir {
|
||||||
Dir::Up => {
|
Dir::Up => {
|
||||||
self.selected = (self.selected + self.ranking.len()).saturating_sub(1)
|
if self.selected == 0 {
|
||||||
% self.ranking.len().max(1)
|
self.selected = self.ranking.len().saturating_sub(1);
|
||||||
|
} else {
|
||||||
|
self.selected = self.selected.saturating_sub(dist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dir::Down => {
|
||||||
|
if self.selected == self.ranking.len().saturating_sub(1) {
|
||||||
|
self.selected = 0;
|
||||||
|
} else {
|
||||||
|
self.selected =
|
||||||
|
(self.selected + dist).min(self.ranking.len().saturating_sub(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Dir::Down => self.selected = (self.selected + 1) % self.ranking.len().max(1),
|
|
||||||
_ => return Err(event),
|
_ => return Err(event),
|
||||||
}
|
}
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
|
|
@ -192,6 +213,8 @@ impl<T: Visual> Visual for Options<T> {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.last_height = frame.size()[1];
|
||||||
|
|
||||||
self.focus = self
|
self.focus = self
|
||||||
.focus
|
.focus
|
||||||
.max(
|
.max(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ pub struct Panes {
|
||||||
selected: usize,
|
selected: usize,
|
||||||
panes: Vec<Pane>,
|
panes: Vec<Pane>,
|
||||||
last_area: Area,
|
last_area: Area,
|
||||||
|
drag_id_counter: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panes {
|
impl Panes {
|
||||||
|
|
@ -29,6 +30,7 @@ impl Panes {
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
last_area: Default::default(),
|
last_area: Default::default(),
|
||||||
|
drag_id_counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +46,7 @@ impl Element for Panes {
|
||||||
.map(Action::PaneMove)
|
.map(Action::PaneMove)
|
||||||
.or_else(|| e.to_pane_open().map(Action::PaneOpen))
|
.or_else(|| e.to_pane_open().map(Action::PaneOpen))
|
||||||
.or_else(|| e.to_pane_close())
|
.or_else(|| e.to_pane_close())
|
||||||
.or_else(|| e.to_mouse(self.last_area))
|
.or_else(|| e.to_mouse(self.last_area, &mut self.drag_id_counter))
|
||||||
}) {
|
}) {
|
||||||
Some(Action::PaneMove(Dir::Left)) => {
|
Some(Action::PaneMove(Dir::Left)) => {
|
||||||
self.selected = (self.selected + self.panes.len() - 1) % self.panes.len();
|
self.selected = (self.selected + self.panes.len() - 1) % self.panes.len();
|
||||||
|
|
@ -86,14 +88,14 @@ impl Element for Panes {
|
||||||
self.selected = new_idx;
|
self.selected = new_idx;
|
||||||
Ok(Resp::handled(None))
|
Ok(Resp::handled(None))
|
||||||
}
|
}
|
||||||
Some(Action::Mouse(action, pos, _)) => {
|
Some(ref action @ Action::Mouse(ref m_action, pos, _, _)) => {
|
||||||
for (i, pane) in self.panes.iter_mut().enumerate() {
|
for (i, pane) in self.panes.iter_mut().enumerate() {
|
||||||
if pane.last_area.contains(pos).is_some() {
|
if pane.last_area.contains(pos).is_some() {
|
||||||
if matches!(action, MouseAction::Click) {
|
if matches!(m_action, MouseAction::Click) {
|
||||||
self.selected = i;
|
self.selected = i;
|
||||||
}
|
}
|
||||||
match &mut pane.kind {
|
match &mut pane.kind {
|
||||||
PaneKind::Doc(doc) => return doc.handle(state, event),
|
PaneKind::Doc(doc) => return doc.handle(state, action.clone().into()),
|
||||||
PaneKind::Empty => {}
|
PaneKind::Empty => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,9 @@ impl Prompt {
|
||||||
- 1;
|
- 1;
|
||||||
Ok(Action::GotoLine(line))
|
Ok(Action::GotoLine(line))
|
||||||
}
|
}
|
||||||
Some("search") => {
|
Some(arg0 @ "search") => {
|
||||||
let needle = args.next().ok_or_else(|| "Expected argument".to_string())?;
|
let needle = cmd.get(arg0.len()..).unwrap().trim().to_string();
|
||||||
Ok(Action::BeginSearch(needle.to_string()))
|
Ok(Action::BeginSearch(needle))
|
||||||
}
|
}
|
||||||
Some(cmd) => Err(format!("Unknown command `{cmd}`")),
|
Some(cmd) => Err(format!("Unknown command `{cmd}`")),
|
||||||
None => Err(format!("No command entered")),
|
None => Err(format!("No command entered")),
|
||||||
|
|
@ -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) {
|
||||||
|
|
@ -325,11 +328,12 @@ impl Opener {
|
||||||
let options = entries
|
let options = entries
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
|
let metadata = fs::metadata(entry.path()).ok()?;
|
||||||
Some(FileOption {
|
Some(FileOption {
|
||||||
path: entry.path(),
|
path: entry.path(),
|
||||||
kind: if entry.file_type().ok()?.is_dir() {
|
kind: if metadata.file_type().is_dir() {
|
||||||
FileKind::Dir
|
FileKind::Dir
|
||||||
} else if entry.file_type().ok()?.is_file() {
|
} else if metadata.file_type().is_file() {
|
||||||
FileKind::File
|
FileKind::File
|
||||||
} else {
|
} else {
|
||||||
FileKind::Unknown
|
FileKind::Unknown
|
||||||
|
|
@ -377,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
|
||||||
|
|
@ -408,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -442,11 +458,12 @@ impl Visual for FileOption {
|
||||||
Some(name) => format!("{name}"),
|
Some(name) => format!("{name}"),
|
||||||
None => format!("Unknown"),
|
None => format!("Unknown"),
|
||||||
};
|
};
|
||||||
|
let is_link = if self.is_link { " (symlink)" } else { "" };
|
||||||
let desc = match self.kind {
|
let desc = match self.kind {
|
||||||
FileKind::Dir => "Directory",
|
FileKind::Dir => format!("Directory{is_link}"),
|
||||||
FileKind::Unknown => "Unknown filesystem item",
|
FileKind::Unknown => format!("Unknown{is_link}"),
|
||||||
FileKind::File => "File",
|
FileKind::File => format!("File{is_link}"),
|
||||||
FileKind::New => "Create new file",
|
FileKind::New => format!("Create new file{is_link}"),
|
||||||
};
|
};
|
||||||
frame
|
frame
|
||||||
.with_fg(match self.kind {
|
.with_fg(match self.kind {
|
||||||
|
|
@ -463,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