Implemented a gutter with line numbers for multi-line editing
This commit is contained in:
@@ -691,7 +691,6 @@ impl Dispatcher {
|
||||
if fork_builtin {
|
||||
cmd.flags |= NdFlags::FORK_BUILTINS;
|
||||
}
|
||||
log::debug!("current io_frame stack: {:#?}", self.io_stack.curr_frame());
|
||||
self.dispatch_node(cmd)?;
|
||||
}
|
||||
let job = self.job_stack.finalize_job().unwrap();
|
||||
@@ -812,6 +811,7 @@ impl Dispatcher {
|
||||
let no_fork = cmd.flags.contains(NdFlags::NO_FORK);
|
||||
|
||||
if argv.is_empty() {
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -2329,7 +2329,14 @@ impl LineBuf {
|
||||
end = self.cursor.get();
|
||||
}
|
||||
},
|
||||
SelectMode::Line(anchor) => todo!(),
|
||||
SelectMode::Line(anchor) => match anchor {
|
||||
SelectAnchor::Start => {
|
||||
start = self.start_of_line();
|
||||
}
|
||||
SelectAnchor::End => {
|
||||
end = self.end_of_line();
|
||||
}
|
||||
}
|
||||
SelectMode::Block(anchor) => todo!(),
|
||||
}
|
||||
if start >= end {
|
||||
@@ -3008,11 +3015,13 @@ impl LineBuf {
|
||||
}
|
||||
|
||||
pub fn get_hint_text(&self) -> String {
|
||||
self
|
||||
let text = self
|
||||
.hint
|
||||
.clone()
|
||||
.map(|h| h.styled(Style::BrightBlack))
|
||||
.unwrap_or_default()
|
||||
.map(|h| format!("\x1b[90m{h}\x1b[0m"))
|
||||
.unwrap_or_default();
|
||||
|
||||
text.replace("\n", "\n\x1b[90m")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -513,7 +513,6 @@ impl ShedVi {
|
||||
let new_layout = self.get_layout(&line);
|
||||
let pending_seq = self.mode.pending_seq();
|
||||
let mut prompt_string_right = self.prompt.psr_expanded.clone();
|
||||
log::debug!("prompt_string_right before truncation: {prompt_string_right:?}");
|
||||
|
||||
if prompt_string_right.as_ref().is_some_and(|psr| psr.lines().count() > 1) {
|
||||
log::warn!("PSR has multiple lines, truncating to one line");
|
||||
@@ -524,7 +523,7 @@ impl ShedVi {
|
||||
.get_ps1()
|
||||
.lines()
|
||||
.next()
|
||||
.map(|l| Layout::calc_pos(self.writer.t_cols, l, Pos { col: 0, row: 0 }))
|
||||
.map(|l| Layout::calc_pos(self.writer.t_cols, l, Pos { col: 0, row: 0 }, 0))
|
||||
.map(|p| p.col)
|
||||
.unwrap_or_default() as usize;
|
||||
let one_line = new_layout.end.row == 0;
|
||||
@@ -537,7 +536,7 @@ impl ShedVi {
|
||||
self.writer.redraw(self.prompt.get_ps1(), &line, &new_layout)?;
|
||||
|
||||
let seq_fits = pending_seq.as_ref().is_some_and(|seq| row0_used + 1 < self.writer.t_cols as usize - seq.width());
|
||||
let psr_fits = prompt_string_right.as_ref().is_some_and(|psr| new_layout.end.col as usize + 1 < self.writer.t_cols as usize - psr.width());
|
||||
let psr_fits = prompt_string_right.as_ref().is_some_and(|psr| new_layout.end.col as usize + 1 < (self.writer.t_cols as usize).saturating_sub(psr.width()));
|
||||
|
||||
if !final_draw && let Some(seq) = pending_seq && !seq.is_empty() && !(prompt_string_right.is_some() && one_line) && seq_fits {
|
||||
let to_col = self.writer.t_cols - calc_str_width(&seq);
|
||||
|
||||
@@ -100,6 +100,38 @@ pub fn get_win_size(fd: RawFd) -> (Col, Row) {
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_lines(s: &str, left_pad: usize) -> String {
|
||||
let total_lines = s.lines().count();
|
||||
let max_num_len = total_lines.to_string().len();
|
||||
s.lines()
|
||||
.enumerate()
|
||||
.fold(String::new(), |mut acc, (i, ln)| {
|
||||
if i == 0 {
|
||||
acc.push_str(ln);
|
||||
acc.push('\n');
|
||||
} else {
|
||||
let num = (i + 1).to_string();
|
||||
let num_pad = max_num_len - num.len();
|
||||
// " 2 | " — num + padding + " | "
|
||||
let prefix_len = max_num_len + 3; // "N | "
|
||||
let trail_pad = left_pad.saturating_sub(prefix_len);
|
||||
if i == total_lines - 1 {
|
||||
// Don't add a newline to the last line
|
||||
write!(acc, "\x1b[90m{}{num} |\x1b[0m {}{ln}",
|
||||
" ".repeat(num_pad),
|
||||
" ".repeat(trail_pad),
|
||||
).unwrap();
|
||||
} else {
|
||||
writeln!(acc, "\x1b[90m{}{num} |\x1b[0m {}{ln}",
|
||||
" ".repeat(num_pad),
|
||||
" ".repeat(trail_pad),
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
fn write_all(fd: RawFd, buf: &str) -> nix::Result<()> {
|
||||
let mut bytes = buf.as_bytes();
|
||||
while !bytes.is_empty() {
|
||||
@@ -771,9 +803,9 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
pub fn from_parts(term_width: u16, prompt: &str, to_cursor: &str, to_end: &str) -> Self {
|
||||
let prompt_end = Self::calc_pos(term_width, prompt, Pos { col: 0, row: 0 });
|
||||
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end);
|
||||
let end = Self::calc_pos(term_width, to_end, prompt_end);
|
||||
let prompt_end = Self::calc_pos(term_width, prompt, Pos { col: 0, row: 0 }, 0);
|
||||
let cursor = Self::calc_pos(term_width, to_cursor, prompt_end, prompt_end.col);
|
||||
let end = Self::calc_pos(term_width, to_end, prompt_end, prompt_end.col);
|
||||
Layout {
|
||||
w_calc: width_calculator(),
|
||||
prompt_end,
|
||||
@@ -782,14 +814,14 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calc_pos(term_width: u16, s: &str, orig: Pos) -> Pos {
|
||||
pub fn calc_pos(term_width: u16, s: &str, orig: Pos, left_margin: u16) -> Pos {
|
||||
const TAB_STOP: u16 = 8;
|
||||
let mut pos = orig;
|
||||
let mut esc_seq = 0;
|
||||
for c in s.graphemes(true) {
|
||||
if c == "\n" {
|
||||
pos.row += 1;
|
||||
pos.col = 0;
|
||||
pos.col = left_margin;
|
||||
}
|
||||
let c_width = if c == "\t" {
|
||||
TAB_STOP - (pos.col % TAB_STOP)
|
||||
@@ -799,12 +831,12 @@ impl Layout {
|
||||
pos.col += c_width;
|
||||
if pos.col > term_width {
|
||||
pos.row += 1;
|
||||
pos.col = c_width;
|
||||
pos.col = left_margin + c_width;
|
||||
}
|
||||
}
|
||||
if pos.col >= term_width {
|
||||
pos.row += 1;
|
||||
pos.col = 0;
|
||||
pos.col = left_margin;
|
||||
}
|
||||
|
||||
pos
|
||||
@@ -987,7 +1019,14 @@ impl LineWriter for TermWriter {
|
||||
}
|
||||
|
||||
self.buffer.push_str(prompt);
|
||||
self.buffer.push_str(line);
|
||||
let multiline = line.contains('\n');
|
||||
if multiline {
|
||||
let prompt_end = Layout::calc_pos(self.t_cols, prompt, Pos { col: 0, row: 0 }, 0);
|
||||
let display_line = enumerate_lines(line, prompt_end.col as usize);
|
||||
self.buffer.push_str(&display_line);
|
||||
} else {
|
||||
self.buffer.push_str(line);
|
||||
}
|
||||
|
||||
if end.col == 0 && end.row > 0 && !ends_with_newline(&self.buffer) {
|
||||
// The line has wrapped. We need to use our own line break.
|
||||
|
||||
@@ -1041,6 +1041,15 @@ impl ViMode for ViNormal {
|
||||
self.clear_cmd();
|
||||
None
|
||||
}
|
||||
E(K::Char('V'), M::SHIFT) => {
|
||||
Some(ViCmd {
|
||||
register: Default::default(),
|
||||
verb: Some(VerbCmd(1, Verb::VisualModeLine)),
|
||||
motion: None,
|
||||
raw_seq: "".into(),
|
||||
flags: self.flags() | CmdFlags::VISUAL_LINE,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
if let Some(cmd) = common_cmds(key) {
|
||||
self.clear_cmd();
|
||||
|
||||
Reference in New Issue
Block a user