1188 lines · 43.1 KB · Rust
RawDownload
use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine};use gpui::{    div, fill, point, px, relative, size, AnyElement, AvailableSpace, Bounds, ContentMask,    DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle, FontWeight, GlobalElementId,    HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement, Interactivity, IntoElement,    LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels,    Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle,    UTF16Selection, UnderlineStyle, View, WeakView, WhiteSpace, WindowContext, WindowTextSystem,};use itertools::Itertools;use language::CursorShape;use settings::Settings;use terminal::{    alacritty_terminal::{        grid::Dimensions,        index::Point as AlacPoint,        term::{cell::Flags, TermMode},        vte::ansi::{            Color::{self as AnsiColor, Named},            CursorShape as AlacCursorShape, NamedColor,        },    },    terminal_settings::TerminalSettings,    HoveredWord, IndexedCell, Terminal, TerminalContent, TerminalSize,};use theme::{ActiveTheme, Theme, ThemeSettings};use ui::{ParentElement, Tooltip};use workspace::Workspace; use std::mem;use std::{fmt::Debug, ops::RangeInclusive, rc::Rc}; use crate::{BlockContext, BlockProperties, TerminalView}; /// The information generated during layout that is necessary for painting.pub struct LayoutState {    hitbox: Hitbox,    cells: Vec<LayoutCell>,    rects: Vec<LayoutRect>,    relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,    cursor: Option<CursorLayout>,    background_color: Hsla,    dimensions: TerminalSize,    mode: TermMode,    display_offset: usize,    hyperlink_tooltip: Option<AnyElement>,    gutter: Pixels,    last_hovered_word: Option<HoveredWord>,    block_below_cursor_element: Option<AnyElement>,} /// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.struct DisplayCursor {    line: i32,    col: usize,} impl DisplayCursor {    fn from(cursor_point: AlacPoint, display_offset: usize) -> Self {        Self {            line: cursor_point.line.0 + display_offset as i32,            col: cursor_point.column.0,        }    }     pub fn line(&self) -> i32 {        self.line    }     pub fn col(&self) -> usize {        self.col    }} #[derive(Debug, Default)]pub struct LayoutCell {    pub point: AlacPoint<i32, i32>,    text: gpui::ShapedLine,} impl LayoutCell {    fn new(point: AlacPoint<i32, i32>, text: gpui::ShapedLine) -> LayoutCell {        LayoutCell { point, text }    }     pub fn paint(        &self,        origin: Point<Pixels>,        dimensions: &TerminalSize,        _visible_bounds: Bounds<Pixels>,        cx: &mut WindowContext,    ) {        let pos = {            let point = self.point;             Point::new(                (origin.x + point.column as f32 * dimensions.cell_width).floor(),                origin.y + point.line as f32 * dimensions.line_height,            )        };         self.text.paint(pos, dimensions.line_height, cx).ok();    }} #[derive(Clone, Debug, Default)]pub struct LayoutRect {    point: AlacPoint<i32, i32>,    num_of_cells: usize,    color: Hsla,} impl LayoutRect {    fn new(point: AlacPoint<i32, i32>, num_of_cells: usize, color: Hsla) -> LayoutRect {        LayoutRect {            point,            num_of_cells,            color,        }    }     fn extend(&self) -> Self {        LayoutRect {            point: self.point,            num_of_cells: self.num_of_cells + 1,            color: self.color,        }    }     pub fn paint(&self, origin: Point<Pixels>, dimensions: &TerminalSize, cx: &mut WindowContext) {        let position = {            let alac_point = self.point;            point(                (origin.x + alac_point.column as f32 * dimensions.cell_width).floor(),                origin.y + alac_point.line as f32 * dimensions.line_height,            )        };        let size = point(            (dimensions.cell_width * self.num_of_cells as f32).ceil(),            dimensions.line_height,        )        .into();         cx.paint_quad(fill(Bounds::new(position, size), self.color));    }} /// The GPUI element that paints the terminal./// We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?pub struct TerminalElement {    terminal: Model<Terminal>,    terminal_view: View<TerminalView>,    workspace: WeakView<Workspace>,    focus: FocusHandle,    focused: bool,    cursor_visible: bool,    can_navigate_to_selected_word: bool,    interactivity: Interactivity,    block_below_cursor: Option<Rc<BlockProperties>>,} impl InteractiveElement for TerminalElement {    fn interactivity(&mut self) -> &mut Interactivity {        &mut self.interactivity    }} impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement {    #[allow(clippy::too_many_arguments)]    pub fn new(        terminal: Model<Terminal>,        terminal_view: View<TerminalView>,        workspace: WeakView<Workspace>,        focus: FocusHandle,        focused: bool,        cursor_visible: bool,        can_navigate_to_selected_word: bool,        block_below_cursor: Option<Rc<BlockProperties>>,    ) -> TerminalElement {        TerminalElement {            terminal,            terminal_view,            workspace,            focused,            focus: focus.clone(),            cursor_visible,            can_navigate_to_selected_word,            block_below_cursor,            interactivity: Default::default(),        }        .track_focus(&focus)        .element    }     //Vec<Range<AlacPoint>> -> Clip out the parts of the ranges     pub fn layout_grid(        grid: impl Iterator<Item = IndexedCell>,        text_style: &TextStyle,        // terminal_theme: &TerminalStyle,        text_system: &WindowTextSystem,        hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,        cx: &WindowContext,    ) -> (Vec<LayoutCell>, Vec<LayoutRect>) {        let theme = cx.theme();        let mut cells = vec![];        let mut rects = vec![];         let mut cur_rect: Option<LayoutRect> = None;        let mut cur_alac_color = None;         let linegroups = grid.into_iter().chunk_by(|i| i.point.line);        for (line_index, (_, line)) in linegroups.into_iter().enumerate() {            for cell in line {                let mut fg = cell.fg;                let mut bg = cell.bg;                if cell.flags.contains(Flags::INVERSE) {                    mem::swap(&mut fg, &mut bg);                }                 //Expand background rect range                {                    if matches!(bg, Named(NamedColor::Background)) {                        //Continue to next cell, resetting variables if necessary                        cur_alac_color = None;                        if let Some(rect) = cur_rect {                            rects.push(rect);                            cur_rect = None                        }                    } else {                        match cur_alac_color {                            Some(cur_color) => {                                if bg == cur_color {                                    // `cur_rect` can be None if it was moved to the `rects` vec after wrapping around                                    // from one line to the next. The variables are all set correctly but there is no current                                    // rect, so we create one if necessary.                                    cur_rect = cur_rect.map_or_else(                                        || {                                            Some(LayoutRect::new(                                                AlacPoint::new(                                                    line_index as i32,                                                    cell.point.column.0 as i32,                                                ),                                                1,                                                convert_color(&bg, theme),                                            ))                                        },                                        |rect| Some(rect.extend()),                                    );                                } else {                                    cur_alac_color = Some(bg);                                    if cur_rect.is_some() {                                        rects.push(cur_rect.take().unwrap());                                    }                                    cur_rect = Some(LayoutRect::new(                                        AlacPoint::new(                                            line_index as i32,                                            cell.point.column.0 as i32,                                        ),                                        1,                                        convert_color(&bg, theme),                                    ));                                }                            }                            None => {                                cur_alac_color = Some(bg);                                cur_rect = Some(LayoutRect::new(                                    AlacPoint::new(line_index as i32, cell.point.column.0 as i32),                                    1,                                    convert_color(&bg, theme),                                ));                            }                        }                    }                }                 //Layout current cell text                {                    if !is_blank(&cell) {                        let cell_text = cell.c.to_string();                        let cell_style =                            TerminalElement::cell_style(&cell, fg, theme, text_style, hyperlink);                         let layout_cell = text_system                            .shape_line(                                cell_text.into(),                                text_style.font_size.to_pixels(cx.rem_size()),                                &[cell_style],                            )                            .unwrap();                         cells.push(LayoutCell::new(                            AlacPoint::new(line_index as i32, cell.point.column.0 as i32),                            layout_cell,                        ))                    };                }            }             if cur_rect.is_some() {                rects.push(cur_rect.take().unwrap());            }        }        (cells, rects)    }     /// Computes the cursor position and expected block width, may return a zero width if x_for_index returns    /// the same position for sequential indexes. Use em_width instead    fn shape_cursor(        cursor_point: DisplayCursor,        size: TerminalSize,        text_fragment: &ShapedLine,    ) -> Option<(Point<Pixels>, Pixels)> {        if cursor_point.line() < size.total_lines() as i32 {            let cursor_width = if text_fragment.width == Pixels::ZERO {                size.cell_width()            } else {                text_fragment.width            };             // Cursor should always surround as much of the text as possible,            // hence when on pixel boundaries round the origin down and the width up            Some((                point(                    (cursor_point.col() as f32 * size.cell_width()).floor(),                    (cursor_point.line() as f32 * size.line_height()).floor(),                ),                cursor_width.ceil(),            ))        } else {            None        }    }     /// Converts the Alacritty cell styles to GPUI text styles and background color.    fn cell_style(        indexed: &IndexedCell,        fg: terminal::alacritty_terminal::vte::ansi::Color,        // bg: terminal::alacritty_terminal::ansi::Color,        colors: &Theme,        text_style: &TextStyle,        hyperlink: Option<(HighlightStyle, &RangeInclusive<AlacPoint>)>,    ) -> TextRun {        let flags = indexed.cell.flags;        let mut fg = convert_color(&fg, colors);         // Ghostty uses (175/255) as the multiplier (~0.69), Alacritty uses 0.66, Kitty        // uses 0.75. We're using 0.7 because it's pretty well in the middle of that.        if flags.intersects(Flags::DIM) {            fg.a *= 0.7;        }         let underline = (flags.intersects(Flags::ALL_UNDERLINES)            || indexed.cell.hyperlink().is_some())        .then(|| UnderlineStyle {            color: Some(fg),            thickness: Pixels::from(1.0),            wavy: flags.contains(Flags::UNDERCURL),        });         let strikethrough = flags            .intersects(Flags::STRIKEOUT)            .then(|| StrikethroughStyle {                color: Some(fg),                thickness: Pixels::from(1.0),            });         let weight = if flags.intersects(Flags::BOLD) {            FontWeight::BOLD        } else {            text_style.font_weight        };         let style = if flags.intersects(Flags::ITALIC) {            FontStyle::Italic        } else {            FontStyle::Normal        };         let mut result = TextRun {            len: indexed.c.len_utf8(),            color: fg,            background_color: None,            font: Font {                weight,                style,                ..text_style.font()            },            underline,            strikethrough,        };         if let Some((style, range)) = hyperlink {            if range.contains(&indexed.point) {                if let Some(underline) = style.underline {                    result.underline = Some(underline);                }                 if let Some(color) = style.color {                    result.color = color;                }            }        }         result    }     fn generic_button_handler<E>(        connection: Model<Terminal>,        origin: Point<Pixels>,        focus_handle: FocusHandle,        f: impl Fn(&mut Terminal, Point<Pixels>, &E, &mut ModelContext<Terminal>),    ) -> impl Fn(&E, &mut WindowContext) {        move |event, cx| {            cx.focus(&focus_handle);            connection.update(cx, |terminal, cx| {                f(terminal, origin, event, cx);                 cx.notify();            })        }    }     fn register_mouse_listeners(        &mut self,        origin: Point<Pixels>,        mode: TermMode,        hitbox: &Hitbox,        cx: &mut WindowContext,    ) {        let focus = self.focus.clone();        let terminal = self.terminal.clone();         self.interactivity.on_mouse_down(MouseButton::Left, {            let terminal = terminal.clone();            let focus = focus.clone();            move |e, cx| {                cx.focus(&focus);                terminal.update(cx, |terminal, cx| {                    terminal.mouse_down(e, origin, cx);                    cx.notify();                })            }        });         cx.on_mouse_event({            let focus = self.focus.clone();            let terminal = self.terminal.clone();            let hitbox = hitbox.clone();            move |e: &MouseMoveEvent, phase, cx| {                if phase != DispatchPhase::Bubble || !focus.is_focused(cx) {                    return;                }                 if e.pressed_button.is_some() && !cx.has_active_drag() {                    let hovered = hitbox.is_hovered(cx);                    terminal.update(cx, |terminal, cx| {                        if terminal.selection_started() {                            terminal.mouse_drag(e, origin, hitbox.bounds);                            cx.notify();                        } else if hovered {                            terminal.mouse_drag(e, origin, hitbox.bounds);                            cx.notify();                        }                    })                }                 if hitbox.is_hovered(cx) {                    terminal.update(cx, |terminal, cx| {                        terminal.mouse_move(e, origin);                        cx.notify();                    })                }            }        });         self.interactivity.on_mouse_up(            MouseButton::Left,            TerminalElement::generic_button_handler(                terminal.clone(),                origin,                focus.clone(),                move |terminal, origin, e, cx| {                    terminal.mouse_up(e, origin, cx);                },            ),        );        self.interactivity.on_mouse_down(            MouseButton::Middle,            TerminalElement::generic_button_handler(                terminal.clone(),                origin,                focus.clone(),                move |terminal, origin, e, cx| {                    terminal.mouse_down(e, origin, cx);                },            ),        );

Lines 1–500 of 1188.