Richard Feldman/auto (#16696)91ffa022 years agoHistory
302 lines · 10.6 KB · Rust
RawDownload
use crate::ProjectIndex;use gpui::{    canvas, div, list, uniform_list, AnyElement, AppContext, CursorStyle, EventEmitter,    FocusHandle, FocusableView, IntoElement, ListOffset, ListState, Model, MouseMoveEvent, Render,    UniformListScrollHandle, View,};use project::WorktreeId;use settings::Settings;use std::{path::Path, sync::Arc};use theme::ThemeSettings;use ui::prelude::*;use workspace::item::Item; pub struct ProjectIndexDebugView {    index: Model<ProjectIndex>,    rows: Vec<Row>,    selected_path: Option<PathState>,    hovered_row_ix: Option<usize>,    focus_handle: FocusHandle,    list_scroll_handle: UniformListScrollHandle,    _subscription: gpui::Subscription,} struct PathState {    path: Arc<Path>,    chunks: Vec<SharedString>,    list_state: ListState,} enum Row {    Worktree(Arc<Path>),    Entry(WorktreeId, Arc<Path>),} impl ProjectIndexDebugView {    pub fn new(index: Model<ProjectIndex>, cx: &mut ViewContext<Self>) -> Self {        let mut this = Self {            rows: Vec::new(),            list_scroll_handle: UniformListScrollHandle::new(),            selected_path: None,            hovered_row_ix: None,            focus_handle: cx.focus_handle(),            _subscription: cx.subscribe(&index, |this, _, _, cx| this.update_rows(cx)),            index,        };        this.update_rows(cx);        this    }     fn update_rows(&mut self, cx: &mut ViewContext<Self>) {        let worktree_indices = self.index.read(cx).worktree_indices(cx);        cx.spawn(|this, mut cx| async move {            let mut rows = Vec::new();             for index in worktree_indices {                let (root_path, worktree_id, worktree_paths) =                    index.read_with(&cx, |index, cx| {                        let worktree = index.worktree().read(cx);                        (                            worktree.abs_path(),                            worktree.id(),                            index.embedding_index().paths(cx),                        )                    })?;                rows.push(Row::Worktree(root_path));                rows.extend(                    worktree_paths                        .await?                        .into_iter()                        .map(|path| Row::Entry(worktree_id, path)),                );            }             this.update(&mut cx, |this, cx| {                this.rows = rows;                cx.notify();            })        })        .detach();    }     fn handle_path_click(        &mut self,        worktree_id: WorktreeId,        file_path: Arc<Path>,        cx: &mut ViewContext<Self>,    ) -> Option<()> {        let project_index = self.index.read(cx);        let fs = project_index.fs().clone();        let worktree_index = project_index.worktree_index(worktree_id, cx)?.read(cx);        let root_path = worktree_index.worktree().read(cx).abs_path();        let chunks = worktree_index            .embedding_index()            .chunks_for_path(file_path.clone(), cx);         cx.spawn(|this, mut cx| async move {            let chunks = chunks.await?;            let content = fs.load(&root_path.join(&file_path)).await?;            let chunks = chunks                .into_iter()                .map(|chunk| {                    let mut start = chunk.chunk.range.start.min(content.len());                    let mut end = chunk.chunk.range.end.min(content.len());                    while !content.is_char_boundary(start) {                        start += 1;                    }                    while !content.is_char_boundary(end) {                        end -= 1;                    }                    content[start..end].to_string().into()                })                .collect::<Vec<_>>();             this.update(&mut cx, |this, cx| {                let view = cx.view().downgrade();                this.selected_path = Some(PathState {                    path: file_path,                    list_state: ListState::new(                        chunks.len(),                        gpui::ListAlignment::Top,                        px(100.),                        move |ix, cx| {                            if let Some(view) = view.upgrade() {                                view.update(cx, |view, cx| view.render_chunk(ix, cx))                            } else {                                div().into_any()                            }                        },                    ),                    chunks,                });                cx.notify();            })        })        .detach();        None    }     fn render_chunk(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {        let buffer_font = ThemeSettings::get_global(cx).buffer_font.clone();        let Some(state) = &self.selected_path else {            return div().into_any();        };         let colors = cx.theme().colors();        let chunk = &state.chunks[ix];         div()            .text_ui(cx)            .w_full()            .font(buffer_font)            .child(                h_flex()                    .justify_between()                    .child(format!(                        "chunk {} of {}. length: {}",                        ix + 1,                        state.chunks.len(),                        chunk.len(),                    ))                    .child(                        h_flex()                            .child(                                Button::new(("prev", ix), "prev")                                    .disabled(ix == 0)                                    .on_click(cx.listener(move |this, _, _| {                                        this.scroll_to_chunk(ix.saturating_sub(1))                                    })),                            )                            .child(                                Button::new(("next", ix), "next")                                    .disabled(ix + 1 == state.chunks.len())                                    .on_click(                                        cx.listener(move |this, _, _| this.scroll_to_chunk(ix + 1)),                                    ),                            ),                    ),            )            .child(                div()                    .bg(colors.editor_background)                    .text_xs()                    .child(chunk.clone()),            )            .into_any_element()    }     fn scroll_to_chunk(&mut self, ix: usize) {        if let Some(state) = self.selected_path.as_mut() {            state.list_state.scroll_to(ListOffset {                item_ix: ix,                offset_in_item: px(0.),            })        }    }} impl Render for ProjectIndexDebugView {    fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> impl IntoElement {        if let Some(selected_path) = self.selected_path.as_ref() {            v_flex()                .child(                    div()                        .id("selected-path-name")                        .child(                            h_flex()                                .justify_between()                                .child(selected_path.path.to_string_lossy().to_string())                                .child("x"),                        )                        .border_b_1()                        .border_color(cx.theme().colors().border)                        .cursor(CursorStyle::PointingHand)                        .on_click(cx.listener(|this, _, cx| {                            this.selected_path.take();                            cx.notify();                        })),                )                .child(list(selected_path.list_state.clone()).size_full())                .size_full()                .into_any_element()        } else {            let mut list = uniform_list(                cx.view().clone(),                "ProjectIndexDebugView",                self.rows.len(),                move |this, range, cx| {                    this.rows[range]                        .iter()                        .enumerate()                        .map(|(ix, row)| match row {                            Row::Worktree(root_path) => div()                                .id(ix)                                .child(Label::new(root_path.to_string_lossy().to_string())),                            Row::Entry(worktree_id, file_path) => div()                                .id(ix)                                .pl_8()                                .child(Label::new(file_path.to_string_lossy().to_string()))                                .on_mouse_move(cx.listener(move |this, _: &MouseMoveEvent, cx| {                                    if this.hovered_row_ix != Some(ix) {                                        this.hovered_row_ix = Some(ix);                                        cx.notify();                                    }                                }))                                .cursor(CursorStyle::PointingHand)                                .on_click(cx.listener({                                    let worktree_id = *worktree_id;                                    let file_path = file_path.clone();                                    move |this, _, cx| {                                        this.handle_path_click(worktree_id, file_path.clone(), cx);                                    }                                })),                        })                        .collect()                },            )            .track_scroll(self.list_scroll_handle.clone())            .size_full()            .text_bg(cx.theme().colors().background)            .into_any_element();             canvas(                move |bounds, cx| {                    list.prepaint_as_root(bounds.origin, bounds.size.into(), cx);                    list                },                |_, mut list, cx| {                    list.paint(cx);                },            )            .size_full()            .into_any_element()        }    }} impl EventEmitter<()> for ProjectIndexDebugView {} impl Item for ProjectIndexDebugView {    type Event = ();     fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {        Some("Project Index (Debug)".into())    }     fn clone_on_split(        &self,        _: Option<workspace::WorkspaceId>,        cx: &mut ViewContext<Self>,    ) -> Option<View<Self>>    where        Self: Sized,    {        Some(cx.new_view(|cx| Self::new(self.index.clone(), cx)))    }} impl FocusableView for ProjectIndexDebugView {    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {        self.focus_handle.clone()    }}