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 <bennet@zed.dev>
This commit is contained in:
Generated
+28
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -41,14 +41,14 @@ impl Declaration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_entry_id(&self) -> Option<ProjectEntryId> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<ScoredSnippet>,
|
||||
pub retrieval_duration: std::time::Duration,
|
||||
}
|
||||
|
||||
impl EditPredictionContext {
|
||||
@@ -29,14 +35,14 @@ impl EditPredictionContext {
|
||||
excerpt_options: EditPredictionExcerptOptions,
|
||||
syntax_index: Entity<SyntaxIndex>,
|
||||
cx: &mut App,
|
||||
) -> Task<Self> {
|
||||
) -> Task<Option<Self>> {
|
||||
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");
|
||||
|
||||
@@ -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<String>,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
@@ -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::<Editor>(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<Project>,
|
||||
last_context: Option<ContextState>,
|
||||
max_bytes_input: Entity<SingleLineInput>,
|
||||
min_bytes_input: Entity<SingleLineInput>,
|
||||
cursor_context_ratio_input: Entity<SingleLineInput>,
|
||||
// TODO move to project or provider?
|
||||
syntax_index: Entity<SyntaxIndex>,
|
||||
last_editor: WeakEntity<Editor>,
|
||||
_active_editor_subscription: Option<Subscription>,
|
||||
_edit_prediction_context_task: Task<()>,
|
||||
}
|
||||
|
||||
struct ContextState {
|
||||
context_editor: Entity<Editor>,
|
||||
retrieval_duration: Duration,
|
||||
}
|
||||
|
||||
impl EditPredictionTools {
|
||||
pub fn new(
|
||||
workspace: &Entity<Workspace>,
|
||||
project: &Entity<Project>,
|
||||
active_editor: Option<Entity<Editor>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> 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::<Editor>(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<Self>|
|
||||
-> Entity<SingleLineInput> {
|
||||
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<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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<T: FromStr + Default>(
|
||||
input: &Entity<SingleLineInput>,
|
||||
cx: &App,
|
||||
) -> T {
|
||||
input
|
||||
.read(cx)
|
||||
.editor()
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.parse::<T>()
|
||||
.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<Icon> {
|
||||
Some(Icon::new(IconName::ZedPredict))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<()> for EditPredictionTools {}
|
||||
|
||||
impl Render for EditPredictionTools {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> 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<Path>,
|
||||
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<Path> {
|
||||
&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
|
||||
}
|
||||
}
|
||||
@@ -646,6 +646,24 @@ impl LanguageRegistry {
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
pub async fn language_for_id(self: &Arc<Self>, id: LanguageId) -> Result<Arc<Language>> {
|
||||
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<Self>, extension: &str) -> Option<LanguageName> {
|
||||
self.state.try_read().and_then(|state| {
|
||||
state
|
||||
|
||||
@@ -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<IconName>,
|
||||
/// 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<Length>) -> Self {
|
||||
self.min_width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user