code-editor: Add markers for display diagnostics to CodeEditor. (#920)
Ref: https://microsoft.github.io/monaco-editor/typedoc/functions/editor.setModelMarkers.html  
This commit is contained in:
parent
9afe0063a4
commit
a8f5ced4e6
12 changed files with 674 additions and 335 deletions
|
|
@ -4,7 +4,7 @@ use gpui_component::{
|
|||
dropdown::{Dropdown, DropdownEvent, DropdownState},
|
||||
h_flex,
|
||||
highlighter::Language,
|
||||
input::{InputEvent, InputState, TabSize, TextInput},
|
||||
input::{InputEvent, InputState, Marker, TabSize, TextInput},
|
||||
v_flex,
|
||||
};
|
||||
use story::Assets;
|
||||
|
|
@ -84,6 +84,25 @@ impl Example {
|
|||
cx.new(|cx| Self::new(window, cx))
|
||||
}
|
||||
|
||||
fn set_markers(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.language.name() != "rust" {
|
||||
return;
|
||||
}
|
||||
|
||||
self.input_state.update(cx, |state, cx| {
|
||||
state.set_markers(
|
||||
vec![
|
||||
Marker::new("warning", (2, 1), (2, 31), "Import but not used."),
|
||||
Marker::new("error", (16, 10), (16, 46), "Syntax error."),
|
||||
Marker::new("info", (25, 10), (25, 20), "This is a info message."),
|
||||
Marker::new("hint", (36, 9), (40, 10), "This is a hint message."),
|
||||
],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn update_highlighter(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.need_update {
|
||||
return;
|
||||
|
|
@ -103,6 +122,7 @@ impl Example {
|
|||
impl Render for Example {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
self.update_highlighter(window, cx);
|
||||
self.set_markers(window, cx);
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
|
|
|
|||
|
|
@ -38,9 +38,10 @@ pub struct SyntaxHighlighter {
|
|||
|
||||
/// Cache of highlight, the range is offset of the token in the tree.
|
||||
///
|
||||
/// The BTreeMap is ordered by the range from 0 to the end of the line.
|
||||
/// The BTreeMap is ordered by the range in the entire text.
|
||||
///
|
||||
/// The `key` is the `start` of the range.
|
||||
/// - The `key` is the `start` of the range.
|
||||
/// -The `value` is a tuple of the range (in the entire text) and the highlight name.
|
||||
cache: BTreeMap<usize, (Range<usize>, String)>,
|
||||
}
|
||||
|
||||
|
|
@ -539,42 +540,37 @@ impl SyntaxHighlighter {
|
|||
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||
let mut styles = vec![];
|
||||
let start_offset = range.start;
|
||||
let line_len = range.len();
|
||||
let mut last_range = 0..0;
|
||||
let mut last_range = start_offset..start_offset;
|
||||
|
||||
// NOTE: the ranges in the cache may have duplicates, so we need to merge them.
|
||||
for (_, (node_range, highlight_name)) in self.cache.range(range.start..) {
|
||||
for (_, (node_range, name)) in self.cache.range(range.start..) {
|
||||
// TODO: If break, the `comment.doc` will not work.
|
||||
// Ref: https://github.com/longbridge/gpui-component/pull/904/commits/d8f886939d3b472f228c1ce72154a951e98f32c5
|
||||
if node_range.end > range.end {
|
||||
break;
|
||||
}
|
||||
|
||||
let range_in_line = node_range.start.saturating_sub(start_offset)
|
||||
..node_range.end.saturating_sub(start_offset);
|
||||
// let range_in_line = node_range.start..node_range.end;
|
||||
|
||||
// Ensure every range is connected.
|
||||
if last_range.end < range_in_line.start {
|
||||
styles.push((
|
||||
last_range.end..range_in_line.start,
|
||||
HighlightStyle::default(),
|
||||
));
|
||||
if last_range.end < node_range.start {
|
||||
styles.push((last_range.end..node_range.start, HighlightStyle::default()));
|
||||
}
|
||||
|
||||
let style = theme.style(&highlight_name).unwrap_or_default();
|
||||
let style = theme.style(&name).unwrap_or_default();
|
||||
|
||||
styles.push((range_in_line.clone(), style));
|
||||
last_range = range_in_line;
|
||||
styles.push((node_range.clone(), style));
|
||||
last_range = node_range.clone();
|
||||
}
|
||||
|
||||
// If the matched styles is empty, return a default range.
|
||||
if styles.len() == 0 {
|
||||
return vec![(0..line_len, HighlightStyle::default())];
|
||||
return vec![(start_offset..range.end, HighlightStyle::default())];
|
||||
}
|
||||
|
||||
// Ensure the last range is connected to the end of the line.
|
||||
if last_range.end < line_len {
|
||||
styles.push((last_range.end..line_len, HighlightStyle::default()));
|
||||
if last_range.end < range.end {
|
||||
styles.push((last_range.end..range.end, HighlightStyle::default()));
|
||||
}
|
||||
|
||||
styles
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const DEFAULT_LIGHT: LazyLock<HighlightTheme> = LazyLock::new(|| {
|
|||
/// Theme for Tree-sitter Highlight
|
||||
///
|
||||
/// https://docs.rs/tree-sitter-highlight/0.25.4/tree_sitter_highlight/
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
|
||||
pub struct SyntaxColors {
|
||||
pub attribute: Option<ThemeStyle>,
|
||||
pub boolean: Option<ThemeStyle>,
|
||||
|
|
@ -232,7 +232,83 @@ impl SyntaxColors {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
|
||||
pub struct StatusColors {
|
||||
#[serde(rename = "error")]
|
||||
error: Option<Hsla>,
|
||||
#[serde(rename = "error.background")]
|
||||
error_background: Option<Hsla>,
|
||||
#[serde(rename = "warning")]
|
||||
warning: Option<Hsla>,
|
||||
#[serde(rename = "warning.background")]
|
||||
warning_background: Option<Hsla>,
|
||||
#[serde(rename = "info")]
|
||||
info: Option<Hsla>,
|
||||
#[serde(rename = "info.background")]
|
||||
info_background: Option<Hsla>,
|
||||
#[serde(rename = "success")]
|
||||
success: Option<Hsla>,
|
||||
#[serde(rename = "success.background")]
|
||||
success_background: Option<Hsla>,
|
||||
#[serde(rename = "hint")]
|
||||
hint: Option<Hsla>,
|
||||
#[serde(rename = "hint.background")]
|
||||
hint_background: Option<Hsla>,
|
||||
}
|
||||
|
||||
impl StatusColors {
|
||||
#[inline]
|
||||
pub fn error(&self) -> Hsla {
|
||||
self.error.unwrap_or(crate::red_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn error_background(&self) -> Hsla {
|
||||
self.error_background.unwrap_or(self.error())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn warning(&self) -> Hsla {
|
||||
self.warning.unwrap_or(crate::yellow_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn warning_background(&self) -> Hsla {
|
||||
self.warning_background.unwrap_or(self.warning())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn info(&self) -> Hsla {
|
||||
self.info.unwrap_or(crate::blue_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn info_background(&self) -> Hsla {
|
||||
self.info_background.unwrap_or(self.info())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn success(&self) -> Hsla {
|
||||
self.success.unwrap_or(crate::green_500())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn success_background(&self) -> Hsla {
|
||||
self.success_background.unwrap_or(self.success())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hint(&self) -> Hsla {
|
||||
self.hint.unwrap_or(crate::cyan_500().opacity(0.5))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hint_background(&self) -> Hsla {
|
||||
self.hint_background.unwrap_or(self.hint())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, JsonSchema, Serialize, Deserialize)]
|
||||
pub struct HighlightThemeStyle {
|
||||
#[serde(rename = "editor.background")]
|
||||
pub background: Option<Hsla>,
|
||||
|
|
@ -244,6 +320,9 @@ pub struct HighlightThemeStyle {
|
|||
pub line_number: Option<Hsla>,
|
||||
#[serde(rename = "editor.active_line_number")]
|
||||
pub active_line_number: Option<Hsla>,
|
||||
#[serde(flatten)]
|
||||
pub status: StatusColors,
|
||||
#[serde(rename = "syntax")]
|
||||
pub syntax: SyntaxColors,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,123 +1,165 @@
|
|||
{
|
||||
"name": "macOS Classic Dark",
|
||||
"appearance": "dark",
|
||||
"style": {
|
||||
"editor.foreground": "#DDDDDD",
|
||||
"editor.background": "#131313",
|
||||
"editor.active_line.background": "#272727",
|
||||
"editor.line_number": "#8F8F8F",
|
||||
"editor.active_line_number": "#DDDDDD",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#be9a52",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment": {
|
||||
"color": "#9E9E9E",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#9E9E9E",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#b5af9a",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#CACCCA",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#A86D3B",
|
||||
"font_style": "normal",
|
||||
"font_weight": null
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6F6D66",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"tag": {
|
||||
"color": "#b5af9a",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"title": {
|
||||
"color": "#A76D3B",
|
||||
"font_style": null,
|
||||
"font_weight": 600
|
||||
},
|
||||
"type": {
|
||||
"color": "#A86D3B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
}
|
||||
}
|
||||
"name": "macOS Classic Dark",
|
||||
"appearance": "dark",
|
||||
"style": {
|
||||
"editor.foreground": "#DDDDDD",
|
||||
"editor.background": "#131313",
|
||||
"editor.active_line.background": "#272727",
|
||||
"editor.line_number": "#8F8F8F",
|
||||
"editor.active_line_number": "#DDDDDD",
|
||||
"conflict": "#D2602D",
|
||||
"conflict.background": null,
|
||||
"conflict.border": null,
|
||||
"created": "#3f72e2",
|
||||
"created.background": "#0C4619",
|
||||
"created.border": null,
|
||||
"deleted": null,
|
||||
"deleted.background": "#46190C",
|
||||
"deleted.border": null,
|
||||
"error": null,
|
||||
"error.background": "#46190C",
|
||||
"error.border": "#802207",
|
||||
"hidden": "#9E9E9E",
|
||||
"hidden.background": null,
|
||||
"hidden.border": null,
|
||||
"hint": null,
|
||||
"hint.background": "#0C194D",
|
||||
"hint.border": "#082190",
|
||||
"ignored": null,
|
||||
"ignored.background": null,
|
||||
"ignored.border": null,
|
||||
"info": null,
|
||||
"info.background": "#0C194D",
|
||||
"info.border": "#082190",
|
||||
"modified": "#B0A878",
|
||||
"modified.background": "#3A310E",
|
||||
"modified.border": null,
|
||||
"predictive": "#5D5945",
|
||||
"predictive.background": null,
|
||||
"predictive.border": null,
|
||||
"renamed": null,
|
||||
"renamed.background": null,
|
||||
"renamed.border": null,
|
||||
"success": null,
|
||||
"success.background": "#0C4619",
|
||||
"success.border": null,
|
||||
"unreachable": null,
|
||||
"unreachable.background": null,
|
||||
"unreachable.border": null,
|
||||
"warning": null,
|
||||
"warning.background": "#3A310E",
|
||||
"warning.border": "#7B6508",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#be9a52",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment": {
|
||||
"color": "#9E9E9E",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#9E9E9E",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#b5af9a",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#CACCCA",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#A86D3B",
|
||||
"font_style": "normal",
|
||||
"font_weight": null
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6F6D66",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"tag": {
|
||||
"color": "#b5af9a",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"title": {
|
||||
"color": "#A76D3B",
|
||||
"font_style": null,
|
||||
"font_weight": 600
|
||||
},
|
||||
"type": {
|
||||
"color": "#A86D3B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,133 +1,175 @@
|
|||
{
|
||||
"name": "macOS Classic Light",
|
||||
"appearance": "light",
|
||||
"style": {
|
||||
"editor.foreground": "#000000",
|
||||
"editor.background": "#ffffff",
|
||||
"editor.active_line.background": "#F0F0F0",
|
||||
"editor.line_number": "#929292",
|
||||
"editor.active_line_number": "#000000",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#957931",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment": {
|
||||
"color": "#007fff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#007fff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function": {
|
||||
"color": "#0000A2",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#0000A2",
|
||||
"font_style": "normal",
|
||||
"font_weight": null
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6A7293",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#d21f07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#d21f07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"tag": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#6F42C1",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"title": {
|
||||
"color": "#0433FF",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"type": {
|
||||
"color": "#6f42c1",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"property": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
}
|
||||
}
|
||||
"name": "macOS Classic Light",
|
||||
"appearance": "light",
|
||||
"style": {
|
||||
"editor.foreground": "#000000",
|
||||
"editor.background": "#ffffff",
|
||||
"editor.active_line.background": "#F0F0F0",
|
||||
"editor.line_number": "#929292",
|
||||
"editor.active_line_number": "#000000",
|
||||
"conflict": "#C5060B",
|
||||
"conflict.background": null,
|
||||
"conflict.border": null,
|
||||
"created": "#1642FF",
|
||||
"created.background": "#e5ffe9",
|
||||
"created.border": null,
|
||||
"deleted": null,
|
||||
"deleted.background": "#FBEAE5",
|
||||
"deleted.border": null,
|
||||
"error": null,
|
||||
"error.background": "#FBEAE5",
|
||||
"error.border": "#EC9F89",
|
||||
"hidden": "#6D6D6D",
|
||||
"hidden.background": null,
|
||||
"hidden.border": null,
|
||||
"hint": null,
|
||||
"hint.background": "#E5F2FF",
|
||||
"hint.border": "#99CCFF",
|
||||
"ignored": null,
|
||||
"ignored.background": null,
|
||||
"ignored.border": null,
|
||||
"info": null,
|
||||
"info.background": "#E5EAFF",
|
||||
"info.border": "#8DA1FF",
|
||||
"modified": "#9e7008",
|
||||
"modified.background": "#fff2e5",
|
||||
"modified.border": null,
|
||||
"predictive": "#A4ABB6",
|
||||
"predictive.background": null,
|
||||
"predictive.border": null,
|
||||
"renamed": null,
|
||||
"renamed.background": null,
|
||||
"renamed.border": null,
|
||||
"success": null,
|
||||
"success.background": "#E5FFE5",
|
||||
"success.border": null,
|
||||
"unreachable": null,
|
||||
"unreachable.background": null,
|
||||
"unreachable.border": null,
|
||||
"warning": "#C99401",
|
||||
"warning.background": "#FFFBE5",
|
||||
"warning.border": "#D9CC89",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#957931",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment": {
|
||||
"color": "#007fff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#007fff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function": {
|
||||
"color": "#0000A2",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#0000A2",
|
||||
"font_style": "normal",
|
||||
"font_weight": null
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6A7293",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#d21f07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#d21f07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"tag": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#6F42C1",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"title": {
|
||||
"color": "#0433FF",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"type": {
|
||||
"color": "#6f42c1",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"property": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
use gpui::{HighlightStyle, TextRun, TextStyle};
|
||||
use std::{ops::Range, rc::Rc};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LineHighlightStyle {
|
||||
pub(crate) offset: usize,
|
||||
pub(crate) styles: Rc<Vec<(Range<usize>, HighlightStyle)>>,
|
||||
}
|
||||
|
||||
impl LineHighlightStyle {
|
||||
pub(super) fn to_run(
|
||||
&self,
|
||||
text_style: &TextStyle,
|
||||
marked_range: &Option<Range<usize>>,
|
||||
marked_run: &TextRun,
|
||||
) -> Vec<TextRun> {
|
||||
self.styles
|
||||
.iter()
|
||||
.map(|(range, style)| {
|
||||
let mut run = text_style.clone().highlight(*style).to_run(range.len());
|
||||
if let Some(marked_range) = marked_range {
|
||||
if self.offset + range.start >= marked_range.start
|
||||
&& self.offset + range.end <= marked_range.end
|
||||
{
|
||||
run.color = marked_run.color;
|
||||
run.strikethrough = marked_run.strikethrough;
|
||||
run.underline = marked_run.underline;
|
||||
}
|
||||
}
|
||||
run
|
||||
})
|
||||
// Add last `\n` Run with len 1
|
||||
.chain(std::iter::once(text_style.clone().to_run(1)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,15 @@ use std::{ops::Range, rc::Rc};
|
|||
|
||||
use gpui::{
|
||||
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
|
||||
Entity, GlobalElementId, IntoElement, LayoutId, MouseButton, MouseMoveEvent, Path, Pixels,
|
||||
Point, SharedString, Size, Style, TextAlign, TextRun, UnderlineStyle, Window, WrappedLine,
|
||||
Entity, GlobalElementId, HighlightStyle, IntoElement, LayoutId, MouseButton, MouseMoveEvent,
|
||||
Path, Pixels, Point, SharedString, Size, Style, TextAlign, TextRun, UnderlineStyle, Window,
|
||||
WrappedLine,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{highlighter::LanguageRegistry, ActiveTheme as _, Root};
|
||||
|
||||
use super::{code_highlighter::LineHighlightStyle, mode::InputMode, InputState, LastLayout};
|
||||
use super::{mode::InputMode, InputState, LastLayout};
|
||||
|
||||
const RIGHT_MARGIN: Pixels = px(5.);
|
||||
const BOTTOM_MARGIN_ROWS: usize = 1;
|
||||
|
|
@ -358,19 +359,25 @@ impl TextElement {
|
|||
&mut self,
|
||||
visible_range: &Range<usize>,
|
||||
cx: &mut App,
|
||||
) -> Option<(usize, Vec<LineHighlightStyle>)> {
|
||||
) -> Option<(usize, Vec<(Range<usize>, HighlightStyle)>)> {
|
||||
let theme = LanguageRegistry::global(cx)
|
||||
.theme(cx.theme().is_dark())
|
||||
.clone();
|
||||
self.input.update(cx, |state, _| match &mut state.mode {
|
||||
InputMode::CodeEditor { highlighter, .. } => {
|
||||
self.input.update(cx, |state, _| match &state.mode {
|
||||
InputMode::CodeEditor {
|
||||
highlighter,
|
||||
markers,
|
||||
..
|
||||
} => {
|
||||
let mut offset = 0;
|
||||
let mut skipped_offset = 0;
|
||||
let mut lines = vec![];
|
||||
let mut styles = vec![];
|
||||
|
||||
for (ix, line) in state.text.split('\n').enumerate() {
|
||||
// +1 for last `\n`.
|
||||
let line_len = line.len() + 1;
|
||||
if ix < visible_range.start {
|
||||
offset += line.len() + 1;
|
||||
offset += line_len;
|
||||
skipped_offset = offset;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -378,16 +385,34 @@ impl TextElement {
|
|||
break;
|
||||
}
|
||||
|
||||
let range = offset..offset + line.len();
|
||||
let styles = highlighter.borrow().styles(&range, &theme);
|
||||
let range = offset..offset + line_len;
|
||||
let line_styles = highlighter.borrow().styles(&range, &theme);
|
||||
|
||||
lines.push(LineHighlightStyle {
|
||||
offset,
|
||||
styles: Rc::new(styles),
|
||||
});
|
||||
offset += line.len() + 1;
|
||||
styles = gpui::combine_highlights(styles, line_styles).collect();
|
||||
|
||||
offset = range.end;
|
||||
}
|
||||
Some((skipped_offset, lines))
|
||||
|
||||
let mut marker_styles = vec![];
|
||||
for marker in markers.iter() {
|
||||
if let Some(range) = marker.byte_range(&state) {
|
||||
if range.start < skipped_offset {
|
||||
continue;
|
||||
}
|
||||
|
||||
let node_range = range.start..range.end;
|
||||
if node_range.start >= visible_range.start
|
||||
|| node_range.end <= visible_range.end
|
||||
{
|
||||
marker_styles
|
||||
.push((node_range, marker.severity.highlight_style(&theme)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
styles = gpui::combine_highlights(marker_styles, styles).collect();
|
||||
|
||||
Some((skipped_offset, styles))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
|
@ -502,7 +527,7 @@ impl Element for TextElement {
|
|||
let line_height = window.line_height();
|
||||
|
||||
let visible_range = self.calculate_visible_range(&state, line_height, &bounds);
|
||||
let highlight_lines = self.highlight_lines(&visible_range, cx);
|
||||
let highlight_styles = self.highlight_lines(&visible_range, cx);
|
||||
|
||||
let multi_line = self.input.read(cx).is_multi_line();
|
||||
let input = self.input.read(cx);
|
||||
|
|
@ -572,7 +597,7 @@ impl Element for TextElement {
|
|||
};
|
||||
|
||||
let runs = if !is_empty {
|
||||
if let Some((skipped_offset, highlight_lines)) = highlight_lines {
|
||||
if let Some((skipped_offset, highlight_styles)) = highlight_styles {
|
||||
let mut runs = vec![];
|
||||
if skipped_offset > 0 {
|
||||
runs.push(TextRun {
|
||||
|
|
@ -581,9 +606,19 @@ impl Element for TextElement {
|
|||
});
|
||||
}
|
||||
|
||||
for style in highlight_lines {
|
||||
runs.extend(style.to_run(&text_style, &input.marked_range, &marked_run));
|
||||
}
|
||||
runs.extend(highlight_styles.iter().map(|(range, style)| {
|
||||
let mut run = text_style.clone().highlight(*style).to_run(range.len());
|
||||
if let Some(marked_range) = &input.marked_range {
|
||||
if range.start >= marked_range.start && range.end <= marked_range.end {
|
||||
run.color = marked_run.color;
|
||||
run.strikethrough = marked_run.strikethrough;
|
||||
run.underline = marked_run.underline;
|
||||
}
|
||||
}
|
||||
|
||||
run
|
||||
}));
|
||||
|
||||
runs.into_iter().filter(|run| run.len > 0).collect()
|
||||
} else {
|
||||
vec![run]
|
||||
|
|
|
|||
125
crates/ui/src/input/marker.rs
Normal file
125
crates/ui/src/input/marker.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
use crate::{highlighter::HighlightTheme, input::InputState};
|
||||
use gpui::{px, HighlightStyle, SharedString, UnderlineStyle};
|
||||
use itertools::Itertools;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Marker represents a diagnostic message, such as an error or warning, in the code editor.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Marker {
|
||||
pub severity: MarkerSeverity,
|
||||
pub start: LineColumn,
|
||||
pub end: LineColumn,
|
||||
/// The message associated with the marker, typically a description of the issue.
|
||||
pub message: SharedString,
|
||||
}
|
||||
|
||||
impl Marker {
|
||||
/// Creates a new marker with the specified severity, start and end positions, and message.
|
||||
pub fn new(
|
||||
severity: impl Into<MarkerSeverity>,
|
||||
start: impl Into<LineColumn>,
|
||||
end: impl Into<LineColumn>,
|
||||
message: impl Into<SharedString>,
|
||||
) -> Self {
|
||||
Self {
|
||||
severity: severity.into(),
|
||||
start: start.into(),
|
||||
end: end.into(),
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the range (zero-based) of bytes in the source code that this marker covers.
|
||||
pub(super) fn byte_range(&self, state: &InputState) -> Option<Range<usize>> {
|
||||
let start_line = state
|
||||
.text_wrapper
|
||||
.lines
|
||||
.get(self.start.line.saturating_sub(1))?;
|
||||
let start_line_str = state.text.get(start_line.range.clone())?;
|
||||
|
||||
let end_line = state
|
||||
.text_wrapper
|
||||
.lines
|
||||
.get(self.end.line.saturating_sub(1))?;
|
||||
let end_line_str = state.text.get(end_line.range.clone())?;
|
||||
|
||||
let start_byte = start_line.range.start
|
||||
+ start_line_str
|
||||
.chars()
|
||||
.take(self.start.column.saturating_sub(1))
|
||||
.counts_by(|c| c.len_utf8())
|
||||
.values()
|
||||
.sum::<usize>();
|
||||
let end_byte = end_line.range.start
|
||||
+ end_line_str
|
||||
.chars()
|
||||
.take(self.end.column.saturating_sub(1))
|
||||
.counts_by(|c| c.len_utf8())
|
||||
.values()
|
||||
.sum::<usize>();
|
||||
|
||||
Some(start_byte..end_byte)
|
||||
}
|
||||
}
|
||||
|
||||
/// Line and column position (1-based) in the source code.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct LineColumn {
|
||||
/// Line number (1-based)
|
||||
pub line: usize,
|
||||
/// Column number (1-based)
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
impl From<(usize, usize)> for LineColumn {
|
||||
fn from(value: (usize, usize)) -> Self {
|
||||
Self {
|
||||
line: value.0.max(1),
|
||||
column: value.1.max(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Severity of the marker.
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum MarkerSeverity {
|
||||
#[default]
|
||||
Hint,
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
}
|
||||
|
||||
impl From<&str> for MarkerSeverity {
|
||||
fn from(value: &str) -> Self {
|
||||
match value {
|
||||
"error" => Self::Error,
|
||||
"warning" => Self::Warning,
|
||||
"info" => Self::Info,
|
||||
"hint" => Self::Hint,
|
||||
_ => Self::Info, // Default to Info if unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MarkerSeverity {
|
||||
/// Returns the [`HighlightStyle`] for the marker severity with the given theme style.
|
||||
pub(super) fn highlight_style(&self, theme: &HighlightTheme) -> HighlightStyle {
|
||||
let color = match self {
|
||||
Self::Error => Some(theme.style.status.error()),
|
||||
Self::Warning => Some(theme.style.status.warning()),
|
||||
Self::Info => Some(theme.style.status.info()),
|
||||
Self::Hint => Some(theme.style.status.hint()),
|
||||
};
|
||||
|
||||
let mut style = HighlightStyle::default();
|
||||
style.underline = Some(UnderlineStyle {
|
||||
color: color,
|
||||
thickness: px(1.),
|
||||
wavy: true,
|
||||
});
|
||||
|
||||
style
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
mod blink_cursor;
|
||||
mod change;
|
||||
mod clear_button;
|
||||
mod code_highlighter;
|
||||
mod element;
|
||||
mod marker;
|
||||
mod mask_pattern;
|
||||
mod mode;
|
||||
mod number_input;
|
||||
|
|
@ -12,6 +12,7 @@ mod text_input;
|
|||
mod text_wrapper;
|
||||
|
||||
pub(crate) use clear_button::*;
|
||||
pub use marker::*;
|
||||
pub use mask_pattern::MaskPattern;
|
||||
pub use mode::TabSize;
|
||||
pub use number_input::{NumberInput, NumberInputEvent, StepAction};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::rc::Rc;
|
|||
|
||||
use gpui::{DefiniteLength, SharedString};
|
||||
|
||||
use crate::highlighter::SyntaxHighlighter;
|
||||
use crate::{highlighter::SyntaxHighlighter, input::marker::Marker};
|
||||
|
||||
use super::text_wrapper::TextWrapper;
|
||||
|
||||
|
|
@ -50,6 +50,7 @@ pub enum InputMode {
|
|||
/// Show line number
|
||||
line_number: bool,
|
||||
highlighter: Rc<RefCell<SyntaxHighlighter>>,
|
||||
markers: Vec<Marker>,
|
||||
},
|
||||
AutoGrow {
|
||||
rows: usize,
|
||||
|
|
@ -160,6 +161,21 @@ impl InputMode {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(super) fn markers(&self) -> Option<&Vec<Marker>> {
|
||||
match &self {
|
||||
InputMode::CodeEditor { markers, .. } => Some(markers),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clear_markers(&mut self) {
|
||||
match self {
|
||||
InputMode::CodeEditor { markers, .. } => markers.clear(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ use super::{
|
|||
text_wrapper::TextWrapper,
|
||||
};
|
||||
use crate::highlighter::SyntaxHighlighter;
|
||||
use crate::input::marker::Marker;
|
||||
use crate::{history::History, scroll::ScrollbarState, Root};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||
|
|
@ -381,6 +382,7 @@ impl InputState {
|
|||
highlighter: Rc::new(RefCell::new(SyntaxHighlighter::new(&language))),
|
||||
line_number: true,
|
||||
height: Some(relative(1.)),
|
||||
markers: vec![],
|
||||
};
|
||||
self
|
||||
}
|
||||
|
|
@ -451,6 +453,21 @@ impl InputState {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set markers, only for [`InputMode::CodeEditor`] mode.
|
||||
///
|
||||
/// For example to set the diagnostic markers in the code editor.
|
||||
pub fn set_markers(
|
||||
&mut self,
|
||||
new_markers: Vec<Marker>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let InputMode::CodeEditor { markers, .. } = &mut self.mode {
|
||||
*markers = new_markers;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set placeholder
|
||||
pub fn set_placeholder(
|
||||
&mut self,
|
||||
|
|
@ -1986,6 +2003,7 @@ impl EntityInputHandler for InputState {
|
|||
.borrow_mut()
|
||||
.update(&range, self.text.clone(), &new_text, cx);
|
||||
}
|
||||
self.mode.clear_markers();
|
||||
self.text_wrapper.update(self.text.clone(), false, cx);
|
||||
self.selected_range = new_pos..new_pos;
|
||||
self.marked_range.take();
|
||||
|
|
@ -2029,6 +2047,7 @@ impl EntityInputHandler for InputState {
|
|||
.borrow_mut()
|
||||
.update(&range, self.text.clone(), &new_text, cx);
|
||||
}
|
||||
self.mode.clear_markers();
|
||||
self.text_wrapper.update(self.text.clone(), false, cx);
|
||||
if new_text.is_empty() {
|
||||
// Cancel selection, when cancel IME input.
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ impl TextWrapper {
|
|||
.line_wrapper(self.font.clone(), self.font_size);
|
||||
|
||||
let mut prev_line_ix = 0;
|
||||
for line in text.lines() {
|
||||
for line in text.split('\n') {
|
||||
let mut line_wraps = vec![];
|
||||
let mut prev_boundary_ix = 0;
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ impl TextWrapper {
|
|||
|
||||
lines.push(LineWrap {
|
||||
wrap_lines: line_wraps.len(),
|
||||
range: prev_line_ix..line.len(),
|
||||
range: prev_line_ix..prev_line_ix + line.len(),
|
||||
});
|
||||
|
||||
wrapped_lines.extend(line_wraps);
|
||||
|
|
@ -92,7 +92,7 @@ impl TextWrapper {
|
|||
wrapped_lines.push(prev_line_ix + prev_boundary_ix..prev_line_ix + line.len());
|
||||
}
|
||||
|
||||
prev_line_ix += line.len();
|
||||
prev_line_ix += line.len() + 1;
|
||||
}
|
||||
|
||||
// Add last empty line.
|
||||
|
|
|
|||
Loading…
Reference in a new issue