Skip to content

Commit 3ad8159

Browse files
committed
Support going to specific positions in file
1 parent fcd564f commit 3ad8159

File tree

1 file changed

+74
-2
lines changed

1 file changed

+74
-2
lines changed

helix-term/src/commands.rs

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

11631163
/// Goto files in selection.
11641164
fn goto_file_impl(cx: &mut Context, action: Action) {
1165+
struct OpenOption {
1166+
path: PathBuf,
1167+
location: Option<(usize, usize)>,
1168+
}
1169+
11651170
let (view, doc) = current_ref!(cx.editor);
11661171
let text = doc.text();
11671172
let selections = doc.selection(view.id);
@@ -1197,6 +1202,10 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11971202
);
11981203
}
11991204

1205+
// Most compilers/linters print in this format
1206+
static REGEX_FILE_ROW_COL: Lazy<Regex> =
1207+
Lazy::new(|| Regex::new("^(?P<path>.[^:]+):(?P<row>\\d+)(:(?P<col>\\d+))?$").unwrap());
1208+
12001209
for sel in paths {
12011210
let p = sel.trim();
12021211
if p.is_empty() {
@@ -1211,8 +1220,71 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
12111220
if path.is_dir() {
12121221
let picker = ui::file_picker(path.into(), &cx.editor.config());
12131222
cx.push_layer(Box::new(overlaid(picker)));
1214-
} else if let Err(e) = cx.editor.open(path, action) {
1215-
cx.editor.set_error(format!("Open file failed: {:?}", e));
1223+
} else {
1224+
let open_option = match REGEX_FILE_ROW_COL.captures(p) {
1225+
Some(file_row_col) => {
1226+
let path = file_row_col.name("path").unwrap().as_str();
1227+
let loc = match file_row_col
1228+
.name("row")
1229+
.unwrap()
1230+
.as_str()
1231+
.parse::<NonZeroUsize>()
1232+
{
1233+
Ok(row) => match file_row_col.name("col") {
1234+
Some(col) => match col.as_str().parse::<NonZeroUsize>() {
1235+
Ok(col) => Some((row.get(), col.get())),
1236+
Err(_) => None,
1237+
},
1238+
None => Some((row.get(), 1)),
1239+
},
1240+
Err(_) => None,
1241+
};
1242+
1243+
OpenOption {
1244+
path: PathBuf::from(path),
1245+
location: loc,
1246+
}
1247+
}
1248+
None => OpenOption {
1249+
path: PathBuf::from(p),
1250+
location: None,
1251+
},
1252+
};
1253+
1254+
match cx.editor.open(&open_option.path, action) {
1255+
Ok(_) => {
1256+
if let Some((row, col)) = open_option.location {
1257+
let (view, doc) = current!(cx.editor);
1258+
1259+
let doc_text = doc.text();
1260+
1261+
// Number of lines is always positive even for empty buffers
1262+
let doc_lines = doc_text.len_lines();
1263+
1264+
// Zero-based line index
1265+
let ind_adjusted_line = usize::min(row, doc_lines) - 1;
1266+
1267+
let ind_dest = if row > doc_lines {
1268+
// Discard designated col and simply set to end of doc
1269+
doc_text.len_chars().saturating_sub(1)
1270+
} else {
1271+
let line_len = doc_text.line(ind_adjusted_line).len_chars();
1272+
1273+
let adjusted_ind_col = if line_len == 0 {
1274+
0
1275+
} else {
1276+
usize::min(col, line_len) - 1
1277+
};
1278+
1279+
doc_text.line_to_char(ind_adjusted_line) + adjusted_ind_col
1280+
};
1281+
1282+
doc.set_selection(view.id, Selection::point(ind_dest));
1283+
align_view(doc, view, Align::Center);
1284+
}
1285+
}
1286+
Err(e) => cx.editor.set_error(format!("Open file failed: {:?}", e)),
1287+
}
12161288
}
12171289
}
12181290
}

0 commit comments

Comments
 (0)