diff --git a/crates/acp/src/thread_view.rs b/crates/acp/src/thread_view.rs index 9853fd9523c5d5bdec3babfbb92be4194056160a..86a09d03835143a00ec5a931bfde9793a187d57d 100644 --- a/crates/acp/src/thread_view.rs +++ b/crates/acp/src/thread_view.rs @@ -1,5 +1,6 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use std::rc::Rc; +use std::sync::Arc; use std::time::Duration; use agentic_coding_protocol::{self as acp, ToolCallConfirmation}; @@ -32,6 +33,7 @@ pub struct AcpThreadView { message_editor: Entity, list_state: ListState, send_task: Option>>, + root: Arc, } enum ThreadState { @@ -47,6 +49,7 @@ enum ThreadState { impl AcpThreadView { pub fn new(project: Entity, window: &mut Window, cx: &mut Context) -> Self { + // todo!(): This should probably be contextual, like the terminal let Some(root_dir) = project .read(cx) .visible_worktrees(cx) @@ -62,7 +65,7 @@ impl AcpThreadView { let child = util::command::new_smol_command("node") .arg(cli_path) .arg("--acp") - .current_dir(root_dir) + .current_dir(&root_dir) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::inherit()) @@ -146,6 +149,7 @@ impl AcpThreadView { message_editor, send_task: None, list_state: list_state, + root: root_dir, } } diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 9c057baec97840d81317d828f635a9aad0f6c9fc..220c47008ee68bf4806b03b8a8eccd6df2513673 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -199,6 +199,9 @@ impl Markdown { self.pending_parse.is_some() } + // Chunks: `Mark|down.md` You need to reparse every back tick, everytime + // `[foo.rs](foo.rs)` [`foo.rs`](foo.rs) `ba|r.rs` + pub fn source(&self) -> &str { &self.source } @@ -439,11 +442,25 @@ impl ParsedMarkdown { } } +// pub trait TextClickHandler { +// fn pattern(&self) -> +// fn hovered(&mut self, text: &str) -> bool; +// fn clicked(&mut self, text: &str); +// } +// const WORD_REGEX: &str = +// r#"[\$\+\w.\[\]:/\\@\-~()]+(?:\((?:\d+|\d+,\d+)\))|[\$\+\w.\[\]:/\\@\-~()]+"#; + +pub struct UrlHandler { + pub on_hover: Box bool>, + pub on_click: Box, +} + pub struct MarkdownElement { markdown: Entity, style: MarkdownStyle, code_block_renderer: CodeBlockRenderer, - on_url_click: Option>, + on_link_click: Option>, + url_handler: Option, } impl MarkdownElement { @@ -456,7 +473,8 @@ impl MarkdownElement { copy_button_on_hover: false, border: false, }, - on_url_click: None, + on_link_click: None, + url_handler: None, } } @@ -490,7 +508,12 @@ impl MarkdownElement { mut self, handler: impl Fn(SharedString, &mut Window, &mut App) + 'static, ) -> Self { - self.on_url_click = Some(Box::new(handler)); + self.on_link_click = Some(Box::new(handler)); + self + } + + pub fn handle_urls(mut self, handler: UrlHandler) -> Self { + self.url_handler = Some(handler); self } @@ -580,7 +603,7 @@ impl MarkdownElement { window.set_cursor_style(CursorStyle::IBeam, hitbox); } - let on_open_url = self.on_url_click.take(); + let on_open_url = self.on_link_click.take(); self.on_mouse_event(window, cx, { let rendered_text = rendered_text.clone(); @@ -591,6 +614,8 @@ impl MarkdownElement { if let Some(link) = rendered_text.link_for_position(event.position) { markdown.pressed_link = Some(link.clone()); } else { + // if + let source_index = match rendered_text.source_index_for_position(event.position) { Ok(ix) | Err(ix) => ix, @@ -1798,8 +1823,10 @@ impl RenderedText { #[cfg(test)] mod tests { + use std::cell::RefCell; + use super::*; - use gpui::{TestAppContext, size}; + use gpui::{Modifiers, MouseButton, TestAppContext, size}; #[gpui::test] fn test_mappings(cx: &mut TestAppContext) { @@ -1881,6 +1908,64 @@ mod tests { ); } + #[gpui::test] + fn test_url_handling(cx: &mut TestAppContext) { + let markdown = r#"hello `world` + Check out `https://zed.dev` for a great editor! + Also available locally: crates/ README.md, + "#; + + struct TestWindow; + + impl Render for TestWindow { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + div() + } + } + + let (_, cx) = cx.add_window_view(|_, _| TestWindow); + let markdown = cx.new(|cx| Markdown::new(markdown.to_string().into(), None, None, cx)); + cx.run_until_parked(); + + let paths_hovered = Rc::new(RefCell::new(Vec::new())); + let paths_clicked = Rc::new(RefCell::new(Vec::new())); + + let handler = { + let paths_hovered = paths_hovered.clone(); + let paths_clicked = paths_clicked.clone(); + + UrlHandler { + on_hover: Box::new(move |path, _window, _app| { + paths_hovered.borrow_mut().push(path.to_string()); + true + }), + on_click: Box::new(move |path, _window, _app| { + paths_clicked.borrow_mut().push(path.to_string()); + }), + } + }; + + let (rendered, _) = cx.draw( + Default::default(), + size(px(600.0), px(600.0)), + |_window, _cx| { + MarkdownElement::new(markdown, MarkdownStyle::default()).handle_urls(handler) + }, + ); + + cx.simulate_mouse_move( + point(px(0.0), px(0.0)), + MouseButton::Left, + Modifiers::default(), + ); + + assert_eq!(paths_hovered.borrow().len(), 1) + } + + // To have a markdown document with paths and links in it + // We want to run a function + // and we want to get those paths and links out? + #[track_caller] fn assert_mappings(rendered: &RenderedText, expected: Vec>) { assert_eq!(rendered.lines.len(), expected.len(), "line count mismatch"); diff --git a/crates/project/src/direnv.rs b/crates/project/src/direnv.rs index 9ba0ad10e3173a1930186db28f7efb5d9a8267f7..32f4963fd19805e369c696cfd30c8ef599bd06f3 100644 --- a/crates/project/src/direnv.rs +++ b/crates/project/src/direnv.rs @@ -65,7 +65,7 @@ pub async fn load_direnv_environment( let output = String::from_utf8_lossy(&direnv_output.stdout); if output.is_empty() { // direnv outputs nothing when it has no changes to apply to environment variables - return Ok(HashMap::new()); + return Ok(HashMap::default()); } match serde_json::from_str(&output) {