code-editor: Improve CodeEditor to has default themes. (#900)
This commit is contained in:
parent
ebe10e20c7
commit
1cbbf6ef18
11 changed files with 94 additions and 91 deletions
|
|
@ -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<InputState>,
|
||||
language_state: Entity<DropdownState<Vec<SharedString>>>,
|
||||
language: SharedString,
|
||||
is_dark: bool,
|
||||
line_number: bool,
|
||||
_subscribes: Vec<Subscription>,
|
||||
}
|
||||
|
|
@ -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<SharedString>, cx: &mut Context<Self>) {
|
||||
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<InputState>,
|
||||
resizable_state: Entity<ResizableState>,
|
||||
is_dark: bool,
|
||||
_subscribe: Subscription,
|
||||
}
|
||||
|
||||
|
|
@ -22,7 +19,7 @@ impl Example {
|
|||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> 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<Self>) -> 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(
|
||||
|
|
|
|||
|
|
@ -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<InputState>,
|
||||
resizable_state: Entity<ResizableState>,
|
||||
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>) -> 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()
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<usize>, HighlightStyle)> {
|
||||
pub fn highlight(&self, line: &str, is_dark: bool) -> Vec<(Range<usize>, 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);
|
||||
|
|
|
|||
|
|
@ -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<LineHighlightStyle>,
|
||||
pub(super) cache_theme_mode: ThemeMode,
|
||||
pub(super) cache: HashMap<u64, LineHighlightStyle>,
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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.)),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HighlightTheme>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue