diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 79dd7c3ba352b..1722ac5d5ba56 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -2,7 +2,13 @@ use helix_core::syntax; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_view::{theme, Editor}; -use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui}; +use crate::{ + args::{Args, Files}, + compositor::Compositor, + config::Config, + job::Jobs, + ui, +}; use log::error; @@ -77,24 +83,19 @@ impl Application { let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys))); compositor.push(editor_view); - if !args.files.is_empty() { - let first = &args.files[0]; // we know it's not empty - if first.is_dir() { + match args.files { + Files::Dir(d) => { editor.new_file(Action::VerticalSplit); - compositor.push(Box::new(ui::file_picker(first.clone()))); - } else { - for file in args.files { - if file.is_dir() { - return Err(anyhow::anyhow!( - "expected a path to file, found a directory. (to open a directory pass it as first argument)" - )); - } else { - editor.open(file, Action::VerticalSplit)?; - } + compositor.push(Box::new(ui::file_picker(d))); + } + Files::Files(f) if f.is_empty() => { + editor.new_file(Action::VerticalSplit); + } + Files::Files(files) => { + for file in files { + editor.open(file, Action::VerticalSplit)?; } } - } else { - editor.new_file(Action::VerticalSplit); } editor.set_theme(theme); diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs index f0ef09eb012e7..cdda06d324b1a 100644 --- a/helix-term/src/args.rs +++ b/helix-term/src/args.rs @@ -1,4 +1,5 @@ use anyhow::{Error, Result}; +use helix_view::editor::File; use std::path::PathBuf; #[derive(Default)] @@ -6,7 +7,18 @@ pub struct Args { pub display_help: bool, pub display_version: bool, pub verbosity: u64, - pub files: Vec, + pub files: Files, +} + +pub enum Files { + Dir(PathBuf), + Files(Vec), +} + +impl Default for Files { + fn default() -> Files { + Files::Files(Vec::new()) + } } impl Args { @@ -39,15 +51,52 @@ impl Args { } } } - arg => args.files.push(PathBuf::from(arg)), + arg => { + parse_files(arg, &mut args.files)?; + } } } // push the remaining args, if any to the files - for filename in iter { - args.files.push(PathBuf::from(filename)); + for arg in iter { + parse_files(arg, &mut args.files)?; } Ok(args) } } + +/// Parse arg into [`File`] and add to files. +pub fn parse_files(s: &str, files: &mut Files) -> Result<()> { + let path = PathBuf::from(s); + if path.is_dir() { + match files { + Files::Dir(_) => return Err(Error::msg("only one dir is supported")), + Files::Files(f) if f.is_empty() => *files = Files::Dir(s.into()), + Files::Files(_) => return Err(Error::msg("cannot mix files with dir")), + } + } else { + match files { + Files::Dir(_) => return Err(Error::msg("cannot mix files with dir")), + Files::Files(ref mut files) => files.push(parse_file(s)), + } + } + Ok(()) +} + +/// Parse arg into [`File`]. +pub(crate) fn parse_file(s: &str) -> File { + split_path_pos(s).unwrap_or_else(|| PathBuf::from(s).into()) +} + +/// Split file.rs:10:2 into [`File`]. +/// +/// Does not validate if file.rs is a file or directory. +fn split_path_pos(s: &str) -> Option { + let mut s = s.rsplitn(3, ':'); + let col: u32 = s.next()?.parse().ok()?; + let row: u32 = s.next()?.parse().ok()?; + let path = s.next()?.into(); + let pos = Some((row.saturating_sub(1), col.saturating_sub(1))); + Some(File { path, pos }) +} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3b208ca8f55a4..69542434951c3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -33,6 +33,7 @@ use insert::*; use movement::Movement; use crate::{ + args, compositor::{self, Component, Compositor}, ui::{self, Picker, Popup, Prompt, PromptEvent}, }; @@ -1198,7 +1199,7 @@ mod cmd { _event: PromptEvent, ) -> anyhow::Result<()> { let path = args.get(0).context("wrong argument count")?; - let _ = cx.editor.open(path.into(), Action::Replace)?; + let _ = cx.editor.open(args::parse_file(path), Action::Replace)?; Ok(()) } @@ -1961,7 +1962,7 @@ fn buffer_picker(cx: &mut Context) { } }, |editor: &mut Editor, (id, _path): &(DocumentId, Option), _action| { - editor.switch(*id, Action::Replace); + editor.switch(*id, Action::Replace, None); }, ); cx.push_layer(Box::new(picker)); @@ -2208,7 +2209,7 @@ fn push_jump(editor: &mut Editor) { fn goto_last_accessed_file(cx: &mut Context) { let alternate_file = view!(cx.editor).last_accessed_doc; if let Some(alt) = alternate_file { - cx.editor.switch(alt, Action::Replace); + cx.editor.switch(alt, Action::Replace, None); } else { cx.editor.set_error("no last accessed buffer".to_owned()) } @@ -2254,7 +2255,9 @@ fn goto_impl( .uri .to_file_path() .expect("unable to convert URI to filepath"); - let _id = editor.open(path, action).expect("editor.open failed"); + let _id = editor + .open(path.into(), action) + .expect("editor.open failed"); let (view, doc) = current!(editor); let definition_pos = location.range.start; // TODO: convert inside server @@ -3420,7 +3423,7 @@ fn split(cx: &mut Context, action: Action) { let selection = doc.selection(view.id).clone(); let first_line = view.first_line; - cx.editor.switch(id, action); + cx.editor.switch(id, action, None); // match the selection in the previous view let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 288d3d2ecbb9a..fc3fa2b98aea0 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -120,7 +120,7 @@ pub fn file_picker(root: PathBuf) -> Picker { }, move |editor: &mut Editor, path: &PathBuf, action| { editor - .open(path.into(), action) + .open(path.clone().into(), action) .expect("editor.open failed"); }, ) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index a468b87ca58e1..bb74e753aec57 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -19,6 +19,19 @@ pub use helix_core::register::Registers; use helix_core::syntax; use helix_core::Position; +/// File path with row and column. +#[derive(Debug, Default)] +pub struct File { + pub path: PathBuf, + pub pos: Option<(u32, u32)>, +} + +impl From for File { + fn from(path: PathBuf) -> File { + File { path, pos: None } + } +} + #[derive(Debug)] pub struct Editor { pub tree: Tree, @@ -114,7 +127,7 @@ impl Editor { } } - pub fn switch(&mut self, id: DocumentId, action: Action) { + pub fn switch(&mut self, id: DocumentId, action: Action, pos: Option<(u32, u32)>) { use crate::tree::Layout; use helix_core::Selection; @@ -140,10 +153,12 @@ impl Editor { let (view, doc) = current!(self); // initialize selection for view - let selection = doc - .selections - .entry(view.id) - .or_insert_with(|| Selection::point(0)); + let pos = if let Some((row, col)) = pos { + Selection::point(doc.text().line_to_char(row as usize) + col as usize) + } else { + Selection::point(0) + }; + let selection = doc.selections.entry(view.id).or_insert(pos); // TODO: reuse align_view let pos = selection.cursor(); let line = doc.text().char_to_line(pos); @@ -156,14 +171,24 @@ impl Editor { let view_id = self.tree.split(view, Layout::Horizontal); // initialize selection for view let doc = &mut self.documents[id]; - doc.selections.insert(view_id, Selection::point(0)); + if let Some((row, col)) = pos { + let pos = doc.text().line_to_char(row as usize) + col as usize; + doc.selections.insert(view_id, Selection::point(pos)); + } else { + doc.selections.insert(view_id, Selection::point(0)); + } } Action::VerticalSplit => { let view = View::new(id); let view_id = self.tree.split(view, Layout::Vertical); // initialize selection for view let doc = &mut self.documents[id]; - doc.selections.insert(view_id, Selection::point(0)); + if let Some((row, col)) = pos { + let pos = doc.text().line_to_char(row as usize) + col as usize; + doc.selections.insert(view_id, Selection::point(pos)); + } else { + doc.selections.insert(view_id, Selection::point(0)); + } } } @@ -174,12 +199,12 @@ impl Editor { let doc = Document::default(); let id = self.documents.insert(doc); self.documents[id].id = id; - self.switch(id, action); + self.switch(id, action, None); id } - pub fn open(&mut self, path: PathBuf, action: Action) -> Result { - let path = crate::document::canonicalize_path(&path)?; + pub fn open(&mut self, file: File, action: Action) -> Result { + let path = crate::document::canonicalize_path(&file.path)?; let id = self .documents() @@ -219,7 +244,7 @@ impl Editor { id }; - self.switch(id, action); + self.switch(id, action, file.pos); Ok(id) }