fresh_core/
text_property.rs

1//! Text properties for embedding metadata in text ranges
2//!
3//! This module provides Emacs-style text properties that allow embedding
4//! arbitrary metadata (like source locations, severity levels, etc.) in
5//! specific ranges of text. This is essential for virtual buffers where
6//! each line might represent a diagnostic, search result, or other structured data.
7
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::ops::Range;
11
12/// A text property that associates metadata with a range of text
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
14#[ts(export)]
15pub struct TextProperty {
16    /// Start byte offset (inclusive)
17    pub start: usize,
18    /// End byte offset (exclusive)
19    pub end: usize,
20    /// Arbitrary properties as key-value pairs
21    #[ts(type = "Record<string, any>")]
22    pub properties: HashMap<String, serde_json::Value>,
23}
24
25impl TextProperty {
26    /// Create a new text property for a range
27    pub fn new(start: usize, end: usize) -> Self {
28        Self {
29            start,
30            end,
31            properties: HashMap::new(),
32        }
33    }
34
35    /// Add a property
36    pub fn with_property(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
37        self.properties.insert(key.into(), value);
38        self
39    }
40
41    /// Set multiple properties at once
42    pub fn with_properties(mut self, props: HashMap<String, serde_json::Value>) -> Self {
43        self.properties.extend(props);
44        self
45    }
46
47    /// Check if this property range contains a byte position
48    pub fn contains(&self, pos: usize) -> bool {
49        pos >= self.start && pos < self.end
50    }
51
52    /// Check if this property range overlaps with another range
53    pub fn overlaps(&self, range: &Range<usize>) -> bool {
54        self.start < range.end && self.end > range.start
55    }
56
57    /// Get a property value by key
58    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
59        self.properties.get(key)
60    }
61
62    /// Get a property as a specific type
63    pub fn get_as<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
64        self.properties
65            .get(key)
66            .and_then(|v| serde_json::from_value(v.clone()).ok())
67    }
68}
69
70/// An entry with text and its properties
71#[derive(Debug, Clone, Serialize, Deserialize, ts_rs::TS)]
72#[ts(export)]
73pub struct TextPropertyEntry {
74    /// The text content
75    pub text: String,
76    /// Properties for this text
77    #[ts(type = "Record<string, any>")]
78    pub properties: HashMap<String, serde_json::Value>,
79}
80
81impl TextPropertyEntry {
82    /// Create a new entry with just text
83    pub fn text(text: impl Into<String>) -> Self {
84        Self {
85            text: text.into(),
86            properties: HashMap::new(),
87        }
88    }
89
90    /// Add a property
91    pub fn with_property(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
92        self.properties.insert(key.into(), value);
93        self
94    }
95
96    /// Set multiple properties
97    pub fn with_properties(mut self, props: HashMap<String, serde_json::Value>) -> Self {
98        self.properties = props;
99        self
100    }
101}