W9 Phase 3: Add InsertOtherFile, InsertCurTagged, InsertOtherTagged

Complete MC Ctrl-X chord parity for the PutCurrent/Other variants:

- Cmd::InsertOtherFile (C-x C-r) — insert other panel cursor filename
- Cmd::InsertCurTagged (C-x t) — insert active panel marked filenames
- Cmd::InsertOtherTagged (C-x C-t) — insert other panel marked names

Direct keymap bindings for the same actions:
- Alt-' — InsertOtherFile
- Alt-t — InsertCurTagged
- Ctrl-Alt-G — InsertOtherTagged

Added 3 unit tests covering each command's dispatch + cmdline insertion.

Tests: 1384 pass (was 1381), zero warnings.
This commit is contained in:
2026-07-06 15:05:21 +03:00
parent 29f08c949a
commit 4d4f940916
3 changed files with 110 additions and 0 deletions
@@ -439,6 +439,42 @@ impl FileManager {
self.cmdline.insert_text(&name);
Ok(true)
}
Cmd::InsertOtherFile => {
let name = self
.other_panel()
.cursor_path()
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
self.cmdline.insert_text(&name);
Ok(true)
}
Cmd::InsertCurTagged => {
let names: Vec<String> = self
.active_panel()
.marked_names()
.into_iter()
.collect();
if names.is_empty() {
self.status.set_message("No tagged files".to_string());
} else {
self.cmdline.insert_text(&names.join(" "));
}
Ok(true)
}
Cmd::InsertOtherTagged => {
let names: Vec<String> = self
.other_panel()
.marked_names()
.into_iter()
.collect();
if names.is_empty() {
self.status.set_message("No tagged files".to_string());
} else {
self.cmdline.insert_text(&names.join(" "));
}
Ok(true)
}
Cmd::SplitLess => {
self.runtime.equal_split = Some(false);
self.split_ratio = self.split_ratio.saturating_sub(5).max(10);
@@ -1546,4 +1546,53 @@ mod tests {
assert_eq!(fm.runtime.equal_split, Some(true));
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn insert_cur_file_inserts_cursor_filename() {
let dir = std::env::temp_dir().join("tlc-fm-insert-cur-file");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join("hello.txt"), b"x").unwrap();
let mut fm = FileManager::new(&dir, &empty_cfg()).unwrap();
fm.active_panel_mut().refresh().unwrap();
fm.move_cursor(1);
fm.dispatch(Cmd::InsertCurFile).unwrap();
assert_eq!(fm.cmdline.value(), "hello.txt");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn insert_other_file_inserts_other_panel_filename() {
let src = std::env::temp_dir().join("tlc-fm-insert-other-file-src");
let dst = std::env::temp_dir().join("tlc-fm-insert-other-file-dst");
let _ = fs::remove_dir_all(&src);
let _ = fs::remove_dir_all(&dst);
fs::create_dir_all(&src).unwrap();
fs::create_dir_all(&dst).unwrap();
fs::write(dst.join("other.txt"), b"x").unwrap();
let mut fm = FileManager::new(&src, &empty_cfg()).unwrap();
fm.active_panel_mut().refresh().unwrap();
let _ = fm.other_panel_mut().set_path(&dst);
fm.other_panel_mut().refresh().unwrap();
fm.dispatch(Cmd::InsertOtherFile).unwrap();
assert_eq!(fm.cmdline.value(), "other.txt");
let _ = fs::remove_dir_all(&src);
let _ = fs::remove_dir_all(&dst);
}
#[test]
fn insert_cur_tagged_inserts_marked_names() {
let dir = std::env::temp_dir().join("tlc-fm-insert-tagged");
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
fs::write(dir.join("a.txt"), b"x").unwrap();
fs::write(dir.join("b.txt"), b"x").unwrap();
let mut fm = FileManager::new(&dir, &empty_cfg()).unwrap();
fm.active_panel_mut().refresh().unwrap();
fm.active_panel_mut().mark_at(1);
fm.dispatch(Cmd::InsertCurTagged).unwrap();
let text = fm.cmdline.value();
assert!(text.contains("a.txt"), "expected 'a.txt' in '{text}'");
let _ = fs::remove_dir_all(&dir);
}
}
@@ -151,6 +151,14 @@ pub enum Cmd {
/// (MC binds Alt-Enter / Ctrl-Enter; tlc binds Alt-Enter to
/// [`Cmd::Cmdline`], so this is bound to `Alt-;` to avoid the clash.)
InsertCurFile,
/// C-x C-r — insert the other panel's cursor filename into the
/// command line.
InsertOtherFile,
/// C-x t — insert all tagged (selected) filenames of the active
/// panel into the command line, space-separated.
InsertCurTagged,
/// C-x C-t — insert all tagged filenames of the inactive panel.
InsertOtherTagged,
/// Shift-F10 (a.k.a. F20) — quit immediately without the confirm dialog.
QuitQuiet,
/// C-z — suspend tlc (send SIGTSTP to self, return to the parent shell).
@@ -275,6 +283,9 @@ impl Cmd {
Cmd::InsertCurPath => "Insert current path",
Cmd::InsertOtherPath => "Insert other path",
Cmd::InsertCurFile => "Insert filename",
Cmd::InsertOtherFile => "Insert other filename",
Cmd::InsertCurTagged => "Insert tagged filenames",
Cmd::InsertOtherTagged => "Insert other tagged",
Cmd::QuitQuiet => "Quit (no confirm)",
Cmd::Suspend => "Suspend",
Cmd::EditHistory => "View/edit history",
@@ -502,6 +513,11 @@ pub fn default_keymap() -> Keymap {
km.bind(Key::alt('a'), Cmd::InsertCurPath);
km.bind(Key::alt('A'), Cmd::InsertOtherPath);
km.bind(Key::alt(';'), Cmd::InsertCurFile);
km.bind(Key::alt('\''), Cmd::InsertOtherFile);
km.bind(Key::alt('t'), Cmd::InsertCurTagged);
let mut ctrl_alt_g = Key::alt('g');
ctrl_alt_g.mods |= Modifiers::CTRL;
km.bind(ctrl_alt_g, Cmd::InsertOtherTagged);
km.bind(Key::f(20), Cmd::QuitQuiet);
km.bind(Key::ctrl('z'), Cmd::Suspend);
km.bind(Key::ctrl('d'), Cmd::CompareFiles);
@@ -609,6 +625,15 @@ mod tests {
}),
Some(Cmd::SortReverse)
);
assert_eq!(km.lookup(Key::alt('\'')), Some(Cmd::InsertOtherFile));
assert_eq!(km.lookup(Key::alt('t')), Some(Cmd::InsertCurTagged));
assert_eq!(
km.lookup(Key {
code: b'g' as u32,
mods: crate::key::Modifiers::CTRL | crate::key::Modifiers::ALT,
}),
Some(Cmd::InsertOtherTagged)
);
}
#[test]