editor: Add document_colors LSP. (#1329)
This commit is contained in:
parent
efe7e14e88
commit
d0396d779e
11 changed files with 507 additions and 109 deletions
199
Cargo.lock
generated
199
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ pub enum HelloError {
|
|||
Timeout,
|
||||
}
|
||||
|
||||
// Document colors: #FF0033, #00AA33, #0033FF, #FFAA33
|
||||
|
||||
type Result<T> = std::result::Result<T, HelloError>;
|
||||
|
||||
impl HelloWorld {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
90
crates/ui/src/input/lsp/document_colors.rs
Normal file
90
crates/ui/src/input/lsp/document_colors.rs
Normal 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(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue