W60: Goto dialog parity — percent/offset/hex options

MC's goto dialog supports Line/Percent/Decimal offset/Hex offset
modes. TLC's goto prompt previously only accepted line numbers.

Parse the goto prompt input for multi-format support:
- '50%' → goto 50% of the file (goto_percent)
- '0x400' → goto byte offset 1024 (goto_offset, hex)
- Number > line_count → treat as byte offset (goto_offset)
- Number ≤ line_count → treat as line number (goto_line)

Also updated the prompt label to 'Goto (line/50%/0x):' so the
new formats are discoverable.

Added 1 unit test verifying 50% goto lands near line 50 in a
100-line file.

Tests: 1421 pass (was 1420), zero warnings.
This commit is contained in:
2026-07-07 14:47:03 +03:00
parent a99fb17568
commit 11a7abcc8d
+48 -4
View File
@@ -816,7 +816,7 @@ impl Viewer {
let label = match self.prompt {
Some(ViewerPrompt::Search) => " Search: ",
Some(ViewerPrompt::SearchBackward) => " Search backward: ",
Some(ViewerPrompt::GotoLine) => " Goto line: ",
Some(ViewerPrompt::GotoLine) => " Goto (line/50%/0x): ",
Some(ViewerPrompt::SaveBeforeQuit) => " Save before quit? (Y/N/Esc) ",
None => return,
};
@@ -1202,13 +1202,42 @@ impl Viewer {
}
}
ViewerPrompt::GotoLine => {
if let Ok(n) = text.trim().parse::<u64>() {
if n >= 1 {
if let Ok(target) = self.goto_line(n) {
let trimmed = text.trim();
// Percent: "50%" → goto 50% of the file.
if let Some(pct) = trimmed.strip_suffix('%') {
if let Ok(n) = pct.parse::<u64>() {
if n <= 100 {
if let Ok(target) = self.goto_percent(n) {
self.cursor = target.offset;
self.top = target.line.saturating_sub(1);
}
}
}
// Hex offset: "0x400" → goto byte offset 1024.
} else if trimmed.starts_with("0x") || trimmed.starts_with("0X") {
if let Ok(off) = u64::from_str_radix(&trimmed[2..], 16) {
if let Ok(target) = self.goto_offset(off) {
self.cursor = target.offset;
self.top = target.line.saturating_sub(1);
}
}
// Decimal offset: plain number → goto byte offset
// if it's larger than the file's line count, else
// goto line.
} else if let Ok(n) = trimmed.parse::<u64>() {
if n >= 1 {
if n > self.goto.line_count() {
if let Ok(target) = self.goto_offset(n) {
self.cursor = target.offset;
self.top = target.line.saturating_sub(1);
}
} else {
if let Ok(target) = self.goto_line(n) {
self.cursor = target.offset;
self.top = target.line.saturating_sub(1);
}
}
}
}
}
ViewerPrompt::SaveBeforeQuit => {
@@ -1849,6 +1878,21 @@ mod tests {
assert!(!v.ruler);
}
#[test]
fn goto_accepts_percent_and_offset() {
// Use a 100-line file so 50% = line 50.
let lines: Vec<String> = (1..=100).map(|i| format!("line{i}")).collect();
let content = lines.join("\n");
let p = make_text("goto-fmt.txt", content.as_bytes());
let mut v = Viewer::open(&p).unwrap();
// Open goto prompt.
v.prompt = Some(ViewerPrompt::GotoLine);
v.prompt_input = "50%".to_string();
// Activate the prompt — the commit handler processes it.
v.handle_prompt_key(Key::ENTER);
assert!(v.top >= 49 && v.top <= 51, "50% should land near line 50, got top={}", v.top);
}
fn k(c: char) -> Key {
Key { code: c as u32, mods: crate::key::Modifiers::empty() }
}