Implemented visual line mode
This commit is contained in:
92
docs/my_prompt.md
Normal file
92
docs/my_prompt.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
## Prompt example
|
||||||
|
|
||||||
|
This is the `shed` code for the prompt that I currently use. Note that the scripting language for `shed` is essentially identical to bash. This prompt code uses the `\!` escape sequence which lets you use the output of a function as your prompt.
|
||||||
|
|
||||||
|
Also note that in `shed`, the `echo` builtin has a new `-p` flag which expands prompt escape sequences. This allows you to access these escape sequences in any context.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
prompt_topline() {
|
||||||
|
local user_and_host="\e[0m\e[1m$USER\e[1;36m@\e[1;31m$HOST\e[0m"
|
||||||
|
echo -n "\e[1;34m┏━ $user_and_host\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_stat_line() {
|
||||||
|
local last_exit_code="$?"
|
||||||
|
local last_cmd_status
|
||||||
|
local last_cmd_runtime
|
||||||
|
if [ "$last_exit_code" -eq "0" ]; then
|
||||||
|
last_cmd_status="\e[1;32m\e[0m"
|
||||||
|
else
|
||||||
|
last_cmd_status="\e[1;31m\e[0m"
|
||||||
|
fi
|
||||||
|
local last_runtime_raw="$(echo -p "\t")"
|
||||||
|
if [ -z "$last_runtime_raw" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
last_cmd_runtime="\e[1;38;2;249;226;175m $(echo -p "\T")\e[0m"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "\e[1;34m┃ $last_cmd_runtime ($last_cmd_status)\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_git_line() {
|
||||||
|
git rev-parse --is-inside-work-tree > /dev/null 2>&1 || return
|
||||||
|
|
||||||
|
local gitsigns
|
||||||
|
local status="$(git status --porcelain 2>/dev/null)"
|
||||||
|
local branch="$(git branch --show-current 2>/dev/null)"
|
||||||
|
|
||||||
|
[ -n "$status" ] && echo "$status" | command grep -q '^ [MADR]' && gitsigns="$gitsigns!"
|
||||||
|
[ -n "$status" ] && echo "$status" | command grep -q '^??' && gitsigns="$gitsigns?"
|
||||||
|
[ -n "$status" ] && echo "$status" | command grep -q '^[MADR]' && gitsigns="$gitsigns+"
|
||||||
|
|
||||||
|
local ahead="$(git rev-list --count @{upstream}..HEAD 2>/dev/null)"
|
||||||
|
local behind="$(git rev-list --count HEAD..@{upstream} 2>/dev/null)"
|
||||||
|
[ $ahead -gt 0 ] && gitsigns="$gitsigns↑"
|
||||||
|
[ $behind -gt 0 ] && gitsigns="$gitsigns↓"
|
||||||
|
|
||||||
|
if [ -n "$gitsigns" ] || [ -n "$branch" ]; then
|
||||||
|
if [ -n "$gitsigns" ]; then
|
||||||
|
gitsigns="\e[1;31m[$gitsigns]"
|
||||||
|
fi
|
||||||
|
echo -n "\e[1;34m┃ \e[1;35m ${branch}$gitsigns\e[0m\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_jobs_line() {
|
||||||
|
local job_count="$(echo -p '\j')"
|
||||||
|
if [ "$job_count" -gt 0 ]; then
|
||||||
|
echo -n "\e[1;34m┃ \e[1;33m $job_count job(s) running\e[0m\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_ssh_line() {
|
||||||
|
local ssh_server="$(echo $SSH_CONNECTION | cut -f3 -d' ')"
|
||||||
|
[ -n "$ssh_server" ] && echo -n "\e[1;34m┃ \e[1;39m🌐 $ssh_server\e[0m\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_pwd_line() {
|
||||||
|
echo -p "\e[1;34m┣━━ \e[1;36m\W\e[1;32m/"
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_dollar_line() {
|
||||||
|
local dollar="$(echo -p "\$ ")"
|
||||||
|
local dollar="$(echo -e "\e[1;32m$dollar\e[0m")"
|
||||||
|
echo -n "\e[1;34m┗━ $dollar "
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt() {
|
||||||
|
local statline="$(prompt_stat_line)"
|
||||||
|
local topline="$(prompt_topline)"
|
||||||
|
local gitline="$(prompt_git_line)"
|
||||||
|
local jobsline="$(prompt_jobs_line)"
|
||||||
|
local sshline="$(prompt_ssh_line)"
|
||||||
|
local pwdline="$(prompt_pwd_line)"
|
||||||
|
local dollarline="$(prompt_dollar_line)"
|
||||||
|
local prompt="$topline$statline$gitline$jobsline$sshline$pwdline\n$dollarline"
|
||||||
|
|
||||||
|
echo -en "$prompt"
|
||||||
|
}
|
||||||
|
|
||||||
|
export PS1="\!prompt "
|
||||||
|
```
|
||||||
@@ -321,7 +321,7 @@ pub struct LineBuf {
|
|||||||
pub cursor: ClampedUsize, // Used to index grapheme_indices
|
pub cursor: ClampedUsize, // Used to index grapheme_indices
|
||||||
|
|
||||||
pub select_mode: Option<SelectMode>,
|
pub select_mode: Option<SelectMode>,
|
||||||
pub select_range: Option<(usize, usize)>,
|
select_range: Option<(usize, usize)>,
|
||||||
pub last_selection: Option<(usize, usize)>,
|
pub last_selection: Option<(usize, usize)>,
|
||||||
|
|
||||||
pub insert_mode_start_pos: Option<usize>,
|
pub insert_mode_start_pos: Option<usize>,
|
||||||
@@ -542,6 +542,9 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
pub fn slice_to(&mut self, end: usize) -> Option<&str> {
|
pub fn slice_to(&mut self, end: usize) -> Option<&str> {
|
||||||
self.update_graphemes_lazy();
|
self.update_graphemes_lazy();
|
||||||
|
self.read_slice_to(end)
|
||||||
|
}
|
||||||
|
pub fn read_slice_to(&self, end: usize) -> Option<&str> {
|
||||||
let grapheme_index = self.grapheme_indices().get(end).copied().or_else(|| {
|
let grapheme_index = self.grapheme_indices().get(end).copied().or_else(|| {
|
||||||
if end == self.grapheme_indices().len() {
|
if end == self.grapheme_indices().len() {
|
||||||
Some(self.buffer.len())
|
Some(self.buffer.len())
|
||||||
@@ -559,6 +562,9 @@ impl LineBuf {
|
|||||||
pub fn slice_to_cursor(&mut self) -> Option<&str> {
|
pub fn slice_to_cursor(&mut self) -> Option<&str> {
|
||||||
self.slice_to(self.cursor.get())
|
self.slice_to(self.cursor.get())
|
||||||
}
|
}
|
||||||
|
pub fn read_slice_to_cursor(&self) -> Option<&str> {
|
||||||
|
self.read_slice_to(self.cursor.get())
|
||||||
|
}
|
||||||
pub fn slice_to_cursor_inclusive(&mut self) -> Option<&str> {
|
pub fn slice_to_cursor_inclusive(&mut self) -> Option<&str> {
|
||||||
self.slice_to(self.cursor.ret_add(1))
|
self.slice_to(self.cursor.ret_add(1))
|
||||||
}
|
}
|
||||||
@@ -614,14 +620,31 @@ impl LineBuf {
|
|||||||
self.update_graphemes();
|
self.update_graphemes();
|
||||||
}
|
}
|
||||||
pub fn select_range(&self) -> Option<(usize, usize)> {
|
pub fn select_range(&self) -> Option<(usize, usize)> {
|
||||||
self.select_range
|
match self.select_mode? {
|
||||||
|
SelectMode::Char(_) => {
|
||||||
|
self.select_range
|
||||||
|
}
|
||||||
|
SelectMode::Line(_) => {
|
||||||
|
let (start, end) = self.select_range?;
|
||||||
|
let start = self.pos_line_number(start);
|
||||||
|
let end = self.pos_line_number(end);
|
||||||
|
let (select_start,_) = self.line_bounds(start);
|
||||||
|
let (_,select_end) = self.line_bounds(end);
|
||||||
|
if self.read_grapheme_before(select_end).is_some_and(|gr| gr == "\n") {
|
||||||
|
Some((select_start, select_end - 1))
|
||||||
|
} else {
|
||||||
|
Some((select_start, select_end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectMode::Block(_) => todo!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn start_selecting(&mut self, mode: SelectMode) {
|
pub fn start_selecting(&mut self, mode: SelectMode) {
|
||||||
|
let range_start = self.cursor;
|
||||||
|
let mut range_end = self.cursor;
|
||||||
|
range_end.add(1);
|
||||||
|
self.select_range = Some((range_start.get(), range_end.get()));
|
||||||
self.select_mode = Some(mode);
|
self.select_mode = Some(mode);
|
||||||
let range_start = self.cursor;
|
|
||||||
let mut range_end = self.cursor;
|
|
||||||
range_end.add(1);
|
|
||||||
self.select_range = Some((range_start.get(), range_end.get()));
|
|
||||||
}
|
}
|
||||||
pub fn stop_selecting(&mut self) {
|
pub fn stop_selecting(&mut self) {
|
||||||
self.select_mode = None;
|
self.select_mode = None;
|
||||||
@@ -629,12 +652,18 @@ impl LineBuf {
|
|||||||
self.last_selection = self.select_range.take();
|
self.last_selection = self.select_range.take();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn total_lines(&mut self) -> usize {
|
pub fn total_lines(&self) -> usize {
|
||||||
self.buffer.graphemes(true).filter(|g| *g == "\n").count()
|
self.buffer.graphemes(true).filter(|g| *g == "\n").count()
|
||||||
}
|
}
|
||||||
pub fn cursor_line_number(&mut self) -> usize {
|
|
||||||
|
pub fn pos_line_number(&self, pos: usize) -> usize {
|
||||||
|
self.read_slice_to(pos)
|
||||||
|
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
pub fn cursor_line_number(&self) -> usize {
|
||||||
self
|
self
|
||||||
.slice_to_cursor()
|
.read_slice_to_cursor()
|
||||||
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
@@ -772,7 +801,7 @@ impl LineBuf {
|
|||||||
|
|
||||||
Some((start, end))
|
Some((start, end))
|
||||||
}
|
}
|
||||||
pub fn line_bounds(&mut self, n: usize) -> (usize, usize) {
|
pub fn line_bounds(&self, n: usize) -> (usize, usize) {
|
||||||
if n > self.total_lines() {
|
if n > self.total_lines() {
|
||||||
panic!(
|
panic!(
|
||||||
"Attempted to find line {n} when there are only {} lines",
|
"Attempted to find line {n} when there are only {} lines",
|
||||||
@@ -2321,7 +2350,7 @@ impl LineBuf {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match mode {
|
match mode {
|
||||||
SelectMode::Char(anchor) => match anchor {
|
SelectMode::Line(anchor) | SelectMode::Char(anchor) => match anchor {
|
||||||
SelectAnchor::Start => {
|
SelectAnchor::Start => {
|
||||||
start = self.cursor.get();
|
start = self.cursor.get();
|
||||||
}
|
}
|
||||||
@@ -2329,14 +2358,6 @@ impl LineBuf {
|
|||||||
end = self.cursor.get();
|
end = self.cursor.get();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SelectMode::Line(anchor) => match anchor {
|
|
||||||
SelectAnchor::Start => {
|
|
||||||
start = self.start_of_line();
|
|
||||||
}
|
|
||||||
SelectAnchor::End => {
|
|
||||||
end = self.end_of_line();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectMode::Block(anchor) => todo!(),
|
SelectMode::Block(anchor) => todo!(),
|
||||||
}
|
}
|
||||||
if start >= end {
|
if start >= end {
|
||||||
@@ -2381,7 +2402,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MotionKind::Exclusive((start,end)) => {
|
MotionKind::Exclusive((start,end)) => {
|
||||||
if self.select_range().is_none() {
|
if self.select_range.is_none() {
|
||||||
self.cursor.set(start)
|
self.cursor.set(start)
|
||||||
} else {
|
} else {
|
||||||
let end = end.saturating_sub(1);
|
let end = end.saturating_sub(1);
|
||||||
@@ -2395,7 +2416,14 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
pub fn range_from_motion(&mut self, motion: &MotionKind) -> Option<(usize, usize)> {
|
pub fn range_from_motion(&mut self, motion: &MotionKind) -> Option<(usize, usize)> {
|
||||||
let range = match motion {
|
let range = match motion {
|
||||||
MotionKind::On(pos) => ordered(self.cursor.get(), *pos),
|
MotionKind::On(pos) => {
|
||||||
|
let cursor_pos = self.cursor.get();
|
||||||
|
if cursor_pos == *pos {
|
||||||
|
ordered(cursor_pos, pos + 1) // scary
|
||||||
|
} else {
|
||||||
|
ordered(cursor_pos, *pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
MotionKind::Onto(pos) => {
|
MotionKind::Onto(pos) => {
|
||||||
// For motions which include the character at the cursor during operations
|
// For motions which include the character at the cursor during operations
|
||||||
// but exclude the character during movements
|
// but exclude the character during movements
|
||||||
@@ -2439,20 +2467,29 @@ impl LineBuf {
|
|||||||
) -> ShResult<()> {
|
) -> ShResult<()> {
|
||||||
match verb {
|
match verb {
|
||||||
Verb::Delete | Verb::Yank | Verb::Change => {
|
Verb::Delete | Verb::Yank | Verb::Change => {
|
||||||
|
log::debug!("Executing verb: {verb:?} with motion: {motion:?}");
|
||||||
let Some((mut start, mut end)) = self.range_from_motion(&motion) else {
|
let Some((mut start, mut end)) = self.range_from_motion(&motion) else {
|
||||||
|
log::debug!("No range from motion, nothing to do");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
log::debug!("Initial range from motion: ({start}, {end})");
|
||||||
|
log::debug!("self.grapheme_indices().len(): {}", self.grapheme_indices().len());
|
||||||
|
|
||||||
let mut do_indent = false;
|
let mut do_indent = false;
|
||||||
if verb == Verb::Change && (start,end) == self.this_line() {
|
if verb == Verb::Change && (start,end) == self.this_line() {
|
||||||
do_indent = read_shopts(|o| o.prompt.auto_indent);
|
do_indent = read_shopts(|o| o.prompt.auto_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = if verb == Verb::Yank {
|
let mut text = if verb == Verb::Yank {
|
||||||
self
|
self
|
||||||
.slice(start..end)
|
.slice(start..end)
|
||||||
.map(|c| c.to_string())
|
.map(|c| c.to_string())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
} else if start == self.grapheme_indices().len() && end == self.grapheme_indices().len() {
|
||||||
|
// user is in normal mode and pressed 'x' on the last char in the buffer
|
||||||
|
let drained = self.drain(end.saturating_sub(1)..end);
|
||||||
|
self.update_graphemes();
|
||||||
|
drained
|
||||||
} else {
|
} else {
|
||||||
let drained = self.drain(start..end);
|
let drained = self.drain(start..end);
|
||||||
self.update_graphemes();
|
self.update_graphemes();
|
||||||
@@ -2460,9 +2497,13 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
let is_linewise = matches!(
|
let is_linewise = matches!(
|
||||||
motion,
|
motion,
|
||||||
MotionKind::InclusiveWithTargetCol(..) | MotionKind::ExclusiveWithTargetCol(..)
|
MotionKind::InclusiveWithTargetCol(..) |
|
||||||
);
|
MotionKind::ExclusiveWithTargetCol(..)
|
||||||
|
) || matches!(self.select_mode, Some(SelectMode::Line(_)));
|
||||||
let register_content = if is_linewise {
|
let register_content = if is_linewise {
|
||||||
|
if !text.ends_with('\n') && !text.is_empty() {
|
||||||
|
text.push('\n');
|
||||||
|
}
|
||||||
RegisterContent::Line(text)
|
RegisterContent::Line(text)
|
||||||
} else {
|
} else {
|
||||||
RegisterContent::Span(text)
|
RegisterContent::Span(text)
|
||||||
@@ -2649,7 +2690,7 @@ impl LineBuf {
|
|||||||
if content.is_empty() {
|
if content.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if let Some(range) = self.select_range {
|
if let Some(range) = self.select_range() {
|
||||||
let register_text = self.drain_inclusive(range.0..=range.1);
|
let register_text = self.drain_inclusive(range.0..=range.1);
|
||||||
write_register(None, RegisterContent::Span(register_text)); // swap deleted text into register
|
write_register(None, RegisterContent::Span(register_text)); // swap deleted text into register
|
||||||
|
|
||||||
@@ -2688,7 +2729,7 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Verb::SwapVisualAnchor => {
|
Verb::SwapVisualAnchor => {
|
||||||
if let Some((start, end)) = self.select_range()
|
if let Some((start, end)) = self.select_range
|
||||||
&& let Some(mut mode) = self.select_mode
|
&& let Some(mut mode) = self.select_mode
|
||||||
{
|
{
|
||||||
mode.invert_anchor();
|
mode.invert_anchor();
|
||||||
@@ -2960,7 +3001,7 @@ impl LineBuf {
|
|||||||
.map(|m| self.eval_motion(verb_ref.as_ref(), m))
|
.map(|m| self.eval_motion(verb_ref.as_ref(), m))
|
||||||
.unwrap_or({
|
.unwrap_or({
|
||||||
self
|
self
|
||||||
.select_range
|
.select_range()
|
||||||
.map(MotionKind::Inclusive)
|
.map(MotionKind::Inclusive)
|
||||||
.unwrap_or(MotionKind::Null)
|
.unwrap_or(MotionKind::Null)
|
||||||
})
|
})
|
||||||
@@ -3028,15 +3069,11 @@ impl LineBuf {
|
|||||||
impl Display for LineBuf {
|
impl Display for LineBuf {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut full_buf = self.buffer.clone();
|
let mut full_buf = self.buffer.clone();
|
||||||
if let Some((start, end)) = self.select_range {
|
if let Some((start, end)) = self.select_range() {
|
||||||
let mode = self.select_mode.unwrap();
|
let mode = self.select_mode.unwrap();
|
||||||
let start_byte = self.read_idx_byte_pos(start);
|
let start_byte = self.read_idx_byte_pos(start);
|
||||||
let end_byte = self.read_idx_byte_pos(end);
|
let end_byte = self.read_idx_byte_pos(end).min(full_buf.len());
|
||||||
|
|
||||||
if start_byte >= full_buf.len() || end_byte >= full_buf.len() {
|
|
||||||
log::warn!("Selection range '{:?}' is out of bounds for buffer of length {}, clearing selection", (start, end), full_buf.len());
|
|
||||||
return write!(f, "{}", full_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
match mode.anchor() {
|
match mode.anchor() {
|
||||||
SelectAnchor::Start => {
|
SelectAnchor::Start => {
|
||||||
@@ -3044,11 +3081,13 @@ impl Display for LineBuf {
|
|||||||
if *inclusive.end() == full_buf.len() {
|
if *inclusive.end() == full_buf.len() {
|
||||||
inclusive = start_byte..=end_byte.saturating_sub(1);
|
inclusive = start_byte..=end_byte.saturating_sub(1);
|
||||||
}
|
}
|
||||||
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[inclusive.clone()], markers::VISUAL_MODE_END);
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[inclusive.clone()], markers::VISUAL_MODE_END)
|
||||||
|
.replace("\n", format!("\n{}",markers::VISUAL_MODE_START).as_str());
|
||||||
full_buf.replace_range(inclusive, &selected);
|
full_buf.replace_range(inclusive, &selected);
|
||||||
}
|
}
|
||||||
SelectAnchor::End => {
|
SelectAnchor::End => {
|
||||||
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[start..end], markers::VISUAL_MODE_END);
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[start_byte..end_byte], markers::VISUAL_MODE_END)
|
||||||
|
.replace("\n", format!("\n{}",markers::VISUAL_MODE_START).as_str());
|
||||||
full_buf.replace_range(start_byte..end_byte, &selected);
|
full_buf.replace_range(start_byte..end_byte, &selected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -561,7 +561,7 @@ impl ShedVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, mut cmd: ViCmd) -> ShResult<()> {
|
||||||
let mut selecting = false;
|
let mut select_mode = None;
|
||||||
let mut is_insert_mode = false;
|
let mut is_insert_mode = false;
|
||||||
if cmd.is_mode_transition() {
|
if cmd.is_mode_transition() {
|
||||||
let count = cmd.verb_count();
|
let count = cmd.verb_count();
|
||||||
@@ -588,7 +588,11 @@ impl ShedVi {
|
|||||||
return self.editor.exec_cmd(cmd);
|
return self.editor.exec_cmd(cmd);
|
||||||
}
|
}
|
||||||
Verb::VisualMode => {
|
Verb::VisualMode => {
|
||||||
selecting = true;
|
select_mode = Some(SelectMode::Char(SelectAnchor::End));
|
||||||
|
Box::new(ViVisual::new())
|
||||||
|
}
|
||||||
|
Verb::VisualModeLine => {
|
||||||
|
select_mode = Some(SelectMode::Line(SelectAnchor::End));
|
||||||
Box::new(ViVisual::new())
|
Box::new(ViVisual::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,10 +610,8 @@ impl ShedVi {
|
|||||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||||
self.editor.exec_cmd(cmd)?;
|
self.editor.exec_cmd(cmd)?;
|
||||||
|
|
||||||
if selecting {
|
if let Some(sel_mode) = select_mode {
|
||||||
self
|
self.editor.start_selecting(sel_mode);
|
||||||
.editor
|
|
||||||
.start_selecting(SelectMode::Char(SelectAnchor::End));
|
|
||||||
} else {
|
} else {
|
||||||
self.editor.stop_selecting();
|
self.editor.stop_selecting();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,12 +117,12 @@ fn enumerate_lines(s: &str, left_pad: usize) -> String {
|
|||||||
let trail_pad = left_pad.saturating_sub(prefix_len);
|
let trail_pad = left_pad.saturating_sub(prefix_len);
|
||||||
if i == total_lines - 1 {
|
if i == total_lines - 1 {
|
||||||
// Don't add a newline to the last line
|
// Don't add a newline to the last line
|
||||||
write!(acc, "\x1b[90m{}{num} |\x1b[0m {}{ln}",
|
write!(acc, "\x1b[0m\x1b[90m{}{num} |\x1b[0m {}{ln}",
|
||||||
" ".repeat(num_pad),
|
" ".repeat(num_pad),
|
||||||
" ".repeat(trail_pad),
|
" ".repeat(trail_pad),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
} else {
|
} else {
|
||||||
writeln!(acc, "\x1b[90m{}{num} |\x1b[0m {}{ln}",
|
writeln!(acc, "\x1b[0m\x1b[90m{}{num} |\x1b[0m {}{ln}",
|
||||||
" ".repeat(num_pad),
|
" ".repeat(num_pad),
|
||||||
" ".repeat(trail_pad),
|
" ".repeat(trail_pad),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ impl ViCmd {
|
|||||||
| Verb::NormalMode
|
| Verb::NormalMode
|
||||||
| Verb::VisualModeSelectLast
|
| Verb::VisualModeSelectLast
|
||||||
| Verb::VisualMode
|
| Verb::VisualMode
|
||||||
|
| Verb::VisualModeLine
|
||||||
| Verb::ReplaceMode
|
| Verb::ReplaceMode
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1018,6 +1018,15 @@ impl ViNormal {
|
|||||||
impl ViMode for ViNormal {
|
impl ViMode for ViNormal {
|
||||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||||
let mut cmd = match key {
|
let mut cmd = match key {
|
||||||
|
E(K::Char('V'), M::NONE) => {
|
||||||
|
Some(ViCmd {
|
||||||
|
register: Default::default(),
|
||||||
|
verb: Some(VerbCmd(1, Verb::VisualModeLine)),
|
||||||
|
motion: None,
|
||||||
|
raw_seq: "".into(),
|
||||||
|
flags: self.flags(),
|
||||||
|
})
|
||||||
|
}
|
||||||
E(K::Char(ch), M::NONE) => self.try_parse(ch),
|
E(K::Char(ch), M::NONE) => self.try_parse(ch),
|
||||||
E(K::Backspace, M::NONE) => Some(ViCmd {
|
E(K::Backspace, M::NONE) => Some(ViCmd {
|
||||||
register: Default::default(),
|
register: Default::default(),
|
||||||
@@ -1041,15 +1050,6 @@ impl ViMode for ViNormal {
|
|||||||
self.clear_cmd();
|
self.clear_cmd();
|
||||||
None
|
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) {
|
if let Some(cmd) = common_cmds(key) {
|
||||||
self.clear_cmd();
|
self.clear_cmd();
|
||||||
|
|||||||
Reference in New Issue
Block a user