From c6a835c060f5d7d3343e45cd5b163eef9534240c Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Fri, 25 Jul 2025 21:09:53 +0200 Subject: [PATCH 01/15] add make command and basic picker integration --- helix-term/src/commands.rs | 12 ++++ helix-term/src/commands/lsp.rs | 18 +++++- helix-term/src/commands/typed.rs | 43 ++++++++++++++ helix-term/src/keymap/default.rs | 1 + helix-term/src/lib.rs | 1 + helix-term/src/make.rs | 99 ++++++++++++++++++++++++++++++++ helix-view/src/editor.rs | 4 ++ helix-view/src/lib.rs | 1 + helix-view/src/make.rs | 63 ++++++++++++++++++++ 9 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 helix-term/src/make.rs create mode 100644 helix-view/src/make.rs diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 18f24a634647..6c07fea1f98b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3,6 +3,7 @@ pub(crate) mod lsp; pub(crate) mod syntax; pub(crate) mod typed; +use crate::make::make_picker; pub use dap::*; use futures_util::FutureExt; use helix_event::status; @@ -613,6 +614,7 @@ impl MappableCommand { goto_prev_tabstop, "Goto next snippet placeholder", rotate_selections_first, "Make the first selection your primary one", rotate_selections_last, "Make the last selection your primary one", + make_cmd_picker, "MAKE PICKER", ); } @@ -5370,6 +5372,16 @@ fn rotate_selections_last(cx: &mut Context) { doc.set_selection(view.id, selection); } +fn make_cmd_picker(cx: &mut Context) { + let root = find_workspace().0; + if !root.exists() { + cx.editor.set_error("Workspace directory does not exist"); + return; + } + let picker = make_picker(cx, root); + cx.push_layer(Box::new(overlaid(picker))); +} + #[derive(Debug)] enum ReorderStrategy { RotateForward, diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 929774c7999d..cfd89471a9e9 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -32,7 +32,9 @@ use crate::{ ui::{self, overlay::overlaid, FileLocation, Picker, Popup, PromptEvent}, }; -use std::{cmp::Ordering, collections::HashSet, fmt::Display, future::Future, path::Path}; +use std::{ + cmp::Ordering, collections::HashSet, fmt::Display, future::Future, path::Path, path::PathBuf, +}; /// Gets the first language server that is attached to a document which supports a specific feature. /// If there is no configured language server that supports the feature, this displays a status message. @@ -59,12 +61,22 @@ macro_rules! language_server_with_feature { /// A wrapper around `lsp::Location` that swaps out the LSP URI for `helix_core::Uri` and adds /// the server's offset encoding. #[derive(Debug, Clone, PartialEq, Eq)] -struct Location { +pub struct Location { uri: Uri, range: lsp::Range, offset_encoding: OffsetEncoding, } +impl Location { + pub fn new(path: PathBuf, range: lsp::Range, offset_encoding: OffsetEncoding) -> Self { + Self { + uri: path.into(), + range: range, + offset_encoding: offset_encoding, + } + } +} + fn lsp_location_to_location( location: lsp::Location, offset_encoding: OffsetEncoding, @@ -109,7 +121,7 @@ fn location_to_file_location(location: &Location) -> Option { Some((path.into(), line)) } -fn jump_to_location(editor: &mut Editor, location: &Location, action: Action) { +pub fn jump_to_location(editor: &mut Editor, location: &Location, action: Action) { let (view, doc) = current!(editor); push_jump(view, doc); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 263d337a41b5..4632db1b45d1 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,8 +1,10 @@ use std::fmt::Write; use std::io::BufReader; use std::ops::{self, Deref}; +use std::str::FromStr; use crate::job::Job; +use crate::make::{self, MakeFormatType}; use super::*; @@ -2642,6 +2644,35 @@ fn noop(_cx: &mut compositor::Context, _args: Args, _event: PromptEvent) -> anyh Ok(()) } +fn make(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + let shell = cx.editor.config().shell.clone(); + let args = args.join(" "); + let make_format_type; + match MakeFormatType::from_str("rust") { + Ok(format_type) => make_format_type = format_type, + Err(_) => return Ok(()), + } + + let callback = async move { + let output = shell_impl_async(&shell, &args, None).await?; + // TODO(szulf): parsing and putting in make picker here + let call: job::Callback = Callback::EditorCompositor(Box::new( + move |editor: &mut Editor, _compositor: &mut Compositor| { + let entries = make::format(make_format_type, output.as_str()); + editor.set_status(format!("Filled make list with {} entries.", entries.len())); + }, + )); + Ok(call) + }; + cx.jobs.callback(callback); + + Ok(()) +} + /// This command accepts a single boolean --skip-visible flag and no positionals. const BUFFER_CLOSE_OTHERS_SIGNATURE: Signature = Signature { positionals: (0, Some(0)), @@ -3668,6 +3699,18 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ ..Signature::DEFAULT }, }, + TypableCommand { + name: "make", + aliases: &["mk"], + doc: "Executes the command and fills the make picker with its output.", + fun: make, + completer: SHELL_COMPLETER, + signature: Signature { + positionals: (2, Some(3)), + raw_after: Some(2), + ..Signature::DEFAULT + }, + }, ]; pub static TYPABLE_COMMAND_MAP: Lazy> = diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 5bbbd3f40429..38bab2d15c1e 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -292,6 +292,7 @@ pub fn default() -> HashMap { "C" => toggle_block_comments, "A-c" => toggle_line_comments, "?" => command_palette, + "m" => make_cmd_picker, }, "z" => { "View" "z" | "c" => align_view_center, diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index 75b67479b411..a33b7bf76578 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -10,6 +10,7 @@ pub mod events; pub mod health; pub mod job; pub mod keymap; +pub mod make; pub mod ui; use std::path::Path; diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs new file mode 100644 index 000000000000..6fa431a6c630 --- /dev/null +++ b/helix-term/src/make.rs @@ -0,0 +1,99 @@ +use crate::commands; +use crate::commands::{jump_to_location, Context}; +use crate::ui::{Picker, PickerColumn}; +use helix_view::make::{Entry, Location}; +use std::{path::PathBuf, str::FromStr}; + +fn make_location_to_location(location: &Location) -> commands::Location { + // TODO(szulf): take offet_encoding as an argument + commands::Location::new( + location.path.clone(), + location.range.clone(), + helix_lsp::OffsetEncoding::Utf8, + ) +} + +#[derive(Debug, Clone)] +pub struct MakePickerData { + root: PathBuf, +} + +type MakePicker = Picker; + +pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { + let options = cx.editor.make_list.clone().into_iter(); + + let data = MakePickerData { root: root }; + + let columns = vec![ + PickerColumn::new("path", |entry: &Entry, data: &MakePickerData| { + let path = match entry.location.path.strip_prefix(&data.root) { + Ok(path) => path.to_str(), + Err(_) => entry.location.path.to_str(), + }; + match path { + Some(str) => str.into(), + None => "".into(), + } + }), + // TODO(szulf): change value to something else + PickerColumn::new("value", |entry: &Entry, _data: &MakePickerData| { + entry.value.err_msg.clone().into() + }), + ]; + + Picker::new(columns, 0, options, data, move |cx, item, action| { + jump_to_location( + cx.editor, + &make_location_to_location(&item.location), + action, + ); + }) +} + +pub enum MakeFormatType { + Rust, + Gcc, + Clang, + Msvc, +} + +impl FromStr for MakeFormatType { + // TODO(szulf): change this later + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "rust" => Ok(MakeFormatType::Rust), + "gcc" => Ok(MakeFormatType::Gcc), + "clang" => Ok(MakeFormatType::Clang), + "msvc" => Ok(MakeFormatType::Msvc), + _ => Err(()), + } + } +} + +pub fn format_rust(_source: &str) -> Vec { + return vec![]; +} + +pub fn format_gcc(_source: &str) -> Vec { + return vec![]; +} + +pub fn format_clang(_source: &str) -> Vec { + return vec![]; +} + +pub fn format_msvc(_source: &str) -> Vec { + return vec![]; +} + +pub fn format(format_type: MakeFormatType, source: &str) -> Vec { + match format_type { + MakeFormatType::Rust => format_rust(source), + MakeFormatType::Gcc => format_gcc(source), + MakeFormatType::Clang => format_clang(source), + MakeFormatType::Msvc => format_msvc(source), + } +} diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 34854054b38f..263c6f4ffad6 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -9,6 +9,7 @@ use crate::{ handlers::Handlers, info::Info, input::KeyEvent, + make, register::Registers, theme::{self, Theme}, tree::{self, Tree}, @@ -1162,6 +1163,8 @@ pub struct Editor { pub mouse_down_range: Option, pub cursor_cache: CursorCache, + + pub make_list: make::List, } pub type Motion = Box; @@ -1283,6 +1286,7 @@ impl Editor { handlers, mouse_down_range: None, cursor_cache: CursorCache::default(), + make_list: make::List::new(), } } diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index e30a233816da..d4279dcc26c7 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -14,6 +14,7 @@ pub mod handlers; pub mod info; pub mod input; pub mod keyboard; +pub mod make; pub mod register; pub mod theme; pub mod tree; diff --git a/helix-view/src/make.rs b/helix-view/src/make.rs new file mode 100644 index 000000000000..5b3666c2362c --- /dev/null +++ b/helix-view/src/make.rs @@ -0,0 +1,63 @@ +use helix_lsp::lsp::{Position, Range}; +use std::path::PathBuf; + +// TODO(szulf): better naming cause god damn + +#[derive(Debug, Default, Clone)] +pub struct Value { + // TODO(szulf): not sure about this + pub err_msg: String, +} + +impl Value { + pub fn new(err_msg: &str) -> Self { + Self { + err_msg: err_msg.to_string(), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct Location { + pub path: PathBuf, + pub range: Range, +} + +// NOTE(szulf): would absolutely love to use helix-term::commands::Location +// but cannot access it from here whyyyyy +#[derive(Debug, Clone, Default)] +pub struct Entry { + pub location: Location, + pub value: Value, +} + +impl Entry { + pub fn new(path: PathBuf, value: Value) -> Self { + Self { + location: Location { + path: path, + range: Range::new(Position::new(2, 5), Position::new(2, 6)), + }, + value: value, + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct List { + entries: Vec, +} + +impl List { + pub fn new() -> Self { + Self::default() + } + + pub fn add(&mut self, entry: Entry) { + self.entries.push(entry); + } + + pub fn into_iter(self) -> impl Iterator { + self.entries.into_iter() + } +} From 538bb61b0e31601ace3d37191b7438c293bfd726 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:35:38 +0200 Subject: [PATCH 02/15] parsing clang and gcc output message --- helix-term/src/commands/typed.rs | 14 +- helix-term/src/make.rs | 234 ++++++++++++++++++++++++++----- helix-view/src/make.rs | 43 ++---- 3 files changed, 222 insertions(+), 69 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 4632db1b45d1..c3edfe794cd6 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2652,18 +2652,19 @@ fn make(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow: let shell = cx.editor.config().shell.clone(); let args = args.join(" "); let make_format_type; - match MakeFormatType::from_str("rust") { + match MakeFormatType::from_str("clang") { Ok(format_type) => make_format_type = format_type, Err(_) => return Ok(()), } let callback = async move { let output = shell_impl_async(&shell, &args, None).await?; - // TODO(szulf): parsing and putting in make picker here let call: job::Callback = Callback::EditorCompositor(Box::new( move |editor: &mut Editor, _compositor: &mut Compositor| { - let entries = make::format(make_format_type, output.as_str()); - editor.set_status(format!("Filled make list with {} entries.", entries.len())); + let entries = make::parse(make_format_type, output.as_str()); + let entries_count = entries.len(); + editor.make_list.set(entries); + editor.set_status(format!("Filled make list with {} entries.", entries_count)); }, )); Ok(call) @@ -3704,10 +3705,11 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ aliases: &["mk"], doc: "Executes the command and fills the make picker with its output.", fun: make, + // TODO(szulf): change this to proper signature with specyfing type of make cmd completer: SHELL_COMPLETER, signature: Signature { - positionals: (2, Some(3)), - raw_after: Some(2), + positionals: (1, Some(2)), + raw_after: Some(1), ..Signature::DEFAULT }, }, diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index 6fa431a6c630..92f5e164c1c7 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -1,21 +1,30 @@ -use crate::commands; -use crate::commands::{jump_to_location, Context}; +use crate::commands::Context; use crate::ui::{Picker, PickerColumn}; -use helix_view::make::{Entry, Location}; -use std::{path::PathBuf, str::FromStr}; - -fn make_location_to_location(location: &Location) -> commands::Location { - // TODO(szulf): take offet_encoding as an argument - commands::Location::new( - location.path.clone(), - location.range.clone(), - helix_lsp::OffsetEncoding::Utf8, - ) -} +use helix_core::Selection; +use helix_lsp::lsp::DiagnosticSeverity; +use helix_view::{ + align_view, + make::{Entry, Location}, + theme::Style, + Align, +}; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; +use tui::text::Span; + +// TODO(szulf): check the not closing error after opening logs on a non modified version of helix +// TODO(szulf): figure out how to display messages from the make_list the same way as diagnostics +// and make it togglable i think #[derive(Debug, Clone)] pub struct MakePickerData { root: PathBuf, + hint: Style, + info: Style, + warning: Style, + error: Style, } type MakePicker = Picker; @@ -23,9 +32,25 @@ type MakePicker = Picker; pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { let options = cx.editor.make_list.clone().into_iter(); - let data = MakePickerData { root: root }; + let data = MakePickerData { + root: root, + hint: cx.editor.theme.get("hint"), + info: cx.editor.theme.get("info"), + warning: cx.editor.theme.get("warning"), + error: cx.editor.theme.get("error"), + }; let columns = vec![ + PickerColumn::new("severity", |entry: &Entry, data: &MakePickerData| { + match entry.severity { + DiagnosticSeverity::HINT => Span::styled("HINT", data.hint), + DiagnosticSeverity::INFORMATION => Span::styled("INFO", data.info), + DiagnosticSeverity::WARNING => Span::styled("WARN", data.warning), + DiagnosticSeverity::ERROR => Span::styled("ERROR", data.error), + _ => Span::raw(""), + } + .into() + }), PickerColumn::new("path", |entry: &Entry, data: &MakePickerData| { let path = match entry.location.path.strip_prefix(&data.root) { Ok(path) => path.to_str(), @@ -36,18 +61,41 @@ pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { None => "".into(), } }), - // TODO(szulf): change value to something else - PickerColumn::new("value", |entry: &Entry, _data: &MakePickerData| { - entry.value.err_msg.clone().into() + PickerColumn::new("message", |entry: &Entry, _data: &MakePickerData| { + entry.msg.clone().into() }), ]; Picker::new(columns, 0, options, data, move |cx, item, action| { - jump_to_location( - cx.editor, - &make_location_to_location(&item.location), - action, - ); + // TODO(szulf): this is copied from the global_search function should i maybe pull it out? + let doc = match cx.editor.open(&item.location.path, action) { + Ok(id) => doc_mut!(cx.editor, &id), + Err(e) => { + cx.editor.set_error(format!( + "Failed to open file '{}': {}", + item.location.path.display(), + e + )); + return; + } + }; + + let line_num = item.location.line; + let view = view_mut!(cx.editor); + let text = doc.text(); + if line_num >= text.len_lines() { + cx.editor.set_error( + "The line you jumped to does not exist anymore because the file has changed.", + ); + return; + } + let start = text.line_to_char(line_num); + let end = text.line_to_char((line_num + 1).min(text.len_lines())); + + doc.set_selection(view.id, Selection::single(start, end)); + if action.align_view(view, doc.id()) { + align_view(doc, view, Align::Center); + } }) } @@ -73,27 +121,143 @@ impl FromStr for MakeFormatType { } } -pub fn format_rust(_source: &str) -> Vec { - return vec![]; +pub fn parse_rust<'a, T>(_lines: T) -> Vec +where + T: IntoIterator, +{ + todo!(); } -pub fn format_gcc(_source: &str) -> Vec { - return vec![]; +pub fn parse_gcc<'a, T>(lines: T) -> Vec +where + T: IntoIterator, +{ + // NOTE(szulf): they should always be the same + return parse_clang(lines); } -pub fn format_clang(_source: &str) -> Vec { - return vec![]; +// TODO(szulf): better naming +// TODO(szulf): make an error type? +pub fn check(s: &str) -> Result { + let mut loc = s.split(':').collect::>(); + loc.retain(|&s| s != ""); + + if loc.len() < 3 { + return Err(()); + } + + let mut loc_fixed: Vec = loc.iter().map(|s| (*s).to_string()).collect(); + // NOTE(szulf): handle paths that contain ':' + while loc_fixed.len() != 3 { + let second = loc_fixed.remove(1); + loc_fixed[0].push_str(second.as_str()); + } + + let path = Path::new(loc_fixed[0].as_str()); + if !path.exists() { + return Err(()); + } + + let line = match loc_fixed[1].parse::() { + Ok(l) => l - 1, + Err(_) => { + log::debug!("couldnt parse splits[1]: {:?}", loc_fixed[1]); + return Err(()); + } + }; + + return Ok(Location { + path: PathBuf::from(loc_fixed.remove(0)), + line: line, + }); } -pub fn format_msvc(_source: &str) -> Vec { - return vec![]; +pub fn parse_clang<'a, T>(lines: T) -> Vec +where + T: IntoIterator, +{ + // TODO(szulf): better naming + let e = lines + .into_iter() + .map(|s| s.split_whitespace().collect::>()) + .collect::>>(); + + let mut entries = Vec::new(); + + let mut message: String = String::default(); + let mut location = None; + let mut severity = DiagnosticSeverity::ERROR; + + for s in e { + let mut iter = s.into_iter().peekable(); + + // TODO(szulf): the naming man + // l loc locat location + // beautiful + while let Some(l) = iter.next() { + let loc = check(l); + match loc { + Ok(locat) => { + location = Some(locat); + + if let Some(sever) = iter.peek() { + match *sever { + "warning:" => { + severity = DiagnosticSeverity::WARNING; + iter.next(); + } + "note:" => { + severity = DiagnosticSeverity::HINT; + iter.next(); + } + "error:" => { + severity = DiagnosticSeverity::ERROR; + iter.next(); + } + _ => severity = DiagnosticSeverity::ERROR, + } + } + + message.clear(); + } + Err(_) => { + if message.len() != 0 { + message.push_str(" "); + } + message.push_str(l); + } + } + } + + match location { + Some(loc) => { + entries.push(Entry::new(loc, message.as_str(), severity)); + } + None => {} + } + + message.clear(); + severity = DiagnosticSeverity::ERROR; + location = None; + } + + entries +} + +pub fn parse_msvc<'a, T>(_lines: T) -> Vec +where + T: IntoIterator, +{ + todo!(); } -pub fn format(format_type: MakeFormatType, source: &str) -> Vec { +pub fn parse(format_type: MakeFormatType, source: &str) -> Vec { + let lines = source.lines(); + match format_type { - MakeFormatType::Rust => format_rust(source), - MakeFormatType::Gcc => format_gcc(source), - MakeFormatType::Clang => format_clang(source), - MakeFormatType::Msvc => format_msvc(source), + MakeFormatType::Rust => parse_rust(lines), + MakeFormatType::Gcc => parse_gcc(lines), + MakeFormatType::Clang => parse_clang(lines), + MakeFormatType::Msvc => parse_msvc(lines), } } diff --git a/helix-view/src/make.rs b/helix-view/src/make.rs index 5b3666c2362c..9835951a44a4 100644 --- a/helix-view/src/make.rs +++ b/helix-view/src/make.rs @@ -1,44 +1,27 @@ -use helix_lsp::lsp::{Position, Range}; +use helix_lsp::lsp::{DiagnosticSeverity, Range}; use std::path::PathBuf; -// TODO(szulf): better naming cause god damn - -#[derive(Debug, Default, Clone)] -pub struct Value { - // TODO(szulf): not sure about this - pub err_msg: String, -} - -impl Value { - pub fn new(err_msg: &str) -> Self { - Self { - err_msg: err_msg.to_string(), - } - } -} - #[derive(Debug, Clone, Default)] pub struct Location { pub path: PathBuf, - pub range: Range, + pub line: usize, } -// NOTE(szulf): would absolutely love to use helix-term::commands::Location -// but cannot access it from here whyyyyy -#[derive(Debug, Clone, Default)] +// TODO(szulf): maybe add a way for entries to reference other entries +// so that things like note: can actually be linked back to the original error +#[derive(Debug, Clone)] pub struct Entry { pub location: Location, - pub value: Value, + pub msg: String, + pub severity: DiagnosticSeverity, } impl Entry { - pub fn new(path: PathBuf, value: Value) -> Self { + pub fn new(location: Location, msg: &str, severity: DiagnosticSeverity) -> Self { Self { - location: Location { - path: path, - range: Range::new(Position::new(2, 5), Position::new(2, 6)), - }, - value: value, + location: location, + msg: msg.to_string(), + severity: severity, } } } @@ -60,4 +43,8 @@ impl List { pub fn into_iter(self) -> impl Iterator { self.entries.into_iter() } + + pub fn set(&mut self, entries: Vec) { + self.entries = entries; + } } From 3a4ffb6954858e4787e03e4d6c0bf5e4c0403145 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Sun, 27 Jul 2025 00:25:54 +0200 Subject: [PATCH 03/15] added a format flag to determine which parser to use --- helix-term/src/commands/typed.rs | 25 ++++++++++++++--------- helix-term/src/make.rs | 35 +++++++++++++++++--------------- helix-view/src/make.rs | 3 ++- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index c3edfe794cd6..6f03c4e79e43 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,7 +1,6 @@ use std::fmt::Write; use std::io::BufReader; use std::ops::{self, Deref}; -use std::str::FromStr; use crate::job::Job; use crate::make::{self, MakeFormatType}; @@ -2649,14 +2648,15 @@ fn make(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow: return Ok(()); } - let shell = cx.editor.config().shell.clone(); - let args = args.join(" "); let make_format_type; - match MakeFormatType::from_str("clang") { - Ok(format_type) => make_format_type = format_type, - Err(_) => return Ok(()), + match args.get_flag("format") { + Some(flag) => make_format_type = MakeFormatType::from(flag), + None => make_format_type = MakeFormatType::Default, } + let shell = cx.editor.config().shell.clone(); + let args = args.join(" "); + let callback = async move { let output = shell_impl_async(&shell, &args, None).await?; let call: job::Callback = Callback::EditorCompositor(Box::new( @@ -3705,12 +3705,17 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ aliases: &["mk"], doc: "Executes the command and fills the make picker with its output.", fun: make, - // TODO(szulf): change this to proper signature with specyfing type of make cmd completer: SHELL_COMPLETER, signature: Signature { - positionals: (1, Some(2)), - raw_after: Some(1), - ..Signature::DEFAULT + flags: &[ + Flag { + name: "format", + alias: Some('f'), + doc: "sets the make output format", + completions: Some(&["rust", "gcc", "clang", "msvc"]), + }, + ], + ..SHELL_SIGNATURE }, }, ]; diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index 92f5e164c1c7..fe41fff7bbe2 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -8,15 +8,12 @@ use helix_view::{ theme::Style, Align, }; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; +use std::path::{Path, PathBuf}; use tui::text::Span; // TODO(szulf): check the not closing error after opening logs on a non modified version of helix // TODO(szulf): figure out how to display messages from the make_list the same way as diagnostics -// and make it togglable i think +// and make it togglable in the config i think #[derive(Debug, Clone)] pub struct MakePickerData { @@ -100,27 +97,32 @@ pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { } pub enum MakeFormatType { + Default, Rust, Gcc, Clang, Msvc, } -impl FromStr for MakeFormatType { - // TODO(szulf): change this later - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "rust" => Ok(MakeFormatType::Rust), - "gcc" => Ok(MakeFormatType::Gcc), - "clang" => Ok(MakeFormatType::Clang), - "msvc" => Ok(MakeFormatType::Msvc), - _ => Err(()), +impl From<&str> for MakeFormatType { + fn from(value: &str) -> Self { + match value { + "rust" => MakeFormatType::Rust, + "gcc" => MakeFormatType::Rust, + "clang" => MakeFormatType::Clang, + "msvc" => MakeFormatType::Msvc, + _ => MakeFormatType::Default, } } } +pub fn parse_default<'a, T>(_lines: T) -> Vec +where + T: IntoIterator, +{ + todo!(); +} + pub fn parse_rust<'a, T>(_lines: T) -> Vec where T: IntoIterator, @@ -255,6 +257,7 @@ pub fn parse(format_type: MakeFormatType, source: &str) -> Vec { let lines = source.lines(); match format_type { + MakeFormatType::Default => parse_default(lines), MakeFormatType::Rust => parse_rust(lines), MakeFormatType::Gcc => parse_gcc(lines), MakeFormatType::Clang => parse_clang(lines), diff --git a/helix-view/src/make.rs b/helix-view/src/make.rs index 9835951a44a4..da15175d5519 100644 --- a/helix-view/src/make.rs +++ b/helix-view/src/make.rs @@ -1,4 +1,4 @@ -use helix_lsp::lsp::{DiagnosticSeverity, Range}; +use helix_lsp::lsp::DiagnosticSeverity; use std::path::PathBuf; #[derive(Debug, Clone, Default)] @@ -40,6 +40,7 @@ impl List { self.entries.push(entry); } + // TODO(szulf): change this to implementing the IntoIterator trait instead pub fn into_iter(self) -> impl Iterator { self.entries.into_iter() } From 166a52027943520f2fd8c948562ded1b072495a3 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:23:20 +0200 Subject: [PATCH 04/15] minor make list changes change visibility of parsing functions change into_iter function to IntoIterator trait --- helix-term/src/commands.rs | 11 +++++++---- helix-term/src/commands/typed.rs | 9 ++++----- helix-term/src/make.rs | 17 +++++++++-------- helix-view/src/make.rs | 15 ++++++++++----- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6c07fea1f98b..cc6323288932 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5374,11 +5374,14 @@ fn rotate_selections_last(cx: &mut Context) { fn make_cmd_picker(cx: &mut Context) { let root = find_workspace().0; - if !root.exists() { - cx.editor.set_error("Workspace directory does not exist"); - return; + + let picker; + if root.exists() { + picker = make_picker(cx, root); + } else { + picker = make_picker(cx, PathBuf::new()); } - let picker = make_picker(cx, root); + cx.push_layer(Box::new(overlaid(picker))); } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 6f03c4e79e43..34a681d361b6 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2648,11 +2648,10 @@ fn make(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow: return Ok(()); } - let make_format_type; - match args.get_flag("format") { - Some(flag) => make_format_type = MakeFormatType::from(flag), - None => make_format_type = MakeFormatType::Default, - } + let make_format_type = match args.get_flag("format") { + Some(flag) => MakeFormatType::from(flag), + None => MakeFormatType::Default, + }; let shell = cx.editor.config().shell.clone(); let args = args.join(" "); diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index fe41fff7bbe2..6306e3a1d811 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -96,6 +96,7 @@ pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { }) } +// TODO(szulf): dont really see the point of this enum honestly pub enum MakeFormatType { Default, Rust, @@ -116,31 +117,31 @@ impl From<&str> for MakeFormatType { } } -pub fn parse_default<'a, T>(_lines: T) -> Vec +fn parse_default<'a, T>(_lines: T) -> Vec where T: IntoIterator, { todo!(); } -pub fn parse_rust<'a, T>(_lines: T) -> Vec +fn parse_rust<'a, T>(_lines: T) -> Vec where T: IntoIterator, { todo!(); } -pub fn parse_gcc<'a, T>(lines: T) -> Vec +fn parse_gcc<'a, T>(lines: T) -> Vec where T: IntoIterator, { - // NOTE(szulf): they should always be the same + // NOTE(szulf): they SHOULD always be the same return parse_clang(lines); } // TODO(szulf): better naming // TODO(szulf): make an error type? -pub fn check(s: &str) -> Result { +fn check(s: &str) -> Result { let mut loc = s.split(':').collect::>(); loc.retain(|&s| s != ""); @@ -150,7 +151,7 @@ pub fn check(s: &str) -> Result { let mut loc_fixed: Vec = loc.iter().map(|s| (*s).to_string()).collect(); // NOTE(szulf): handle paths that contain ':' - while loc_fixed.len() != 3 { + while loc_fixed.len() > 3 { let second = loc_fixed.remove(1); loc_fixed[0].push_str(second.as_str()); } @@ -174,7 +175,7 @@ pub fn check(s: &str) -> Result { }); } -pub fn parse_clang<'a, T>(lines: T) -> Vec +fn parse_clang<'a, T>(lines: T) -> Vec where T: IntoIterator, { @@ -246,7 +247,7 @@ where entries } -pub fn parse_msvc<'a, T>(_lines: T) -> Vec +fn parse_msvc<'a, T>(_lines: T) -> Vec where T: IntoIterator, { diff --git a/helix-view/src/make.rs b/helix-view/src/make.rs index da15175d5519..7b1d917f5be1 100644 --- a/helix-view/src/make.rs +++ b/helix-view/src/make.rs @@ -40,12 +40,17 @@ impl List { self.entries.push(entry); } - // TODO(szulf): change this to implementing the IntoIterator trait instead - pub fn into_iter(self) -> impl Iterator { - self.entries.into_iter() - } - pub fn set(&mut self, entries: Vec) { self.entries = entries; } } + +impl IntoIterator for List { + type Item = Entry; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } +} From d16a3f515fcf5d7d318e59b76cf0e08428438366 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:23:43 +0200 Subject: [PATCH 05/15] pull out goto_location from global_search --- helix-term/src/commands.rs | 60 ++++++++++++++++++++++---------------- helix-term/src/make.rs | 32 ++------------------ 2 files changed, 37 insertions(+), 55 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index cc6323288932..491a718980a1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2450,6 +2450,40 @@ fn make_search_word_bounded(cx: &mut Context) { } } +// TODO(szulf): is there some more global function like this that doesnt depend on lsp stuff? +pub fn goto_location( + cx: &mut compositor::Context, + path: &PathBuf, + line_num: &usize, + action: Action, +) { + let doc = match cx.editor.open(path, action) { + Ok(id) => doc_mut!(cx.editor, &id), + Err(e) => { + cx.editor + .set_error(format!("Failed to open file '{}': {}", path.display(), e)); + return; + } + }; + + let line_num = *line_num; + let view = view_mut!(cx.editor); + let text = doc.text(); + if line_num >= text.len_lines() { + cx.editor.set_error( + "The line you jumped to does not exist anymore because the file has changed.", + ); + return; + } + let start = text.line_to_char(line_num); + let end = text.line_to_char((line_num + 1).min(text.len_lines())); + + doc.set_selection(view.id, Selection::single(start, end)); + if action.align_view(view, doc.id()) { + align_view(doc, view, Align::Center); + } +} + fn global_search(cx: &mut Context) { #[derive(Debug)] struct FileResult { @@ -2644,31 +2678,7 @@ fn global_search(cx: &mut Context) { [], config, move |cx, FileResult { path, line_num, .. }, action| { - let doc = match cx.editor.open(path, action) { - Ok(id) => doc_mut!(cx.editor, &id), - Err(e) => { - cx.editor - .set_error(format!("Failed to open file '{}': {}", path.display(), e)); - return; - } - }; - - let line_num = *line_num; - let view = view_mut!(cx.editor); - let text = doc.text(); - if line_num >= text.len_lines() { - cx.editor.set_error( - "The line you jumped to does not exist anymore because the file has changed.", - ); - return; - } - let start = text.line_to_char(line_num); - let end = text.line_to_char((line_num + 1).min(text.len_lines())); - - doc.set_selection(view.id, Selection::single(start, end)); - if action.align_view(view, doc.id()) { - align_view(doc, view, Align::Center); - } + goto_location(cx, path, line_num, action); }, ) .with_preview(|_editor, FileResult { path, line_num, .. }| { diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index 6306e3a1d811..47c4e560e6c0 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -1,4 +1,4 @@ -use crate::commands::Context; +use crate::commands::{goto_location, Context}; use crate::ui::{Picker, PickerColumn}; use helix_core::Selection; use helix_lsp::lsp::DiagnosticSeverity; @@ -64,35 +64,7 @@ pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { ]; Picker::new(columns, 0, options, data, move |cx, item, action| { - // TODO(szulf): this is copied from the global_search function should i maybe pull it out? - let doc = match cx.editor.open(&item.location.path, action) { - Ok(id) => doc_mut!(cx.editor, &id), - Err(e) => { - cx.editor.set_error(format!( - "Failed to open file '{}': {}", - item.location.path.display(), - e - )); - return; - } - }; - - let line_num = item.location.line; - let view = view_mut!(cx.editor); - let text = doc.text(); - if line_num >= text.len_lines() { - cx.editor.set_error( - "The line you jumped to does not exist anymore because the file has changed.", - ); - return; - } - let start = text.line_to_char(line_num); - let end = text.line_to_char((line_num + 1).min(text.len_lines())); - - doc.set_selection(view.id, Selection::single(start, end)); - if action.align_view(view, doc.id()) { - align_view(doc, view, Align::Center); - } + goto_location(cx, &item.location.path, &item.location.line, action); }) } From bd64bf10e3336e3e2dab0440c185002a3c05b294 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:25:58 +0200 Subject: [PATCH 06/15] improved naming in make list functions --- helix-term/src/commands.rs | 9 +--- helix-term/src/make.rs | 92 +++++++++++++++----------------------- helix-view/src/make.rs | 4 +- 3 files changed, 40 insertions(+), 65 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 491a718980a1..f59f26cffa9f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -5384,14 +5384,7 @@ fn rotate_selections_last(cx: &mut Context) { fn make_cmd_picker(cx: &mut Context) { let root = find_workspace().0; - - let picker; - if root.exists() { - picker = make_picker(cx, root); - } else { - picker = make_picker(cx, PathBuf::new()); - } - + let picker = make_picker(cx, root); cx.push_layer(Box::new(overlaid(picker))); } diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index 47c4e560e6c0..b6174c0429f2 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -1,19 +1,16 @@ use crate::commands::{goto_location, Context}; use crate::ui::{Picker, PickerColumn}; -use helix_core::Selection; use helix_lsp::lsp::DiagnosticSeverity; use helix_view::{ - align_view, make::{Entry, Location}, theme::Style, - Align, }; use std::path::{Path, PathBuf}; use tui::text::Span; // TODO(szulf): check the not closing error after opening logs on a non modified version of helix // TODO(szulf): figure out how to display messages from the make_list the same way as diagnostics -// and make it togglable in the config i think +// and make it togglable in the config i think(off by default i think) #[derive(Debug, Clone)] pub struct MakePickerData { @@ -69,6 +66,7 @@ pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { } // TODO(szulf): dont really see the point of this enum honestly +#[derive(Debug)] pub enum MakeFormatType { Default, Rust, @@ -81,7 +79,7 @@ impl From<&str> for MakeFormatType { fn from(value: &str) -> Self { match value { "rust" => MakeFormatType::Rust, - "gcc" => MakeFormatType::Rust, + "gcc" => MakeFormatType::Gcc, "clang" => MakeFormatType::Clang, "msvc" => MakeFormatType::Msvc, _ => MakeFormatType::Default, @@ -103,117 +101,102 @@ where todo!(); } -fn parse_gcc<'a, T>(lines: T) -> Vec -where - T: IntoIterator, -{ - // NOTE(szulf): they SHOULD always be the same - return parse_clang(lines); -} - -// TODO(szulf): better naming // TODO(szulf): make an error type? -fn check(s: &str) -> Result { - let mut loc = s.split(':').collect::>(); - loc.retain(|&s| s != ""); - - if loc.len() < 3 { +fn get_location_from_token(token: &str, col_amount: usize) -> Result { + let mut loc = token + .split(':') + .map(|s| (*s).to_string()) + .collect::>(); + loc.retain(|token| token != ""); + + if loc.len() < col_amount { return Err(()); } - let mut loc_fixed: Vec = loc.iter().map(|s| (*s).to_string()).collect(); // NOTE(szulf): handle paths that contain ':' - while loc_fixed.len() > 3 { - let second = loc_fixed.remove(1); - loc_fixed[0].push_str(second.as_str()); + while loc.len() > col_amount { + let second = loc.remove(1); + loc[0].push_str(second.as_str()); } - let path = Path::new(loc_fixed[0].as_str()); + let path = PathBuf::from(loc.remove(0)); if !path.exists() { return Err(()); } - let line = match loc_fixed[1].parse::() { + let line = match loc[0].parse::() { Ok(l) => l - 1, Err(_) => { - log::debug!("couldnt parse splits[1]: {:?}", loc_fixed[1]); return Err(()); } }; return Ok(Location { - path: PathBuf::from(loc_fixed.remove(0)), + path: path, line: line, }); } -fn parse_clang<'a, T>(lines: T) -> Vec +fn parse_gcc<'a, T>(lines: T) -> Vec where T: IntoIterator, { - // TODO(szulf): better naming - let e = lines + let tokenized_lines = lines .into_iter() .map(|s| s.split_whitespace().collect::>()) .collect::>>(); let mut entries = Vec::new(); - let mut message: String = String::default(); - let mut location = None; - let mut severity = DiagnosticSeverity::ERROR; + for line_tokens in tokenized_lines { + let mut message = String::new(); + let mut location = None; + let mut severity = DiagnosticSeverity::ERROR; - for s in e { - let mut iter = s.into_iter().peekable(); + let mut token_iter = line_tokens.into_iter().peekable(); - // TODO(szulf): the naming man - // l loc locat location - // beautiful - while let Some(l) = iter.next() { - let loc = check(l); - match loc { - Ok(locat) => { - location = Some(locat); + while let Some(token) = token_iter.next() { + let location_result = get_location_from_token(token, 3); + match location_result { + Ok(loc) => { + location = Some(loc); - if let Some(sever) = iter.peek() { + if let Some(sever) = token_iter.peek() { match *sever { "warning:" => { severity = DiagnosticSeverity::WARNING; - iter.next(); + token_iter.next(); } "note:" => { severity = DiagnosticSeverity::HINT; - iter.next(); + token_iter.next(); } "error:" => { severity = DiagnosticSeverity::ERROR; - iter.next(); + token_iter.next(); } _ => severity = DiagnosticSeverity::ERROR, } } + // NOTE(szulf): discard any messages before the file location message.clear(); } Err(_) => { if message.len() != 0 { message.push_str(" "); } - message.push_str(l); + message.push_str(token); } } } match location { Some(loc) => { - entries.push(Entry::new(loc, message.as_str(), severity)); + entries.push(Entry::new(loc, message, severity)); } None => {} } - - message.clear(); - severity = DiagnosticSeverity::ERROR; - location = None; } entries @@ -232,8 +215,7 @@ pub fn parse(format_type: MakeFormatType, source: &str) -> Vec { match format_type { MakeFormatType::Default => parse_default(lines), MakeFormatType::Rust => parse_rust(lines), - MakeFormatType::Gcc => parse_gcc(lines), - MakeFormatType::Clang => parse_clang(lines), + MakeFormatType::Gcc | MakeFormatType::Clang => parse_gcc(lines), MakeFormatType::Msvc => parse_msvc(lines), } } diff --git a/helix-view/src/make.rs b/helix-view/src/make.rs index 7b1d917f5be1..95f0b029d6d9 100644 --- a/helix-view/src/make.rs +++ b/helix-view/src/make.rs @@ -17,10 +17,10 @@ pub struct Entry { } impl Entry { - pub fn new(location: Location, msg: &str, severity: DiagnosticSeverity) -> Self { + pub fn new(location: Location, msg: String, severity: DiagnosticSeverity) -> Self { Self { location: location, - msg: msg.to_string(), + msg: msg, severity: severity, } } From c174edb521902dedbde880e593d7189a63799502 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Sat, 23 Aug 2025 13:16:50 +0200 Subject: [PATCH 07/15] parsing compiler errors via regex in make list --- Cargo.lock | 1 + helix-term/Cargo.toml | 2 + helix-term/src/make.rs | 92 +++++++++++++++++------------------------- 3 files changed, 40 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8dc602e04f3d..0f1ec0db4d3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1554,6 +1554,7 @@ dependencies = [ "once_cell", "open", "pulldown-cmark", + "regex", "same-file", "serde", "serde_json", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 68526a2f15b1..7c06c7a7d881 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -93,6 +93,8 @@ grep-searcher = "0.1.14" dashmap = "6.0" +regex = "1" + [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } libc = "0.2.175" diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index b6174c0429f2..b74ab2615fe2 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -5,7 +5,8 @@ use helix_view::{ make::{Entry, Location}, theme::Style, }; -use std::path::{Path, PathBuf}; +use regex::Regex; +use std::path::PathBuf; use tui::text::Span; // TODO(szulf): check the not closing error after opening logs on a non modified version of helix @@ -141,65 +142,46 @@ fn parse_gcc<'a, T>(lines: T) -> Vec where T: IntoIterator, { - let tokenized_lines = lines - .into_iter() - .map(|s| s.split_whitespace().collect::>()) - .collect::>>(); - - let mut entries = Vec::new(); - - for line_tokens in tokenized_lines { - let mut message = String::new(); - let mut location = None; - let mut severity = DiagnosticSeverity::ERROR; - - let mut token_iter = line_tokens.into_iter().peekable(); - - while let Some(token) = token_iter.next() { - let location_result = get_location_from_token(token, 3); - match location_result { - Ok(loc) => { - location = Some(loc); - - if let Some(sever) = token_iter.peek() { - match *sever { - "warning:" => { - severity = DiagnosticSeverity::WARNING; - token_iter.next(); - } - "note:" => { - severity = DiagnosticSeverity::HINT; - token_iter.next(); - } - "error:" => { - severity = DiagnosticSeverity::ERROR; - token_iter.next(); - } - _ => severity = DiagnosticSeverity::ERROR, - } - } - - // NOTE(szulf): discard any messages before the file location - message.clear(); - } - Err(_) => { - if message.len() != 0 { - message.push_str(" "); - } - message.push_str(token); - } - } - } - - match location { - Some(loc) => { - entries.push(Entry::new(loc, message, severity)); + let regex = Regex::new( + r"^(?P[^:\n\s]+)(?::(?P\d+))?(?::\d+)?(?::\([^)]+\))?:\s(?Perror|warning|note)?:?\s?(?P.+)$", + ) + .unwrap(); + + let mut results = Vec::new(); + + for line in lines { + let capture = regex.captures(line); + match capture { + Some(cap) => { + let Some(path) = cap.name("path") else { + continue; + }; + let Some(line) = cap.name("line") else { + continue; + }; + + let location = Location { + path: path.as_str().into(), + line: line.as_str().parse::().unwrap() - 1, + }; + + let severity = match cap.name("severity").map(|c| c.as_str()).unwrap_or_default() { + "warning" => DiagnosticSeverity::WARNING, + "note" => DiagnosticSeverity::HINT, + "error" | _ => DiagnosticSeverity::ERROR, + }; + + let Some(message) = cap.name("message") else { + continue; + }; + + results.push(Entry::new(location, message.as_str().to_owned(), severity)); } None => {} } } - entries + results } fn parse_msvc<'a, T>(_lines: T) -> Vec From 366a02fc2a19ea9c93056057574c274f8f71ffce Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:07:23 +0200 Subject: [PATCH 08/15] rework of regex parsing in make list --- helix-term/src/make.rs | 136 +++++++++++++---------------------------- 1 file changed, 43 insertions(+), 93 deletions(-) diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index b74ab2615fe2..612b541874e0 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -5,7 +5,7 @@ use helix_view::{ make::{Entry, Location}, theme::Style, }; -use regex::Regex; +use regex::RegexBuilder; use std::path::PathBuf; use tui::text::Span; @@ -88,116 +88,66 @@ impl From<&str> for MakeFormatType { } } -fn parse_default<'a, T>(_lines: T) -> Vec -where - T: IntoIterator, -{ - todo!(); -} +fn parse_with_regex(source: &str, regex: &str) -> Vec { + let regex = RegexBuilder::new(regex).multi_line(true).build().unwrap(); -fn parse_rust<'a, T>(_lines: T) -> Vec -where - T: IntoIterator, -{ - todo!(); -} + let mut results = Vec::new(); -// TODO(szulf): make an error type? -fn get_location_from_token(token: &str, col_amount: usize) -> Result { - let mut loc = token - .split(':') - .map(|s| (*s).to_string()) - .collect::>(); - loc.retain(|token| token != ""); + for cap in regex.captures_iter(source) { + log::debug!("capture: {:?}", cap); - if loc.len() < col_amount { - return Err(()); - } + let Some(path) = cap.name("path") else { + continue; + }; + let Some(line) = cap.name("line") else { + continue; + }; - // NOTE(szulf): handle paths that contain ':' - while loc.len() > col_amount { - let second = loc.remove(1); - loc[0].push_str(second.as_str()); - } + let location = Location { + path: path.as_str().into(), + line: line.as_str().parse::().unwrap() - 1, + }; - let path = PathBuf::from(loc.remove(0)); - if !path.exists() { - return Err(()); + let severity = match cap.name("severity").map(|c| c.as_str()).unwrap_or_default() { + "warning" => DiagnosticSeverity::WARNING, + "note" => DiagnosticSeverity::HINT, + "error" | _ => DiagnosticSeverity::ERROR, + }; + + let Some(message) = cap.name("message") else { + continue; + }; + + results.push(Entry::new(location, message.as_str().to_owned(), severity)); } - let line = match loc[0].parse::() { - Ok(l) => l - 1, - Err(_) => { - return Err(()); - } - }; + results +} - return Ok(Location { - path: path, - line: line, - }); +fn parse_default(_source: &str) -> Vec { + todo!(); +} + +fn parse_rust(_source: &str) -> Vec { + todo!(); } -fn parse_gcc<'a, T>(lines: T) -> Vec -where - T: IntoIterator, -{ - let regex = Regex::new( +fn parse_gcc(source: &str) -> Vec { + parse_with_regex( + source, r"^(?P[^:\n\s]+)(?::(?P\d+))?(?::\d+)?(?::\([^)]+\))?:\s(?Perror|warning|note)?:?\s?(?P.+)$", ) - .unwrap(); - - let mut results = Vec::new(); - - for line in lines { - let capture = regex.captures(line); - match capture { - Some(cap) => { - let Some(path) = cap.name("path") else { - continue; - }; - let Some(line) = cap.name("line") else { - continue; - }; - - let location = Location { - path: path.as_str().into(), - line: line.as_str().parse::().unwrap() - 1, - }; - - let severity = match cap.name("severity").map(|c| c.as_str()).unwrap_or_default() { - "warning" => DiagnosticSeverity::WARNING, - "note" => DiagnosticSeverity::HINT, - "error" | _ => DiagnosticSeverity::ERROR, - }; - - let Some(message) = cap.name("message") else { - continue; - }; - - results.push(Entry::new(location, message.as_str().to_owned(), severity)); - } - None => {} - } - } - - results } -fn parse_msvc<'a, T>(_lines: T) -> Vec -where - T: IntoIterator, -{ +fn parse_msvc(_source: &str) -> Vec { todo!(); } pub fn parse(format_type: MakeFormatType, source: &str) -> Vec { - let lines = source.lines(); - match format_type { - MakeFormatType::Default => parse_default(lines), - MakeFormatType::Rust => parse_rust(lines), - MakeFormatType::Gcc | MakeFormatType::Clang => parse_gcc(lines), - MakeFormatType::Msvc => parse_msvc(lines), + MakeFormatType::Default => parse_default(source), + MakeFormatType::Rust => parse_rust(source), + MakeFormatType::Gcc | MakeFormatType::Clang => parse_gcc(source), + MakeFormatType::Msvc => parse_msvc(source), } } From 4a7615014754944edcbdb6b3eca1572ab91394a2 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:08:00 +0200 Subject: [PATCH 09/15] add support for rust parsing in make list --- helix-term/src/make.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index 612b541874e0..a464f64ebce3 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -128,8 +128,11 @@ fn parse_default(_source: &str) -> Vec { todo!(); } -fn parse_rust(_source: &str) -> Vec { - todo!(); +fn parse_rust(source: &str) -> Vec { + parse_with_regex( + source, + r"^(?Phelp|warning|error)(?:\[.+\])?:?\s(?P.+)\n\s+-->\s(?P[^:\n\s]+):(?P\d+):(\d+)$", + ) } fn parse_gcc(source: &str) -> Vec { From 8cefde9c72f301e111fb715b32f27aa3dd754af4 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:53:11 +0200 Subject: [PATCH 10/15] add support for msvc in make list --- helix-term/src/make.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index a464f64ebce3..36eafbfbe624 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -12,6 +12,7 @@ use tui::text::Span; // TODO(szulf): check the not closing error after opening logs on a non modified version of helix // TODO(szulf): figure out how to display messages from the make_list the same way as diagnostics // and make it togglable in the config i think(off by default i think) +// TODO(szulf): write the return code(success/fail) while writing 'filled make list with ? entries' #[derive(Debug, Clone)] pub struct MakePickerData { @@ -94,8 +95,6 @@ fn parse_with_regex(source: &str, regex: &str) -> Vec { let mut results = Vec::new(); for cap in regex.captures_iter(source) { - log::debug!("capture: {:?}", cap); - let Some(path) = cap.name("path") else { continue; }; @@ -110,7 +109,7 @@ fn parse_with_regex(source: &str, regex: &str) -> Vec { let severity = match cap.name("severity").map(|c| c.as_str()).unwrap_or_default() { "warning" => DiagnosticSeverity::WARNING, - "note" => DiagnosticSeverity::HINT, + "note" | "help" => DiagnosticSeverity::HINT, "error" | _ => DiagnosticSeverity::ERROR, }; @@ -142,8 +141,12 @@ fn parse_gcc(source: &str) -> Vec { ) } -fn parse_msvc(_source: &str) -> Vec { - todo!(); +// TODO(szulf): test this +fn parse_msvc(source: &str) -> Vec { + parse_with_regex( + source, + r"^<(?P.+)>\((?P\d+)\):\s(?Perror|warning|note)(?:[^:]+)?:\s(?P.+)$", + ) } pub fn parse(format_type: MakeFormatType, source: &str) -> Vec { From eeddb74a74268372c5709c809268415807a19872 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:54:52 +0200 Subject: [PATCH 11/15] add support for default parsing in make list --- helix-term/src/make.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index 36eafbfbe624..693159e32853 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -123,10 +123,6 @@ fn parse_with_regex(source: &str, regex: &str) -> Vec { results } -fn parse_default(_source: &str) -> Vec { - todo!(); -} - fn parse_rust(source: &str) -> Vec { parse_with_regex( source, @@ -149,6 +145,16 @@ fn parse_msvc(source: &str) -> Vec { ) } +fn parse_default(source: &str) -> Vec { + let mut entries = Vec::new(); + + entries.append(&mut parse_rust(source)); + entries.append(&mut parse_gcc(source)); + entries.append(&mut parse_msvc(source)); + + entries +} + pub fn parse(format_type: MakeFormatType, source: &str) -> Vec { match format_type { MakeFormatType::Default => parse_default(source), From 4fe9d376e069fee3348675df8082f3818ec9fe2a Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:39:44 +0200 Subject: [PATCH 12/15] change to Severity from DiagnosticSeverity in make list Entry type plus impl Index traits for list --- helix-term/src/make.rs | 18 +++++++++--------- helix-view/src/make.rs | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index 693159e32853..f881bfdbc49d 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -1,6 +1,6 @@ use crate::commands::{goto_location, Context}; use crate::ui::{Picker, PickerColumn}; -use helix_lsp::lsp::DiagnosticSeverity; +use helix_core::diagnostic::Severity; use helix_view::{ make::{Entry, Location}, theme::Style, @@ -13,6 +13,7 @@ use tui::text::Span; // TODO(szulf): figure out how to display messages from the make_list the same way as diagnostics // and make it togglable in the config i think(off by default i think) // TODO(szulf): write the return code(success/fail) while writing 'filled make list with ? entries' +// TODO(szulf): add keybindings for going to next/prev item in make list #[derive(Debug, Clone)] pub struct MakePickerData { @@ -39,11 +40,10 @@ pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { let columns = vec![ PickerColumn::new("severity", |entry: &Entry, data: &MakePickerData| { match entry.severity { - DiagnosticSeverity::HINT => Span::styled("HINT", data.hint), - DiagnosticSeverity::INFORMATION => Span::styled("INFO", data.info), - DiagnosticSeverity::WARNING => Span::styled("WARN", data.warning), - DiagnosticSeverity::ERROR => Span::styled("ERROR", data.error), - _ => Span::raw(""), + Severity::Hint => Span::styled("HINT", data.hint), + Severity::Info => Span::styled("INFO", data.info), + Severity::Warning => Span::styled("WARN", data.warning), + Severity::Error => Span::styled("ERROR", data.error), } .into() }), @@ -108,9 +108,9 @@ fn parse_with_regex(source: &str, regex: &str) -> Vec { }; let severity = match cap.name("severity").map(|c| c.as_str()).unwrap_or_default() { - "warning" => DiagnosticSeverity::WARNING, - "note" | "help" => DiagnosticSeverity::HINT, - "error" | _ => DiagnosticSeverity::ERROR, + "warning" => Severity::Warning, + "note" | "help" => Severity::Hint, + "error" | _ => Severity::Error, }; let Some(message) = cap.name("message") else { diff --git a/helix-view/src/make.rs b/helix-view/src/make.rs index 95f0b029d6d9..12837658d9c4 100644 --- a/helix-view/src/make.rs +++ b/helix-view/src/make.rs @@ -1,4 +1,5 @@ -use helix_lsp::lsp::DiagnosticSeverity; +use helix_core::diagnostic::Severity; +use std::ops::{Index, IndexMut}; use std::path::PathBuf; #[derive(Debug, Clone, Default)] @@ -13,11 +14,11 @@ pub struct Location { pub struct Entry { pub location: Location, pub msg: String, - pub severity: DiagnosticSeverity, + pub severity: Severity, } impl Entry { - pub fn new(location: Location, msg: String, severity: DiagnosticSeverity) -> Self { + pub fn new(location: Location, msg: String, severity: Severity) -> Self { Self { location: location, msg: msg, @@ -43,14 +44,42 @@ impl List { pub fn set(&mut self, entries: Vec) { self.entries = entries; } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } +} + +impl Index for List { + type Output = Entry; + + fn index(&self, index: usize) -> &Self::Output { + &self.entries[index] + } +} + +impl IndexMut for List { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.entries[index] + } } impl IntoIterator for List { type Item = Entry; - type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.entries.into_iter() } } + +// TODO(szulf): dont know if this is the right way to iterate over this collection without +// consuming it +impl<'a> IntoIterator for &'a List { + type Item = &'a Entry; + type IntoIter = std::slice::Iter<'a, Entry>; + + fn into_iter(self) -> Self::IntoIter { + self.entries.iter() + } +} From 762f7d91c46f846614974d4bf4e1c5d5ec9bbe18 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:40:32 +0200 Subject: [PATCH 13/15] add underline highlighting to entries from make list --- helix-term/src/commands/typed.rs | 5 ++- helix-term/src/ui/editor.rs | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 34a681d361b6..e6d6c9ef1474 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2663,7 +2663,10 @@ fn make(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyhow: let entries = make::parse(make_format_type, output.as_str()); let entries_count = entries.len(); editor.make_list.set(entries); - editor.set_status(format!("Filled make list with {} entries.", entries_count)); + editor.set_status(format!( + "Command run. Filled make list with {} entries.", + entries_count + )); }, )); Ok(call) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 1f0ff4b3ee44..dba7bf576612 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -15,6 +15,7 @@ use crate::{ use helix_core::{ diagnostic::NumberOrString, + find_workspace, graphemes::{next_grapheme_boundary, prev_grapheme_boundary}, movement::Direction, syntax::{self, OverlayHighlights}, @@ -140,6 +141,7 @@ impl EditorView { } Self::doc_diagnostics_highlights_into(doc, theme, &mut overlays); + Self::doc_makelist_highlights_into(doc, editor, theme, &mut overlays); if is_focused { if let Some(tabstops) = Self::tabstop_highlights(doc, theme) { @@ -158,6 +160,7 @@ impl EditorView { } } + // NOTE(szulf): gutter diagnostics HERE let gutter_overflow = view.gutter_offset(doc) == 0; if !gutter_overflow { Self::render_gutter( @@ -185,6 +188,8 @@ impl EditorView { } let width = view.inner_width(doc); let config = doc.config.load(); + + // NOTE(szulf): text for diagnostics HERE let enable_cursor_line = view .diagnostics_handler .show_cursorline_diagnostics(doc, view.id); @@ -196,6 +201,7 @@ impl EditorView { inline_diagnostic_config, config.end_of_line_diagnostics, )); + render_document( surface, inner, @@ -459,6 +465,76 @@ impl EditorView { ]); } + pub fn doc_makelist_highlights_into( + doc: &Document, + editor: &Editor, + theme: &Theme, + overlay_highlights: &mut Vec, + ) { + use helix_core::diagnostic::Severity; + + let get_scope_of = |scope| { + theme + .find_highlight_exact(scope) + // get one of the themes below as fallback values + .or_else(|| theme.find_highlight_exact("diagnostic")) + .or_else(|| theme.find_highlight_exact("ui.cursor")) + .or_else(|| theme.find_highlight_exact("ui.selection")) + .expect( + "at least one of the following scopes must be defined in the theme: `diagnostic`, `ui.cursor`, or `ui.selection`", + ) + }; + + let mut info_vec = Vec::new(); + let mut hint_vec = Vec::new(); + let mut warning_vec = Vec::new(); + let mut error_vec = Vec::new(); + + for entry in &editor.make_list { + let vec = match entry.severity { + Severity::Hint => &mut hint_vec, + Severity::Warning => &mut warning_vec, + Severity::Error => &mut error_vec, + Severity::Info => &mut info_vec, + }; + + // TODO(szulf): have to check here if im in the right file + let current_path = match doc.path() { + Some(path) => match path.strip_prefix(find_workspace().0) { + Ok(path) => path, + Err(_) => continue, + }, + None => continue, + }; + + if current_path == entry.location.path { + let text = doc.text().slice(..); + let range_start = text.line_to_byte(entry.location.line); + let range_end = text.line_to_byte(entry.location.line + 1) - 1; + vec.push(range_start..range_end); + } + } + + overlay_highlights.extend([ + OverlayHighlights::Homogeneous { + highlight: get_scope_of("diagnostic.info"), + ranges: info_vec, + }, + OverlayHighlights::Homogeneous { + highlight: get_scope_of("diagnostic.hint"), + ranges: hint_vec, + }, + OverlayHighlights::Homogeneous { + highlight: get_scope_of("diagnostic.warning"), + ranges: warning_vec, + }, + OverlayHighlights::Homogeneous { + highlight: get_scope_of("diagnostic.error"), + ranges: error_vec, + }, + ]); + } + /// Get highlight spans for selections in a document view. pub fn doc_selection_highlights( mode: Mode, From 590e1b8d9846c97f8b75a2b9aa994de74c40f3cb Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:18:58 +0200 Subject: [PATCH 14/15] add gutter diagnostic highlighting to entries in make list --- helix-view/src/gutter.rs | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index c2cbc0da500e..4fa0d15c30b3 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -1,6 +1,7 @@ use std::fmt::Write; use helix_core::syntax::config::LanguageServerFeature; +use helix_loader::find_workspace; use crate::{ editor::GutterType, @@ -87,6 +88,50 @@ pub fn diagnostic<'doc>( ) } +pub fn make_list_diagnostic<'doc>( + editor: &'doc Editor, + doc: &'doc Document, + _view: &View, + theme: &Theme, + _is_focused: bool, +) -> GutterFn<'doc> { + let warning = theme.get("warning"); + let error = theme.get("error"); + let info = theme.get("info"); + let hint = theme.get("hint"); + let root_path = find_workspace().0; + + Box::new( + move |line: usize, _selected: bool, first_visual_line: bool, out: &mut String| { + if !first_visual_line { + return None; + } + use helix_core::diagnostic::Severity; + let path = match doc.path() { + Some(path) => match path.strip_prefix(&root_path) { + Ok(p) => p, + Err(_) => return None, + }, + None => return None, + }; + + let diagnostics = (&editor.make_list) + .into_iter() + .filter(|entry| entry.location.path == path && entry.location.line == line); + + diagnostics.max_by_key(|e| e.severity).map(|e| { + write!(out, "●").ok(); + match e.severity { + Severity::Error => error, + Severity::Warning => warning, + Severity::Info => info, + Severity::Hint => hint, + } + }) + }, + ) +} + pub fn diff<'doc>( _editor: &'doc Editor, doc: &'doc Document, @@ -313,6 +358,7 @@ pub fn diagnostics_or_breakpoints<'doc>( theme: &Theme, is_focused: bool, ) -> GutterFn<'doc> { + let mut make_list_diagnostics = make_list_diagnostic(editor, doc, view, theme, is_focused); let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused); let mut breakpoints = breakpoints(editor, doc, view, theme, is_focused); let mut execution_pause_indicator = execution_pause_indicator(editor, doc, theme, is_focused); @@ -321,6 +367,7 @@ pub fn diagnostics_or_breakpoints<'doc>( execution_pause_indicator(line, selected, first_visual_line, out) .or_else(|| breakpoints(line, selected, first_visual_line, out)) .or_else(|| diagnostics(line, selected, first_visual_line, out)) + .or_else(|| make_list_diagnostics(line, selected, first_visual_line, out)) }) } From acb7c8c2a381fea656eeeef1abc8beebd6c7d722 Mon Sep 17 00:00:00 2001 From: szulf <139226203+szulf@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:05:34 +0200 Subject: [PATCH 15/15] remove useless/done todos for makelist --- helix-term/src/make.rs | 4 ---- helix-term/src/ui/editor.rs | 3 --- helix-view/src/make.rs | 2 -- 3 files changed, 9 deletions(-) diff --git a/helix-term/src/make.rs b/helix-term/src/make.rs index f881bfdbc49d..309f131cd8d8 100644 --- a/helix-term/src/make.rs +++ b/helix-term/src/make.rs @@ -9,10 +9,8 @@ use regex::RegexBuilder; use std::path::PathBuf; use tui::text::Span; -// TODO(szulf): check the not closing error after opening logs on a non modified version of helix // TODO(szulf): figure out how to display messages from the make_list the same way as diagnostics // and make it togglable in the config i think(off by default i think) -// TODO(szulf): write the return code(success/fail) while writing 'filled make list with ? entries' // TODO(szulf): add keybindings for going to next/prev item in make list #[derive(Debug, Clone)] @@ -67,7 +65,6 @@ pub fn make_picker(cx: &Context, root: PathBuf) -> MakePicker { }) } -// TODO(szulf): dont really see the point of this enum honestly #[derive(Debug)] pub enum MakeFormatType { Default, @@ -137,7 +134,6 @@ fn parse_gcc(source: &str) -> Vec { ) } -// TODO(szulf): test this fn parse_msvc(source: &str) -> Vec { parse_with_regex( source, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index dba7bf576612..6eda1ec21efb 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -160,7 +160,6 @@ impl EditorView { } } - // NOTE(szulf): gutter diagnostics HERE let gutter_overflow = view.gutter_offset(doc) == 0; if !gutter_overflow { Self::render_gutter( @@ -189,7 +188,6 @@ impl EditorView { let width = view.inner_width(doc); let config = doc.config.load(); - // NOTE(szulf): text for diagnostics HERE let enable_cursor_line = view .diagnostics_handler .show_cursorline_diagnostics(doc, view.id); @@ -498,7 +496,6 @@ impl EditorView { Severity::Info => &mut info_vec, }; - // TODO(szulf): have to check here if im in the right file let current_path = match doc.path() { Some(path) => match path.strip_prefix(find_workspace().0) { Ok(path) => path, diff --git a/helix-view/src/make.rs b/helix-view/src/make.rs index 12837658d9c4..ca105c7d4326 100644 --- a/helix-view/src/make.rs +++ b/helix-view/src/make.rs @@ -8,8 +8,6 @@ pub struct Location { pub line: usize, } -// TODO(szulf): maybe add a way for entries to reference other entries -// so that things like note: can actually be linked back to the original error #[derive(Debug, Clone)] pub struct Entry { pub location: Location,