//! Shared helpers for the editor's views: gutter cells, role colors, code //! line text, language mapping, change washes and diff row building. #[cfg(any(feature = "diff_view", feature = "markdown_view"))] use std::ops::Range; #[cfg(feature = "markdown_view")] use gpui::StyledText; use gpui::{App, HighlightStyle, Hsla, IntoElement, ParentElement as _, Styled as _, div}; use gpui_native_theme::{ActiveTheme as _, ColorTokens, Ramps}; #[cfg(feature = "diff_view")] use similar::{Change, ChangeTag}; #[cfg(feature = "diff_view")] use crate::diff_view::{DiffRow, DiffRowKind}; #[cfg(feature = "diff_view")] use crate::highlight::HighlightedCode; use crate::highlight::Role; #[cfg(feature = "markdown_view")] use crate::highlight::{Language, Line}; /// Maps a highlight role to the theme's semantic palette. pub(crate) fn role_color(role: Role, colors: ColorTokens, ramps: Ramps) -> Hsla { match role { Role::Text => colors.foreground, Role::Keyword => ramps.primary.base, Role::String | Role::Escape => ramps.success.base, Role::Comment | Role::Punctuation => colors.muted_foreground, Role::Function | Role::Property => ramps.info.base, Role::Type => ramps.warning.base, Role::Number | Role::Constant => ramps.secondary.base, Role::Operator => colors.foreground, Role::Tag => ramps.destructive.base, } } /// Builds the highlight style for one run of a code view row. pub(crate) fn run_style( role: Role, selected: bool, colors: ColorTokens, ramps: Ramps, ) -> HighlightStyle { HighlightStyle { color: Some(role_color(role, colors, ramps)), background_color: selected.then_some(colors.selection), ..Default::default() } } /// A gutter cell sized to the whole row. pub(crate) fn gutter_cell(cx: &App, color: Hsla, content: E) -> gpui::Div where E: IntoElement, { let theme = cx.theme(); div() .size_10() .flex() .items_center() .justify_center() .flex_none() .font_family(theme.mono_font) .text_size(theme.sizes.small.text_size) .line_height(gpui::relative(1.0)) .text_color(color) .child(content) } /// Builds a diff line row from a `similar` change. #[cfg(feature = "diff_view")] pub(crate) fn line_row( change: &Change<&str>, code: &HighlightedCode, changed_ranges: Vec>, ) -> DiffRow { let content = match change.tag() { ChangeTag::Equal => change .new_index() .and_then(|ix| code.lines.get(ix)) .cloned() .unwrap_or_default(), ChangeTag::Delete => change .old_index() .and_then(|ix| code.lines.get(ix)) .cloned() .unwrap_or_default(), ChangeTag::Insert => change .new_index() .and_then(|ix| code.lines.get(ix)) .cloned() .unwrap_or_default(), }; let (old_number, new_number) = match change.tag() { ChangeTag::Equal => ( change.old_index().map(|ix| ix + 1), change.new_index().map(|ix| ix + 1), ), ChangeTag::Delete => (change.old_index().map(|ix| ix + 1), None), ChangeTag::Insert => (None, change.new_index().map(|ix| ix + 1)), }; DiffRow { kind: match change.tag() { ChangeTag::Equal => DiffRowKind::Context, ChangeTag::Delete => DiffRowKind::Removed, ChangeTag::Insert => DiffRowKind::Added, }, content, old_number, new_number, changed_ranges, header: Default::default(), hidden_lines: 0, } } /// A change wash: the ramp color at the alpha rows tint with. pub(crate) fn wash(color: Hsla) -> Hsla { Hsla { a: color.a * 0.14, ..color } } /// The stronger wash under number gutters and intra-line word changes. pub(crate) fn wash_strong(color: Hsla) -> Hsla { Hsla { a: color.a * 0.34, ..color } } /// The subtle wash that sets the number gutter off from the code. pub(crate) fn wash_gutter(colors: &ColorTokens) -> Hsla { Hsla { a: colors.muted.a * 0.18, ..colors.muted } } /// One highlighted code line as styled text, roles resolved from the theme. #[cfg(feature = "markdown_view")] pub(crate) fn code_line(line: &Line, colors: ColorTokens, ramps: Ramps) -> StyledText { let mut text = String::new(); let mut highlights: Vec<(Range, HighlightStyle)> = Vec::new(); for span in &line.spans { let start = text.len(); text.push_str(&span.text); if span.role != Role::Text { highlights.push(( start..text.len(), HighlightStyle { color: Some(role_color(span.role, colors, ramps)), ..Default::default() }, )); } } StyledText::new(text).with_highlights(highlights) } /// Maps a fence info string to a bundled grammar. #[cfg(feature = "markdown_view")] pub(crate) fn grammar_language(info: &str) -> Option { match info.to_ascii_lowercase().as_str() { "rust" | "rs" => Some(Language::Rust), "typescript" | "ts" => Some(Language::TypeScript), "javascript" | "js" => Some(Language::JavaScript), "python" | "py" => Some(Language::Python), _ => None, } }