Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying file start position #445

Merged
merged 1 commit into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion helix-core/src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Comment on lines +113 to +115
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really necessary? It's handled a few lines lower

Copy link
Contributor Author

@pickfire pickfire Nov 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, or otherwise it will panic when it is out of bounds.

let line_start = text.line_to_char(row);
let line_end = if limit_before_line_ending {
line_end_char_index(&text, row)
Expand Down Expand Up @@ -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);
}
}
23 changes: 18 additions & 5 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
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 serde_json::json;

use crate::{
args::Args, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs,
args::Args,
commands::{align_view, apply_workspace_edit, Align},
compositor::Compositor,
config::Config,
job::Jobs,
ui,
};

Expand Down Expand Up @@ -130,24 +134,33 @@ 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);
compositor.push(Box::new(ui::file_picker(".".into(), &config.editor)));
} 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);
Expand Down
45 changes: 40 additions & 5 deletions helix-term/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use anyhow::{Error, Result};
use std::path::PathBuf;
use helix_core::Position;
use std::path::{Path, PathBuf};

#[derive(Default)]
pub struct Args {
pub display_help: bool,
pub display_version: bool,
pub load_tutor: bool,
pub verbosity: u64,
pub files: Vec<PathBuf>,
pub files: Vec<(PathBuf, Position)>,
}

impl Args {
Expand Down Expand Up @@ -41,15 +42,49 @@ 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) {
let def = || (PathBuf::from(s), Position::default());
if Path::new(s).exists() {
return def();
}
split_path_row_col(s)
.or_else(|| split_path_row(s))
.unwrap_or_else(def)
}

/// 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)> {
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)> {
let (row, path) = s.rsplit_once(':')?;
let row: usize = row.parse().ok()?;
let path = path.into();
let pos = Position::new(row.saturating_sub(1), 0);
Some((path, pos))
}
13 changes: 10 additions & 3 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use insert::*;
use movement::Movement;

use crate::{
args,
compositor::{self, Component, Compositor},
ui::{self, FilePicker, Picker, Popup, Prompt, PromptEvent},
};
Expand Down Expand Up @@ -112,13 +113,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()
Expand Down Expand Up @@ -2025,7 +2026,13 @@ pub mod cmd {
) -> anyhow::Result<()> {
ensure!(!args.is_empty(), "wrong argument count");
for arg in args {
let _ = cx.editor.open(arg.as_ref().into(), Action::Replace)?;
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(())
}
Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ USAGE:
hx [FLAGS] [files]...

ARGS:
<files>... Sets the input file to use
<files>... Sets the input file to use, position can also be specified via file[:row[:col]]

FLAGS:
-h, --help Prints help information
Expand Down