Skip to main content

cdx_core/extensions/collaboration/
thread.rs

1//! Comment thread management for Codex documents.
2
3use serde::{Deserialize, Serialize};
4
5use super::Comment;
6
7/// A collection of comments organized by thread.
8#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct CommentThread {
11    /// All comments in the thread.
12    pub comments: Vec<Comment>,
13}
14
15impl CommentThread {
16    /// Create a new empty thread.
17    #[must_use]
18    pub fn new() -> Self {
19        Self {
20            comments: Vec::new(),
21        }
22    }
23
24    /// Add a comment to the thread.
25    pub fn add(&mut self, comment: Comment) {
26        self.comments.push(comment);
27    }
28
29    /// Get a comment by ID.
30    #[must_use]
31    pub fn get(&self, id: &str) -> Option<&Comment> {
32        Self::find_comment(id, &self.comments)
33    }
34
35    /// Get a mutable comment by ID.
36    #[must_use]
37    pub fn get_mut(&mut self, id: &str) -> Option<&mut Comment> {
38        self.find_comment_mut(id)
39    }
40
41    /// Get comments for a specific block.
42    #[must_use]
43    pub fn for_block(&self, block_ref: &str) -> Vec<&Comment> {
44        self.comments
45            .iter()
46            .filter(|c| c.block_ref == block_ref)
47            .collect()
48    }
49
50    /// Get all unresolved comments.
51    #[must_use]
52    pub fn unresolved(&self) -> Vec<&Comment> {
53        self.comments.iter().filter(|c| !c.resolved).collect()
54    }
55
56    /// Get all resolved comments.
57    #[must_use]
58    pub fn resolved(&self) -> Vec<&Comment> {
59        self.comments.iter().filter(|c| c.resolved).collect()
60    }
61
62    /// Get the number of comments.
63    #[must_use]
64    pub fn len(&self) -> usize {
65        self.comments.len()
66    }
67
68    /// Check if the thread is empty.
69    #[must_use]
70    pub fn is_empty(&self) -> bool {
71        self.comments.is_empty()
72    }
73
74    /// Find a comment recursively.
75    fn find_comment<'a>(id: &str, comments: &'a [Comment]) -> Option<&'a Comment> {
76        for comment in comments {
77            if comment.id == id {
78                return Some(comment);
79            }
80            if let Some(found) = Self::find_comment(id, &comment.replies) {
81                return Some(found);
82            }
83        }
84        None
85    }
86
87    /// Find a mutable comment.
88    fn find_comment_mut(&mut self, id: &str) -> Option<&mut Comment> {
89        // Note: Can't recurse into replies with mutable reference easily
90        // This is a limitation of the current implementation
91        self.comments.iter_mut().find(|comment| comment.id == id)
92    }
93}