From d47108628c3c0d48e7ab8e7a6afb631dba42b44f Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Wed, 14 Jul 2021 23:58:09 +0800 Subject: [PATCH] Allow specifying file start position Like helix-term/src/commands.rs:3426:15 --- helix-core/src/position.rs | 12 ++++++++- helix-term/src/application.rs | 26 +++++++++++++++---- helix-term/src/args.rs | 47 +++++++++++++++++++++++++++++++---- helix-term/src/commands.rs | 15 ++++++++--- helix-term/src/main.rs | 2 +- 5 files changed, 86 insertions(+), 16 deletions(-) diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs index c6018ce692bf8..93362c775ad64 100644 --- a/helix-core/src/position.rs +++ b/helix-core/src/position.rs @@ -109,7 +109,10 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po /// TODO: this should be changed to work in terms of visual row/column, not /// graphemes. pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending: bool) -> usize { - let Position { row, col } = coords; + let Position { mut row, col } = coords; + if limit_before_line_ending { + row = row.min(text.len_lines() - 1); + }; let line_start = text.line_to_char(row); let line_end = if limit_before_line_ending { line_end_char_index(&text, row) @@ -290,5 +293,12 @@ mod test { assert_eq!(pos_at_coords(slice, (0, 0).into(), false), 0); assert_eq!(pos_at_coords(slice, (0, 1).into(), false), 1); assert_eq!(pos_at_coords(slice, (0, 2).into(), false), 2); + + // Test out of bounds. + let text = Rope::new(); + let slice = text.slice(..); + assert_eq!(pos_at_coords(slice, (10, 0).into(), true), 0); + assert_eq!(pos_at_coords(slice, (0, 10).into(), true), 0); + assert_eq!(pos_at_coords(slice, (10, 10).into(), true), 0); } } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 78b93cd960e6c..0d496d7870926 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,8 +1,15 @@ -use helix_core::{merge_toml_values, syntax}; +use helix_core::{merge_toml_values, pos_at_coords, syntax, Selection}; 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, + commands::{align_view, Align}, + compositor::Compositor, + config::Config, + job::Jobs, + ui, +}; use log::{error, warn}; @@ -116,7 +123,7 @@ impl Application { // Unset path to prevent accidentally saving to the original tutor file. doc_mut!(editor).set_path(None)?; } else if !args.files.is_empty() { - let first = &args.files[0]; // we know it's not empty + let first = &args.files[0].0; // we know it's not empty if first.is_dir() { std::env::set_current_dir(&first)?; editor.new_file(Action::VerticalSplit); @@ -124,16 +131,25 @@ impl Application { } else { let nr_of_files = args.files.len(); editor.open(first.to_path_buf(), Action::VerticalSplit)?; - for file in args.files { + for (file, pos) 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.to_path_buf(), Action::Load)?; + let doc_id = editor.open(file, Action::Load)?; + // with Action::Load all documents have the same view + let view_id = editor.tree.focus; + let doc = editor.document_mut(doc_id).unwrap(); + let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); + doc.set_selection(view_id, pos); } } editor.set_status(format!("Loaded {} files.", nr_of_files)); + // align the view to center after all files are loaded, + // does not affect views without pos since it is at the top + let (view, doc) = current!(editor); + align_view(doc, view, Align::Center); } } else if stdin().is_tty() { editor.new_file(Action::VerticalSplit); diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs index 40113db92fd8b..ccecb42b45c94 100644 --- a/helix-term/src/args.rs +++ b/helix-term/src/args.rs @@ -1,5 +1,6 @@ use anyhow::{Error, Result}; -use std::path::PathBuf; +use helix_core::Position; +use std::path::{Path, PathBuf}; #[derive(Default)] pub struct Args { @@ -7,7 +8,7 @@ pub struct Args { pub display_version: bool, pub load_tutor: bool, pub verbosity: u64, - pub files: Vec, + pub files: Vec<(PathBuf, Position)>, } impl Args { @@ -41,15 +42,51 @@ impl Args { } } } - arg => args.files.push(PathBuf::from(arg)), + arg => args.files.push(parse_file(arg)), } } // push the remaining args, if any to the files - for filename in iter { - args.files.push(PathBuf::from(filename)); + for arg in iter { + args.files.push(parse_file(arg)); } Ok(args) } } + +/// Parse arg into [`PathBuf`] and position. +pub(crate) fn parse_file(s: &str) -> (PathBuf, Position) { + split_path_row_col(s) + .or_else(|| split_path_row(s)) + .unwrap_or_else(|| (PathBuf::from(s), Position::default())) +} + +/// Split file.rs:10:2 into [`PathBuf`], row and col. +/// +/// Does not validate if file.rs is a file or directory. +fn split_path_row_col(s: &str) -> Option<(PathBuf, Position)> { + if Path::new(s).exists() { + return None; + } + let mut s = s.rsplitn(3, ':'); + let col: usize = s.next()?.parse().ok()?; + let row: usize = s.next()?.parse().ok()?; + let path = s.next()?.into(); + let pos = Position::new(row.saturating_sub(1), col.saturating_sub(1)); + Some((path, pos)) +} + +/// Split file.rs:10 into [`PathBuf`] and row. +/// +/// Does not validate if file.rs is a file or directory. +fn split_path_row(s: &str) -> Option<(PathBuf, Position)> { + if Path::new(s).exists() { + return None; + } + let mut s = s.rsplitn(2, ':'); + let row: usize = s.next()?.parse().ok()?; + let path = s.next()?.into(); + let pos = Position::new(row.saturating_sub(1), 0); + Some((path, pos)) +} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 431265cd5113e..081df9f00b5cb 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -35,6 +35,7 @@ use insert::*; use movement::Movement; use crate::{ + args, compositor::{self, Component, Compositor}, ui::{self, FilePicker, Picker, Popup, Prompt, PromptEvent}, }; @@ -111,13 +112,13 @@ impl<'a> Context<'a> { } } -enum Align { +pub(crate) enum Align { Top, Center, Bottom, } -fn align_view(doc: &Document, view: &mut View, align: Align) { +pub(crate) fn align_view(doc: &Document, view: &mut View, align: Align) { let pos = doc .selection(view.id) .primary() @@ -1767,8 +1768,14 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - let path = args.get(0).context("wrong argument count")?; - let _ = cx.editor.open(path.into(), Action::Replace)?; + let arg = args.get(0).context("wrong argument count")?; + let (path, pos) = args::parse_file(arg); + let _ = cx.editor.open(path, Action::Replace)?; + let (view, doc) = current!(cx.editor); + let pos = Selection::point(pos_at_coords(doc.text().slice(..), pos, true)); + doc.set_selection(view.id, pos); + // does not affect opening a buffer without pos + align_view(doc, view, Align::Center); Ok(()) } diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 8814013040230..90932eb6f5211 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -56,7 +56,7 @@ USAGE: hx [FLAGS] [files]... ARGS: - ... Sets the input file to use + ... Sets the input file to use or with pos file[:row[:col]] FLAGS: -h, --help Prints help information