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;
657 self.left_config.enabled = false;
658 } else {
659 self.left_config.enabled = true;
660 if self.left_config.width == 0 {
661 self.left_config.width = MIN_LINE_NUMBER_DIGITS;
662 }
663 }
664 }
665
666 pub fn annotation_count(&self, position: MarginPosition) -> usize {
668 match position {
669 MarginPosition::Left => self.left_annotations.values().map(|v| v.len()).sum(),
670 MarginPosition::Right => self.right_annotations.values().map(|v| v.len()).sum(),
671 }
672 }
673}
674
675impl Default for MarginManager {
676 fn default() -> Self {
677 Self::new()
678 }
679}
680
681#[cfg(test)]
682mod tests {
683 use super::*;
684
685 #[test]
686 fn test_margin_content_text() {
687 let content = MarginContent::text("123");
688 let (rendered, style) = content.render(5);
689 assert_eq!(rendered, " 123");
690 assert!(style.is_none());
691 }
692
693 #[test]
694 fn test_margin_content_symbol() {
695 let content = MarginContent::colored_symbol("●", Color::Red);
696 let (rendered, style) = content.render(3);
697 assert_eq!(rendered, " ●");
698 assert!(style.is_some());
699 }
700
701 #[test]
702 fn test_margin_config_total_width() {
703 let mut config = MarginConfig::left_default();
704 config.width = 4;
705 config.separator = " │ ".to_string();
706 assert_eq!(config.total_width(), 8); config.show_separator = false;
709 assert_eq!(config.total_width(), 5); config.enabled = false;
712 assert_eq!(config.total_width(), 0);
713 }
714
715 #[test]
716 fn test_margin_annotation_helpers() {
717 let line_num = MarginAnnotation::line_number(5);
718 assert_eq!(line_num.line, 5);
719 assert_eq!(line_num.position, MarginPosition::Left);
720
721 let breakpoint = MarginAnnotation::breakpoint(10);
722 assert_eq!(breakpoint.line, 10);
723 assert_eq!(breakpoint.position, MarginPosition::Left);
724 }
725
726 #[test]
727 fn test_margin_manager_add_remove() {
728 let mut manager = MarginManager::new();
729
730 let annotation = MarginAnnotation::line_number(5);
732 manager.add_annotation(annotation);
733
734 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
735
736 let annotation = MarginAnnotation::with_id(
738 10,
739 MarginPosition::Left,
740 MarginContent::text("test"),
741 "test-id".to_string(),
742 );
743 manager.add_annotation(annotation);
744
745 assert_eq!(manager.annotation_count(MarginPosition::Left), 2);
746
747 manager.remove_by_id("test-id");
749 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
750
751 manager.clear_all();
753 assert_eq!(manager.annotation_count(MarginPosition::Left), 0);
754 }
755
756 #[test]
757 fn test_margin_manager_render_line() {
758 let mut manager = MarginManager::new();
759
760 let content = manager.render_line(5, MarginPosition::Left, 100, true);
762 let (rendered, _) = content.render(4);
763 assert!(rendered.contains("6")); manager.add_annotation(MarginAnnotation::breakpoint(5));
767
768 let content = manager.render_line(5, MarginPosition::Left, 100, true);
770 assert!(matches!(content, MarginContent::Stacked(_)));
771 }
772
773 #[test]
774 fn test_margin_manager_update_width() {
775 let mut manager = MarginManager::new();
776
777 manager.update_width_for_buffer(1, true);
779 assert_eq!(manager.left_config.width, MIN_LINE_NUMBER_DIGITS);
780
781 manager.update_width_for_buffer(99, true);
783 assert_eq!(manager.left_config.width, 2);
784
785 manager.update_width_for_buffer(500, true);
788 assert_eq!(manager.left_config.width, 3);
789
790 manager.update_width_for_buffer(1000, true);
792 assert_eq!(manager.left_config.width, 4);
793
794 manager.update_width_for_buffer(10000, true);
796 assert_eq!(manager.left_config.width, 5);
797
798 manager.update_width_for_buffer(1000000, true);
800 assert_eq!(manager.left_config.width, 7);
801 }
802
803 #[test]
804 fn test_margin_manager_total_width_adapts_to_buffer() {
805 let mut manager = MarginManager::new();
809
810 manager.update_width_for_buffer(10, true);
812 assert_eq!(manager.left_total_width(), 6);
813
814 manager.update_width_for_buffer(250, true);
816 assert_eq!(manager.left_total_width(), 7);
817
818 manager.update_width_for_buffer(5000, true);
820 assert_eq!(manager.left_total_width(), 8);
821 }
822
823 #[test]
824 fn test_margin_manager_without_line_numbers() {
825 let manager = MarginManager::without_line_numbers();
826 assert!(!manager.left_config.enabled);
827
828 let content = manager.render_line(5, MarginPosition::Left, 100, false);
829 assert!(content.is_empty());
830 }
831
832 #[test]
833 fn test_margin_position_left_right() {
834 let mut manager = MarginManager::new();
835
836 manager.add_annotation(MarginAnnotation::new(
837 1,
838 MarginPosition::Left,
839 MarginContent::text("left"),
840 ));
841
842 manager.add_annotation(MarginAnnotation::new(
843 1,
844 MarginPosition::Right,
845 MarginContent::text("right"),
846 ));
847
848 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
849 assert_eq!(manager.annotation_count(MarginPosition::Right), 1);
850
851 manager.clear_position(MarginPosition::Left);
852 assert_eq!(manager.annotation_count(MarginPosition::Left), 0);
853 assert_eq!(manager.annotation_count(MarginPosition::Right), 1);
854 }
855
856 fn byte_to_line(byte_offset: usize) -> usize {
859 byte_offset / 10
860 }
861
862 fn line_to_byte(line: usize) -> usize {
864 line * 10
865 }
866
867 #[test]
868 fn test_line_indicator_basic() {
869 let mut manager = MarginManager::new();
870
871 let indicator = LineIndicator::new("│", Color::Green, 10);
873 manager.set_line_indicator(line_to_byte(5), "git-gutter".to_string(), indicator);
874
875 let retrieved = manager.get_line_indicator(5, byte_to_line);
877 assert!(retrieved.is_some());
878 let retrieved = retrieved.unwrap();
879 assert_eq!(retrieved.symbol, "│");
880 assert_eq!(retrieved.color, Color::Green);
881 assert_eq!(retrieved.priority, 10);
882
883 assert!(manager.get_line_indicator(10, byte_to_line).is_none());
885 }
886
887 #[test]
888 fn test_line_indicator_multiple_namespaces() {
889 let mut manager = MarginManager::new();
890
891 let git_indicator = LineIndicator::new("│", Color::Green, 10);
893 let breakpoint_indicator = LineIndicator::new("●", Color::Red, 20);
894
895 manager.set_line_indicator(line_to_byte(5), "git-gutter".to_string(), git_indicator);
896 manager.set_line_indicator(
897 line_to_byte(5),
898 "breakpoints".to_string(),
899 breakpoint_indicator,
900 );
901
902 let retrieved = manager.get_line_indicator(5, byte_to_line);
904 assert!(retrieved.is_some());
905 let retrieved = retrieved.unwrap();
906 assert_eq!(retrieved.symbol, "●"); assert_eq!(retrieved.priority, 20);
908 }
909
910 #[test]
911 fn test_line_indicator_clear_namespace() {
912 let mut manager = MarginManager::new();
913
914 manager.set_line_indicator(
916 line_to_byte(1),
917 "git-gutter".to_string(),
918 LineIndicator::new("│", Color::Green, 10),
919 );
920 manager.set_line_indicator(
921 line_to_byte(2),
922 "git-gutter".to_string(),
923 LineIndicator::new("│", Color::Yellow, 10),
924 );
925 manager.set_line_indicator(
926 line_to_byte(3),
927 "breakpoints".to_string(),
928 LineIndicator::new("●", Color::Red, 20),
929 );
930
931 manager.clear_line_indicators_for_namespace("git-gutter");
933
934 assert!(manager.get_line_indicator(1, byte_to_line).is_none());
936 assert!(manager.get_line_indicator(2, byte_to_line).is_none());
937
938 let breakpoint = manager.get_line_indicator(3, byte_to_line);
940 assert!(breakpoint.is_some());
941 assert_eq!(breakpoint.unwrap().symbol, "●");
942 }
943
944 #[test]
945 fn test_line_indicator_remove_specific() {
946 let mut manager = MarginManager::new();
947
948 let git_marker = manager.set_line_indicator(
950 line_to_byte(5),
951 "git-gutter".to_string(),
952 LineIndicator::new("│", Color::Green, 10),
953 );
954 let bp_marker = manager.set_line_indicator(
955 line_to_byte(5),
956 "breakpoints".to_string(),
957 LineIndicator::new("●", Color::Red, 20),
958 );
959
960 manager.remove_line_indicator(git_marker, "git-gutter");
962
963 let retrieved = manager.get_line_indicator(5, byte_to_line);
965 assert!(retrieved.is_some());
966 assert_eq!(retrieved.unwrap().symbol, "●");
967
968 manager.remove_line_indicator(bp_marker, "breakpoints");
970
971 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
973 }
974
975 #[test]
976 fn test_line_indicator_shifts_on_insert() {
977 let mut manager = MarginManager::new();
978
979 manager.set_line_indicator(
981 line_to_byte(5),
982 "git-gutter".to_string(),
983 LineIndicator::new("│", Color::Green, 10),
984 );
985
986 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
988 assert!(manager.get_line_indicator(6, byte_to_line).is_none());
989
990 manager.adjust_for_insert(0, 10);
992
993 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
995 assert!(manager.get_line_indicator(6, byte_to_line).is_some());
996 }
997
998 #[test]
999 fn test_line_indicator_shifts_on_delete() {
1000 let mut manager = MarginManager::new();
1001
1002 manager.set_line_indicator(
1004 line_to_byte(5),
1005 "git-gutter".to_string(),
1006 LineIndicator::new("│", Color::Green, 10),
1007 );
1008
1009 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
1011
1012 manager.adjust_for_delete(0, 20);
1014
1015 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
1017 assert!(manager.get_line_indicator(3, byte_to_line).is_some());
1018 }
1019
1020 #[test]
1021 fn test_multiple_indicators_shift_together() {
1022 let mut manager = MarginManager::new();
1023
1024 manager.set_line_indicator(
1026 line_to_byte(3),
1027 "git-gutter".to_string(),
1028 LineIndicator::new("│", Color::Green, 10),
1029 );
1030 manager.set_line_indicator(
1031 line_to_byte(5),
1032 "git-gutter".to_string(),
1033 LineIndicator::new("│", Color::Yellow, 10),
1034 );
1035 manager.set_line_indicator(
1036 line_to_byte(7),
1037 "git-gutter".to_string(),
1038 LineIndicator::new("│", Color::Red, 10),
1039 );
1040
1041 manager.adjust_for_insert(25, 20);
1044
1045 assert!(manager.get_line_indicator(3, byte_to_line).is_none());
1047
1048 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
1050 assert!(manager.get_line_indicator(7, byte_to_line).is_some());
1051 assert!(manager.get_line_indicator(9, byte_to_line).is_some());
1052 }
1053}