tlc: Phase C.3 — Hotlist Edit/Sort/Up/Down operations

Phase C.3 — Hotlist dialog (hotlist.rs):
- New edit_cursor(label, path): updates the entry at the cursor
  in the filtered view
- New move_up(): moves the cursor entry up one position in stored order
- New move_down(): moves the cursor entry down one position
- New sort_by_label(): sorts all entries alphabetically by label
  (case-insensitive)
- All four operations mark the dialog dirty so the caller saves
- These methods are no-ops when the cursor is out of range or the
  filtered view is empty

Tests: 1486 passing (was 1486). No regression.

Refs: MC-PARITY-AUDIT.md §5.9 (GAP-HL-1..2)
This commit is contained in:
Sisyphus
2026-07-26 02:31:54 +09:00
parent 66ec2b0c24
commit 9508c6332f
@@ -229,6 +229,73 @@ impl HotlistDialog {
self.dirty = true;
}
/// Edit the entry at the cursor in the *filtered* view.
/// Returns `true` if an entry was updated.
pub fn edit_cursor(&mut self, new_label: String, new_path: String) -> bool {
let target = {
let visible = self.filtered_entries();
match visible.get(self.cursor) {
Some(e) => Some((e.label.clone(), e.path.clone())),
None => None,
}
};
if let Some((label, path)) = target {
for entry in self.entries.iter_mut() {
if entry.label == label && entry.path == path {
entry.label = new_label;
entry.path = PathBuf::from(expand(&new_path));
self.dirty = true;
return true;
}
}
}
false
}
/// Move the cursor entry up by one position in the *stored* order.
pub fn move_up(&mut self) -> bool {
let target_idx = {
let visible = self.filtered_entries();
match visible.get(self.cursor) {
Some(entry) => self.entries.iter().position(|e| e.label == entry.label && e.path == entry.path),
None => None,
}
};
if let Some(idx) = target_idx {
if idx > 0 {
self.entries.swap(idx, idx - 1);
self.dirty = true;
return true;
}
}
false
}
/// Move the cursor entry down by one position.
pub fn move_down(&mut self) -> bool {
let target_idx = {
let visible = self.filtered_entries();
match visible.get(self.cursor) {
Some(entry) => self.entries.iter().position(|e| e.label == entry.label && e.path == entry.path),
None => None,
}
};
if let Some(idx) = target_idx {
if idx + 1 < self.entries.len() {
self.entries.swap(idx, idx + 1);
self.dirty = true;
return true;
}
}
false
}
/// Sort entries alphabetically by label.
pub fn sort_by_label(&mut self) {
self.entries.sort_by(|a, b| a.label.to_lowercase().cmp(&b.label.to_lowercase()));
self.dirty = true;
}
/// Delete the entry at the cursor in the *filtered* view.
/// Returns `true` if an entry was removed.
pub fn delete_cursor(&mut self) -> bool {