From df50b5c14a7cfae0d07c4e5d6a0ff91bd5587c89 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 18 Sep 2025 15:09:44 -0300 Subject: [PATCH] edit prediction: Context debug view (#38435) Adds a `dev: open edit prediction context` action that opens a new workspace pane that displays the excerpts and snippets that would be included in the edit prediction request. Release Notes: - N/A --------- Co-authored-by: Bennet --- Cargo.lock | 28 ++ Cargo.toml | 2 + .../src/declaration.rs | 6 +- .../src/declaration_scoring.rs | 6 +- .../src/edit_prediction_context.rs | 20 +- crates/edit_prediction_context/src/excerpt.rs | 2 +- .../edit_prediction_context/src/reference.rs | 3 +- .../src/syntax_index.rs | 4 +- crates/edit_prediction_tools/Cargo.toml | 41 ++ crates/edit_prediction_tools/LICENSE-GPL | 1 + .../src/edit_prediction_tools.rs | 457 ++++++++++++++++++ crates/language/src/language_registry.rs | 18 + crates/ui_input/src/ui_input.rs | 12 +- crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + 15 files changed, 583 insertions(+), 19 deletions(-) create mode 100644 crates/edit_prediction_tools/Cargo.toml create mode 120000 crates/edit_prediction_tools/LICENSE-GPL create mode 100644 crates/edit_prediction_tools/src/edit_prediction_tools.rs diff --git a/Cargo.lock b/Cargo.lock index b80e101f868dbe8dd7d6d939a6ab0c94951ed555..28247c7f4c6bcab64a8acce951e072e80abf751b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5197,6 +5197,33 @@ dependencies = [ "zlog", ] +[[package]] +name = "edit_prediction_tools" +version = "0.1.0" +dependencies = [ + "clap", + "collections", + "edit_prediction_context", + "editor", + "futures 0.3.31", + "gpui", + "indoc", + "language", + "log", + "pretty_assertions", + "project", + "serde", + "serde_json", + "settings", + "text", + "ui", + "ui_input", + "util", + "workspace", + "workspace-hack", + "zlog", +] + [[package]] name = "editor" version = "0.1.0" @@ -21217,6 +21244,7 @@ dependencies = [ "debugger_ui", "diagnostics", "edit_prediction_button", + "edit_prediction_tools", "editor", "env_logger 0.11.8", "extension", diff --git a/Cargo.toml b/Cargo.toml index 08a9b41315c36d7facd7b9d1751b949a2577395c..8a67b28b0e4a7b6e146f4c6a4b84c73d229ceb27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "crates/edit_prediction", "crates/edit_prediction_button", "crates/edit_prediction_context", + "crates/edit_prediction_tools", "crates/editor", "crates/eval", "crates/explorer_command_injector", @@ -314,6 +315,7 @@ image_viewer = { path = "crates/image_viewer" } edit_prediction = { path = "crates/edit_prediction" } edit_prediction_button = { path = "crates/edit_prediction_button" } edit_prediction_context = { path = "crates/edit_prediction_context" } +edit_prediction_tools = { path = "crates/edit_prediction_tools" } inspector_ui = { path = "crates/inspector_ui" } install_cli = { path = "crates/install_cli" } jj = { path = "crates/jj" } diff --git a/crates/edit_prediction_context/src/declaration.rs b/crates/edit_prediction_context/src/declaration.rs index fcf54fead80194fe97a2719971f86318a57ad75c..8fba85367c70e2cb1211e343bba7a6675a5a5360 100644 --- a/crates/edit_prediction_context/src/declaration.rs +++ b/crates/edit_prediction_context/src/declaration.rs @@ -41,14 +41,14 @@ impl Declaration { } } - pub fn project_entry_id(&self) -> Option { + pub fn project_entry_id(&self) -> ProjectEntryId { match self { Declaration::File { project_entry_id, .. - } => Some(*project_entry_id), + } => *project_entry_id, Declaration::Buffer { project_entry_id, .. - } => Some(*project_entry_id), + } => *project_entry_id, } } diff --git a/crates/edit_prediction_context/src/declaration_scoring.rs b/crates/edit_prediction_context/src/declaration_scoring.rs index dc442710516a935a65e755393fdfc15026ff1f0e..4cbc4e83c02e0cae912813a261d99f8fe8c41b55 100644 --- a/crates/edit_prediction_context/src/declaration_scoring.rs +++ b/crates/edit_prediction_context/src/declaration_scoring.rs @@ -119,15 +119,13 @@ pub fn scored_snippets( ) }) } else { - // TODO should we prefer the current file instead? - Some((false, 0, declaration)) + Some((false, u32::MAX, declaration)) } } Declaration::File { .. } => { - // TODO should we prefer the current file instead? // We can assume that a file declaration is in a different file, // because the current one must be open - Some((false, 0, declaration)) + Some((false, u32::MAX, declaration)) } }) .sorted_by_key(|&(_, distance, _)| distance) diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs index 5d73dc7f7dcf2223ae1f23b22c2f104842206e12..aed2953777d82d65b7e9cb42229d78634d5e4a3d 100644 --- a/crates/edit_prediction_context/src/edit_prediction_context.rs +++ b/crates/edit_prediction_context/src/edit_prediction_context.rs @@ -6,8 +6,12 @@ mod reference; mod syntax_index; mod text_similarity; +use std::time::Instant; + pub use declaration::{BufferDeclaration, Declaration, FileDeclaration, Identifier}; +pub use declaration_scoring::SnippetStyle; pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions, EditPredictionExcerptText}; + use gpui::{App, AppContext as _, Entity, Task}; use language::BufferSnapshot; pub use reference::references_in_excerpt; @@ -16,10 +20,12 @@ use text::{Point, ToOffset as _}; use crate::declaration_scoring::{ScoredSnippet, scored_snippets}; +#[derive(Debug)] pub struct EditPredictionContext { pub excerpt: EditPredictionExcerpt, pub excerpt_text: EditPredictionExcerptText, pub snippets: Vec, + pub retrieval_duration: std::time::Duration, } impl EditPredictionContext { @@ -29,14 +35,14 @@ impl EditPredictionContext { excerpt_options: EditPredictionExcerptOptions, syntax_index: Entity, cx: &mut App, - ) -> Task { + ) -> Task> { + let start = Instant::now(); let index_state = syntax_index.read_with(cx, |index, _cx| index.state().clone()); cx.background_spawn(async move { let index_state = index_state.lock().await; let excerpt = - EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &excerpt_options) - .unwrap(); + EditPredictionExcerpt::select_from_buffer(cursor_point, &buffer, &excerpt_options)?; let excerpt_text = excerpt.text(&buffer); let references = references_in_excerpt(&excerpt, &excerpt_text, &buffer); let cursor_offset = cursor_point.to_offset(&buffer); @@ -50,11 +56,12 @@ impl EditPredictionContext { &buffer, ); - Self { + Some(Self { excerpt, excerpt_text, snippets, - } + retrieval_duration: start.elapsed(), + }) }) } } @@ -107,7 +114,8 @@ mod tests { cx, ) }) - .await; + .await + .unwrap(); assert_eq!(context.snippets.len(), 1); assert_eq!(context.snippets[0].identifier.name.as_ref(), "process_data"); diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index da1de042623167d17f078c1e85b461fb0ecc8c24..3fde142093efd095129f51a83f836953a76d20cb 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -38,7 +38,7 @@ pub struct EditPredictionExcerpt { pub size: usize, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EditPredictionExcerptText { pub body: String, pub parent_signatures: Vec, diff --git a/crates/edit_prediction_context/src/reference.rs b/crates/edit_prediction_context/src/reference.rs index ee2fc7ba573c3909b5a650e3ca0ff20155272b9f..975f15c81f44dad9ee1d3105f6a5863a4685b164 100644 --- a/crates/edit_prediction_context/src/reference.rs +++ b/crates/edit_prediction_context/src/reference.rs @@ -89,7 +89,8 @@ pub fn identifiers_in_range( } let identifier_text = - &range_text[node_range.start - range.start..node_range.end - range.start]; + // TODO we changed this to saturating_sub for now, but we should fix the actually issue + &range_text[node_range.start.saturating_sub(range.start)..node_range.end.saturating_sub(range.start)]; references.push(Reference { identifier: Identifier { name: identifier_text.into(), diff --git a/crates/edit_prediction_context/src/syntax_index.rs b/crates/edit_prediction_context/src/syntax_index.rs index 852973dd7296647b0f868c3f9242ed59b81b6743..64982f5805f08a3ba791578e28778f0c8399fde8 100644 --- a/crates/edit_prediction_context/src/syntax_index.rs +++ b/crates/edit_prediction_context/src/syntax_index.rs @@ -9,7 +9,7 @@ use project::worktree_store::{WorktreeStore, WorktreeStoreEvent}; use project::{PathChange, Project, ProjectEntryId, ProjectPath}; use slotmap::SlotMap; use text::BufferId; -use util::{ResultExt as _, debug_panic, some_or_debug_panic}; +use util::{debug_panic, some_or_debug_panic}; use crate::declaration::{ BufferDeclaration, Declaration, DeclarationId, FileDeclaration, Identifier, @@ -332,7 +332,7 @@ impl SyntaxIndex { let language = language_registry .language_for_file_path(&project_path.path) .await - .log_err(); + .ok(); let buffer = cx.new(|cx| { let mut buffer = Buffer::local(loaded_file.text, cx); diff --git a/crates/edit_prediction_tools/Cargo.toml b/crates/edit_prediction_tools/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ffd34abb2537006dd914d9bf9d30b735de91c5ba --- /dev/null +++ b/crates/edit_prediction_tools/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "edit_prediction_tools" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/edit_prediction_tools.rs" + +[dependencies] +edit_prediction_context.workspace = true +collections.workspace = true +editor.workspace = true +gpui.workspace = true +language.workspace = true +log.workspace = true +project.workspace = true +serde.workspace = true +text.workspace = true +ui.workspace = true +ui_input.workspace = true +workspace-hack.workspace = true +workspace.workspace = true + +[dev-dependencies] +clap.workspace = true +futures.workspace = true +gpui = { workspace = true, features = ["test-support"] } +indoc.workspace = true +language = { workspace = true, features = ["test-support"] } +pretty_assertions.workspace = true +project = {workspace= true, features = ["test-support"]} +serde_json.workspace = true +settings = {workspace= true, features = ["test-support"]} +text = { workspace = true, features = ["test-support"] } +util = { workspace = true, features = ["test-support"] } +zlog.workspace = true diff --git a/crates/edit_prediction_tools/LICENSE-GPL b/crates/edit_prediction_tools/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/edit_prediction_tools/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/edit_prediction_tools/src/edit_prediction_tools.rs b/crates/edit_prediction_tools/src/edit_prediction_tools.rs new file mode 100644 index 0000000000000000000000000000000000000000..f00a16e026704f1d1da318956f41128a9783a54c --- /dev/null +++ b/crates/edit_prediction_tools/src/edit_prediction_tools.rs @@ -0,0 +1,457 @@ +use std::{ + collections::hash_map::Entry, + ffi::OsStr, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, + time::Duration, +}; + +use collections::HashMap; +use editor::{Editor, EditorEvent, EditorMode, ExcerptRange, MultiBuffer}; +use gpui::{ + Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, actions, + prelude::*, +}; +use language::{Buffer, DiskState}; +use project::{Project, WorktreeId}; +use text::ToPoint; +use ui::prelude::*; +use ui_input::SingleLineInput; +use workspace::{Item, SplitDirection, Workspace}; + +use edit_prediction_context::{ + EditPredictionContext, EditPredictionExcerptOptions, SnippetStyle, SyntaxIndex, +}; + +actions!( + dev, + [ + /// Opens the language server protocol logs viewer. + OpenEditPredictionContext + ] +); + +pub fn init(cx: &mut App) { + cx.observe_new(move |workspace: &mut Workspace, _, _cx| { + workspace.register_action( + move |workspace, _: &OpenEditPredictionContext, window, cx| { + let workspace_entity = cx.entity(); + let project = workspace.project(); + let active_editor = workspace.active_item_as::(cx); + workspace.split_item( + SplitDirection::Right, + Box::new(cx.new(|cx| { + EditPredictionTools::new( + &workspace_entity, + &project, + active_editor, + window, + cx, + ) + })), + window, + cx, + ); + }, + ); + }) + .detach(); +} + +pub struct EditPredictionTools { + focus_handle: FocusHandle, + project: Entity, + last_context: Option, + max_bytes_input: Entity, + min_bytes_input: Entity, + cursor_context_ratio_input: Entity, + // TODO move to project or provider? + syntax_index: Entity, + last_editor: WeakEntity, + _active_editor_subscription: Option, + _edit_prediction_context_task: Task<()>, +} + +struct ContextState { + context_editor: Entity, + retrieval_duration: Duration, +} + +impl EditPredictionTools { + pub fn new( + workspace: &Entity, + project: &Entity, + active_editor: Option>, + window: &mut Window, + cx: &mut Context, + ) -> Self { + cx.subscribe_in(workspace, window, |this, workspace, event, window, cx| { + if let workspace::Event::ActiveItemChanged = event { + if let Some(editor) = workspace.read(cx).active_item_as::(cx) { + this._active_editor_subscription = Some(cx.subscribe_in( + &editor, + window, + |this, editor, event, window, cx| { + if let EditorEvent::SelectionsChanged { .. } = event { + this.update_context(editor, window, cx); + } + }, + )); + this.update_context(&editor, window, cx); + } else { + this._active_editor_subscription = None; + } + } + }) + .detach(); + let syntax_index = cx.new(|cx| SyntaxIndex::new(project, cx)); + + let number_input = |label: &'static str, + value: &'static str, + window: &mut Window, + cx: &mut Context| + -> Entity { + let input = cx.new(|cx| { + let input = SingleLineInput::new(window, cx, "") + .label(label) + .label_min_width(px(64.)); + input.set_text(value, window, cx); + input + }); + cx.subscribe_in( + &input.read(cx).editor().clone(), + window, + |this, _, event, window, cx| { + if let EditorEvent::BufferEdited = event + && let Some(editor) = this.last_editor.upgrade() + { + this.update_context(&editor, window, cx); + } + }, + ) + .detach(); + input + }; + + let mut this = Self { + focus_handle: cx.focus_handle(), + project: project.clone(), + last_context: None, + max_bytes_input: number_input("Max Bytes", "512", window, cx), + min_bytes_input: number_input("Min Bytes", "128", window, cx), + cursor_context_ratio_input: number_input("Cursor Context Ratio", "0.5", window, cx), + syntax_index, + last_editor: WeakEntity::new_invalid(), + _active_editor_subscription: None, + _edit_prediction_context_task: Task::ready(()), + }; + + if let Some(editor) = active_editor { + this.update_context(&editor, window, cx); + } + + this + } + + fn update_context( + &mut self, + editor: &Entity, + window: &mut Window, + cx: &mut Context, + ) { + self.last_editor = editor.downgrade(); + + let editor = editor.read(cx); + let buffer = editor.buffer().clone(); + let cursor_position = editor.selections.newest_anchor().start; + + let Some(buffer) = buffer.read(cx).buffer_for_anchor(cursor_position, cx) else { + self.last_context.take(); + return; + }; + let current_buffer_snapshot = buffer.read(cx).snapshot(); + let cursor_position = cursor_position + .text_anchor + .to_point(¤t_buffer_snapshot); + + let language = current_buffer_snapshot.language().cloned(); + let Some(worktree_id) = self + .project + .read(cx) + .worktrees(cx) + .next() + .map(|worktree| worktree.read(cx).id()) + else { + log::error!("Open a worktree to use edit prediction debug view"); + self.last_context.take(); + return; + }; + + self._edit_prediction_context_task = cx.spawn_in(window, { + let language_registry = self.project.read(cx).languages().clone(); + async move |this, cx| { + cx.background_executor() + .timer(Duration::from_millis(50)) + .await; + + let Ok(task) = this.update(cx, |this, cx| { + fn number_input_value( + input: &Entity, + cx: &App, + ) -> T { + input + .read(cx) + .editor() + .read(cx) + .text(cx) + .parse::() + .unwrap_or_default() + } + + let options = EditPredictionExcerptOptions { + max_bytes: number_input_value(&this.max_bytes_input, cx), + min_bytes: number_input_value(&this.min_bytes_input, cx), + target_before_cursor_over_total_bytes: number_input_value( + &this.cursor_context_ratio_input, + cx, + ), + // TODO Display and add to options + include_parent_signatures: false, + }; + + EditPredictionContext::gather( + cursor_position, + current_buffer_snapshot, + options, + this.syntax_index.clone(), + cx, + ) + }) else { + this.update(cx, |this, _cx| { + this.last_context.take(); + }) + .ok(); + return; + }; + + let Some(context) = task.await else { + // TODO: Display message + this.update(cx, |this, _cx| { + this.last_context.take(); + }) + .ok(); + return; + }; + + let mut languages = HashMap::default(); + for snippet in context.snippets.iter() { + let lang_id = snippet.declaration.identifier().language_id; + if let Entry::Vacant(entry) = languages.entry(lang_id) { + // Most snippets are gonna be the same language, + // so we think it's fine to do this sequentially for now + entry.insert(language_registry.language_for_id(lang_id).await.ok()); + } + } + + this.update_in(cx, |this, window, cx| { + let context_editor = cx.new(|cx| { + let multibuffer = cx.new(|cx| { + let mut multibuffer = MultiBuffer::new(language::Capability::ReadOnly); + let excerpt_file = Arc::new(ExcerptMetadataFile { + title: PathBuf::from("Cursor Excerpt").into(), + worktree_id, + }); + + let excerpt_buffer = cx.new(|cx| { + let mut buffer = Buffer::local(context.excerpt_text.body, cx); + buffer.set_language(language, cx); + buffer.file_updated(excerpt_file, cx); + buffer + }); + + multibuffer.push_excerpts( + excerpt_buffer, + [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], + cx, + ); + + for snippet in context.snippets { + let path = this + .project + .read(cx) + .path_for_entry(snippet.declaration.project_entry_id(), cx); + + let snippet_file = Arc::new(ExcerptMetadataFile { + title: PathBuf::from(format!( + "{} (Score density: {})", + path.map(|p| p.path.to_string_lossy().to_string()) + .unwrap_or_else(|| "".to_string()), + snippet.score_density(SnippetStyle::Declaration) + )) + .into(), + worktree_id, + }); + + let excerpt_buffer = cx.new(|cx| { + let mut buffer = + Buffer::local(snippet.declaration.item_text().0, cx); + buffer.file_updated(snippet_file, cx); + if let Some(language) = + languages.get(&snippet.declaration.identifier().language_id) + { + buffer.set_language(language.clone(), cx); + } + buffer + }); + + multibuffer.push_excerpts( + excerpt_buffer, + [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], + cx, + ); + } + + multibuffer + }); + + Editor::new(EditorMode::full(), multibuffer, None, window, cx) + }); + + this.last_context = Some(ContextState { + context_editor, + retrieval_duration: context.retrieval_duration, + }); + cx.notify(); + }) + .ok(); + } + }); + } +} + +impl Focusable for EditPredictionTools { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Item for EditPredictionTools { + type Event = (); + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Edit Prediction Context Debug View".into() + } + + fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { + Some(Icon::new(IconName::ZedPredict)) + } +} + +impl EventEmitter<()> for EditPredictionTools {} + +impl Render for EditPredictionTools { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .size_full() + .bg(cx.theme().colors().editor_background) + .child( + h_flex() + .items_start() + .w_full() + .child( + v_flex() + .flex_1() + .p_4() + .gap_2() + .child(Headline::new("Excerpt Options").size(HeadlineSize::Small)) + .child( + h_flex() + .gap_2() + .child(self.max_bytes_input.clone()) + .child(self.min_bytes_input.clone()) + .child(self.cursor_context_ratio_input.clone()), + ), + ) + .child(ui::Divider::vertical()) + .when_some(self.last_context.as_ref(), |this, last_context| { + this.child( + v_flex() + .p_4() + .gap_2() + .min_w(px(160.)) + .child(Headline::new("Stats").size(HeadlineSize::Small)) + .child( + h_flex() + .gap_1() + .child( + Label::new("Time to retrieve") + .color(Color::Muted) + .size(LabelSize::Small), + ) + .child( + Label::new( + if last_context.retrieval_duration.as_micros() + > 1000 + { + format!( + "{} ms", + last_context.retrieval_duration.as_millis() + ) + } else { + format!( + "{} µs", + last_context.retrieval_duration.as_micros() + ) + }, + ) + .size(LabelSize::Small), + ), + ), + ) + }), + ) + .children(self.last_context.as_ref().map(|c| c.context_editor.clone())) + } +} + +// Using same approach as commit view + +struct ExcerptMetadataFile { + title: Arc, + worktree_id: WorktreeId, +} + +impl language::File for ExcerptMetadataFile { + fn as_local(&self) -> Option<&dyn language::LocalFile> { + None + } + + fn disk_state(&self) -> DiskState { + DiskState::New + } + + fn path(&self) -> &Arc { + &self.title + } + + fn full_path(&self, _: &App) -> PathBuf { + self.title.as_ref().into() + } + + fn file_name<'a>(&'a self, _: &'a App) -> &'a OsStr { + self.title.file_name().unwrap() + } + + fn worktree_id(&self, _: &App) -> WorktreeId { + self.worktree_id + } + + fn to_proto(&self, _: &App) -> language::proto::File { + unimplemented!() + } + + fn is_private(&self) -> bool { + false + } +} diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 9ec57b90b39f40b9530bef7ff02753ea8f37d2e5..92efe122aa5a13e0d4b1c196c019d9090ce3aa22 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -646,6 +646,24 @@ impl LanguageRegistry { async move { rx.await? } } + pub async fn language_for_id(self: &Arc, id: LanguageId) -> Result> { + let available_language = { + let state = self.state.read(); + + let Some(available_language) = state + .available_languages + .iter() + .find(|lang| lang.id == id) + .cloned() + else { + anyhow::bail!(LanguageNotFound); + }; + available_language + }; + + self.load_language(&available_language).await? + } + pub fn language_name_for_extension(self: &Arc, extension: &str) -> Option { self.state.try_read().and_then(|state| { state diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs index 45c0deba4adfe71ea99d83c1bd081af1fc272671..79bddf6a182f1fefa495e635bd0dd348211fdc94 100644 --- a/crates/ui_input/src/ui_input.rs +++ b/crates/ui_input/src/ui_input.rs @@ -7,7 +7,7 @@ use component::{example_group, single_example}; use editor::{Editor, EditorElement, EditorStyle}; -use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, TextStyle}; +use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, Length, TextStyle}; use settings::Settings; use std::sync::Arc; use theme::ThemeSettings; @@ -42,6 +42,8 @@ pub struct SingleLineInput { start_icon: Option, /// Whether the text field is disabled. disabled: bool, + /// The minimum width of for the input + min_width: Length, } impl Focusable for SingleLineInput { @@ -67,6 +69,7 @@ impl SingleLineInput { editor, start_icon: None, disabled: false, + min_width: px(192.).into(), } } @@ -85,6 +88,11 @@ impl SingleLineInput { self } + pub fn label_min_width(mut self, width: impl Into) -> Self { + self.min_width = width.into(); + self + } + pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context) { self.disabled = disabled; self.editor @@ -167,7 +175,7 @@ impl Render for SingleLineInput { }) .child( h_flex() - .min_w_48() + .min_w(self.min_width) .min_h_8() .w_full() .px_2() diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c85d3e70245ff1ee1ea1253492643b603b8ca70c..5b6cb3924610b89406a37230497fee8ffc511e34 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -52,6 +52,7 @@ debugger_tools.workspace = true debugger_ui.workspace = true diagnostics.workspace = true editor.workspace = true +edit_prediction_tools.workspace = true env_logger.workspace = true extension.workspace = true extension_host.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5cce6a6e2974d2fad9638f00811273ee202ab7b6..3dbbed0ce50ad84a1717f81afdf95c432b09259d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -549,6 +549,7 @@ pub fn main() { language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx); agent_settings::init(cx); acp_tools::init(cx); + edit_prediction_tools::init(cx); web_search::init(cx); web_search_providers::init(app_state.client.clone(), cx); snippet_provider::init(cx);