1use crate::model::marker::{MarkerId, MarkerList};
2use ratatui::style::{Color, Style};
3use std::collections::BTreeMap;
4
5pub const MIN_LINE_NUMBER_DIGITS: usize = 2;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum MarginPosition {
15 Left,
17 Right,
19}
20
21#[derive(Debug, Clone, PartialEq)]
27pub struct LineIndicator {
28 pub symbol: String,
30 pub color: Color,
32 pub priority: i32,
34 pub marker_id: MarkerId,
37}
38
39impl LineIndicator {
40 pub fn new(symbol: impl Into<String>, color: Color, priority: i32) -> Self {
42 Self {
43 symbol: symbol.into(),
44 color,
45 priority,
46 marker_id: MarkerId(0), }
48 }
49
50 pub fn with_marker(
52 symbol: impl Into<String>,
53 color: Color,
54 priority: i32,
55 marker_id: MarkerId,
56 ) -> Self {
57 Self {
58 symbol: symbol.into(),
59 color,
60 priority,
61 marker_id,
62 }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub enum MarginContent {
69 Text(String),
71 Symbol { text: String, style: Style },
73 Stacked(Vec<MarginContent>),
75 Empty,
77}
78
79impl MarginContent {
80 pub fn text(text: impl Into<String>) -> Self {
82 Self::Text(text.into())
83 }
84
85 pub fn symbol(text: impl Into<String>, style: Style) -> Self {
87 Self::Symbol {
88 text: text.into(),
89 style,
90 }
91 }
92
93 pub fn colored_symbol(text: impl Into<String>, color: Color) -> Self {
95 Self::Symbol {
96 text: text.into(),
97 style: Style::default().fg(color),
98 }
99 }
100
101 pub fn is_empty(&self) -> bool {
103 matches!(self, Self::Empty)
104 }
105
106 pub fn render(&self, width: usize) -> (String, Option<Style>) {
108 match self {
109 Self::Text(text) => {
110 let padded = format!("{:>width$}", text, width = width);
111 (padded, None)
112 }
113 Self::Symbol { text, style } => {
114 let padded = format!("{:>width$}", text, width = width);
115 (padded, Some(*style))
116 }
117 Self::Stacked(items) => {
118 for item in items.iter().rev() {
120 if !item.is_empty() {
121 return item.render(width);
122 }
123 }
124 (format!("{:>width$}", "", width = width), None)
125 }
126 Self::Empty => (format!("{:>width$}", "", width = width), None),
127 }
128 }
129}
130
131#[derive(Debug, Clone, PartialEq)]
133pub struct MarginConfig {
134 pub position: MarginPosition,
136
137 pub width: usize,
140
141 pub enabled: bool,
143
144 pub show_separator: bool,
146
147 pub separator: String,
149
150 pub style: Style,
152
153 pub separator_style: Style,
155}
156
157impl MarginConfig {
158 pub fn left_default() -> Self {
160 Self {
161 position: MarginPosition::Left,
162 width: MIN_LINE_NUMBER_DIGITS,
163 enabled: true,
164 show_separator: true,
165 separator: " │ ".to_string(), style: Style::default().fg(Color::DarkGray),
167 separator_style: Style::default().fg(Color::DarkGray),
168 }
169 }
170
171 pub fn right_default() -> Self {
173 Self {
174 position: MarginPosition::Right,
175 width: 0,
176 enabled: false,
177 show_separator: false,
178 separator: String::new(),
179 style: Style::default(),
180 separator_style: Style::default(),
181 }
182 }
183
184 pub fn total_width(&self) -> usize {
187 if self.enabled {
188 1 + self.width
190 + if self.show_separator {
191 self.separator.chars().count()
192 } else {
193 0
194 }
195 } else {
196 0
197 }
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct MarginAnnotation {
204 pub line: usize,
206
207 pub position: MarginPosition,
209
210 pub content: MarginContent,
212
213 pub id: Option<String>,
215}
216
217impl MarginAnnotation {
218 pub fn new(line: usize, position: MarginPosition, content: MarginContent) -> Self {
220 Self {
221 line,
222 position,
223 content,
224 id: None,
225 }
226 }
227
228 pub fn with_id(
230 line: usize,
231 position: MarginPosition,
232 content: MarginContent,
233 id: String,
234 ) -> Self {
235 Self {
236 line,
237 position,
238 content,
239 id: Some(id),
240 }
241 }
242
243 pub fn line_number(line: usize) -> Self {
245 Self::new(
246 line,
247 MarginPosition::Left,
248 MarginContent::text(format!("{}", line + 1)), )
250 }
251
252 pub fn breakpoint(line: usize) -> Self {
254 Self::new(
255 line,
256 MarginPosition::Left,
257 MarginContent::colored_symbol("●", Color::Red),
258 )
259 }
260
261 pub fn error(line: usize) -> Self {
263 Self::new(
264 line,
265 MarginPosition::Left,
266 MarginContent::colored_symbol("✗", Color::Red),
267 )
268 }
269
270 pub fn warning(line: usize) -> Self {
272 Self::new(
273 line,
274 MarginPosition::Left,
275 MarginContent::colored_symbol("⚠", Color::Yellow),
276 )
277 }
278
279 pub fn info(line: usize) -> Self {
281 Self::new(
282 line,
283 MarginPosition::Left,
284 MarginContent::colored_symbol("ℹ", Color::Blue),
285 )
286 }
287}
288
289#[derive(Debug)]
295pub struct MarginManager {
296 pub left_config: MarginConfig,
298
299 pub right_config: MarginConfig,
301
302 left_annotations: BTreeMap<usize, Vec<MarginAnnotation>>,
305
306 right_annotations: BTreeMap<usize, Vec<MarginAnnotation>>,
308
309 indicator_markers: MarkerList,
312
313 line_indicators: BTreeMap<u64, BTreeMap<String, LineIndicator>>,
317}
318
319impl MarginManager {
320 pub fn new() -> Self {
322 Self {
323 left_config: MarginConfig::left_default(),
324 right_config: MarginConfig::right_default(),
325 left_annotations: BTreeMap::new(),
326 right_annotations: BTreeMap::new(),
327 indicator_markers: MarkerList::new(),
328 line_indicators: BTreeMap::new(),
329 }
330 }
331
332 pub fn without_line_numbers() -> Self {
334 let mut manager = Self::new();
335 manager.left_config.width = 0;
336 manager.left_config.enabled = false;
337 manager
338 }
339
340 pub fn adjust_for_insert(&mut self, position: usize, length: usize) {
347 self.indicator_markers.adjust_for_insert(position, length);
348 }
349
350 pub fn adjust_for_delete(&mut self, position: usize, length: usize) {
353 self.indicator_markers.adjust_for_delete(position, length);
354 }
355
356 pub fn set_line_indicator(
363 &mut self,
364 byte_offset: usize,
365 namespace: String,
366 mut indicator: LineIndicator,
367 ) -> MarkerId {
368 let marker_id = self.indicator_markers.create(byte_offset, true);
370 indicator.marker_id = marker_id;
371
372 self.line_indicators
373 .entry(marker_id.0)
374 .or_default()
375 .insert(namespace, indicator);
376
377 marker_id
378 }
379
380 pub fn remove_line_indicator(&mut self, marker_id: MarkerId, namespace: &str) {
382 if let Some(indicators) = self.line_indicators.get_mut(&marker_id.0) {
383 indicators.remove(namespace);
384 if indicators.is_empty() {
385 self.line_indicators.remove(&marker_id.0);
386 self.indicator_markers.delete(marker_id);
387 }
388 }
389 }
390
391 pub fn clear_line_indicators_for_namespace(&mut self, namespace: &str) {
393 let mut markers_to_delete = Vec::new();
395
396 for (&marker_id, indicators) in self.line_indicators.iter_mut() {
397 indicators.remove(namespace);
398 if indicators.is_empty() {
399 markers_to_delete.push(marker_id);
400 }
401 }
402
403 for marker_id in markers_to_delete {
405 self.line_indicators.remove(&marker_id);
406 self.indicator_markers.delete(MarkerId(marker_id));
407 }
408 }
409
410 pub fn get_line_indicator(
418 &self,
419 line: usize,
420 get_line_fn: impl Fn(usize) -> usize,
421 ) -> Option<&LineIndicator> {
422 let mut best: Option<&LineIndicator> = None;
424
425 for (&marker_id, indicators) in &self.line_indicators {
426 if let Some(byte_pos) = self.indicator_markers.get_position(MarkerId(marker_id)) {
427 let indicator_line = get_line_fn(byte_pos);
428 if indicator_line == line {
429 for indicator in indicators.values() {
431 if best.is_none() || indicator.priority > best.unwrap().priority {
432 best = Some(indicator);
433 }
434 }
435 }
436 }
437 }
438
439 best
440 }
441
442 pub fn get_indicators_for_viewport(
450 &self,
451 viewport_start: usize,
452 viewport_end: usize,
453 get_line_fn: impl Fn(usize) -> usize,
454 ) -> BTreeMap<usize, LineIndicator> {
455 let mut by_line: BTreeMap<usize, LineIndicator> = BTreeMap::new();
456
457 for (marker_id, byte_pos, _end) in self
459 .indicator_markers
460 .query_range(viewport_start, viewport_end)
461 {
462 if let Some(indicators) = self.line_indicators.get(&marker_id.0) {
464 let line = get_line_fn(byte_pos);
465
466 if let Some(indicator) = indicators.values().max_by_key(|ind| ind.priority) {
468 if let Some(existing) = by_line.get(&line) {
470 if indicator.priority > existing.priority {
471 by_line.insert(line, indicator.clone());
472 }
473 } else {
474 by_line.insert(line, indicator.clone());
475 }
476 }
477 }
478 }
479
480 by_line
481 }
482
483 pub fn get_indicator_position(&self, marker_id: MarkerId) -> Option<usize> {
489 self.indicator_markers.get_position(marker_id)
490 }
491
492 pub fn namespaces_for_marker(&self, marker_id: MarkerId) -> Vec<String> {
496 self.line_indicators
497 .get(&marker_id.0)
498 .map(|indicators| indicators.keys().cloned().collect())
499 .unwrap_or_default()
500 }
501
502 pub fn query_indicator_range(&self, start: usize, end: usize) -> Vec<(MarkerId, usize, usize)> {
505 self.indicator_markers.query_range(start, end)
506 }
507
508 pub fn set_indicator_position(&mut self, marker_id: MarkerId, new_position: usize) {
513 if self.line_indicators.contains_key(&marker_id.0) {
515 self.indicator_markers.set_position(marker_id, new_position);
516 }
517 }
518
519 pub fn add_annotation(&mut self, annotation: MarginAnnotation) {
521 let annotations = match annotation.position {
522 MarginPosition::Left => &mut self.left_annotations,
523 MarginPosition::Right => &mut self.right_annotations,
524 };
525
526 annotations
527 .entry(annotation.line)
528 .or_insert_with(Vec::new)
529 .push(annotation);
530 }
531
532 pub fn remove_by_id(&mut self, id: &str) {
534 for annotations in self.left_annotations.values_mut() {
536 annotations.retain(|a| a.id.as_deref() != Some(id));
537 }
538
539 for annotations in self.right_annotations.values_mut() {
541 annotations.retain(|a| a.id.as_deref() != Some(id));
542 }
543
544 self.left_annotations.retain(|_, v| !v.is_empty());
546 self.right_annotations.retain(|_, v| !v.is_empty());
547 }
548
549 pub fn remove_at_line(&mut self, line: usize, position: MarginPosition) {
551 match position {
552 MarginPosition::Left => {
553 self.left_annotations.remove(&line);
554 }
555 MarginPosition::Right => {
556 self.right_annotations.remove(&line);
557 }
558 }
559 }
560
561 pub fn clear_position(&mut self, position: MarginPosition) {
563 match position {
564 MarginPosition::Left => self.left_annotations.clear(),
565 MarginPosition::Right => self.right_annotations.clear(),
566 }
567 }
568
569 pub fn clear_all(&mut self) {
571 self.left_annotations.clear();
572 self.right_annotations.clear();
573 }
574
575 pub fn get_at_line(
577 &self,
578 line: usize,
579 position: MarginPosition,
580 ) -> Option<&[MarginAnnotation]> {
581 let annotations = match position {
582 MarginPosition::Left => &self.left_annotations,
583 MarginPosition::Right => &self.right_annotations,
584 };
585 annotations.get(&line).map(|v| v.as_slice())
586 }
587
588 pub fn render_line(
591 &self,
592 line: usize,
593 position: MarginPosition,
594 _buffer_total_lines: usize,
595 show_line_numbers: bool,
596 ) -> MarginContent {
597 let annotations = match position {
598 MarginPosition::Left => &self.left_annotations,
599 MarginPosition::Right => &self.right_annotations,
600 };
601
602 let user_annotations = annotations.get(&line).cloned().unwrap_or_default();
604
605 if position == MarginPosition::Left && show_line_numbers {
607 let line_num = MarginContent::text(format!("{}", line + 1));
608
609 if user_annotations.is_empty() {
610 return line_num;
611 }
612
613 let mut stack = vec![line_num];
615 stack.extend(user_annotations.into_iter().map(|a| a.content));
616 MarginContent::Stacked(stack)
617 } else if let Some(annotation) = user_annotations.first() {
618 annotation.content.clone()
619 } else {
620 MarginContent::Empty
621 }
622 }
623
624 pub fn update_width_for_buffer(&mut self, buffer_total_lines: usize, show_line_numbers: bool) {
627 if show_line_numbers {
628 let digits = if buffer_total_lines == 0 {
629 1
630 } else {
631 ((buffer_total_lines as f64).log10().floor() as usize) + 1
632 };
633 self.left_config.width = digits.max(MIN_LINE_NUMBER_DIGITS);
634 }
635 }
636
637 pub fn left_total_width(&self) -> usize {
640 self.left_config.total_width()
641 }
642
643 pub fn right_total_width(&self) -> usize {
645 self.right_config.total_width()
646 }
647
648 pub fn configure_for_line_numbers(&mut self, show_line_numbers: bool) {
655 if !show_line_numbers {
656 self.left_config.width = 0;
663 self.left_config.enabled = true;
664 self.left_config.show_separator = false;
665 } else {
666 self.left_config.enabled = true;
667 self.left_config.show_separator = true;
668 if self.left_config.width == 0 {
669 self.left_config.width = MIN_LINE_NUMBER_DIGITS;
670 }
671 }
672 }
673
674 pub fn annotation_count(&self, position: MarginPosition) -> usize {
676 match position {
677 MarginPosition::Left => self.left_annotations.values().map(|v| v.len()).sum(),
678 MarginPosition::Right => self.right_annotations.values().map(|v| v.len()).sum(),
679 }
680 }
681}
682
683impl Default for MarginManager {
684 fn default() -> Self {
685 Self::new()
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692
693 #[test]
694 fn test_margin_content_text() {
695 let content = MarginContent::text("123");
696 let (rendered, style) = content.render(5);
697 assert_eq!(rendered, " 123");
698 assert!(style.is_none());
699 }
700
701 #[test]
702 fn test_margin_content_symbol() {
703 let content = MarginContent::colored_symbol("●", Color::Red);
704 let (rendered, style) = content.render(3);
705 assert_eq!(rendered, " ●");
706 assert!(style.is_some());
707 }
708
709 #[test]
710 fn test_margin_config_total_width() {
711 let mut config = MarginConfig::left_default();
712 config.width = 4;
713 config.separator = " │ ".to_string();
714 assert_eq!(config.total_width(), 8); config.show_separator = false;
717 assert_eq!(config.total_width(), 5); config.enabled = false;
720 assert_eq!(config.total_width(), 0);
721 }
722
723 #[test]
724 fn test_margin_annotation_helpers() {
725 let line_num = MarginAnnotation::line_number(5);
726 assert_eq!(line_num.line, 5);
727 assert_eq!(line_num.position, MarginPosition::Left);
728
729 let breakpoint = MarginAnnotation::breakpoint(10);
730 assert_eq!(breakpoint.line, 10);
731 assert_eq!(breakpoint.position, MarginPosition::Left);
732 }
733
734 #[test]
735 fn test_margin_manager_add_remove() {
736 let mut manager = MarginManager::new();
737
738 let annotation = MarginAnnotation::line_number(5);
740 manager.add_annotation(annotation);
741
742 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
743
744 let annotation = MarginAnnotation::with_id(
746 10,
747 MarginPosition::Left,
748 MarginContent::text("test"),
749 "test-id".to_string(),
750 );
751 manager.add_annotation(annotation);
752
753 assert_eq!(manager.annotation_count(MarginPosition::Left), 2);
754
755 manager.remove_by_id("test-id");
757 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
758
759 manager.clear_all();
761 assert_eq!(manager.annotation_count(MarginPosition::Left), 0);
762 }
763
764 #[test]
765 fn test_margin_manager_render_line() {
766 let mut manager = MarginManager::new();
767
768 let content = manager.render_line(5, MarginPosition::Left, 100, true);
770 let (rendered, _) = content.render(4);
771 assert!(rendered.contains("6")); manager.add_annotation(MarginAnnotation::breakpoint(5));
775
776 let content = manager.render_line(5, MarginPosition::Left, 100, true);
778 assert!(matches!(content, MarginContent::Stacked(_)));
779 }
780
781 #[test]
782 fn test_margin_manager_update_width() {
783 let mut manager = MarginManager::new();
784
785 manager.update_width_for_buffer(1, true);
787 assert_eq!(manager.left_config.width, MIN_LINE_NUMBER_DIGITS);
788
789 manager.update_width_for_buffer(99, true);
791 assert_eq!(manager.left_config.width, 2);
792
793 manager.update_width_for_buffer(500, true);
796 assert_eq!(manager.left_config.width, 3);
797
798 manager.update_width_for_buffer(1000, true);
800 assert_eq!(manager.left_config.width, 4);
801
802 manager.update_width_for_buffer(10000, true);
804 assert_eq!(manager.left_config.width, 5);
805
806 manager.update_width_for_buffer(1000000, true);
808 assert_eq!(manager.left_config.width, 7);
809 }
810
811 #[test]
812 fn test_margin_manager_total_width_adapts_to_buffer() {
813 let mut manager = MarginManager::new();
817
818 manager.update_width_for_buffer(10, true);
820 assert_eq!(manager.left_total_width(), 6);
821
822 manager.update_width_for_buffer(250, true);
824 assert_eq!(manager.left_total_width(), 7);
825
826 manager.update_width_for_buffer(5000, true);
828 assert_eq!(manager.left_total_width(), 8);
829 }
830
831 #[test]
832 fn test_margin_manager_without_line_numbers() {
833 let manager = MarginManager::without_line_numbers();
834 assert!(!manager.left_config.enabled);
835
836 let content = manager.render_line(5, MarginPosition::Left, 100, false);
837 assert!(content.is_empty());
838 }
839
840 #[test]
841 fn test_margin_position_left_right() {
842 let mut manager = MarginManager::new();
843
844 manager.add_annotation(MarginAnnotation::new(
845 1,
846 MarginPosition::Left,
847 MarginContent::text("left"),
848 ));
849
850 manager.add_annotation(MarginAnnotation::new(
851 1,
852 MarginPosition::Right,
853 MarginContent::text("right"),
854 ));
855
856 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
857 assert_eq!(manager.annotation_count(MarginPosition::Right), 1);
858
859 manager.clear_position(MarginPosition::Left);
860 assert_eq!(manager.annotation_count(MarginPosition::Left), 0);
861 assert_eq!(manager.annotation_count(MarginPosition::Right), 1);
862 }
863
864 fn byte_to_line(byte_offset: usize) -> usize {
867 byte_offset / 10
868 }
869
870 fn line_to_byte(line: usize) -> usize {
872 line * 10
873 }
874
875 #[test]
876 fn test_line_indicator_basic() {
877 let mut manager = MarginManager::new();
878
879 let indicator = LineIndicator::new("│", Color::Green, 10);
881 manager.set_line_indicator(line_to_byte(5), "git-gutter".to_string(), indicator);
882
883 let retrieved = manager.get_line_indicator(5, byte_to_line);
885 assert!(retrieved.is_some());
886 let retrieved = retrieved.unwrap();
887 assert_eq!(retrieved.symbol, "│");
888 assert_eq!(retrieved.color, Color::Green);
889 assert_eq!(retrieved.priority, 10);
890
891 assert!(manager.get_line_indicator(10, byte_to_line).is_none());
893 }
894
895 #[test]
896 fn test_line_indicator_multiple_namespaces() {
897 let mut manager = MarginManager::new();
898
899 let git_indicator = LineIndicator::new("│", Color::Green, 10);
901 let breakpoint_indicator = LineIndicator::new("●", Color::Red, 20);
902
903 manager.set_line_indicator(line_to_byte(5), "git-gutter".to_string(), git_indicator);
904 manager.set_line_indicator(
905 line_to_byte(5),
906 "breakpoints".to_string(),
907 breakpoint_indicator,
908 );
909
910 let retrieved = manager.get_line_indicator(5, byte_to_line);
912 assert!(retrieved.is_some());
913 let retrieved = retrieved.unwrap();
914 assert_eq!(retrieved.symbol, "●"); assert_eq!(retrieved.priority, 20);
916 }
917
918 #[test]
919 fn test_line_indicator_clear_namespace() {
920 let mut manager = MarginManager::new();
921
922 manager.set_line_indicator(
924 line_to_byte(1),
925 "git-gutter".to_string(),
926 LineIndicator::new("│", Color::Green, 10),
927 );
928 manager.set_line_indicator(
929 line_to_byte(2),
930 "git-gutter".to_string(),
931 LineIndicator::new("│", Color::Yellow, 10),
932 );
933 manager.set_line_indicator(
934 line_to_byte(3),
935 "breakpoints".to_string(),
936 LineIndicator::new("●", Color::Red, 20),
937 );
938
939 manager.clear_line_indicators_for_namespace("git-gutter");
941
942 assert!(manager.get_line_indicator(1, byte_to_line).is_none());
944 assert!(manager.get_line_indicator(2, byte_to_line).is_none());
945
946 let breakpoint = manager.get_line_indicator(3, byte_to_line);
948 assert!(breakpoint.is_some());
949 assert_eq!(breakpoint.unwrap().symbol, "●");
950 }
951
952 #[test]
953 fn test_line_indicator_remove_specific() {
954 let mut manager = MarginManager::new();
955
956 let git_marker = manager.set_line_indicator(
958 line_to_byte(5),
959 "git-gutter".to_string(),
960 LineIndicator::new("│", Color::Green, 10),
961 );
962 let bp_marker = manager.set_line_indicator(
963 line_to_byte(5),
964 "breakpoints".to_string(),
965 LineIndicator::new("●", Color::Red, 20),
966 );
967
968 manager.remove_line_indicator(git_marker, "git-gutter");
970
971 let retrieved = manager.get_line_indicator(5, byte_to_line);
973 assert!(retrieved.is_some());
974 assert_eq!(retrieved.unwrap().symbol, "●");
975
976 manager.remove_line_indicator(bp_marker, "breakpoints");
978
979 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
981 }
982
983 #[test]
984 fn test_line_indicator_shifts_on_insert() {
985 let mut manager = MarginManager::new();
986
987 manager.set_line_indicator(
989 line_to_byte(5),
990 "git-gutter".to_string(),
991 LineIndicator::new("│", Color::Green, 10),
992 );
993
994 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
996 assert!(manager.get_line_indicator(6, byte_to_line).is_none());
997
998 manager.adjust_for_insert(0, 10);
1000
1001 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
1003 assert!(manager.get_line_indicator(6, byte_to_line).is_some());
1004 }
1005
1006 #[test]
1007 fn test_line_indicator_shifts_on_delete() {
1008 let mut manager = MarginManager::new();
1009
1010 manager.set_line_indicator(
1012 line_to_byte(5),
1013 "git-gutter".to_string(),
1014 LineIndicator::new("│", Color::Green, 10),
1015 );
1016
1017 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
1019
1020 manager.adjust_for_delete(0, 20);
1022
1023 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
1025 assert!(manager.get_line_indicator(3, byte_to_line).is_some());
1026 }
1027
1028 #[test]
1029 fn test_multiple_indicators_shift_together() {
1030 let mut manager = MarginManager::new();
1031
1032 manager.set_line_indicator(
1034 line_to_byte(3),
1035 "git-gutter".to_string(),
1036 LineIndicator::new("│", Color::Green, 10),
1037 );
1038 manager.set_line_indicator(
1039 line_to_byte(5),
1040 "git-gutter".to_string(),
1041 LineIndicator::new("│", Color::Yellow, 10),
1042 );
1043 manager.set_line_indicator(
1044 line_to_byte(7),
1045 "git-gutter".to_string(),
1046 LineIndicator::new("│", Color::Red, 10),
1047 );
1048
1049 manager.adjust_for_insert(25, 20);
1052
1053 assert!(manager.get_line_indicator(3, byte_to_line).is_none());
1055
1056 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
1058 assert!(manager.get_line_indicator(7, byte_to_line).is_some());
1059 assert!(manager.get_line_indicator(9, byte_to_line).is_some());
1060 }
1061}