4 text::{StyledGrapheme, Text},
6 reflow::{LineComposer, LineTruncator, WordWrapper},
10use helix_core::unicode::width::UnicodeWidthStr;
11use helix_view::graphics::{Rect, Style};
14fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
16 Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
17 Alignment::Right => text_area_width.saturating_sub(line_width),
22/// A widget to display some text.
27/// # use helix_tui::text::{Text, Spans, Span};
28/// # use helix_tui::widgets::{Block, Borders, Paragraph, Wrap};
29/// # use helix_tui::layout::{Alignment};
30/// # use helix_view::graphics::{Style, Color, Modifier};
33/// Span::raw("First"),
34/// Span::styled("line",Style::default().add_modifier(Modifier::ITALIC)),
37/// Spans::from(Span::styled("Second line", Style::default().fg(Color::Red))),
39/// Paragraph::new(text)
40/// .block(Block::default().title("Paragraph").borders(Borders::ALL))
41/// .style(Style::default().fg(Color::White).bg(Color::Black))
42/// .alignment(Alignment::Center)
43/// .wrap(Wrap { trim: true });
45#[derive(Debug, Clone)]
46pub struct Paragraph<'a> {
47 /// A block to wrap the widget in
48 block: Option<Block<'a>>,
51 /// How to wrap the text
53 /// The text to display
57 /// Alignment of the text
61/// Describes how to wrap text across lines.
66/// # use helix_tui::widgets::{Paragraph, Wrap};
67/// # use helix_tui::text::Text;
68/// let bullet_points = Text::from(r#"Some indented points:
69/// - First thing goes here and is long so that it wraps
70/// - Here is another point that is long enough to wrap"#);
72/// // With leading spaces trimmed (window width of 30 chars):
73/// Paragraph::new(bullet_points.clone()).wrap(Wrap { trim: true });
74/// // Some indented points:
75/// // - First thing goes here and is
76/// // long so that it wraps
77/// // - Here is another point that
78/// // is long enough to wrap
80/// // But without trimming, indentation is preserved:
81/// Paragraph::new(bullet_points).wrap(Wrap { trim: false });
82/// // Some indented points:
83/// // - First thing goes here
84/// // and is long so that it wraps
85/// // - Here is another point
86/// // that is long enough to wrap
88#[derive(Debug, Clone, Copy)]
90 /// Should leading whitespace be trimmed
94impl<'a> Paragraph<'a> {
95 pub fn new<T>(text: T) -> Paragraph<'a>
101 style: Default::default(),
105 alignment: Alignment::Left,
109 pub fn block(mut self, block: Block<'a>) -> Paragraph<'a> {
110 self.block = Some(block);
114 pub fn style(mut self, style: Style) -> Paragraph<'a> {
119 pub fn wrap(mut self, wrap: Wrap) -> Paragraph<'a> {
120 self.wrap = Some(wrap);
124 pub fn scroll(mut self, offset: (u16, u16)) -> Paragraph<'a> {
125 self.scroll = offset;
129 pub fn alignment(mut self, alignment: Alignment) -> Paragraph<'a> {
130 self.alignment = alignment;
135impl<'a> Widget for Paragraph<'a> {
136 fn render(mut self, area: Rect, buf: &mut Buffer) {
137 buf.set_style(area, self.style);
138 let text_area = match self.block.take() {
140 let inner_area = b.inner(area);
147 if text_area.height < 1 {
151 let style = self.style;
152 let mut styled = self.text.lines.iter().flat_map(|spans| {
156 .flat_map(|span| span.styled_graphemes(style))
157 // Required given the way composers work but might be refactored out if we change
158 // composers to operate on lines instead of a stream of graphemes.
159 .chain(iter::once(StyledGrapheme {
165 let mut line_composer: Box<dyn LineComposer> = if let Some(Wrap { trim }) = self.wrap {
166 Box::new(WordWrapper::new(&mut styled, text_area.width, trim))
168 let mut line_composer = Box::new(LineTruncator::new(&mut styled, text_area.width));
169 if let Alignment::Left = self.alignment {
170 line_composer.set_horizontal_offset(self.scroll.1);
175 while let Some((current_line, current_line_width)) = line_composer.next_line() {
176 if y >= self.scroll.0 {
177 let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
178 for StyledGrapheme { symbol, style } in current_line {
179 buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll.0)
180 .set_symbol(if symbol.is_empty() {
181 // If the symbol is empty, the last char which rendered last time will
182 // leave on the line. It's a quick fix.
188 x += symbol.width() as u16;
192 if y >= text_area.height + self.scroll.0 {
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list