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 query_indicator_range(&self, start: usize, end: usize) -> Vec<(MarkerId, usize, usize)> {
495 self.indicator_markers.query_range(start, end)
496 }
497
498 pub fn set_indicator_position(&mut self, marker_id: MarkerId, new_position: usize) {
503 if self.line_indicators.contains_key(&marker_id.0) {
505 self.indicator_markers.set_position(marker_id, new_position);
506 }
507 }
508
509 pub fn add_annotation(&mut self, annotation: MarginAnnotation) {
511 let annotations = match annotation.position {
512 MarginPosition::Left => &mut self.left_annotations,
513 MarginPosition::Right => &mut self.right_annotations,
514 };
515
516 annotations
517 .entry(annotation.line)
518 .or_insert_with(Vec::new)
519 .push(annotation);
520 }
521
522 pub fn remove_by_id(&mut self, id: &str) {
524 for annotations in self.left_annotations.values_mut() {
526 annotations.retain(|a| a.id.as_deref() != Some(id));
527 }
528
529 for annotations in self.right_annotations.values_mut() {
531 annotations.retain(|a| a.id.as_deref() != Some(id));
532 }
533
534 self.left_annotations.retain(|_, v| !v.is_empty());
536 self.right_annotations.retain(|_, v| !v.is_empty());
537 }
538
539 pub fn remove_at_line(&mut self, line: usize, position: MarginPosition) {
541 match position {
542 MarginPosition::Left => {
543 self.left_annotations.remove(&line);
544 }
545 MarginPosition::Right => {
546 self.right_annotations.remove(&line);
547 }
548 }
549 }
550
551 pub fn clear_position(&mut self, position: MarginPosition) {
553 match position {
554 MarginPosition::Left => self.left_annotations.clear(),
555 MarginPosition::Right => self.right_annotations.clear(),
556 }
557 }
558
559 pub fn clear_all(&mut self) {
561 self.left_annotations.clear();
562 self.right_annotations.clear();
563 }
564
565 pub fn get_at_line(
567 &self,
568 line: usize,
569 position: MarginPosition,
570 ) -> Option<&[MarginAnnotation]> {
571 let annotations = match position {
572 MarginPosition::Left => &self.left_annotations,
573 MarginPosition::Right => &self.right_annotations,
574 };
575 annotations.get(&line).map(|v| v.as_slice())
576 }
577
578 pub fn render_line(
581 &self,
582 line: usize,
583 position: MarginPosition,
584 _buffer_total_lines: usize,
585 show_line_numbers: bool,
586 ) -> MarginContent {
587 let annotations = match position {
588 MarginPosition::Left => &self.left_annotations,
589 MarginPosition::Right => &self.right_annotations,
590 };
591
592 let user_annotations = annotations.get(&line).cloned().unwrap_or_default();
594
595 if position == MarginPosition::Left && show_line_numbers {
597 let line_num = MarginContent::text(format!("{}", line + 1));
598
599 if user_annotations.is_empty() {
600 return line_num;
601 }
602
603 let mut stack = vec![line_num];
605 stack.extend(user_annotations.into_iter().map(|a| a.content));
606 MarginContent::Stacked(stack)
607 } else if let Some(annotation) = user_annotations.first() {
608 annotation.content.clone()
609 } else {
610 MarginContent::Empty
611 }
612 }
613
614 pub fn update_width_for_buffer(&mut self, buffer_total_lines: usize, show_line_numbers: bool) {
617 if show_line_numbers {
618 let digits = if buffer_total_lines == 0 {
619 1
620 } else {
621 ((buffer_total_lines as f64).log10().floor() as usize) + 1
622 };
623 self.left_config.width = digits.max(MIN_LINE_NUMBER_DIGITS);
624 }
625 }
626
627 pub fn left_total_width(&self) -> usize {
630 self.left_config.total_width()
631 }
632
633 pub fn right_total_width(&self) -> usize {
635 self.right_config.total_width()
636 }
637
638 pub fn configure_for_line_numbers(&mut self, show_line_numbers: bool) {
645 if !show_line_numbers {
646 self.left_config.width = 0;
647 self.left_config.enabled = false;
648 } else {
649 self.left_config.enabled = true;
650 if self.left_config.width == 0 {
651 self.left_config.width = MIN_LINE_NUMBER_DIGITS;
652 }
653 }
654 }
655
656 pub fn annotation_count(&self, position: MarginPosition) -> usize {
658 match position {
659 MarginPosition::Left => self.left_annotations.values().map(|v| v.len()).sum(),
660 MarginPosition::Right => self.right_annotations.values().map(|v| v.len()).sum(),
661 }
662 }
663}
664
665impl Default for MarginManager {
666 fn default() -> Self {
667 Self::new()
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674
675 #[test]
676 fn test_margin_content_text() {
677 let content = MarginContent::text("123");
678 let (rendered, style) = content.render(5);
679 assert_eq!(rendered, " 123");
680 assert!(style.is_none());
681 }
682
683 #[test]
684 fn test_margin_content_symbol() {
685 let content = MarginContent::colored_symbol("●", Color::Red);
686 let (rendered, style) = content.render(3);
687 assert_eq!(rendered, " ●");
688 assert!(style.is_some());
689 }
690
691 #[test]
692 fn test_margin_config_total_width() {
693 let mut config = MarginConfig::left_default();
694 config.width = 4;
695 config.separator = " │ ".to_string();
696 assert_eq!(config.total_width(), 8); config.show_separator = false;
699 assert_eq!(config.total_width(), 5); config.enabled = false;
702 assert_eq!(config.total_width(), 0);
703 }
704
705 #[test]
706 fn test_margin_annotation_helpers() {
707 let line_num = MarginAnnotation::line_number(5);
708 assert_eq!(line_num.line, 5);
709 assert_eq!(line_num.position, MarginPosition::Left);
710
711 let breakpoint = MarginAnnotation::breakpoint(10);
712 assert_eq!(breakpoint.line, 10);
713 assert_eq!(breakpoint.position, MarginPosition::Left);
714 }
715
716 #[test]
717 fn test_margin_manager_add_remove() {
718 let mut manager = MarginManager::new();
719
720 let annotation = MarginAnnotation::line_number(5);
722 manager.add_annotation(annotation);
723
724 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
725
726 let annotation = MarginAnnotation::with_id(
728 10,
729 MarginPosition::Left,
730 MarginContent::text("test"),
731 "test-id".to_string(),
732 );
733 manager.add_annotation(annotation);
734
735 assert_eq!(manager.annotation_count(MarginPosition::Left), 2);
736
737 manager.remove_by_id("test-id");
739 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
740
741 manager.clear_all();
743 assert_eq!(manager.annotation_count(MarginPosition::Left), 0);
744 }
745
746 #[test]
747 fn test_margin_manager_render_line() {
748 let mut manager = MarginManager::new();
749
750 let content = manager.render_line(5, MarginPosition::Left, 100, true);
752 let (rendered, _) = content.render(4);
753 assert!(rendered.contains("6")); manager.add_annotation(MarginAnnotation::breakpoint(5));
757
758 let content = manager.render_line(5, MarginPosition::Left, 100, true);
760 assert!(matches!(content, MarginContent::Stacked(_)));
761 }
762
763 #[test]
764 fn test_margin_manager_update_width() {
765 let mut manager = MarginManager::new();
766
767 manager.update_width_for_buffer(1, true);
769 assert_eq!(manager.left_config.width, MIN_LINE_NUMBER_DIGITS);
770
771 manager.update_width_for_buffer(99, true);
773 assert_eq!(manager.left_config.width, 2);
774
775 manager.update_width_for_buffer(500, true);
778 assert_eq!(manager.left_config.width, 3);
779
780 manager.update_width_for_buffer(1000, true);
782 assert_eq!(manager.left_config.width, 4);
783
784 manager.update_width_for_buffer(10000, true);
786 assert_eq!(manager.left_config.width, 5);
787
788 manager.update_width_for_buffer(1000000, true);
790 assert_eq!(manager.left_config.width, 7);
791 }
792
793 #[test]
794 fn test_margin_manager_total_width_adapts_to_buffer() {
795 let mut manager = MarginManager::new();
799
800 manager.update_width_for_buffer(10, true);
802 assert_eq!(manager.left_total_width(), 6);
803
804 manager.update_width_for_buffer(250, true);
806 assert_eq!(manager.left_total_width(), 7);
807
808 manager.update_width_for_buffer(5000, true);
810 assert_eq!(manager.left_total_width(), 8);
811 }
812
813 #[test]
814 fn test_margin_manager_without_line_numbers() {
815 let manager = MarginManager::without_line_numbers();
816 assert!(!manager.left_config.enabled);
817
818 let content = manager.render_line(5, MarginPosition::Left, 100, false);
819 assert!(content.is_empty());
820 }
821
822 #[test]
823 fn test_margin_position_left_right() {
824 let mut manager = MarginManager::new();
825
826 manager.add_annotation(MarginAnnotation::new(
827 1,
828 MarginPosition::Left,
829 MarginContent::text("left"),
830 ));
831
832 manager.add_annotation(MarginAnnotation::new(
833 1,
834 MarginPosition::Right,
835 MarginContent::text("right"),
836 ));
837
838 assert_eq!(manager.annotation_count(MarginPosition::Left), 1);
839 assert_eq!(manager.annotation_count(MarginPosition::Right), 1);
840
841 manager.clear_position(MarginPosition::Left);
842 assert_eq!(manager.annotation_count(MarginPosition::Left), 0);
843 assert_eq!(manager.annotation_count(MarginPosition::Right), 1);
844 }
845
846 fn byte_to_line(byte_offset: usize) -> usize {
849 byte_offset / 10
850 }
851
852 fn line_to_byte(line: usize) -> usize {
854 line * 10
855 }
856
857 #[test]
858 fn test_line_indicator_basic() {
859 let mut manager = MarginManager::new();
860
861 let indicator = LineIndicator::new("│", Color::Green, 10);
863 manager.set_line_indicator(line_to_byte(5), "git-gutter".to_string(), indicator);
864
865 let retrieved = manager.get_line_indicator(5, byte_to_line);
867 assert!(retrieved.is_some());
868 let retrieved = retrieved.unwrap();
869 assert_eq!(retrieved.symbol, "│");
870 assert_eq!(retrieved.color, Color::Green);
871 assert_eq!(retrieved.priority, 10);
872
873 assert!(manager.get_line_indicator(10, byte_to_line).is_none());
875 }
876
877 #[test]
878 fn test_line_indicator_multiple_namespaces() {
879 let mut manager = MarginManager::new();
880
881 let git_indicator = LineIndicator::new("│", Color::Green, 10);
883 let breakpoint_indicator = LineIndicator::new("●", Color::Red, 20);
884
885 manager.set_line_indicator(line_to_byte(5), "git-gutter".to_string(), git_indicator);
886 manager.set_line_indicator(
887 line_to_byte(5),
888 "breakpoints".to_string(),
889 breakpoint_indicator,
890 );
891
892 let retrieved = manager.get_line_indicator(5, byte_to_line);
894 assert!(retrieved.is_some());
895 let retrieved = retrieved.unwrap();
896 assert_eq!(retrieved.symbol, "●"); assert_eq!(retrieved.priority, 20);
898 }
899
900 #[test]
901 fn test_line_indicator_clear_namespace() {
902 let mut manager = MarginManager::new();
903
904 manager.set_line_indicator(
906 line_to_byte(1),
907 "git-gutter".to_string(),
908 LineIndicator::new("│", Color::Green, 10),
909 );
910 manager.set_line_indicator(
911 line_to_byte(2),
912 "git-gutter".to_string(),
913 LineIndicator::new("│", Color::Yellow, 10),
914 );
915 manager.set_line_indicator(
916 line_to_byte(3),
917 "breakpoints".to_string(),
918 LineIndicator::new("●", Color::Red, 20),
919 );
920
921 manager.clear_line_indicators_for_namespace("git-gutter");
923
924 assert!(manager.get_line_indicator(1, byte_to_line).is_none());
926 assert!(manager.get_line_indicator(2, byte_to_line).is_none());
927
928 let breakpoint = manager.get_line_indicator(3, byte_to_line);
930 assert!(breakpoint.is_some());
931 assert_eq!(breakpoint.unwrap().symbol, "●");
932 }
933
934 #[test]
935 fn test_line_indicator_remove_specific() {
936 let mut manager = MarginManager::new();
937
938 let git_marker = manager.set_line_indicator(
940 line_to_byte(5),
941 "git-gutter".to_string(),
942 LineIndicator::new("│", Color::Green, 10),
943 );
944 let bp_marker = manager.set_line_indicator(
945 line_to_byte(5),
946 "breakpoints".to_string(),
947 LineIndicator::new("●", Color::Red, 20),
948 );
949
950 manager.remove_line_indicator(git_marker, "git-gutter");
952
953 let retrieved = manager.get_line_indicator(5, byte_to_line);
955 assert!(retrieved.is_some());
956 assert_eq!(retrieved.unwrap().symbol, "●");
957
958 manager.remove_line_indicator(bp_marker, "breakpoints");
960
961 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
963 }
964
965 #[test]
966 fn test_line_indicator_shifts_on_insert() {
967 let mut manager = MarginManager::new();
968
969 manager.set_line_indicator(
971 line_to_byte(5),
972 "git-gutter".to_string(),
973 LineIndicator::new("│", Color::Green, 10),
974 );
975
976 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
978 assert!(manager.get_line_indicator(6, byte_to_line).is_none());
979
980 manager.adjust_for_insert(0, 10);
982
983 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
985 assert!(manager.get_line_indicator(6, byte_to_line).is_some());
986 }
987
988 #[test]
989 fn test_line_indicator_shifts_on_delete() {
990 let mut manager = MarginManager::new();
991
992 manager.set_line_indicator(
994 line_to_byte(5),
995 "git-gutter".to_string(),
996 LineIndicator::new("│", Color::Green, 10),
997 );
998
999 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
1001
1002 manager.adjust_for_delete(0, 20);
1004
1005 assert!(manager.get_line_indicator(5, byte_to_line).is_none());
1007 assert!(manager.get_line_indicator(3, byte_to_line).is_some());
1008 }
1009
1010 #[test]
1011 fn test_multiple_indicators_shift_together() {
1012 let mut manager = MarginManager::new();
1013
1014 manager.set_line_indicator(
1016 line_to_byte(3),
1017 "git-gutter".to_string(),
1018 LineIndicator::new("│", Color::Green, 10),
1019 );
1020 manager.set_line_indicator(
1021 line_to_byte(5),
1022 "git-gutter".to_string(),
1023 LineIndicator::new("│", Color::Yellow, 10),
1024 );
1025 manager.set_line_indicator(
1026 line_to_byte(7),
1027 "git-gutter".to_string(),
1028 LineIndicator::new("│", Color::Red, 10),
1029 );
1030
1031 manager.adjust_for_insert(25, 20);
1034
1035 assert!(manager.get_line_indicator(3, byte_to_line).is_none());
1037
1038 assert!(manager.get_line_indicator(5, byte_to_line).is_some());
1040 assert!(manager.get_line_indicator(7, byte_to_line).is_some());
1041 assert!(manager.get_line_indicator(9, byte_to_line).is_some());
1042 }
1043}