ratatui_core/symbols/merge.rs
1//! This module provides strategies for merging symbols in a layout.
2//!
3//! It defines the [`MergeStrategy`] enum, which allows for different behaviors when combining
4//! symbols, such as replacing the previous symbol, merging them if an exact match exists, or using
5//! a fuzzy match to find the closest representation.
6//!
7//! The merging strategies are useful for [collapsing borders] in layouts, where multiple symbols
8//! may need to be combined to create a single, coherent border representation.
9//!
10//! [collapsing borders]: https://ratatui.rs/recipes/layout/collapse-borders
11use core::str::FromStr;
12
13/// A strategy for merging two symbols into one.
14///
15/// This enum defines how two symbols should be merged together, allowing for different behaviors
16/// when combining symbols, such as replacing the previous symbol, merging them if an exact match
17/// exists, or using a fuzzy match to find the closest representation.
18///
19/// This is useful for [collapsing borders] in layouts, where multiple symbols may need to be
20/// combined to create a single, coherent border representation.
21///
22/// Not all combinations of box drawing symbols can be represented as a single unicode character, as
23/// many of them are not defined in the [Box Drawing Unicode block]. This means that some merging
24/// strategies will not yield a valid unicode character. The [`MergeStrategy::Replace`] strategy
25/// will be used as a fallback in such cases, replacing the previous symbol with the next one.
26///
27/// Specifically, the following combinations of box drawing symbols are not defined in the [Box
28/// Drawing Unicode block]:
29///
30/// - Combining any dashed segments with any non dashed segments (e.g. `╎` with `─` or `━`).
31/// - Combining any rounded segments with any other segments (e.g. `╯` with `─` or `━`).
32/// - Combining any double segments with any thick segments (e.g. `═` with `┃` or `━`).
33/// - Combining some double segments with some plain segments (e.g. `┐` with `╔`).
34///
35/// The merging strategies include:
36///
37/// - [`Self::Replace`]: Replaces the previous symbol with the next one.
38/// - [`Self::Exact`]: Merges symbols only if an exact composite unicode character exists, falling
39/// back to [`Self::Replace`] if not.
40/// - [`Self::Fuzzy`]: Merges symbols even if an exact composite unicode character doesn't exist,
41/// using the closest match, and falling back to [`Self::Exact`] if necessary.
42///
43/// See [`Cell::merge_symbol`] for how to use this strategy in practice, and
44/// [`Block::merge_borders`] for a more concrete example of merging borders in a layout.
45///
46/// # Examples
47///
48/// ```
49/// # use ratatui_core::symbols::merge::MergeStrategy;
50///
51/// assert_eq!(MergeStrategy::Replace.merge("│", "━"), "━");
52/// assert_eq!(MergeStrategy::Exact.merge("│", "─"), "┼");
53/// assert_eq!(MergeStrategy::Fuzzy.merge("┘", "╔"), "╬");
54/// ```
55///
56/// [Box Drawing Unicode block]: https://en.wikipedia.org/wiki/Box_Drawing
57/// [collapsing borders]: https://ratatui.rs/recipes/layout/collapse-borders
58/// [`Block::merge_borders`]:
59/// https://docs.rs/ratatui/latest/ratatui/widgets/block/struct.Block.html#method.merge_borders
60/// [`Cell::merge_symbol`]: crate::buffer::Cell::merge_symbol
61#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
62pub enum MergeStrategy {
63 /// Replaces the previous symbol with the next one.
64 ///
65 /// This strategy simply replaces the previous symbol with the next one, without attempting to
66 /// merge them. This is useful when you want to ensure that the last rendered symbol takes
67 /// precedence over the previous one, regardless of their compatibility.
68 ///
69 /// The following diagram illustrates how this would apply to several overlapping blocks where
70 /// the thick bordered blocks are rendered last, replacing the previous symbols:
71 ///
72 /// ```text
73 /// ┌───┐ ┌───┐ ┌───┏━━━┓┌───┐
74 /// │ │ │ │ │ ┃ ┃│ │
75 /// │ │ │ ┏━━━┓│ ┃ ┃│ │
76 /// │ │ │ ┃ │ ┃│ ┃ ┃│ │
77 /// └───┏━━━┓└─┃─┘ ┃└───┗━━━┛┏━━━┓
78 /// ┃ ┃ ┃ ┃ ┃ ┃
79 /// ┃ ┃ ┗━━━┛ ┃ ┃
80 /// ┃ ┃ ┃ ┃
81 /// ┗━━━┛ ┗━━━┛
82 /// ```
83 ///
84 /// # Example
85 ///
86 /// ```
87 /// # use ratatui_core::symbols::merge::MergeStrategy;
88 /// let strategy = MergeStrategy::Replace;
89 /// assert_eq!(strategy.merge("│", "━"), "━");
90 /// ```
91 #[default]
92 Replace,
93
94 /// Merges symbols only if an exact composite unicode character exists.
95 ///
96 /// This strategy attempts to merge two symbols into a single composite unicode character if the
97 /// exact representation exists. If the required unicode symbol does not exist, it falls back to
98 /// [`MergeStrategy::Replace`], replacing the previous symbol with the next one.
99 ///
100 /// The following diagram illustrates how this would apply to several overlapping blocks where
101 /// the thick bordered blocks are rendered last, merging the previous symbols into a single
102 /// composite character. All combinations of the plain and thick segments exist, so these
103 /// symbols can be merged into a single character:
104 ///
105 /// ```text
106 /// ┌───┐ ┌───┐ ┌───┲━━━┓┌───┐
107 /// │ │ │ │ │ ┃ ┃│ │
108 /// │ │ │ ┏━┿━┓│ ┃ ┃│ │
109 /// │ │ │ ┃ │ ┃│ ┃ ┃│ │
110 /// └───╆━━━┓└─╂─┘ ┃└───┺━━━┛┢━━━┪
111 /// ┃ ┃ ┃ ┃ ┃ ┃
112 /// ┃ ┃ ┗━━━┛ ┃ ┃
113 /// ┃ ┃ ┃ ┃
114 /// ┗━━━┛ ┗━━━┛
115 /// ```
116 ///
117 /// The following diagram illustrates how this would apply to several overlapping blocks where
118 /// the characters don't have a composite unicode character, so the previous symbols are
119 /// replaced by the next one:
120 ///
121 /// ```text
122 /// ┌───┐ ┌───┐ ┌───╔═══╗┌───┐
123 /// │ │ │ │ │ ║ ║│ │
124 /// │ │ │ ╔═╪═╗│ ║ ║│ │
125 /// │ │ │ ║ │ ║│ ║ ║│ │
126 /// └───╔═══╗└─╫─┘ ║└───╚═══╝╔═══╗
127 /// ║ ║ ║ ║ ║ ║
128 /// ║ ║ ╚═══╝ ║ ║
129 /// ║ ║ ║ ║
130 /// ╚═══╝ ╚═══╝
131 /// ┌───┐ ┌───┐ ┌───╭───╮┌───┐
132 /// │ │ │ │ │ │ ││ │
133 /// │ │ │ ╭─┼─╮│ │ ││ │
134 /// │ │ │ │ │ ││ │ ││ │
135 /// └───╭───╮└─┼─┘ │└───╰───╯╭───╮
136 /// │ │ │ │ │ │
137 /// │ │ ╰───╯ │ │
138 /// │ │ │ │
139 /// ╰───╯ ╰───╯
140 /// ```
141 ///
142 /// # Example
143 ///
144 /// ```
145 /// # use ratatui_core::symbols::merge::MergeStrategy;
146 /// let strategy = MergeStrategy::Exact;
147 /// assert_eq!(strategy.merge("│", "━"), "┿"); // exact match exists
148 /// assert_eq!(strategy.merge("┘", "╔"), "╔"); // no exact match, falls back to Replace
149 /// ```
150 Exact,
151
152 /// Merges symbols even if an exact composite unicode character doesn't exist, using the closest
153 /// match.
154 ///
155 /// If required unicode symbol exists, acts exactly like [`MergeStrategy::Exact`], if not, the
156 /// following rules are applied:
157 ///
158 /// 1. There are no characters that combine dashed with plain / thick segments, so we replace
159 /// dashed segments with plain and thick dashed segments with thick. The following diagram
160 /// shows how this would apply to merging a block with thick dashed borders over a block with
161 /// plain dashed borders:
162 ///
163 /// ```text
164 /// ┌╌╌╌┐ ┌╌╌╌┐ ┌╌╌╌┲╍╍╍┓┌╌╌╌┐
165 /// ╎ ╎ ╎ ╎ ╎ ╏ ╏╎ ╎
166 /// ╎ ╎ ╎ ┏╍┿╍┓╎ ╏ ╏╎ ╎
167 /// ╎ ╎ ╎ ╏ ╎ ╏╎ ╏ ╏╎ ╎
168 /// └╌╌╌╆╍╍╍┓└╌╂╌┘ ╏└╌╌╌┺╍╍╍┛┢╍╍╍┪
169 /// ╏ ╏ ╏ ╏ ╏ ╏
170 /// ╏ ╏ ┗╍╍╍┛ ╏ ╏
171 /// ╏ ╏ ╏ ╏
172 /// ┗╍╍╍┛ ┗╍╍╍┛
173 /// ```
174 ///
175 /// 2. There are no characters that combine rounded segments with other segments, so we replace
176 /// rounded segments with plain. The following diagram shows how this would apply to merging
177 /// a block with rounded corners over a block with plain corners:
178 ///
179 /// ```text
180 /// ┌───┐ ┌───┐ ┌───┬───╮┌───┐
181 /// │ │ │ │ │ │ ││ │
182 /// │ │ │ ╭─┼─╮│ │ ││ │
183 /// │ │ │ │ │ ││ │ ││ │
184 /// └───┼───╮└─┼─┘ │└───┴───╯├───┤
185 /// │ │ │ │ │ │
186 /// │ │ ╰───╯ │ │
187 /// │ │ │ │
188 /// ╰───╯ ╰───╯
189 /// ```
190 ///
191 /// 3. There are no symbols that combine thick and double borders, so we replace all double
192 /// segments with thick or all thick with double. The second symbol parameter takes
193 /// precedence in choosing whether to use double or thick. The following diagram shows how
194 /// this would apply to merging a block with double borders over a block with thick borders
195 /// and then the reverse (merging a block with thick borders over a block with double
196 /// borders):
197 ///
198 /// ```text
199 /// ┏━━━┓ ┏━━━┓ ┏━━━╦═══╗┏━━━┓
200 /// ┃ ┃ ┃ ┃ ┃ ║ ║┃ ┃
201 /// ┃ ┃ ┃ ╔═╬═╗┃ ║ ║┃ ┃
202 /// ┃ ┃ ┃ ║ ┃ ║┃ ║ ║┃ ┃
203 /// ┗━━━╬═══╗┗━╬━┛ ║┗━━━╩═══╝╠═══╣
204 /// ║ ║ ║ ║ ║ ║
205 /// ║ ║ ╚═══╝ ║ ║
206 /// ║ ║ ║ ║
207 /// ╚═══╝ ╚═══╝
208 ///
209 /// ╔═══╗ ╔═══╗ ╔═══┳━━━┓╔═══╗
210 /// ║ ║ ║ ║ ║ ┃ ┃║ ║
211 /// ║ ║ ║ ┏━╋━┓║ ┃ ┃║ ║
212 /// ║ ║ ║ ┃ ║ ┃║ ┃ ┃║ ║
213 /// ╚═══╋━━━┓╚═╋═╝ ┃╚═══┻━━━┛┣━━━┫
214 /// ┃ ┃ ┃ ┃ ┃ ┃
215 /// ┃ ┃ ┗━━━┛ ┃ ┃
216 /// ┃ ┃ ┃ ┃
217 /// ┗━━━┛ ┗━━━┛
218 /// ```
219 ///
220 /// 4. Some combinations of double and plain don't exist, so if the symbol is still
221 /// unrepresentable, change all plain segments with double or all double with plain. The
222 /// second symbol parameter takes precedence in choosing whether to use double or plain. The
223 /// following diagram shows how this would apply to merging a block with double borders over
224 /// a block with plain borders and then the reverse (merging a block with plain borders over
225 /// a block with double borders):
226 ///
227 /// ```text
228 /// ┌───┐ ┌───┐ ┌───╦═══╗┌───┐
229 /// │ │ │ │ │ ║ ║│ │
230 /// │ │ │ ╔═╪═╗│ ║ ║│ │
231 /// │ │ │ ║ │ ║│ ║ ║│ │
232 /// └───╬═══╗└─╫─┘ ║└───╩═══╝╠═══╣
233 /// ║ ║ ║ ║ ║ ║
234 /// ║ ║ ╚═══╝ ║ ║
235 /// ║ ║ ║ ║
236 /// ╚═══╝ ╚═══╝
237 /// ╔═══╗ ╔═══╗ ╔═══┬───┐╔═══╗
238 /// ║ ║ ║ ║ ║ │ │║ ║
239 /// ║ ║ ║ ┌─╫─┐║ │ │║ ║
240 /// ║ ║ ║ │ ║ │║ │ │║ ║
241 /// ╚═══┼───┐╚═╪═╝ │╚═══┴───┘├───┤
242 /// │ │ │ │ │ │
243 /// │ │ └───┘ │ │
244 /// │ │ │ │
245 /// └───┘ └───┘
246 /// ```
247 ///
248 /// # Examples
249 ///
250 /// ```
251 /// # use ratatui_core::symbols::merge::MergeStrategy;
252 /// let strategy = MergeStrategy::Fuzzy;
253 ///
254 /// // exact matches are merged normally
255 /// assert_eq!(strategy.merge("┌", "┐"), "┬");
256 ///
257 /// // dashed segments are replaced with plain
258 /// assert_eq!(strategy.merge("╎", "╍"), "┿");
259 ///
260 /// // rounded segments are replaced with plain
261 /// assert_eq!(strategy.merge("┘", "╭"), "┼");
262 ///
263 /// // double and thick segments are merged based on the second symbol
264 /// assert_eq!(strategy.merge("┃", "═"), "╬");
265 /// assert_eq!(strategy.merge("═", "┃"), "╋");
266 ///
267 /// // combinations of double with plain that don't exist are merged based on the second symbol
268 /// assert_eq!(strategy.merge("┐", "╔"), "╦");
269 /// assert_eq!(strategy.merge("╔", "┐"), "┬");
270 /// ```
271 Fuzzy,
272}
273
274impl MergeStrategy {
275 /// Merges two symbols using this merge strategy.
276 ///
277 /// This method takes two string slices representing the previous and next symbols, and
278 /// returns a string slice representing the merged symbol based on the merge strategy.
279 ///
280 /// If either of the symbols are not in the [Box Drawing Unicode block], the `next` symbol is
281 /// returned as is. If both symbols are valid, they are merged according to the rules defined
282 /// in the [`MergeStrategy`].
283 ///
284 /// Most code using this method will use the [`Cell::merge_symbol`] method, which uses this
285 /// method internally to merge the symbols of a cell.
286 ///
287 /// # Example
288 ///
289 /// ```
290 /// # use ratatui_core::symbols::merge::MergeStrategy;
291 ///
292 /// let strategy = MergeStrategy::Fuzzy;
293 /// assert_eq!(strategy.merge("┌", "┐"), "┬"); // merges to a single character
294 /// assert_eq!(strategy.merge("┘", "╭"), "┼"); // replaces rounded with plain
295 /// assert_eq!(strategy.merge("╎", "╍"), "┿"); // replaces dashed with plain
296 /// assert_eq!(strategy.merge("┐", "╔"), "╦"); // merges double with plain
297 /// assert_eq!(strategy.merge("╔", "┐"), "┬"); // merges plain with double
298 /// ```
299 ///
300 /// [Box Drawing Unicode block]: https://en.wikipedia.org/wiki/Box_Drawing
301 /// [`Cell::merge_symbol`]: crate::buffer::Cell::merge_symbol
302 pub fn merge<'a>(self, prev: &'a str, next: &'a str) -> &'a str {
303 // Replace should always just return the last symbol.
304 if self == Self::Replace {
305 return next;
306 }
307
308 match (BorderSymbol::from_str(prev), BorderSymbol::from_str(next)) {
309 (Ok(prev_symbol), Ok(next_symbol)) => prev_symbol
310 .merge(next_symbol, self)
311 .try_into()
312 .unwrap_or(next),
313 // Non-border symbols take precedence in strategies other than Replace.
314 (Err(_), Ok(_)) => prev,
315 (_, Err(_)) => next,
316 }
317 }
318}
319
320/// Represents a composite border symbol using individual line components.
321///
322/// This is an internal type for now specifically used to make the merge logic easier to implement.
323/// At some point in the future, we might make a similar type public to represent the
324#[derive(Debug, Copy, Clone, PartialEq, Eq)]
325struct BorderSymbol {
326 right: LineStyle,
327 up: LineStyle,
328 left: LineStyle,
329 down: LineStyle,
330}
331
332impl BorderSymbol {
333 /// Creates a new [`BorderSymbol`], based on individual line styles.
334 #[must_use]
335 const fn new(right: LineStyle, up: LineStyle, left: LineStyle, down: LineStyle) -> Self {
336 Self {
337 right,
338 up,
339 left,
340 down,
341 }
342 }
343
344 /// Finds the closest representation of the [`BorderSymbol`], that has a corresponding unicode
345 /// character.
346 #[must_use]
347 fn fuzzy(mut self, other: Self) -> Self {
348 #[allow(clippy::enum_glob_use)]
349 use LineStyle::*;
350
351 // Dashes only include vertical and horizontal lines.
352 if !self.is_straight() {
353 self = self
354 .replace(DoubleDash, Plain)
355 .replace(TripleDash, Plain)
356 .replace(QuadrupleDash, Plain)
357 .replace(DoubleDashThick, Thick)
358 .replace(TripleDashThick, Thick)
359 .replace(QuadrupleDashThick, Thick);
360 }
361
362 // Rounded has only corner variants.
363 if !self.is_corner() {
364 self = self.replace(Rounded, Plain);
365 }
366
367 // There are no Double + Thick variants.
368 if self.contains(Double) && self.contains(Thick) {
369 // Decide whether to use Double or Thick, based on the last merged-in symbol.
370 if other.contains(Double) {
371 self = self.replace(Thick, Double);
372 } else {
373 self = self.replace(Double, Thick);
374 }
375 }
376
377 // Some Plain + Double variants don't exist.
378 if <&str>::try_from(self).is_err() {
379 // Decide whether to use Double or Plain, based on the last merged-in symbol.
380 if other.contains(Double) {
381 self = self.replace(Plain, Double);
382 } else {
383 self = self.replace(Double, Plain);
384 }
385 }
386 self
387 }
388
389 /// Return true only if the symbol is a line and both parts have the same [`LineStyle`].
390 fn is_straight(self) -> bool {
391 use LineStyle::Nothing;
392 (self.up == self.down && self.left == self.right)
393 && (self.up == Nothing || self.left == Nothing)
394 }
395
396 /// Return true only if the symbol is a corner and both parts have the same [`LineStyle`].
397 fn is_corner(self) -> bool {
398 use LineStyle::Nothing;
399 match (self.up, self.right, self.down, self.left) {
400 (up, right, Nothing, Nothing) => up == right,
401 (Nothing, right, down, Nothing) => right == down,
402 (Nothing, Nothing, down, left) => down == left,
403 (up, Nothing, Nothing, left) => up == left,
404 _ => false,
405 }
406 }
407
408 /// Checks if any of the line components making the [`BorderSymbol`] matches the `style`.
409 fn contains(self, style: LineStyle) -> bool {
410 self.up == style || self.right == style || self.down == style || self.left == style
411 }
412
413 /// Replaces all line styles matching `from` by `to`.
414 #[must_use]
415 fn replace(mut self, from: LineStyle, to: LineStyle) -> Self {
416 self.up = if self.up == from { to } else { self.up };
417 self.right = if self.right == from { to } else { self.right };
418 self.down = if self.down == from { to } else { self.down };
419 self.left = if self.left == from { to } else { self.left };
420 self
421 }
422
423 /// Merges two border symbols into one.
424 fn merge(self, other: Self, strategy: MergeStrategy) -> Self {
425 let exact_result = Self::new(
426 self.right.merge(other.right),
427 self.up.merge(other.up),
428 self.left.merge(other.left),
429 self.down.merge(other.down),
430 );
431 match strategy {
432 MergeStrategy::Replace => other,
433 MergeStrategy::Fuzzy => exact_result.fuzzy(other),
434 MergeStrategy::Exact => exact_result,
435 }
436 }
437}
438
439#[derive(thiserror::Error, Debug, PartialEq, Eq)]
440enum BorderSymbolError {
441 #[error("cannot parse &str `{0}` to BorderSymbol")]
442 CannotParse(alloc::string::String),
443 #[error("cannot convert BorderSymbol `{0:#?}` to &str: no such symbol exists")]
444 Unrepresentable(BorderSymbol),
445}
446
447/// A visual style defining the appearance of a single line making up a block border.
448///
449/// This is an internal type used to represent the different styles of lines that can be used in
450/// border symbols.
451///
452/// At some point in the future, we might make this type (or a similar one) public to allow users to
453/// work with line styles directly, but for now, it is used internally only to simplify the merge
454/// logic of border symbols.
455#[derive(Debug, Clone, Copy, PartialEq, Eq)]
456enum LineStyle {
457 /// Represents the absence of a line.
458 Nothing,
459
460 /// A single line (e.g. `─`, `│`).
461 Plain,
462
463 /// A rounded line style, only applicable in corner symbols (e.g. `╭`, `╯`).
464 Rounded,
465
466 /// A double line (e.g. `═`, `║`).
467 Double,
468
469 /// A thickened line (e.g. `━`, `┃`).
470 Thick,
471
472 /// A dashed line with a double dash pattern (e.g. `╌`, `╎`).
473 DoubleDash,
474
475 /// A thicker variant of the double dash (e.g. `╍`, `╏`)
476 DoubleDashThick,
477
478 /// A dashed line with a triple dash pattern (e.g. `┄`, `┆`).
479 TripleDash,
480
481 /// A thicker variant of the triple dash (e.g. `┅`, `┇`).
482 TripleDashThick,
483
484 /// A dashed line with four dashes (e.g. `┈`, `┊`).
485 QuadrupleDash,
486
487 /// A thicker variant of the quadruple dash (e.g. `┉`, `┋`).
488 QuadrupleDashThick,
489}
490
491impl LineStyle {
492 /// Merges line styles.
493 #[must_use]
494 pub fn merge(self, other: Self) -> Self {
495 if other == Self::Nothing { self } else { other }
496 }
497}
498
499// Defines a translation between `BorderSymbol` and the corresponding character.
500macro_rules! define_symbols {
501 (
502 $( $symbol:expr => ($right:ident, $up:ident, $left:ident, $down:ident) ),* $(,)?
503 ) => {
504
505 impl FromStr for BorderSymbol {
506 type Err = BorderSymbolError;
507 fn from_str(s: &str) -> Result<Self, Self::Err> {
508 use LineStyle::*;
509 use alloc::string::ToString;
510 match s {
511 $( $symbol => Ok(Self::new($right, $up, $left, $down)) ),* ,
512 _ => Err(BorderSymbolError::CannotParse(s.to_string())),
513 }
514 }
515 }
516
517 impl TryFrom<BorderSymbol> for &'static str {
518 type Error = BorderSymbolError;
519 fn try_from(value: BorderSymbol) -> Result<Self, Self::Error> {
520 use LineStyle::*;
521 match (value.right, value.up, value.left, value.down) {
522 $( ($right, $up, $left, $down) => Ok($symbol) ),* ,
523 _ => Err(BorderSymbolError::Unrepresentable(value)),
524 }
525 }
526 }
527 };
528}
529
530define_symbols!(
531 "─" => (Plain, Nothing, Plain, Nothing),
532 "━" => (Thick, Nothing, Thick, Nothing),
533 "│" => (Nothing, Plain, Nothing, Plain),
534 "┃" => (Nothing, Thick, Nothing, Thick),
535 "┄" => (TripleDash, Nothing, TripleDash, Nothing),
536 "┅" => (TripleDashThick, Nothing, TripleDashThick, Nothing),
537 "┆" => (Nothing, TripleDash, Nothing, TripleDash),
538 "┇" => (Nothing, TripleDashThick, Nothing, TripleDashThick),
539 "┈" => (QuadrupleDash, Nothing, QuadrupleDash, Nothing),
540 "┉" => (QuadrupleDashThick, Nothing, QuadrupleDashThick, Nothing),
541 "┊" => (Nothing, QuadrupleDash, Nothing, QuadrupleDash),
542 "┋" => (Nothing, QuadrupleDashThick, Nothing, QuadrupleDashThick),
543 "┌" => (Plain, Nothing, Nothing, Plain),
544 "┍" => (Thick, Nothing, Nothing, Plain),
545 "┎" => (Plain, Nothing, Nothing, Thick),
546 "┏" => (Thick, Nothing, Nothing, Thick),
547 "┐" => (Nothing, Nothing, Plain, Plain),
548 "┑" => (Nothing, Nothing, Thick, Plain),
549 "┒" => (Nothing, Nothing, Plain, Thick),
550 "┓" => (Nothing, Nothing, Thick, Thick),
551 "└" => (Plain, Plain, Nothing, Nothing),
552 "┕" => (Thick, Plain, Nothing, Nothing),
553 "┖" => (Plain, Thick, Nothing, Nothing),
554 "┗" => (Thick, Thick, Nothing, Nothing),
555 "┘" => (Nothing, Plain, Plain, Nothing),
556 "┙" => (Nothing, Plain, Thick, Nothing),
557 "┚" => (Nothing, Thick, Plain, Nothing),
558 "┛" => (Nothing, Thick, Thick, Nothing),
559 "├" => (Plain, Plain, Nothing, Plain),
560 "┝" => (Thick, Plain, Nothing, Plain),
561 "┞" => (Plain, Thick, Nothing, Plain),
562 "┟" => (Plain, Plain, Nothing, Thick),
563 "┠" => (Plain, Thick, Nothing, Thick),
564 "┡" => (Thick, Thick, Nothing, Plain),
565 "┢" => (Thick, Plain, Nothing, Thick),
566 "┣" => (Thick, Thick, Nothing, Thick),
567 "┤" => (Nothing, Plain, Plain, Plain),
568 "┥" => (Nothing, Plain, Thick, Plain),
569 "┦" => (Nothing, Thick, Plain, Plain),
570 "┧" => (Nothing, Plain, Plain, Thick),
571 "┨" => (Nothing, Thick, Plain, Thick),
572 "┩" => (Nothing, Thick, Thick, Plain),
573 "┪" => (Nothing, Plain, Thick, Thick),
574 "┫" => (Nothing, Thick, Thick, Thick),
575 "┬" => (Plain, Nothing, Plain, Plain),
576 "┭" => (Plain, Nothing, Thick, Plain),
577 "┮" => (Thick, Nothing, Plain, Plain),
578 "┯" => (Thick, Nothing, Thick, Plain),
579 "┰" => (Plain, Nothing, Plain, Thick),
580 "┱" => (Plain, Nothing, Thick, Thick),
581 "┲" => (Thick, Nothing, Plain, Thick),
582 "┳" => (Thick, Nothing, Thick, Thick),
583 "┴" => (Plain, Plain, Plain, Nothing),
584 "┵" => (Plain, Plain, Thick, Nothing),
585 "┶" => (Thick, Plain, Plain, Nothing),
586 "┷" => (Thick, Plain, Thick, Nothing),
587 "┸" => (Plain, Thick, Plain, Nothing),
588 "┹" => (Plain, Thick, Thick, Nothing),
589 "┺" => (Thick, Thick, Plain, Nothing),
590 "┻" => (Thick, Thick, Thick, Nothing),
591 "┼" => (Plain, Plain, Plain, Plain),
592 "┽" => (Plain, Plain, Thick, Plain),
593 "┾" => (Thick, Plain, Plain, Plain),
594 "┿" => (Thick, Plain, Thick, Plain),
595 "╀" => (Plain, Thick, Plain, Plain),
596 "╁" => (Plain, Plain, Plain, Thick),
597 "╂" => (Plain, Thick, Plain, Thick),
598 "╃" => (Plain, Thick, Thick, Plain),
599 "╄" => (Thick, Thick, Plain, Plain),
600 "╅" => (Plain, Plain, Thick, Thick),
601 "╆" => (Thick, Plain, Plain, Thick),
602 "╇" => (Thick, Thick, Thick, Plain),
603 "╈" => (Thick, Plain, Thick, Thick),
604 "╉" => (Plain, Thick, Thick, Thick),
605 "╊" => (Thick, Thick, Plain, Thick),
606 "╋" => (Thick, Thick, Thick, Thick),
607 "╌" => (DoubleDash, Nothing, DoubleDash, Nothing),
608 "╍" => (DoubleDashThick, Nothing, DoubleDashThick, Nothing),
609 "╎" => (Nothing, DoubleDash, Nothing, DoubleDash),
610 "╏" => (Nothing, DoubleDashThick, Nothing, DoubleDashThick),
611 "═" => (Double, Nothing, Double, Nothing),
612 "║" => (Nothing, Double, Nothing, Double),
613 "╒" => (Double, Nothing, Nothing, Plain),
614 "╓" => (Plain, Nothing, Nothing, Double),
615 "╔" => (Double, Nothing, Nothing, Double),
616 "╕" => (Nothing, Nothing, Double, Plain),
617 "╖" => (Nothing, Nothing, Plain, Double),
618 "╗" => (Nothing, Nothing, Double, Double),
619 "╘" => (Double, Plain, Nothing, Nothing),
620 "╙" => (Plain, Double, Nothing, Nothing),
621 "╚" => (Double, Double, Nothing, Nothing),
622 "╛" => (Nothing, Plain, Double, Nothing),
623 "╜" => (Nothing, Double, Plain, Nothing),
624 "╝" => (Nothing, Double, Double, Nothing),
625 "╞" => (Double, Plain, Nothing, Plain),
626 "╟" => (Plain, Double, Nothing, Double),
627 "╠" => (Double, Double, Nothing, Double),
628 "╡" => (Nothing, Plain, Double, Plain),
629 "╢" => (Nothing, Double, Plain, Double),
630 "╣" => (Nothing, Double, Double, Double),
631 "╤" => (Double, Nothing, Double, Plain),
632 "╥" => (Plain, Nothing, Plain, Double),
633 "╦" => (Double, Nothing, Double, Double),
634 "╧" => (Double, Plain, Double, Nothing),
635 "╨" => (Plain, Double, Plain, Nothing),
636 "╩" => (Double, Double, Double, Nothing),
637 "╪" => (Double, Plain, Double, Plain),
638 "╫" => (Plain, Double, Plain, Double),
639 "╬" => (Double, Double, Double, Double),
640 "╭" => (Rounded, Nothing, Nothing, Rounded),
641 "╮" => (Nothing, Nothing, Rounded, Rounded),
642 "╯" => (Nothing, Rounded, Rounded, Nothing),
643 "╰" => (Rounded, Rounded, Nothing, Nothing),
644 "╴" => (Nothing, Nothing, Plain, Nothing),
645 "╵" => (Nothing, Plain, Nothing, Nothing),
646 "╶" => (Plain, Nothing, Nothing, Nothing),
647 "╷" => (Nothing, Nothing, Nothing, Plain),
648 "╸" => (Nothing, Nothing, Thick, Nothing),
649 "╹" => (Nothing, Thick, Nothing, Nothing),
650 "╺" => (Thick, Nothing, Nothing, Nothing),
651 "╻" => (Nothing, Nothing, Nothing, Thick),
652 "╼" => (Thick, Nothing, Plain, Nothing),
653 "╽" => (Nothing, Plain, Nothing, Thick),
654 "╾" => (Plain, Nothing, Thick, Nothing),
655 "╿" => (Nothing, Thick, Nothing, Plain),
656);
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn replace_merge_strategy() {
664 let strategy = MergeStrategy::Replace;
665 let symbols = [
666 "─", "━", "│", "┃", "┄", "┅", "┆", "┇", "┈", "┉", "┊", "┋", "┌", "┍", "┎", "┏", "┐",
667 "┑", "┒", "┓", "└", "┕", "┖", "┗", "┘", "┙", "┚", "┛", "├", "┝", "┞", "┟", "┠", "┡",
668 "┢", "┣", "┤", "┥", "┦", "┧", "┨", "┩", "┪", "┫", "┬", "┭", "┮", "┯", "┰", "┱", "┲",
669 "┳", "┴", "┵", "┶", "┷", "┸", "┹", "┺", "┻", "┼", "┽", "┾", "┿", "╀", "╁", "╂", "╃",
670 "╄", "╅", "╆", "╇", "╈", "╉", "╊", "╋", "╌", "╍", "╎", "╏", "═", "║", "╒", "╓", "╔",
671 "╕", "╖", "╗", "╘", "╙", "╚", "╛", "╜", "╝", "╞", "╟", "╠", "╡", "╢", "╣", "╤", "╥",
672 "╦", "╧", "╨", "╩", "╪", "╫", "╬", "╭", "╮", "╯", "╰", "╴", "╵", "╶", "╷", "╸", "╹",
673 "╺", "╻", "╼", "╽", "╾", "╿", " ", "a", "b",
674 ];
675
676 for a in symbols {
677 for b in symbols {
678 assert_eq!(strategy.merge(a, b), b);
679 }
680 }
681 }
682
683 #[test]
684 fn exact_merge_strategy() {
685 let strategy = MergeStrategy::Exact;
686 assert_eq!(strategy.merge("┆", "─"), "─");
687 assert_eq!(strategy.merge("┏", "┆"), "┆");
688 assert_eq!(strategy.merge("╎", "┉"), "┉");
689 assert_eq!(strategy.merge("╎", "┉"), "┉");
690 assert_eq!(strategy.merge("┋", "┋"), "┋");
691 assert_eq!(strategy.merge("╷", "╶"), "┌");
692 assert_eq!(strategy.merge("╭", "┌"), "┌");
693 assert_eq!(strategy.merge("│", "┕"), "┝");
694 assert_eq!(strategy.merge("┏", "│"), "┝");
695 assert_eq!(strategy.merge("│", "┏"), "┢");
696 assert_eq!(strategy.merge("╽", "┕"), "┢");
697 assert_eq!(strategy.merge("│", "─"), "┼");
698 assert_eq!(strategy.merge("┘", "┌"), "┼");
699 assert_eq!(strategy.merge("┵", "┝"), "┿");
700 assert_eq!(strategy.merge("│", "━"), "┿");
701 assert_eq!(strategy.merge("┵", "╞"), "╞");
702 assert_eq!(strategy.merge(" ", "╠"), " ");
703 assert_eq!(strategy.merge("╠", " "), " ");
704 assert_eq!(strategy.merge("╎", "╧"), "╧");
705 assert_eq!(strategy.merge("╛", "╒"), "╪");
706 assert_eq!(strategy.merge("│", "═"), "╪");
707 assert_eq!(strategy.merge("╤", "╧"), "╪");
708 assert_eq!(strategy.merge("╡", "╞"), "╪");
709 assert_eq!(strategy.merge("┌", "╭"), "╭");
710 assert_eq!(strategy.merge("┘", "╭"), "╭");
711 assert_eq!(strategy.merge("┌", "a"), "a");
712 assert_eq!(strategy.merge("a", "╭"), "a");
713 assert_eq!(strategy.merge("a", "b"), "b");
714 }
715
716 #[test]
717 fn fuzzy_merge_strategy() {
718 let strategy = MergeStrategy::Fuzzy;
719 assert_eq!(strategy.merge("┄", "╴"), "─");
720 assert_eq!(strategy.merge("│", "┆"), "┆");
721 assert_eq!(strategy.merge(" ", "┉"), " ");
722 assert_eq!(strategy.merge("┋", "┋"), "┋");
723 assert_eq!(strategy.merge("╷", "╶"), "┌");
724 assert_eq!(strategy.merge("╭", "┌"), "┌");
725 assert_eq!(strategy.merge("│", "┕"), "┝");
726 assert_eq!(strategy.merge("┏", "│"), "┝");
727 assert_eq!(strategy.merge("┏", "┆"), "┝");
728 assert_eq!(strategy.merge("│", "┏"), "┢");
729 assert_eq!(strategy.merge("╽", "┕"), "┢");
730 assert_eq!(strategy.merge("│", "─"), "┼");
731 assert_eq!(strategy.merge("┆", "─"), "┼");
732 assert_eq!(strategy.merge("┘", "┌"), "┼");
733 assert_eq!(strategy.merge("┘", "╭"), "┼");
734 assert_eq!(strategy.merge("╎", "┉"), "┿");
735 assert_eq!(strategy.merge(" ", "╠"), " ");
736 assert_eq!(strategy.merge("╠", " "), " ");
737 assert_eq!(strategy.merge("┵", "╞"), "╪");
738 assert_eq!(strategy.merge("╛", "╒"), "╪");
739 assert_eq!(strategy.merge("│", "═"), "╪");
740 assert_eq!(strategy.merge("╤", "╧"), "╪");
741 assert_eq!(strategy.merge("╡", "╞"), "╪");
742 assert_eq!(strategy.merge("╎", "╧"), "╪");
743 assert_eq!(strategy.merge("┌", "╭"), "╭");
744 assert_eq!(strategy.merge("┌", "a"), "a");
745 assert_eq!(strategy.merge("a", "╭"), "a");
746 assert_eq!(strategy.merge("a", "b"), "b");
747 }
748}