source_span/
layout.rs

1use std::iter::Extend;
2use crate::{
3	Position,
4	Span,
5	Metrics
6};
7
8/// Text layout.
9///
10/// Keep track of the byte index of each line in a UTF8-encoded so it can be indexed by cursor
11/// position.
12pub struct Layout<M: Metrics> {
13	/// Byte index of each line.
14	lines: Vec<usize>,
15
16	/// Span of the text.
17	span: Span,
18
19	/// Metrics used.
20	metrics: M,
21
22	/// Byte length of the text.
23	len: usize
24}
25
26impl<M: Metrics> Layout<M> {
27	/// Create a new empty layout from a given metrics.
28	pub fn new(metrics: M) -> Layout<M> {
29		Layout {
30			lines: vec![0],
31			span: Span::default(),
32			metrics,
33			len: 0
34		}
35	}
36
37	/// Get the layout's span.
38	pub fn span(&self) -> Span {
39		self.span
40	}
41
42	/// Create a new layout from a `char` iterator.
43	pub fn from<Chars: Iterator<Item=char>>(chars: Chars, metrics: M) -> Layout<M> {
44		let mut layout = Layout::new(metrics);
45		layout.extend(chars);
46		layout
47	}
48
49	/// Try to create a new layout from an unreliable `char` iterator.
50	pub fn try_from<E, Chars: Iterator<Item=Result<char, E>>>(chars: Chars, metrics: M) -> Result<Layout<M>, E> {
51		let mut layout = Layout::new(metrics);
52
53		for c in chars {
54			layout.push(c?)
55		}
56
57		Ok(layout)
58	}
59
60	/// Extend the layout with a new character.
61	pub fn push(&mut self, c: char) {
62		self.span.push(c, &self.metrics);
63		self.len += c.len_utf8();
64		if c == '\n' {
65			self.lines.push(self.len)
66		}
67	}
68
69	/// Get the byte index mapping to the given position in the input string slice.
70	///
71	/// It is assumed that the input string slice matches the layout.
72	/// Otherwise, the returned index may not point to an UTF8 character boundary nor even be in
73	/// the slice bounds.
74	pub fn byte_index(&self, str: &str, position: Position) -> Option<usize> {
75		if let Some(line_offset) = self.lines.get(position.line) {
76			let mut column = 0;
77			for (i, c) in str[*line_offset..].char_indices() {
78				if column == position.column {
79					return Some(line_offset + i)
80				}
81
82				if c == '\n' {
83					return None
84				}
85
86				column += self.metrics.char_width(c)
87			}
88		}
89
90		None
91	}
92
93	/// Get the sub slice of the input string matching the given span.
94	pub fn span_slice<'a>(&self, str: &'a str, span: Span) -> &'a str {
95		let start = match self.byte_index(str, span.start) {
96			Some(index) => index,
97			None => 0
98		};
99
100		let end = match self.byte_index(str, span.end) {
101			Some(index) => index,
102			None => str.len()
103		};
104
105		&str[start..end]
106	}
107}
108
109impl<M: Metrics> Extend<char> for Layout<M> {
110	fn extend<Chars: IntoIterator<Item=char>>(&mut self, chars: Chars) {
111		for c in chars {
112			self.push(c)
113		}
114	}
115}
116
117#[cfg(test)]
118mod tests {
119	use super::*;
120
121	#[test]
122	fn get_byte_index1() {
123		let str = "Hello World!";
124		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
125
126		assert_eq!(layout.byte_index(str, Position::new(0, 2)), Some(2));
127	}
128
129	#[test]
130	fn get_byte_index2() {
131		let str = "Hello\nWorld!";
132		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
133
134		assert_eq!(layout.byte_index(str, Position::new(1, 0)), Some(6));
135	}
136
137	#[test]
138	fn get_byte_index3() {
139		let str = "Hel\nlo\nWorld!";
140		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
141
142		assert_eq!(layout.byte_index(str, Position::new(2, 0)), Some(7));
143	}
144
145	#[test]
146	fn get_byte_index_out_of_bounds1() {
147		let str = "Hel\nlo\nWorld!";
148		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
149
150		assert_eq!(layout.byte_index(str, Position::new(3, 0)), None);
151	}
152
153	#[test]
154	fn get_byte_index_out_of_bounds2() {
155		let str = "Hel\nlo\nWorld!";
156		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
157
158		assert_eq!(layout.byte_index(str, Position::new(1, 3)), None);
159	}
160
161	#[test]
162	fn get_span_slice1() {
163		let str = "Hello\nWorld!";
164		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
165
166		assert_eq!(layout.span_slice(str, layout.span), str);
167	}
168
169	#[test]
170	fn get_span_slice2() {
171		let str = "Hel\nlo\nWorld!";
172		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
173
174		let span = Span::new(Position::new(0, 0), Position::new(0, 3), Position::new(1, 0));
175		assert_eq!(layout.span_slice(str, span), "Hel\n");
176	}
177
178	#[test]
179	fn get_span_slice3() {
180		let str = "Hel\nlo\nWorld!";
181		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
182
183		let span = Span::new(Position::new(1, 0), Position::new(1, 2), Position::new(2, 0));
184		assert_eq!(layout.span_slice(str, span), "lo\n");
185	}
186
187	#[test]
188	fn get_span_slice4() {
189		let str = "Hel\nlo\nWorld!";
190		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
191
192		let span = Span::new(Position::new(2, 0), Position::new(2, 5), Position::new(2, 6));
193		assert_eq!(layout.span_slice(str, span), "World!");
194	}
195
196	#[test]
197	fn get_span_slice5() {
198		let str = "Hel\nlo\nWorld!";
199		let layout = Layout::from(str.chars(), crate::DEFAULT_METRICS);
200
201		let span = Span::new(Position::new(0, 2), Position::new(2, 2), Position::new(2, 3));
202		assert_eq!(layout.span_slice(str, span), "l\nlo\nWor");
203	}
204}