250 lines · 7.7 KB · Rust
RawDownload
use std::any::Any;use std::collections::HashMap;use std::str::FromStr;use std::sync::Arc; use pathfinder_geometry::vector::{Vector2F, vec2f};use serde_yaml::Mapping;use uuid::Uuid;use warp_editor::content::markdown::MarkdownStyle;use warp_editor::editor::EmbeddedItemModel;use warp_editor::render::element::{RenderContext, RenderableBlock};use warp_editor::render::layout::TextLayout;use warp_editor::render::model::viewport::ViewportItem;use warp_editor::render::model::{    BlockSpacing, EmbeddedItem, EmbeddedItemHTMLRepresentation, EmbeddedItemRichFormat,    LaidOutEmbeddedItem, RenderState,};use warp_errors::report_error;use warpui::event::DispatchedEvent;use warpui::units::Pixels;use warpui::{AppContext, EntityId, EventContext, LayoutContext, ViewHandle, WindowId}; use crate::code::editor::comment_editor::CommentEditor;use crate::code_review::comments::CommentId; const COMMENT_ID_MAPPING_KEY: &str = "comment_id";const ENTITY_ID_MAPPING_KEY: &str = "entity_id";const WINDOW_ID_MAPPING_KEY: &str = "window_id"; #[derive(Debug)]pub struct EmbeddedCommentSpace {    // We unfortunately need to store a string version of the ID    // in order to return it in EmbeddedItem::hashed_id()    id_string: String,    editor_entity_id: EntityId,    window_id: WindowId,} impl EmbeddedCommentSpace {    fn new(id: CommentId, editor_entity_id: EntityId, window_id: WindowId) -> Self {        Self {            id_string: id.to_string(),            editor_entity_id,            window_id,        }    }     // Fetch the underlying comment editor view    fn get_comment_editor(&self, app: &AppContext) -> Option<ViewHandle<CommentEditor>> {        app.view_with_id::<CommentEditor>(self.window_id, self.editor_entity_id)    }} impl EmbeddedItem for EmbeddedCommentSpace {    fn layout(&self, _text_layout: &TextLayout, app: &AppContext) -> Box<dyn LaidOutEmbeddedItem> {        let comment_editor = self.get_comment_editor(app);        if comment_editor.is_none() {            report_error!(                "EmbeddedComment can't layout missing comment editor",                extra: { "comment_id" => ?self.id_string }            );        };         let size = comment_editor            .and_then(|editor| editor.read(app, |editor, _ctx| editor.get_laid_out_size()))            .unwrap_or_else(|| {                report_error!(                    "Didn't find laid out size for editor",                    extra: { "editor_id" => ?self.id_string }                );                Vector2F::new(100.0, 24.0)            });         Box::new(LaidOutEmbeddedCommentSpace { size })    }     fn hashed_id(&self) -> &str {        self.id_string.as_str()    }     fn to_mapping(&self, _style: MarkdownStyle) -> Mapping {        let mut map = Mapping::new();        let comment_id = self.id_string.clone();        let editor_entity_id = self.editor_entity_id.to_string();        let window_id = self.window_id.to_string();        map.insert(COMMENT_ID_MAPPING_KEY.into(), comment_id.into());        map.insert(ENTITY_ID_MAPPING_KEY.into(), editor_entity_id.into());        map.insert(WINDOW_ID_MAPPING_KEY.into(), window_id.into());        map    }     fn to_rich_format(&self, app: &AppContext) -> EmbeddedItemRichFormat<'_> {        let text = match self.get_comment_editor(app) {            Some(editor) => editor.read(app, |editor, app| editor.comment_text(app)),            _ => String::new(),        };         EmbeddedItemRichFormat {            plain_text: text.to_string(),            html: EmbeddedItemHTMLRepresentation {                element_name: "div",                content: text.to_string(),                attributes: HashMap::new(),            },        }    }} #[derive(Debug)]pub struct LaidOutEmbeddedCommentSpace {    pub size: Vector2F,} impl LaidOutEmbeddedItem for LaidOutEmbeddedCommentSpace {    fn height(&self) -> Pixels {        Pixels::new(self.size.y())    }     fn size(&self) -> Vector2F {        self.size    }     fn first_line_bound(&self) -> Vector2F {        vec2f(self.size.x(), 24.0)    }     fn element(        &self,        _state: &RenderState,        viewport_item: ViewportItem,        _model: Option<&dyn EmbeddedItemModel>,        _ctx: &AppContext,    ) -> Box<dyn RenderableBlock> {        // Just create a spacer - no child view rendering here        Box::new(RenderableEmbeddedCommentSpace::new(viewport_item))    }     fn spacing(&self) -> BlockSpacing {        BlockSpacing::default()    }     fn as_any(&self) -> &dyn Any {        self    }} pub struct RenderableEmbeddedCommentSpace {    viewport_item: ViewportItem,} impl RenderableEmbeddedCommentSpace {    pub(crate) fn new(viewport_item: ViewportItem) -> Self {        Self { viewport_item }    }} impl RenderableBlock for RenderableEmbeddedCommentSpace {    fn viewport_item(&self) -> &ViewportItem {        &self.viewport_item    }     fn layout(&mut self, _model: &RenderState, _ctx: &mut LayoutContext, _app: &AppContext) {        // No-op: this is just a spacer, the actual editor is laid out by EditorWrapper    }     fn paint(&mut self, _model: &RenderState, _ctx: &mut RenderContext, _app: &AppContext) {        // No-op: this is just empty space, the actual editor is painted by EditorWrapper    }     fn dispatch_event(        &mut self,        _model: &RenderState,        _event: &DispatchedEvent,        _ctx: &mut EventContext,        _app: &AppContext,    ) -> bool {        // No interactivity: events are handled by the editor rendered by EditorWrapper        false    }     fn is_embedded_comment(&self) -> bool {        true    }} /// The embedded item transformation for comments.#[cfg_attr(not(test), allow(unused))] // TODO(CODE-1464): use thispub(super) fn comment_embedded_item_conversion(    mut mapping: serde_yaml::Mapping,) -> Option<Arc<dyn EmbeddedItem>> {    use serde_yaml::Value;    let Some(Value::String(comment_uuid)) =        mapping.remove(&Value::String(COMMENT_ID_MAPPING_KEY.to_string()))    else {        report_error!("Unable to deserialize embedded comment ID");        return None;    };    let Some(Value::String(entity_id)) =        mapping.remove(&Value::String(ENTITY_ID_MAPPING_KEY.to_string()))    else {        report_error!("Unable to deserialize embedded comment entity ID");        return None;    };    let Some(Value::String(window_id)) =        mapping.remove(&Value::String(WINDOW_ID_MAPPING_KEY.to_string()))    else {        report_error!("Unable to deserialize embedded comment window ID");        return None;    };     let comment_id = CommentId::from_uuid(        Uuid::from_str(&comment_uuid)            .map_err(|e| {                report_error!(                    anyhow::Error::new(e).context("Unable to parse comment ID"),                    extra: { "comment_id" => %comment_uuid }                );            })            .ok()?,    );    let entity_id = EntityId::from_usize(        entity_id            .parse::<usize>()            .map_err(|e| {                report_error!(                    anyhow::Error::new(e).context("Unable to parse entity ID"),                    extra: { "entity_id" => %entity_id }                );            })            .ok()?,    );    let window_id = WindowId::from_usize(        window_id            .parse::<usize>()            .map_err(|e| {                report_error!(                    anyhow::Error::new(e).context("Unable to parse window ID"),                    extra: { "window_id" => %window_id }                );            })            .ok()?,    );    Some(Arc::new(EmbeddedCommentSpace::new(        comment_id, entity_id, window_id,    )))} #[cfg(test)]#[path = "embedded_comment_tests.rs"]mod tests;