diff --git a/assets/manpage/edit.1 b/assets/manpage/edit.1 index bef840a78e8..90a9789f5ad 100644 --- a/assets/manpage/edit.1 +++ b/assets/manpage/edit.1 @@ -9,10 +9,15 @@ edit is a simple text editor inspired by MS-DOS edit. Edit is an interactive mode-less editor. Use F10 to access the menus. .SH ARGUMENTS .TP -\fIFILE[:LINE[:COLUMN]]\fP -The file to open, optionally with line and column (e.g., \fBfoo.txt:123:45\fP). +\fIFILE\fP +The file to open. .SH OPTIONS .TP +\fB\-g\fP, \fB\-\-goto\fP \fIFILE:LINE[:CHARACTER]\fP +Open a file at the specified line and character position. +The option applies only to the next file. +Repeat it to open multiple files at specified positions. +.TP \fB\-h\fP, \fB\-\-help\fP Print the help message. .TP diff --git a/crates/edit/src/bin/edit/documents.rs b/crates/edit/src/bin/edit/documents.rs index 1bbdde61afd..6c0d4cf16a7 100644 --- a/crates/edit/src/bin/edit/documents.rs +++ b/crates/edit/src/bin/edit/documents.rs @@ -194,7 +194,6 @@ impl DocumentManager { } pub fn add_file_path(&mut self, path: &Path) -> apperr::Result<&mut Document> { - let (path, goto) = Self::parse_filename_goto(path); let path = path::normalize(path); let mut file = match File::open(&path) { @@ -208,9 +207,6 @@ impl DocumentManager { // Check if the file is already open. if file_id.is_some() && self.update_active(|doc| doc.file_id == file_id) { let doc = self.active_mut().unwrap(); - if let Some(goto) = goto { - doc.buffer.borrow_mut().cursor_move_to_logical(goto); - } return Ok(doc); } @@ -219,12 +215,6 @@ impl DocumentManager { if let Some(file) = &mut file { let mut tb = buffer.borrow_mut(); tb.read_file(file, None)?; - - if let Some(goto) = goto - && goto != Default::default() - { - tb.cursor_move_to_logical(goto); - } } } @@ -288,63 +278,63 @@ impl DocumentManager { } Ok(buffer) } +} - // Parse a filename in the form of "filename:line:char". - // Returns the position of the first colon and the line/char coordinates. - fn parse_filename_goto(path: &Path) -> (&Path, Option) { - fn parse(s: &[u8]) -> Option { - if s.is_empty() { - return None; - } - - let mut num: CoordType = 0; - for &b in s { - if !b.is_ascii_digit() { - return None; - } - let digit = (b - b'0') as CoordType; - num = num.checked_mul(10)?.checked_add(digit)?; - } - Some(num) +/// Parse a filename in the form of "filename:line:char". +/// Returns the position of the first colon and the line/char coordinates. +pub fn parse_filename_goto(path: &Path) -> (&Path, Option) { + fn parse(s: &[u8]) -> Option { + if s.is_empty() { + return None; } - fn find_colon_rev(bytes: &[u8], offset: usize) -> Option { - (0..offset.min(bytes.len())).rev().find(|&i| bytes[i] == b':') + let mut num: CoordType = 0; + for &b in s { + if !b.is_ascii_digit() { + return None; + } + let digit = (b - b'0') as CoordType; + num = num.checked_mul(10)?.checked_add(digit)?; } + Some(num) + } - let bytes = path.as_os_str().as_encoded_bytes(); - let colend = match find_colon_rev(bytes, bytes.len()) { - // Reject filenames that would result in an empty filename after stripping off the :line:char suffix. - // For instance, a filename like ":123:456" will not be processed by this function. - Some(colend) if colend > 0 => colend, - _ => return (path, None), - }; + fn find_colon_rev(bytes: &[u8], offset: usize) -> Option { + (0..offset.min(bytes.len())).rev().find(|&i| bytes[i] == b':') + } - let last = match parse(&bytes[colend + 1..]) { - Some(last) => last, - None => return (path, None), - }; - let last = (last - 1).max(0); - let mut len = colend; - let mut goto = Point { x: 0, y: last }; - - if let Some(colbeg) = find_colon_rev(bytes, colend) { - // Same here: Don't allow empty filenames. - if colbeg != 0 - && let Some(first) = parse(&bytes[colbeg + 1..colend]) - { - let first = (first - 1).max(0); - len = colbeg; - goto = Point { x: last, y: first }; - } + let bytes = path.as_os_str().as_encoded_bytes(); + let colend = match find_colon_rev(bytes, bytes.len()) { + // Reject filenames that would result in an empty filename after stripping off the :line:char suffix. + // For instance, a filename like ":123:456" will not be processed by this function. + Some(colend) if colend > 0 => colend, + _ => return (path, None), + }; + + let last = match parse(&bytes[colend + 1..]) { + Some(last) => last, + None => return (path, None), + }; + let last = (last - 1).max(0); + let mut len = colend; + let mut goto = Point { x: 0, y: last }; + + if let Some(colbeg) = find_colon_rev(bytes, colend) { + // Same here: Don't allow empty filenames. + if colbeg != 0 + && let Some(first) = parse(&bytes[colbeg + 1..colend]) + { + let first = (first - 1).max(0); + len = colbeg; + goto = Point { x: last, y: first }; } - - // Strip off the :line:char suffix. - let path = &bytes[..len]; - let path = unsafe { OsStr::from_encoded_bytes_unchecked(path) }; - let path = Path::new(path); - (path, Some(goto)) } + + // Strip off the :line:char suffix. + let path = &bytes[..len]; + let path = unsafe { OsStr::from_encoded_bytes_unchecked(path) }; + let path = Path::new(path); + (path, Some(goto)) } #[cfg(test)] @@ -354,7 +344,7 @@ mod tests { #[test] fn test_parse_last_numbers() { fn parse(s: &str) -> (&str, Option) { - let (p, g) = DocumentManager::parse_filename_goto(Path::new(s)); + let (p, g) = parse_filename_goto(Path::new(s)); (p.to_str().unwrap(), g) } diff --git a/crates/edit/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs index c15113941f6..d53e864ee4d 100644 --- a/crates/edit/src/bin/edit/main.rs +++ b/crates/edit/src/bin/edit/main.rs @@ -243,18 +243,24 @@ fn handle_args(state: &mut State) -> apperr::Result { let cwd = env::current_dir()?; let mut dir = None; let mut parse_args = true; + let mut goto_next = false; // The best CLI argument parser in the world. for arg in env::args_os().skip(1) { if parse_args { if arg == "--" { parse_args = false; + goto_next = false; continue; } if arg == "-" { paths.clear(); break; } + if arg == "-g" || arg == "--goto" { + goto_next = true; + continue; + } if arg == "-h" || arg == "--help" || (cfg!(windows) && arg == "/?") { print_help(); return Ok(true); @@ -265,22 +271,32 @@ fn handle_args(state: &mut State) -> apperr::Result { } } - let p = cwd.join(Path::new(&arg)); + let (arg, goto) = if goto_next { + goto_next = false; + documents::parse_filename_goto(Path::new(&arg)) + } else { + (Path::new(&arg), None) + }; + + let p = cwd.join(arg); let p = path::normalize(&p); if p.is_dir() { state.wants_file_picker = StateFilePicker::Open; dir = Some(p); } else { - paths.push(&*scratch, p); + paths.push(&*scratch, (p, goto)); } } - for p in &paths { - state.documents.add_file_path(p)?; + for (p, goto) in &paths { + let doc = state.documents.add_file_path(p)?; + if let Some(goto) = goto { + doc.buffer.borrow_mut().cursor_move_to_logical(*goto); + } } if dir.is_none() - && let Some(parent) = paths.last().and_then(|p| p.parent()) + && let Some(parent) = paths.last().and_then(|(p, _)| p.parent()) { dir = Some(parent.to_path_buf()); } @@ -307,13 +323,12 @@ fn handle_stdin(state: &mut State) -> apperr::Result<()> { fn print_help() { sys::write_stdout(concat!( - "Usage: edit [OPTIONS] [FILE[:LINE[:COLUMN]]]\n", + "Usage: edit [OPTIONS] [FILE]...\n", "Options:\n", - " -h, --help Print this help message\n", - " -v, --version Print the version number\n", - "\n", - "Arguments:\n", - " FILE[:LINE[:COLUMN]] The file to open, optionally with line and column (e.g., foo.txt:123:45)\n", + " -g, --goto Open a file at the specified line and character position\n", + " -h, --help Print this help message\n", + " -v, --version Print the version number\n", + "\n" )); }