Skip to content

Commit 02a68c8

Browse files
committed
Support going to specific positions in file
1 parent 65c3cca commit 02a68c8

File tree

1 file changed

+75
-2
lines changed

1 file changed

+75
-2
lines changed

helix-term/src/commands.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,11 @@ fn goto_file_vsplit(cx: &mut Context) {
11551155

11561156
/// Goto files in selection.
11571157
fn goto_file_impl(cx: &mut Context, action: Action) {
1158+
struct OpenOption {
1159+
path: PathBuf,
1160+
location: Option<(usize, usize)>,
1161+
}
1162+
11581163
let (view, doc) = current_ref!(cx.editor);
11591164
let text = doc.text();
11601165
let selections = doc.selection(view.id);
@@ -1189,15 +1194,83 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11891194
.to_string(),
11901195
);
11911196
}
1197+
1198+
// Most compilers/linters print in this format
1199+
static REGEX_FILE_ROW_COL: Lazy<Regex> =
1200+
Lazy::new(|| Regex::new("^(?P<path>.[^:]+):(?P<row>\\d+)(:(?P<col>\\d+))?$").unwrap());
1201+
11921202
for sel in paths {
11931203
let p = sel.trim();
11941204
if !p.is_empty() {
11951205
let path = &rel_path.join(p);
11961206
if path.is_dir() {
11971207
let picker = ui::file_picker(path.into(), &cx.editor.config());
11981208
cx.push_layer(Box::new(overlaid(picker)));
1199-
} else if let Err(e) = cx.editor.open(path, action) {
1200-
cx.editor.set_error(format!("Open file failed: {:?}", e));
1209+
} else {
1210+
let open_option = match REGEX_FILE_ROW_COL.captures(p) {
1211+
Some(file_row_col) => {
1212+
let path = file_row_col.name("path").unwrap().as_str();
1213+
let loc = match file_row_col
1214+
.name("row")
1215+
.unwrap()
1216+
.as_str()
1217+
.parse::<NonZeroUsize>()
1218+
{
1219+
Ok(row) => match file_row_col.name("col") {
1220+
Some(col) => match col.as_str().parse::<NonZeroUsize>() {
1221+
Ok(col) => Some((row.get(), col.get())),
1222+
Err(_) => None,
1223+
},
1224+
None => Some((row.get(), 1)),
1225+
},
1226+
Err(_) => None,
1227+
};
1228+
1229+
OpenOption {
1230+
path: PathBuf::from(path),
1231+
location: loc,
1232+
}
1233+
}
1234+
None => OpenOption {
1235+
path: PathBuf::from(p),
1236+
location: None,
1237+
},
1238+
};
1239+
1240+
match cx.editor.open(&open_option.path, action) {
1241+
Ok(_) => {
1242+
if let Some((row, col)) = open_option.location {
1243+
let (view, doc) = current!(cx.editor);
1244+
1245+
let doc_text = doc.text();
1246+
1247+
// Number of lines is always positive even for empty buffers
1248+
let doc_lines = doc_text.len_lines();
1249+
1250+
// Zero-based line index
1251+
let ind_adjusted_line = usize::min(row, doc_lines) - 1;
1252+
1253+
let ind_dest = if row > doc_lines {
1254+
// Discard designated col and simply set to end of doc
1255+
doc_text.len_chars().saturating_sub(1)
1256+
} else {
1257+
let line_len = doc_text.line(ind_adjusted_line).len_chars();
1258+
1259+
let adjusted_ind_col = if line_len == 0 {
1260+
0
1261+
} else {
1262+
usize::min(col, line_len) - 1
1263+
};
1264+
1265+
doc_text.line_to_char(ind_adjusted_line) + adjusted_ind_col
1266+
};
1267+
1268+
doc.set_selection(view.id, Selection::point(ind_dest));
1269+
align_view(doc, view, Align::Center);
1270+
}
1271+
}
1272+
Err(e) => cx.editor.set_error(format!("Open file failed: {:?}", e)),
1273+
}
12011274
}
12021275
}
12031276
}

0 commit comments

Comments
 (0)