1use crate::utils::calculate_indentation_width_default;
4use std::collections::HashSet;
5use std::ops::Range;
6
7fn find_char_boundary(s: &str, byte_idx: usize) -> usize {
14 if byte_idx >= s.len() {
15 return s.len();
16 }
17
18 if s.is_char_boundary(byte_idx) {
20 return byte_idx;
21 }
22
23 let mut pos = byte_idx;
26 while pos > 0 && !s.is_char_boundary(pos) {
27 pos -= 1;
28 }
29 pos
30}
31
32pub(crate) fn byte_to_char_count(s: &str, byte_idx: usize) -> usize {
40 let safe_byte_idx = find_char_boundary(s, byte_idx);
41 s[..safe_byte_idx].chars().count() + 1 }
43
44#[derive(Debug)]
45pub struct LineIndex<'a> {
46 line_starts: Vec<usize>,
47 content: &'a str,
48 code_block_lines: Option<HashSet<usize>>,
49}
50
51impl<'a> LineIndex<'a> {
52 pub fn new(content: &'a str) -> Self {
53 let mut line_starts = vec![0];
54 let mut pos = 0;
55
56 for c in content.chars() {
57 pos += c.len_utf8();
58 if c == '\n' {
59 line_starts.push(pos);
60 }
61 }
62
63 let mut index = Self {
64 line_starts,
65 content,
66 code_block_lines: None,
67 };
68
69 index.compute_code_block_lines();
71
72 index
73 }
74
75 pub fn with_line_starts(content: &'a str, line_starts: Vec<usize>) -> Self {
79 let mut index = Self {
80 line_starts,
81 content,
82 code_block_lines: None,
83 };
84
85 index.compute_code_block_lines();
87
88 index
89 }
90
91 pub fn with_line_starts_and_code_blocks(
96 content: &'a str,
97 line_starts: Vec<usize>,
98 code_block_byte_ranges: &[(usize, usize)],
99 ) -> Self {
100 let mut code_block_lines = HashSet::new();
101
102 for &(block_start, block_end) in code_block_byte_ranges {
103 let start_line = match line_starts.binary_search(&block_start) {
104 Ok(idx) => idx,
105 Err(idx) => idx.saturating_sub(1),
106 };
107 let end_line = if block_end == 0 {
108 0
109 } else {
110 match line_starts.binary_search(&block_end) {
111 Ok(idx) => idx.saturating_sub(1),
113 Err(idx) => idx.saturating_sub(1),
114 }
115 };
116 for line_idx in start_line..=end_line {
117 code_block_lines.insert(line_idx);
118 }
119 }
120
121 Self {
122 line_starts,
123 content,
124 code_block_lines: Some(code_block_lines),
125 }
126 }
127
128 fn get_line(&self, line_idx: usize) -> Option<&'a str> {
131 let start = *self.line_starts.get(line_idx)?;
132 let end = self
133 .line_starts
134 .get(line_idx + 1)
135 .copied()
136 .unwrap_or(self.content.len());
137 let line = &self.content[start..end];
138 let line = line.strip_suffix('\n').unwrap_or(line);
140 let line = line.strip_suffix('\r').unwrap_or(line);
141 Some(line)
142 }
143
144 pub fn line_col_to_byte_range(&self, line: usize, column: usize) -> Range<usize> {
145 let line = line.saturating_sub(1);
146 let line_start = *self.line_starts.get(line).unwrap_or(&self.content.len());
147
148 let current_line = self.get_line(line).unwrap_or("");
149 let char_col = column.saturating_sub(1);
151 let char_count = current_line.chars().count();
152 let safe_char_col = char_col.min(char_count);
153
154 let byte_offset = current_line
156 .char_indices()
157 .nth(safe_char_col)
158 .map_or(current_line.len(), |(idx, _)| idx);
159
160 let start = line_start + byte_offset;
161 start..start
162 }
163
164 pub fn line_col_to_byte_range_with_length(&self, line: usize, column: usize, length: usize) -> Range<usize> {
171 let line = line.saturating_sub(1);
172 let line_start = *self.line_starts.get(line).unwrap_or(&self.content.len());
173 let line_end = self.line_starts.get(line + 1).copied().unwrap_or(self.content.len());
174 let mut current_line = &self.content[line_start..line_end];
175 if let Some(stripped) = current_line.strip_suffix('\n') {
176 current_line = stripped.strip_suffix('\r').unwrap_or(stripped);
177 }
178 if current_line.is_ascii() {
179 let line_len = current_line.len();
180 let start_byte = column.saturating_sub(1).min(line_len);
181 let end_byte = start_byte.saturating_add(length).min(line_len);
182 let start = line_start + start_byte;
183 let end = line_start + end_byte;
184 return start..end;
185 }
186 let char_col = column.saturating_sub(1);
188 let char_count = current_line.chars().count();
189 let safe_char_col = char_col.min(char_count);
190
191 let mut char_indices = current_line.char_indices();
193 let start_byte = char_indices
194 .nth(safe_char_col)
195 .map_or(current_line.len(), |(idx, _)| idx);
196
197 let end_char_col = (safe_char_col + length).min(char_count);
199 let end_byte = current_line
200 .char_indices()
201 .nth(end_char_col)
202 .map_or(current_line.len(), |(idx, _)| idx);
203
204 let start = line_start + start_byte;
205 let end = line_start + end_byte;
206 start..end
207 }
208
209 pub fn whole_line_range(&self, line: usize) -> Range<usize> {
212 let line_idx = line.saturating_sub(1);
213 let start = *self.line_starts.get(line_idx).unwrap_or(&self.content.len());
214 let end = self
215 .line_starts
216 .get(line_idx + 1)
217 .copied()
218 .unwrap_or(self.content.len());
219 start..end
220 }
221
222 pub fn multi_line_range(&self, start_line: usize, end_line: usize) -> Range<usize> {
225 let start_idx = start_line.saturating_sub(1);
226 let end_idx = end_line.saturating_sub(1);
227
228 let start = *self.line_starts.get(start_idx).unwrap_or(&self.content.len());
229 let end = self.line_starts.get(end_idx + 1).copied().unwrap_or(self.content.len());
230 start..end
231 }
232
233 pub fn line_text_range(&self, line: usize, start_col: usize, end_col: usize) -> Range<usize> {
240 let line_idx = line.saturating_sub(1);
241 let line_start = *self.line_starts.get(line_idx).unwrap_or(&self.content.len());
242
243 let current_line = self.get_line(line_idx).unwrap_or("");
245 let char_count = current_line.chars().count();
246
247 let start_char_col = start_col.saturating_sub(1).min(char_count);
249 let end_char_col = end_col.saturating_sub(1).min(char_count);
250
251 let mut char_indices = current_line.char_indices();
252 let start_byte = char_indices
253 .nth(start_char_col)
254 .map_or(current_line.len(), |(idx, _)| idx);
255
256 let end_byte = current_line
257 .char_indices()
258 .nth(end_char_col)
259 .map_or(current_line.len(), |(idx, _)| idx);
260
261 let start = line_start + start_byte;
262 let end = line_start + end_byte.max(start_byte);
263 start..end
264 }
265
266 pub fn line_content_range(&self, line: usize) -> Range<usize> {
269 let line_idx = line.saturating_sub(1);
270 let line_start = *self.line_starts.get(line_idx).unwrap_or(&self.content.len());
271
272 let current_line = self.get_line(line_idx).unwrap_or("");
273 let line_end = line_start + current_line.len();
274 line_start..line_end
275 }
276
277 pub fn get_line_start_byte(&self, line_num: usize) -> Option<usize> {
279 if line_num == 0 {
280 return None; }
282 self.line_starts.get(line_num - 1).copied()
284 }
285
286 pub fn is_code_block(&self, line: usize) -> bool {
288 if let Some(ref code_block_lines) = self.code_block_lines {
289 code_block_lines.contains(&line)
290 } else {
291 self.is_code_fence(line)
293 }
294 }
295
296 pub fn is_code_fence(&self, line: usize) -> bool {
298 self.get_line(line).is_some_and(|l| {
299 let trimmed = l.trim();
300 trimmed.starts_with("```") || trimmed.starts_with("~~~")
301 })
302 }
303
304 pub fn is_tilde_code_block(&self, line: usize) -> bool {
306 self.get_line(line).is_some_and(|l| l.trim().starts_with("~~~"))
307 }
308
309 pub fn get_content(&self) -> &str {
311 self.content
312 }
313
314 fn compute_code_block_lines(&mut self) {
316 let mut code_block_lines = HashSet::new();
317 let lines: Vec<&str> = self.content.lines().collect();
318
319 let mut in_block = false;
321 let mut active_fence_type = ' '; let mut block_indent = 0;
323 let mut block_fence_length = 0;
324 let mut in_markdown_block = false;
325 let mut nested_fence_start = None;
326 let mut nested_fence_end = None;
327
328 for (i, line) in lines.iter().enumerate() {
330 let trimmed = line.trim();
331 let indent = line.len() - trimmed.len();
332
333 if calculate_indentation_width_default(line) >= 4 {
335 code_block_lines.insert(i);
336 continue; }
338
339 if !in_block {
341 if trimmed.starts_with("```") || trimmed.starts_with("~~~") {
343 let char_type = if trimmed.starts_with("```") { '`' } else { '~' };
344 let count = trimmed.chars().take_while(|&c| c == char_type).count();
345 let info_string = if trimmed.len() > count {
346 trimmed[count..].trim()
347 } else {
348 ""
349 };
350
351 in_block = true;
353 active_fence_type = char_type;
354 block_indent = indent;
355 block_fence_length = count;
356 in_markdown_block = info_string == "markdown";
357 nested_fence_start = None;
358 nested_fence_end = None;
359
360 code_block_lines.insert(i);
361 }
362 } else {
363 code_block_lines.insert(i);
365
366 if in_markdown_block && nested_fence_start.is_none() && trimmed.starts_with("```") {
368 let count = trimmed.chars().take_while(|&c| c == '`').count();
370 let remaining = if trimmed.len() > count {
371 trimmed[count..].trim()
372 } else {
373 ""
374 };
375
376 if !remaining.is_empty() {
377 nested_fence_start = Some(i);
378 }
379 }
380
381 if in_markdown_block
383 && nested_fence_start.is_some()
384 && nested_fence_end.is_none()
385 && trimmed.starts_with("```")
386 && trimmed.trim_start_matches('`').trim().is_empty()
387 {
388 nested_fence_end = Some(i);
389 }
390
391 if trimmed.starts_with(&active_fence_type.to_string().repeat(3)) {
393 let count = trimmed.chars().take_while(|&c| c == active_fence_type).count();
394 let remaining = if trimmed.len() > count {
395 trimmed[count..].trim()
396 } else {
397 ""
398 };
399
400 let is_valid_closing_fence =
406 count >= block_fence_length && remaining.is_empty() && indent <= block_indent;
407
408 let is_nested_closing = nested_fence_end.is_some() && i == nested_fence_end.unwrap();
411
412 if is_valid_closing_fence && !is_nested_closing {
414 in_block = false;
415 in_markdown_block = false;
416 }
417 }
418 }
419 }
420
421 self.code_block_lines = Some(code_block_lines);
422 }
423}
424
425pub fn calculate_single_line_range(line: usize, start_col: usize, length: usize) -> (usize, usize, usize, usize) {
427 (line, start_col, line, start_col + length)
428}
429
430pub fn calculate_line_range(line: usize, line_content: &str) -> (usize, usize, usize, usize) {
435 let trimmed_char_len = line_content.trim_end().chars().count();
436 (line, 1, line, trimmed_char_len + 1)
437}
438
439pub fn calculate_match_range(
445 line: usize,
446 line_content: &str,
447 match_start: usize,
448 match_len: usize,
449) -> (usize, usize, usize, usize) {
450 let line_len = line_content.len();
452 if match_start > line_len {
453 let char_count = line_content.chars().count();
455 return (line, char_count + 1, line, char_count + 1);
456 }
457
458 let safe_match_start = find_char_boundary(line_content, match_start);
460 let safe_match_end_byte = find_char_boundary(line_content, (match_start + match_len).min(line_len));
461
462 let char_start = byte_to_char_count(line_content, safe_match_start);
464 let char_len = if safe_match_end_byte > safe_match_start {
465 line_content[safe_match_start..safe_match_end_byte].chars().count()
467 } else {
468 0
469 };
470 (line, char_start, line, char_start + char_len)
471}
472
473pub fn calculate_trailing_range(line: usize, line_content: &str, content_end: usize) -> (usize, usize, usize, usize) {
479 let safe_content_end = find_char_boundary(line_content, content_end);
481 let char_content_end = byte_to_char_count(line_content, safe_content_end);
482 let line_char_len = line_content.chars().count() + 1;
483 (line, char_content_end, line, line_char_len)
484}
485
486pub fn calculate_heading_range(line: usize, line_content: &str) -> (usize, usize, usize, usize) {
488 calculate_line_range(line, line_content)
489}
490
491pub fn calculate_emphasis_range(
497 line: usize,
498 line_content: &str,
499 start_pos: usize,
500 end_pos: usize,
501) -> (usize, usize, usize, usize) {
502 let safe_start_pos = find_char_boundary(line_content, start_pos);
504 let safe_end_pos = find_char_boundary(line_content, end_pos);
505 let char_start = byte_to_char_count(line_content, safe_start_pos);
506 let char_end = byte_to_char_count(line_content, safe_end_pos);
507 (line, char_start, line, char_end)
508}
509
510pub fn calculate_html_tag_range(
512 line: usize,
513 line_content: &str,
514 tag_start: usize,
515 tag_len: usize,
516) -> (usize, usize, usize, usize) {
517 calculate_match_range(line, line_content, tag_start, tag_len)
518}
519
520pub fn calculate_url_range(
522 line: usize,
523 line_content: &str,
524 url_start: usize,
525 url_len: usize,
526) -> (usize, usize, usize, usize) {
527 calculate_match_range(line, line_content, url_start, url_len)
528}
529
530pub fn calculate_list_marker_range(
532 line: usize,
533 line_content: &str,
534 marker_start: usize,
535 marker_len: usize,
536) -> (usize, usize, usize, usize) {
537 calculate_match_range(line, line_content, marker_start, marker_len)
538}
539
540pub fn calculate_excess_range(line: usize, line_content: &str, limit: usize) -> (usize, usize, usize, usize) {
542 let char_limit = std::cmp::min(limit, line_content.chars().count());
543 let line_char_len = line_content.chars().count() + 1;
544 (line, char_limit + 1, line, line_char_len)
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550
551 #[test]
552 fn test_single_line_range() {
553 let (start_line, start_col, end_line, end_col) = calculate_single_line_range(5, 10, 3);
554 assert_eq!(start_line, 5);
555 assert_eq!(start_col, 10);
556 assert_eq!(end_line, 5);
557 assert_eq!(end_col, 13);
558 }
559
560 #[test]
561 fn test_line_range() {
562 let content = "# This is a heading ";
563 let (start_line, start_col, end_line, end_col) = calculate_line_range(1, content);
564 assert_eq!(start_line, 1);
565 assert_eq!(start_col, 1);
566 assert_eq!(end_line, 1);
567 assert_eq!(end_col, 20); }
569
570 #[test]
571 fn test_line_range_non_ascii() {
572 let content = "你好 heading ";
576 let (_, start_col, _, end_col) = calculate_line_range(1, content);
577 assert_eq!(start_col, 1);
578 assert_eq!(end_col, 11);
579 }
580
581 #[test]
582 fn test_match_range() {
583 let content = "Text <div>content</div> more";
584 let tag_start = 5; let tag_len = 5; let (start_line, start_col, end_line, end_col) = calculate_match_range(1, content, tag_start, tag_len);
587 assert_eq!(start_line, 1);
588 assert_eq!(start_col, 6); assert_eq!(end_line, 1);
590 assert_eq!(end_col, 11); }
592
593 #[test]
594 fn test_trailing_range() {
595 let content = "Text content "; let content_end = 12; let (start_line, start_col, end_line, end_col) = calculate_trailing_range(1, content, content_end);
598 assert_eq!(start_line, 1);
599 assert_eq!(start_col, 13); assert_eq!(end_line, 1);
601 assert_eq!(end_col, 16); }
603
604 #[test]
605 fn test_excess_range() {
606 let content = "This line is too long for the limit";
607 let limit = 20;
608 let (start_line, start_col, end_line, end_col) = calculate_excess_range(1, content, limit);
609 assert_eq!(start_line, 1);
610 assert_eq!(start_col, 21); assert_eq!(end_line, 1);
612 assert_eq!(end_col, 36); }
614
615 #[test]
616 fn test_whole_line_range() {
617 let content = "Line 1\nLine 2\nLine 3";
618 let line_index = LineIndex::new(content);
619
620 let range = line_index.whole_line_range(1);
622 assert_eq!(range, 0..7); let range = line_index.whole_line_range(2);
626 assert_eq!(range, 7..14); let range = line_index.whole_line_range(3);
630 assert_eq!(range, 14..20); }
632
633 #[test]
634 fn test_line_content_range() {
635 let content = "Line 1\nLine 2\nLine 3";
636 let line_index = LineIndex::new(content);
637
638 let range = line_index.line_content_range(1);
640 assert_eq!(range, 0..6); let range = line_index.line_content_range(2);
644 assert_eq!(range, 7..13); let range = line_index.line_content_range(3);
648 assert_eq!(range, 14..20); }
650
651 #[test]
652 fn test_line_text_range() {
653 let content = "Hello world\nAnother line";
654 let line_index = LineIndex::new(content);
655
656 let range = line_index.line_text_range(1, 1, 5); assert_eq!(range, 0..4);
659
660 let range = line_index.line_text_range(2, 1, 7); assert_eq!(range, 12..18);
663
664 let range = line_index.line_text_range(1, 1, 100); assert_eq!(range, 0..11); }
668
669 #[test]
670 fn test_calculate_match_range_bounds_checking() {
671 let line_content = "] not a link [";
673 let (line, start_col, end_line, end_col) = calculate_match_range(121, line_content, 57, 10);
674 assert_eq!(line, 121);
675 assert_eq!(start_col, 15); assert_eq!(end_line, 121);
677 assert_eq!(end_col, 15); let line_content = "short";
681 let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 2, 10);
682 assert_eq!(line, 1);
683 assert_eq!(start_col, 3); assert_eq!(end_line, 1);
685 assert_eq!(end_col, 6); let line_content = "normal text here";
689 let (line, start_col, end_line, end_col) = calculate_match_range(5, line_content, 7, 4);
690 assert_eq!(line, 5);
691 assert_eq!(start_col, 8); assert_eq!(end_line, 5);
693 assert_eq!(end_col, 12); let line_content = "test line";
697 let (line, start_col, end_line, end_col) = calculate_match_range(10, line_content, 5, 0);
698 assert_eq!(line, 10);
699 assert_eq!(start_col, 6); assert_eq!(end_line, 10);
701 assert_eq!(end_col, 6); }
703
704 #[test]
709 fn test_issue_154_korean_character_boundary() {
710 let line_content = "- 2023 년 초 이후 주가 상승 +1,000% (10 배 상승) ";
713
714 let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 19, 1);
717
718 assert!(start_col > 0);
720 assert_eq!(line, 1);
721 assert_eq!(end_line, 1);
722 assert!(end_col >= start_col);
723 }
724
725 #[test]
726 fn test_calculate_match_range_korean() {
727 let line_content = "안녕하세요";
730 let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 3, 3);
732 assert_eq!(line, 1);
733 assert_eq!(start_col, 2); assert_eq!(end_line, 1);
735 assert_eq!(end_col, 3); let (line, start_col, end_line, _end_col) = calculate_match_range(1, line_content, 4, 3);
739 assert_eq!(line, 1);
740 assert_eq!(start_col, 2); assert_eq!(end_line, 1);
742 }
743
744 #[test]
745 fn test_calculate_match_range_chinese() {
746 let line_content = "你好世界";
749 let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 6, 3);
751 assert_eq!(line, 1);
752 assert_eq!(start_col, 3); assert_eq!(end_line, 1);
754 assert_eq!(end_col, 4); }
756
757 #[test]
758 fn test_calculate_match_range_japanese() {
759 let line_content = "こんにちは";
762 let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 9, 3);
764 assert_eq!(line, 1);
765 assert_eq!(start_col, 4); assert_eq!(end_line, 1);
767 assert_eq!(end_col, 5); }
769
770 #[test]
771 fn test_calculate_match_range_mixed_unicode() {
772 let line_content = "Hello 世界";
777
778 let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 5, 1);
780 assert_eq!(line, 1);
781 assert_eq!(start_col, 6); assert_eq!(end_line, 1);
783 assert_eq!(end_col, 7); let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 6, 3);
787 assert_eq!(line, 1);
788 assert_eq!(start_col, 7); assert_eq!(end_line, 1);
790 assert_eq!(end_col, 8); }
792
793 #[test]
794 fn test_calculate_trailing_range_korean() {
795 let line_content = "안녕하세요 ";
797 let (line, start_col, end_line, end_col) = calculate_trailing_range(1, line_content, 15);
799 assert_eq!(line, 1);
800 assert!(start_col > 0);
801 assert_eq!(end_line, 1);
802 assert!(end_col > start_col);
803 }
804
805 #[test]
806 fn test_calculate_emphasis_range_chinese() {
807 let line_content = "这是**重要**的";
809 let (line, start_col, end_line, end_col) = calculate_emphasis_range(1, line_content, 6, 12);
811 assert_eq!(line, 1);
812 assert!(start_col > 0);
813 assert_eq!(end_line, 1);
814 assert!(end_col > start_col);
815 }
816
817 #[test]
818 fn test_line_col_to_byte_range_korean() {
819 let content = "안녕하세요\nWorld";
821 let line_index = LineIndex::new(content);
822
823 let range = line_index.line_col_to_byte_range(1, 1);
825 assert_eq!(range, 0..0);
826
827 let range = line_index.line_col_to_byte_range(1, 2);
829 assert_eq!(range, 3..3); let range = line_index.line_col_to_byte_range(1, 3);
833 assert_eq!(range, 6..6); }
835
836 #[test]
837 fn test_line_col_to_byte_range_with_length_chinese() {
838 let content = "你好世界\nTest";
840 let line_index = LineIndex::new(content);
841
842 let range = line_index.line_col_to_byte_range_with_length(1, 1, 2);
844 assert_eq!(range, 0..6); let range = line_index.line_col_to_byte_range_with_length(1, 2, 1);
848 assert_eq!(range, 3..6); }
850
851 #[test]
852 fn test_line_text_range_japanese() {
853 let content = "こんにちは\nHello";
855 let line_index = LineIndex::new(content);
856
857 let range = line_index.line_text_range(1, 2, 4);
859 assert_eq!(range, 3..9); }
861
862 #[test]
863 fn test_find_char_boundary_edge_cases() {
864 let s = "안녕";
866
867 assert_eq!(find_char_boundary(s, 0), 0);
869
870 assert_eq!(find_char_boundary(s, 1), 0);
872
873 assert_eq!(find_char_boundary(s, 2), 0);
875
876 assert_eq!(find_char_boundary(s, 3), 3);
878
879 assert_eq!(find_char_boundary(s, 4), 3);
881
882 assert_eq!(find_char_boundary(s, 100), s.len());
884 }
885
886 #[test]
887 fn test_byte_to_char_count_unicode() {
888 let s = "안녕하세요";
890
891 assert_eq!(byte_to_char_count(s, 0), 1);
893
894 assert_eq!(byte_to_char_count(s, 3), 2);
896
897 assert_eq!(byte_to_char_count(s, 6), 3);
899
900 assert_eq!(byte_to_char_count(s, 9), 4);
902
903 assert_eq!(byte_to_char_count(s, 12), 5);
905
906 assert_eq!(byte_to_char_count(s, 15), 6);
908 }
909
910 #[test]
911 fn test_all_range_functions_with_emoji() {
912 let line_content = "Hello 🎉 World 🌍";
914
915 let (line, start_col, end_line, end_col) = calculate_match_range(1, line_content, 6, 4);
917 assert_eq!(line, 1);
918 assert!(start_col > 0);
919 assert_eq!(end_line, 1);
920 assert!(end_col > start_col);
921
922 let (line, start_col, end_line, end_col) = calculate_trailing_range(1, line_content, 12);
924 assert_eq!(line, 1);
925 assert!(start_col > 0);
926 assert_eq!(end_line, 1);
927 assert!(end_col > start_col);
928
929 let (line, start_col, end_line, end_col) = calculate_emphasis_range(1, line_content, 0, 5);
931 assert_eq!(line, 1);
932 assert_eq!(start_col, 1);
933 assert_eq!(end_line, 1);
934 assert!(end_col > start_col);
935 }
936}