1use gpui::{Hsla, SharedString};
2
3use gpui_kit::base::text::editor_host::{LinkMark, TextMark};
4
5#[non_exhaustive]
7#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
8pub enum MarkKind {
9 Bold,
10 Italic,
11 Strikethrough,
12 Underline,
13 Code,
14 Highlight,
15 Link,
16}
17
18impl MarkKind {
19 pub(crate) fn is_set(self, mark: &TextMark) -> bool {
20 match self {
21 Self::Bold => mark.bold,
22 Self::Italic => mark.italic,
23 Self::Strikethrough => mark.strikethrough,
24 Self::Underline => mark.underline,
25 Self::Code => mark.code,
26 Self::Highlight => mark.highlight.is_some(),
27 Self::Link => mark.link.is_some(),
28 }
29 }
30
31 pub(crate) fn extends_at_boundary(self) -> bool {
33 !matches!(self, Self::Link | Self::Code)
34 }
35}
36
37#[derive(Clone, Default, PartialEq, Debug)]
39pub struct MarkSet(pub(crate) TextMark);
40
41impl MarkSet {
42 pub fn has(&self, kind: MarkKind) -> bool {
43 kind.is_set(&self.0)
44 }
45
46 pub fn highlight(&self) -> Option<Hsla> {
47 self.0.highlight
48 }
49
50 pub fn link_url(&self) -> Option<&SharedString> {
51 self.0.link.as_ref().map(|link| &link.url)
52 }
53
54 pub fn is_empty(&self) -> bool {
55 self.0 == TextMark::default()
56 }
57}
58
59#[derive(Clone, Copy, PartialEq, Eq, Debug)]
61pub enum MarkState {
62 Off,
63 On,
64 Mixed,
65}
66
67#[derive(Clone, Default, PartialEq, Debug)]
69pub struct MarkChange {
70 set: Vec<MarkKind>,
71 clear: Vec<MarkKind>,
72 link: Option<LinkMark>,
73 highlight: Option<Hsla>,
74}
75
76pub(crate) fn default_highlight() -> Hsla {
78 gpui::rgb(0xfef08a).into()
79}
80
81impl MarkChange {
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub fn set(mut self, kind: MarkKind) -> Self {
87 self.clear.retain(|k| *k != kind);
88 self.set.push(kind);
89 self
90 }
91
92 pub fn clear(mut self, kind: MarkKind) -> Self {
93 self.set.retain(|k| *k != kind);
94 self.clear.push(kind);
95 self
96 }
97
98 pub fn with_link(mut self, url: impl Into<SharedString>, title: Option<SharedString>) -> Self {
99 self.link = Some(LinkMark {
100 url: url.into(),
101 identifier: None,
102 title,
103 });
104 self.set(MarkKind::Link)
105 }
106
107 pub fn with_highlight(mut self, color: Hsla) -> Self {
108 self.highlight = Some(color);
109 self.set(MarkKind::Highlight)
110 }
111
112 pub(crate) fn apply(&self, mark: &TextMark) -> TextMark {
113 let mut out = self.apply_raw(mark);
114 if out.code {
117 out.bold = false;
118 out.italic = false;
119 out.strikethrough = false;
120 out.underline = false;
121 out.highlight = None;
122 }
123 out
124 }
125
126 fn apply_raw(&self, mark: &TextMark) -> TextMark {
127 let mut out = mark.clone();
128 for kind in &self.clear {
129 match kind {
130 MarkKind::Bold => out.bold = false,
131 MarkKind::Italic => out.italic = false,
132 MarkKind::Strikethrough => out.strikethrough = false,
133 MarkKind::Underline => out.underline = false,
134 MarkKind::Code => out.code = false,
135 MarkKind::Highlight => out.highlight = None,
136 MarkKind::Link => out.link = None,
137 }
138 }
139 for kind in &self.set {
140 match kind {
141 MarkKind::Bold => out.bold = true,
142 MarkKind::Italic => out.italic = true,
143 MarkKind::Strikethrough => out.strikethrough = true,
144 MarkKind::Underline => out.underline = true,
145 MarkKind::Code => out.code = true,
146 MarkKind::Highlight => {
147 out.highlight = Some(self.highlight.unwrap_or_else(default_highlight))
148 }
149 MarkKind::Link => {
150 if let Some(link) = &self.link {
151 out.link = Some(link.clone());
152 }
153 }
154 }
155 }
156 out
157 }
158}