diff --git a/crates/story/examples/code-editor.rs b/crates/story/examples/code-editor.rs index ffa7793d..d142cf16 100644 --- a/crates/story/examples/code-editor.rs +++ b/crates/story/examples/code-editor.rs @@ -5,7 +5,7 @@ use gpui_component::{ h_flex, highlighter::{HighlightTheme, Highlighter}, input::{InputEvent, InputState, TabSize, TextInput}, - v_flex, ActiveTheme as _, + v_flex, }; use story::Assets; @@ -13,7 +13,6 @@ pub struct Example { input_state: Entity, language_state: Entity>>, language: SharedString, - is_dark: bool, line_number: bool, _subscribes: Vec, } @@ -26,7 +25,7 @@ impl Example { let default_language: SharedString = LANGUAGES[0].into(); let input_state = cx.new(|cx| { InputState::new(window, cx) - .code_editor(Some(&default_language), &HighlightTheme::default_light()) + .code_editor(Some(&default_language)) .line_number(true) .tab_size(TabSize { tab_size: 4, @@ -63,7 +62,6 @@ impl Example { input_state, language_state, language: default_language, - is_dark: false, line_number: true, _subscribes, } @@ -74,26 +72,21 @@ impl Example { } fn update_highlighter(&mut self, new_language: Option, cx: &mut Context) { - let is_dark = cx.theme().mode.is_dark(); let is_language_changed = new_language.is_some(); if new_language.is_some() { self.language = new_language.unwrap(); } let language = self.language.as_ref(); - if self.is_dark != is_dark || is_language_changed { - self.is_dark = is_dark; + if is_language_changed { self.input_state.update(cx, |state, cx| { - if is_dark { - state.set_highlighter( - Highlighter::new(Some(language), &HighlightTheme::default_dark()), - cx, - ); - } else { - state.set_highlighter( - Highlighter::new(Some(language), &HighlightTheme::default_dark()), - cx, - ); - } + state.set_highlighter( + Highlighter::new( + Some(language), + &HighlightTheme::default_light(), + &HighlightTheme::default_dark(), + ), + cx, + ); }); } } diff --git a/crates/story/examples/html.rs b/crates/story/examples/html.rs index b87d7cd5..9bc780cd 100644 --- a/crates/story/examples/html.rs +++ b/crates/story/examples/html.rs @@ -1,17 +1,14 @@ use gpui::*; use gpui_component::{ - highlighter::{HighlightTheme, Highlighter}, input::{InputState, TabSize, TextInput}, resizable::{h_resizable, resizable_panel, ResizableState}, text::TextView, - ActiveTheme as _, }; use story::Assets; pub struct Example { input_state: Entity, resizable_state: Entity, - is_dark: bool, _subscribe: Subscription, } @@ -22,7 +19,7 @@ impl Example { pub fn new(window: &mut Window, cx: &mut Context) -> Self { let input_state = cx.new(|cx| { InputState::new(window, cx) - .code_editor(Some(LANG), &HighlightTheme::default_light()) + .code_editor(Some(LANG)) .tab_size(TabSize { tab_size: 4, hard_tabs: false, @@ -43,7 +40,6 @@ impl Example { Self { input_state, resizable_state, - is_dark: false, _subscribe, } } @@ -55,24 +51,6 @@ impl Example { impl Render for Example { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let is_dark = cx.theme().mode.is_dark(); - if self.is_dark != is_dark { - self.is_dark = is_dark; - self.input_state.update(cx, |state, cx| { - if is_dark { - state.set_highlighter( - Highlighter::new(Some(LANG), &HighlightTheme::default_dark()), - cx, - ); - } else { - state.set_highlighter( - Highlighter::new(Some(LANG), &HighlightTheme::default_light()), - cx, - ); - } - }); - } - h_resizable("container", self.resizable_state.clone()) .child( resizable_panel().child( diff --git a/crates/story/examples/markdown.rs b/crates/story/examples/markdown.rs index 44652106..303f727d 100644 --- a/crates/story/examples/markdown.rs +++ b/crates/story/examples/markdown.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use gpui::*; use gpui_component::{ - highlighter::{HighlightTheme, Highlighter}, + highlighter::HighlightTheme, input::{InputEvent, InputState, TabSize, TextInput}, resizable::{h_resizable, resizable_panel, ResizableState}, text::{TextView, TextViewStyle}, @@ -15,7 +15,6 @@ const LANG: &str = "markdown"; pub struct Example { input_state: Entity, resizable_state: Entity, - is_dark: bool, } const EXAMPLE: &str = include_str!("./markdown.md"); @@ -24,7 +23,7 @@ impl Example { pub fn new(window: &mut Window, cx: &mut Context) -> Self { let input_state = cx.new(|cx| { InputState::new(window, cx) - .code_editor(Some(LANG), &HighlightTheme::default_light()) + .code_editor(Some(LANG)) .line_number(false) .tab_size(TabSize { tab_size: 2, @@ -42,7 +41,6 @@ impl Example { Self { resizable_state, input_state, - is_dark: false, } } @@ -60,22 +58,6 @@ impl Render for Example { }; let is_dark = cx.theme().mode.is_dark(); - if self.is_dark != is_dark { - self.is_dark = is_dark; - self.input_state.update(cx, |state, cx| { - if is_dark { - state.set_highlighter( - Highlighter::new(Some(LANG), &HighlightTheme::default_dark()), - cx, - ); - } else { - state.set_highlighter( - Highlighter::new(Some(LANG), &HighlightTheme::default_light()), - cx, - ); - } - }); - } h_resizable("container", self.resizable_state.clone()) .child( @@ -97,6 +79,7 @@ impl Render for Example { TextView::markdown("preview", self.input_state.read(cx).value()).style( TextViewStyle { highlight_theme: Rc::new(theme.clone()), + is_dark, ..Default::default() }, ), diff --git a/crates/ui/src/highlighter/highlight.rs b/crates/ui/src/highlighter/highlight.rs index 566a2549..6c78abe8 100644 --- a/crates/ui/src/highlighter/highlight.rs +++ b/crates/ui/src/highlighter/highlight.rs @@ -66,26 +66,51 @@ impl HighlightTheme { /// https://github.com/iced-rs/iced/blob/master/highlighter/src/lib.rs#L24 pub struct Highlighter<'a> { syntax: &'static parsing::SyntaxReference, - pub(crate) theme: &'a HighlightTheme, - highlighter: highlighting::Highlighter<'a>, + pub(crate) light_theme: &'a HighlightTheme, + pub(crate) dark_theme: &'a HighlightTheme, + light_highlighter: highlighting::Highlighter<'a>, + dark_highlighter: highlighting::Highlighter<'a>, } impl<'a> Highlighter<'a> { - pub fn new(lang: Option<&str>, theme: &'a HighlightTheme) -> Self { + pub fn new( + lang: Option<&str>, + light_theme: &'a HighlightTheme, + dark_theme: &'a HighlightTheme, + ) -> Self { let syntax = lang .and_then(|lang| SYNTAXES.find_syntax_by_token(&lang)) .unwrap_or_else(|| SYNTAXES.find_syntax_plain_text()); - let highlighter = highlighting::Highlighter::new(&theme.inner); + let light_highlighter = highlighting::Highlighter::new(&light_theme.inner); + let dark_highlighter = highlighting::Highlighter::new(&dark_theme.inner); Self { syntax, - theme, - highlighter, + light_theme, + dark_theme, + light_highlighter, + dark_highlighter, + } + } + + pub(crate) fn theme(&self, is_dark: bool) -> &HighlightTheme { + if is_dark { + self.dark_theme + } else { + self.light_theme + } + } + + fn highlighter(&self, is_dark: bool) -> &highlighting::Highlighter<'a> { + if is_dark { + &self.dark_highlighter + } else { + &self.light_highlighter } } /// Highlight a line and returns a vector of ranges and highlight styles - pub fn highlight(&self, line: &str) -> Vec<(Range, HighlightStyle)> { + pub fn highlight(&self, line: &str, is_dark: bool) -> Vec<(Range, HighlightStyle)> { let mut parser = parsing::ParseState::new(self.syntax); let mut stack = parsing::ScopeStack::new(); @@ -102,7 +127,7 @@ impl<'a> Highlighter<'a> { if range.is_empty() { return None; } else { - let style_mod = self.highlighter.style_mod_for_stack(&stack.scopes); + let style_mod = self.highlighter(is_dark).style_mod_for_stack(&stack.scopes); let mut style = HighlightStyle::default(); style.color = style_mod.foreground.map(color_to_hsla); style.background_color = style_mod.background.map(color_to_hsla); diff --git a/crates/ui/src/input/code_highlighter.rs b/crates/ui/src/input/code_highlighter.rs index 772bf4f2..e82ff599 100644 --- a/crates/ui/src/input/code_highlighter.rs +++ b/crates/ui/src/input/code_highlighter.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, ops::Range, rc::Rc}; use gpui::{App, HighlightStyle, SharedString, TextRun, TextStyle}; -use crate::highlighter::Highlighter; +use crate::{highlighter::Highlighter, ActiveTheme, ThemeMode}; #[derive(Debug, Clone)] pub(crate) struct LineHighlightStyle { @@ -44,6 +44,7 @@ pub(super) struct CodeHighlighter { pub(super) text: SharedString, /// The lines by split \n pub(super) lines: Vec, + pub(super) cache_theme_mode: ThemeMode, pub(super) cache: HashMap, } @@ -53,6 +54,7 @@ impl CodeHighlighter { highlighter, text: SharedString::default(), lines: vec![], + cache_theme_mode: ThemeMode::default(), cache: HashMap::new(), } } @@ -64,11 +66,16 @@ impl CodeHighlighter { self.update(self.text.clone(), true, cx); } - pub fn update(&mut self, text: SharedString, force: bool, _: &mut App) { - if self.text == text && !force { + pub fn update(&mut self, text: SharedString, force: bool, cx: &mut App) { + if self.text == text && self.cache_theme_mode == cx.theme().mode && !force { return; } + // Clear if mode is changed + if self.cache_theme_mode != cx.theme().mode { + self.cache.clear(); + } + let mut lines = vec![]; let mut offset = 0; let mut new_cache = HashMap::new(); @@ -85,7 +92,7 @@ impl CodeHighlighter { lines.push(new_style); } else { // cache miss - let styles = Rc::new(self.highlighter.highlight(line)); + let styles = Rc::new(self.highlighter.highlight(line, cx.theme().is_dark())); let line_style = LineHighlightStyle { offset, styles }; new_cache.insert(cache_key, line_style.clone()); lines.push(line_style); @@ -96,6 +103,7 @@ impl CodeHighlighter { } // Ensure to recreate cache to remove unused caches. + self.cache_theme_mode = cx.theme().mode; self.cache = new_cache; self.lines = lines; self.text = text; diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 854516c8..1dfaabff 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -709,7 +709,7 @@ impl Element for TextElement { .read(cx) .mode .highlighter() - .and_then(|h| h.theme.settings().line_highlight) + .and_then(|h| h.theme(cx.theme().is_dark()).settings().line_highlight) .map(crate::highlighter::color_to_hsla) { window.paint_quad(fill( diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index 728ae15a..c582659a 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -359,6 +359,8 @@ impl InputState { /// - hard_tabs: false /// - height: full /// + /// If `highlighter` is None, will use the default highlighter. + /// /// Code Editor aim for help used to simple code editing or display, not a full-featured code editor. /// /// ## Features @@ -366,12 +368,15 @@ impl InputState { /// - Syntax Highlighting /// - Auto Indent /// - Line Number - pub fn code_editor(mut self, language: Option<&str>, theme: &'static HighlightTheme) -> Self { - let highlighter = Rc::new(Highlighter::new(language, theme)); + pub fn code_editor(mut self, language: Option<&str>) -> Self { self.mode = InputMode::CodeEditor { rows: 2, tab: TabSize::default(), - highlighter: CodeHighlighter::new(highlighter), + highlighter: CodeHighlighter::new(Rc::new(Highlighter::new( + language, + &HighlightTheme::default_light(), + &HighlightTheme::default_dark(), + ))), line_number: true, height: Some(relative(1.)), }; diff --git a/crates/ui/src/input/text_input.rs b/crates/ui/src/input/text_input.rs index 91dd9ab6..355a0ed2 100644 --- a/crates/ui/src/input/text_input.rs +++ b/crates/ui/src/input/text_input.rs @@ -27,6 +27,7 @@ pub struct TextInput { cleanable: bool, mask_toggle: bool, disabled: bool, + bordered: bool, } impl Sizable for TextInput { @@ -47,6 +48,7 @@ impl TextInput { suffix: None, height: None, appearance: true, + bordered: true, cleanable: false, mask_toggle: false, disabled: false, @@ -81,6 +83,12 @@ impl TextInput { self } + /// Set the bordered for the input field, default: true + pub fn bordered(mut self, bordered: bool) -> Self { + self.bordered = bordered; + self + } + /// Set true to show the clear button when the input field is not empty. pub fn cleanable(mut self) -> Self { self.cleanable = true; @@ -234,11 +242,13 @@ impl RenderOnce for TextInput { }) .when(self.appearance, |this| { this.bg(bg) - .border_color(cx.theme().input) - .border_1() .rounded(cx.theme().radius) - .when(cx.theme().shadow, |this| this.shadow_sm()) - .when(focused, |this| this.focused_border(cx)) + .when(self.bordered, |this| { + this.border_color(cx.theme().input) + .border_1() + .when(cx.theme().shadow, |this| this.shadow_sm()) + .when(focused, |this| this.focused_border(cx)) + }) }) .when(prefix.is_none(), |this| this.input_pl(self.size)) .input_pr(self.size) diff --git a/crates/ui/src/inspector.rs b/crates/ui/src/inspector.rs index bec90a0e..21f64029 100644 --- a/crates/ui/src/inspector.rs +++ b/crates/ui/src/inspector.rs @@ -11,7 +11,6 @@ use crate::{ clipboard::Clipboard, description_list::DescriptionList, h_flex, - highlighter::HighlightTheme, input::{InputState, TextInput}, link::Link, v_flex, ActiveTheme, IconName, Selectable, Sizable, TITLE_BAR_HEIGHT, @@ -60,15 +59,9 @@ pub struct DivInspector { impl DivInspector { pub fn new(window: &mut Window, cx: &mut App) -> Self { - let theme = if cx.theme().is_dark() { - HighlightTheme::default_dark() - } else { - HighlightTheme::default_light() - }; - let input_state = cx.new(|cx| { InputState::new(window, cx) - .code_editor(Some("json"), theme) + .code_editor(Some("json")) .line_number(false) .disabled(true) }); @@ -134,7 +127,9 @@ impl Render for DivInspector { .w_full() .font_family("Monaco") .text_size(px(12.)) - .child(TextInput::new(&input_state).h_full()), + .border_1() + .border_color(cx.theme().border) + .child(TextInput::new(&input_state).h_full().appearance(false)), ), ) }, @@ -150,6 +145,7 @@ fn render_inspector( let inspector_element_id = inspector.active_element_id(); let source_location = inspector_element_id.map(|id| SharedString::new(format!("{}", id.path.source_location))); + let element_global_id = inspector_element_id.map(|id| format!("{}", id.path.global_id)); v_flex() .id("inspector") @@ -202,6 +198,7 @@ fn render_inspector( .flex_1() .p_3() .gap_3() + .text_sm() .when_some(source_location, |this, source_location| { this.child( h_flex() @@ -215,6 +212,7 @@ fn render_inspector( .child(Clipboard::new("copy-source-location").value(source_location)), ) }) + .children(element_global_id) .children(inspector.render_inspector_states(window, cx)), ) .into_any_element() diff --git a/crates/ui/src/text/element.rs b/crates/ui/src/text/element.rs index e5d0f9e5..00b533ab 100644 --- a/crates/ui/src/text/element.rs +++ b/crates/ui/src/text/element.rs @@ -215,8 +215,9 @@ impl CodeBlock { let highlight = Highlighter::new( lang.as_ref().map(|v| v.as_ref()), text_view_style.highlight_theme.as_ref(), + text_view_style.highlight_theme.as_ref(), ); - let styles = highlight.highlight(code.as_ref()); + let styles = highlight.highlight(code.as_ref(), text_view_style.is_dark); Self { code, lang, styles } } } diff --git a/crates/ui/src/text/text_view.rs b/crates/ui/src/text/text_view.rs index e7867d97..1726909f 100644 --- a/crates/ui/src/text/text_view.rs +++ b/crates/ui/src/text/text_view.rs @@ -77,6 +77,7 @@ pub struct TextViewStyle { pub heading_base_font_size: Pixels, /// Highlight theme for code blocks. Default: [`HighlightTheme::default_light()`] pub highlight_theme: Rc, + pub is_dark: bool, } impl Default for TextViewStyle { @@ -85,6 +86,7 @@ impl Default for TextViewStyle { paragraph_gap: rems(1.), heading_base_font_size: px(14.), highlight_theme: Rc::new(HighlightTheme::default_light().clone()), + is_dark: false, } } }