diff --git a/Cargo.lock b/Cargo.lock index ccb187f3da9d7bcc9a2f5c5ac03d6a7a645a6b7a..d4a826f20bb76b4caa571a9b0771df5cfdc2bfa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20730,6 +20730,8 @@ dependencies = [ "indoc", "language", "log", + "multi_buffer", + "ordered-float 2.10.1", "pretty_assertions", "project", "serde", diff --git a/crates/zeta2_tools/Cargo.toml b/crates/zeta2_tools/Cargo.toml index e2dd18e46efee5faa89918c6b11efa8bf5b5422b..c600b3b86e4f3f8477431275d7f85591ccb22ac7 100644 --- a/crates/zeta2_tools/Cargo.toml +++ b/crates/zeta2_tools/Cargo.toml @@ -22,6 +22,8 @@ futures.workspace = true gpui.workspace = true language.workspace = true log.workspace = true +multi_buffer.workspace = true +ordered-float.workspace = true project.workspace = true serde.workspace = true text.workspace = true diff --git a/crates/zeta2_tools/src/zeta2_tools.rs b/crates/zeta2_tools/src/zeta2_tools.rs index 4209731eb499ee27358d0f093af40d04955524a1..69536ad46806cb271ef987cadb4e95a2061ac953 100644 --- a/crates/zeta2_tools/src/zeta2_tools.rs +++ b/crates/zeta2_tools/src/zeta2_tools.rs @@ -1,16 +1,20 @@ -use std::{collections::hash_map::Entry, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; +use std::{ + cmp::Reverse, collections::hash_map::Entry, path::PathBuf, str::FromStr, sync::Arc, + time::Duration, +}; use chrono::TimeDelta; use client::{Client, UserStore}; -use cloud_llm_client::predict_edits_v3::PromptFormat; +use cloud_llm_client::predict_edits_v3::{DeclarationScoreComponents, PromptFormat}; use collections::HashMap; use editor::{Editor, EditorEvent, EditorMode, ExcerptRange, MultiBuffer}; use futures::{StreamExt as _, channel::oneshot}; use gpui::{ - Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, actions, - prelude::*, + CursorStyle, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, + actions, prelude::*, }; use language::{Buffer, DiskState}; +use ordered_float::OrderedFloat; use project::{Project, WorktreeId}; use ui::{ContextMenu, ContextMenuEntry, DropdownMenu, prelude::*}; use ui_input::SingleLineInput; @@ -298,6 +302,8 @@ impl Zeta2Inspector { this.update_in(cx, |this, window, cx| { let context_editor = cx.new(|cx| { + let mut excerpt_score_components = HashMap::default(); + let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(language::Capability::ReadOnly); let excerpt_file = Arc::new(ExcerptMetadataFile { @@ -328,7 +334,14 @@ impl Zeta2Inspector { cx, ); - for snippet in &prediction.context.declarations { + let mut declarations = prediction.context.declarations.clone(); + declarations.sort_unstable_by_key(|declaration| { + Reverse(OrderedFloat( + declaration.score(DeclarationStyle::Declaration), + )) + }); + + for snippet in &declarations { let path = this .project .read(cx) @@ -336,10 +349,10 @@ impl Zeta2Inspector { let snippet_file = Arc::new(ExcerptMetadataFile { title: RelPath::unix(&format!( - "{} (Score density: {})", + "{} (Score: {})", path.map(|p| p.path.display(path_style).to_string()) .unwrap_or_else(|| "".to_string()), - snippet.score_density(DeclarationStyle::Declaration) + snippet.score(DeclarationStyle::Declaration) )) .unwrap() .into(), @@ -359,17 +372,26 @@ impl Zeta2Inspector { buffer }); - multibuffer.push_excerpts( + let excerpt_ids = multibuffer.push_excerpts( excerpt_buffer, [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)], cx, ); + let excerpt_id = excerpt_ids.first().unwrap(); + + excerpt_score_components + .insert(*excerpt_id, snippet.components.clone()); } multibuffer }); - Editor::new(EditorMode::full(), multibuffer, None, window, cx) + let mut editor = + Editor::new(EditorMode::full(), multibuffer, None, window, cx); + editor.register_addon(ZetaContextAddon { + excerpt_score_components, + }); + editor }); let PredictionDebugInfo { @@ -807,3 +829,58 @@ impl language::File for ExcerptMetadataFile { false } } + +struct ZetaContextAddon { + excerpt_score_components: HashMap, +} + +impl editor::Addon for ZetaContextAddon { + fn to_any(&self) -> &dyn std::any::Any { + self + } + + fn render_buffer_header_controls( + &self, + excerpt_info: &multi_buffer::ExcerptInfo, + _window: &Window, + _cx: &App, + ) -> Option { + let score_components = self.excerpt_score_components.get(&excerpt_info.id)?.clone(); + + Some( + div() + .id(excerpt_info.id.to_proto() as usize) + .child(ui::Icon::new(IconName::Info)) + .cursor(CursorStyle::PointingHand) + .tooltip(move |_, cx| { + cx.new(|_| ScoreComponentsTooltip::new(&score_components)) + .into() + }) + .into_any(), + ) + } +} + +struct ScoreComponentsTooltip { + text: SharedString, +} + +impl ScoreComponentsTooltip { + fn new(components: &DeclarationScoreComponents) -> Self { + Self { + text: format!("{:#?}", components).into(), + } + } +} + +impl Render for ScoreComponentsTooltip { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + div().pl_2().pt_2p5().child( + div() + .elevation_2(cx) + .py_1() + .px_2() + .child(ui::Label::new(self.text.clone()).buffer_font(cx)), + ) + } +}