editor: Add document_colors LSP. (#1329)

This commit is contained in:
Jason Lee 2025-10-06 18:08:12 +08:00 committed by GitHub
parent efe7e14e88
commit d0396d779e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 507 additions and 109 deletions

199
Cargo.lock generated
View file

@ -484,6 +484,17 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "auto_impl"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.105",
]
[[package]]
name = "autocfg"
version = "1.5.0"
@ -1159,6 +1170,18 @@ dependencies = [
"workspace-hack",
]
[[package]]
name = "color-lsp"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d4faf31b0bd306514c4ab7078cd992d611a79a5c8aad7a6e6538571f80f7eed"
dependencies = [
"csscolorparser",
"indoc",
"tokio",
"tower-lsp",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@ -1466,6 +1489,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "csscolorparser"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fda6aace1fbef3aa217b27f4c8d7d071ef2a70a5ca51050b1f17d40299d3f16"
dependencies = [
"phf 0.11.3",
]
[[package]]
name = "cssparser"
version = "0.29.6"
@ -1543,6 +1575,19 @@ dependencies = [
"syn 2.0.105",
]
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "data-url"
version = "0.3.1"
@ -2813,7 +2858,7 @@ dependencies = [
"html5ever 0.27.0",
"indoc",
"itertools 0.13.0",
"lsp-types",
"lsp-types 0.97.0",
"markdown",
"markup5ever_rcdom",
"notify",
@ -3249,7 +3294,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.58.0",
"windows-core 0.61.2",
]
[[package]]
@ -3874,6 +3919,19 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "lsp-types"
version = "0.94.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1"
dependencies = [
"bitflags 1.3.2",
"serde",
"serde_json",
"serde_repr",
"url",
]
[[package]]
name = "lsp-types"
version = "0.97.0"
@ -4456,7 +4514,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro-crate 3.3.0",
"proc-macro2",
"quote",
"syn 2.0.105",
@ -4870,7 +4928,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_macros 0.8.0",
"phf_shared 0.8.0",
"proc-macro-hack",
]
@ -4881,6 +4939,7 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros 0.11.3",
"phf_shared 0.11.3",
]
@ -4938,6 +4997,19 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
"proc-macro2",
"quote",
"syn 2.0.105",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
@ -5729,7 +5801,7 @@ dependencies = [
"tokio-rustls",
"tokio-socks",
"tokio-util",
"tower",
"tower 0.5.2",
"tower-service",
"url",
"wasm-bindgen",
@ -6744,12 +6816,13 @@ dependencies = [
"anyhow",
"autocorrect",
"chrono",
"color-lsp",
"fake",
"gpui",
"gpui-component",
"gtk",
"itertools 0.14.0",
"lsp-types",
"lsp-types 0.97.0",
"rand 0.8.5",
"raw-window-handle",
"regex",
@ -7325,9 +7398,21 @@ dependencies = [
"pin-project-lite",
"slab",
"socket2 0.6.0",
"tokio-macros",
"windows-sys 0.59.0",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.105",
]
[[package]]
name = "tokio-rustls"
version = "0.26.2"
@ -7465,6 +7550,20 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower"
version = "0.5.2"
@ -7486,6 +7585,40 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-lsp"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508"
dependencies = [
"async-trait",
"auto_impl",
"bytes",
"dashmap",
"futures",
"httparse",
"lsp-types 0.94.1",
"memchr",
"serde",
"serde_json",
"tokio",
"tokio-util",
"tower 0.4.13",
"tower-lsp-macros",
"tracing",
]
[[package]]
name = "tower-lsp-macros"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.105",
]
[[package]]
name = "tower-service"
version = "0.3.3"
@ -8676,19 +8809,6 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [
"windows-implement 0.58.0",
"windows-interface 0.58.0",
"windows-result 0.2.0",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.61.2"
@ -8724,17 +8844,6 @@ dependencies = [
"syn 2.0.105",
]
[[package]]
name = "windows-implement"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.105",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
@ -8757,17 +8866,6 @@ dependencies = [
"syn 2.0.105",
]
[[package]]
name = "windows-interface"
version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.105",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
@ -8826,15 +8924,6 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.3.4"
@ -8844,16 +8933,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result 0.2.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.3.1"

View file

@ -26,6 +26,7 @@ tree-sitter-navi = "0.2.2"
itertools = "0.14.0"
lsp-types.workspace = true
autocorrect = "2.14.2"
color-lsp = "0.2.0"
[target.'cfg(target_os = "linux")'.dependencies]
gtk = { version = "0.18" }

View file

@ -14,8 +14,8 @@ use gpui_component::{
h_flex,
highlighter::{Diagnostic, DiagnosticSeverity, Language, LanguageConfig, LanguageRegistry},
input::{
self, CodeActionProvider, CompletionProvider, DefinitionProvider, HoverProvider,
InputEvent, InputState, Position, Rope, RopeExt, TabSize, TextInput,
self, CodeActionProvider, CompletionProvider, DefinitionProvider, DocumentColorProvider,
HoverProvider, InputEvent, InputState, Position, Rope, RopeExt, TabSize, TextInput,
},
v_flex, ActiveTheme, ContextModal, IconName, IndexPath, Selectable, Sizable,
};
@ -619,6 +619,39 @@ impl CodeActionProvider for TextConvertor {
}
}
impl DocumentColorProvider for ExampleLspStore {
fn document_colors(
&self,
text: &Rope,
_window: &mut Window,
_cx: &mut App,
) -> Task<gpui::Result<Vec<lsp_types::ColorInformation>>> {
let nodes = color_lsp::parse(&text.to_string());
let colors = nodes
.into_iter()
.map(|node| {
let start = lsp_types::Position::new(node.position.line, node.position.character);
let end = lsp_types::Position::new(
node.position.line,
node.position.character + node.matched.chars().count() as u32,
);
lsp_types::ColorInformation {
range: lsp_types::Range { start, end },
color: lsp_types::Color {
red: node.color.r,
green: node.color.g,
blue: node.color.b,
alpha: node.color.a,
},
}
})
.collect::<Vec<_>>();
Task::ready(Ok(colors))
}
}
impl Example {
pub fn new(default: Option<String>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let default_language = if let Some(name) = default {
@ -650,6 +683,7 @@ impl Example {
editor.lsp.code_action_providers = vec![lsp_store.clone(), Rc::new(TextConvertor)];
editor.lsp.hover_provider = Some(lsp_store.clone());
editor.lsp.definition_provider = Some(lsp_store.clone());
editor.lsp.document_color_provider = Some(lsp_store.clone());
editor
});

View file

@ -5,6 +5,10 @@
"license": "Apache-2.0",
"keywords": ["UI", "desktop", "application"],
"stars": 3000,
"rgb": "rgb(100, 200, 100)",
"hsla": "hsla(20, 100%, 50%, .5)",
"hsl": "hsl(225, 100%, 70%)",
"中文": "#EEAAFF",
"public": true,
"repository": "https://github.com/longbridge/gpui-component"
}

View file

@ -30,6 +30,8 @@ pub enum HelloError {
Timeout,
}
// Document colors: #FF0033, #00AA33, #0033FF, #FFAA33
type Result<T> = std::result::Result<T, HelloError>;
impl HelloWorld {

View file

@ -2,19 +2,15 @@ use std::{ops::Range, rc::Rc};
use gpui::{
fill, point, px, relative, size, App, Bounds, Corners, Element, ElementId, ElementInputHandler,
Entity, GlobalElementId, Half, HighlightStyle, Hitbox, IntoElement, LayoutId, MouseButton,
MouseMoveEvent, Path, Pixels, Point, ShapedLine, SharedString, Size, Style, TextRun, TextStyle,
UnderlineStyle, Window,
Entity, GlobalElementId, Half, HighlightStyle, Hitbox, Hsla, IntoElement, LayoutId,
MouseButton, MouseMoveEvent, Path, Pixels, Point, ShapedLine, SharedString, Size, Style,
TextRun, TextStyle, UnderlineStyle, Window,
};
use ropey::Rope;
use smallvec::SmallVec;
use crate::{
input::{
blink_cursor::CURSOR_WIDTH,
text_wrapper::{LineLayout, TextWrapper},
RopeExt as _,
},
input::{blink_cursor::CURSOR_WIDTH, text_wrapper::LineLayout, RopeExt as _},
ActiveTheme as _, Colorize, PixelsExt, Root,
};
@ -241,7 +237,7 @@ impl TextElement {
pub(crate) fn layout_match_range(
range: Range<usize>,
last_layout: &LastLayout,
bounds: &mut Bounds<Pixels>,
bounds: &Bounds<Pixels>,
) -> Option<Path<Pixels>> {
if range.is_empty() {
return None;
@ -374,7 +370,7 @@ impl TextElement {
fn layout_search_matches(
&self,
last_layout: &LastLayout,
bounds: &mut Bounds<Pixels>,
bounds: &Bounds<Pixels>,
cx: &mut App,
) -> Vec<(Path<Pixels>, bool)> {
let search_panel = self.state.read(cx).search_panel.clone();
@ -401,7 +397,7 @@ impl TextElement {
fn layout_hover_highlight(
&self,
last_layout: &LastLayout,
bounds: &mut Bounds<Pixels>,
bounds: &Bounds<Pixels>,
cx: &mut App,
) -> Option<Path<Pixels>> {
let hover_popover = self.state.read(cx).hover_popover.clone();
@ -413,6 +409,22 @@ impl TextElement {
Self::layout_match_range(symbol_range, last_layout, bounds)
}
fn layout_document_colors(
&self,
document_colors: &[(Range<usize>, Hsla)],
last_layout: &LastLayout,
bounds: &Bounds<Pixels>,
) -> Vec<(Path<Pixels>, Hsla)> {
let mut paths = vec![];
for (range, color) in document_colors.iter() {
if let Some(path) = Self::layout_match_range(range.clone(), last_layout, bounds) {
paths.push((path, *color));
}
}
paths
}
fn layout_selections(
&self,
last_layout: &LastLayout,
@ -530,13 +542,16 @@ impl TextElement {
fn layout_lines(
state: &InputState,
display_text: &Rope,
text_wrapper: &TextWrapper,
visible_range: &Range<usize>,
last_layout: &LastLayout,
font_size: Pixels,
runs: &[TextRun],
bg_segments: &[(Range<usize>, Hsla)],
window: &mut Window,
) -> Vec<LineLayout> {
let is_multi_line = state.mode.is_multi_line();
let text_wrapper = &state.text_wrapper;
let visible_range = &last_layout.visible_range;
let visible_range_offset = &last_layout.visible_range_offset;
if !is_multi_line {
let shaped_line = window.text_system().shape_line(
@ -585,6 +600,16 @@ impl TextElement {
for range in &line_item.wrapped_lines {
let line_runs = runs_for_range(runs, offset, &range);
let line_runs = if bg_segments.is_empty() {
line_runs
} else {
split_runs_by_bg_segments(
visible_range_offset.start + offset,
&line_runs,
bg_segments,
)
};
let sub_line: SharedString = line[range.clone()].to_string().into();
let shaped_line = window
.text_system()
@ -671,6 +696,7 @@ pub(super) struct PrepaintState {
selection_path: Option<Path<Pixels>>,
hover_highlight_path: Option<Path<Pixels>>,
search_match_paths: Vec<(Path<Pixels>, bool)>,
document_color_paths: Vec<(Path<Pixels>, Hsla)>,
hover_definition_hitbox: Option<Hitbox>,
bounds: Bounds<Pixels>,
}
@ -812,6 +838,23 @@ impl Element for TextElement {
let (line_number_width, line_number_len) =
Self::layout_line_numbers(&state, &text, font_size, &text_style, window);
let wrap_width = if multi_line && state.soft_wrap {
Some(bounds.size.width - line_number_width - RIGHT_MARGIN)
} else {
None
};
let mut last_layout = LastLayout {
visible_range,
visible_top,
visible_range_offset: visible_start_offset..visible_end_offset,
line_height,
wrap_width,
line_number_width,
lines: Rc::new(vec![]),
cursor_bounds: None,
};
let run = TextRun {
len: display_text.len(),
font: style.font(),
@ -880,19 +923,16 @@ impl Element for TextElement {
vec![run]
};
let wrap_width = if multi_line && state.soft_wrap {
Some(bounds.size.width - line_number_width - RIGHT_MARGIN)
} else {
None
};
let document_colors = state
.lsp
.document_colors_for_range(&text, &last_layout.visible_range);
let lines = Self::layout_lines(
&state,
&display_text,
&state.text_wrapper,
&visible_range,
&last_layout,
font_size,
&runs,
&document_colors,
window,
);
@ -917,6 +957,7 @@ impl Element for TextElement {
)
.width;
}
last_layout.lines = Rc::new(lines);
let total_wrapped_lines = state.text_wrapper.len();
let empty_bottom_height = bounds
@ -934,17 +975,6 @@ impl Element for TextElement {
.max(bounds.size.height),
);
let mut last_layout = LastLayout {
visible_range,
visible_top,
visible_range_offset: visible_start_offset..visible_end_offset,
line_height,
wrap_width,
line_number_width,
lines: Rc::new(lines),
cursor_bounds: None,
};
// `position_for_index` for example
//
// #### text
@ -983,6 +1013,8 @@ impl Element for TextElement {
let search_match_paths = self.layout_search_matches(&last_layout, &mut bounds, cx);
let selection_path = self.layout_selections(&last_layout, &mut bounds, cx);
let hover_highlight_path = self.layout_hover_highlight(&last_layout, &mut bounds, cx);
let document_color_paths =
self.layout_document_colors(&document_colors, &last_layout, &bounds);
let state = self.state.read(cx);
let line_numbers = if state.mode.line_number() {
@ -1045,6 +1077,7 @@ impl Element for TextElement {
search_match_paths,
hover_highlight_path,
hover_definition_hitbox,
document_color_paths,
}
}
@ -1158,6 +1191,11 @@ impl Element for TextElement {
}
}
// Paint document colors
for (path, color) in prepaint.document_color_paths.iter() {
window.paint_path(path.clone(), *color);
}
// Paint text
let mut offset_y = mask_offset_y + invisible_top_padding;
for line in prepaint.last_layout.lines.iter() {
@ -1191,11 +1229,7 @@ impl Element for TextElement {
input_bounds.size.height,
),
},
cx.theme()
.highlight_theme
.style
.editor_background
.unwrap_or(cx.theme().background),
cx.theme().editor_background(),
));
// Each item is the normal lines.
@ -1285,6 +1319,68 @@ pub(super) fn runs_for_range(
result
}
fn split_runs_by_bg_segments(
start_offset: usize,
runs: &[TextRun],
bg_segments: &[(Range<usize>, Hsla)],
) -> Vec<TextRun> {
let mut result = vec![];
let mut cursor = start_offset;
for run in runs {
let mut run_start = cursor;
let run_end = cursor + run.len;
for (bg_range, bg_color) in bg_segments {
if run_end <= bg_range.start || run_start >= bg_range.end {
continue;
}
// Overlap exists
if run_start < bg_range.start {
// Add the part before the background range
result.push(TextRun {
len: bg_range.start - run_start,
..run.clone()
});
}
// Add the overlapping part with background color
let overlap_start = run_start.max(bg_range.start);
let overlap_end = run_end.min(bg_range.end);
let text_color = if bg_color.l >= 0.5 {
gpui::black()
} else {
gpui::white()
};
let run_len = overlap_end.saturating_sub(overlap_start);
if run_len > 0 {
result.push(TextRun {
len: run_len,
color: text_color,
..run.clone()
});
cursor = bg_range.end;
run_start = cursor;
}
}
if run_end > cursor {
// Add the part after the background range
result.push(TextRun {
len: run_end - cursor,
..run.clone()
});
}
cursor = run_end;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
@ -1346,4 +1442,44 @@ mod tests {
assert_runs(runs_for_range(&runs, 3, &(2..10)), &[4, 1, 3]);
assert_runs(runs_for_range(&runs, 9, &(0..8)), &[1, 7]);
}
#[test]
fn test_split_runs_by_bg_segments() {
let run = TextRun {
len: 0,
font: gpui::font(".SystemUIFont"),
color: gpui::blue(),
background_color: None,
underline: None,
strikethrough: None,
};
let runs = vec![
TextRun {
len: 5,
..run.clone()
},
TextRun {
len: 7,
..run.clone()
},
TextRun {
len: 24,
..run.clone()
},
];
let bg_segments = vec![(8..12, gpui::red()), (12..18, gpui::blue())];
let result = split_runs_by_bg_segments(5, &runs, &bg_segments);
assert_eq!(
result.iter().map(|run| run.len).collect::<Vec<_>>(),
vec![3, 2, 2, 5, 1, 23]
);
assert_eq!(result[0].color, gpui::blue());
assert_eq!(result[1].color, gpui::black());
assert_eq!(result[2].color, gpui::black());
assert_eq!(result[3].color, gpui::black());
assert_eq!(result[4].color, gpui::black());
assert_eq!(result[5].color, gpui::blue());
}
}

View file

@ -0,0 +1,90 @@
use std::ops::Range;
use anyhow::Result;
use gpui::{App, Context, Hsla, Task, Window};
use lsp_types::ColorInformation;
use ropey::Rope;
use crate::input::{InputState, Lsp, RopeExt};
pub trait DocumentColorProvider {
/// Fetches document colors for the specified range.
///
/// textDocument/documentColor
///
/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentColor
fn document_colors(
&self,
_text: &Rope,
window: &mut Window,
cx: &mut App,
) -> Task<Result<Vec<ColorInformation>>>;
}
impl Lsp {
/// Get document colors that intersect with the visible range (0-based row).
///
/// Returns byte ranges and colors.
pub(crate) fn document_colors_for_range(
&self,
text: &Rope,
visible_range: &Range<usize>,
) -> Vec<(Range<usize>, Hsla)> {
self.document_colors
.iter()
.filter_map(|(range, color)| {
if (range.start.line as usize) > visible_range.end
|| (range.end.line as usize) < visible_range.start
{
return None;
}
let start = text.position_to_offset(&range.start);
let end = text.position_to_offset(&range.end);
Some((start..end, *color))
})
.collect()
}
pub(crate) fn update_document_colors(
&mut self,
text: &Rope,
window: &mut Window,
cx: &mut Context<InputState>,
) {
let Some(provider) = self.document_color_provider.as_ref() else {
return;
};
let task = provider.document_colors(text, window, cx);
self._hover_task = cx.spawn_in(window, async move |editor, cx| {
let colors = task.await?;
editor.update(cx, |editor, cx| {
let mut document_colors: Vec<(lsp_types::Range, Hsla)> = colors
.iter()
.map(|info| {
let color = gpui::Rgba {
r: info.color.red,
g: info.color.green,
b: info.color.blue,
a: info.color.alpha,
}
.into();
(info.range, color)
})
.collect();
document_colors.sort_by_key(|(range, _)| range.start);
if document_colors == editor.lsp.document_colors {
return;
}
editor.lsp.document_colors = document_colors;
cx.notify();
})?;
Ok(())
});
}
}

View file

@ -1,5 +1,6 @@
use anyhow::Result;
use gpui::{App, Context, MouseMoveEvent, Task, Window};
use gpui::{App, Context, Hsla, MouseMoveEvent, Task, Window};
use ropey::Rope;
use std::rc::Rc;
use crate::input::{popovers::ContextMenu, InputState, RopeExt};
@ -7,11 +8,13 @@ use crate::input::{popovers::ContextMenu, InputState, RopeExt};
mod code_actions;
mod completions;
mod definitions;
mod document_colors;
mod hover;
pub use code_actions::*;
pub use completions::*;
pub use definitions::*;
pub use document_colors::*;
pub use hover::*;
/// LSP ServerCapabilities
@ -26,7 +29,12 @@ pub struct Lsp {
pub hover_provider: Option<Rc<dyn HoverProvider>>,
/// The definition provider.
pub definition_provider: Option<Rc<dyn DefinitionProvider>>,
/// The document color provider.
pub document_color_provider: Option<Rc<dyn DocumentColorProvider>>,
document_colors: Vec<(lsp_types::Range, Hsla)>,
_hover_task: Task<Result<()>>,
_document_color_task: Task<Result<()>>,
}
impl Default for Lsp {
@ -36,11 +44,33 @@ impl Default for Lsp {
code_action_providers: vec![],
hover_provider: None,
definition_provider: None,
document_color_provider: None,
document_colors: vec![],
_hover_task: Task::ready(Ok(())),
_document_color_task: Task::ready(Ok(())),
}
}
}
impl Lsp {
/// Update the LSP when the text changes.
pub(crate) fn update(
&mut self,
text: &Rope,
window: &mut Window,
cx: &mut Context<InputState>,
) {
self.update_document_colors(text, window, cx);
}
/// Reset all LSP states.
pub(crate) fn reset(&mut self) {
self.document_colors.clear();
self._hover_task = Task::ready(Ok(()));
self._document_color_task = Task::ready(Ok(()));
}
}
impl InputState {
pub(crate) fn hide_context_menu(&mut self, cx: &mut Context<Self>) {
self.context_menu = None;

View file

@ -323,6 +323,10 @@ pub struct InputState {
pub lsp: Lsp,
/// A flag to indicate if we have a pending update to the text.
///
/// If true, will call some update (for example LSP, Syntax Highlight) before render.
_pending_update: bool,
/// A flag to indicate if we should ignore the next completion event.
pub(super) silent_replace_text: bool,
@ -415,6 +419,7 @@ impl InputState {
silent_replace_text: false,
_subscriptions,
_context_menu_task: Task::ready(Ok(())),
_pending_update: false,
}
}
@ -640,6 +645,9 @@ impl InputState {
self.selected_range = (self.text.len()..self.text.len()).into();
} else {
self.selected_range.clear();
self._pending_update = true;
self.lsp.reset();
}
// Move scroll to top
self.scroll_handle.set_offset(point(px(0.), px(0.)));
@ -799,6 +807,7 @@ impl InputState {
diagnostics.reset(&self.text)
}
self.text_wrapper.set_default_text(&self.text);
self._pending_update = true;
self
}
@ -2145,6 +2154,7 @@ impl EntityInputHandler for InputState {
.update(&self.text, &range, &Rope::from(new_text), cx);
self.mode
.update_highlighter(&range, &self.text, &new_text, true, cx);
self.lsp.update(&self.text, window, cx);
self.selected_range = (new_offset..new_offset).into();
self.ime_marked_range.take();
self.update_preferred_column();
@ -2163,13 +2173,15 @@ impl EntityInputHandler for InputState {
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range_utf16: Option<Range<usize>>,
_: &mut Window,
window: &mut Window,
cx: &mut Context<Self>,
) {
if self.disabled {
return;
}
self.lsp.reset();
let range = range_utf16
.as_ref()
.map(|range_utf16| self.range_from_utf16(range_utf16))
@ -2198,6 +2210,7 @@ impl EntityInputHandler for InputState {
.update(&self.text, &range, &Rope::from(new_text), cx);
self.mode
.update_highlighter(&range, &self.text, &new_text, true, cx);
self.lsp.update(&self.text, window, cx);
if new_text.is_empty() {
// Cancel selection, when cancel IME input.
self.selected_range = (range.start..range.start).into();
@ -2300,9 +2313,13 @@ impl Focusable for InputState {
}
impl Render for InputState {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
self.mode
.update_highlighter(&(0..0), &self.text, "", false, cx);
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self._pending_update {
self.mode
.update_highlighter(&(0..0), &self.text, "", false, cx);
self.lsp.update(&self.text, window, cx);
self._pending_update = false;
}
div()
.id("input-state")

View file

@ -249,11 +249,7 @@ impl RenderOnce for TextInput {
cx.theme().muted
} else {
if state.mode.is_code_editor() {
cx.theme()
.highlight_theme
.style
.editor_background
.unwrap_or(cx.theme().background)
cx.theme().editor_background()
} else {
cx.theme().background
}

View file

@ -168,6 +168,15 @@ impl Theme {
window.refresh();
}
}
/// Get the editor background color, if not set, use the theme background color.
#[inline]
pub(crate) fn editor_background(&self) -> Hsla {
self.highlight_theme
.style
.editor_background
.unwrap_or(self.background)
}
}
impl From<ThemeColor> for Theme {