tlc: fix F5/F6/F8 to MC parity — no-op on no selection

Reverts F5/F6/F8 from operating on current line (TLC extension) to MC behavior: do nothing when no selection is active. MC's eval_marks returns false when mark1==mark2, so F5/F6/F8 in MC are no-ops without a selection.

Removes the now-dead copy_line/cut_line/delete_line_no_sel helpers and their tests. Replaces with MC-parity no-op tests.

Extracts current_line_range() helper from select_current_line and duplicate_line_or_selection (also covers unindent_selection pattern). Removes ~50 lines of duplicated line-range computation.
This commit is contained in:
2026-06-20 13:01:14 +03:00
parent e2d4da441f
commit 7d5b4088a6
2 changed files with 37 additions and 120 deletions
@@ -396,44 +396,34 @@ impl Editor {
}
return EditorResult::Running;
}
// F5 — Copy block (or current line if no selection).
// F5 — Copy block. MC parity: no-op when no selection.
if key == Key::f(5) {
if self.cursor.has_selection() {
if let Some(text) = self.cursor.selected_text(&self.buffer) {
self.clipboard = Some(text.to_string());
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
self.message = Some("Block copied".to_string());
}
} else {
let _ = self.copy_line();
if let Some(text) = self.cursor.selected_text(&self.buffer) {
self.clipboard = Some(text.to_string());
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
self.message = Some("Block copied".to_string());
}
return EditorResult::Running;
}
// F6 — Move (cut) block (or current line if no selection).
// F6 — Move (cut) block. MC parity: no-op when no selection.
if key == Key::f(6) {
if self.cursor.has_selection() {
if let Some(text) = self.cursor.selected_text(&self.buffer) {
self.clipboard = Some(text.to_string());
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
self.cursor.delete_selection(&mut self.buffer);
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
self.modified = true;
self.message = Some("Block moved".to_string());
}
} else {
let _ = self.cut_line();
if let Some(text) = self.cursor.selected_text(&self.buffer) {
self.clipboard = Some(text.to_string());
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
self.cursor.delete_selection(&mut self.buffer);
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
self.modified = true;
self.message = Some("Block moved".to_string());
}
return EditorResult::Running;
}
// F8 — Delete block (or current line if no selection).
// F8 — Delete block. MC parity: no-op when no selection.
if key == Key::f(8) {
if self.cursor.has_selection() {
self.cursor.delete_selection(&mut self.buffer);
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
self.modified = true;
self.message = Some("Block deleted".to_string());
} else {
let _ = self.delete_line_no_sel();
}
return EditorResult::Running;
}
+23 -96
View File
@@ -737,105 +737,38 @@ bracket_flash: None,
/// any). Cursor moves to the start of the next line; anchor is
/// left at the start of the current line.
pub fn select_current_line(&mut self) {
let total = self.buffer.as_string().len();
let bytes = self.buffer.as_string().into_bytes();
let mut line_start = self.buffer.cursor();
while line_start > 0 && bytes[line_start - 1] != b'\n' {
line_start -= 1;
}
let mut line_end = self.buffer.cursor();
while line_end < total && bytes[line_end] != b'\n' {
line_end += 1;
}
if line_end < total {
line_end += 1;
}
let (line_start, line_end) = self.current_line_range();
self.buffer.set_cursor(line_start);
self.cursor.set_position(line_start, &self.buffer);
self.cursor.start_selection();
self.cursor.set_position(line_end, &self.buffer);
}
/// Copy the current line (with newline) to the clipboard when no
/// selection is active. Returns true if a line was copied.
pub fn copy_line(&mut self) -> bool {
if self.cursor.has_selection() {
return false;
}
/// Compute the byte range of the current line, starting at the
/// beginning of the line and ending just past the trailing
/// newline (or at end of buffer if the last line has no newline).
fn current_line_range(&self) -> (usize, usize) {
let total = self.buffer.as_string().len();
let bytes = self.buffer.as_string().into_bytes();
let mut line_start = self.buffer.cursor();
let cursor = self.buffer.cursor();
let mut line_start = cursor;
while line_start > 0 && bytes[line_start - 1] != b'\n' {
line_start -= 1;
}
let mut line_end = self.buffer.cursor();
let mut line_end = cursor;
while line_end < total && bytes[line_end] != b'\n' {
line_end += 1;
}
if line_end < total {
line_end += 1;
}
let text = String::from_utf8_lossy(&bytes[line_start..line_end]).into_owned();
self.clipboard = Some(text.clone());
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
self.message = Some("Line copied".to_string());
true
}
/// Cut the current line (with newline) to the clipboard when no
/// selection is active. Returns true if a line was cut.
pub fn cut_line(&mut self) -> bool {
if self.cursor.has_selection() {
return false;
}
let total = self.buffer.as_string().len();
let bytes = self.buffer.as_string().into_bytes();
let mut line_start = self.buffer.cursor();
while line_start > 0 && bytes[line_start - 1] != b'\n' {
line_start -= 1;
}
let mut line_end = self.buffer.cursor();
while line_end < total && bytes[line_end] != b'\n' {
line_end += 1;
}
if line_end < total {
line_end += 1;
}
let text = String::from_utf8_lossy(&bytes[line_start..line_end]).into_owned();
self.clipboard = Some(text.clone());
let _ = crate::editor::clipboard_osc52::osc52_copy(&text);
self.buffer.begin_undo_group();
self.buffer.set_cursor(line_start);
for _ in line_start..line_end {
self.buffer.delete_forward();
}
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
self.cursor.clear_selection();
self.buffer.end_undo_group();
self.modified = true;
self.message = Some("Line cut".to_string());
true
}
/// Delete the current line (with newline) when no selection is
/// active. Returns true if a line was deleted.
pub fn delete_line_no_sel(&mut self) -> bool {
if self.cursor.has_selection() {
return false;
}
self.buffer.delete_line();
self.cursor.set_position(self.buffer.cursor(), &self.buffer);
self.cursor.clear_selection();
self.modified = true;
self.message = Some("Line deleted".to_string());
true
(line_start, line_end)
}
/// Duplicate the current line (with newline) when no selection
/// is active, or duplicate the selected block otherwise.
/// Returns true if anything was duplicated.
pub fn duplicate_line_or_selection(&mut self) -> bool {
let total = self.buffer.as_string().len();
let bytes = self.buffer.as_string().into_bytes();
if let Some((s, e)) = self.cursor.selection() {
let text = String::from_utf8_lossy(&bytes[s..e]).into_owned();
@@ -849,17 +782,7 @@ bracket_flash: None,
self.message = Some("Duplicated".to_string());
return true;
}
let mut line_start = self.buffer.cursor();
while line_start > 0 && bytes[line_start - 1] != b'\n' {
line_start -= 1;
}
let mut line_end = self.buffer.cursor();
while line_end < total && bytes[line_end] != b'\n' {
line_end += 1;
}
if line_end < total {
line_end += 1;
}
let (line_start, line_end) = self.current_line_range();
let text = String::from_utf8_lossy(&bytes[line_start..line_end]).into_owned();
self.buffer.begin_undo_group();
self.buffer.set_cursor(line_end);
@@ -2362,37 +2285,41 @@ mod tests {
}
#[test]
fn f5_without_selection_copies_line() {
fn f5_without_selection_is_noop_mc_parity() {
let mut e = make_empty();
e.insert_str("first\nsecond\nthird\n");
e.buffer.set_cursor(8);
e.cursor.set_position(8, &e.buffer);
e.handle_key(Key::f(5));
assert_eq!(e.clipboard.as_deref(), Some("second\n"));
assert_eq!(e.clipboard, None);
assert_eq!(e.buffer().as_string(), "first\nsecond\nthird\n");
}
#[test]
fn f6_without_selection_cuts_line() {
fn f6_without_selection_is_noop_mc_parity() {
let mut e = make_empty();
e.insert_str("first\nsecond\nthird\n");
e.buffer.mark_saved();
e.modified = false;
e.buffer.set_cursor(8);
e.cursor.set_position(8, &e.buffer);
e.handle_key(Key::f(6));
assert_eq!(e.clipboard.as_deref(), Some("second\n"));
assert_eq!(e.buffer().as_string(), "first\nthird\n");
assert!(e.is_modified());
assert_eq!(e.clipboard, None);
assert_eq!(e.buffer().as_string(), "first\nsecond\nthird\n");
assert!(!e.is_modified());
}
#[test]
fn f8_without_selection_deletes_line() {
fn f8_without_selection_is_noop_mc_parity() {
let mut e = make_empty();
e.insert_str("first\nsecond\nthird\n");
e.buffer.mark_saved();
e.modified = false;
e.buffer.set_cursor(8);
e.cursor.set_position(8, &e.buffer);
e.handle_key(Key::f(8));
assert_eq!(e.buffer().as_string(), "first\nthird\n");
assert!(e.is_modified());
assert_eq!(e.buffer().as_string(), "first\nsecond\nthird\n");
assert!(!e.is_modified());
}
#[test]