on master help sign in
Fix panics when resizing (#1408)
f5b0821Mathis Brossier
helix-term/src/ui/editor.rs+2−4
@@ -97,8 +97,7 @@ impl EditorView {
97 let x = area.right();
97 let x = area.right();
98 let border_style = theme.get("ui.window");
98 let border_style = theme.get("ui.window");
99 for y in area.top()..area.bottom() {
99 for y in area.top()..area.bottom() {
100− surface
100+ surface[(x, y)]
101− .get_mut(x, y)
102 .set_symbol(tui::symbols::line::VERTICAL)
101 .set_symbol(tui::symbols::line::VERTICAL)
103 //.set_symbol(" ")
102 //.set_symbol(" ")
104 .set_style(border_style);
103 .set_style(border_style);
@@ -393,8 +392,7 @@ impl EditorView {
393 .add_modifier(Modifier::DIM)
392 .add_modifier(Modifier::DIM)
394 });
393 });
395
394
396− surface
397− .get_mut(viewport.x + pos.col as u16, viewport.y + pos.row as u16)
395+ surface[(viewport.x + pos.col as u16, viewport.y + pos.row as u16)]
398 .set_style(style);
396 .set_style(style);
399 }
397 }
400 }
398 }
helix-term/src/ui/menu.rs+1−1
@@ -301,7 +301,7 @@ impl<T: Item + 'static> Component for Menu<T> {
301 let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
301 let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
302
302
303 if is_marked {
303 if is_marked {
304− let cell = surface.get_mut(area.x + area.width - 2, area.y + i as u16);
304+ let cell = &mut surface[(area.x + area.width - 2, area.y + i as u16)];
305 cell.set_symbol("▐ ");
305 cell.set_symbol("▐ ");
306 // cell.set_style(selected);
306 // cell.set_style(selected);
307 // cell.set_style(if is_marked { selected } else { style });
307 // cell.set_style(if is_marked { selected } else { style });
helix-term/src/ui/picker.rs+6−6
@@ -159,6 +159,7 @@ impl<T: 'static> Component for FilePicker<T> {
159 // |picker | | |
159 // |picker | | |
160 // | | | |
160 // | | | |
161 // +---------+ +---------+
161 // +---------+ +---------+
162+
162 let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW;
163 let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW;
163 let area = inner_rect(area);
164 let area = inner_rect(area);
164 // -- Render the frame:
165 // -- Render the frame:
@@ -492,10 +493,9 @@ impl<T: 'static> Component for Picker<T> {
492 let sep_style = Style::default().fg(Color::Rgb(90, 89, 119));
493 let sep_style = Style::default().fg(Color::Rgb(90, 89, 119));
493 let borders = BorderType::line_symbols(BorderType::Plain);
494 let borders = BorderType::line_symbols(BorderType::Plain);
494 for x in inner.left()..inner.right() {
495 for x in inner.left()..inner.right() {
495− surface
496− .get_mut(x, inner.y + 1)
496+ if let Some(cell) = surface.get_mut(x, inner.y + 1) {
497− .set_symbol(borders.horizontal)
497+ cell.set_symbol(borders.horizontal).set_style(sep_style);
498− .set_style(sep_style);
498+ }
499 }
499 }
500
500
501 // -- Render the contents:
501 // -- Render the contents:
@@ -505,7 +505,7 @@ impl<T: 'static> Component for Picker<T> {
505 let selected = cx.editor.theme.get("ui.text.focus");
505 let selected = cx.editor.theme.get("ui.text.focus");
506
506
507 let rows = inner.height;
507 let rows = inner.height;
508− let offset = self.cursor / (rows as usize) * (rows as usize);
508+ let offset = self.cursor / std::cmp::max(1, (rows as usize) * (rows as usize));
509
509
510 let files = self.matches.iter().skip(offset).map(|(index, _score)| {
510 let files = self.matches.iter().skip(offset).map(|(index, _score)| {
511 (index, self.options.get(*index).unwrap()) // get_unchecked
511 (index, self.options.get(*index).unwrap()) // get_unchecked
@@ -513,7 +513,7 @@ impl<T: 'static> Component for Picker<T> {
513
513
514 for (i, (_index, option)) in files.take(rows as usize).enumerate() {
514 for (i, (_index, option)) in files.take(rows as usize).enumerate() {
515 if i == (self.cursor - offset) {
515 if i == (self.cursor - offset) {
516− surface.set_string(inner.x - 2, inner.y + i as u16, ">", selected);
516+ surface.set_string(inner.x.saturating_sub(2), inner.y + i as u16, ">", selected);
517 }
517 }
518
518
519 surface.set_string_truncated(
519 surface.set_string_truncated(
helix-term/src/ui/prompt.rs+1−1
@@ -330,7 +330,7 @@ impl Prompt {
330 .max(BASE_WIDTH);
330 .max(BASE_WIDTH);
331
331
332 let cols = std::cmp::max(1, area.width / max_len);
332 let cols = std::cmp::max(1, area.width / max_len);
333− let col_width = (area.width - (cols)) / cols;
333+ let col_width = (area.width.saturating_sub(cols)) / cols;
334
334
335 let height = ((self.completion.len() as u16 + cols - 1) / cols)
335 let height = ((self.completion.len() as u16 + cols - 1) / cols)
336 .min(10) // at most 10 rows (or less)
336 .min(10) // at most 10 rows (or less)
helix-tui/src/backend/test.rs+1−2
@@ -111,8 +111,7 @@ impl Backend for TestBackend {
111 I: Iterator<Item = (u16, u16, &'a Cell)>,
111 I: Iterator<Item = (u16, u16, &'a Cell)>,
112 {
112 {
113 for (x, y, c) in content {
113 for (x, y, c) in content {
114− let cell = self.buffer.get_mut(x, y);
114+ self.buffer[(x, y)] = c.clone();
115− *cell = c.clone();
116 }
115 }
117 Ok(())
116 Ok(())
118 }
117 }
helix-tui/src/buffer.rs+71−20
@@ -90,17 +90,17 @@ impl Default for Cell {
90/// use helix_view::graphics::{Rect, Color, Style, Modifier};
90/// use helix_view::graphics::{Rect, Color, Style, Modifier};
91///
91///
92/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
92/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5});
93−/// buf.get_mut(0, 2).set_symbol("x");
93+/// buf[(0, 2)].set_symbol("x");
94−/// assert_eq!(buf.get(0, 2).symbol, "x");
94+/// assert_eq!(buf[(0, 2)].symbol, "x");
95/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
95/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White));
96−/// assert_eq!(buf.get(5, 0), &Cell{
96+/// assert_eq!(buf[(5, 0)], Cell{
97/// symbol: String::from("r"),
97/// symbol: String::from("r"),
98/// fg: Color::Red,
98/// fg: Color::Red,
99/// bg: Color::White,
99/// bg: Color::White,
100/// modifier: Modifier::empty()
100/// modifier: Modifier::empty()
101/// });
101/// });
102−/// buf.get_mut(5, 0).set_char('x');
102+/// buf[(5, 0)].set_char('x');
103−/// assert_eq!(buf.get(5, 0).symbol, "x");
103+/// assert_eq!(buf[(5, 0)].symbol, "x");
104/// ```
104/// ```
105#[derive(Debug, Default, Clone, PartialEq)]
105#[derive(Debug, Default, Clone, PartialEq)]
106pub struct Buffer {
106pub struct Buffer {
@@ -162,15 +162,38 @@ impl Buffer {
162 }
162 }
163
163
164 /// Returns a reference to Cell at the given coordinates
164 /// Returns a reference to Cell at the given coordinates
165− pub fn get(&self, x: u16, y: u16) -> &Cell {
165+ pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
166− let i = self.index_of(x, y);
167− &self.content[i]
166+ self.index_of_opt(x, y).map(|i| &self.content[i])
168 }
167 }
169
168
170 /// Returns a mutable reference to Cell at the given coordinates
169 /// Returns a mutable reference to Cell at the given coordinates
171− pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
170+ pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
172− let i = self.index_of(x, y);
173− &mut self.content[i]
171+ self.index_of_opt(x, y).map(|i| &mut self.content[i])
172+ }
173+
174+ /// Tells whether the global (x, y) coordinates are inside the Buffer's area.
175+ ///
176+ /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
177+ ///
178+ /// # Examples
179+ ///
180+ /// ```
181+ /// # use helix_tui::buffer::Buffer;
182+ /// # use helix_view::graphics::Rect;
183+ /// let rect = Rect::new(200, 100, 10, 10);
184+ /// let buffer = Buffer::empty(rect);
185+ /// // Global coordinates inside the Buffer's area
186+ /// assert!(buffer.in_bounds(209, 100));
187+ /// // Global coordinates outside the Buffer's area
188+ /// assert!(!buffer.in_bounds(210, 100));
189+ /// ```
190+ ///
191+ /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
192+ pub fn in_bounds(&self, x: u16, y: u16) -> bool {
193+ x >= self.area.left()
194+ && x < self.area.right()
195+ && y >= self.area.top()
196+ && y < self.area.bottom()
174 }
197 }
175
198
176 /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
199 /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
@@ -184,7 +207,7 @@ impl Buffer {
184 /// # use helix_view::graphics::Rect;
207 /// # use helix_view::graphics::Rect;
185 /// let rect = Rect::new(200, 100, 10, 10);
208 /// let rect = Rect::new(200, 100, 10, 10);
186 /// let buffer = Buffer::empty(rect);
209 /// let buffer = Buffer::empty(rect);
187− /// // Global coordinates to the top corner of this buffer's area
210+ /// // Global coordinates to the top corner of this Buffer's area
188 /// assert_eq!(buffer.index_of(200, 100), 0);
211 /// assert_eq!(buffer.index_of(200, 100), 0);
189 /// ```
212 /// ```
190 ///
213 ///
@@ -193,10 +216,7 @@ impl Buffer {
193 /// Panics when given an coordinate that is outside of this Buffer's area.
216 /// Panics when given an coordinate that is outside of this Buffer's area.
194 pub fn index_of(&self, x: u16, y: u16) -> usize {
217 pub fn index_of(&self, x: u16, y: u16) -> usize {
195 debug_assert!(
218 debug_assert!(
196− x >= self.area.left()
219+ self.in_bounds(x, y),
197− && x < self.area.right()
198− && y >= self.area.top()
199− && y < self.area.bottom(),
200 "Trying to access position outside the buffer: x={}, y={}, area={:?}",
220 "Trying to access position outside the buffer: x={}, y={}, area={:?}",
201 x,
221 x,
202 y,
222 y,
@@ -205,6 +225,16 @@ impl Buffer {
205 ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
225 ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
206 }
226 }
207
227
228+ /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates,
229+ /// or `None` if the coordinates are outside the buffer's area.
230+ fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
231+ if self.in_bounds(x, y) {
232+ Some(self.index_of(x, y))
233+ } else {
234+ None
235+ }
236+ }
237+
208 /// Returns the (global) coordinates of a cell given its index
238 /// Returns the (global) coordinates of a cell given its index
209 ///
239 ///
210 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
240 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
@@ -278,6 +308,11 @@ impl Buffer {
278 where
308 where
279 S: AsRef<str>,
309 S: AsRef<str>,
280 {
310 {
311+ // prevent panic if out of range
312+ if !self.in_bounds(x, y) || width == 0 {
313+ return (x, y);
314+ }
315+
281 let mut index = self.index_of(x, y);
316 let mut index = self.index_of(x, y);
282 let mut x_offset = x as usize;
317 let mut x_offset = x as usize;
283 let width = if ellipsis { width - 1 } else { width };
318 let width = if ellipsis { width - 1 } else { width };
@@ -372,7 +407,7 @@ impl Buffer {
372 pub fn set_background(&mut self, area: Rect, color: Color) {
407 pub fn set_background(&mut self, area: Rect, color: Color) {
373 for y in area.top()..area.bottom() {
408 for y in area.top()..area.bottom() {
374 for x in area.left()..area.right() {
409 for x in area.left()..area.right() {
375− self.get_mut(x, y).set_bg(color);
410+ self[(x, y)].set_bg(color);
376 }
411 }
377 }
412 }
378 }
413 }
@@ -380,7 +415,7 @@ impl Buffer {
380 pub fn set_style(&mut self, area: Rect, style: Style) {
415 pub fn set_style(&mut self, area: Rect, style: Style) {
381 for y in area.top()..area.bottom() {
416 for y in area.top()..area.bottom() {
382 for x in area.left()..area.right() {
417 for x in area.left()..area.right() {
383− self.get_mut(x, y).set_style(style);
418+ self[(x, y)].set_style(style);
384 }
419 }
385 }
420 }
386 }
421 }
@@ -408,7 +443,7 @@ impl Buffer {
408 pub fn clear(&mut self, area: Rect) {
443 pub fn clear(&mut self, area: Rect) {
409 for x in area.left()..area.right() {
444 for x in area.left()..area.right() {
410 for y in area.top()..area.bottom() {
445 for y in area.top()..area.bottom() {
411− self.get_mut(x, y).reset();
446+ self[(x, y)].reset();
412 }
447 }
413 }
448 }
414 }
449 }
@@ -417,7 +452,7 @@ impl Buffer {
417 pub fn clear_with(&mut self, area: Rect, style: Style) {
452 pub fn clear_with(&mut self, area: Rect, style: Style) {
418 for x in area.left()..area.right() {
453 for x in area.left()..area.right() {
419 for y in area.top()..area.bottom() {
454 for y in area.top()..area.bottom() {
420− let cell = self.get_mut(x, y);
455+ let cell = &mut self[(x, y)];
421 cell.reset();
456 cell.reset();
422 cell.set_style(style);
457 cell.set_style(style);
423 }
458 }
@@ -509,6 +544,22 @@ impl Buffer {
509 }
544 }
510}
545}
511
546
547+impl std::ops::Index<(u16, u16)> for Buffer {
548+ type Output = Cell;
549+
550+ fn index(&self, (x, y): (u16, u16)) -> &Self::Output {
551+ let i = self.index_of(x, y);
552+ &self.content[i]
553+ }
554+}
555+
556+impl std::ops::IndexMut<(u16, u16)> for Buffer {
557+ fn index_mut(&mut self, (x, y): (u16, u16)) -> &mut Self::Output {
558+ let i = self.index_of(x, y);
559+ &mut self.content[i]
560+ }
561+}
562+
512#[cfg(test)]
563#[cfg(test)]
513mod tests {
564mod tests {
514 use super::*;
565 use super::*;
helix-tui/src/widgets/block.rs+8−8
@@ -140,14 +140,14 @@ impl<'a> Widget for Block<'a> {
140 // Sides
140 // Sides
141 if self.borders.intersects(Borders::LEFT) {
141 if self.borders.intersects(Borders::LEFT) {
142 for y in area.top()..area.bottom() {
142 for y in area.top()..area.bottom() {
143− buf.get_mut(area.left(), y)
143+ buf[(area.left(), y)]
144 .set_symbol(symbols.vertical)
144 .set_symbol(symbols.vertical)
145 .set_style(self.border_style);
145 .set_style(self.border_style);
146 }
146 }
147 }
147 }
148 if self.borders.intersects(Borders::TOP) {
148 if self.borders.intersects(Borders::TOP) {
149 for x in area.left()..area.right() {
149 for x in area.left()..area.right() {
150− buf.get_mut(x, area.top())
150+ buf[(x, area.top())]
151 .set_symbol(symbols.horizontal)
151 .set_symbol(symbols.horizontal)
152 .set_style(self.border_style);
152 .set_style(self.border_style);
153 }
153 }
@@ -155,7 +155,7 @@ impl<'a> Widget for Block<'a> {
155 if self.borders.intersects(Borders::RIGHT) {
155 if self.borders.intersects(Borders::RIGHT) {
156 let x = area.right() - 1;
156 let x = area.right() - 1;
157 for y in area.top()..area.bottom() {
157 for y in area.top()..area.bottom() {
158− buf.get_mut(x, y)
158+ buf[(x, y)]
159 .set_symbol(symbols.vertical)
159 .set_symbol(symbols.vertical)
160 .set_style(self.border_style);
160 .set_style(self.border_style);
161 }
161 }
@@ -163,7 +163,7 @@ impl<'a> Widget for Block<'a> {
163 if self.borders.intersects(Borders::BOTTOM) {
163 if self.borders.intersects(Borders::BOTTOM) {
164 let y = area.bottom() - 1;
164 let y = area.bottom() - 1;
165 for x in area.left()..area.right() {
165 for x in area.left()..area.right() {
166− buf.get_mut(x, y)
166+ buf[(x, y)]
167 .set_symbol(symbols.horizontal)
167 .set_symbol(symbols.horizontal)
168 .set_style(self.border_style);
168 .set_style(self.border_style);
169 }
169 }
@@ -171,22 +171,22 @@ impl<'a> Widget for Block<'a> {
171
171
172 // Corners
172 // Corners
173 if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
173 if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
174− buf.get_mut(area.right() - 1, area.bottom() - 1)
174+ buf[(area.right() - 1, area.bottom() - 1)]
175 .set_symbol(symbols.bottom_right)
175 .set_symbol(symbols.bottom_right)
176 .set_style(self.border_style);
176 .set_style(self.border_style);
177 }
177 }
178 if self.borders.contains(Borders::RIGHT | Borders::TOP) {
178 if self.borders.contains(Borders::RIGHT | Borders::TOP) {
179− buf.get_mut(area.right() - 1, area.top())
179+ buf[(area.right() - 1, area.top())]
180 .set_symbol(symbols.top_right)
180 .set_symbol(symbols.top_right)
181 .set_style(self.border_style);
181 .set_style(self.border_style);
182 }
182 }
183 if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
183 if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
184− buf.get_mut(area.left(), area.bottom() - 1)
184+ buf[(area.left(), area.bottom() - 1)]
185 .set_symbol(symbols.bottom_left)
185 .set_symbol(symbols.bottom_left)
186 .set_style(self.border_style);
186 .set_style(self.border_style);
187 }
187 }
188 if self.borders.contains(Borders::LEFT | Borders::TOP) {
188 if self.borders.contains(Borders::LEFT | Borders::TOP) {
189− buf.get_mut(area.left(), area.top())
189+ buf[(area.left(), area.top())]
190 .set_symbol(symbols.top_left)
190 .set_symbol(symbols.top_left)
191 .set_style(self.border_style);
191 .set_style(self.border_style);
192 }
192 }
helix-tui/src/widgets/paragraph.rs+1−1
@@ -176,7 +176,7 @@ impl<'a> Widget for Paragraph<'a> {
176 if y >= self.scroll.0 {
176 if y >= self.scroll.0 {
177 let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
177 let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
178 for StyledGrapheme { symbol, style } in current_line {
178 for StyledGrapheme { symbol, style } in current_line {
179− buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll.0)
179+ buf[(text_area.left() + x, text_area.top() + y - self.scroll.0)]
180 .set_symbol(if symbol.is_empty() {
180 .set_symbol(if symbol.is_empty() {
181 // If the symbol is empty, the last char which rendered last time will
181 // If the symbol is empty, the last char which rendered last time will
182 // leave on the line. It's a quick fix.
182 // leave on the line. It's a quick fix.
helix-view/src/graphics.rs+4−4
@@ -323,7 +323,7 @@ impl FromStr for Modifier {
323/// ];
323/// ];
324/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
324/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
325/// for style in &styles {
325/// for style in &styles {
326−/// buffer.get_mut(0, 0).set_style(*style);
326+/// buffer[(0, 0)].set_style(*style);
327/// }
327/// }
328/// assert_eq!(
328/// assert_eq!(
329/// Style {
329/// Style {
@@ -332,7 +332,7 @@ impl FromStr for Modifier {
332/// add_modifier: Modifier::BOLD,
332/// add_modifier: Modifier::BOLD,
333/// sub_modifier: Modifier::empty(),
333/// sub_modifier: Modifier::empty(),
334/// },
334/// },
335−/// buffer.get(0, 0).style(),
335+/// buffer[(0, 0)].style(),
336/// );
336/// );
337/// ```
337/// ```
338///
338///
@@ -348,7 +348,7 @@ impl FromStr for Modifier {
348/// ];
348/// ];
349/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
349/// let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
350/// for style in &styles {
350/// for style in &styles {
351−/// buffer.get_mut(0, 0).set_style(*style);
351+/// buffer[(0, 0)].set_style(*style);
352/// }
352/// }
353/// assert_eq!(
353/// assert_eq!(
354/// Style {
354/// Style {
@@ -357,7 +357,7 @@ impl FromStr for Modifier {
357/// add_modifier: Modifier::empty(),
357/// add_modifier: Modifier::empty(),
358/// sub_modifier: Modifier::empty(),
358/// sub_modifier: Modifier::empty(),
359/// },
359/// },
360−/// buffer.get(0, 0).style(),
360+/// buffer[(0, 0)].style(),
361/// );
361/// );
362/// ```
362/// ```
363#[derive(Debug, Clone, Copy, PartialEq)]
363#[derive(Debug, Clone, Copy, PartialEq)]