Delete edit_prediction_tools.rs (was moved to zeta2_tools.rs) (#38745)

Move happened in #38718

Release Notes:

- N/A
This commit is contained in:
Michael Sloan
2025-09-23 21:18:04 +00:00
committed by GitHub
parent 9112554262
commit 74fe3b17f7
@@ -1,461 +0,0 @@
use std::{
collections::hash_map::Entry,
ffi::OsStr,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
time::{Duration, Instant},
};
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(&current_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 mut start_time = None;
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,
),
};
start_time = Some(Instant::now());
// TODO use global zeta instead
EditPredictionContext::gather_context_in_background(
cursor_position,
current_buffer_snapshot,
options,
Some(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 retrieval_duration = start_time.unwrap().elapsed();
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,
});
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
}
}